@@ -758,6 +758,94 @@ describe("memory index", () => {
758758 }
759759 } ) ;
760760
761+ it ( "runs a full reindex after fallback activates during targeted sync" , async ( ) => {
762+ const stateDir = path . join ( fixtureRoot , `state-targeted-fallback-${ randomUUID ( ) } ` ) ;
763+ const sessionDir = path . join ( stateDir , "agents" , "main" , "sessions" ) ;
764+ const sessionPath = path . join ( sessionDir , "targeted-fallback.jsonl" ) ;
765+ const storePath = path . join ( workspaceDir , `index-targeted-fallback-${ randomUUID ( ) } .sqlite` ) ;
766+ const previousStateDir = process . env . OPENCLAW_STATE_DIR ;
767+ process . env . OPENCLAW_STATE_DIR = stateDir ;
768+
769+ await fs . mkdir ( sessionDir , { recursive : true } ) ;
770+ await fs . writeFile (
771+ sessionPath ,
772+ `${ JSON . stringify ( {
773+ type : "message" ,
774+ message : { role : "user" , content : [ { type : "text" , text : "fallback transcript v1" } ] } ,
775+ } ) } \n`,
776+ ) ;
777+
778+ try {
779+ const manager = requireManager (
780+ await getMemorySearchManager ( {
781+ cfg : createCfg ( {
782+ storePath,
783+ sources : [ "sessions" ] ,
784+ sessionMemory : true ,
785+ } ) ,
786+ agentId : "main" ,
787+ } ) ,
788+ ) ;
789+ await manager . sync ( { reason : "test" } ) ;
790+
791+ const internal = manager as unknown as {
792+ syncSessionFiles : ( params : {
793+ targetSessionFiles ?: string [ ] ;
794+ needsFullReindex : boolean ;
795+ } ) => Promise < void > ;
796+ shouldFallbackOnError : ( message : string ) => boolean ;
797+ activateFallbackProvider : ( reason : string ) => Promise < boolean > ;
798+ runUnsafeReindex : ( params : {
799+ reason ?: string ;
800+ force ?: boolean ;
801+ progress ?: unknown ;
802+ } ) => Promise < void > ;
803+ } ;
804+ const originalSyncSessionFiles = internal . syncSessionFiles . bind ( manager ) ;
805+ const originalShouldFallbackOnError = internal . shouldFallbackOnError . bind ( manager ) ;
806+ const originalActivateFallbackProvider = internal . activateFallbackProvider . bind ( manager ) ;
807+ const originalRunUnsafeReindex = internal . runUnsafeReindex . bind ( manager ) ;
808+
809+ internal . syncSessionFiles = async ( params ) => {
810+ if ( params . targetSessionFiles ?. length ) {
811+ throw new Error ( "embedding backend failed" ) ;
812+ }
813+ return await originalSyncSessionFiles ( params ) ;
814+ } ;
815+ internal . shouldFallbackOnError = ( ) => true ;
816+ const activateFallbackProvider = vi . fn ( async ( ) => true ) ;
817+ internal . activateFallbackProvider = activateFallbackProvider ;
818+ const runUnsafeReindex = vi . fn ( async ( ) => { } ) ;
819+ internal . runUnsafeReindex = runUnsafeReindex ;
820+
821+ await manager . sync ( {
822+ reason : "post-compaction" ,
823+ sessionFiles : [ sessionPath ] ,
824+ } ) ;
825+
826+ expect ( activateFallbackProvider ) . toHaveBeenCalledWith ( "embedding backend failed" ) ;
827+ expect ( runUnsafeReindex ) . toHaveBeenCalledWith ( {
828+ reason : "post-compaction" ,
829+ force : true ,
830+ progress : undefined ,
831+ } ) ;
832+
833+ internal . syncSessionFiles = originalSyncSessionFiles ;
834+ internal . shouldFallbackOnError = originalShouldFallbackOnError ;
835+ internal . activateFallbackProvider = originalActivateFallbackProvider ;
836+ internal . runUnsafeReindex = originalRunUnsafeReindex ;
837+ await manager . close ?.( ) ;
838+ } finally {
839+ if ( previousStateDir === undefined ) {
840+ delete process . env . OPENCLAW_STATE_DIR ;
841+ } else {
842+ process . env . OPENCLAW_STATE_DIR = previousStateDir ;
843+ }
844+ await fs . rm ( stateDir , { recursive : true , force : true } ) ;
845+ await fs . rm ( storePath , { force : true } ) ;
846+ }
847+ } ) ;
848+
761849 it ( "reindexes when the embedding model changes" , async ( ) => {
762850 const base = createCfg ( { storePath : indexModelPath } ) ;
763851 const baseAgents = base . agents ! ;
0 commit comments