@@ -917,146 +917,3 @@ describe('poisoned payload persistence', () => {
917917 expect ( block . outputRaw . self ) . toBe ( '[Circular]' )
918918 } )
919919} )
920-
921- describe ( 'run state recovery' , ( ) => {
922- // Point persistence at a temp dir via the explicit test override.
923- const chatDir = path . join ( TEST_ROOT , 'codebuff-test-recovery' )
924-
925- const runStateWithSession = ( marker : string ) : RunState =>
926- ( {
927- sessionState : {
928- mainAgentState : {
929- messageHistory : [ { role : 'user' , content : marker } ] ,
930- } ,
931- } ,
932- output : { type : 'lastMessage' , value : marker } ,
933- traceSessionId : 'trace-1' ,
934- } ) as unknown as RunState
935-
936- const runStatePath = path . join ( chatDir , 'run-state.json' )
937- const bakPath = runStatePath + '.bak'
938- const messagesPath = path . join ( chatDir , 'chat-messages.json' )
939-
940- const writePrimary = ( contents : string ) =>
941- fs . writeFileSync ( runStatePath , contents )
942- const validMessages = JSON . stringify ( [
943- {
944- id : 'msg-1' ,
945- variant : 'user' ,
946- content : 'the prompt' ,
947- timestamp : new Date ( ) . toISOString ( ) ,
948- } ,
949- ] as ChatMessage [ ] )
950-
951- beforeEach ( ( ) => {
952- fs . rmSync ( chatDir , { recursive : true , force : true } )
953- fs . mkdirSync ( chatDir , { recursive : true } )
954- setChatDirOverrideForTesting ( chatDir )
955- } )
956-
957- afterEach ( ( ) => {
958- setChatDirOverrideForTesting ( undefined )
959- } )
960-
961- test ( 'recovers agent context from the .bak when the primary is torn' , ( ) => {
962- writePrimary ( '{"sessionState": {"main"' ) // torn: power loss after rename
963- fs . writeFileSync ( bakPath , JSON . stringify ( runStateWithSession ( 'from-bak' ) ) )
964- fs . writeFileSync ( messagesPath , validMessages )
965-
966- const loaded = loadMostRecentChatState ( )
967- expect ( loaded ) . not . toBeNull ( )
968- // Agent context survived — the model is NOT amnesiac next turn.
969- expect ( ( loaded ! . runState as any ) . sessionState ) . toBeDefined ( )
970- expect ( loaded ! . runStateRestored ) . toBe ( true )
971- // Self-healed: the primary is the recovered generation again.
972- expect (
973- JSON . parse ( fs . readFileSync ( runStatePath , 'utf8' ) ) . output . value ,
974- ) . toBe ( 'from-bak' )
975- } )
976-
977- test ( 'recovers from the newest complete checkpoint temp when bak is absent' , ( ) => {
978- writePrimary ( '{ torn' )
979- fs . writeFileSync ( messagesPath , validMessages )
980- // Two temps: an older torn one and a newer complete one (SIGKILL between
981- // write and rename leaves the latter behind).
982- fs . writeFileSync (
983- runStatePath + '.999.oldest.tmp' ,
984- '{"half":' ,
985- )
986- fs . writeFileSync (
987- runStatePath + '.1234.newest.tmp' ,
988- JSON . stringify ( runStateWithSession ( 'from-tmp' ) ) ,
989- )
990-
991- const loaded = loadMostRecentChatState ( )
992- expect ( loaded ) . not . toBeNull ( )
993- expect ( ( loaded ! . runState as any ) . sessionState ) . toBeDefined ( )
994- expect ( loaded ! . runStateRestored ) . toBe ( true )
995- // Self-healed into the primary.
996- expect (
997- JSON . parse ( fs . readFileSync ( runStatePath , 'utf8' ) ) . output . value ,
998- ) . toBe ( 'from-tmp' )
999- } )
1000-
1001- test ( 'flags a healthy primary as fully restored' , ( ) => {
1002- writePrimary ( JSON . stringify ( runStateWithSession ( 'healthy' ) ) )
1003- fs . writeFileSync ( messagesPath , validMessages )
1004-
1005- const loaded = loadMostRecentChatState ( )
1006- expect ( loaded ! . runStateRestored ) . toBe ( true )
1007- expect ( ( loaded ! . runState as any ) . sessionState ) . toBeDefined ( )
1008- } )
1009-
1010- test ( 'falls back to a context-less placeholder with the loss flagged when nothing recovers' , ( ) => {
1011- writePrimary ( '{ torn' )
1012- fs . writeFileSync ( messagesPath , validMessages )
1013-
1014- const loaded = loadMostRecentChatState ( )
1015- expect ( loaded ) . not . toBeNull ( )
1016- // The amnesia carrier: no sessionState — the SDK will start a fresh
1017- // session next turn. runStateRestored=false is what makes the UI say so
1018- // instead of the model silently forgetting every earlier turn.
1019- expect ( ( loaded ! . runState as any ) . sessionState ) . toBeUndefined ( )
1020- expect ( loaded ! . runStateRestored ) . toBe ( false )
1021- // The transcript still survives.
1022- expect ( loaded ! . messages . length ) . toBe ( 1 )
1023- } )
1024-
1025- test ( 'saveChatState rotates the previous primary into .bak' , ( ) => {
1026- saveChatState ( runStateWithSession ( 'generation-1' ) , [
1027- {
1028- id : 'msg-1' ,
1029- variant : 'user' ,
1030- content : 'first' ,
1031- timestamp : new Date ( ) . toISOString ( ) ,
1032- } ,
1033- ] )
1034- expect ( fs . existsSync ( bakPath ) ) . toBe ( false )
1035-
1036- saveChatState ( runStateWithSession ( 'generation-2' ) , [
1037- {
1038- id : 'msg-2' ,
1039- variant : 'user' ,
1040- content : 'second' ,
1041- timestamp : new Date ( ) . toISOString ( ) ,
1042- } ,
1043- ] )
1044-
1045- expect ( JSON . parse ( fs . readFileSync ( runStatePath , 'utf8' ) ) . output . value ) . toBe (
1046- 'generation-2' ,
1047- )
1048- expect ( JSON . parse ( fs . readFileSync ( bakPath , 'utf8' ) ) . output . value ) . toBe (
1049- 'generation-1' ,
1050- )
1051- } )
1052-
1053- test ( 'clearChatState removes the backup too' , ( ) => {
1054- writePrimary ( JSON . stringify ( runStateWithSession ( 'x' ) ) )
1055- fs . writeFileSync ( bakPath , JSON . stringify ( runStateWithSession ( 'bak' ) ) )
1056- fs . writeFileSync ( messagesPath , validMessages )
1057-
1058- clearChatState ( )
1059- expect ( fs . existsSync ( runStatePath ) ) . toBe ( false )
1060- expect ( fs . existsSync ( bakPath ) ) . toBe ( false )
1061- } )
1062- } )
0 commit comments