@@ -17,6 +17,7 @@ import { formatError } from "../server-utils.js";
1717import type { GatewayRequestHandlers } from "./types.js" ;
1818
1919const SHORT_TERM_STORE_RELATIVE_PATH = path . join ( "memory" , ".dreams" , "short-term-recall.json" ) ;
20+ const SHORT_TERM_PHASE_SIGNAL_RELATIVE_PATH = path . join ( "memory" , ".dreams" , "phase-signals.json" ) ;
2021const MANAGED_DEEP_SLEEP_CRON_NAME = "Memory Dreaming Promotion" ;
2122const MANAGED_DEEP_SLEEP_CRON_TAG = "[managed-by=memory-core.short-term-promotion]" ;
2223const DEEP_SLEEP_SYSTEM_EVENT_TEXT = "__openclaw_memory_core_short_term_promotion_dream__" ;
@@ -56,11 +57,19 @@ type DoctorMemoryDreamingPayload = {
5657 storageMode : "inline" | "separate" | "both" ;
5758 separateReports : boolean ;
5859 shortTermCount : number ;
60+ recallSignalCount : number ;
61+ dailySignalCount : number ;
62+ totalSignalCount : number ;
63+ phaseSignalCount : number ;
64+ lightPhaseHitCount : number ;
65+ remPhaseHitCount : number ;
5966 promotedTotal : number ;
6067 promotedToday : number ;
6168 storePath ?: string ;
69+ phaseSignalPath ?: string ;
6270 lastPromotedAt ?: string ;
6371 storeError ?: string ;
72+ phaseSignalError ?: string ;
6473 phases : {
6574 light : DoctorMemoryLightDreamingPayload ;
6675 deep : DoctorMemoryDeepDreamingPayload ;
@@ -106,11 +115,19 @@ function resolveDreamingConfig(
106115) : Omit <
107116 DoctorMemoryDreamingPayload ,
108117 | "shortTermCount"
118+ | "recallSignalCount"
119+ | "dailySignalCount"
120+ | "totalSignalCount"
121+ | "phaseSignalCount"
122+ | "lightPhaseHitCount"
123+ | "remPhaseHitCount"
109124 | "promotedTotal"
110125 | "promotedToday"
111126 | "storePath"
127+ | "phaseSignalPath"
112128 | "lastPromotedAt"
113129 | "storeError"
130+ | "phaseSignalError"
114131> {
115132 const resolved = resolveMemoryDreamingConfig ( {
116133 pluginConfig : resolveMemoryCorePluginConfig ( cfg ) ,
@@ -180,31 +197,55 @@ function isShortTermMemoryPath(filePath: string): boolean {
180197type DreamingStoreStats = Pick <
181198 DoctorMemoryDreamingPayload ,
182199 | "shortTermCount"
200+ | "recallSignalCount"
201+ | "dailySignalCount"
202+ | "totalSignalCount"
203+ | "phaseSignalCount"
204+ | "lightPhaseHitCount"
205+ | "remPhaseHitCount"
183206 | "promotedTotal"
184207 | "promotedToday"
185208 | "storePath"
209+ | "phaseSignalPath"
186210 | "lastPromotedAt"
187211 | "storeError"
212+ | "phaseSignalError"
188213> ;
189214
215+ function toNonNegativeInt ( value : unknown ) : number {
216+ const num = Number ( value ) ;
217+ if ( ! Number . isFinite ( num ) ) {
218+ return 0 ;
219+ }
220+ return Math . max ( 0 , Math . floor ( num ) ) ;
221+ }
222+
190223async function loadDreamingStoreStats (
191224 workspaceDir : string ,
192225 nowMs : number ,
193226 timezone ?: string ,
194227) : Promise < DreamingStoreStats > {
195228 const storePath = path . join ( workspaceDir , SHORT_TERM_STORE_RELATIVE_PATH ) ;
229+ const phaseSignalPath = path . join ( workspaceDir , SHORT_TERM_PHASE_SIGNAL_RELATIVE_PATH ) ;
196230 try {
197231 const raw = await fs . readFile ( storePath , "utf-8" ) ;
198232 const parsed = JSON . parse ( raw ) as unknown ;
199233 const store = asRecord ( parsed ) ;
200234 const entries = asRecord ( store ?. entries ) ?? { } ;
201235 let shortTermCount = 0 ;
236+ let recallSignalCount = 0 ;
237+ let dailySignalCount = 0 ;
238+ let totalSignalCount = 0 ;
239+ let phaseSignalCount = 0 ;
240+ let lightPhaseHitCount = 0 ;
241+ let remPhaseHitCount = 0 ;
202242 let promotedTotal = 0 ;
203243 let promotedToday = 0 ;
204244 let latestPromotedAtMs = Number . NEGATIVE_INFINITY ;
205245 let latestPromotedAt : string | undefined ;
246+ const activeKeys = new Set < string > ( ) ;
206247
207- for ( const value of Object . values ( entries ) ) {
248+ for ( const [ entryKey , value ] of Object . entries ( entries ) ) {
208249 const entry = asRecord ( value ) ;
209250 if ( ! entry ) {
210251 continue ;
@@ -217,6 +258,12 @@ async function loadDreamingStoreStats(
217258 const promotedAt = normalizeTrimmedString ( entry . promotedAt ) ;
218259 if ( ! promotedAt ) {
219260 shortTermCount += 1 ;
261+ activeKeys . add ( entryKey ) ;
262+ const recallCount = toNonNegativeInt ( entry . recallCount ) ;
263+ const dailyCount = toNonNegativeInt ( entry . dailyCount ) ;
264+ recallSignalCount += recallCount ;
265+ dailySignalCount += dailyCount ;
266+ totalSignalCount += recallCount + dailyCount ;
220267 continue ;
221268 }
222269 promotedTotal += 1 ;
@@ -230,52 +277,118 @@ async function loadDreamingStoreStats(
230277 }
231278 }
232279
280+ let phaseSignalError : string | undefined ;
281+ try {
282+ const phaseRaw = await fs . readFile ( phaseSignalPath , "utf-8" ) ;
283+ const parsedPhase = JSON . parse ( phaseRaw ) as unknown ;
284+ const phaseStore = asRecord ( parsedPhase ) ;
285+ const phaseEntries = asRecord ( phaseStore ?. entries ) ?? { } ;
286+ for ( const [ key , value ] of Object . entries ( phaseEntries ) ) {
287+ if ( ! activeKeys . has ( key ) ) {
288+ continue ;
289+ }
290+ const phaseEntry = asRecord ( value ) ;
291+ const lightHits = toNonNegativeInt ( phaseEntry ?. lightHits ) ;
292+ const remHits = toNonNegativeInt ( phaseEntry ?. remHits ) ;
293+ lightPhaseHitCount += lightHits ;
294+ remPhaseHitCount += remHits ;
295+ phaseSignalCount += lightHits + remHits ;
296+ }
297+ } catch ( err ) {
298+ const code = ( err as NodeJS . ErrnoException | undefined ) ?. code ;
299+ if ( code !== "ENOENT" ) {
300+ phaseSignalError = formatError ( err ) ;
301+ }
302+ }
303+
233304 return {
234305 shortTermCount,
306+ recallSignalCount,
307+ dailySignalCount,
308+ totalSignalCount,
309+ phaseSignalCount,
310+ lightPhaseHitCount,
311+ remPhaseHitCount,
235312 promotedTotal,
236313 promotedToday,
237314 storePath,
315+ phaseSignalPath,
238316 ...( latestPromotedAt ? { lastPromotedAt : latestPromotedAt } : { } ) ,
317+ ...( phaseSignalError ? { phaseSignalError } : { } ) ,
239318 } ;
240319 } catch ( err ) {
241320 const code = ( err as NodeJS . ErrnoException | undefined ) ?. code ;
242321 if ( code === "ENOENT" ) {
243322 return {
244323 shortTermCount : 0 ,
324+ recallSignalCount : 0 ,
325+ dailySignalCount : 0 ,
326+ totalSignalCount : 0 ,
327+ phaseSignalCount : 0 ,
328+ lightPhaseHitCount : 0 ,
329+ remPhaseHitCount : 0 ,
245330 promotedTotal : 0 ,
246331 promotedToday : 0 ,
247332 storePath,
333+ phaseSignalPath,
248334 } ;
249335 }
250336 return {
251337 shortTermCount : 0 ,
338+ recallSignalCount : 0 ,
339+ dailySignalCount : 0 ,
340+ totalSignalCount : 0 ,
341+ phaseSignalCount : 0 ,
342+ lightPhaseHitCount : 0 ,
343+ remPhaseHitCount : 0 ,
252344 promotedTotal : 0 ,
253345 promotedToday : 0 ,
254346 storePath,
347+ phaseSignalPath,
255348 storeError : formatError ( err ) ,
256349 } ;
257350 }
258351}
259352
260353function mergeDreamingStoreStats ( stats : DreamingStoreStats [ ] ) : DreamingStoreStats {
261354 let shortTermCount = 0 ;
355+ let recallSignalCount = 0 ;
356+ let dailySignalCount = 0 ;
357+ let totalSignalCount = 0 ;
358+ let phaseSignalCount = 0 ;
359+ let lightPhaseHitCount = 0 ;
360+ let remPhaseHitCount = 0 ;
262361 let promotedTotal = 0 ;
263362 let promotedToday = 0 ;
264363 let latestPromotedAtMs = Number . NEGATIVE_INFINITY ;
265364 let lastPromotedAt : string | undefined ;
266365 const storePaths = new Set < string > ( ) ;
366+ const phaseSignalPaths = new Set < string > ( ) ;
267367 const storeErrors : string [ ] = [ ] ;
368+ const phaseSignalErrors : string [ ] = [ ] ;
268369
269370 for ( const stat of stats ) {
270371 shortTermCount += stat . shortTermCount ;
372+ recallSignalCount += stat . recallSignalCount ;
373+ dailySignalCount += stat . dailySignalCount ;
374+ totalSignalCount += stat . totalSignalCount ;
375+ phaseSignalCount += stat . phaseSignalCount ;
376+ lightPhaseHitCount += stat . lightPhaseHitCount ;
377+ remPhaseHitCount += stat . remPhaseHitCount ;
271378 promotedTotal += stat . promotedTotal ;
272379 promotedToday += stat . promotedToday ;
273380 if ( stat . storePath ) {
274381 storePaths . add ( stat . storePath ) ;
275382 }
383+ if ( stat . phaseSignalPath ) {
384+ phaseSignalPaths . add ( stat . phaseSignalPath ) ;
385+ }
276386 if ( stat . storeError ) {
277387 storeErrors . push ( stat . storeError ) ;
278388 }
389+ if ( stat . phaseSignalError ) {
390+ phaseSignalErrors . push ( stat . phaseSignalError ) ;
391+ }
279392 const promotedAtMs = stat . lastPromotedAt ? Date . parse ( stat . lastPromotedAt ) : Number . NaN ;
280393 if ( Number . isFinite ( promotedAtMs ) && promotedAtMs > latestPromotedAtMs ) {
281394 latestPromotedAtMs = promotedAtMs ;
@@ -285,15 +398,27 @@ function mergeDreamingStoreStats(stats: DreamingStoreStats[]): DreamingStoreStat
285398
286399 return {
287400 shortTermCount,
401+ recallSignalCount,
402+ dailySignalCount,
403+ totalSignalCount,
404+ phaseSignalCount,
405+ lightPhaseHitCount,
406+ remPhaseHitCount,
288407 promotedTotal,
289408 promotedToday,
290409 ...( storePaths . size === 1 ? { storePath : [ ...storePaths ] [ 0 ] } : { } ) ,
410+ ...( phaseSignalPaths . size === 1 ? { phaseSignalPath : [ ...phaseSignalPaths ] [ 0 ] } : { } ) ,
291411 ...( lastPromotedAt ? { lastPromotedAt } : { } ) ,
292412 ...( storeErrors . length === 1
293413 ? { storeError : storeErrors [ 0 ] }
294414 : storeErrors . length > 1
295415 ? { storeError : `${ storeErrors . length } dreaming stores had read errors.` }
296416 : { } ) ,
417+ ...( phaseSignalErrors . length === 1
418+ ? { phaseSignalError : phaseSignalErrors [ 0 ] }
419+ : phaseSignalErrors . length > 1
420+ ? { phaseSignalError : `${ phaseSignalErrors . length } phase signal stores had read errors.` }
421+ : { } ) ,
297422 } ;
298423}
299424
@@ -472,6 +597,12 @@ export const doctorHandlers: GatewayRequestHandlers = {
472597 )
473598 : {
474599 shortTermCount : 0 ,
600+ recallSignalCount : 0 ,
601+ dailySignalCount : 0 ,
602+ totalSignalCount : 0 ,
603+ phaseSignalCount : 0 ,
604+ lightPhaseHitCount : 0 ,
605+ remPhaseHitCount : 0 ,
475606 promotedTotal : 0 ,
476607 promotedToday : 0 ,
477608 } ;
0 commit comments