@@ -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 {
78 embeddedRunMock ,
@@ -21,42 +22,90 @@ import {
2122 directSessionReq ,
2223} from "./test/server-sessions.test-helpers.js" ;
2324
24- 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+ }
2587
2688test ( "sessions.compaction.* lists checkpoints and branches or restores from compacted transcripts" , async ( ) => {
2789 const { dir, storePath } = await createSessionStoreDir ( ) ;
2890 const fixture = await createCheckpointFixture ( dir , { legacyPreCompactionSnapshot : false } ) ;
2991 expect ( ( await fs . readdir ( dir ) ) . some ( ( file ) => file . includes ( ".checkpoint." ) ) ) . toBe ( false ) ;
3092 const checkpointEntryCount = fixture . session . getEntries ( ) . length ;
3193 const 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+ } ) ;
32103 const { SessionManager } = await getSessionManagerModule ( ) ;
33104 await writeSessionStore ( {
34105 entries : {
35106 main : sessionStoreEntry ( fixture . sessionId , {
36107 sessionFile : 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
101150 expect ( listedCheckpoints . ok ) . toBe ( true ) ;
102151 expect ( listedCheckpoints . payload ?. key ) . toBe ( "agent:main:main" ) ;
103152 expect ( 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 ) ;
125154
126155 const checkpoint = await rpcReq < {
127156 ok : true ;
@@ -277,20 +306,21 @@ test("sessions.compaction.* lists checkpoints and branches or restores from comp
277306} ) ;
278307
279308test ( "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 ( ) ;
287310 const workDir = path . dirname ( workStorePath ) ;
288311 await fs . mkdir ( path . dirname ( mainStorePath ) , { recursive : true } ) ;
289312 await fs . mkdir ( workDir , { recursive : true } ) ;
290313 const mainSessionFile = path . join ( path . dirname ( mainStorePath ) , "sess-main-global.jsonl" ) ;
291314 await fs . writeFile ( mainSessionFile , `${ JSON . stringify ( { role : "user" , content : "main" } ) } \n` ) ;
292315 const fixture = await createCheckpointFixture ( workDir , { legacyPreCompactionSnapshot : false } ) ;
293316 const 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+ } ) ;
294324 await fs . writeFile (
295325 mainStorePath ,
296326 JSON . stringify (
@@ -305,27 +335,7 @@ test("sessions.compaction.* scopes selected global checkpoints to the requested
305335 {
306336 global : sessionStoreEntry ( fixture . sessionId , {
307337 sessionFile : 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 } ,
331341 null ,
@@ -410,22 +420,8 @@ test("sessions.compact without maxLines runs embedded manual compaction for chec
410420
411421 const { ws } = await openClient ( ) ;
412422 await 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" ) ) ;
429425 const compacted = await rpcReq < {
430426 ok : true ;
431427 key : string ;
@@ -435,9 +431,7 @@ test("sessions.compact without maxLines runs embedded manual compaction for chec
435431 key : "main" ,
436432 } ) ;
437433
438- 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 ) ;
441435 const startEvent = await startEventPromise ;
442436 const endEvent = await endEventPromise ;
443437 const startPayload = startEvent . payload as {
@@ -568,14 +562,7 @@ test("sessions.compact treats Codex native compaction start as pending, not comp
568562
569563 const { ws } = await openClient ( ) ;
570564 await 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" ) ) ;
579566
580567 const compacted = await rpcReq < {
581568 ok : true ;
@@ -586,9 +573,7 @@ test("sessions.compact treats Codex native compaction start as pending, not comp
586573 key : "main" ,
587574 } ) ;
588575
589- 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 ) ;
592577 expect ( compacted . payload ?. result ?. details ) . toMatchObject ( {
593578 backend : "codex-app-server" ,
594579 threadId : "thread-1" ,
0 commit comments