fix: allow continuing after interrupting assistant (#178)
## Description This PR fixes the issue where the CLI can't continue after interrupting the assistant with ESC ESC (Fixes #114). The problem was caused by duplicate code in the `cancel()` method and improper state reset after cancellation. ## Changes - Fixed duplicate code in the `cancel()` method of the `AgentLoop` class - Added proper reset of the `currentStream` property in the `cancel()` method - Created a new `AbortController` after aborting the current one to ensure future tool calls work - Added a system message to indicate the interruption to the user - Added a comprehensive test to verify the fix ## Benefits - Users can now continue using the CLI after interrupting the assistant - Improved user experience by providing feedback when interruption occurs - Better state management in the agent loop ## Testing - Added a dedicated test that verifies the agent can process new input after cancellation - Manually tested the fix by interrupting the assistant and confirming that new input is processed correctly --------- Signed-off-by: crazywolf132 <crazywolf132@gmail.com>
This commit is contained in:
@@ -108,6 +108,9 @@ export class AgentLoop {
|
||||
if (this.terminated) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset the current stream to allow new requests
|
||||
this.currentStream = null;
|
||||
if (isLoggingEnabled()) {
|
||||
log(
|
||||
`AgentLoop.cancel() invoked – currentStream=${Boolean(
|
||||
@@ -122,22 +125,16 @@ export class AgentLoop {
|
||||
)?.controller?.abort?.();
|
||||
|
||||
this.canceled = true;
|
||||
|
||||
// Abort any in-progress tool calls
|
||||
this.execAbortController?.abort();
|
||||
|
||||
// Create a new abort controller for future tool calls
|
||||
this.execAbortController = new AbortController();
|
||||
if (isLoggingEnabled()) {
|
||||
log("AgentLoop.cancel(): execAbortController.abort() called");
|
||||
}
|
||||
|
||||
// If we have *not* seen any function_call IDs yet there is nothing that
|
||||
// needs to be satisfied in a follow‑up request. In that case we clear
|
||||
// the stored lastResponseId so a subsequent run starts a clean turn.
|
||||
if (this.pendingAborts.size === 0) {
|
||||
try {
|
||||
this.onLastResponseId("");
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: We intentionally do *not* clear `lastResponseId` here. If the
|
||||
// stream produced a `function_call` before the user cancelled, OpenAI now
|
||||
// expects a corresponding `function_call_output` that must reference that
|
||||
@@ -155,11 +152,6 @@ export class AgentLoop {
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: We intentionally do *not* clear `lastResponseId` here. If the
|
||||
// stream produced a `function_call` before the user cancelled, OpenAI now
|
||||
// expects a corresponding `function_call_output` that must reference that
|
||||
// very same response ID. We therefore keep the ID around so the
|
||||
// follow‑up request can still satisfy the contract.
|
||||
this.onLoading(false);
|
||||
|
||||
/* Inform the UI that the run was aborted by the user. */
|
||||
@@ -400,8 +392,10 @@ export class AgentLoop {
|
||||
// identified and dropped.
|
||||
const thisGeneration = ++this.generation;
|
||||
|
||||
// Reset cancellation flag for a fresh run.
|
||||
// Reset cancellation flag and stream for a fresh run.
|
||||
this.canceled = false;
|
||||
this.currentStream = null;
|
||||
|
||||
// Create a fresh AbortController for this run so that tool calls from a
|
||||
// previous run do not accidentally get signalled.
|
||||
this.execAbortController = new AbortController();
|
||||
|
||||
Reference in New Issue
Block a user