























@@ -20,6 +20,7 @@ import {
2020replaceSessionEntry,
2121resolveSessionTranscriptRuntimeReadTarget,
2222resolveSessionTranscriptRuntimeTarget,
23+trimSessionTranscriptForManualCompact,
2324updateSessionEntry,
2425upsertSessionEntry,
2526} from "./session-accessor.js";
@@ -543,6 +544,102 @@ describe("session accessor file-backed seam", () => {
543544expect(listSessionEntries({ storePath })).toEqual([]);
544545});
545546547+it("trims a manual compact transcript and clears stale token metadata", async () => {
548+const sessionId = "11111111-1111-4111-8111-111111111111";
549+const manualTranscriptPath = path.join(tempDir, `${sessionId}.jsonl`);
550+const scope = {
551+agentId: "main",
552+ sessionId,
553+sessionKey: "agent:main:main",
554+ storePath,
555+};
556+const contextBudgetStatus: NonNullable<SessionEntry["contextBudgetStatus"]> = {
557+schemaVersion: 1,
558+source: "pre-prompt-estimate",
559+updatedAt: 90,
560+provider: "openai",
561+model: "gpt-5.5",
562+route: "fits",
563+shouldCompact: false,
564+estimatedPromptTokens: 10,
565+contextTokenBudget: 100,
566+promptBudgetBeforeReserve: 80,
567+reserveTokens: 20,
568+effectiveReserveTokens: 20,
569+remainingPromptBudgetTokens: 70,
570+overflowTokens: 0,
571+toolResultReducibleChars: 0,
572+messageCount: 1,
573+unwindowedMessageCount: 1,
574+};
575+await upsertSessionEntry(scope, {
576+ contextBudgetStatus,
577+inputTokens: 10,
578+outputTokens: 20,
579+sessionFile: manualTranscriptPath,
580+ sessionId,
581+totalTokens: 30,
582+totalTokensFresh: true,
583+updatedAt: 100,
584+});
585+fs.writeFileSync(manualTranscriptPath, "line 1\n\nline 2\nline 3\nline 4\n", "utf-8");
586+const updates: unknown[] = [];
587+const unsubscribe = onSessionTranscriptUpdate((update) => updates.push(update));
588+589+const result = await trimSessionTranscriptForManualCompact(scope, {
590+maxLines: 2,
591+nowMs: 500,
592+});
593+594+unsubscribe();
595+expect(result).toMatchObject({ compacted: true, kept: 2 });
596+const archived = result.compacted ? result.archived : "";
597+expect(path.basename(archived)).toMatch(new RegExp(`^${sessionId}\\.jsonl\\.bak\\.`));
598+expect(fs.readFileSync(archived, "utf-8")).toBe("line 1\n\nline 2\nline 3\nline 4\n");
599+expect(fs.readFileSync(manualTranscriptPath, "utf-8")).toBe("line 3\nline 4\n");
600+const updatedEntry = loadSessionEntry(scope);
601+expect(updatedEntry).toMatchObject({
602+sessionFile: manualTranscriptPath,
603+ sessionId,
604+updatedAt: 500,
605+});
606+expect(updatedEntry?.contextBudgetStatus).toBeUndefined();
607+expect(updatedEntry?.inputTokens).toBeUndefined();
608+expect(updatedEntry?.outputTokens).toBeUndefined();
609+expect(updatedEntry?.totalTokens).toBeUndefined();
610+expect(updatedEntry?.totalTokensFresh).toBeUndefined();
611+expect(updates).toEqual([{ sessionFile: archived }]);
612+});
613+614+it("prefers the current generated transcript over a stale generated sessionFile", async () => {
615+const currentSessionId = "11111111-1111-4111-8111-111111111111";
616+const staleSessionId = "22222222-2222-4222-8222-222222222222";
617+const currentTranscriptPath = path.join(tempDir, `${currentSessionId}.jsonl`);
618+const staleTranscriptPath = path.join(tempDir, `${staleSessionId}.jsonl`);
619+const scope = {
620+agentId: "main",
621+sessionId: currentSessionId,
622+sessionKey: "agent:main:main",
623+ storePath,
624+};
625+await upsertSessionEntry(scope, {
626+sessionFile: staleTranscriptPath,
627+sessionId: currentSessionId,
628+updatedAt: 100,
629+});
630+fs.writeFileSync(currentTranscriptPath, "current one\ncurrent two\n", "utf-8");
631+fs.writeFileSync(staleTranscriptPath, "stale one\nstale two\n", "utf-8");
632+633+const result = await trimSessionTranscriptForManualCompact(scope, {
634+maxLines: 1,
635+sessionFile: staleTranscriptPath,
636+});
637+638+expect(result).toMatchObject({ compacted: true, kept: 1 });
639+expect(fs.readFileSync(currentTranscriptPath, "utf-8")).toBe("current two\n");
640+expect(fs.readFileSync(staleTranscriptPath, "utf-8")).toBe("stale one\nstale two\n");
641+});
642+546643it("rejects transcript writes without a session key or explicit file", async () => {
547644await expect(
548645appendTranscriptEvent({ sessionId: "session-1", storePath }, { type: "metadata" }),
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。