/** * Guard against opencode's post-abort "orphan" terminal event (F.1). * * When a turn is aborted (`client.session.abort`), opencode emits one trailing * `session.idle` / `session.error` for the cancelled turn. Without a guard that * orphan settles whatever turn currently holds the session slot — which, after * the user immediately sends another message, is the NEXT turn, settling it early * as success (the v2.6.5 Stop-button bug). opencode terminal events carry only a * `sessionID` (no turn id), so we can't match by id; instead we swallow exactly * one terminal per abort, and self-heal if that orphan never arrives. */ export interface AbortTerminalGuard { /** True between an abort and the orphan terminal event that follows it. */ swallowNextTerminal: boolean; } /** Arm on abort: the next terminal event for this session is the orphan. */ export function armAbortGuard(g: AbortTerminalGuard): void { g.swallowNextTerminal = true; } /** * A new turn produced activity (delta) → the orphan window is over. Self-heals * the case where opencode emits no orphan idle (e.g. abort-before-prompt), so a * real terminal still settles instead of being swallowed forever. */ export function noteTurnActivity(g: AbortTerminalGuard): void { g.swallowNextTerminal = false; } /** Decide a terminal (idle/error): swallow the post-abort orphan once, else settle. */ export function consumeTerminal(g: AbortTerminalGuard): 'swallow' | 'settle' { if (g.swallowNextTerminal) { g.swallowNextTerminal = false; return 'swallow'; } return 'settle'; }