From 1c6911c01ff4d185341305797855976a60763dae Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 26 Apr 2026 00:11:14 +0100 Subject: [PATCH] fix: ignore compaction checkpoints in session usage --- src/config/sessions/artifacts.test.ts | 34 ++++++++ src/config/sessions/artifacts.ts | 19 +++++ src/config/sessions/disk-budget.test.ts | 61 ++++++++++++++ src/config/sessions/disk-budget.ts | 20 ++++- src/config/sessions/paths.ts | 6 +- src/config/sessions/sessions.test.ts | 8 +- .../session-compaction-checkpoints.test.ts | 81 +++++++++++++++++++ src/gateway/session-compaction-checkpoints.ts | 64 +++++++++++++-- src/infra/session-cost-usage.test.ts | 54 +++++++++++++ .../host/session-files.test.ts | 5 +- src/memory-host-sdk/host/session-files.ts | 9 +-- 11 files changed, 346 insertions(+), 15 deletions(-) diff --git a/src/config/sessions/artifacts.test.ts b/src/config/sessions/artifacts.test.ts index 53eb2526e73..8b7d27bdf2f 100644 --- a/src/config/sessions/artifacts.test.ts +++ b/src/config/sessions/artifacts.test.ts @@ -1,9 +1,11 @@ import { describe, expect, it } from "vitest"; import { formatSessionArchiveTimestamp, + isCompactionCheckpointTranscriptFileName, isPrimarySessionTranscriptFileName, isSessionArchiveArtifactName, isUsageCountedSessionTranscriptFileName, + parseCompactionCheckpointTranscriptFileName, parseUsageCountedSessionIdFromFileName, parseSessionArchiveTimestamp, } from "./artifacts.js"; @@ -21,6 +23,11 @@ describe("session artifact helpers", () => { it("classifies primary transcript files", () => { expect(isPrimarySessionTranscriptFileName("abc.jsonl")).toBe(true); expect(isPrimarySessionTranscriptFileName("keep.deleted.keep.jsonl")).toBe(true); + expect( + isPrimarySessionTranscriptFileName( + "abc.checkpoint.11111111-1111-4111-8111-111111111111.jsonl", + ), + ).toBe(false); expect(isPrimarySessionTranscriptFileName("abc.jsonl.deleted.2026-01-01T00-00-00.000Z")).toBe( false, ); @@ -38,6 +45,11 @@ describe("session artifact helpers", () => { expect(isUsageCountedSessionTranscriptFileName("abc.jsonl.bak.2026-01-01T00-00-00.000Z")).toBe( false, ); + expect( + isUsageCountedSessionTranscriptFileName( + "abc.checkpoint.11111111-1111-4111-8111-111111111111.jsonl", + ), + ).toBe(false); }); it("parses usage-counted session ids from file names", () => { @@ -51,6 +63,28 @@ describe("session artifact helpers", () => { expect(parseUsageCountedSessionIdFromFileName("abc.jsonl.bak.2026-01-01T00-00-00.000Z")).toBe( null, ); + expect( + parseUsageCountedSessionIdFromFileName( + "abc.checkpoint.11111111-1111-4111-8111-111111111111.jsonl", + ), + ).toBeNull(); + }); + + it("parses exact compaction checkpoint transcript file names", () => { + expect( + parseCompactionCheckpointTranscriptFileName( + "abc.checkpoint.11111111-1111-4111-8111-111111111111.jsonl", + ), + ).toEqual({ + sessionId: "abc", + checkpointId: "11111111-1111-4111-8111-111111111111", + }); + expect(isCompactionCheckpointTranscriptFileName("abc.checkpoint.not-a-uuid.jsonl")).toBe(false); + expect( + isCompactionCheckpointTranscriptFileName( + "abc.checkpoint.11111111-1111-4111-8111-111111111111.jsonl.deleted.2026-01-01T00-00-00.000Z", + ), + ).toBe(false); }); it("formats and parses archive timestamps", () => { diff --git a/src/config/sessions/artifacts.ts b/src/config/sessions/artifacts.ts index 14edb495d15..73d47874afc 100644 --- a/src/config/sessions/artifacts.ts +++ b/src/config/sessions/artifacts.ts @@ -2,6 +2,8 @@ export type SessionArchiveReason = "bak" | "reset" | "deleted"; const ARCHIVE_TIMESTAMP_RE = /^\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}(?:\.\d{3})?Z$/; const LEGACY_STORE_BACKUP_RE = /^sessions\.json\.bak\.\d+$/; +const COMPACTION_CHECKPOINT_TRANSCRIPT_RE = + /^(.+)\.checkpoint\.([0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})\.jsonl$/i; function hasArchiveSuffix(fileName: string, reason: SessionArchiveReason): boolean { const marker = `.${reason}.`; @@ -24,6 +26,20 @@ export function isSessionArchiveArtifactName(fileName: string): boolean { ); } +export function parseCompactionCheckpointTranscriptFileName(fileName: string): { + sessionId: string; + checkpointId: string; +} | null { + const match = COMPACTION_CHECKPOINT_TRANSCRIPT_RE.exec(fileName); + const sessionId = match?.[1]; + const checkpointId = match?.[2]; + return sessionId && checkpointId ? { sessionId, checkpointId } : null; +} + +export function isCompactionCheckpointTranscriptFileName(fileName: string): boolean { + return parseCompactionCheckpointTranscriptFileName(fileName) !== null; +} + export function isPrimarySessionTranscriptFileName(fileName: string): boolean { if (fileName === "sessions.json") { return false; @@ -31,6 +47,9 @@ export function isPrimarySessionTranscriptFileName(fileName: string): boolean { if (!fileName.endsWith(".jsonl")) { return false; } + if (isCompactionCheckpointTranscriptFileName(fileName)) { + return false; + } return !isSessionArchiveArtifactName(fileName); } diff --git a/src/config/sessions/disk-budget.test.ts b/src/config/sessions/disk-budget.test.ts index fb3fdfa19fd..6c214625c0b 100644 --- a/src/config/sessions/disk-budget.test.ts +++ b/src/config/sessions/disk-budget.test.ts @@ -81,4 +81,65 @@ describe("enforceSessionDiskBudget", () => { ); }); }); + + it("removes unreferenced compaction checkpoint artifacts under pressure", async () => { + await withTempDir({ prefix: "openclaw-disk-budget-" }, async (dir) => { + const storePath = path.join(dir, "sessions.json"); + const sessionId = "keep"; + const transcriptPath = path.join(dir, `${sessionId}.jsonl`); + const checkpointPath = path.join( + dir, + "keep.checkpoint.11111111-1111-4111-8111-111111111111.jsonl", + ); + const referencedCheckpointPath = path.join( + dir, + "keep.checkpoint.22222222-2222-4222-8222-222222222222.jsonl", + ); + const store: Record = { + "agent:main:main": { + sessionId, + updatedAt: Date.now(), + compactionCheckpoints: [ + { + checkpointId: "referenced", + sessionKey: "agent:main:main", + sessionId, + createdAt: Date.now(), + reason: "manual", + preCompaction: { + sessionId, + sessionFile: referencedCheckpointPath, + leafId: "leaf", + }, + postCompaction: { sessionId }, + }, + ], + }, + }; + await fs.writeFile(storePath, JSON.stringify(store, null, 2), "utf-8"); + await fs.writeFile(transcriptPath, "k".repeat(80), "utf-8"); + await fs.writeFile(checkpointPath, "c".repeat(5000), "utf-8"); + await fs.writeFile(referencedCheckpointPath, "r".repeat(260), "utf-8"); + + const result = await enforceSessionDiskBudget({ + store, + storePath, + maintenance: { + maxDiskBytes: 4000, + highWaterBytes: 3000, + }, + warnOnly: false, + }); + + await expect(fs.stat(transcriptPath)).resolves.toBeDefined(); + await expect(fs.stat(checkpointPath)).rejects.toThrow(); + await expect(fs.stat(referencedCheckpointPath)).resolves.toBeDefined(); + expect(result).toEqual( + expect.objectContaining({ + removedFiles: 1, + removedEntries: 0, + }), + ); + }); + }); }); diff --git a/src/config/sessions/disk-budget.ts b/src/config/sessions/disk-budget.ts index ee0dcaf676a..d17155aa16b 100644 --- a/src/config/sessions/disk-budget.ts +++ b/src/config/sessions/disk-budget.ts @@ -4,7 +4,11 @@ import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, } from "../../shared/string-coerce.js"; -import { isPrimarySessionTranscriptFileName, isSessionArchiveArtifactName } from "./artifacts.js"; +import { + isCompactionCheckpointTranscriptFileName, + isPrimarySessionTranscriptFileName, + isSessionArchiveArtifactName, +} from "./artifacts.js"; import { resolveSessionFilePath } from "./paths.js"; import type { SessionEntry } from "./types.js"; @@ -120,6 +124,7 @@ function resolveReferencedSessionTranscriptPaths(params: { store: Record; }): Set { const referenced = new Set(); + const resolvedSessionsDir = canonicalizePathForComparison(params.sessionsDir); for (const entry of Object.values(params.store)) { const resolved = resolveSessionTranscriptPathForEntry({ sessionsDir: params.sessionsDir, @@ -128,6 +133,17 @@ function resolveReferencedSessionTranscriptPaths(params: { if (resolved) { referenced.add(canonicalizePathForComparison(resolved)); } + for (const checkpoint of entry.compactionCheckpoints ?? []) { + const checkpointFile = checkpoint.preCompaction.sessionFile?.trim(); + if (!checkpointFile) { + continue; + } + const resolvedCheckpointPath = canonicalizePathForComparison(checkpointFile); + const relative = path.relative(resolvedSessionsDir, resolvedCheckpointPath); + if (relative && !relative.startsWith("..") && !path.isAbsolute(relative)) { + referenced.add(resolvedCheckpointPath); + } + } } return referenced; } @@ -259,6 +275,8 @@ export async function enforceSessionDiskBudget(params: { .filter( (file) => isSessionArchiveArtifactName(file.name) || + (isCompactionCheckpointTranscriptFileName(file.name) && + !referencedPaths.has(file.canonicalPath)) || (isPrimarySessionTranscriptFileName(file.name) && !referencedPaths.has(file.canonicalPath)), ) .toSorted((a, b) => a.mtimeMs - b.mtimeMs); diff --git a/src/config/sessions/paths.ts b/src/config/sessions/paths.ts index e8e2a222634..6141d368255 100644 --- a/src/config/sessions/paths.ts +++ b/src/config/sessions/paths.ts @@ -5,6 +5,7 @@ import { expandHomePrefix, resolveRequiredHomeDir } from "../../infra/home-dir.j import { DEFAULT_AGENT_ID, normalizeAgentId } from "../../routing/session-key.js"; import { normalizeLowercaseStringOrEmpty } from "../../shared/string-coerce.js"; import { resolveStateDir } from "../paths.js"; +import { isCompactionCheckpointTranscriptFileName } from "./artifacts.js"; function resolveAgentSessionsDir( agentId?: string, @@ -62,7 +63,10 @@ export const SAFE_SESSION_ID_RE = /^[a-z0-9][a-z0-9._-]{0,127}$/i; export function validateSessionId(sessionId: string): string { const trimmed = sessionId.trim(); - if (!SAFE_SESSION_ID_RE.test(trimmed)) { + if ( + !SAFE_SESSION_ID_RE.test(trimmed) || + isCompactionCheckpointTranscriptFileName(`${trimmed}.jsonl`) + ) { throw new Error(`Invalid session ID: ${sessionId}`); } return trimmed; diff --git a/src/config/sessions/sessions.test.ts b/src/config/sessions/sessions.test.ts index ace50ffc9f1..07d43fc9c6d 100644 --- a/src/config/sessions/sessions.test.ts +++ b/src/config/sessions/sessions.test.ts @@ -21,7 +21,13 @@ import { mergeSessionEntry, type SessionEntry } from "./types.js"; describe("session path safety", () => { it("rejects unsafe session IDs", () => { - const unsafeSessionIds = ["../etc/passwd", "a/b", "a\\b", "/abs"]; + const unsafeSessionIds = [ + "../etc/passwd", + "a/b", + "a\\b", + "/abs", + "sess.checkpoint.11111111-1111-4111-8111-111111111111", + ]; for (const sessionId of unsafeSessionIds) { expect(() => validateSessionId(sessionId), sessionId).toThrow(/Invalid session ID/); } diff --git a/src/gateway/session-compaction-checkpoints.test.ts b/src/gateway/session-compaction-checkpoints.test.ts index 63a64804bfe..28b5de4409b 100644 --- a/src/gateway/session-compaction-checkpoints.test.ts +++ b/src/gateway/session-compaction-checkpoints.test.ts @@ -5,9 +5,11 @@ import path from "node:path"; import type { AssistantMessage, UserMessage } from "@mariozechner/pi-ai"; import { SessionManager } from "@mariozechner/pi-coding-agent"; import { afterEach, describe, expect, test } from "vitest"; +import type { OpenClawConfig } from "../config/types.openclaw.js"; import { captureCompactionCheckpointSnapshot, cleanupCompactionCheckpointSnapshot, + persistSessionCompactionCheckpoint, } from "./session-compaction-checkpoints.js"; const tempDirs: string[] = []; @@ -81,4 +83,83 @@ describe("session-compaction-checkpoints", () => { expect(fsSync.existsSync(snapshot!.sessionFile)).toBe(false); expect(fsSync.existsSync(sessionFile!)).toBe(true); }); + + test("persist trims old checkpoint metadata and removes trimmed snapshot files", async () => { + const dir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-checkpoint-trim-")); + tempDirs.push(dir); + + const storePath = path.join(dir, "sessions.json"); + const sessionId = "sess"; + const sessionKey = "agent:main:main"; + const now = Date.now(); + const existingCheckpoints = Array.from({ length: 26 }, (_, index) => { + const uuid = `${String(index + 1).padStart(8, "0")}-1111-4111-8111-111111111111`; + const sessionFile = path.join(dir, `sess.checkpoint.${uuid}.jsonl`); + fsSync.writeFileSync(sessionFile, `checkpoint ${index}`, "utf-8"); + return { + checkpointId: `old-${index}`, + sessionKey, + sessionId, + createdAt: now + index, + reason: "manual" as const, + preCompaction: { + sessionId, + sessionFile, + leafId: `old-leaf-${index}`, + }, + postCompaction: { sessionId }, + }; + }); + await fs.writeFile( + storePath, + JSON.stringify( + { + [sessionKey]: { + sessionId, + updatedAt: now, + compactionCheckpoints: existingCheckpoints, + }, + }, + null, + 2, + ), + "utf-8", + ); + + const currentSnapshotFile = path.join( + dir, + "sess.checkpoint.99999999-9999-4999-8999-999999999999.jsonl", + ); + await fs.writeFile(currentSnapshotFile, "current", "utf-8"); + + const stored = await persistSessionCompactionCheckpoint({ + cfg: { + session: { store: storePath }, + agents: { list: [{ id: "main", default: true }] }, + } as OpenClawConfig, + sessionKey: "main", + sessionId, + reason: "manual", + snapshot: { + sessionId, + sessionFile: currentSnapshotFile, + leafId: "current-leaf", + }, + createdAt: now + 100, + }); + + expect(stored).not.toBeNull(); + expect(fsSync.existsSync(existingCheckpoints[0].preCompaction.sessionFile)).toBe(false); + expect(fsSync.existsSync(existingCheckpoints[1].preCompaction.sessionFile)).toBe(false); + expect(fsSync.existsSync(existingCheckpoints[2].preCompaction.sessionFile)).toBe(true); + expect(fsSync.existsSync(currentSnapshotFile)).toBe(true); + + const nextStore = JSON.parse(await fs.readFile(storePath, "utf-8")) as Record< + string, + { compactionCheckpoints?: unknown[] } + >; + expect( + Object.values(nextStore).find((entry) => entry.compactionCheckpoints)?.compactionCheckpoints, + ).toHaveLength(25); + }); }); diff --git a/src/gateway/session-compaction-checkpoints.ts b/src/gateway/session-compaction-checkpoints.ts index 45731d777f1..6c9a3adaeae 100644 --- a/src/gateway/session-compaction-checkpoints.ts +++ b/src/gateway/session-compaction-checkpoints.ts @@ -9,6 +9,7 @@ import type { SessionCompactionCheckpointReason, SessionEntry, } from "../config/sessions.js"; +import { isCompactionCheckpointTranscriptFileName } from "../config/sessions/artifacts.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import { createSubsystemLogger } from "../logging/subsystem.js"; import { resolveGatewaySessionStoreTarget } from "./session-utils.js"; @@ -22,13 +23,18 @@ export type CapturedCompactionCheckpointSnapshot = { leafId: string; }; -function trimSessionCheckpoints( - checkpoints: SessionCompactionCheckpoint[] | undefined, -): SessionCompactionCheckpoint[] | undefined { +function trimSessionCheckpoints(checkpoints: SessionCompactionCheckpoint[] | undefined): { + kept: SessionCompactionCheckpoint[] | undefined; + removed: SessionCompactionCheckpoint[]; +} { if (!Array.isArray(checkpoints) || checkpoints.length === 0) { - return undefined; + return { kept: undefined, removed: [] }; } - return checkpoints.slice(-MAX_COMPACTION_CHECKPOINTS_PER_SESSION); + const kept = checkpoints.slice(-MAX_COMPACTION_CHECKPOINTS_PER_SESSION); + return { + kept, + removed: checkpoints.slice(0, Math.max(0, checkpoints.length - kept.length)), + }; } function sessionStoreCheckpoints( @@ -117,6 +123,40 @@ export async function cleanupCompactionCheckpointSnapshot( } } +async function cleanupTrimmedCompactionCheckpointFiles(params: { + removed: SessionCompactionCheckpoint[]; + retained: SessionCompactionCheckpoint[] | undefined; + currentSnapshotFile: string; +}): Promise { + if (params.removed.length === 0) { + return; + } + const retainedPaths = new Set( + (params.retained ?? []) + .map((checkpoint) => checkpoint.preCompaction.sessionFile?.trim()) + .filter((filePath): filePath is string => Boolean(filePath)), + ); + const snapshotDir = path.resolve(path.dirname(params.currentSnapshotFile)); + for (const checkpoint of params.removed) { + const sessionFile = checkpoint.preCompaction.sessionFile?.trim(); + if (!sessionFile || retainedPaths.has(sessionFile)) { + continue; + } + const resolvedSessionFile = path.resolve(sessionFile); + if ( + path.dirname(resolvedSessionFile) !== snapshotDir || + !isCompactionCheckpointTranscriptFileName(path.basename(resolvedSessionFile)) + ) { + continue; + } + try { + await fs.unlink(resolvedSessionFile); + } catch { + // Best-effort cleanup; disk budget can still collect old checkpoint artifacts. + } + } +} + export async function persistSessionCompactionCheckpoint(params: { cfg: OpenClawConfig; sessionKey: string; @@ -163,6 +203,12 @@ export async function persistSessionCompactionCheckpoint(params: { }; let stored = false; + let trimmedCheckpoints: + | { + kept: SessionCompactionCheckpoint[] | undefined; + removed: SessionCompactionCheckpoint[]; + } + | undefined; await updateSessionStore(target.storePath, (store) => { const existing = store[target.canonicalKey]; if (!existing?.sessionId) { @@ -170,10 +216,11 @@ export async function persistSessionCompactionCheckpoint(params: { } const checkpoints = sessionStoreCheckpoints(existing); checkpoints.push(checkpoint); + trimmedCheckpoints = trimSessionCheckpoints(checkpoints); store[target.canonicalKey] = { ...existing, updatedAt: Math.max(existing.updatedAt ?? 0, createdAt), - compactionCheckpoints: trimSessionCheckpoints(checkpoints), + compactionCheckpoints: trimmedCheckpoints.kept, }; stored = true; }); @@ -184,6 +231,11 @@ export async function persistSessionCompactionCheckpoint(params: { }); return null; } + await cleanupTrimmedCompactionCheckpointFiles({ + removed: trimmedCheckpoints?.removed ?? [], + retained: trimmedCheckpoints?.kept, + currentSnapshotFile: params.snapshot.sessionFile, + }); return checkpoint; } diff --git a/src/infra/session-cost-usage.test.ts b/src/infra/session-cost-usage.test.ts index 76bfff4c99d..8a1b0eb89f9 100644 --- a/src/infra/session-cost-usage.test.ts +++ b/src/infra/session-cost-usage.test.ts @@ -18,6 +18,12 @@ describe("session cost usage", () => { await withEnvAsync({ OPENCLAW_STATE_DIR: stateDir }, fn); const makeSessionCostRoot = async (prefix: string): Promise => await suiteRootTracker.make(prefix); + const transcriptText = (sessionId: string, entry: unknown): string => + [ + JSON.stringify({ type: "session", version: 1, id: sessionId }), + JSON.stringify(entry), + "", + ].join("\n"); beforeAll(async () => { await suiteRootTracker.setup(); @@ -121,6 +127,54 @@ describe("session cost usage", () => { }); }); + it("ignores compaction checkpoint transcript snapshots in daily totals and discovery", async () => { + const root = await makeSessionCostRoot("cost-checkpoint"); + const sessionsDir = path.join(root, "agents", "main", "sessions"); + await fs.mkdir(sessionsDir, { recursive: true }); + + const now = new Date(); + const assistantEntry = { + type: "message", + timestamp: now.toISOString(), + message: { + role: "assistant", + provider: "openai", + model: "gpt-5.4", + usage: { + input: 10, + output: 20, + cacheRead: 0, + cacheWrite: 0, + totalTokens: 30, + cost: { total: 0.03 }, + }, + }, + }; + + await fs.writeFile( + path.join(sessionsDir, "sess-1.jsonl"), + transcriptText("sess-1", assistantEntry), + "utf-8", + ); + await fs.writeFile( + path.join(sessionsDir, "sess-1.checkpoint.11111111-1111-4111-8111-111111111111.jsonl"), + transcriptText("sess-1", assistantEntry), + "utf-8", + ); + + await withStateDir(root, async () => { + const summary = await loadCostUsageSummary({ days: 30 }); + expect(summary.daily.length).toBe(1); + expect(summary.totals.totalTokens).toBe(30); + expect(summary.totals.totalCost).toBeCloseTo(0.03, 5); + + const sessions = await discoverAllSessions(); + expect(sessions).toHaveLength(1); + expect(sessions[0]?.sessionId).toBe("sess-1"); + expect(sessions[0]?.sessionFile.endsWith("sess-1.jsonl")).toBe(true); + }); + }); + it("summarizes a single session file", async () => { const root = await makeSessionCostRoot("cost-session"); const sessionFile = path.join(root, "session.jsonl"); diff --git a/src/memory-host-sdk/host/session-files.test.ts b/src/memory-host-sdk/host/session-files.test.ts index e1df2f4de8a..9b077fac336 100644 --- a/src/memory-host-sdk/host/session-files.test.ts +++ b/src/memory-host-sdk/host/session-files.test.ts @@ -643,7 +643,10 @@ describe("buildSessionEntry", () => { it("skips deleted and checkpoint transcripts for dreaming ingestion", async () => { const deletedPath = path.join(tmpDir, "ordinary.jsonl.deleted.2026-02-16T22-27-33.000Z"); - const checkpointPath = path.join(tmpDir, "ordinary.checkpoint.abc123.jsonl"); + const checkpointPath = path.join( + tmpDir, + "ordinary.checkpoint.11111111-1111-4111-8111-111111111111.jsonl", + ); const content = JSON.stringify({ type: "message", message: { role: "user", content: "This should never reach the dreaming corpus." }, diff --git a/src/memory-host-sdk/host/session-files.ts b/src/memory-host-sdk/host/session-files.ts index 059b62abbc5..e86e3b171e0 100644 --- a/src/memory-host-sdk/host/session-files.ts +++ b/src/memory-host-sdk/host/session-files.ts @@ -7,6 +7,7 @@ import { HEARTBEAT_PROMPT } from "../../auto-reply/heartbeat.js"; import { stripInboundMetadata } from "../../auto-reply/reply/strip-inbound-meta.js"; import { HEARTBEAT_TOKEN, isSilentReplyPayloadText } from "../../auto-reply/tokens.js"; import { + isCompactionCheckpointTranscriptFileName, isSessionArchiveArtifactName, isUsageCountedSessionTranscriptFileName, } from "../../config/sessions/artifacts.js"; @@ -58,13 +59,11 @@ type SessionTranscriptStoreEntry = { sessionId?: unknown; }; -function isCheckpointTranscriptFileName(fileName: string): boolean { - return fileName.endsWith(".jsonl") && fileName.includes(".checkpoint."); -} - function shouldSkipTranscriptFileForDreaming(absPath: string): boolean { const fileName = path.basename(absPath); - return isSessionArchiveArtifactName(fileName) || isCheckpointTranscriptFileName(fileName); + return ( + isSessionArchiveArtifactName(fileName) || isCompactionCheckpointTranscriptFileName(fileName) + ); } function isDreamingNarrativeBootstrapRecord(record: unknown): boolean {