@@ -174,6 +174,40 @@ describe("createEventStreamWriter", () => {
174174 expect ( records . map ( ( r ) => r . seq ) ) . toEqual ( [ 0 , 1 ] ) ;
175175 } ) ;
176176
177+ test ( "a non-ASCII line plus a torn tail stays two parseable records with distinct seqs" , async ( ) => {
178+ const dir = tempDir ( ) ;
179+ const writer = createEventStreamWriter ( dir ) ;
180+ await writer . append ( {
181+ id : "evt-1" ,
182+ type : "session.started" ,
183+ payload : { text : "é" } ,
184+ } ) ;
185+ await writer . flush ( ) ;
186+
187+ fs . appendFileSync (
188+ path . join ( dir , EVENT_LOG_FILE ) ,
189+ '{"id":"torn","type":"eve' ,
190+ ) ;
191+
192+ const reopened = createEventStreamWriter ( dir ) ;
193+ const next = await reopened . append ( { id : "evt-2" , type : "turn.done" } ) ;
194+ expect ( next . seq ) . toBe ( 1 ) ;
195+
196+ const lines = rawLines ( dir ) ;
197+ expect ( lines ) . toHaveLength ( 2 ) ;
198+ const parsed = lines . map (
199+ ( line ) => JSON . parse ( line ) as { id : string ; seq : number } ,
200+ ) ;
201+ expect ( parsed . map ( ( record ) => record . id ) ) . toEqual ( [ "evt-1" , "evt-2" ] ) ;
202+ expect ( parsed . map ( ( record ) => record . seq ) ) . toEqual ( [ 0 , 1 ] ) ;
203+
204+ const records = await reopened . readFrom ( - 1 ) ;
205+ expect ( records . map ( ( r ) => r . id ) ) . toEqual ( [ "evt-1" , "evt-2" ] ) ;
206+ expect ( records . map ( ( r ) => r . seq ) ) . toEqual ( [ 0 , 1 ] ) ;
207+ const again = await reopened . append ( { id : "evt-3" , type : "turn.done" } ) ;
208+ expect ( again . seq ) . toBe ( 2 ) ;
209+ } ) ;
210+
177211 test ( "readFrom returns only events past the cursor" , async ( ) => {
178212 const dir = tempDir ( ) ;
179213 const writer = createEventStreamWriter ( dir ) ;
@@ -253,10 +287,7 @@ describe("createEventStreamWriter", () => {
253287 }
254288 expect ( first . parentId ) . toBeUndefined ( ) ;
255289 expect ( second . parentId ) . toBe ( first . id ) ;
256- expect ( ( first . payload as { record : unknown } ) . record ) . toEqual ( {
257- role : "user" ,
258- text : "one" ,
259- } ) ;
290+ expect ( first . payload ) . toMatchObject ( { role : "user" , text : "one" } ) ;
260291 } ) ;
261292
262293 test ( "backfillFromLegacy returns 0 when turns.jsonl is absent" , async ( ) => {
@@ -298,6 +329,59 @@ describe("createEventStreamWriter", () => {
298329 const records = await writer . readFrom ( - 1 ) ;
299330 expect ( records ) . toHaveLength ( 3 ) ;
300331 } ) ;
332+
333+ test ( "backfillFromLegacy keeps identical lines and does not duplicate them on re-run" , async ( ) => {
334+ const dir = tempDir ( ) ;
335+ const line = '{"role":"user","text":"same"}\n' ;
336+ fs . writeFileSync ( path . join ( dir , "turns.jsonl" ) , line + line ) ;
337+ const writer = createEventStreamWriter ( dir ) ;
338+ expect ( await writer . backfillFromLegacy ( ) ) . toBe ( 2 ) ;
339+ expect ( await writer . backfillFromLegacy ( ) ) . toBe ( 0 ) ;
340+ expect ( await writer . readFrom ( - 1 ) ) . toHaveLength ( 2 ) ;
341+ } ) ;
342+
343+ test ( "backfillFromLegacy includes rolled turn segments in order" , async ( ) => {
344+ const dir = tempDir ( ) ;
345+ fs . writeFileSync (
346+ path . join ( dir , "turns.jsonl" ) ,
347+ '{"role":"user","text":"one"}\n' ,
348+ ) ;
349+ fs . writeFileSync (
350+ path . join ( dir , "turns-0001.jsonl" ) ,
351+ '{"role":"user","text":"two"}\n' ,
352+ ) ;
353+ const writer = createEventStreamWriter ( dir ) ;
354+ expect ( await writer . backfillFromLegacy ( ) ) . toBe ( 2 ) ;
355+ const records = await writer . readFrom ( - 1 ) ;
356+ expect (
357+ records . map ( ( record ) => ( record . payload as { text ?: string } ) . text ) ,
358+ ) . toEqual ( [ "one" , "two" ] ) ;
359+ } ) ;
360+
361+ test ( "backfillFromLegacy stores user text where projectPrompt reads it" , async ( ) => {
362+ const dir = tempDir ( ) ;
363+ fs . writeFileSync (
364+ path . join ( dir , "turns.jsonl" ) ,
365+ '{"role":"user","text":"one"}\n' ,
366+ ) ;
367+ const writer = createEventStreamWriter ( dir ) ;
368+ expect ( await writer . backfillFromLegacy ( ) ) . toBe ( 1 ) ;
369+ expect ( await writer . projectPrompt ( ) ) . toBe ( "one" ) ;
370+ } ) ;
371+
372+ test ( "flush rejects when the append it covers rejects" , async ( ) => {
373+ const dir = tempDir ( ) ;
374+ const writer = createEventStreamWriter ( dir ) ;
375+ const payload : { self ?: unknown } = { } ;
376+ payload . self = payload ;
377+ const appending = writer . append ( { type : "event" , payload } ) ;
378+ const flushing = writer . flush ( ) ;
379+ const settled = await Promise . allSettled ( [ appending , flushing ] ) ;
380+ expect ( settled [ 0 ] ?. status ) . toBe ( "rejected" ) ;
381+ expect ( settled [ 1 ] ?. status ) . toBe ( "rejected" ) ;
382+ await expect ( appending ) . rejects . toThrow ( TypeError ) ;
383+ await expect ( flushing ) . rejects . toThrow ( TypeError ) ;
384+ } ) ;
301385} ) ;
302386
303387describe ( "event stream flag gating" , ( ) => {
0 commit comments