@@ -5,9 +5,11 @@ import path from "node:path";
55import type { AssistantMessage , UserMessage } from "@mariozechner/pi-ai" ;
66import { SessionManager } from "@mariozechner/pi-coding-agent" ;
77import { afterEach , describe , expect , test } from "vitest" ;
8+ import type { OpenClawConfig } from "../config/types.openclaw.js" ;
89import {
910 captureCompactionCheckpointSnapshot ,
1011 cleanupCompactionCheckpointSnapshot ,
12+ persistSessionCompactionCheckpoint ,
1113} from "./session-compaction-checkpoints.js" ;
1214
1315const tempDirs : string [ ] = [ ] ;
@@ -81,4 +83,83 @@ describe("session-compaction-checkpoints", () => {
8183 expect ( fsSync . existsSync ( snapshot ! . sessionFile ) ) . toBe ( false ) ;
8284 expect ( fsSync . existsSync ( sessionFile ! ) ) . toBe ( true ) ;
8385 } ) ;
86+
87+ test ( "persist trims old checkpoint metadata and removes trimmed snapshot files" , async ( ) => {
88+ const dir = await fs . mkdtemp ( path . join ( os . tmpdir ( ) , "openclaw-checkpoint-trim-" ) ) ;
89+ tempDirs . push ( dir ) ;
90+
91+ const storePath = path . join ( dir , "sessions.json" ) ;
92+ const sessionId = "sess" ;
93+ const sessionKey = "agent:main:main" ;
94+ const now = Date . now ( ) ;
95+ const existingCheckpoints = Array . from ( { length : 26 } , ( _ , index ) => {
96+ const uuid = `${ String ( index + 1 ) . padStart ( 8 , "0" ) } -1111-4111-8111-111111111111` ;
97+ const sessionFile = path . join ( dir , `sess.checkpoint.${ uuid } .jsonl` ) ;
98+ fsSync . writeFileSync ( sessionFile , `checkpoint ${ index } ` , "utf-8" ) ;
99+ return {
100+ checkpointId : `old-${ index } ` ,
101+ sessionKey,
102+ sessionId,
103+ createdAt : now + index ,
104+ reason : "manual" as const ,
105+ preCompaction : {
106+ sessionId,
107+ sessionFile,
108+ leafId : `old-leaf-${ index } ` ,
109+ } ,
110+ postCompaction : { sessionId } ,
111+ } ;
112+ } ) ;
113+ await fs . writeFile (
114+ storePath ,
115+ JSON . stringify (
116+ {
117+ [ sessionKey ] : {
118+ sessionId,
119+ updatedAt : now ,
120+ compactionCheckpoints : existingCheckpoints ,
121+ } ,
122+ } ,
123+ null ,
124+ 2 ,
125+ ) ,
126+ "utf-8" ,
127+ ) ;
128+
129+ const currentSnapshotFile = path . join (
130+ dir ,
131+ "sess.checkpoint.99999999-9999-4999-8999-999999999999.jsonl" ,
132+ ) ;
133+ await fs . writeFile ( currentSnapshotFile , "current" , "utf-8" ) ;
134+
135+ const stored = await persistSessionCompactionCheckpoint ( {
136+ cfg : {
137+ session : { store : storePath } ,
138+ agents : { list : [ { id : "main" , default : true } ] } ,
139+ } as OpenClawConfig ,
140+ sessionKey : "main" ,
141+ sessionId,
142+ reason : "manual" ,
143+ snapshot : {
144+ sessionId,
145+ sessionFile : currentSnapshotFile ,
146+ leafId : "current-leaf" ,
147+ } ,
148+ createdAt : now + 100 ,
149+ } ) ;
150+
151+ expect ( stored ) . not . toBeNull ( ) ;
152+ expect ( fsSync . existsSync ( existingCheckpoints [ 0 ] . preCompaction . sessionFile ) ) . toBe ( false ) ;
153+ expect ( fsSync . existsSync ( existingCheckpoints [ 1 ] . preCompaction . sessionFile ) ) . toBe ( false ) ;
154+ expect ( fsSync . existsSync ( existingCheckpoints [ 2 ] . preCompaction . sessionFile ) ) . toBe ( true ) ;
155+ expect ( fsSync . existsSync ( currentSnapshotFile ) ) . toBe ( true ) ;
156+
157+ const nextStore = JSON . parse ( await fs . readFile ( storePath , "utf-8" ) ) as Record <
158+ string ,
159+ { compactionCheckpoints ?: unknown [ ] }
160+ > ;
161+ expect (
162+ Object . values ( nextStore ) . find ( ( entry ) => entry . compactionCheckpoints ) ?. compactionCheckpoints ,
163+ ) . toHaveLength ( 25 ) ;
164+ } ) ;
84165} ) ;
0 commit comments