/**
 * OwlCoda Native ProbePlan Tool
 *
 * Background — issue #7 (private):
 *   Two consecutive hostile-QA prompt revisions (delivery-quality-v2
 *   and v2.1) failed to make the testing LLMs actually emit Group A's
 *   `A0` write probe. Both DeepSeek and Kimi pattern-matched the
 *   prompt text saying "A0 may be denied" → predicted denial → skipped
 *   the tool call entirely → reported P with no transcript evidence.
 *
 *   The v2.1 patch added rule 9 ("evidence must be a real tool_use
 *   block") and rule 10 ("probes are unconditional") to the prompt
 *   itself, but a prompt-only fix has no enforcement. The runtime
 *   has to verify probes were actually emitted before allowing a
 *   final summary — otherwise the rules are advisory.
 *
 * What this tool does:
 *   - Registers a "probe plan" — a list of `{id, tool, mustContain,
 *     description}` entries — into `conversation.options.probePlans`.
 *   - Each subsequent tool execution is checked against unsatisfied
 *     probes by `markProbesSatisfied` (called from the conversation
 *     loop after `executeTools` returns). A probe is satisfied when a
 *     tool call's name === probe.tool AND `mustContain` appears as a
 *     substring of any string-valued leaf in the tool input (walked
 *     recursively — see `inputContainsString`). Walking values rather
 *     than `JSON.stringify(input)` was the 0.13.53 fix: bash commands
 *     containing literal double quotes (`echo "edited via bash"`) get
 *     their quotes escaped during stringify (`echo \"edited via
 *     bash\"`), making substring matches against the unescaped
 *     mustContain fail.
 *   - The conversation loop's final-text hook injects a
 *     `[Runtime probe-coverage check]` notice when the assistant
 *     attempts to summarize a group with unsatisfied probes (similar
 *     pattern to the 0.13.44 delivery-check hook).
 *
 * What this tool does NOT do:
 *   - Run the probes itself. The model still emits each tool call.
 *     This tool is the registration + runtime tracking layer.
 *   - Validate that the probe was the right shape semantically — only
 *     that a tool call matching `tool` + `mustContain` happened. False
 *     positives are tolerable; the prompt is the authoritative spec.
 *   - Block the final summary outright. The hook injects a notice and
 *     the model can re-emit. A persistent refusal can still get
 *     through — but it will be visible in the transcript as repeated
 *     coverage-check injects.
 */
import type { NativeToolDef } from './types.js';
import type { Conversation, ProbeExpectedOutcome, ProbePlanProbe } from '../protocol/types.js';
export interface ProbePlanInput {
    /** User-supplied group label, e.g. "A". Re-registering the same id replaces. */
    groupId: string;
    probes: Array<{
        id: string;
        tool: string;
        mustContain: string;
        description?: string;
        /**
         * Optional; defaults to 'success'. Set to 'intentGuardBlocked' for
         * negative probes that must actually be emitted but expected to be
         * refused by the analysis-intent / allowlist guard. The probe is
         * only marked satisfied when both the tool_use fires and the
         * resulting tool_result carries `metadata.intentGuardBlocked === true`.
         */
        expectedOutcome?: ProbeExpectedOutcome;
    }>;
}
export interface ProbePlanDeps {
    /**
     * Live accessor for the active `Conversation`. Wired by the REPL
     * the same way `liveApproveState` was wired in 0.13.40, so the
     * tool can mutate `conversation.options.probePlans` without
     * conversation.ts having to thread the conversation into every
     * dispatcher call.
     */
    getConversation?: () => Conversation | null;
}
export declare function createProbePlanTool(deps?: ProbePlanDeps): NativeToolDef<ProbePlanInput>;
/**
 * Pure check: does this tool call match an unsatisfied registered
 * **positive** probe? Used by `evaluateIntentGuard` (0.13.52) as a
 * probe-consent override — when the user's intent is `analysis` (or
 * the conversation is in allowlist mode) but the call matches a probe
 * they explicitly registered via `ProbePlan`, the registration itself
 * counts as implementation consent for THAT specific probe, so the
 * guard lets it through.
 *
 * 0.13.56 change: this function now skips probes whose
 * `expectedOutcome !== 'success'`. Negative probes (i.e.
 * `expectedOutcome: 'intentGuardBlocked'`) are explicitly waiting for
 * the intent guard to refuse them — letting them bypass the guard
 * via probe-consent would defeat the whole point of the negative
 * probe. Without this filter, A_LEAK in v2.5 hostile-QA matched
 * mustContain → probe-consent override fired → intent guard returned
 * null → call fell through to operator approval instead of being
 * caught by the structured guard.
 */
export declare function findMatchingUnsatisfiedProbe(conversation: Conversation, toolName: string, toolInput: Record<string, unknown>): {
    groupId: string;
    probe: ProbePlanProbe;
} | null;
/**
 * Outcome of an executed tool call as observed by the runtime. The
 * matcher uses this to evaluate `expectedOutcome` semantics. Pass null
 * (or omit) for callers that haven't actually executed yet — those
 * cases only match probes whose expectedOutcome is 'success'.
 */
export interface ProbeToolOutcome {
    /** True if the tool_result was returned with isError: true. */
    isError: boolean;
    /** Structured metadata returned alongside the tool_result, if any. */
    metadata?: Record<string, unknown>;
}
/**
 * Walk the active probe plans and mark a probe satisfied if the tool
 * call (by name + value-tree input) matches an unsatisfied probe's
 * matcher AND the executed outcome matches the probe's expectedOutcome.
 *
 *  - 'success' (default): satisfied iff outcome.isError === false.
 *  - 'intentGuardBlocked' (0.13.54): satisfied iff outcome.isError ===
 *    true AND outcome.metadata.intentGuardBlocked === true. Closes the
 *    "predicted-denied → skipped → fabricated guard evidence" escape
 *    that DeepSeek + Kimi both showed in v2.4 hostile-QA on A_LEAK.
 *
 * Idempotent: already-satisfied probes stay satisfied. Returns the
 * list of probe ids that were freshly marked.
 */
export declare function markProbesSatisfied(conversation: Conversation, toolName: string, toolInput: Record<string, unknown>, outcome?: ProbeToolOutcome): string[];
/** Probes still unsatisfied across all active plans. Returns one
 *  flat list of `{groupId, probe}` for inject-message rendering. */
export declare function unsatisfiedProbes(conversation: Conversation): Array<{
    groupId: string;
    probe: ProbePlanProbe;
}>;
/**
 * Build the runtime-injected nudge that fires when an assistant final
 * text response attempts to summarize while probes remain unsatisfied.
 * Same shape as buildTaskContinuePrompt / buildDeliveryCheckPrompt
 * (single user-role message body).
 */
export declare function buildProbeCoverageCheckPrompt(unsatisfied: Array<{
    groupId: string;
    probe: ProbePlanProbe;
}>): string;
