@@ -561,4 +561,48 @@ describe("importRooTaskHistory", () => {
561561 code : "ENOTDIR" ,
562562 } )
563563 } )
564+
565+ it ( "imports the complete file set when two calls run concurrently against the same Roo task" , async ( ) => {
566+ const zooGlobalStoragePath = path . join ( tempRoot , "globalStorage" , "zoocodeorganization.zoo-code" )
567+ const rooDefaultStorageRoot = path . join ( tempRoot , "globalStorage" , "rooveterinaryinc.roo-cline" )
568+
569+ mockStorageConfiguration ( )
570+
571+ await fs . mkdir ( path . join ( rooDefaultStorageRoot , "tasks" , "task-concurrent" ) , { recursive : true } )
572+ await fs . writeFile (
573+ path . join ( rooDefaultStorageRoot , "tasks" , "task-concurrent" , "history_item.json" ) ,
574+ makeHistoryItem ( "task-concurrent" ) ,
575+ )
576+ await fs . writeFile (
577+ path . join ( rooDefaultStorageRoot , "tasks" , "task-concurrent" , "ui_messages.json" ) ,
578+ "concurrent-ui" ,
579+ )
580+ await fs . writeFile (
581+ path . join ( rooDefaultStorageRoot , "tasks" , "task-concurrent" , "api_conversation_history.json" ) ,
582+ "concurrent-api" ,
583+ )
584+
585+ const [ result1 , result2 ] = await Promise . all ( [
586+ importRooTaskHistory ( zooGlobalStoragePath ) ,
587+ importRooTaskHistory ( zooGlobalStoragePath ) ,
588+ ] )
589+
590+ // Exactly one call should win the atomic rename; the other should skip gracefully.
591+ expect ( result1 . importedTaskCount + result2 . importedTaskCount ) . toBe ( 1 )
592+
593+ const destTaskDir = path . join ( zooGlobalStoragePath , "tasks" , "task-concurrent" )
594+
595+ // The winning import must have written all three files completely.
596+ expect ( await fs . readFile ( path . join ( destTaskDir , "history_item.json" ) , "utf8" ) ) . toBe (
597+ makeHistoryItem ( "task-concurrent" ) ,
598+ )
599+ expect ( await fs . readFile ( path . join ( destTaskDir , "ui_messages.json" ) , "utf8" ) ) . toBe ( "concurrent-ui" )
600+ expect ( await fs . readFile ( path . join ( destTaskDir , "api_conversation_history.json" ) , "utf8" ) ) . toBe (
601+ "concurrent-api" ,
602+ )
603+
604+ // No staging directories should be left behind.
605+ const tasksEntries = await fs . readdir ( path . join ( zooGlobalStoragePath , "tasks" ) )
606+ expect ( tasksEntries . filter ( ( e ) => e . startsWith ( "_staging_" ) ) ) . toHaveLength ( 0 )
607+ } )
564608} )
0 commit comments