summaryrefslogtreecommitdiffhomepage
path: root/src/core/chunks/reducer.test.ts
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-22 14:07:30 +0900
committerAdam Malczewski <[email protected]>2026-06-22 14:07:30 +0900
commit4e1d041c0ee34fa1b74b0d5ecbd432cdacf696e9 (patch)
tree1d4b482b2193631d96be3eb107974c1c6133220d /src/core/chunks/reducer.test.ts
parent06185717c61343e732002d782294f7de54c183b0 (diff)
downloaddispatch-web-4e1d041c0ee34fa1b74b0d5ecbd432cdacf696e9.tar.gz
dispatch-web-4e1d041c0ee34fa1b74b0d5ecbd432cdacf696e9.zip
feat: trim chunks during generation via step-complete syncTail (CR-6)
The backend now persists chunks at step boundaries during generation (CR-6). The FE calls syncTail on each step-complete event to fetch the newly committed chunks. applyHistory clears the provisional array when new committed chunks arrive mid-generation (they're duplicates of what was folded from live events). The accumulating chunk (current in-progress step) is kept. This means trimTranscript can now drop oldest committed chunks uniformly during a long turn — no unbounded provisional growth. The browser never holds more than chatLimit chunks, even mid-generation. 3 new tests: clears provisional on new committed during generation, keeps provisional when no new chunks, keeps accumulating when clearing. 689 tests green.
Diffstat (limited to 'src/core/chunks/reducer.test.ts')
-rw-r--r--src/core/chunks/reducer.test.ts80
1 files changed, 80 insertions, 0 deletions
diff --git a/src/core/chunks/reducer.test.ts b/src/core/chunks/reducer.test.ts
index a346545..c479488 100644
--- a/src/core/chunks/reducer.test.ts
+++ b/src/core/chunks/reducer.test.ts
@@ -551,6 +551,86 @@ 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", () => {