From b3c830237206a319348a0eb7078b49e20f60883b Mon Sep 17 00:00:00 2001 From: Adam Malczewski Date: Mon, 22 Jun 2026 14:18:56 +0900 Subject: fix: trim provisional chunks during long turns (browser stays responsive) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit trimTranscript now drops oldest provisional chunks (the in-flight turn) when committed chunks are exhausted. Previously it bailed with drop=0 when committed was empty, allowing unbounded provisional growth during long generating turns (300+ chunks → browser crawls). Root cause of the syncTail approach failing: the kernel emits step-complete (line 360) BEFORE calling onStepComplete (line 542) — chunks are persisted only after tool results come back, not when step-complete fires. So syncTail on step-complete found nothing. Reverted the applyHistory + syncTail-on-step-complete changes from 4e1d041. The new approach is simpler: trim provisional directly in trimTranscript. Dropped chunks are lost temporarily (no Show Earlier) but come back as committed when the turn seals and syncTail fetches everything from the server. 686 tests green. --- src/core/chunks/reducer.test.ts | 80 ----------------------------------------- 1 file changed, 80 deletions(-) (limited to 'src/core/chunks/reducer.test.ts') diff --git a/src/core/chunks/reducer.test.ts b/src/core/chunks/reducer.test.ts index c479488..a346545 100644 --- a/src/core/chunks/reducer.test.ts +++ b/src/core/chunks/reducer.test.ts @@ -551,86 +551,6 @@ describe("applyHistory", () => { expect(s.committed).toHaveLength(2); expect(s.committed.map((c) => c.seq)).toEqual([1, 2]); }); - - it("clears provisional when new committed chunks arrive during generation (CR-6)", () => { - let s = initialState(); - s = foldEvent(s, turnStart("t1")); - s = foldEvent(s, textDelta("t1", "hello")); - s = foldEvent(s, toolCall("t1", "tc1", "run_shell", {})); - s = foldEvent(s, toolResult("t1", "tc1", "run_shell", "output")); - expect(s.generating).toBe(true); - expect(s.provisional.length).toBeGreaterThan(0); - - s = applyHistory(s, [ - storedChunk(1, "assistant", { type: "text", text: "hello" }), - storedChunk(2, "assistant", { - type: "tool-call", - toolCallId: "tc1", - toolName: "run_shell", - input: {}, - stepId: "s0" as StepId, - }), - storedChunk(3, "tool", { - type: "tool-result", - toolCallId: "tc1", - toolName: "run_shell", - content: "output", - isError: false, - stepId: "s0" as StepId, - }), - ]); - - expect(s.provisional).toEqual([]); - expect(s.generating).toBe(true); - expect(s.committed).toHaveLength(3); - }); - - it("keeps provisional when no new committed chunks arrive during generation", () => { - let s = initialState(); - s = applyHistory(s, [storedChunk(1, "user", { type: "text", text: "q" })]); - s = foldEvent(s, turnStart("t1")); - s = foldEvent(s, textDelta("t1", "hello")); - s = foldEvent(s, toolCall("t1", "tc1", "run_shell", {})); - // At this point: provisional has [text "hello", tool-call] - expect(s.provisional.length).toBeGreaterThan(0); - - // applyHistory with chunks already in committed — no new additions - s = applyHistory(s, [storedChunk(1, "user", { type: "text", text: "q" })]); - - expect(s.provisional.length).toBeGreaterThan(0); - }); - - it("keeps accumulating when clearing provisional during generation (CR-6)", () => { - let s = initialState(); - s = applyHistory(s, [storedChunk(1, "user", { type: "text", text: "q" })]); - s = foldEvent(s, turnStart("t1")); - s = foldEvent(s, toolCall("t1", "tc1", "run_shell", {})); - s = foldEvent(s, toolResult("t1", "tc1", "run_shell", "output")); - // Start accumulating text for the NEXT step - s = foldEvent(s, textDelta("t1", "streaming...")); - expect(s.accumulating).not.toBeNull(); - - s = applyHistory(s, [ - storedChunk(2, "assistant", { - type: "tool-call", - toolCallId: "tc1", - toolName: "run_shell", - input: {}, - stepId: "s0" as StepId, - }), - storedChunk(3, "tool", { - type: "tool-result", - toolCallId: "tc1", - toolName: "run_shell", - content: "output", - isError: false, - stepId: "s0" as StepId, - }), - ]); - - expect(s.provisional).toEqual([]); - expect(s.accumulating).not.toBeNull(); - }); }); describe("selectChunks", () => { -- cgit v1.2.3