






















@@ -2,6 +2,7 @@ import fs from "node:fs/promises";
22import os from "node:os";
33import path from "node:path";
44import { expect, test, vi } from "vitest";
5+import type { SessionCompactionCheckpoint } from "../config/sessions.js";
56import { withEnvAsync } from "../test-utils/env.js";
67import {
78embeddedRunMock,
@@ -21,42 +22,90 @@ import {
2122directSessionReq,
2223} from "./test/server-sessions.test-helpers.js";
232424-const { createSessionStoreDir, openClient } = setupGatewaySessionsTestHarness();
25+const { createSessionStoreDir, createSelectedGlobalSessionStore, openClient } =
26+setupGatewaySessionsTestHarness();
27+28+type CheckpointFixture = Awaited<ReturnType<typeof createCheckpointFixture>>;
29+30+function compactionCheckpointEntry(
31+fixture: CheckpointFixture,
32+options: {
33+checkpointId: string;
34+sessionKey: string;
35+createdAt: number;
36+reason: SessionCompactionCheckpoint["reason"];
37+summary: string;
38+tokensBefore?: number;
39+tokensAfter?: number;
40+},
41+) {
42+return {
43+checkpointId: options.checkpointId,
44+sessionKey: options.sessionKey,
45+sessionId: fixture.sessionId,
46+createdAt: options.createdAt,
47+reason: options.reason,
48+summary: options.summary,
49+ ...(options.tokensBefore === undefined ? {} : { tokensBefore: options.tokensBefore }),
50+ ...(options.tokensAfter === undefined ? {} : { tokensAfter: options.tokensAfter }),
51+firstKeptEntryId: fixture.preCompactionLeafId,
52+preCompaction: {
53+sessionId: fixture.sessionId,
54+leafId: fixture.preCompactionLeafId,
55+},
56+postCompaction: {
57+sessionId: fixture.sessionId,
58+sessionFile: fixture.sessionFile,
59+leafId: fixture.postCompactionLeafId,
60+entryId: fixture.postCompactionLeafId,
61+},
62+};
63+}
64+65+function isCompactOperationEvent(message: unknown, phase: "start" | "end") {
66+const candidate = message as {
67+event?: unknown;
68+payload?: { operation?: unknown; phase?: unknown };
69+type?: unknown;
70+};
71+return (
72+candidate.type === "event" &&
73+candidate.event === "session.operation" &&
74+candidate.payload?.operation === "compact" &&
75+candidate.payload?.phase === phase
76+);
77+}
78+79+function expectMainCompactionResult(
80+compacted: { ok?: boolean; payload?: { compacted?: boolean; key?: string } | null },
81+expectedCompacted: boolean,
82+) {
83+expect(compacted.ok).toBe(true);
84+expect(compacted.payload?.key).toBe("agent:main:main");
85+expect(compacted.payload?.compacted).toBe(expectedCompacted);
86+}
25872688test("sessions.compaction.* lists checkpoints and branches or restores from compacted transcripts", async () => {
2789const { dir, storePath } = await createSessionStoreDir();
2890const fixture = await createCheckpointFixture(dir, { legacyPreCompactionSnapshot: false });
2991expect((await fs.readdir(dir)).some((file) => file.includes(".checkpoint."))).toBe(false);
3092const checkpointEntryCount = fixture.session.getEntries().length;
3193const checkpointCreatedAt = Date.now();
94+const checkpointEntry = compactionCheckpointEntry(fixture, {
95+checkpointId: "checkpoint-1",
96+sessionKey: "agent:main:main",
97+createdAt: checkpointCreatedAt,
98+reason: "manual",
99+summary: "checkpoint summary",
100+tokensBefore: 123,
101+tokensAfter: 45,
102+});
32103const { SessionManager } = await getSessionManagerModule();
33104await writeSessionStore({
34105entries: {
35106main: sessionStoreEntry(fixture.sessionId, {
36107sessionFile: fixture.sessionFile,
37-compactionCheckpoints: [
38-{
39-checkpointId: "checkpoint-1",
40-sessionKey: "agent:main:main",
41-sessionId: fixture.sessionId,
42-createdAt: checkpointCreatedAt,
43-reason: "manual",
44-tokensBefore: 123,
45-tokensAfter: 45,
46-summary: "checkpoint summary",
47-firstKeptEntryId: fixture.preCompactionLeafId,
48-preCompaction: {
49-sessionId: fixture.sessionId,
50-leafId: fixture.preCompactionLeafId,
51-},
52-postCompaction: {
53-sessionId: fixture.sessionId,
54-sessionFile: fixture.sessionFile,
55-leafId: fixture.postCompactionLeafId,
56-entryId: fixture.postCompactionLeafId,
57-},
58-},
59-],
108+compactionCheckpoints: [checkpointEntry],
60109}),
61110},
62111});
@@ -101,27 +150,7 @@ test("sessions.compaction.* lists checkpoints and branches or restores from comp
101150expect(listedCheckpoints.ok).toBe(true);
102151expect(listedCheckpoints.payload?.key).toBe("agent:main:main");
103152expect(listedCheckpoints.payload?.checkpoints).toHaveLength(1);
104-expect(listedCheckpoints.payload?.checkpoints[0]).toEqual({
105-checkpointId: "checkpoint-1",
106-sessionKey: "agent:main:main",
107-sessionId: fixture.sessionId,
108-createdAt: checkpointCreatedAt,
109-reason: "manual",
110-summary: "checkpoint summary",
111-tokensBefore: 123,
112-tokensAfter: 45,
113-firstKeptEntryId: fixture.preCompactionLeafId,
114-preCompaction: {
115-sessionId: fixture.sessionId,
116-leafId: fixture.preCompactionLeafId,
117-},
118-postCompaction: {
119-sessionId: fixture.sessionId,
120-sessionFile: fixture.sessionFile,
121-leafId: fixture.postCompactionLeafId,
122-entryId: fixture.postCompactionLeafId,
123-},
124-});
153+expect(listedCheckpoints.payload?.checkpoints[0]).toEqual(checkpointEntry);
125154126155const checkpoint = await rpcReq<{
127156ok: true;
@@ -277,20 +306,21 @@ test("sessions.compaction.* lists checkpoints and branches or restores from comp
277306});
278307279308test("sessions.compaction.* scopes selected global checkpoints to the requested agent", async () => {
280-const { dir } = await createSessionStoreDir();
281-const storeTemplate = path.join(dir, "{agentId}", "sessions.json");
282-testState.sessionStorePath = storeTemplate;
283-testState.sessionConfig = { scope: "global" };
284-testState.agentsConfig = { list: [{ id: "main", default: true }, { id: "work" }] };
285-const mainStorePath = storeTemplate.replace("{agentId}", "main");
286-const workStorePath = storeTemplate.replace("{agentId}", "work");
309+const { mainStorePath, workStorePath } = await createSelectedGlobalSessionStore();
287310const workDir = path.dirname(workStorePath);
288311await fs.mkdir(path.dirname(mainStorePath), { recursive: true });
289312await fs.mkdir(workDir, { recursive: true });
290313const mainSessionFile = path.join(path.dirname(mainStorePath), "sess-main-global.jsonl");
291314await fs.writeFile(mainSessionFile, `${JSON.stringify({ role: "user", content: "main" })}\n`);
292315const fixture = await createCheckpointFixture(workDir, { legacyPreCompactionSnapshot: false });
293316const checkpointCreatedAt = Date.now();
317+const checkpointEntry = compactionCheckpointEntry(fixture, {
318+checkpointId: "checkpoint-work",
319+sessionKey: "global",
320+createdAt: checkpointCreatedAt,
321+reason: "manual",
322+summary: "work checkpoint",
323+});
294324await fs.writeFile(
295325mainStorePath,
296326JSON.stringify(
@@ -305,27 +335,7 @@ test("sessions.compaction.* scopes selected global checkpoints to the requested
305335{
306336global: sessionStoreEntry(fixture.sessionId, {
307337sessionFile: fixture.sessionFile,
308-compactionCheckpoints: [
309-{
310-checkpointId: "checkpoint-work",
311-sessionKey: "global",
312-sessionId: fixture.sessionId,
313-createdAt: checkpointCreatedAt,
314-reason: "manual",
315-summary: "work checkpoint",
316-firstKeptEntryId: fixture.preCompactionLeafId,
317-preCompaction: {
318-sessionId: fixture.sessionId,
319-leafId: fixture.preCompactionLeafId,
320-},
321-postCompaction: {
322-sessionId: fixture.sessionId,
323-sessionFile: fixture.sessionFile,
324-leafId: fixture.postCompactionLeafId,
325-entryId: fixture.postCompactionLeafId,
326-},
327-},
328-],
338+compactionCheckpoints: [checkpointEntry],
329339}),
330340},
331341null,
@@ -410,22 +420,8 @@ test("sessions.compact without maxLines runs embedded manual compaction for chec
410420411421const { ws } = await openClient();
412422await rpcReq(ws, "sessions.subscribe", {});
413-const startEventPromise = onceMessage(
414-ws,
415-(message) =>
416-message.type === "event" &&
417-message.event === "session.operation" &&
418-(message.payload as { operation?: unknown; phase?: unknown })?.operation === "compact" &&
419-(message.payload as { operation?: unknown; phase?: unknown })?.phase === "start",
420-);
421-const endEventPromise = onceMessage(
422-ws,
423-(message) =>
424-message.type === "event" &&
425-message.event === "session.operation" &&
426-(message.payload as { operation?: unknown; phase?: unknown })?.operation === "compact" &&
427-(message.payload as { operation?: unknown; phase?: unknown })?.phase === "end",
428-);
423+const startEventPromise = onceMessage(ws, (message) => isCompactOperationEvent(message, "start"));
424+const endEventPromise = onceMessage(ws, (message) => isCompactOperationEvent(message, "end"));
429425const compacted = await rpcReq<{
430426ok: true;
431427key: string;
@@ -435,9 +431,7 @@ test("sessions.compact without maxLines runs embedded manual compaction for chec
435431key: "main",
436432});
437433438-expect(compacted.ok).toBe(true);
439-expect(compacted.payload?.key).toBe("agent:main:main");
440-expect(compacted.payload?.compacted).toBe(true);
434+expectMainCompactionResult(compacted, true);
441435const startEvent = await startEventPromise;
442436const endEvent = await endEventPromise;
443437const startPayload = startEvent.payload as {
@@ -568,14 +562,7 @@ test("sessions.compact treats Codex native compaction start as pending, not comp
568562569563const { ws } = await openClient();
570564await rpcReq(ws, "sessions.subscribe", {});
571-const endEventPromise = onceMessage(
572-ws,
573-(message) =>
574-message.type === "event" &&
575-message.event === "session.operation" &&
576-(message.payload as { operation?: unknown; phase?: unknown })?.operation === "compact" &&
577-(message.payload as { operation?: unknown; phase?: unknown })?.phase === "end",
578-);
565+const endEventPromise = onceMessage(ws, (message) => isCompactOperationEvent(message, "end"));
579566580567const compacted = await rpcReq<{
581568ok: true;
@@ -586,9 +573,7 @@ test("sessions.compact treats Codex native compaction start as pending, not comp
586573key: "main",
587574});
588575589-expect(compacted.ok).toBe(true);
590-expect(compacted.payload?.key).toBe("agent:main:main");
591-expect(compacted.payload?.compacted).toBe(false);
576+expectMainCompactionResult(compacted, false);
592577expect(compacted.payload?.result?.details).toMatchObject({
593578backend: "codex-app-server",
594579threadId: "thread-1",
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。