import { Type } from "typebox"; export function installRepairGate(pi, { workflow, memory, waitForReviewer, toTextResult }) { pi.registerTool({ name: "reviewer_archive_repair", label: "Resolve Memory / Evidence conflict", description: "Present closed archive correction choices with current and resulting content. " + "Pass a reason and options (id, label, archive memory|evidence, target_id, revision, " + "complete content). Only the human selects the persistent correction. " + "Resume with repair_id from tht memory repairs --session. Never run repair-apply yourself. " + "This gate does not approve or advance the current workflow phase.", parameters: Type.Object({ session: Type.String(), repair_id: Type.Optional(Type.String()), proposal: Type.Optional(Type.Object({ reason: Type.String(), options: Type.Array(Type.Object({ id: Type.String(), label: Type.String(), archive: Type.Union([Type.Literal("memory"), Type.Literal("evidence")]), target_id: Type.String(), revision: Type.String(), content: Type.Record(Type.String(), Type.Unknown()), }), { minItems: 1, maxItems: 5 }), })), }), async execute(_id, params, _signal, _update, ctx) { workflow.activate(); try { if (!!params.repair_id === !!params.proposal) return toTextResult("Supply either a new proposal or an existing repair_id."); const run = args => JSON.parse(memory.execute(ctx, [...args, "--session", params.session, "--json"])); let repair = params.repair_id ? run(["repair-show", "--repair-id", params.repair_id]) : run(["repair-prepare", "--proposal-json", JSON.stringify(params.proposal)]); let error; while (true) { const response = await waitForReviewer(ctx, { id: `u${Date.now()}`, schema_version: 1, phase: workflow.phase(ctx, params.session).id, widget: "archive-repair", title: "Resolve archive conflict", repair, error, reserved: ["other", "back", "exit"], }); if (response.control) return toTextResult(response.control === "freetext" ? `Reviewer feedback: ${response.text}. Reformulate the repair choices. ` + `Existing receipt ${repair.repair_id}: ${repair.status}.` : `Reviewer requested ${response.control}. Repair ${repair.repair_id}: ${repair.status}.`); const selected = response.choices; if (!Array.isArray(selected) || selected.length !== 1) return toTextResult("Invalid archive repair selection. Present the gate again."); const choice = selected[0]; if (choice === "continue" && (repair.choice || !repair.can_apply)) return toTextResult(JSON.stringify({ repair_id: repair.repair_id, status: repair.status, saved: repair.saved, indexed: repair.indexed, choice: repair.choice, correction: repair.options.find(o => o.id === repair.choice)?.content, next: "Continue the normal question review gates. Archive status above is authoritative." })); if (choice !== "reject" && !repair.options.some(o => o.id === choice)) return toTextResult("Select only a displayed repair choice."); try { repair = run(["repair-apply", "--repair-id", repair.repair_id, "--choice", choice]); error = undefined; if (repair.status === "rejected") return toTextResult("All repair proposals were rejected as inadequate. " + "No archive was changed. Reformulate the options for the reviewer."); } catch (failure) { error = "The correction could not complete. Review the current archive state before retrying."; repair = run(["repair-show", "--repair-id", repair.repair_id]); } } } catch (error) { return toTextResult(`[reviewer_archive_repair] ${String(error.stderr || error.message || error).trim()}`); } }, }); }