@@ -642,6 +642,205 @@ describe("doctor.memory.status", () => {
642642 }
643643 } ) ;
644644
645+ it ( "counts timestamp-suffixed and slugged recall-store entries in dreaming stats" , async ( ) => {
646+ const now = Date . parse ( "2026-06-06T00:30:00.000Z" ) ;
647+ vi . useFakeTimers ( ) ;
648+ vi . setSystemTime ( now ) ;
649+ const recentIso = "2026-06-05T23:45:00.000Z" ;
650+ const olderIso = "2026-06-03T10:00:00.000Z" ;
651+ const workspaceRoot = await fs . mkdtemp ( path . join ( os . tmpdir ( ) , "doctor-memory-slug-" ) ) ;
652+ const mainWorkspaceDir = path . join ( workspaceRoot , "main" ) ;
653+ const mainStorePath = path . join (
654+ mainWorkspaceDir ,
655+ "memory" ,
656+ ".dreams" ,
657+ "short-term-recall.json" ,
658+ ) ;
659+ const mainPhaseSignalPath = path . join (
660+ mainWorkspaceDir ,
661+ "memory" ,
662+ ".dreams" ,
663+ "phase-signals.json" ,
664+ ) ;
665+ await fs . mkdir ( path . dirname ( mainStorePath ) , { recursive : true } ) ;
666+ // Include both timestamp-suffixed (-HHMM) and slugged (-vendor-pitch) paths
667+ await fs . writeFile (
668+ mainStorePath ,
669+ `${ JSON . stringify (
670+ {
671+ version : 1 ,
672+ updatedAt : recentIso ,
673+ entries : {
674+ "memory:memory/2026-06-04-1503.md:1:2" : {
675+ path : "memory/2026-06-04-1503.md" ,
676+ startLine : 1 ,
677+ endLine : 2 ,
678+ snippet : "Timestamp-suffixed entry." ,
679+ source : "memory" ,
680+ recallCount : 4 ,
681+ dailyCount : 2 ,
682+ lastRecalledAt : recentIso ,
683+ promotedAt : recentIso ,
684+ } ,
685+ "memory:memory/2026-06-03-vendor-pitch.md:1:2" : {
686+ path : "memory/2026-06-03-vendor-pitch.md" ,
687+ startLine : 1 ,
688+ endLine : 2 ,
689+ snippet : "Slugged llmSlug entry." ,
690+ source : "memory" ,
691+ recallCount : 7 ,
692+ dailyCount : 4 ,
693+ promotedAt : olderIso ,
694+ } ,
695+ "memory:memory/2026-06-05-0930.md:1:2" : {
696+ path : "memory/2026-06-05-0930.md" ,
697+ startLine : 1 ,
698+ endLine : 2 ,
699+ snippet : "Another timestamp-suffixed entry." ,
700+ source : "memory" ,
701+ recallCount : 3 ,
702+ dailyCount : 1 ,
703+ } ,
704+ } ,
705+ } ,
706+ null ,
707+ 2 ,
708+ ) } \n`,
709+ "utf-8" ,
710+ ) ;
711+ await fs . writeFile (
712+ mainPhaseSignalPath ,
713+ `${ JSON . stringify (
714+ {
715+ version : 1 ,
716+ updatedAt : recentIso ,
717+ entries : {
718+ "memory:memory/2026-06-04-1503.md:1:2" : {
719+ lightHits : 2 ,
720+ remHits : 3 ,
721+ } ,
722+ "memory:memory/2026-06-03-vendor-pitch.md:1:2" : {
723+ lightHits : 1 ,
724+ remHits : 2 ,
725+ } ,
726+ "memory:memory/2026-06-05-0930.md:1:2" : {
727+ lightHits : 1 ,
728+ remHits : 1 ,
729+ } ,
730+ } ,
731+ } ,
732+ null ,
733+ 2 ,
734+ ) } \n`,
735+ "utf-8" ,
736+ ) ;
737+
738+ getRuntimeConfig . mockReturnValue ( {
739+ agents : {
740+ defaults : {
741+ userTimezone : "America/Los_Angeles" ,
742+ memorySearch : {
743+ enabled : true ,
744+ } ,
745+ } ,
746+ list : [ ] ,
747+ } ,
748+ plugins : {
749+ entries : {
750+ "memory-core" : {
751+ config : {
752+ dreaming : {
753+ enabled : true ,
754+ frequency : "0 */4 * * *" ,
755+ phases : {
756+ deep : {
757+ recencyHalfLifeDays : 21 ,
758+ maxAgeDays : 30 ,
759+ } ,
760+ } ,
761+ } ,
762+ } ,
763+ } ,
764+ } ,
765+ } ,
766+ } as OpenClawConfig ) ;
767+ resolveAgentWorkspaceDir . mockReturnValue ( mainWorkspaceDir ) ;
768+ const close = vi . fn ( ) . mockResolvedValue ( undefined ) ;
769+ getMemorySearchManager . mockResolvedValue ( {
770+ manager : {
771+ status : ( ) => ( { provider : "gemini" , workspaceDir : mainWorkspaceDir } ) ,
772+ probeEmbeddingAvailability : vi . fn ( ) . mockResolvedValue ( { ok : true } ) ,
773+ close,
774+ } ,
775+ } ) ;
776+ const cronList = vi . fn ( async ( ) => [
777+ {
778+ name : "Memory Dreaming Promotion" ,
779+ description : "[managed-by=memory-core.short-term-promotion] test" ,
780+ enabled : true ,
781+ payload : {
782+ kind : "systemEvent" ,
783+ text : "__openclaw_memory_core_short_term_promotion_dream__" ,
784+ } ,
785+ state : { nextRunAtMs : now + 60_000 } ,
786+ } ,
787+ ] ) ;
788+ const respond = vi . fn ( ) ;
789+
790+ try {
791+ await invokeDoctorMemoryStatus ( respond , { cron : { list : cronList } } ) ;
792+ const payload = respondPayload ( respond ) ;
793+ const dreaming = expectRecordFields ( payload . dreaming , {
794+ enabled : true ,
795+ // All 3 entries (including timestamp and slugged) should be counted
796+ // Active (non-promoted) entries: only 2026-06-05-0930.md
797+ shortTermCount : 1 ,
798+ recallSignalCount : 3 , // from active entry
799+ dailySignalCount : 1 , // from active entry
800+ totalSignalCount : 4 , // recallSignalCount + dailySignalCount
801+ phaseSignalCount : 2 , // lightHits 1 + remHits 1 from active entry
802+ lightPhaseHitCount : 1 ,
803+ remPhaseHitCount : 1 ,
804+ promotedTotal : 2 , // both promoted entries counted
805+ promotedToday : 1 , // only 2026-06-04-1503 promoted today
806+ } ) ;
807+ // Verify timestamp-suffixed entry is present and counted
808+ expectRecordFields (
809+ findRecordByField ( dreaming . promotedEntries , "path" , "memory/2026-06-04-1503.md" ) ,
810+ {
811+ promotedAt : recentIso ,
812+ snippet : "Timestamp-suffixed entry." ,
813+ } ,
814+ ) ;
815+ // Verify slugged entry is present and counted
816+ expectRecordFields (
817+ findRecordByField ( dreaming . promotedEntries , "path" , "memory/2026-06-03-vendor-pitch.md" ) ,
818+ {
819+ promotedAt : olderIso ,
820+ snippet : "Slugged llmSlug entry." ,
821+ } ,
822+ ) ;
823+ // Verify short-term entry with timestamp suffix is present
824+ expectRecordFields ( ( dreaming . shortTermEntries as unknown [ ] ) [ 0 ] , {
825+ path : "memory/2026-06-05-0930.md" ,
826+ snippet : "Another timestamp-suffixed entry." ,
827+ totalSignalCount : 4 ,
828+ lightHits : 1 ,
829+ remHits : 1 ,
830+ phaseHitCount : 2 ,
831+ } ) ;
832+ // Verify signal entries include slugged path
833+ expectRecordFields ( ( dreaming . signalEntries as unknown [ ] ) [ 0 ] , {
834+ path : "memory/2026-06-05-0930.md" ,
835+ totalSignalCount : 4 ,
836+ } ) ;
837+ expect ( close ) . toHaveBeenCalled ( ) ;
838+ } finally {
839+ vi . useRealTimers ( ) ;
840+ await fs . rm ( workspaceRoot , { recursive : true , force : true } ) ;
841+ }
842+ } ) ;
843+
645844 it ( "scopes dreaming status to the requested agent workspace" , async ( ) => {
646845 const workspaceRoot = await fs . mkdtemp ( path . join ( os . tmpdir ( ) , "doctor-memory-selected-" ) ) ;
647846 const mainWorkspaceDir = path . join ( workspaceRoot , "main" ) ;
0 commit comments