import type { Logger } from './types.js'; export interface StuckTurnVerdict { /** True only when the loop owes the model a call, inbound is queued, and the * freeze is older than the threshold. */ wedged: boolean; /** ms epoch of the last agent-progress record (assistant turn, or a * tool_result-carrying user record). Null when no progress record is in the * tail window — in which case `wedged` is always false (safe default). */ lastProgressTs: number | null; /** ms epoch of the first `queue-operation:enqueue` after the last progress * record — the oldest still-pending inbound, and the age baseline for the * wedge. Null when no pending enqueue is present or its timestamp is * unparseable; in that case the wedge cannot be aged and `wedged` is false * (safe default — we never recycle on an inbound we can't prove has waited). */ oldestPendingEnqueueTs: number | null; /** Count of `queue-operation:enqueue` records appearing after the last * progress record (inbound queued with no agent progress since). */ pendingQueued: number; /** The last assistant record emitted tool_use block(s) with no later * progress record — a tool is legitimately running, so this is NOT a wedge. */ toolInFlight: boolean; } /** Pure wedge classifier. Walks records in file order; see `StuckTurnVerdict`. */ export declare function classifyStuckTurn(records: ReadonlyArray>, opts: { now: number; thresholdMs: number; }): StuckTurnVerdict; /** Read the last `tailBytes` of a JSONL and parse complete records. If no * progress record (assistant turn or tool_result user) falls within that * window, the window doubles on each iteration until a progress record is found * or the scan reaches the full file size (capped at MAX_SCAN_BYTES). Returns [] * on any read/parse failure. */ export declare function readJsonlTailRecords(path: string, tailBytes?: number): Record[]; /** One eligible session for a watchdog tick: a live channel session whose * process is alive. The watch reads its JSONL, classifies, and recycles. */ export interface StuckTurnCandidate { sessionId: string; jsonlPath: string; } export interface StuckTurnWatchDeps { /** Eligible sessions this tick — live channel sessions with a living pid. */ candidates: () => StuckTurnCandidate[]; /** SIGTERM the wedged session's rc scope (clean exit 143; next inbound * rc-spawns --resume and drains). Fire-and-forget; failures logged by the * callee. */ recycle: (sessionId: string) => void; thresholdMs: number; intervalMs: number; /** A session recycled within this window is not re-flagged or re-recycled. * A recycle (SIGTERM the scope) is async and the row stays live until the * process exits and its pid file disappears — without a cooldown a slow or * failed stop re-fires every tick. Default 5 min. */ recycleCooldownMs?: number; /** Grace after a recycle before the drain post-condition is checked. The * resume needs a few seconds to re-bind the channel and replay the JSONL * queue; this stays well under intervalMs so the check lands on the next * tick. Default 30 s. */ verifyGraceMs?: number; logger: Logger; now?: () => number; /** Injectable tail reader for tests. */ readTail?: (path: string) => Record[]; } export interface StuckTurnWatch { start(): void; stop(): void; tickOnce(): { scanned: number; wedged: number; }; } export declare function createStuckTurnWatch(deps: StuckTurnWatchDeps): StuckTurnWatch; //# sourceMappingURL=stuck-turn.d.ts.map