singularity-forge/src/headless-events.ts
TÂCHES 87cd612542 refactor(headless): split 772-line god file into events, UI, and context modules (#1047)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 18:36:20 -06:00

65 lines
2.8 KiB
TypeScript

/**
* Headless Event Detection — notification classification and command detection
*
* Detects terminal notifications, blocked notifications, milestone-ready signals,
* and classifies commands as quick (single-turn) vs long-running.
*/
// ---------------------------------------------------------------------------
// Completion Detection
// ---------------------------------------------------------------------------
/**
* Detect genuine auto-mode termination notifications.
*
* Only matches the actual stop signals emitted by stopAuto():
* "Auto-mode stopped..."
* "Step-mode stopped..."
*
* Does NOT match progress notifications that happen to contain words like
* "complete" or "stopped" (e.g., "Override resolved — rewrite-docs completed",
* "All slices are complete — nothing to discuss", "Skipped 5+ completed units").
*
* Blocked detection is separate — checked via isBlockedNotification.
*/
export const TERMINAL_PREFIXES = ['auto-mode stopped', 'step-mode stopped']
export const IDLE_TIMEOUT_MS = 15_000
// new-milestone is a long-running creative task where the LLM may pause
// between tool calls (e.g. after mkdir, before writing files). Use a
// longer idle timeout to avoid killing the session prematurely (#808).
export const NEW_MILESTONE_IDLE_TIMEOUT_MS = 120_000
export function isTerminalNotification(event: Record<string, unknown>): boolean {
if (event.type !== 'extension_ui_request' || event.method !== 'notify') return false
const message = String(event.message ?? '').toLowerCase()
return TERMINAL_PREFIXES.some((prefix) => message.startsWith(prefix))
}
export function isBlockedNotification(event: Record<string, unknown>): boolean {
if (event.type !== 'extension_ui_request' || event.method !== 'notify') return false
const message = String(event.message ?? '').toLowerCase()
// Blocked notifications come through stopAuto as "Auto-mode stopped (Blocked: ...)"
return message.includes('blocked:')
}
export function isMilestoneReadyNotification(event: Record<string, unknown>): boolean {
if (event.type !== 'extension_ui_request' || event.method !== 'notify') return false
return /milestone\s+m\d+.*ready/i.test(String(event.message ?? ''))
}
// ---------------------------------------------------------------------------
// Quick Command Detection
// ---------------------------------------------------------------------------
export const FIRE_AND_FORGET_METHODS = new Set(['notify', 'setStatus', 'setWidget', 'setTitle', 'set_editor_text'])
export const QUICK_COMMANDS = new Set([
'status', 'queue', 'history', 'hooks', 'export', 'stop', 'pause',
'capture', 'skip', 'undo', 'knowledge', 'config', 'prefs',
'cleanup', 'migrate', 'doctor', 'remote', 'help', 'steer',
'triage', 'visualize',
])
export function isQuickCommand(command: string): boolean {
return QUICK_COMMANDS.has(command)
}