/** * Tool interception — the eval-tier half of the tool-call spy. * * The eval tier drives the REAL model, so the agent's tool decisions are genuine. * But letting a side-effecting tool actually run — a paid image API, `git push`, * spawning a paid subagent — makes the eval expensive and dangerous. An * **intercept** lets the model emit the `tool_use` (so its arguments are captured * in the `Trace`, where `toolWith` / `notTool` assert on them) while a PreToolUse * hook **denies the real execution** with a block message. The run stays cheap and * side-effect-free, but the agent's *decision* — the thing a completion grader * can't see — is fully observable. * * This module is the PURE decision + wiring core, mirroring the agent-contract * rail (`src/adapters/claude-code/agent-runtime.ts`): * * - `decideIntercept` — does this call get intercepted, and with what deny reason; * - `buildInterceptSettings` — the PreToolUse hook fragment routing matched tools * through `vigiles hook-runtime intercept-tool`; * - `serializeIntercepts` / `parseIntercepts` — the env round-trip (incl. RegExp * matchers) the hook subprocess reads back. * * The hook denies via exit 2 + a stderr message (the same block mechanism the * agent rail uses). IMPORTANT — Claude Code surfaces that to the model as a * *blocked* call: the tool is intercepted (prevented), NOT executed, and the model * is NOT handed a faked successful return. So this is **intercept-and-prevent + * observe the attempt**, not a faithful tool mock: it's sound for "did the agent * ATTEMPT X / call it with these args / push to the wrong branch" (first-attempt * questions, where what happens after doesn't matter), and unsound for "stub the * tool and let the trajectory continue as if it succeeded" (the model is told it * was blocked, so a multi-step flow that needs the real result will derail). CC * exposes no "skip execution but return this as success" primitive for arbitrary * tools, so deny+reason is the closest available — with that ceiling. */ import { type ArgMatcher } from "./arg-match.js"; /** Env var the spawned `vigiles hook-runtime intercept-tool` reads its intercept list from. */ export declare const INTERCEPT_TOOLS_ENV = "VIGILES_INTERCEPT_TOOLS"; /** Declare a tool to intercept: deny its real execution with a block message. */ export interface ToolIntercept { /** The tool to intercept (e.g. `"Bash"`, `"WebFetch"`, `"Task"`, `"mcp__img__gen"`). */ readonly tool: string; /** * Only intercept calls whose `input` matches (so you can intercept `git push` * while letting other `Bash` through). Omit to intercept every call to `tool`. */ readonly when?: ArgMatcher; /** * The denial reason shown to the model when the call is intercepted. This is a * *block* message: the call is intercepted (prevented), NOT executed — so phrase * it as "this was prevented; don't retry", not "here's your result". * Defaults to {@link DEFAULT_INTERCEPT_REASON}. */ readonly denyReason?: string; } /** The default denial reason — honest that the call was intercepted (prevented), NOT executed. */ export declare const DEFAULT_INTERCEPT_REASON: string; /** The decision for one tool call: intercept it (deny + reason), or let it run. */ export type InterceptDecision = { readonly intercept: true; readonly denyReason: string; } | { readonly intercept: false; }; /** * Decide whether a tool call should be intercepted. Returns the first matching * intercept's denial reason (preventing real execution), or `{ intercept: false }` * to let the call run for real. Pure — the same logic `vigiles hook-runtime intercept-tool` * runs. */ export declare function decideIntercept(toolName: string, input: unknown, intercepts: readonly ToolIntercept[]): InterceptDecision; /** * Decide from a raw PreToolUse event JSON (the hook's stdin). Parses `tool_name` * + `tool_input`, then defers to {@link decideIntercept}. Malformed input or a * missing tool name is a no-op (let it run) — never fail closed on a parse error. */ export declare function interceptHookDecision(rawEvent: string, intercepts: readonly ToolIntercept[]): InterceptDecision; /** A `.claude/settings.json` fragment registering the intercept PreToolUse hook. */ export interface InterceptSettings { readonly hooks: { readonly PreToolUse: readonly unknown[]; }; } /** * Build the PreToolUse hook fragment that routes every intercepted tool through * `vigiles hook-runtime intercept-tool`. The `matcher` is a CC tool-name regex over the * union of intercepted tool names (each escaped), so unrelated tools are never * intercepted. Merge the result into an arm's `settings`; the intercept list * itself travels in the {@link INTERCEPT_TOOLS_ENV} env var (see * {@link serializeIntercepts}). */ export declare function buildInterceptSettings(intercepts: readonly ToolIntercept[], opts?: { command?: string; }): InterceptSettings; /** * Serialize an intercept list to a JSON string for {@link INTERCEPT_TOOLS_ENV}. * RegExp matchers are encoded as `{ re, flags }` so they round-trip exactly (a * plain `JSON.stringify` would drop them to `{}`). */ export declare function serializeIntercepts(intercepts: readonly ToolIntercept[]): string; /** Parse an intercept list from the env JSON (tolerant — a bad entry is skipped). */ export declare function parseIntercepts(json: string): ToolIntercept[]; //# sourceMappingURL=tool-intercept.d.ts.map