






















@@ -17,6 +17,25 @@ import {
1717} from "./session-compaction-checkpoints.js";
18181919const tempDirs: string[] = [];
20+const MAIN_AGENT_ID = "main";
21+const MAIN_SESSION_KEY = "agent:main:main";
22+const TEST_SESSION_ID = "sess";
23+24+type PersistCheckpointInput = Parameters<typeof persistSessionCompactionCheckpoint>[0];
25+26+type LegacyCheckpointFixture = {
27+checkpointId: string;
28+sessionKey: string;
29+sessionId: string;
30+createdAt: number;
31+reason: "manual";
32+preCompaction: {
33+sessionId: string;
34+sessionFile: string;
35+leafId: string;
36+};
37+postCompaction: { sessionId: string };
38+};
20392140function requireNonEmptyString(value: string | null | undefined, message: string): string {
2241if (!value) {
@@ -46,6 +65,94 @@ function expectNonEmptyStringField(value: unknown, message: string): string {
4665return value;
4766}
486768+async function makeTempSessionStore(prefix: string, sessionId = TEST_SESSION_ID) {
69+const dir = await fs.mkdtemp(path.join(os.tmpdir(), prefix));
70+tempDirs.push(dir);
71+return {
72+ dir,
73+storePath: path.join(dir, "sessions.json"),
74+ sessionId,
75+sessionKey: MAIN_SESSION_KEY,
76+now: Date.now(),
77+};
78+}
79+80+function checkpointConfig(storePath: string): OpenClawConfig {
81+return {
82+session: { store: storePath },
83+agents: { list: [{ id: MAIN_AGENT_ID, default: true }] },
84+} as OpenClawConfig;
85+}
86+87+async function writeSessionStore(
88+storePath: string,
89+sessionKey: string,
90+entry: { sessionId: string; updatedAt: number; compactionCheckpoints?: unknown[] },
91+): Promise<void> {
92+await fs.writeFile(storePath, JSON.stringify({ [sessionKey]: entry }, null, 2), "utf-8");
93+}
94+95+async function readSessionStore<T extends object>(storePath: string): Promise<Record<string, T>> {
96+return JSON.parse(await fs.readFile(storePath, "utf-8")) as Record<string, T>;
97+}
98+99+async function readFirstCompactionCheckpoints<T>(storePath: string): Promise<T[] | undefined> {
100+const store = await readSessionStore<{ compactionCheckpoints?: T[] }>(storePath);
101+return Object.values(store).find((entry) => entry.compactionCheckpoints)?.compactionCheckpoints;
102+}
103+104+async function createLegacyCheckpointFixtures(options: {
105+dir: string;
106+sessionId: string;
107+sessionKey: string;
108+now: number;
109+count: number;
110+initializeFile: (sessionFile: string, index: number) => Promise<void>;
111+}): Promise<LegacyCheckpointFixture[]> {
112+return Promise.all(
113+Array.from({ length: options.count }, async (_, index) => {
114+const uuid = `${String(index + 1).padStart(8, "0")}-1111-4111-8111-111111111111`;
115+const sessionFile = path.join(options.dir, `sess.checkpoint.${uuid}.jsonl`);
116+await options.initializeFile(sessionFile, index);
117+return {
118+checkpointId: `old-${index}`,
119+sessionKey: options.sessionKey,
120+sessionId: options.sessionId,
121+createdAt: options.now + index,
122+reason: "manual",
123+preCompaction: {
124+sessionId: options.sessionId,
125+ sessionFile,
126+leafId: `old-leaf-${index}`,
127+},
128+postCompaction: { sessionId: options.sessionId },
129+};
130+}),
131+);
132+}
133+134+async function persistMainCheckpoint(
135+storePath: string,
136+options: {
137+sessionId: string;
138+snapshot: PersistCheckpointInput["snapshot"];
139+createdAt: number;
140+postSessionFile?: string;
141+postLeafId?: string;
142+},
143+) {
144+return persistSessionCompactionCheckpoint({
145+cfg: checkpointConfig(storePath),
146+sessionKey: MAIN_AGENT_ID,
147+sessionId: options.sessionId,
148+reason: "manual",
149+snapshot: options.snapshot,
150+createdAt: options.createdAt,
151+ ...(options.postSessionFile === undefined ? {} : { postSessionFile: options.postSessionFile }),
152+ ...(options.postLeafId === undefined ? {} : { postLeafId: options.postLeafId }),
153+});
154+}
155+49156afterEach(async () => {
50157await Promise.all(tempDirs.splice(0).map((dir) => fs.rm(dir, { recursive: true, force: true })));
51158});
@@ -456,55 +563,27 @@ describe("session-compaction-checkpoints", () => {
456563});
457564458565test("persist stores codex-style checkpoint metadata and trims old legacy snapshot files", async () => {
459-const dir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-checkpoint-trim-"));
460-tempDirs.push(dir);
461-462-const storePath = path.join(dir, "sessions.json");
463-const sessionId = "sess";
464-const sessionKey = "agent:main:main";
465-const now = Date.now();
466-const existingCheckpoints = Array.from({ length: 26 }, (_, index) => {
467-const uuid = `${String(index + 1).padStart(8, "0")}-1111-4111-8111-111111111111`;
468-const sessionFile = path.join(dir, `sess.checkpoint.${uuid}.jsonl`);
469-fsSync.writeFileSync(sessionFile, `checkpoint ${index}`, "utf-8");
470-return {
471-checkpointId: `old-${index}`,
472- sessionKey,
473- sessionId,
474-createdAt: now + index,
475-reason: "manual" as const,
476-preCompaction: {
477- sessionId,
478- sessionFile,
479-leafId: `old-leaf-${index}`,
480-},
481-postCompaction: { sessionId },
482-};
483-});
484-await fs.writeFile(
485-storePath,
486-JSON.stringify(
487-{
488-[sessionKey]: {
489- sessionId,
490-updatedAt: now,
491-compactionCheckpoints: existingCheckpoints,
492-},
493-},
494-null,
495-2,
496-),
497-"utf-8",
566+const { dir, storePath, sessionId, sessionKey, now } = await makeTempSessionStore(
567+"openclaw-checkpoint-trim-",
498568);
569+const existingCheckpoints = await createLegacyCheckpointFixtures({
570+ dir,
571+ sessionId,
572+ sessionKey,
573+ now,
574+count: 26,
575+initializeFile: async (sessionFile, index) => {
576+await fs.writeFile(sessionFile, `checkpoint ${index}`, "utf-8");
577+},
578+});
579+await writeSessionStore(storePath, sessionKey, {
580+ sessionId,
581+updatedAt: now,
582+compactionCheckpoints: existingCheckpoints,
583+});
499584500-const stored = await persistSessionCompactionCheckpoint({
501-cfg: {
502-session: { store: storePath },
503-agents: { list: [{ id: "main", default: true }] },
504-} as OpenClawConfig,
505-sessionKey: "main",
585+const stored = await persistMainCheckpoint(storePath, {
506586 sessionId,
507-reason: "manual",
508587snapshot: {
509588 sessionId,
510589leafId: "current-leaf",
@@ -525,105 +604,54 @@ describe("session-compaction-checkpoints", () => {
525604expect(fsSync.existsSync(existingCheckpoints[2].preCompaction.sessionFile)).toBe(true);
526605expect(fsSync.readdirSync(dir).some((file) => file.includes("99999999"))).toBe(false);
527606528-const nextStore = JSON.parse(await fs.readFile(storePath, "utf-8")) as Record<
529-string,
530-{ compactionCheckpoints?: unknown[] }
531->;
532-expect(
533-Object.values(nextStore).find((entry) => entry.compactionCheckpoints)?.compactionCheckpoints,
534-).toHaveLength(25);
607+expect(await readFirstCompactionCheckpoints(storePath)).toHaveLength(25);
535608});
536609537610test("persist skips codex-style checkpoints without a stable post-compaction leaf", async () => {
538-const dir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-checkpoint-no-leaf-"));
539-tempDirs.push(dir);
540-541-const storePath = path.join(dir, "sessions.json");
542-const sessionId = "sess";
543-await fs.writeFile(
544-storePath,
545-JSON.stringify(
546-{
547-"agent:main:main": {
548- sessionId,
549-updatedAt: Date.now(),
550-},
551-},
552-null,
553-2,
554-),
555-"utf-8",
611+const { storePath, sessionId, sessionKey, now } = await makeTempSessionStore(
612+"openclaw-checkpoint-no-leaf-",
556613);
614+await writeSessionStore(storePath, sessionKey, {
615+ sessionId,
616+updatedAt: now,
617+});
557618558-const stored = await persistSessionCompactionCheckpoint({
559-cfg: {
560-session: { store: storePath },
561-agents: { list: [{ id: "main", default: true }] },
562-} as OpenClawConfig,
563-sessionKey: "main",
619+const stored = await persistMainCheckpoint(storePath, {
564620 sessionId,
565-reason: "manual",
566621snapshot: {
567622 sessionId,
568623leafId: "pre-leaf",
569624},
570-postSessionFile: path.join(dir, "sess.compacted.jsonl"),
571-createdAt: Date.now(),
625+postSessionFile: path.join(path.dirname(storePath), "sess.compacted.jsonl"),
626+createdAt: now,
572627});
573628574629expect(stored).toBeNull();
575-const nextStore = JSON.parse(await fs.readFile(storePath, "utf-8")) as Record<
576-string,
577-{ compactionCheckpoints?: unknown[] }
578->;
579-expect(nextStore["agent:main:main"]?.compactionCheckpoints).toBeUndefined();
630+const nextStore = await readSessionStore<{ compactionCheckpoints?: unknown[] }>(storePath);
631+expect(nextStore[MAIN_SESSION_KEY]?.compactionCheckpoints).toBeUndefined();
580632});
581633582634test("persist trims retained checkpoint snapshots by total byte budget", async () => {
583-const dir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-checkpoint-byte-trim-"));
584-tempDirs.push(dir);
585-586-const storePath = path.join(dir, "sessions.json");
587-const sessionId = "sess";
588-const sessionKey = "agent:main:main";
589-const now = Date.now();
635+const { dir, storePath, sessionId, sessionKey, now } = await makeTempSessionStore(
636+"openclaw-checkpoint-byte-trim-",
637+);
590638const checkpointSize = Math.floor(MAX_COMPACTION_CHECKPOINT_RETAINED_BYTES_PER_SESSION / 6);
591-const existingCheckpoints = await Promise.all(
592-Array.from({ length: 8 }, async (_, index) => {
593-const uuid = `${String(index + 1).padStart(8, "0")}-1111-4111-8111-111111111111`;
594-const sessionFile = path.join(dir, `sess.checkpoint.${uuid}.jsonl`);
639+const existingCheckpoints = await createLegacyCheckpointFixtures({
640+ dir,
641+ sessionId,
642+ sessionKey,
643+ now,
644+count: 8,
645+initializeFile: async (sessionFile) => {
595646await fs.writeFile(sessionFile, "", "utf-8");
596647await fs.truncate(sessionFile, checkpointSize);
597-return {
598-checkpointId: `old-${index}`,
599- sessionKey,
600- sessionId,
601-createdAt: now + index,
602-reason: "manual" as const,
603-preCompaction: {
604- sessionId,
605- sessionFile,
606-leafId: `old-leaf-${index}`,
607-},
608-postCompaction: { sessionId },
609-};
610-}),
611-);
612-await fs.writeFile(
613-storePath,
614-JSON.stringify(
615-{
616-[sessionKey]: {
617- sessionId,
618-updatedAt: now,
619-compactionCheckpoints: existingCheckpoints,
620-},
621-},
622-null,
623-2,
624-),
625-"utf-8",
626-);
648+},
649+});
650+await writeSessionStore(storePath, sessionKey, {
651+ sessionId,
652+updatedAt: now,
653+compactionCheckpoints: existingCheckpoints,
654+});
627655628656const currentSnapshotFile = path.join(
629657dir,
@@ -632,14 +660,8 @@ describe("session-compaction-checkpoints", () => {
632660await fs.writeFile(currentSnapshotFile, "", "utf-8");
633661await fs.truncate(currentSnapshotFile, checkpointSize);
634662635-await persistSessionCompactionCheckpoint({
636-cfg: {
637-session: { store: storePath },
638-agents: { list: [{ id: "main", default: true }] },
639-} as OpenClawConfig,
640-sessionKey: "main",
663+await persistMainCheckpoint(storePath, {
641664 sessionId,
642-reason: "manual",
643665snapshot: {
644666 sessionId,
645667sessionFile: currentSnapshotFile,
@@ -654,13 +676,7 @@ describe("session-compaction-checkpoints", () => {
654676expect(fsSync.existsSync(existingCheckpoints[3].preCompaction.sessionFile)).toBe(true);
655677expect(fsSync.existsSync(currentSnapshotFile)).toBe(true);
656678657-const nextStore = JSON.parse(await fs.readFile(storePath, "utf-8")) as Record<
658-string,
659-{ compactionCheckpoints?: Array<{ checkpointId?: string }> }
660->;
661-const retained = Object.values(nextStore).find(
662-(entry) => entry.compactionCheckpoints,
663-)?.compactionCheckpoints;
679+const retained = await readFirstCompactionCheckpoints<{ checkpointId?: string }>(storePath);
664680expect(retained?.map((checkpoint) => checkpoint.checkpointId)).toEqual([
665681"old-3",
666682"old-4",
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。