import { randomBytes } from 'node:crypto'; const MARKER_PREFIX = '__MAESTRO_DONE_'; export function makeNonce(): string { return randomBytes(16).toString('hex'); } export function makeMarkerCommand(command: string, nonce: string): { text: string; marker: string } { const marker = `${MARKER_PREFIX}${nonce}:`; // `$?` after `command;` reflects the command's exit code. return { text: `${command}; echo "${marker}$?__"\n`, marker }; } export function parseMarker(text: string, nonce: string): { found: boolean; exitCode: number | null } { const re = new RegExp(`${MARKER_PREFIX}${nonce}:(\\d+)__`); const m = re.exec(text); if (!m) return { found: false, exitCode: null }; return { found: true, exitCode: Number(m[1]) }; } // Best-effort: drop the echoed command line and the marker line(s). PTY echo / // prompt noise may remain — perfect formatting is a non-goal. export function extractOutput(raw: string, command: string, nonce: string): string { const markerRe = new RegExp(`${MARKER_PREFIX}${nonce}:.*?__`); return raw .split('\n') .filter((line) => !markerRe.test(line)) .filter((line) => line.trim() !== `${command}; echo "${MARKER_PREFIX}${nonce}:$?__"`) .join('\n'); } const WAITING_RE = /(password:|\[y\/n\]|\(yes\/no\)\??|continue\?|press .*key|passphrase:)\s*$/i; export function detectWaitingForInput(screenTail: string): boolean { const lastLines = screenTail.split('\n').slice(-3).join('\n').trim(); return WAITING_RE.test(lastLines); } export interface InterruptLiveness { idleMs: number; msSinceAiInput: number; sessionAgeMs: number; } const GUARD_IDLE_MS = 3000, GUARD_RECENT_INPUT_MS = 5000, GUARD_SESSION_AGE_MS = 5000; export function shouldGuardInterrupt(l: InterruptLiveness, confirmInterrupt: boolean): boolean { if (confirmInterrupt) return false; const alive = l.idleMs < GUARD_IDLE_MS || l.msSinceAiInput < GUARD_RECENT_INPUT_MS || l.sessionAgeMs < GUARD_SESSION_AGE_MS; return alive; // true => block the interrupt }