feat(coding-agent): introduced resolve tool for AST edit preview confirmation with reasoning

- Added `resolve` tool to apply or discard pending AST edit previews with required reasoning.
- Changed `ast_edit` tool to always return previews by default; removed `preview` parameter.
- Added `getToolChoice` callback option to dynamically override tool choice per LLM call.
- Implemented PendingActionStore for managing deferred tool action state across agent sessions.
- Updated agent loop to support dynamic tool choice resolution via optional callback.
This commit is contained in:
can1357
2026-03-01 02:35:43 +01:00
parent a065662cd9
commit 67e1aa40bf
16 changed files with 481 additions and 24 deletions
@@ -86,6 +86,7 @@ import ttsrInterruptTemplate from "../prompts/system/ttsr-interrupt.md" with { t
import type { SecretObfuscator } from "../secrets/obfuscator";
import { outputMeta } from "../tools/output-meta";
import { resolveToCwd } from "../tools/path-utils";
import type { PendingActionStore } from "../tools/pending-action";
import { getLatestTodoPhasesFromEntries, type TodoItem, type TodoPhase } from "../tools/todo-write";
import { parseCommandArgs } from "../utils/command-args";
import { resolveFileDisplayMode } from "../utils/file-display-mode";
@@ -177,6 +178,8 @@ export interface AgentSessionConfig {
forceCopilotAgentInitiator?: boolean;
/** Secret obfuscator for deobfuscating streaming edit content */
obfuscator?: SecretObfuscator;
/** Pending action store for preview/apply workflows */
pendingActionStore?: PendingActionStore;
}
/** Options for AgentSession.prompt() */
@@ -368,6 +371,7 @@ export class AgentSession {
#streamingEditFileCache = new Map<string, string>();
#promptInFlight = false;
#obfuscator: SecretObfuscator | undefined;
#pendingActionStore: PendingActionStore | undefined;
#promptGeneration = 0;
#providerSessionState = new Map<string, ProviderSessionState>();
@@ -392,6 +396,7 @@ export class AgentSession {
this.#forceCopilotAgentInitiator = config.forceCopilotAgentInitiator ?? false;
this.#obfuscator = config.obfuscator;
this.agent.providerSessionState = this.#providerSessionState;
this.#pendingActionStore = config.pendingActionStore;
this.#syncTodoPhasesFromBranch();
// Always subscribe to agent events for internal handling
@@ -683,6 +688,22 @@ export class AgentSession {
{ deliverAs: "nextTurn" },
);
}
if (!isError && this.#pendingActionStore?.hasPending) {
const reminderText = [
"<system-reminder>",
"This is a preview. Call the `resolve` tool to apply or discard these changes.",
"</system-reminder>",
].join("\n");
await this.sendCustomMessage(
{
customType: "resolve-reminder",
content: reminderText,
display: false,
details: { toolName },
},
{ deliverAs: "nextTurn" },
);
}
}
}