import { type ActivityWatchdogDiagnostic } from "./activity-watchdog.ts"; import type { InteractiveEngineGenerationEnded } from "./engine-generation.ts"; export type EngineDiagnosticListener = (diagnostic: ActivityWatchdogDiagnostic) => void; export interface EngineHealthDeps { /** Terminate the current (or hung replacement) engine child. */ stop(): Promise; /** Replace the engine child and reload host state from it. */ restart(replacementReady: () => void): Promise; /** Drop streaming/compaction/callback activity owned by the dead generation. */ clearActivity(): void; } export declare const UNEXPECTED_ENGINE_LOSS_NOTICE = "Interactive engine stopped unexpectedly; restarting."; export declare const UNRESPONSIVE_ENGINE_NOTICE = "Interactive engine is not responding; restarting."; /** * Keep the actionable transport cause in the recovery notice. Child stderr can * contain provider output or secrets, so only the bounded summary before the * RpcClient's `. Stderr:` suffix is safe to render. */ export declare function formatUnexpectedEngineLossNotice(event: InteractiveEngineGenerationEnded): string; export interface EngineHealthOptions { /** Injectable clock so tests can cross the unresponsive threshold instantly. */ now?: () => number; } /** * Engine health surface: diagnostic fan-out plus single-flight recovery. * * Recovery has exactly two entry points, deliberately separate: * * - {@link handleGenerationEnded} runs ONE automatic replacement attempt after * an unexpected loss of a generation. Expected losses (explicit stop, * deliberate generation replacement, host shutdown) never restart. * - {@link terminate} is the explicit user escape hatch (Ctrl+C). It force-stops * first, so it can rescue a replacement child that is hung waiting for * readiness instead of joining that pending promise. * * Neither path is reachable from an interrupt: pressing Escape only requests the * engine's own cooperative abort and waits for it. */ export declare class EngineHealthController { private readonly deps; private readonly now; private readonly listeners; private pending; private unresponsive; private abortStartedAt; private readonly pendingControls; private controlFailed; private attempt; /** When the current replacement attempt began; undefined once it settles. */ private attemptStartedAt; private attemptId; private forceStoppedAttemptId; private rescueRequested; /** * Latched when a replacement failed on its own. Automatic recovery stays * single-shot, but the user's Ctrl+C must remain able to try again: without * this the host would be left with no engine, no pending attempt, no overdue * abort, and no watchdog flag, so nothing could arm the escape hatch. */ private replacementNeeded; private termination; /** Set once disposal begins; joins the in-flight attempt and termination. */ private shutdownPromise; private stopped; constructor(deps: EngineHealthDeps, options?: EngineHealthOptions); onDiagnostic(listener: EngineDiagnosticListener): () => void; /** Publish a diagnostic produced elsewhere (watchdog, transport, bridge). */ publish(diagnostic: ActivityWatchdogDiagnostic): void; /** A heartbeat proves the child's loop turns again, so clear a reported stall. */ clearUnresponsive(): void; markCooperativeAbortStarted(): void; markCooperativeAbortSettled(): void; trackControlRequest(request: () => Promise): Promise; /** * True when a control failed or the engine is not answering: * * - the heartbeat watchdog declared it unresponsive; * - a replacement failed on its own and the host has no engine; * - a queue clear or cooperative abort failed or exceeded the unresponsive * threshold, even if the child's loop still turns; * - a replacement has been waiting for `engine_ready` past that threshold. * * The readiness case has no watchdog coverage at all: a child stopped before * readiness emits no heartbeat and no diagnostic, and `start()` waits without * a deadline by design. Crossing a threshold arms nothing on its own — it * only decides whether a deliberate Ctrl+C escalates — and all of this stays * false for a healthy engine so a stray second Ctrl+C cannot replace one. */ needsExplicitTermination(): boolean; private isOverdue; isRecovering(): boolean; /** * Stop accepting recovery work and join whatever is still running. * * Disposal must not return while a replacement is between its own stop and * its spawn: the client's restart permit is voided by `deps.stop()`, and the * attempt is then awaited so no child, and no host initialization, outlives * teardown. Idempotent, and bounded by the client's own stop deadlines. */ shutdown(): Promise; handleGenerationEnded(event: InteractiveEngineGenerationEnded): void; terminate(): Promise; /** Force-stop an overdue replacement, at most once per attempt. */ private rescueOverdueAttempt; private stopQuietly; private recover; private resetControls; private notice; private deliver; } //# sourceMappingURL=engine-health.d.ts.map