From 0ee76b2ea08eb4e5f1d9992750674431cc045c74 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Fri, 29 May 2026 09:55:40 +0700 Subject: [PATCH] Cover timeline epoch reset behavior --- .../timeline/session-stream-reducers.test.ts | 35 +++++++++++++++++++ .../src/server/agent/agent-manager.test.ts | 14 ++++++++ 2 files changed, 49 insertions(+) diff --git a/packages/app/src/timeline/session-stream-reducers.test.ts b/packages/app/src/timeline/session-stream-reducers.test.ts index e17244b3b..0f5bda598 100644 --- a/packages/app/src/timeline/session-stream-reducers.test.ts +++ b/packages/app/src/timeline/session-stream-reducers.test.ts @@ -388,6 +388,41 @@ describe("processTimelineResponse", () => { expect(getUserTexts(result.tail)).toHaveLength(0); }); + it("treats a stale epoch reset as a replacement with the new epoch window", () => { + const oldTail: StreamItem[] = [makeAssistantItem("old epoch message", "old-assistant")]; + const oldHead: StreamItem[] = [makeAssistantItem("old live head", "old-head")]; + + const result = processTimelineResponse({ + ...baseTimelineInput, + currentTail: oldTail, + currentHead: oldHead, + currentCursor: { epoch: "old-epoch", startSeq: 1, endSeq: 95 }, + payload: { + ...baseTimelineInput.payload, + reset: true, + epoch: "new-epoch", + startCursor: { seq: 101 }, + endCursor: { seq: 105 }, + hasOlder: true, + entries: [ + makeTimelineEntry(101, "new one"), + makeTimelineEntry(102, "new two"), + makeTimelineEntry(103, "new three"), + makeTimelineEntry(104, "new four"), + makeTimelineEntry(105, "new five"), + ], + }, + }); + + expect(getAssistantTexts(result.tail)).toEqual(["new onenew twonew threenew fournew five"]); + expect(result.head).toEqual([]); + expect(result.cursor).toEqual({ epoch: "new-epoch", startSeq: 101, endSeq: 105 }); + expect(result.sideEffects).not.toContainEqual({ + type: "catch_up", + cursor: { epoch: "old-epoch", endSeq: 95 }, + }); + }); + it("performs bootstrap tail init with catch-up side effect", () => { const result = processTimelineResponse({ ...baseTimelineInput, diff --git a/packages/server/src/server/agent/agent-manager.test.ts b/packages/server/src/server/agent/agent-manager.test.ts index 7c428cdd0..d4824e0f8 100644 --- a/packages/server/src/server/agent/agent-manager.test.ts +++ b/packages/server/src/server/agent/agent-manager.test.ts @@ -2214,6 +2214,20 @@ test("fetchTimeline returns a bounded reset window when cursor epoch is stale", expect(result.rows[0]?.seq).toBe(3); expect(result.rows[result.rows.length - 1]?.seq).toBe(3); expect(result.hasOlder).toBe(true); + + const older = manager.fetchTimeline(snapshot.id, { + direction: "before", + cursor: { + epoch: result.epoch, + seq: result.rows[0]?.seq ?? 0, + }, + limit: 1, + }); + + expect(older.reset).toBe(false); + expect(older.rows).toHaveLength(1); + expect(older.rows[0]?.seq).toBe(2); + expect(older.hasOlder).toBe(true); }); test("getTimelineRows falls back to the in-memory timeline when no durable store is configured", async () => {