7.5 KiB
7.5 KiB
resolve
Finalizes a pending action by applying or discarding it.
Source
- Entry:
packages/coding-agent/src/tools/resolve.ts - Model-facing prompt:
packages/coding-agent/src/prompts/tools/resolve.md - Key collaborators:
docs/resolve-tool-runtime.md— preview/apply runtime referencepackages/coding-agent/src/extensibility/custom-tools/loader.ts— forwards custom pending actions into the queuepackages/coding-agent/src/tools/ast-edit.ts— built-in preview producer examplepackages/coding-agent/src/session/agent-session.ts— tool-choice queue, standing resolve handler, and invoker access
Inputs
| Field | Type | Required | Description |
|---|---|---|---|
action |
`"apply" | "discard"` | Yes |
reason |
string |
Yes | Required explanation passed through to the handler. |
extra |
Record<string, unknown> |
No | Free-form metadata passed through to the handler. Plan approval uses this for data such as a title slug; preview-style actions usually ignore it. |
Outputs
- Single-shot result.
execute()returns whatever the queued or standing invoker returns, withdetailswrapped/augmented to include:actionreasonextra?sourceToolName?label?sourceResultDetails?— originalresult.detailsfrom the apply/reject callback when present
- If
discardhas no custom reject callback, or the reject callback returnsundefined, the default success payload isDiscarded: <label>. Reason: <reason>. - The TUI renderer is inline and merges call+result into one block.
Flow
- Preview-producing code can call
queueResolveHandler(...)with a label, source tool name,apply(reason, extra?)callback, and optionalreject(reason, extra?)callback. - Modes can also register a standing resolve handler through
session.setStandingResolveHandler(...);resolve.execute()consults it only when no queued invoker is active. queueResolveHandler(...)registers a non-forcing pending invoker on the session's tool-choice queue under a uniquepending-action:<sourceTool>:<seq>id. It does NOT force a tool choice and does NOT steer a reminder.- While a preview is pending, the session's
getToolChoice(nextToolChoiceDirective) returns aSoftToolRequirement(toolName: "resolve") carrying the resolve reminder — a non-consuming peek. The agent runtime injects the reminder once and forcestool_choice: resolvefor one turn only if the model declines (seedocs/resolve-tool-runtime.md). The reminder text is:
<system-reminder>
This is a preview. Call the `resolve` tool to apply or discard these changes.
</system-reminder>
- When
resolve.execute()runs, it wraps the call inuntilAborted(...)and dispatches viasession.peekQueueInvoker?.() ?? session.peekPendingInvoker?.() ?? session.peekStandingResolveHandler?.(). - If no invoker exists,
applythrowsToolError("No pending action to resolve. Nothing to apply or discard.");discardinstead returns a success payloadNothing to discard; no pending action remains.because the desired end-state (no staged change) already holds. - Otherwise it invokes the current handler with the full params object.
runResolveInvocation(...)builds base details fromaction,reason,extra,sourceToolName, andlabel.- For
apply, it calls the producer'sapply(reason, extra)callback. - If
applythrows,runResolveInvocation(...)callsonApplyErrorwhen present. The pending-preview integration uses this to re-register the same pending invoker (same id) so the action remains pending for discard or retry. Non-ToolErrorexceptions are wrapped asToolError("Apply failed: <message>"). - For
discard, it callsreject(reason, extra)when provided. If no reject callback exists or it returnsundefined,resolvefabricates the default discard message. - Before returning callback results, it merges resolve metadata into
result.detailsso renderer/UI code can show the action, label, and originating tool.
Modes / Variants
apply: runs the pending action'sapply(reason, extra?)callback and returns its content.discardwith reject callback: runsreject(reason, extra?)and returns that callback's content when non-undefined.discardwithout reject callback, or with a reject callback returningundefined: returns the built-inDiscarded: ...text payload.discardwith no pending action at all: returnsNothing to discard; no pending action remains.as a success result.- Pending invoker: a non-forcing preview invoker in the pending-invoker registry (separate from the consuming directive queue), used by preview producers such as
ast_edit. - Standing handler: long-lived mode-owned handler, used as a fallback when no queue invoker is active.
Side Effects
- Session state
- Consumes or invokes the current pending action through the pending invoker, tool-choice queue, or standing handler;
resolvedoes not maintain its own stack. - Does not steer a reminder or force a tool choice for previews — the reminder rides a non-forcing
SoftToolRequirementand the agent runtime forcesresolveonly on non-compliance. - On queued apply failure, requeues the same pending action before rethrowing so the model can discard or retry instead of losing the pending preview.
- Consumes or invokes the current pending action through the pending invoker, tool-choice queue, or standing handler;
- User-visible prompts / interactive UI
- The visible effect depends on the preview-producing tool and the resolve renderer.
- Renderer result blocks show
Accept,Discard, orFailed, include the pending action label, and display the reason.
- Background work / cancellation
untilAborted(...)lets abort signals interrupt resolution before or while the callback awaits.
Limits & Caps
- Hidden tool:
ResolveTool.hidden = true, and normal requested-tool filtering removesresolve;createTools(...)adds it separately as a hidden tool. - Per call,
resolveconsults the in-flight hard-directive queue invoker (session.peekQueueInvoker()), then the non-forcing pending-preview invoker (session.peekPendingInvoker()), then a standing handler (session.peekStandingResolveHandler()). - There is no independent depth cap in this tool; pending previews stack as unique-keyed invokers (resolved head-first), separate from the consuming directive queue and the mode-owned standing handler lifecycle.
Errors
applywith no pending action or standing handler: throwsToolError("No pending action to resolve. Nothing to apply or discard.").discardin the same situation succeeds withNothing to discard; no pending action remains.instead of erroring.applycallback throwsToolError: the originalToolErrorpropagates.applycallback throws any other value:resolvewraps it asToolError("Apply failed: <message>")after runningonApplyErrorwhen present.rejectcallback exceptions propagate without the apply-specific wrapper.- Aborts during
untilAborted(...)surface as the underlying abort error from the utility.
Notes
reasonandextraare passed through;resolveitself does not interpret them.queueResolveHandler(...)is the canonical built-in preview integration point; custom tools usepushPendingAction(...), which the loader forwards into the same mechanism.- Standing handlers let modes accept
resolveinvocations without forcing the tool choice every turn. sourceResultDetailsis added only when the apply/reject callback returned a non-nulldetailsfield; custom pending-actiondetailsare not forwarded automatically by the loader.