46 lines
2.0 KiB
TypeScript
46 lines
2.0 KiB
TypeScript
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
|
|
}
|