/** * Hook registry, command registry, tool policy, task ledger, and task * registry. Mirrors `_harness/_hooks.py`, `_commands.py`, `_tool_policy.py`, * `_task_ledger.py`, `_tasks.py`. */ import { type HarnessTool } from "./types.js"; import type { EventBus } from "./events.js"; /** * Canonical hook event names. Mirrors Python `_hooks._events.HookEvent`, * with six extra legacy values (`ToolCallStart`, `ToolCallEnd`, `TurnStart`, * `TurnComplete`, `Edit`, `Commit`, `Compress`, `ErrorEvent`) retained so * that existing `repl.ts` callers keep working. * * Use the const-object form — TypeScript widens this into both a namespace * of string constants and a string-union type under the same name via * declaration merging below. */ export declare const HookEvent: { readonly PreToolUse: "pre_tool_use"; readonly PostToolUse: "post_tool_use"; readonly ToolError: "tool_error"; readonly PreModel: "pre_model"; readonly PostModel: "post_model"; readonly ModelError: "model_error"; readonly PreAgent: "pre_agent"; readonly PostAgent: "post_agent"; readonly AgentError: "agent_error"; readonly SessionStart: "session_start"; readonly UserPromptSubmit: "user_prompt_submit"; readonly SessionEnd: "session_end"; readonly OnEvent: "on_event"; readonly PreCompact: "pre_compact"; readonly PermissionRequest: "permission_request"; readonly Notification: "notification"; readonly ToolCallStart: "tool_call_start"; readonly ToolCallEnd: "tool_call_end"; readonly TurnStart: "turn_start"; readonly TurnComplete: "turn_complete"; readonly Edit: "edit"; readonly Commit: "commit"; readonly Compress: "compress"; readonly ErrorEvent: "error"; }; export type HookEvent = (typeof HookEvent)[keyof typeof HookEvent]; /** Frozen set of every canonical event name. */ export declare const ALL_HOOK_EVENTS: ReadonlySet; /** * Normalized context passed to every hook callable. Not every field is * populated for every event — consult the event taxonomy: tool events * populate `toolName`/`toolInput`, model events populate `model`/`response`, * and so on. The mutable `toolInput` slot is rewritten in place when a * `modify` decision fires, so downstream hooks see the rewritten args. */ export interface HookContext { event: string; sessionId?: string; invocationId?: string; agentName?: string; toolName?: string; toolInput?: Record; toolOutput?: unknown; model?: string; request?: unknown; response?: unknown; userMessage?: string; error?: Error; state?: Record; extra?: Record; } export type HookAction = "allow" | "deny" | "modify" | "replace" | "ask" | "inject"; export interface HookDecisionFields { action: HookAction; reason?: string; toolInput?: Record; output?: unknown; prompt?: string; systemMessage?: string; metadata?: Record; } /** * Structured decision returned from a hook callable. Use the static * constructors (`HookDecision.allow()`, `.deny()`, etc.) instead of * building instances directly. */ export declare class HookDecision { readonly action: HookAction; readonly reason: string; readonly toolInput: Record | undefined; readonly output: unknown; readonly prompt: string; readonly systemMessage: string; readonly metadata: Readonly>; constructor(fields: HookDecisionFields); /** Pass-through — no opinion. */ static allow(): HookDecision; /** Block the wrapped call and surface `reason` to the LLM. */ static deny(reason?: string): HookDecision; /** Rewrite tool arguments. Only valid for `PreToolUse`. */ static modify(toolInput: Record): HookDecision; /** Short-circuit the wrapped call and return `output` instead. */ static replace(output: unknown): HookDecision; /** Raise a permission request with `prompt`. */ static ask(prompt: string): HookDecision; /** Append `systemMessage` to the system message channel. */ static inject(systemMessage: string): HookDecision; get isAllow(): boolean; /** True if this decision short-circuits the call (deny/replace/ask). */ get isTerminal(): boolean; /** True if this decision does not alter the wrapped call's output. */ get isSideEffect(): boolean; } export interface HookMatcherOptions { event: string; /** Regex (anchored) matched against `ctx.toolName`. */ toolName?: string; /** Per-key glob patterns matched against `ctx.toolInput` values. */ args?: Record; /** Optional predicate with full access to the context. */ predicate?: (ctx: HookContext) => boolean; } /** * Filter controlling which contexts a hook callable sees. All layers are * ANDed: event equality, optional anchored regex on `toolName`, optional * per-key fnmatch globs on `toolInput`, and an optional predicate. */ export declare class HookMatcher { readonly event: string; readonly toolName: string | undefined; readonly args: Readonly>; readonly predicate: ((ctx: HookContext) => boolean) | undefined; private readonly toolNameRegex; private readonly argRegexes; constructor(options: HookMatcherOptions); /** Return true if `ctx` should trigger the associated hook. */ matches(ctx: HookContext): boolean; /** Match every context for `event` (no additional filters). */ static any(event: string): HookMatcher; /** Shorthand for matching a specific tool by name with optional arg globs. */ static forTool(event: string, toolName: string, args?: Record): HookMatcher; } export declare const SYSTEM_MESSAGE_STATE_KEY = "_adkf_hook_system_messages"; /** * Transient system-message queue backed by a session state object. Hooks * that return `HookDecision.inject(msg)` append to this channel; a * before-model pump drains and prepends them to the next LLM request. */ export declare class SystemMessageChannel { private readonly state; constructor(state: Record | undefined); private bucket; append(message: string): void; peek(): string[]; /** Return pending messages and clear the channel. */ drain(): string[]; get pendingCount(): number; } export type HookCallable = (ctx: HookContext) => HookDecision | void | undefined | Promise; export interface HookEntry { matcher: HookMatcher; fn?: HookCallable; command?: string; name: string; timeout: number; blocking: boolean; metadata: Readonly>; } export interface HookOnOptions { match?: HookMatcher; name?: string; } export interface HookShellOptions extends HookOnOptions { /** Maximum execution time (seconds). */ timeout?: number; /** Await the subprocess before continuing. Defaults to false. */ blocking?: boolean; } /** Back-compat legacy shell-only alias, kept so old code keeps compiling. */ export interface HookSpec { event: HookEvent; command: string; } /** * User-facing registry of hook callables and shell commands. Entries are * event-partitioned internally for O(1) dispatch. The registry supports * both the new Python-parity API (callable hooks + structured decisions) * and the legacy shell-based `.fire(event, vars)` pipeline used by the * REPL. */ export declare class HookRegistry { private readonly entriesByEvent; readonly workspace: string | undefined; constructor(workspace?: string); /** * Register a hook for `event`. Accepts either a callable returning a * `HookDecision` (new API) or a shell-command string (legacy API — same * as calling `.shell(event, cmd)`). */ on(event: HookEvent, fn: HookCallable, opts?: HookOnOptions): this; on(event: HookEvent, command: string, opts?: HookShellOptions): this; /** Register a shell-command hook for `event`. */ shell(event: HookEvent, command: string, opts?: HookShellOptions): this; private append; onEdit(command: string): this; onError(command: string): this; onCommit(command: string): this; onCompress(command: string): this; /** Return a new registry containing entries from both sides. */ merge(other: HookRegistry): HookRegistry; /** Return a snapshot of entries registered for `event`. */ entriesFor(event: HookEvent): readonly HookEntry[]; /** * Back-compat listing of shell entries. Mirrors the old `list()` surface * that predated callable hooks — callable entries are excluded. */ list(event?: HookEvent): readonly HookSpec[]; get registeredEvents(): readonly string[]; /** * Run every matching entry for `ctx.event` and return the collapsed * decision. Entries run in registration order. Iteration stops at the * first terminal decision (`deny` / `replace` / `ask`). Non-terminal * decisions are folded: `modify` rewrites `ctx.toolInput` in place so * downstream hooks see the rewritten args, and `inject` is collected and * attached to the final decision's metadata under `pendingInjects` so the * plugin can drain it to the SystemMessageChannel. */ dispatch(ctx: HookContext): Promise; private runEntry; private runShell; /** * Legacy shell-fire entry point used by the REPL. Substitutes `{key}` * placeholders against `vars` and awaits every matching shell hook for * `event`. Callable hooks are ignored — use `.dispatch()` for those. */ fire(event: HookEvent, vars?: Record): Promise; } export type CommandHandler = (args: string) => string | Promise; export interface CommandRegistration { name: string; handler: CommandHandler; description?: string; } /** Slash command registry for the REPL. */ export declare class CommandRegistry { readonly prefix: string; private readonly commands; constructor(prefix?: string); register(name: string, handler: CommandHandler, description?: string): this; isCommand(text: string): boolean; /** Parse and execute a `/command rest` line. Returns the handler output. */ dispatch(text: string): Promise; list(): readonly CommandRegistration[]; } export type ToolAction = "retry" | "skip" | "ask" | "propagate"; export interface ToolRule { action: ToolAction; maxAttempts?: number; backoff?: number; fallback?: string; handler?: (toolName: string, error: Error) => boolean | Promise; } /** * Per-tool error recovery policy with backoff and EventBus integration. * Unlike `ErrorStrategy` (which maps tool sets → action), this is a * fluent builder with per-tool granularity. */ export declare class ToolPolicy { readonly default: ToolAction; private readonly rules; private bus?; constructor(opts?: { default?: ToolAction; }); retry(toolName: string, opts?: { maxAttempts?: number; backoff?: number; }): this; skip(toolName: string, opts?: { fallback?: string; }): this; ask(toolName: string, handler: ToolRule["handler"]): this; withBus(bus: EventBus): this; ruleFor(toolName: string): ToolRule; /** Build an after-tool callback that applies the policy on errors. */ afterToolHook(): (toolName: string, error: Error | null, result: unknown) => Promise; } export interface TaskRecord { id: string; name: string; promise: Promise; status: "pending" | "running" | "done" | "error"; result?: unknown; error?: string; startedAt: number; endedAt?: number; } export declare class TaskRegistry { readonly maxTasks: number; private readonly tasks; private nextId; constructor(opts?: { maxTasks?: number; }); launch(name: string, fn: () => Promise): TaskRecord; get(id: string): TaskRecord | undefined; list(): readonly TaskRecord[]; cancel(id: string): boolean; } /** Build LLM-callable tools that wrap a TaskRegistry. */ export declare function taskTools(registry: TaskRegistry): HarnessTool[]; /** * Bridges `dispatch()`/`join()` expression primitives to tool-level task * management. The LLM can launch, check, list, and cancel tasks. */ export declare class TaskLedger { readonly registry: TaskRegistry; private bus?; constructor(opts?: { maxTasks?: number; }); withBus(bus: EventBus): this; tools(): HarnessTool[]; } //# sourceMappingURL=registries.d.ts.map