/** * Agent Turn State * * Whether an agent is mid-turn according to its runtime, not according to * its screen. Long tool calls, a model writing a long tool input and * background subagents are all silent on the PTY. Reading silence as "idle" * let a restart kill Eve mid-work (2026-10-02, TKT-194). See * specs/2026-10-02-restart-busy-and-resume.md. * * Sources: * - Claude Code hooks (POST /api/agent-hooks): SessionStart, UserPromptSubmit, * PreToolUse, PostToolUse, SubagentStart, SubagentStop, Stop. Only * identifiers are kept (event names, tool-use ids, subagent ids), never * payload content. * - The Claude Code transcript (see claude-transcript-turn.ts). * * How they combine: * - The hooks lead. A transcript turn end (end_turn, interrupt, API or * usage-limit error) newer than the last hook event ends a hook `turn`: * Esc and API errors fire no Stop hook. * - `Stop` clears open tool calls and holds the turn's subagents. Held * subagents count as `background` only while the transcript (Claude Code's * own pending-background count) agrees; a transcript turn end with nothing * pending drops them. * - A transcript-only `background` (no hook saw a subagent) is reported as * `idle`: on its own it never blocks the drain or settling. * - A transcript `turn` newer than the last hook event (a lost hook) is a turn. * * Verdicts: * - `turn`: a turn is in progress; * - `background`: the turn ended, but a subagent is still running and will * start a new turn when it finishes; * - `idle`: the runtime said the turn ended and nothing is pending; * - `unknown`: no trustworthy signal; callers fall back to the screen. * * @module services/monitoring/agent-turn-state */ /** What the runtime says about a session. */ export type TurnVerdictState = 'turn' | 'background' | 'idle' | 'unknown'; /** A verdict with the details callers need. */ export interface TurnVerdict { /** The state */ state: TurnVerdictState; /** A tool call, subagent or background task is open (the drain waits longer) */ longRunning: boolean; /** Epoch ms the turn / background work began, when known */ since: number | null; /** Which source decided */ source: 'hooks' | 'transcript' | 'none'; } /** Identifiers a hook event may carry. */ export interface TurnHookIds { /** Claude Code tool_use_id (PreToolUse / PostToolUse) */ toolUseId?: string; /** Subagent id (SubagentStart / SubagentStop) */ agentId?: string; /** SessionStart source (startup / resume / clear / compact) */ source?: string; } /** Resolves a session to its Claude Code transcript file, or null. */ export type TranscriptLocator = (sessionName: string) => string | null; /** A background subagent that stopped making progress. */ export interface StalledBackground { /** Subagent ids still open in the hooks' view */ agentIds: string[]; /** Epoch ms of the last sign of life (newest subagent transcript write, or the launch) */ lastProgressAt: number; /** The launch description (from the subagent's meta file), when readable */ description?: string; } /** * Process-wide runtime turn state. Singleton; in memory only (a backend * restart kills the agents too, so nothing needs to survive one). */ export declare class AgentTurnStateService { private static instance; private readonly records; private transcriptLocator; /** Located transcript per session, re-resolved after LOCATOR_CACHE_MS */ private readonly located; /** Cached progress probes of background subagents, per session */ private readonly progress; /** * @returns The singleton */ static getInstance(): AgentTurnStateService; /** Reset the singleton (tests only). */ static resetInstance(): void; /** * Install the transcript locator. * * @param locator - Locator, or null to disable the transcript */ setTranscriptLocator(locator: TranscriptLocator | null): void; /** * A runtime process was (re)started for the session: forget its old turn * state, and ignore transcript background launches from before now. * * @param sessionName - Session * @param now - Start time */ noteRuntimeStart(sessionName: string, now?: number): void; /** * Record a Claude Code hook event. * * @param sessionName - Reporting session * @param event - Hook event name (validated by the caller) * @param ids - Tool-use / subagent ids and SessionStart source, already validated * @param now - Clock * @returns True when the event changed the turn state model */ recordHook(sessionName: string, event: string, ids?: TurnHookIds, now?: number): boolean; /** * Forget a session (its runtime exited). * * @param sessionName - Session */ forget(sessionName: string): void; /** * Whether the session's background subagents (the turn ended, they still * run) have stopped making progress: none of their transcripts under * `/subagents/agent-.jsonl` was written for * BACKGROUND_STALL_MS. Only stat calls, cached for BACKGROUND_PROBE_CACHE_MS. * Null when the agent's own turn is active, nothing is held, or any * subagent still makes progress. * * @param sessionName - Session * @param now - Clock * @returns The stalled work, or null */ stalledBackground(sessionName: string, now?: number): StalledBackground | null; /** * Stop counting the session's stalled background subagents (they are told * about, not waited for). A late SubagentStop is harmless. * * @param sessionName - Session */ dropBackground(sessionName: string): void; /** Directory of the session's subagent transcripts, when its transcript is known. */ private subagentsDir; /** First readable `description` among the subagents' meta files (truncated). */ private subagentDescription; /** * Sessions that reported a hook event (still remembered). * * @returns Session names */ knownSessions(): string[]; /** * Epoch ms of the session's last hook event, or null when it never sent one. * * @param sessionName - Session * @returns Time or null */ lastHookEventAt(sessionName: string): number | null; /** * How many tool calls the session started after `since` (PreToolUse hooks). * Used to tell "said it would do X" from "did something after saying so". * * @param sessionName - Session * @param since - Epoch ms (exclusive) * @returns Count, or null when the session reports no hooks (the caller must not guess) */ toolStartsSince(sessionName: string, since: number): number | null; /** * Epoch ms the session's current runtime process started, when known. * * @param sessionName - Session * @returns Time or null */ runtimeStartedAt(sessionName: string): number | null; /** * What the hooks alone say (held subagents count as background here). * * @param sessionName - Session * @param now - Clock * @returns Verdict (`unknown` without hooks or after a long silence) */ hookVerdict(sessionName: string, now?: number): TurnVerdict; /** * Combined verdict (see the module doc for the rules). * * @param sessionName - Session * @param now - Clock * @returns Verdict * * @example * ```typescript * const v = AgentTurnStateService.getInstance().getVerdict('eve-1'); * if (v.state === 'turn' || v.state === 'background') // do not call it idle * ``` */ getVerdict(sessionName: string, now?: number): TurnVerdict; /** * Whether the agent still has background work after its turn ended: the * work it is answering for is not finished yet. * * @param sessionName - Session * @param now - Clock * @returns True for a `background` verdict */ hasBackgroundWork(sessionName: string, now?: number): boolean; /** * End a record's turn: open tools are cleared, the turn's subagents are held. * * @param r - Record * @param at - When the turn ended */ private endTurn; /** * Drop open tool calls and subagents older than OPEN_WORK_MAX_MS. * * @param r - Record * @param now - Clock */ private expire; /** * Read the session's transcript, if a locator finds one. * * @param sessionName - Session * @param now - Clock * @param launchedAfter - Runtime start (older launches ignored) * @returns The transcript state, or null */ private readTranscript; /** * Get or create a session's record (bounded). * * @param sessionName - Session * @param now - Clock * @returns The record */ private recordFor; } //# sourceMappingURL=agent-turn-state.d.ts.map