1- import os from "node:os" ;
2- import path from "node:path" ;
3- import { resolveAgentWorkspaceDir } from "../agents/agent-scope.js" ;
41import type { OpenClawConfig } from "../config/config.js" ;
5- import { resolveStateDir } from "../config/paths.js" ;
62import { createSubsystemLogger } from "../logging/subsystem.js" ;
73import { resolveGlobalSingleton } from "../shared/global-singleton.js" ;
84import type { ResolvedQmdConfig } from "./backend-config.js" ;
@@ -57,13 +53,13 @@ export async function getMemorySearchManager(params: {
5753 return { manager : cached } ;
5854 }
5955 if ( statusOnly ) {
60- const manager = new QmdStatusOnlyManager ( {
61- cfg : params . cfg ,
62- agentId : params . agentId ,
63- resolved : resolved . qmd ,
64- } ) ;
65- QMD_MANAGER_CACHE . set ( cacheKey , manager ) ;
66- return { manager } ;
56+ const fullCached = QMD_MANAGER_CACHE . get ( ` ${ baseCacheKey } :full` ) ;
57+ if ( fullCached ) {
58+ // Status callers often close the manager they receive. Wrap the live
59+ // full manager with a no-op close so health/status probes do not tear
60+ // down the active QMD manager for the process.
61+ return { manager : new BorrowedMemoryManager ( fullCached ) } ;
62+ }
6763 }
6864 try {
6965 const { QmdMemoryManager } = await import ( "./qmd-manager.js" ) ;
@@ -75,8 +71,11 @@ export async function getMemorySearchManager(params: {
7571 } ) ;
7672 if ( primary ) {
7773 if ( statusOnly ) {
78- QMD_MANAGER_CACHE . set ( cacheKey , primary ) ;
79- return { manager : primary } ;
74+ const wrapper = new CachedStatusMemoryManager ( primary , ( ) => {
75+ QMD_MANAGER_CACHE . delete ( cacheKey ) ;
76+ } ) ;
77+ QMD_MANAGER_CACHE . set ( cacheKey , wrapper ) ;
78+ return { manager : wrapper } ;
8079 }
8180 const wrapper = new FallbackMemoryManager (
8281 {
@@ -109,87 +108,95 @@ export async function getMemorySearchManager(params: {
109108 }
110109}
111110
112- class QmdStatusOnlyManager implements MemorySearchManager {
113- private readonly workspaceDir : string ;
114- private readonly indexPath : string ;
115- private readonly sourceSet : Set < "memory" | "sessions" > ;
111+ class BorrowedMemoryManager implements MemorySearchManager {
112+ constructor ( private readonly inner : MemorySearchManager ) { }
116113
117- constructor (
118- private readonly params : {
119- cfg : OpenClawConfig ;
120- agentId : string ;
121- resolved : ResolvedQmdConfig ;
122- } ,
114+ async search (
115+ query : string ,
116+ opts ?: { maxResults ?: number ; minScore ?: number ; sessionKey ?: string } ,
123117 ) {
124- this . workspaceDir = resolveAgentWorkspaceDir ( params . cfg , params . agentId ) ;
125- const stateDir = resolveStateDir ( process . env , os . homedir ) ;
126- this . indexPath = path . join (
127- stateDir ,
128- "agents" ,
129- params . agentId ,
130- "qmd" ,
131- "xdg-cache" ,
132- "qmd" ,
133- "index.sqlite" ,
134- ) ;
135- this . sourceSet = new Set (
136- params . resolved . collections . map ( ( collection ) =>
137- collection . kind === "sessions" ? "sessions" : "memory" ,
138- ) ,
139- ) ;
118+ return await this . inner . search ( query , opts ) ;
140119 }
141120
142- async search ( ) : Promise < never > {
143- throw new Error ( "memory search unavailable in status-only mode" ) ;
121+ async readFile ( params : { relPath : string ; from ?: number ; lines ?: number } ) {
122+ return await this . inner . readFile ( params ) ;
144123 }
145124
146- async readFile ( ) : Promise < never > {
147- throw new Error ( "memory read unavailable in status-only mode" ) ;
125+ status ( ) {
126+ return this . inner . status ( ) ;
127+ }
128+
129+ async sync ( params ?: {
130+ reason ?: string ;
131+ force ?: boolean ;
132+ sessionFiles ?: string [ ] ;
133+ progress ?: ( update : MemorySyncProgressUpdate ) => void ;
134+ } ) {
135+ await this . inner . sync ?.( params ) ;
136+ }
137+
138+ async probeEmbeddingAvailability ( ) : Promise < MemoryEmbeddingProbeResult > {
139+ return await this . inner . probeEmbeddingAvailability ( ) ;
140+ }
141+
142+ async probeVectorAvailability ( ) {
143+ return await this . inner . probeVectorAvailability ( ) ;
144+ }
145+
146+ async close ( ) { }
147+ }
148+
149+ class CachedStatusMemoryManager implements MemorySearchManager {
150+ private closed = false ;
151+
152+ constructor (
153+ private readonly inner : MemorySearchManager ,
154+ private readonly onClose : ( ) => void ,
155+ ) { }
156+
157+ async search (
158+ query : string ,
159+ opts ?: { maxResults ?: number ; minScore ?: number ; sessionKey ?: string } ,
160+ ) {
161+ return await this . inner . search ( query , opts ) ;
162+ }
163+
164+ async readFile ( params : { relPath : string ; from ?: number ; lines ?: number } ) {
165+ return await this . inner . readFile ( params ) ;
148166 }
149167
150168 status ( ) {
151- return {
152- backend : "qmd" as const ,
153- provider : "qmd" ,
154- model : "qmd" ,
155- requestedProvider : "qmd" ,
156- files : 0 ,
157- chunks : 0 ,
158- dirty : false ,
159- workspaceDir : this . workspaceDir ,
160- dbPath : this . indexPath ,
161- sources : Array . from ( this . sourceSet ) ,
162- vector : { enabled : true , available : true } ,
163- batch : {
164- enabled : false ,
165- failures : 0 ,
166- limit : 0 ,
167- wait : false ,
168- concurrency : 0 ,
169- pollIntervalMs : 0 ,
170- timeoutMs : 0 ,
171- } ,
172- custom : {
173- qmd : {
174- collections : this . params . resolved . collections . length ,
175- lastUpdateAt : null ,
176- lightweightStatus : true ,
177- } ,
178- } ,
179- } ;
169+ return this . inner . status ( ) ;
180170 }
181171
182- async sync ( ) : Promise < void > { }
172+ async sync ( params ?: {
173+ reason ?: string ;
174+ force ?: boolean ;
175+ sessionFiles ?: string [ ] ;
176+ progress ?: ( update : MemorySyncProgressUpdate ) => void ;
177+ } ) {
178+ await this . inner . sync ?.( params ) ;
179+ }
183180
184181 async probeEmbeddingAvailability ( ) : Promise < MemoryEmbeddingProbeResult > {
185- return { ok : true } ;
182+ return await this . inner . probeEmbeddingAvailability ( ) ;
186183 }
187184
188- async probeVectorAvailability ( ) : Promise < boolean > {
189- return true ;
185+ async probeVectorAvailability ( ) {
186+ return await this . inner . probeVectorAvailability ( ) ;
190187 }
191188
192- async close ( ) : Promise < void > { }
189+ async close ( ) {
190+ if ( this . closed ) {
191+ return ;
192+ }
193+ this . closed = true ;
194+ try {
195+ await this . inner . close ?.( ) ;
196+ } finally {
197+ this . onClose ( ) ;
198+ }
199+ }
193200}
194201
195202export async function closeAllMemorySearchManagers ( ) : Promise < void > {
0 commit comments