diff --git a/packages/agent/CHANGELOG.md b/packages/agent/CHANGELOG.md index 7c04cc4c..62318b07 100644 --- a/packages/agent/CHANGELOG.md +++ b/packages/agent/CHANGELOG.md @@ -4,6 +4,7 @@ ### Fixed +- Fixed tool-call preflight to stop preparing sibling tool calls after the run is aborted ([#4276](https://github.com/earendil-works/pi/issues/4276)). - Fixed tail truncation for oversized single-line output that ends with a trailing newline ([#4715](https://github.com/earendil-works/pi/issues/4715)). - Fixed Windows Node execution environment command spawns to hide helper console windows from background processes ([#4699](https://github.com/earendil-works/pi/issues/4699)). diff --git a/packages/agent/src/agent-loop.ts b/packages/agent/src/agent-loop.ts index 7226082a..eb57039f 100644 --- a/packages/agent/src/agent-loop.ts +++ b/packages/agent/src/agent-loop.ts @@ -436,6 +436,10 @@ async function executeToolCallsSequential( await emitToolResultMessage(toolResultMessage, emit); finalizedCalls.push(finalized); messages.push(toolResultMessage); + + if (signal?.aborted) { + break; + } } return { @@ -471,6 +475,9 @@ async function executeToolCallsParallel( } satisfies FinalizedToolCallOutcome; await emitToolExecutionEnd(finalized, emit); finalizedCalls.push(finalized); + if (signal?.aborted) { + break; + } continue; } @@ -487,6 +494,9 @@ async function executeToolCallsParallel( await emitToolExecutionEnd(finalized, emit); return finalized; }); + if (signal?.aborted) { + break; + } } const orderedFinalizedCalls = await Promise.all( @@ -578,6 +588,13 @@ async function prepareToolCall( }, signal, ); + if (signal?.aborted) { + return { + kind: "immediate", + result: createErrorToolResult("Operation aborted"), + isError: true, + }; + } if (beforeResult?.block) { return { kind: "immediate", @@ -586,6 +603,13 @@ async function prepareToolCall( }; } } + if (signal?.aborted) { + return { + kind: "immediate", + result: createErrorToolResult("Operation aborted"), + isError: true, + }; + } return { kind: "prepared", toolCall, diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index d4725a75..d664d11a 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -4,6 +4,7 @@ ### Fixed +- Fixed extension `ctx.abort()` during tool-call preflight to stop later confirmations and restore queued interactive input like Escape ([#4276](https://github.com/earendil-works/pi/issues/4276)). - Fixed AgentSession retry, compaction, and event settlement to use the awaited agent lifecycle instead of a separate event queue, and added `willRetry` to `agent_end` session events. - Fixed the subagent extension's parallel mode to return useful per-task output and failed-task diagnostics to the parent model instead of 100-character previews ([#4710](https://github.com/earendil-works/pi/issues/4710)). - Fixed Windows local bash execution to hide helper console windows when launched from background SDK processes ([#4699](https://github.com/earendil-works/pi/issues/4699)). diff --git a/packages/coding-agent/src/core/agent-session.ts b/packages/coding-agent/src/core/agent-session.ts index c04791c2..362a9b9f 100644 --- a/packages/coding-agent/src/core/agent-session.ts +++ b/packages/coding-agent/src/core/agent-session.ts @@ -185,6 +185,7 @@ export interface AgentSessionConfig { export interface ExtensionBindings { uiContext?: ExtensionUIContext; commandContextActions?: ExtensionCommandContextActions; + abortHandler?: () => void; shutdownHandler?: ShutdownHandler; onError?: ExtensionErrorListener; } @@ -296,6 +297,7 @@ export class AgentSession { private _sessionStartEvent: SessionStartEvent; private _extensionUIContext?: ExtensionUIContext; private _extensionCommandContextActions?: ExtensionCommandContextActions; + private _extensionAbortHandler?: () => void; private _extensionShutdownHandler?: ShutdownHandler; private _extensionErrorListener?: ExtensionErrorListener; private _extensionErrorUnsubscriber?: () => void; @@ -2042,6 +2044,9 @@ export class AgentSession { if (bindings.commandContextActions !== undefined) { this._extensionCommandContextActions = bindings.commandContextActions; } + if (bindings.abortHandler !== undefined) { + this._extensionAbortHandler = bindings.abortHandler; + } if (bindings.shutdownHandler !== undefined) { this._extensionShutdownHandler = bindings.shutdownHandler; } @@ -2206,7 +2211,13 @@ export class AgentSession { getModel: () => this.model, isIdle: () => !this.isStreaming, getSignal: () => this.agent.signal, - abort: () => this.abort(), + abort: () => { + if (this._extensionAbortHandler) { + this._extensionAbortHandler(); + return; + } + void this.abort(); + }, hasPendingMessages: () => this.pendingMessageCount > 0, shutdown: () => { this._extensionShutdownHandler?.(); diff --git a/packages/coding-agent/src/modes/interactive/interactive-mode.ts b/packages/coding-agent/src/modes/interactive/interactive-mode.ts index 1cd75a47..616e6218 100644 --- a/packages/coding-agent/src/modes/interactive/interactive-mode.ts +++ b/packages/coding-agent/src/modes/interactive/interactive-mode.ts @@ -1478,6 +1478,9 @@ export class InteractiveMode { const uiContext = this.createExtensionUIContext(); await this.session.bindExtensions({ uiContext, + abortHandler: () => { + this.restoreQueuedMessagesToEditor({ abort: true }); + }, commandContextActions: { waitForIdle: () => this.session.agent.waitForIdle(), newSession: async (options) => { @@ -1627,7 +1630,9 @@ export class InteractiveMode { model: this.session.model, isIdle: () => !this.session.isStreaming, signal: this.session.agent.signal, - abort: () => this.session.abort(), + abort: () => { + this.restoreQueuedMessagesToEditor({ abort: true }); + }, hasPendingMessages: () => this.session.pendingMessageCount > 0, shutdown: () => { this.shutdownRequested = true;