mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-26 12:26:38 -06:00
fix(sessions): exclude archived trajectory artifacts (#126912)
* fix(sessions): exclude archived trajectory artifacts * fix(sessions): classify archived transcripts through canonical parser Reject archived trajectory and compaction checkpoint artifacts while preserving retained conversation transcripts and existing Windows test cleanup. Co-authored-by: sunlit-deng <yang.jiajun1@xydigit.com> * test(memory): compact session archive listener expectations Preserve every archive-listener invariant while keeping the regression suite below its enforced line budget. Co-authored-by: sunlit-deng <yang.jiajun1@xydigit.com> --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
@@ -1083,6 +1083,10 @@ describe("session startup catch-up", () => {
|
||||
it.each([
|
||||
"thread.jsonl.bak.2026-06-23T10-00-00.000Z",
|
||||
"thread.trajectory.jsonl",
|
||||
"thread.trajectory.jsonl.deleted.2026-06-23T10-00-00.000Z",
|
||||
"thread.trajectory.jsonl.reset.2026-06-23T10-00-00.000Z.zst",
|
||||
"thread.checkpoint.11111111-1111-4111-8111-111111111111.jsonl.deleted.2026-06-23T10-00-00.000Z",
|
||||
"thread.checkpoint.11111111-1111-4111-8111-111111111111.jsonl.reset.2026-06-23T10-00-00.000Z.zst",
|
||||
"sessions.json",
|
||||
])("ignores non-corpus session artifact updates for %s", async (fileName) => {
|
||||
vi.useFakeTimers();
|
||||
@@ -1096,10 +1100,8 @@ describe("session startup catch-up", () => {
|
||||
await vi.advanceTimersByTimeAsync(6000);
|
||||
await harness.waitForSessionSync();
|
||||
|
||||
expect(harness.getPendingArchiveFiles()).toEqual([]);
|
||||
expect(harness.getDirtyArchiveFiles()).toEqual([]);
|
||||
expect(harness.syncCalls).toEqual([]);
|
||||
expect(harness.indexedPaths).toEqual([]);
|
||||
expect([harness.getPendingArchiveFiles(), harness.getDirtyArchiveFiles()]).toEqual([[], []]);
|
||||
expect([harness.syncCalls, harness.indexedPaths]).toEqual([[], []]);
|
||||
} finally {
|
||||
harness.stopTranscriptListener();
|
||||
}
|
||||
@@ -1119,8 +1121,7 @@ describe("session startup catch-up", () => {
|
||||
await harness.waitForSessionSync();
|
||||
|
||||
expect(harness.getDirtyArchiveFiles()).toEqual([]);
|
||||
expect(harness.syncCalls).toEqual([]);
|
||||
expect(harness.indexedPaths).toEqual([]);
|
||||
expect([harness.syncCalls, harness.indexedPaths]).toEqual([[], []]);
|
||||
} finally {
|
||||
harness.stopTranscriptListener();
|
||||
}
|
||||
|
||||
@@ -110,7 +110,15 @@ describe("listSessionFilesForAgent", () => {
|
||||
"active.jsonl.reset.2026-02-16T22-26-33.000Z",
|
||||
"active.jsonl.deleted.2026-02-16T22-27-33.000Z",
|
||||
];
|
||||
const excluded = ["active.jsonl.bak.2026-02-16T22-28-33.000Z", "sessions.json", "notes.md"];
|
||||
const excluded = [
|
||||
"active.jsonl.bak.2026-02-16T22-28-33.000Z",
|
||||
"active.trajectory.jsonl.deleted.2026-02-16T22-30-33.000Z",
|
||||
"active.trajectory.jsonl.reset.2026-02-16T22-31-33.000Z.zst",
|
||||
"active.checkpoint.11111111-1111-4111-8111-111111111111.jsonl.deleted.2026-02-16T22-32-33.000Z",
|
||||
"active.checkpoint.11111111-1111-4111-8111-111111111111.jsonl.reset.2026-02-16T22-33-33.000Z.zst",
|
||||
"sessions.json",
|
||||
"notes.md",
|
||||
];
|
||||
excluded.push("active.checkpoint.11111111-1111-4111-8111-111111111111.jsonl");
|
||||
|
||||
for (const fileName of [...included, ...excluded]) {
|
||||
|
||||
@@ -90,6 +90,8 @@ describe("session artifact helpers", () => {
|
||||
|
||||
it("classifies usage-counted transcript files", () => {
|
||||
expect(isUsageCountedSessionTranscriptFileName("abc.jsonl")).toBe(true);
|
||||
expect(isUsageCountedSessionTranscriptFileName(".jsonl")).toBe(true);
|
||||
expect(isUsageCountedSessionTranscriptFileName("keep.deleted.keep.jsonl")).toBe(true);
|
||||
expect(
|
||||
isUsageCountedSessionTranscriptFileName("abc.jsonl.reset.2026-01-01T00-00-00.000Z"),
|
||||
).toBe(true);
|
||||
@@ -101,6 +103,39 @@ describe("session artifact helpers", () => {
|
||||
`abc.jsonl.deleted.2026-01-01T00-00-00.000Z.${"a".repeat(32)}`,
|
||||
),
|
||||
).toBe(true);
|
||||
expect(
|
||||
isUsageCountedSessionTranscriptFileName("abc.jsonl.reset.2026-01-01T00-00-00.000Z.zst"),
|
||||
).toBe(true);
|
||||
expect(
|
||||
isUsageCountedSessionTranscriptFileName(
|
||||
"abc.checkpoint.not-a-uuid.jsonl.deleted.2026-01-01T00-00-00.000Z",
|
||||
),
|
||||
).toBe(true);
|
||||
expect(
|
||||
isUsageCountedSessionTranscriptFileName(
|
||||
"abc.trajectory.jsonl.deleted.2026-01-01T00-00-00.000Z",
|
||||
),
|
||||
).toBe(false);
|
||||
expect(
|
||||
isUsageCountedSessionTranscriptFileName(
|
||||
"abc.trajectory.jsonl.reset.2026-01-01T00-00-00.000Z.zst",
|
||||
),
|
||||
).toBe(false);
|
||||
for (const reason of ["reset", "deleted"] as const) {
|
||||
expect(
|
||||
isUsageCountedSessionTranscriptFileName(
|
||||
`abc.checkpoint.11111111-1111-4111-8111-111111111111.jsonl.${reason}.2026-01-01T00-00-00.000Z.zst`,
|
||||
),
|
||||
).toBe(false);
|
||||
}
|
||||
expect(isUsageCountedSessionTranscriptFileName(".jsonl.reset.2026-01-01T00-00-00.000Z")).toBe(
|
||||
false,
|
||||
);
|
||||
expect(
|
||||
isUsageCountedSessionTranscriptFileName(
|
||||
`abc.jsonl.deleted.2026-01-01T00-00-00.000Z.${"A".repeat(32)}`,
|
||||
),
|
||||
).toBe(false);
|
||||
expect(isUsageCountedSessionTranscriptFileName("abc.jsonl.bak.2026-01-01T00-00-00.000Z")).toBe(
|
||||
false,
|
||||
);
|
||||
@@ -114,6 +149,10 @@ describe("session artifact helpers", () => {
|
||||
|
||||
it("parses usage-counted session ids from file names", () => {
|
||||
expect(parseUsageCountedSessionIdFromFileName("abc.jsonl")).toBe("abc");
|
||||
expect(parseUsageCountedSessionIdFromFileName(".jsonl")).toBe("");
|
||||
expect(parseUsageCountedSessionIdFromFileName("keep.deleted.keep.jsonl")).toBe(
|
||||
"keep.deleted.keep",
|
||||
);
|
||||
expect(parseUsageCountedSessionIdFromFileName("abc.jsonl.reset.2026-01-01T00-00-00.000Z")).toBe(
|
||||
"abc",
|
||||
);
|
||||
@@ -125,6 +164,14 @@ describe("session artifact helpers", () => {
|
||||
`abc.jsonl.deleted.2026-01-01T00-00-00.000Z.${"a".repeat(32)}`,
|
||||
),
|
||||
).toBe("abc");
|
||||
expect(
|
||||
parseUsageCountedSessionIdFromFileName("abc.jsonl.reset.2026-01-01T00-00-00.000Z.zst"),
|
||||
).toBe("abc");
|
||||
expect(
|
||||
parseUsageCountedSessionIdFromFileName(
|
||||
"abc.checkpoint.not-a-uuid.jsonl.deleted.2026-01-01T00-00-00.000Z",
|
||||
),
|
||||
).toBe("abc.checkpoint.not-a-uuid");
|
||||
expect(parseUsageCountedSessionIdFromFileName("abc.jsonl.bak.2026-01-01T00-00-00.000Z")).toBe(
|
||||
null,
|
||||
);
|
||||
@@ -134,6 +181,26 @@ describe("session artifact helpers", () => {
|
||||
),
|
||||
).toBeNull();
|
||||
expect(parseUsageCountedSessionIdFromFileName("abc.trajectory.jsonl")).toBeNull();
|
||||
expect(
|
||||
parseUsageCountedSessionIdFromFileName(
|
||||
"abc.trajectory.jsonl.deleted.2026-01-01T00-00-00.000Z",
|
||||
),
|
||||
).toBeNull();
|
||||
expect(
|
||||
parseUsageCountedSessionIdFromFileName(
|
||||
"abc.trajectory.jsonl.reset.2026-01-01T00-00-00.000Z.zst",
|
||||
),
|
||||
).toBeNull();
|
||||
for (const reason of ["reset", "deleted"] as const) {
|
||||
expect(
|
||||
parseUsageCountedSessionIdFromFileName(
|
||||
`abc.checkpoint.11111111-1111-4111-8111-111111111111.jsonl.${reason}.2026-01-01T00-00-00.000Z.zst`,
|
||||
),
|
||||
).toBeNull();
|
||||
}
|
||||
expect(
|
||||
parseUsageCountedSessionIdFromFileName(".jsonl.reset.2026-01-01T00-00-00.000Z"),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it("formats and parses archive timestamps", () => {
|
||||
|
||||
@@ -137,10 +137,7 @@ export function isPrimarySessionTranscriptFileName(fileName: string): boolean {
|
||||
|
||||
/** Returns true for transcript files counted in usage, including reset/deleted archives. */
|
||||
export function isUsageCountedSessionTranscriptFileName(fileName: string): boolean {
|
||||
if (isPrimarySessionTranscriptFileName(fileName)) {
|
||||
return true;
|
||||
}
|
||||
return hasArchiveSuffix(fileName, "reset") || hasArchiveSuffix(fileName, "deleted");
|
||||
return parseUsageCountedSessionIdFromFileName(fileName) !== null;
|
||||
}
|
||||
|
||||
/** Extracts the session id from a usage-counted transcript filename. */
|
||||
@@ -153,7 +150,8 @@ export function parseUsageCountedSessionIdFromFileName(fileName: string): string
|
||||
const marker = `.jsonl.${reason}.`;
|
||||
const index = normalized.lastIndexOf(marker);
|
||||
if (index > 0 && hasArchiveSuffix(normalized, reason)) {
|
||||
return normalized.slice(0, index);
|
||||
const sessionId = normalized.slice(0, index);
|
||||
return isPrimarySessionTranscriptFileName(`${sessionId}.jsonl`) ? sessionId : null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
Reference in New Issue
Block a user