@xdarkicex/openclaw-memory-libravdb 1.4.23 → 1.4.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -69,6 +69,12 @@ export function register(api) {
69
69
  api.registerContextEngine(MEMORY_ID, () => buildContextEngineFactory(runtime, cfg, recallCache, api.logger ?? console));
70
70
  const markdownIngestion = createMarkdownIngestionHandle(cfg, runtime.getRpc, api.logger ?? console);
71
71
  const dreamPromotion = createDreamPromotionHandle(cfg, runtime.getRpc, api.logger ?? console);
72
+ runtime.onShutdown(async () => {
73
+ await markdownIngestion.stop();
74
+ });
75
+ runtime.onShutdown(async () => {
76
+ await dreamPromotion.stop();
77
+ });
72
78
  void markdownIngestion.start().catch((error) => {
73
79
  api.logger?.warn?.(`LibraVDB markdown ingestion failed to start: ${error instanceof Error ? error.message : String(error)}`);
74
80
  });
@@ -78,8 +84,6 @@ export function register(api) {
78
84
  api.on("before_reset", createBeforeResetHook(runtime, api.logger ?? console));
79
85
  api.on("session_end", createSessionEndHook(runtime, api.logger ?? console));
80
86
  api.on("gateway_stop", async () => {
81
- await dreamPromotion.stop();
82
- await markdownIngestion.stop();
83
87
  await runtime.shutdown();
84
88
  });
85
89
  }
@@ -73,9 +73,11 @@ class DirectoryMarkdownSourceAdapter {
73
73
  logger;
74
74
  states = new Map();
75
75
  fileStates = new Map();
76
+ activeScans = new Set();
76
77
  tokenizerId;
77
78
  coreDoc;
78
79
  started = false;
80
+ stopping = false;
79
81
  constructor(kind, config, getRpc, logger, fsApi) {
80
82
  this.kind = kind;
81
83
  this.roots = config.roots;
@@ -93,10 +95,11 @@ class DirectoryMarkdownSourceAdapter {
93
95
  return;
94
96
  }
95
97
  this.started = true;
98
+ this.stopping = false;
96
99
  await this.refresh();
97
100
  }
98
101
  async refresh() {
99
- if (!this.started) {
102
+ if (!this.started || this.stopping) {
100
103
  return;
101
104
  }
102
105
  for (const root of this.roots) {
@@ -104,6 +107,7 @@ class DirectoryMarkdownSourceAdapter {
104
107
  }
105
108
  }
106
109
  async stop() {
110
+ this.stopping = true;
107
111
  for (const state of this.states.values()) {
108
112
  if (state.scanState.timer) {
109
113
  clearTimeout(state.scanState.timer);
@@ -114,6 +118,9 @@ class DirectoryMarkdownSourceAdapter {
114
118
  }
115
119
  state.directoryWatchers.clear();
116
120
  }
121
+ if (this.activeScans.size > 0) {
122
+ await Promise.allSettled([...this.activeScans]);
123
+ }
117
124
  this.states.clear();
118
125
  this.fileStates.clear();
119
126
  this.started = false;
@@ -138,27 +145,46 @@ class DirectoryMarkdownSourceAdapter {
138
145
  return created;
139
146
  }
140
147
  async scanRoot(root) {
148
+ if (!this.started || this.stopping) {
149
+ return;
150
+ }
141
151
  const rootState = this.getRootState(root);
142
152
  if (rootState.scanState.scanning) {
143
153
  rootState.scanState.dirty = true;
144
154
  return;
145
155
  }
146
156
  rootState.scanState.scanning = true;
157
+ const scan = (async () => {
158
+ try {
159
+ const currentFiles = new Set();
160
+ await this.walkDirectory(rootState, rootState.root, currentFiles);
161
+ if (!this.stopping) {
162
+ await this.pruneDeletedFiles(rootState, currentFiles);
163
+ rootState.knownFiles = currentFiles;
164
+ }
165
+ }
166
+ finally {
167
+ rootState.scanState.scanning = false;
168
+ if (rootState.scanState.dirty) {
169
+ rootState.scanState.dirty = false;
170
+ if (!this.stopping) {
171
+ this.scheduleRootScan(rootState);
172
+ }
173
+ }
174
+ }
175
+ })();
176
+ this.activeScans.add(scan);
147
177
  try {
148
- const currentFiles = new Set();
149
- await this.walkDirectory(rootState, rootState.root, currentFiles);
150
- await this.pruneDeletedFiles(rootState, currentFiles);
151
- rootState.knownFiles = currentFiles;
178
+ await scan;
152
179
  }
153
180
  finally {
154
- rootState.scanState.scanning = false;
155
- if (rootState.scanState.dirty) {
156
- rootState.scanState.dirty = false;
157
- this.scheduleRootScan(rootState);
158
- }
181
+ this.activeScans.delete(scan);
159
182
  }
160
183
  }
161
184
  scheduleRootScan(rootState) {
185
+ if (!this.started || this.stopping) {
186
+ return;
187
+ }
162
188
  if (rootState.scanState.scanning) {
163
189
  rootState.scanState.dirty = true;
164
190
  return;
@@ -187,6 +213,9 @@ class DirectoryMarkdownSourceAdapter {
187
213
  return;
188
214
  }
189
215
  for (const entry of entries) {
216
+ if (this.stopping) {
217
+ return;
218
+ }
190
219
  const child = path.join(dir, entry.name);
191
220
  if (entry.isDirectory()) {
192
221
  await this.walkDirectory(rootState, child, currentFiles);
@@ -203,7 +232,9 @@ class DirectoryMarkdownSourceAdapter {
203
232
  await this.syncMarkdownFile(rootState, child);
204
233
  }
205
234
  catch (error) {
206
- this.logger.warn?.(`[markdown-ingest] sync failed for ${child}: ${formatError(error)}`);
235
+ if (!this.stopping) {
236
+ this.logger.warn?.(`[markdown-ingest] sync failed for ${child}: ${formatError(error)}`);
237
+ }
207
238
  }
208
239
  }
209
240
  }
@@ -213,7 +244,9 @@ class DirectoryMarkdownSourceAdapter {
213
244
  }
214
245
  try {
215
246
  const watcher = this.fsApi.watch(dir, () => {
216
- this.scheduleRootScan(rootState);
247
+ if (!this.stopping) {
248
+ this.scheduleRootScan(rootState);
249
+ }
217
250
  });
218
251
  watcher.on("error", (error) => {
219
252
  this.logger.warn?.(`[markdown-ingest] watch error for ${dir}: ${formatError(error)}`);
@@ -19,10 +19,12 @@ export interface LifecycleHint {
19
19
  nextSessionId?: string;
20
20
  nextSessionKey?: string;
21
21
  }
22
+ export type RuntimeShutdownTask = () => Promise<void> | void;
22
23
  export interface PluginRuntime {
23
24
  getRpc: RpcGetter;
24
25
  getKernel(): GrpcKernelClient | null;
25
26
  emitLifecycleHint(hint: LifecycleHint): Promise<void>;
27
+ onShutdown(task: RuntimeShutdownTask): void;
26
28
  shutdown(): Promise<void>;
27
29
  }
28
30
  export declare function createPluginRuntime(cfg: PluginConfig, logger?: LoggerLike): PluginRuntime;
@@ -10,7 +10,9 @@ export function resolveStartupHealthTimeoutMs(cfg) {
10
10
  export function createPluginRuntime(cfg, logger = console) {
11
11
  let started = null;
12
12
  let stopped = false;
13
+ let shuttingDown = false;
13
14
  let resolvedKernel = null;
15
+ const shutdownTasks = [];
14
16
  const ensureStarted = async () => {
15
17
  if (stopped) {
16
18
  throw new Error("LibraVDB plugin runtime has been shut down");
@@ -74,7 +76,25 @@ export function createPluginRuntime(cfg, logger = console) {
74
76
  logger.warn?.(`LibraVDB lifecycle hint dropped: ${formatError(error)}`);
75
77
  }
76
78
  },
79
+ onShutdown(task) {
80
+ if (stopped || shuttingDown) {
81
+ return;
82
+ }
83
+ shutdownTasks.push(task);
84
+ },
77
85
  async shutdown() {
86
+ if (stopped || shuttingDown) {
87
+ return;
88
+ }
89
+ shuttingDown = true;
90
+ for (const task of shutdownTasks.splice(0).reverse()) {
91
+ try {
92
+ await task();
93
+ }
94
+ catch (error) {
95
+ logger.warn?.(`LibraVDB shutdown task failed: ${formatError(error)}`);
96
+ }
97
+ }
78
98
  stopped = true;
79
99
  if (!started) {
80
100
  return;
@@ -2,7 +2,7 @@
2
2
  "id": "libravdb-memory",
3
3
  "name": "LibraVDB Memory",
4
4
  "description": "Persistent vector memory with three-tier hybrid scoring",
5
- "version": "1.4.23",
5
+ "version": "1.4.24",
6
6
  "kind": [
7
7
  "memory",
8
8
  "context-engine"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xdarkicex/openclaw-memory-libravdb",
3
- "version": "1.4.23",
3
+ "version": "1.4.24",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",