@remnic/core 9.3.740 → 9.3.742

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.
Files changed (74) hide show
  1. package/dist/access-boundary.d.ts +2 -2
  2. package/dist/access-boundary.js +2 -2
  3. package/dist/access-cli.js +8 -8
  4. package/dist/access-http.d.ts +2 -2
  5. package/dist/access-http.js +6 -6
  6. package/dist/access-mcp.d.ts +2 -2
  7. package/dist/access-mcp.js +5 -5
  8. package/dist/access-operations-batch.js +3 -3
  9. package/dist/access-operations.d.ts +2 -2
  10. package/dist/access-operations.js +4 -4
  11. package/dist/{access-service-Cbk-Kv_A.d.ts → access-service-K8n12dXu.d.ts} +1 -1
  12. package/dist/access-service.d.ts +2 -2
  13. package/dist/access-service.js +1 -1
  14. package/dist/access-surface-catalog.d.ts +2 -2
  15. package/dist/{auto-sync-G3V5AN4C.js → auto-sync-XHSKKQAH.js} +2 -2
  16. package/dist/bootstrap.d.ts +1 -1
  17. package/dist/{chunk-DBVBV2RF.js → chunk-7ADHJ6SU.js} +3 -3
  18. package/dist/{chunk-VC7QELR6.js → chunk-7HUGLMSW.js} +346 -305
  19. package/dist/chunk-7HUGLMSW.js.map +1 -0
  20. package/dist/{chunk-QCO2QFLZ.js → chunk-AM7YKD7V.js} +3 -2
  21. package/dist/{chunk-QCO2QFLZ.js.map → chunk-AM7YKD7V.js.map} +1 -1
  22. package/dist/{chunk-OZECYVGI.js → chunk-C7LDPZJJ.js} +6 -4
  23. package/dist/{chunk-OZECYVGI.js.map → chunk-C7LDPZJJ.js.map} +1 -1
  24. package/dist/{chunk-4J53ZVPQ.js → chunk-EZZUNKMJ.js} +3 -3
  25. package/dist/{chunk-H3WSZWHK.js → chunk-GFJ5ABKS.js} +7 -6
  26. package/dist/chunk-GFJ5ABKS.js.map +1 -0
  27. package/dist/{chunk-LD6A7XG5.js → chunk-JMMSVRF4.js} +4 -4
  28. package/dist/{chunk-7Y5KKMIP.js → chunk-NHJYUIII.js} +3 -2
  29. package/dist/{chunk-7Y5KKMIP.js.map → chunk-NHJYUIII.js.map} +1 -1
  30. package/dist/{chunk-DGVM5SFL.js → chunk-PHRLJFWZ.js} +3 -2
  31. package/dist/{chunk-DGVM5SFL.js.map → chunk-PHRLJFWZ.js.map} +1 -1
  32. package/dist/{chunk-M5KLYHHI.js → chunk-QUNH5UZH.js} +2 -2
  33. package/dist/{chunk-3TUDKDTQ.js → chunk-UEIF7RWQ.js} +3 -3
  34. package/dist/{chunk-773DPI6T.js → chunk-V5RVMULT.js} +11 -8
  35. package/dist/chunk-V5RVMULT.js.map +1 -0
  36. package/dist/{chunk-XPOOY35N.js → chunk-W4H5A3TJ.js} +3 -2
  37. package/dist/chunk-W4H5A3TJ.js.map +1 -0
  38. package/dist/{cli-BQW2xKdb.d.ts → cli-BEOLy1xo.d.ts} +2 -2
  39. package/dist/cli.d.ts +3 -3
  40. package/dist/cli.js +8 -8
  41. package/dist/explicit-capture.d.ts +1 -1
  42. package/dist/graph-recall.js +1 -1
  43. package/dist/index.d.ts +4 -4
  44. package/dist/index.js +13 -13
  45. package/dist/lcm/engine.js +2 -2
  46. package/dist/lcm/index.js +2 -2
  47. package/dist/lcm/summarizer.js +1 -1
  48. package/dist/mcp-memory-inspector-app.d.ts +2 -2
  49. package/dist/operator-toolkit.js +1 -1
  50. package/dist/{orchestrator-BOAFN_Yr.d.ts → orchestrator-Dr-1v5EL.d.ts} +58 -0
  51. package/dist/orchestrator.d.ts +1 -1
  52. package/dist/orchestrator.js +4 -4
  53. package/dist/schemas.d.ts +22 -22
  54. package/dist/transfer/types.d.ts +12 -12
  55. package/package.json +2 -2
  56. package/src/cli.ts +2 -1
  57. package/src/graph-recall.ts +2 -1
  58. package/src/lcm/engine.ts +9 -6
  59. package/src/lcm/summarizer.ts +2 -1
  60. package/src/operator-toolkit.ts +2 -1
  61. package/src/orchestration/qmd-result-resolver.ts +377 -0
  62. package/src/orchestrator.ts +14 -279
  63. package/src/procedural/procedure-stats.ts +2 -1
  64. package/src/wearables/pipeline.ts +5 -3
  65. package/dist/chunk-773DPI6T.js.map +0 -1
  66. package/dist/chunk-H3WSZWHK.js.map +0 -1
  67. package/dist/chunk-VC7QELR6.js.map +0 -1
  68. package/dist/chunk-XPOOY35N.js.map +0 -1
  69. /package/dist/{auto-sync-G3V5AN4C.js.map → auto-sync-XHSKKQAH.js.map} +0 -0
  70. /package/dist/{chunk-DBVBV2RF.js.map → chunk-7ADHJ6SU.js.map} +0 -0
  71. /package/dist/{chunk-4J53ZVPQ.js.map → chunk-EZZUNKMJ.js.map} +0 -0
  72. /package/dist/{chunk-LD6A7XG5.js.map → chunk-JMMSVRF4.js.map} +0 -0
  73. /package/dist/{chunk-M5KLYHHI.js.map → chunk-QUNH5UZH.js.map} +0 -0
  74. /package/dist/{chunk-3TUDKDTQ.js.map → chunk-UEIF7RWQ.js.map} +0 -0
@@ -0,0 +1,377 @@
1
+ /**
2
+ * QMD result resolver — extracted from the orchestrator (issue #1526 seam 11).
3
+ *
4
+ * Owns the subsystem that resolves a QMD search-result path back to the
5
+ * concrete `MemoryFile` (and its owning namespace/storage). Three concerns
6
+ * live here:
7
+ *
8
+ * - `readQmdResultMemory` — resolve a result path to a `MemoryFile`,
9
+ * handling cold-tier collections, namespace-prefixed collections, and
10
+ * absolute vs relative paths.
11
+ * - `resolveColdQmdResultForRecall` — wrap a result with its owner
12
+ * namespace so the recall pipeline can scope-filter candidates.
13
+ * - `storageForAbsoluteQmdResultPath` — find the `StorageManager` whose
14
+ * directory tree contains an absolute result path.
15
+ *
16
+ * Behavior-preserving move from orchestrator.ts — no logic changes. The
17
+ * orchestrator constructs one instance and keeps thin delegating methods so
18
+ * existing call sites (recallInternal, applyColdFallbackPipeline,
19
+ * loadSearchResultMemoryMap, suggestLinksForMemory, and the
20
+ * RecallRerankCoordinator callback) continue to work.
21
+ *
22
+ * Config, the storage router, and the orchestrator's namespace-resolution
23
+ * helpers are accessed through getter callbacks (not captured at
24
+ * construction) so that post-construction reassignment of the
25
+ * orchestrator's live fields is honored. This mirrors the
26
+ * RecallRerankCoordinator / ConversationIndexCoordinator accessor pattern.
27
+ */
28
+
29
+ import path from "node:path";
30
+ import { log } from "../logger.js";
31
+ import { isPathInsideStorageRoot } from "../storage-paths.js";
32
+ import { namespaceIdentityFromToken } from "../namespaces/identity.js";
33
+ import { resolveNamespaceCapabilities } from "../capabilities.js";
34
+ import { SecureStoreLockedError } from "../secure-store/index.js";
35
+ import type { PluginConfig, QmdSearchResult, MemoryFile } from "../types.js";
36
+ import type { StorageManager } from "../index.js";
37
+
38
+ /**
39
+ * Split a relative QMD result path into its collection prefix and the
40
+ * remainder. Returns `null` for absolute paths, date-only prefixes, or
41
+ * paths with no collection separator.
42
+ */
43
+ export function qmdCollectionPathParts(resultPath: string): {
44
+ collection: string;
45
+ relativePath: string;
46
+ } | null {
47
+ if (!resultPath || path.isAbsolute(resultPath)) return null;
48
+ const normalized = resultPath.replace(/\\/g, "/").replace(/^\/+/, "");
49
+ const slashIndex = normalized.indexOf("/");
50
+ if (slashIndex <= 0 || slashIndex >= normalized.length - 1) return null;
51
+ const collection = normalized.slice(0, slashIndex);
52
+ if (/^\d{4}-\d{2}-\d{2}$/.test(collection)) return null;
53
+ return {
54
+ collection,
55
+ relativePath: normalized.slice(slashIndex + 1),
56
+ };
57
+ }
58
+
59
+ /**
60
+ * Build the set of on-disk candidate paths to probe for a QMD result,
61
+ * constrained to the storage root so a result path can never escape the
62
+ * namespace directory.
63
+ */
64
+ export function qmdResultPathCandidates(
65
+ storageDir: string,
66
+ resultPath: string,
67
+ ): string[] {
68
+ const candidates = new Set<string>();
69
+ const storageRoot = path.resolve(storageDir);
70
+ const addCandidate = (candidate: string) => {
71
+ const resolved = path.resolve(candidate);
72
+ if (isPathInsideStorageRoot(storageRoot, resolved)) {
73
+ candidates.add(resolved);
74
+ }
75
+ };
76
+ const addRelativeCandidates = (relativePath: string) => {
77
+ const normalized = relativePath.replace(/\\/g, "/").replace(/^\/+/, "");
78
+ if (!normalized) return;
79
+ addCandidate(path.join(storageRoot, normalized));
80
+ if (/^\d{4}-\d{2}-\d{2}\//.test(normalized)) {
81
+ addCandidate(path.join(storageRoot, "facts", normalized));
82
+ }
83
+ };
84
+
85
+ if (path.isAbsolute(resultPath)) {
86
+ addCandidate(resultPath);
87
+ } else {
88
+ addRelativeCandidates(resultPath);
89
+ }
90
+
91
+ return [...candidates];
92
+ }
93
+
94
+ /**
95
+ * Extract a non-empty `dir` from a storage, returning `null` when the
96
+ * property is absent or not a non-empty string at runtime. Proxy-backed
97
+ * fake storages used in recall paths may not provide a real `dir`, so
98
+ * every caller must handle the null case (skip or fall back to a direct
99
+ * `readMemoryByPath`).
100
+ */
101
+ function storageDirOrNull(storage: StorageManager): string | null {
102
+ const dir = storage.dir;
103
+ return typeof dir === "string" && dir.length > 0 ? dir : null;
104
+ }
105
+
106
+ /**
107
+ * Coordinator for resolving QMD search-result paths to concrete memory
108
+ * files and their owning namespace/storage.
109
+ */
110
+ export class QmdResultResolver {
111
+ private readonly getConfig: () => PluginConfig;
112
+ private readonly storageFor: (namespace: string) => Promise<StorageManager>;
113
+ private readonly storageDirNamespace: (storageDir: string) => string;
114
+ private readonly qmdCollectionNamespaceFromPrefix: (
115
+ collectionPrefix: string,
116
+ ) => string | null;
117
+ private readonly namespaceFromPath: (p: string) => string;
118
+
119
+ constructor(options: {
120
+ getConfig: () => PluginConfig;
121
+ storageFor: (namespace: string) => Promise<StorageManager>;
122
+ storageDirNamespace: (storageDir: string) => string;
123
+ qmdCollectionNamespaceFromPrefix: (
124
+ collectionPrefix: string,
125
+ ) => string | null;
126
+ namespaceFromPath: (p: string) => string;
127
+ }) {
128
+ this.getConfig = options.getConfig;
129
+ this.storageFor = options.storageFor;
130
+ this.storageDirNamespace = options.storageDirNamespace;
131
+ this.qmdCollectionNamespaceFromPrefix =
132
+ options.qmdCollectionNamespaceFromPrefix;
133
+ this.namespaceFromPath = options.namespaceFromPath;
134
+ }
135
+
136
+ async readQmdResultMemory(
137
+ resultPath: string,
138
+ fallbackStorage: StorageManager,
139
+ recallNamespaces: readonly string[] = [],
140
+ ): Promise<MemoryFile | null> {
141
+ const parts = qmdCollectionPathParts(resultPath);
142
+ const fallbackStorageDir = storageDirOrNull(fallbackStorage);
143
+ const config = this.getConfig();
144
+ const coldCollection = config.qmdColdCollection ?? "openclaw-engram-cold";
145
+ if (parts && parts.collection === coldCollection) {
146
+ const storages: StorageManager[] = [];
147
+ const seenStorageDirs = new Set<string>();
148
+ const addStorage = (storage: StorageManager): void => {
149
+ const storageDir = storageDirOrNull(storage);
150
+ const storageKey = storageDir
151
+ ? path.resolve(storageDir)
152
+ : `storage-without-dir-${storages.length}`;
153
+ if (seenStorageDirs.has(storageKey)) return;
154
+ seenStorageDirs.add(storageKey);
155
+ storages.push(storage);
156
+ };
157
+
158
+ const fallbackNamespace =
159
+ fallbackStorageDir !== null
160
+ ? this.storageDirNamespace(fallbackStorageDir)
161
+ : config.defaultNamespace;
162
+ if (
163
+ recallNamespaces.length === 0 ||
164
+ !resolveNamespaceCapabilities(config).namespaces ||
165
+ recallNamespaces.includes(fallbackNamespace)
166
+ ) {
167
+ addStorage(fallbackStorage);
168
+ }
169
+
170
+ if (recallNamespaces.length > 0) {
171
+ for (const namespace of recallNamespaces) {
172
+ try {
173
+ addStorage(await this.storageFor(namespace));
174
+ } catch (err) {
175
+ log.debug("qmd cold result namespace storage lookup skipped", {
176
+ path: resultPath,
177
+ namespace,
178
+ error: (err as Error).message,
179
+ });
180
+ }
181
+ }
182
+ }
183
+
184
+ for (const storage of storages) {
185
+ const storageDir = storageDirOrNull(storage);
186
+ if (!storageDir) {
187
+ const memory = await storage.readMemoryByPath(resultPath);
188
+ if (memory) return memory;
189
+ continue;
190
+ }
191
+ try {
192
+ const coldRoot = path.join(storageDir, "cold");
193
+ for (const candidate of qmdResultPathCandidates(
194
+ coldRoot,
195
+ parts.relativePath,
196
+ )) {
197
+ const memory = await storage.readMemoryByPath(candidate);
198
+ if (memory) return memory;
199
+ }
200
+ } catch (err) {
201
+ if (err instanceof SecureStoreLockedError) throw err;
202
+ log.debug("qmd cold result path lookup failed open", {
203
+ path: resultPath,
204
+ collection: coldCollection,
205
+ error: (err as Error).message,
206
+ });
207
+ }
208
+ }
209
+ return null;
210
+ }
211
+ const collectionNamespace = parts
212
+ ? this.qmdCollectionNamespaceFromPrefix(parts.collection)
213
+ : null;
214
+
215
+ if (parts && collectionNamespace) {
216
+ try {
217
+ const collectionStorage = await this.storageFor(collectionNamespace);
218
+ for (const candidate of qmdResultPathCandidates(
219
+ collectionStorage.dir,
220
+ parts.relativePath,
221
+ )) {
222
+ const memory = await collectionStorage.readMemoryByPath(candidate);
223
+ if (memory) return memory;
224
+ }
225
+ return null;
226
+ } catch (err) {
227
+ if (err instanceof SecureStoreLockedError) throw err;
228
+ log.debug("qmd result namespace path lookup failed open", {
229
+ path: resultPath,
230
+ namespace: collectionNamespace,
231
+ error: (err as Error).message,
232
+ });
233
+ return null;
234
+ }
235
+ }
236
+
237
+ if (path.isAbsolute(resultPath)) {
238
+ if (!fallbackStorageDir) {
239
+ return await fallbackStorage.readMemoryByPath(resultPath);
240
+ }
241
+ const ownerStorage = await this.storageForAbsoluteQmdResultPath(
242
+ resultPath,
243
+ fallbackStorage,
244
+ recallNamespaces,
245
+ );
246
+ if (!ownerStorage) return null;
247
+ for (const candidate of qmdResultPathCandidates(
248
+ ownerStorage.dir,
249
+ resultPath,
250
+ )) {
251
+ const memory = await ownerStorage.storage.readMemoryByPath(candidate);
252
+ if (memory) return memory;
253
+ }
254
+ return null;
255
+ }
256
+
257
+ if (!fallbackStorageDir) {
258
+ return await fallbackStorage.readMemoryByPath(resultPath);
259
+ }
260
+ for (const candidate of qmdResultPathCandidates(
261
+ fallbackStorageDir,
262
+ resultPath,
263
+ )) {
264
+ const memory = await fallbackStorage.readMemoryByPath(candidate);
265
+ if (memory) return memory;
266
+ }
267
+ return null;
268
+ }
269
+
270
+ async resolveColdQmdResultForRecall(
271
+ result: QmdSearchResult,
272
+ fallbackStorage: StorageManager,
273
+ recallNamespaces: readonly string[] = [],
274
+ ): Promise<{ namespace: string; result: QmdSearchResult } | null> {
275
+ const memory = await this.readQmdResultMemory(
276
+ result.path,
277
+ fallbackStorage,
278
+ recallNamespaces,
279
+ );
280
+ if (!memory) return null;
281
+
282
+ let ownerNamespace: string | null = null;
283
+ if (path.isAbsolute(memory.path)) {
284
+ const ownerStorage = await this.storageForAbsoluteQmdResultPath(
285
+ memory.path,
286
+ fallbackStorage,
287
+ recallNamespaces,
288
+ );
289
+ ownerNamespace = ownerStorage?.namespace ?? null;
290
+ if (!ownerNamespace && resolveNamespaceCapabilities(this.getConfig()).namespaces) return null;
291
+ }
292
+ ownerNamespace ??= this.namespaceFromPath(memory.path);
293
+ if (
294
+ recallNamespaces.length > 0 &&
295
+ !recallNamespaces.includes(ownerNamespace)
296
+ ) {
297
+ return null;
298
+ }
299
+
300
+ return {
301
+ namespace: ownerNamespace,
302
+ result: {
303
+ ...result,
304
+ docid: result.docid || memory.frontmatter.id,
305
+ path: memory.path,
306
+ snippet: result.snippet || memory.content.slice(0, 400),
307
+ },
308
+ };
309
+ }
310
+
311
+ async storageForAbsoluteQmdResultPath(
312
+ resultPath: string,
313
+ fallbackStorage: StorageManager,
314
+ recallNamespaces: readonly string[] = [],
315
+ ): Promise<{ storage: StorageManager; dir: string; namespace: string } | null> {
316
+ const resolvedPath = path.resolve(resultPath);
317
+ const config = this.getConfig();
318
+ const memoryRoot = path.resolve(config.memoryDir);
319
+ const namespacesRoot = path.join(memoryRoot, "namespaces");
320
+ const fallbackStorageDir = storageDirOrNull(fallbackStorage);
321
+ const matches: Array<{ storage: StorageManager; dir: string; namespace: string }> = [];
322
+ const seenDirs = new Set<string>();
323
+
324
+ const maybeAddStorage = (storage: StorageManager, namespace: string) => {
325
+ const storageDir = storageDirOrNull(storage);
326
+ if (!storageDir) return;
327
+ const candidateRoot = path.resolve(storageDir);
328
+ if (seenDirs.has(candidateRoot)) return;
329
+ if (!isPathInsideStorageRoot(candidateRoot, resolvedPath)) return;
330
+ if (
331
+ candidateRoot === memoryRoot &&
332
+ isPathInsideStorageRoot(namespacesRoot, resolvedPath)
333
+ ) {
334
+ return;
335
+ }
336
+ seenDirs.add(candidateRoot);
337
+ matches.push({ storage, dir: candidateRoot, namespace });
338
+ };
339
+
340
+ const fallbackNamespace =
341
+ fallbackStorageDir !== null
342
+ ? this.storageDirNamespace(fallbackStorageDir)
343
+ : config.defaultNamespace;
344
+ maybeAddStorage(fallbackStorage, fallbackNamespace);
345
+
346
+ const candidateNamespaces = new Set<string>();
347
+ candidateNamespaces.add(config.defaultNamespace);
348
+ candidateNamespaces.add(config.sharedNamespace);
349
+ for (const ns of recallNamespaces) {
350
+ candidateNamespaces.add(ns);
351
+ }
352
+ if (isPathInsideStorageRoot(namespacesRoot, resolvedPath)) {
353
+ const relativeToNamespaces = path.relative(namespacesRoot, resolvedPath);
354
+ const [namespaceSegment] = relativeToNamespaces.split(/[\\/]/);
355
+ if (namespaceSegment) {
356
+ candidateNamespaces.add(
357
+ namespaceIdentityFromToken(namespaceSegment) ?? namespaceSegment,
358
+ );
359
+ }
360
+ }
361
+ for (const policy of config.namespacePolicies ?? []) {
362
+ candidateNamespaces.add(policy.name);
363
+ }
364
+
365
+ for (const ns of candidateNamespaces) {
366
+ if (!ns) continue;
367
+ try {
368
+ maybeAddStorage(await this.storageFor(ns), ns);
369
+ } catch {
370
+ continue;
371
+ }
372
+ }
373
+
374
+ matches.sort((a, b) => b.dir.length - a.dir.length);
375
+ return matches[0] ?? null;
376
+ }
377
+ }