@@ -146,19 +146,78 @@ describe("createOptimizedContextStore load", () => {
146146 expect ( loaded . connectorState ) . toBeNull ( ) ;
147147 } ) ;
148148
149- test ( "unrecoverable turns.jsonl names the file in the error " , async ( ) => {
149+ test ( "resumes past mid-file interleaved garbage in turns.jsonl " , async ( ) => {
150150 const dir = tempDir ( ) ;
151151 const store = await createOptimizedContextStore ( dir ) ;
152152
153- // Mid-file garbage that is not null padding and not a torn tail — unrecoverable .
153+ // Mid-file garbage that is not null padding — skip the bad line, keep neighbors .
154154 fs . writeFileSync (
155155 path . join ( dir , TURNS_FILE ) ,
156156 jsonl ( [ turn ( "a" ) ] ) + "THIS IS NOT JSON\n" + jsonl ( [ turn ( "b" ) ] ) ,
157157 ) ;
158158
159+ const loaded = await store . load ( ) ;
160+ expect ( loaded . turns . map ( ( t ) => ( t . content [ 0 ] as { text : string } ) . text ) ) . toEqual ( [
161+ "a" ,
162+ "b" ,
163+ ] ) ;
164+ } ) ;
165+
166+ test ( "schema-invalid turns.jsonl still fails closed and names the file" , async ( ) => {
167+ const dir = tempDir ( ) ;
168+ const store = await createOptimizedContextStore ( dir ) ;
169+
170+ const badTurn = JSON . stringify ( { role : "user" , content : "not-an-array" , timestamp : 1 } ) ;
171+ fs . writeFileSync (
172+ path . join ( dir , TURNS_FILE ) ,
173+ jsonl ( [ turn ( "a" ) ] ) + badTurn + "\n" + jsonl ( [ turn ( "b" ) ] ) ,
174+ ) ;
175+
159176 await expect ( store . load ( ) ) . rejects . toThrow ( / t u r n s \. j s o n l / ) ;
160177 } ) ;
161178
179+ test ( "salvages a glued truncated manage_tasks record and the next turn" , async ( ) => {
180+ const dir = tempDir ( ) ;
181+ const store = await createOptimizedContextStore ( dir ) ;
182+
183+ // Production shape: truncated manage_tasks tool_call JSON glued onto the next
184+ // turn with no newline — JSON.parse of the whole line fails, but salvage keeps
185+ // the complete trailing turn.
186+ const truncatedManageTasks =
187+ '{"role":"assistant","content":[{"type":"tool_call","id":"call-mt-1","name":"manage_tasks","arguments":{"action":"update","updates":[{"id":"t1","status":"do' ;
188+ const nextTurn = JSON . stringify ( turn ( "after-glue" ) ) ;
189+ fs . writeFileSync (
190+ path . join ( dir , TURNS_FILE ) ,
191+ jsonl ( [ turn ( "before" ) ] ) + truncatedManageTasks + nextTurn + "\n" + jsonl ( [ turn ( "tail" ) ] ) ,
192+ ) ;
193+
194+ const loaded = await store . load ( ) ;
195+ expect ( loaded . turns . map ( ( t ) => ( t . content [ 0 ] as { text : string } ) . text ) ) . toEqual ( [
196+ "before" ,
197+ "after-glue" ,
198+ "tail" ,
199+ ] ) ;
200+ } ) ;
201+
202+ test ( "resumes past mid-file garbage plus a torn trailing line" , async ( ) => {
203+ const dir = tempDir ( ) ;
204+ const store = await createOptimizedContextStore ( dir ) ;
205+
206+ fs . writeFileSync (
207+ path . join ( dir , TURNS_FILE ) ,
208+ jsonl ( [ turn ( "a" ) ] ) +
209+ "GARBAGE\n" +
210+ jsonl ( [ turn ( "b" ) ] ) +
211+ '{"role":"user","content":[{"type":"te' ,
212+ ) ;
213+
214+ const loaded = await store . load ( ) ;
215+ expect ( loaded . turns . map ( ( t ) => ( t . content [ 0 ] as { text : string } ) . text ) ) . toEqual ( [
216+ "a" ,
217+ "b" ,
218+ ] ) ;
219+ } ) ;
220+
162221 // Compacted head rewrites segment 0 while a prior multi-segment history's
163222 // tails stay on disk. Concatenating them reintroduces tool_call ids that the
164223 // compact head already kept — drop the orphan tails so the session can resume.
@@ -388,29 +447,53 @@ describe("loadRecentTurns", () => {
388447 expect ( loaded . map ( ( t ) => ( t . content [ 0 ] as { text : string } ) . text ) ) . toEqual ( [ "a" , "b" , "c" ] ) ;
389448 } ) ;
390449
391- test ( "the reactor's load() stays strict on the same corrupt fixture and names the segment " , async ( ) => {
450+ test ( "the reactor's load() skips mid-file parse garbage and keeps neighbors " , async ( ) => {
392451 const dir = tempDir ( ) ;
393452 const store = await createOptimizedContextStore ( dir ) ;
394453 fs . writeFileSync (
395454 path . join ( dir , TURNS_FILE ) ,
396455 jsonl ( [ turn ( "a" ) ] ) + '{"role":"user","content":[{"type":"te\n' + jsonl ( [ turn ( "b" ) ] ) ,
397456 ) ;
398457
399- await expect ( store . load ( ) ) . rejects . toThrow ( TURNS_FILE ) ;
458+ const loaded = await store . load ( ) ;
459+ expect ( loaded . turns . map ( ( t ) => ( t . content [ 0 ] as { text : string } ) . text ) ) . toEqual ( [
460+ "a" ,
461+ "b" ,
462+ ] ) ;
400463 } ) ;
401464
402- test ( "reactor's load() stays strict and names an unrecoverable extra segment" , async ( ) => {
465+ test ( "reactor's load() skips mid-file garbage in an extra segment" , async ( ) => {
403466 const dir = tempDir ( ) ;
404467 const store = await createOptimizedContextStore ( dir ) ;
405468 const segmentName = segmentFileName ( TURNS_FILE , 1 ) ;
406469
407470 fs . writeFileSync ( path . join ( dir , TURNS_FILE ) , jsonl ( [ turn ( "a" ) ] ) ) ;
408- // Mid-file garbage that is neither null padding nor a torn tail — unrecoverable .
471+ // Mid-file garbage that is neither null padding nor a torn tail — skip it .
409472 fs . writeFileSync (
410473 path . join ( dir , segmentName ) ,
411474 jsonl ( [ turn ( "b" ) ] ) + "THIS IS NOT JSON\n" + jsonl ( [ turn ( "c" ) ] ) ,
412475 ) ;
413476
477+ const loaded = await store . load ( ) ;
478+ expect ( loaded . turns . map ( ( t ) => ( t . content [ 0 ] as { text : string } ) . text ) ) . toEqual ( [
479+ "a" ,
480+ "b" ,
481+ "c" ,
482+ ] ) ;
483+ } ) ;
484+
485+ test ( "reactor's load() fails closed on schema-invalid lines in an extra segment" , async ( ) => {
486+ const dir = tempDir ( ) ;
487+ const store = await createOptimizedContextStore ( dir ) ;
488+ const segmentName = segmentFileName ( TURNS_FILE , 1 ) ;
489+
490+ fs . writeFileSync ( path . join ( dir , TURNS_FILE ) , jsonl ( [ turn ( "a" ) ] ) ) ;
491+ const badTurn = JSON . stringify ( { role : "user" , content : "not-an-array" , timestamp : 1 } ) ;
492+ fs . writeFileSync (
493+ path . join ( dir , segmentName ) ,
494+ jsonl ( [ turn ( "b" ) ] ) + badTurn + "\n" + jsonl ( [ turn ( "c" ) ] ) ,
495+ ) ;
496+
414497 await expect ( store . load ( ) ) . rejects . toThrow ( segmentName ) ;
415498 } ) ;
416499} ) ;
0 commit comments