/**
 * Generic tool-error retry harness.
 *
 * Lifts the `verification_result`-only schema-retry pattern from
 * `verification-harness.ts` (`buildJsonRetryPrompt` + `tool_execution_end`
 * listeners) into a reusable, instance-per-session helper that observes
 * every session and re-emits a targeted nudge when any tool call
 * returns a deterministic JSON / structured-input validation error.
 *
 * Coverage classes:
 *   - `isError === true` matching `detectJsonValidationError` (V8/Node
 *     SyntaxError family + "Validation failed for tool").
 *   - `isError === false` with an `ok({ error })` body whose text starts
 *     with `ask_user_choices: questions[` or `propose_*:` — these are
 *     successful tool invocations whose extension returned a structured
 *     validation error.
 *
 * Retries are dispatched via `rpcClient.prompt(nudge)` — the same path
 * verification reminders use. Never `_dispatchSteer`, never
 * `enqueuePrompt`. Per-`tool_use_id` cap (default 2). The harness never
 * writes a user-visible message.
 *
 * Verification harness coordination: the verification harness adds the
 * `tool_use_id` it's about to retry to `session._verificationOwnedToolUses`;
 * this harness skips any `tool_use_id` already in that set.
 *
 * See `docs/design/tool-retry-harness.md` for the full design.
 */
import { detectJsonValidationError } from "./verification-logic.js";
const DEFAULT_MAX_RETRIES = 2;
// ---------------------------------------------------------------------------
// Classifier helpers
// ---------------------------------------------------------------------------
/** Best-effort extract of a readable string from an agent tool result. */
export function extractToolResultText(result) {
    if (!result)
        return "";
    if (typeof result === "string")
        return result;
    try {
        const r = result;
        const content = r.content;
        if (Array.isArray(content)) {
            return content
                .map((c) => (typeof c === "string" ? c : typeof c?.text === "string" ? c.text : ""))
                .join("\n");
        }
        if (typeof content === "string")
            return content;
        if (typeof r.error === "string")
            return r.error;
    }
    catch { /* ignore */ }
    try {
        return JSON.stringify(result);
    }
    catch {
        return String(result);
    }
}
/**
 * Inspect a successful (`isError===false`) tool result for a structured
 * `{ error: "..." }` validation message. Returns the error string or null.
 */
export function detectStructuredValidationError(text) {
    if (!text)
        return null;
    const trimmed = text.trim();
    // Direct prefix matches — what the extensions emit verbatim.
    const PREFIXES = [
        "ask_user_choices: questions[",
        "propose_goal:",
        "propose_role:",
        "propose_project:",
        "propose_staff:",
        "propose_tool:",
    ];
    for (const p of PREFIXES) {
        if (trimmed.startsWith(p))
            return trimmed.slice(0, 400);
    }
    // JSON body containing only an `error` string.
    if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
        try {
            const parsed = JSON.parse(trimmed);
            if (parsed && typeof parsed === "object" && typeof parsed.error === "string") {
                const msg = parsed.error.trim();
                for (const p of PREFIXES) {
                    if (msg.startsWith(p))
                        return msg.slice(0, 400);
                }
            }
        }
        catch { /* not JSON */ }
    }
    return null;
}
// ---------------------------------------------------------------------------
// Nudge prompt
// ---------------------------------------------------------------------------
export function buildToolRetryNudge(toolName, quotedError) {
    const name = toolName || "tool";
    return (`Your previous \`${name}\` call failed with a validation error:\n\n` +
        `    ${quotedError}\n\n` +
        `This is almost certainly a streaming / argument-validation glitch in the previous attempt, ` +
        `not a real problem with your analysis. Re-emit the \`${name}\` tool call now with corrected ` +
        `arguments that match the tool's schema. Do not re-run any analysis — just submit the fixed call.`);
}
// ---------------------------------------------------------------------------
// Harness
// ---------------------------------------------------------------------------
export class ToolRetryHarness {
    session;
    maxRetries;
    onMetadata;
    debug;
    unsub = null;
    retries = new Map();
    /** Total nudges issued in this session. Used for the metadata `count` field. */
    totalRetries = 0;
    constructor(opts) {
        this.session = opts.session;
        this.maxRetries = opts.maxRetries ?? DEFAULT_MAX_RETRIES;
        this.onMetadata = opts.onMetadata;
        this.debug = opts.debug ?? (() => { });
    }
    start() {
        if (this.unsub)
            return;
        this.unsub = this.session.rpcClient.onEvent((event) => {
            if (!event || event.type !== "tool_execution_end")
                return;
            void this._handleEvent(event).catch((err) => {
                // eslint-disable-next-line no-console
                console.error(`[tool-retry] handler failed for session ${this.session.id}:`, err);
            });
        });
    }
    stop() {
        try {
            this.unsub?.();
        }
        catch { /* ignore */ }
        this.unsub = null;
        this.retries.clear();
    }
    /**
     * Public for tests — classify a `tool_execution_end` event into one of
     * `"schema" | "domain" | "ignore"`.
     */
    classify(event) {
        if (!event || event.type !== "tool_execution_end")
            return "ignore";
        const text = extractToolResultText(event.result);
        if (event.isError === true) {
            if (text && detectJsonValidationError(text))
                return "schema";
            return "domain";
        }
        // isError === false (or missing) — only schema if we recognise the
        // structured validation-error envelope.
        if (text && detectStructuredValidationError(text))
            return "schema";
        return "ignore";
    }
    /** Public for tests. */
    buildNudge(toolName, quotedError) {
        return buildToolRetryNudge(toolName, quotedError);
    }
    async _handleEvent(event) {
        const classifier = this.classify(event);
        this.debug("event=", {
            session: this.session.id,
            toolName: event.toolName,
            toolUseId: event.toolCallId,
            isError: event.isError,
            classifier,
        });
        const toolUseId = event.toolCallId;
        // Reset retry count on success / domain-error for the same tool_use_id
        // so a recovered call doesn't carry stale charges.
        if (toolUseId && classifier !== "schema") {
            this.retries.delete(toolUseId);
            return;
        }
        if (classifier !== "schema")
            return;
        if (!toolUseId)
            return; // Cannot key retry counts without an ID.
        // Defer to verification harness if it owns this tool_use_id.
        if (this.session._verificationOwnedToolUses?.has(toolUseId))
            return;
        const used = this.retries.get(toolUseId) ?? 0;
        if (used >= this.maxRetries)
            return;
        this.retries.set(toolUseId, used + 1);
        const text = extractToolResultText(event.result);
        const quoted = (event.isError === true ? detectJsonValidationError(text) : detectStructuredValidationError(text))
            ?? text.slice(0, 400);
        const toolName = event.toolName || "tool";
        const nudge = this.buildNudge(toolName, quoted);
        try {
            await this.session.rpcClient.prompt(nudge);
        }
        catch (err) {
            // eslint-disable-next-line no-console
            console.warn(`[tool-retry] prompt() failed for session ${this.session.id}:`, err);
            return;
        }
        this.totalRetries += 1;
        const reason = `${toolName}: ${quoted.slice(0, 160)}`;
        try {
            this.onMetadata?.({ count: this.totalRetries, lastReason: reason });
        }
        catch (err) {
            // eslint-disable-next-line no-console
            console.warn(`[tool-retry] onMetadata callback threw for session ${this.session.id}:`, err);
        }
    }
}
//# sourceMappingURL=tool-retry-harness.js.map