/** * What the message hook decides, with no I/O, so every branch is testable. * * Two kinds of run, from the same bundle: * * turn hooks SessionStart, UserPromptSubmit, Stop and SessionEnd. Each one * reports the session's state and delivers what already waits: * plain stdout on SessionStart and UserPromptSubmit, which Claude * Code adds to context; on Stop, decision "block" with the * messages as the reason, so the agent acts on them before it stops. * * the waiter `--wait`, registered with asyncRewake on SessionStart, * UserPromptSubmit and Stop. It runs in the background and * long-polls the server. When a message arrives it writes the * messages to stderr and exits 2, and Claude Code wakes the * session with them, also a session that is idle and waits for * its user. One waiter runs per session. * * The other tools run the turn hooks with `--tool `. normalizePayload maps * each payload to Claude's shape, and renderRun writes the output each tool * reads. A tool adds text only at the events in DELIVERS. */ import { type SessionProcessKind, type WireMessage } from '@chat-recall/engine/core/session-messages.js'; export declare const TURN_EVENTS: Set; /** The AI tools the message hook runs in. */ export type HookTool = 'claude' | 'codex' | 'agy' | 'cursor' | 'opencode' | 'pi'; export declare const HOOK_TOOLS: readonly HookTool[]; /** The session-id prefix of each tool, as its backend writes it. */ export declare const SESSION_PREFIX: Record; export declare function isHookTool(v: unknown): v is HookTool; /** * One tool's hook payload in the shape planRun reads, or null for an event * this hook does not handle. * * Antigravity names no event in its payload, so its registration passes the * event as `--event`. It runs PreInvocation before every model call of a turn; * only the first one (`invocationNum` 0) is the prompt. A Cursor stop with * `loop_count` above 0 comes after a follow-up, as Claude's `stop_hook_active` * does. */ export declare function normalizePayload(tool: HookTool, raw: Record, event?: string): HookPayload | null; /** Per-session state the hook keeps on disk between runs. */ export interface HookState { /** The session has been told its own id. */ introduced: boolean; /** A Stop in this turn already continued the agent once. */ stopBlocked: boolean; /** The session's working directory, for a child that this session starts. */ cwd?: string; /** CLAUDE_CONFIG_DIR of the session, so a child starts in the same profile. */ configDir?: string; } export declare const EMPTY_STATE: HookState; export interface HookPayload { session_id?: unknown; hook_event_name?: unknown; cwd?: unknown; agent_id?: unknown; stop_hook_active?: unknown; /** The process kind, when the tool's plugin can see it (the opencode plugin). */ kind?: unknown; /** The process id of the tool, for the local view of running processes. */ pid?: unknown; /** Message ids the TUI plugin has delivered and wants acknowledged. */ ids?: unknown; } /** * The process kind of this run. The plugin of a tool that knows it (opencode: * `serve`, `run`, or the TUI plugin's `tui`) sends it with the payload; a * Claude Code background session carries it in its environment; anything else * is reported as it runs. */ export declare function processKindOf(tool: HookTool, payload: HookPayload, env?: NodeJS.ProcessEnv): SessionProcessKind | undefined; export declare function sessionIdOf(payload: HookPayload): string | null; export interface HookPlan { sessionId: string; event: string; cwd?: string; /** Ask the server for the waiting messages. */ take: boolean; } /** * Decide what one turn-hook run does. * * Messages are not taken inside a subagent. Its context is not the session's, * so a message delivered there never reaches the agent that was addressed. * * A Stop continues the agent at most once per turn. Two sessions that answer * each other at every turn end would otherwise keep each other running. */ export declare function planRun(payload: HookPayload, state: HookState, tool?: HookTool): HookPlan | null; export interface HookResult { /** What to write to stdout; empty for nothing. */ stdout: string; state: HookState; } /** * What a run prints when it has nothing to add. Cursor reads a JSON object * from every hook, and `beforeSubmitPrompt` must allow the prompt. */ export declare function emptyOutput(tool: HookTool, event: string): string; /** The output for one turn-hook run, given the messages the server returned. */ export declare function renderRun(plan: HookPlan, messages: readonly WireMessage[], state: HookState, now: number, labels?: ReadonlyMap, tool?: HookTool): HookResult; /** The waiter's stderr when messages arrive. Claude Code shows it as a system reminder. */ export declare function renderWake(sessionId: string, messages: readonly WireMessage[], now: number, labels?: ReadonlyMap): string; //# sourceMappingURL=message-hook-core.d.ts.map