From 8636e745e6118fd1766ecd1ac8131a3ef581f166 Mon Sep 17 00:00:00 2001 From: robo7 Date: Fri, 13 Mar 2026 22:45:37 +0800 Subject: [PATCH] fix: ignore backup artifacts in usage transcript scan --- src/infra/session-cost-usage.test.ts | 7 ++++++- src/infra/session-cost-usage.ts | 28 ++++++++++++++++++++++++---- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/src/infra/session-cost-usage.test.ts b/src/infra/session-cost-usage.test.ts index bacb2ead581..15dc30e4f6e 100644 --- a/src/infra/session-cost-usage.test.ts +++ b/src/infra/session-cost-usage.test.ts @@ -241,6 +241,8 @@ describe("session cost usage", () => { "sess-active.jsonl", "sess-reset.jsonl.reset.2026-03-13T10-05-00.000Z", "sess-deleted.jsonl.deleted.2026-03-13T10-10-00.000Z", + "sess-backup.jsonl.bak.2026-03-13T10-20-00.000Z", + "sessions.json.bak.1737420882", ]; for (const [i, name] of files.entries()) { @@ -286,6 +288,7 @@ describe("session cost usage", () => { ["sess-active.jsonl", "active hello"], ["sess-reset.jsonl.reset.2026-03-13T10-05-00.000Z", "reset hello"], ["sess-deleted.jsonl.deleted.2026-03-13T10-10-00.000Z", "deleted hello"], + ["foo.jsonl.bar.jsonl.reset.2026-03-13T10-15-00.000Z", "compound hello"], ] as const; for (const [name, text] of files) { @@ -309,11 +312,13 @@ describe("session cost usage", () => { startMs: new Date("2026-03-13T00:00:00.000Z").getTime(), }); const ids = sessions.map((session) => session.sessionId).sort(); - expect(ids).toEqual(["sess-active", "sess-deleted", "sess-reset"]); + expect(ids).toEqual(["foo.jsonl.bar", "sess-active", "sess-deleted", "sess-reset"]); const reset = sessions.find((session) => session.sessionId === "sess-reset"); const deleted = sessions.find((session) => session.sessionId === "sess-deleted"); + const compound = sessions.find((session) => session.sessionId === "foo.jsonl.bar"); expect(reset?.firstUserMessage).toContain("reset hello"); expect(deleted?.firstUserMessage).toContain("deleted hello"); + expect(compound?.firstUserMessage).toContain("compound hello"); }); }); diff --git a/src/infra/session-cost-usage.ts b/src/infra/session-cost-usage.ts index e4b9b4af466..3cf6385e295 100644 --- a/src/infra/session-cost-usage.ts +++ b/src/infra/session-cost-usage.ts @@ -9,6 +9,10 @@ import { resolveSessionFilePath, resolveSessionTranscriptsDirForAgent, } from "../config/sessions/paths.js"; +import { + isPrimarySessionTranscriptFileName, + parseSessionArchiveTimestamp, +} from "../config/sessions/artifacts.js"; import type { SessionEntry } from "../config/sessions/types.js"; import { stripEnvelope, stripMessageIdHints } from "../shared/chat-envelope.js"; import { countToolResults, extractToolCallNames } from "../utils/transcript-tools.js"; @@ -53,14 +57,30 @@ export type { SessionUsageTimeSeries, } from "./session-cost-usage.types.js"; -const SESSION_TRANSCRIPT_FILE_RE = /\.jsonl(?:\.(?:reset|deleted)\..+)?$/; +const isArchivedUsageTranscriptFilename = (name: string): boolean => + parseSessionArchiveTimestamp(name, "reset") !== null || + parseSessionArchiveTimestamp(name, "deleted") !== null; const isSessionTranscriptFilename = (name: string): boolean => - SESSION_TRANSCRIPT_FILE_RE.test(name); + isPrimarySessionTranscriptFileName(name) || isArchivedUsageTranscriptFilename(name); const deriveSessionIdFromFilename = (name: string): string => { - const match = name.match(/^(.*?\.jsonl)(?:\.(?:reset|deleted)\..+)?$/); - return match ? match[1].slice(0, -6) : name.replace(/\.jsonl$/, ""); + for (const reason of ["reset", "deleted"] as const) { + const archivedAt = parseSessionArchiveTimestamp(name, reason); + if (archivedAt === null) { + continue; + } + const marker = `.${reason}.`; + const index = name.lastIndexOf(marker); + if (index >= 0) { + const base = name.slice(0, index); + if (base.endsWith(".jsonl")) { + return base.slice(0, -6); + } + return base; + } + } + return name.endsWith(".jsonl") ? name.slice(0, -6) : name; }; const emptyTotals = (): CostUsageTotals => ({