diff --git a/src/cli/App.tsx b/src/cli/App.tsx index e8cb523..4c71464 100644 --- a/src/cli/App.tsx +++ b/src/cli/App.tsx @@ -4809,6 +4809,7 @@ export default function App({ conversationIdRef.current; userCancelledRef.current = true; // Prevent dequeue setStreaming(false); + resetTrajectoryBases(); setIsExecutingTool(false); toolResultsInFlightRef.current = false; refreshDerived(); @@ -4887,6 +4888,7 @@ export default function App({ // Stop streaming and show error message (unless tool calls were cancelled, // since the tool result will show "Interrupted by user") setStreaming(false); + resetTrajectoryBases(); toolResultsInFlightRef.current = false; if (!toolsCancelled) { appendError(INTERRUPT_MESSAGE, true); @@ -4988,6 +4990,7 @@ export default function App({ autoHandledResults, autoDeniedApprovals, queueApprovalResults, + resetTrajectoryBases, ]); // Keep ref to latest processConversation to avoid circular deps in useEffect diff --git a/src/tests/cli/interrupt-recovery-wiring.test.ts b/src/tests/cli/interrupt-recovery-wiring.test.ts index 17eeade..5cda8dd 100644 --- a/src/tests/cli/interrupt-recovery-wiring.test.ts +++ b/src/tests/cli/interrupt-recovery-wiring.test.ts @@ -18,4 +18,58 @@ describe("interrupt recovery alert wiring", () => { "pendingInterruptRecoveryConversationIdRef.current = null;", ); }); + + test("resets trajectory bases in tool-interrupt eager-cancel branch", () => { + const appPath = fileURLToPath( + new URL("../../cli/App.tsx", import.meta.url), + ); + const source = readFileSync(appPath, "utf-8"); + + const start = source.indexOf("if (\n isExecutingTool"); + const end = source.indexOf("if (!streaming || interruptRequested)"); + + expect(start).toBeGreaterThan(-1); + expect(end).toBeGreaterThan(start); + + const segment = source.slice(start, end); + expect(segment).toContain("setStreaming(false);"); + expect(segment).toContain("resetTrajectoryBases();"); + }); + + test("resets trajectory bases in regular eager-cancel branch", () => { + const appPath = fileURLToPath( + new URL("../../cli/App.tsx", import.meta.url), + ); + const source = readFileSync(appPath, "utf-8"); + + const start = source.indexOf("if (EAGER_CANCEL) {"); + const end = source.indexOf("} else {\n setInterruptRequested(true);"); + + expect(start).toBeGreaterThan(-1); + expect(end).toBeGreaterThan(start); + + const segment = source.slice(start, end); + expect(segment).toContain("setStreaming(false);"); + expect(segment).toContain("resetTrajectoryBases();"); + }); + + test("includes resetTrajectoryBases in handleInterrupt dependency array", () => { + const appPath = fileURLToPath( + new URL("../../cli/App.tsx", import.meta.url), + ); + const source = readFileSync(appPath, "utf-8"); + + const start = source.indexOf( + "const handleInterrupt = useCallback(async () => {", + ); + const end = source.indexOf( + "const processConversationRef = useRef(processConversation);", + ); + + expect(start).toBeGreaterThan(-1); + expect(end).toBeGreaterThan(start); + + const segment = source.slice(start, end); + expect(segment).toContain("resetTrajectoryBases,"); + }); });