/** * The execute_python gate: matching a caller's rulings against the candidate * actions a tool search returned (#960, #938). * * The gate is the front door to this project's feedback pipeline. Every Python * escape hatch is a recorded gap, and the record only exists if the agent gets * through the gate and runs the code. Two separate reporters found that it * could not be satisfied at all, for two different reasons: * * 1. The refusal printed the qualified spelling, `editor(invoke_function)`, * and then matched what came back against the bare `invoke_function`. * Passing back exactly the strings the tool had just printed did not * satisfy it. Matching is now spelling-insensitive: bare, `tool(action)` * and `tool.action` all resolve to the same key. * * 2. The candidate set is recomputed from the taskSummary on every call, so * rewording the summary swapped in candidates that then had to be ruled out * as well. One reporter needed five re-justifications for one unchanged * intent. Accepted rulings are now remembered for the session, so a reword * can only ever add the candidates that are genuinely new, and the refusal * hands back the exact `ruledOut` array to send. * * A reason shorter than MIN_REASON_LENGTH used to be dropped in silence, which * left the caller re-sending an entry the gate had thrown away without saying * so. Those are now reported back by name. */ import { type WorkaroundScopeSource } from "./workaround-tracker.js"; /** A ruling has to say something. Twelve characters is the long-standing bar. */ export declare const MIN_REASON_LENGTH = 12; /** A candidate action the tool search returned for the caller's taskSummary. */ export interface GateCandidate { tool: string; action: string; description?: string; score?: number; } /** An entry the gate refused to count, and why, so the caller can fix it. */ export interface RejectedRuling { entry: string; why: string; } export interface ParsedRulings { /** Normalized action key to the reason given. */ accepted: Map; rejected: RejectedRuling[]; } /** * Reduce any spelling of an action reference to the bare action name. * * Accepted: `invoke_function`, `editor(invoke_function)`, `editor.invoke_function`, * `editor:invoke_function`, `editor/invoke_function`, with any surrounding * whitespace and in any case. Deliberately liberal: this is a gate that has to * be passable, and an agent that names the right action in the wrong shape has * done the thinking the gate exists to force. */ export declare function ruledOutKey(raw: unknown): string; /** Every spelling of a candidate the gate will print, for a copy-paste answer. */ export declare function candidateSpellings(c: GateCandidate): string[]; /** Read the caller's `ruledOut` parameter, separating what counts from what does not. */ export declare function parseRulings(raw: unknown): ParsedRulings; /** * Fold this call's rulings into what the session already knows, and return the * union. This is what makes the gate converge: a reworded taskSummary produces * a different candidate set, and without this every earlier justification would * have to be typed again. */ export declare function recordRulings(ctx: WorkaroundScopeSource | undefined, rulings: Map): Map; /** What this session has already ruled out. Read-only view. */ export declare function knownRulings(ctx?: WorkaroundScopeSource): Map; /** Drop every partition. Test-only. */ export declare function resetRulings(): void; export interface GateVerdict { /** Candidates with no accepted ruling. Empty means Python may run. */ unresolved: GateCandidate[]; /** Entries the caller sent that did not count, with the reason they did not. */ rejected: RejectedRuling[]; /** Candidates already covered, in the spelling the caller will recognise. */ satisfied: string[]; /** The exact array to send back, one entry per unresolved candidate. */ ruledOutTemplate: Array<{ action: string; reason: string; }>; } /** * Decide whether the caller has cleared the gate, and if not, say precisely * what is still needed. */ export declare function evaluateGate(candidates: GateCandidate[], rawRuledOut: unknown, ctx?: WorkaroundScopeSource): GateVerdict; /** * The refusal text. It has one job: make the next call the successful one, so * it states the accepted spellings, names what is still missing, and shows the * array to send rather than describing it. */ export declare function gateRefusalMessage(taskSummary: string, candidates: GateCandidate[], verdict: GateVerdict): string;