


























@@ -102,6 +102,57 @@ describe("rotateTranscriptAfterCompaction", () => {
102102expect(successor.getLabel(oldUserId)).toBeUndefined();
103103});
104104105+it("deduplicates stale pre-compaction session state", async () => {
106+const dir = await createTmpDir();
107+const manager = SessionManager.create(dir, dir);
108+109+const staleModelId = manager.appendModelChange("anthropic", "claude-sonnet-4-5");
110+const staleThinkingId = manager.appendThinkingLevelChange("low");
111+const staleSessionInfoId = manager.appendSessionInfo("stale title");
112+manager.appendCustomEntry("test-extension", { cursor: "preserved" });
113+manager.appendMessage({ role: "user", content: "old user", timestamp: 1 });
114+manager.appendMessage(makeAssistant("old assistant", 2));
115+116+manager.appendModelChange("openai", "gpt-5.2");
117+manager.appendThinkingLevelChange("high");
118+manager.appendSessionInfo("current title");
119+const firstKeptId = manager.appendMessage({ role: "user", content: "kept user", timestamp: 3 });
120+manager.appendMessage(makeAssistant("kept assistant", 4));
121+manager.appendCompaction("Summary of old user and old assistant.", firstKeptId, 5000);
122+manager.appendMessage({ role: "user", content: "post user", timestamp: 5 });
123+124+const result = await rotateTranscriptAfterCompaction({
125+sessionManager: manager,
126+sessionFile: manager.getSessionFile()!,
127+now: () => new Date("2026-04-27T12:05:00.000Z"),
128+});
129+130+expect(result.rotated).toBe(true);
131+const successor = SessionManager.open(result.sessionFile!);
132+const entries = successor.getEntries();
133+expect(entries.find((entry) => entry.id === staleModelId)).toBeUndefined();
134+expect(entries.find((entry) => entry.id === staleThinkingId)).toBeUndefined();
135+expect(entries.find((entry) => entry.id === staleSessionInfoId)).toBeUndefined();
136+expect(entries.filter((entry) => entry.type === "model_change")).toHaveLength(1);
137+expect(entries.filter((entry) => entry.type === "thinking_level_change")).toHaveLength(1);
138+expect(entries.filter((entry) => entry.type === "session_info")).toHaveLength(1);
139+expect(entries.find((entry) => entry.type === "model_change")).toMatchObject({
140+provider: "openai",
141+modelId: "gpt-5.2",
142+});
143+expect(entries).toContainEqual(
144+expect.objectContaining({
145+type: "custom",
146+customType: "test-extension",
147+data: { cursor: "preserved" },
148+}),
149+);
150+151+const context = successor.buildSessionContext();
152+expect(context.thinkingLevel).toBe("high");
153+expect(successor.getSessionName()).toBe("current title");
154+});
155+105156it("skips sessions with no compaction entry", async () => {
106157const dir = await createTmpDir();
107158const manager = SessionManager.create(dir, dir);
@@ -212,6 +263,55 @@ describe("rotateTranscriptAfterCompaction", () => {
212263expect(activeContextText).toContain("next");
213264expect(activeContextText).not.toContain("do task B instead");
214265});
266+267+it("orders preserved sibling branches after their surviving parents", async () => {
268+const dir = await createTmpDir();
269+const manager = SessionManager.create(dir, dir);
270+271+manager.appendMessage({ role: "user", content: "hello", timestamp: 1 });
272+const branchFromId = manager.appendMessage(makeAssistant("hi there", 2));
273+274+const branchSummaryId = manager.branchWithSummary(
275+branchFromId,
276+"Summary of the inactive branch.",
277+);
278+const inactiveMsgId = manager.appendMessage({
279+role: "user",
280+content: "inactive branch",
281+timestamp: 3,
282+});
283+manager.appendMessage(makeAssistant("inactive done", 4));
284+285+manager.branch(branchFromId);
286+manager.appendMessage({ role: "user", content: "active branch", timestamp: 5 });
287+manager.appendMessage(makeAssistant("active done", 6));
288+manager.appendCompaction("Summary of active work.", branchFromId, 5000);
289+const activeLeafId = manager.appendMessage({
290+role: "user",
291+content: "next active",
292+timestamp: 7,
293+});
294+295+const result = await rotateTranscriptAfterCompaction({
296+sessionManager: manager,
297+sessionFile: manager.getSessionFile()!,
298+now: () => new Date("2026-04-27T13:00:00.000Z"),
299+});
300+301+expect(result.rotated).toBe(true);
302+const successor = SessionManager.open(result.sessionFile!);
303+const entries = successor.getEntries();
304+const indexById = new Map(entries.map((entry, index) => [entry.id, index]));
305+expect(indexById.get(branchFromId)).toBeLessThan(indexById.get(branchSummaryId)!);
306+expect(indexById.get(branchSummaryId)).toBeLessThan(indexById.get(inactiveMsgId)!);
307+expect(entries.at(-1)?.id).toBe(activeLeafId);
308+expect(successor.getLeafId()).toBe(activeLeafId);
309+310+const activeContextText = JSON.stringify(successor.buildSessionContext().messages);
311+expect(activeContextText).toContain("Summary of active work.");
312+expect(activeContextText).toContain("next active");
313+expect(activeContextText).not.toContain("inactive branch");
314+});
215315});
216316217317describe("shouldRotateCompactionTranscript", () => {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。