@remnic/core 9.3.651 → 9.3.652

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 (76) hide show
  1. package/dist/access-cli.js +9 -9
  2. package/dist/access-http.d.ts +3 -2
  3. package/dist/access-http.js +10 -10
  4. package/dist/access-mcp.d.ts +3 -2
  5. package/dist/access-mcp.js +9 -9
  6. package/dist/{access-service-DIZRHQ7Q.d.ts → access-service-CdJFd3_b.d.ts} +23 -2
  7. package/dist/access-service.d.ts +3 -2
  8. package/dist/access-service.js +8 -8
  9. package/dist/bootstrap.d.ts +2 -1
  10. package/dist/{chunk-QT4THOLT.js → chunk-2DGQLOOM.js} +1 -1
  11. package/dist/chunk-2DGQLOOM.js.map +1 -0
  12. package/dist/{chunk-SLYD3AH4.js → chunk-5V3TAB7D.js} +176 -4
  13. package/dist/chunk-5V3TAB7D.js.map +1 -0
  14. package/dist/{chunk-FOVPSMGI.js → chunk-7WEB3FLJ.js} +2 -2
  15. package/dist/{chunk-WJK75OCH.js → chunk-GI45G4BK.js} +2 -2
  16. package/dist/{chunk-76QTEJ2Q.js → chunk-JBHXMCYN.js} +2 -2
  17. package/dist/{chunk-4PTKFBST.js → chunk-JVRPJ7D4.js} +126 -26
  18. package/dist/chunk-JVRPJ7D4.js.map +1 -0
  19. package/dist/{chunk-TQUWNX7C.js → chunk-JX2RINDR.js} +2 -2
  20. package/dist/{chunk-RSS2KWN6.js → chunk-MGGNV3H2.js} +2 -2
  21. package/dist/{chunk-I4COC5XW.js → chunk-PYWNNF2I.js} +47 -9
  22. package/dist/chunk-PYWNNF2I.js.map +1 -0
  23. package/dist/{chunk-5WSDHTBO.js → chunk-TCX4WLKK.js} +7 -4
  24. package/dist/chunk-TCX4WLKK.js.map +1 -0
  25. package/dist/{chunk-RKN5J4RO.js → chunk-WSFNYPAT.js} +6 -6
  26. package/dist/{chunk-LFTLXOFX.js → chunk-WTI35CVJ.js} +2 -2
  27. package/dist/{chunk-6UKL6IXM.js → chunk-YM3LR4LS.js} +5 -5
  28. package/dist/{chunk-MF32AL7N.js → chunk-YOVKPOMD.js} +3 -3
  29. package/dist/{cli-BG4ybtJr.d.ts → cli-DDo7Qgs-.d.ts} +2 -2
  30. package/dist/cli.d.ts +4 -3
  31. package/dist/cli.js +13 -13
  32. package/dist/explicit-capture.d.ts +2 -1
  33. package/dist/index.d.ts +5 -4
  34. package/dist/index.js +14 -14
  35. package/dist/mcp-memory-inspector-app.d.ts +3 -2
  36. package/dist/namespaces/migrate.js +8 -8
  37. package/dist/namespaces/search.d.ts +18 -1
  38. package/dist/namespaces/search.js +7 -7
  39. package/dist/operator-toolkit.js +9 -9
  40. package/dist/{orchestrator-CX-oqwJq.d.ts → orchestrator-8fTZsa0y.d.ts} +2 -0
  41. package/dist/orchestrator.d.ts +2 -1
  42. package/dist/orchestrator.js +8 -8
  43. package/dist/qmd.d.ts +2 -1
  44. package/dist/qmd.js +2 -2
  45. package/dist/schemas.d.ts +22 -22
  46. package/dist/search/factory.js +6 -6
  47. package/dist/search/index.js +6 -6
  48. package/dist/search/lancedb-backend.js +2 -2
  49. package/dist/search/meilisearch-backend.js +2 -2
  50. package/dist/search/orama-backend.js +2 -2
  51. package/dist/search/port.d.ts +6 -0
  52. package/dist/search/port.js +1 -1
  53. package/dist/transfer/types.d.ts +12 -12
  54. package/package.json +1 -1
  55. package/src/access-service-health.test.ts +402 -0
  56. package/src/access-service.ts +274 -2
  57. package/src/namespaces/search.test.ts +258 -3
  58. package/src/namespaces/search.ts +184 -30
  59. package/src/orchestrator.ts +11 -1
  60. package/src/qmd.test.ts +102 -0
  61. package/src/qmd.ts +54 -7
  62. package/src/search/port.ts +6 -0
  63. package/dist/chunk-4PTKFBST.js.map +0 -1
  64. package/dist/chunk-5WSDHTBO.js.map +0 -1
  65. package/dist/chunk-I4COC5XW.js.map +0 -1
  66. package/dist/chunk-QT4THOLT.js.map +0 -1
  67. package/dist/chunk-SLYD3AH4.js.map +0 -1
  68. /package/dist/{chunk-FOVPSMGI.js.map → chunk-7WEB3FLJ.js.map} +0 -0
  69. /package/dist/{chunk-WJK75OCH.js.map → chunk-GI45G4BK.js.map} +0 -0
  70. /package/dist/{chunk-76QTEJ2Q.js.map → chunk-JBHXMCYN.js.map} +0 -0
  71. /package/dist/{chunk-TQUWNX7C.js.map → chunk-JX2RINDR.js.map} +0 -0
  72. /package/dist/{chunk-RSS2KWN6.js.map → chunk-MGGNV3H2.js.map} +0 -0
  73. /package/dist/{chunk-RKN5J4RO.js.map → chunk-WSFNYPAT.js.map} +0 -0
  74. /package/dist/{chunk-LFTLXOFX.js.map → chunk-WTI35CVJ.js.map} +0 -0
  75. /package/dist/{chunk-6UKL6IXM.js.map → chunk-YM3LR4LS.js.map} +0 -0
  76. /package/dist/{chunk-MF32AL7N.js.map → chunk-YOVKPOMD.js.map} +0 -0
package/src/qmd.test.ts CHANGED
@@ -183,6 +183,108 @@ test("QmdClient preserves configured qmdPath diagnostics when all probes fail",
183
183
  }
184
184
  });
185
185
 
186
+ test("QmdClient read-only availability failures preserve operational state", async () => {
187
+ const { QmdClient } = await import("./qmd.js");
188
+ const originalPath = process.env.PATH;
189
+ const originalWindowsPath = process.env.Path;
190
+ const missingQmdPath = path.join(
191
+ os.tmpdir(),
192
+ `remnic-missing-readonly-qmd-${process.pid}-${Date.now()}`,
193
+ "qmd.cmd",
194
+ );
195
+
196
+ process.env.PATH = "";
197
+ process.env.Path = "";
198
+ try {
199
+ const client = new QmdClient("test-collection", 10, {
200
+ qmdPath: missingQmdPath,
201
+ qmdFallbackPaths: [],
202
+ });
203
+ (client as any).available = true;
204
+ (client as any).qmdPath = "qmd";
205
+ (client as any).qmdPathSource = "auto-path";
206
+ (client as any).cliVersion = "qmd 2.5.3";
207
+ (client as any).qmdCapabilities = resolveQmdCapabilities("qmd 2.5.3");
208
+ (client as any).lastCliProbeError = null;
209
+
210
+ assert.equal(await client.checkAvailability(), false);
211
+
212
+ assert.equal(client.isAvailable(), true);
213
+ assert.equal((client as any).qmdPath, "qmd");
214
+ assert.equal((client as any).qmdPathSource, "auto-path");
215
+ assert.equal((client as any).cliVersion, "qmd 2.5.3");
216
+ assert.equal((client as any).lastCliProbeError, null);
217
+ } finally {
218
+ if (originalPath === undefined) delete process.env.PATH;
219
+ else process.env.PATH = originalPath;
220
+ if (originalWindowsPath === undefined) delete process.env.Path;
221
+ else process.env.Path = originalWindowsPath;
222
+ }
223
+ });
224
+
225
+ test("QmdClient read-only availability preserves live daemon availability", async () => {
226
+ const { QmdClient } = await import("./qmd.js");
227
+ const originalPath = process.env.PATH;
228
+ const originalWindowsPath = process.env.Path;
229
+ const missingQmdPath = path.join(
230
+ os.tmpdir(),
231
+ `remnic-missing-daemon-qmd-${process.pid}-${Date.now()}`,
232
+ "qmd.cmd",
233
+ );
234
+
235
+ process.env.PATH = "";
236
+ process.env.Path = "";
237
+ try {
238
+ const client = new QmdClient("test-collection", 10, {
239
+ qmdPath: missingQmdPath,
240
+ qmdFallbackPaths: [],
241
+ });
242
+ (client as any).daemonAvailable = true;
243
+ (client as any).daemonSession = { isActive: () => true };
244
+
245
+ assert.equal(await client.checkAvailability(), true);
246
+ assert.equal(client.isAvailable(), true);
247
+ } finally {
248
+ if (originalPath === undefined) delete process.env.PATH;
249
+ else process.env.PATH = originalPath;
250
+ if (originalWindowsPath === undefined) delete process.env.Path;
251
+ else process.env.Path = originalWindowsPath;
252
+ }
253
+ });
254
+
255
+ test("QmdClient read-only availability clears stale daemon availability", async () => {
256
+ const { QmdClient } = await import("./qmd.js");
257
+ const originalPath = process.env.PATH;
258
+ const originalWindowsPath = process.env.Path;
259
+ const missingQmdPath = path.join(
260
+ os.tmpdir(),
261
+ `remnic-missing-stale-daemon-qmd-${process.pid}-${Date.now()}`,
262
+ "qmd.cmd",
263
+ );
264
+
265
+ process.env.PATH = "";
266
+ process.env.Path = "";
267
+ try {
268
+ const client = new QmdClient("test-collection", 10, {
269
+ qmdPath: missingQmdPath,
270
+ qmdFallbackPaths: [],
271
+ });
272
+ (client as any).daemonAvailable = true;
273
+ (client as any).daemonSession = { isActive: () => false };
274
+ (client as any).lastDaemonCheckAtMs = Date.now();
275
+
276
+ assert.equal(await client.checkAvailability(), false);
277
+ assert.equal(client.isAvailable(), false);
278
+ assert.equal((client as any).daemonAvailable, false);
279
+ assert.equal((client as any).lastDaemonCheckAtMs, 0);
280
+ } finally {
281
+ if (originalPath === undefined) delete process.env.PATH;
282
+ else process.env.PATH = originalPath;
283
+ if (originalWindowsPath === undefined) delete process.env.Path;
284
+ else process.env.Path = originalWindowsPath;
285
+ }
286
+ });
287
+
186
288
  test("QmdClient applies chunk strategy to normal and forced embed args", async () => {
187
289
  const { QmdClient } = await import("./qmd.js");
188
290
  const client = new QmdClient("test", 5, {
package/src/qmd.ts CHANGED
@@ -1362,6 +1362,22 @@ export class QmdClient implements SearchBackend {
1362
1362
  return cliOk || this.daemonAvailable;
1363
1363
  }
1364
1364
 
1365
+ async checkAvailability(execution?: SearchExecutionOptions): Promise<boolean> {
1366
+ const cliAvailable = await this.probeCli({
1367
+ allowAutoUpgrade: false,
1368
+ preserveStateOnFailure: true,
1369
+ signal: execution?.signal,
1370
+ });
1371
+ if (this.daemonAvailable && this.daemonSession?.isActive()) {
1372
+ return true;
1373
+ }
1374
+ if (this.daemonAvailable) {
1375
+ this.daemonAvailable = false;
1376
+ this.lastDaemonCheckAtMs = 0;
1377
+ }
1378
+ return cliAvailable;
1379
+ }
1380
+
1365
1381
  private async probeDaemon(): Promise<boolean> {
1366
1382
  this.lastDaemonCheckAtMs = Date.now();
1367
1383
  const normalizedPath = this.qmdPath.trim() || "qmd";
@@ -1409,7 +1425,32 @@ export class QmdClient implements SearchBackend {
1409
1425
  }
1410
1426
  }
1411
1427
 
1412
- private async probeCli(): Promise<boolean> {
1428
+ private async probeCli(
1429
+ options: {
1430
+ allowAutoUpgrade?: boolean;
1431
+ preserveStateOnFailure?: boolean;
1432
+ signal?: AbortSignal;
1433
+ } = {},
1434
+ ): Promise<boolean> {
1435
+ const priorState = options.preserveStateOnFailure === true
1436
+ ? {
1437
+ available: this.available,
1438
+ qmdPath: this.qmdPath,
1439
+ qmdPathSource: this.qmdPathSource,
1440
+ cliVersion: this.cliVersion,
1441
+ lastCliProbeError: this.lastCliProbeError,
1442
+ qmdCapabilities: this.qmdCapabilities,
1443
+ }
1444
+ : null;
1445
+ const restorePriorState = (): void => {
1446
+ if (!priorState) return;
1447
+ this.available = priorState.available;
1448
+ this.qmdPath = priorState.qmdPath;
1449
+ this.qmdPathSource = priorState.qmdPathSource;
1450
+ this.cliVersion = priorState.cliVersion;
1451
+ this.lastCliProbeError = priorState.lastCliProbeError;
1452
+ this.qmdCapabilities = priorState.qmdCapabilities;
1453
+ };
1413
1454
  let configuredProbeFailure: string | null = null;
1414
1455
  const markProbeFailure = (err: unknown): void => {
1415
1456
  this.lastCliProbeError = err instanceof Error ? err.message : String(err);
@@ -1431,12 +1472,14 @@ export class QmdClient implements SearchBackend {
1431
1472
  this.cliVersion = parseQmdVersionOutput(result.stdout, result.stderr);
1432
1473
  this.qmdCapabilities = resolveQmdCapabilities(this.cliVersion);
1433
1474
  this.lastCliProbeError = null;
1434
- await this.maybeAutoUpgradeQmd();
1475
+ if (options.allowAutoUpgrade !== false) {
1476
+ await this.maybeAutoUpgradeQmd();
1477
+ }
1435
1478
  };
1436
1479
 
1437
1480
  if (this.configuredQmdPath) {
1438
1481
  try {
1439
- const result = await runQmd(["--version"], QMD_PROBE_TIMEOUT_MS, this.configuredQmdPath, undefined, this.qmdRuntimeEnv);
1482
+ const result = await runQmd(["--version"], QMD_PROBE_TIMEOUT_MS, this.configuredQmdPath, options.signal, this.qmdRuntimeEnv);
1440
1483
  await recordProbeSuccess(result, this.configuredQmdPath, "configured");
1441
1484
  return true;
1442
1485
  } catch (err) {
@@ -1452,7 +1495,7 @@ export class QmdClient implements SearchBackend {
1452
1495
 
1453
1496
  // Try PATH first
1454
1497
  try {
1455
- const result = await runQmd(["--version"], QMD_PROBE_TIMEOUT_MS, "qmd", undefined, this.qmdRuntimeEnv);
1498
+ const result = await runQmd(["--version"], QMD_PROBE_TIMEOUT_MS, "qmd", options.signal, this.qmdRuntimeEnv);
1456
1499
  await recordProbeSuccess(result, "qmd", "auto-path");
1457
1500
  return true;
1458
1501
  } catch (err) {
@@ -1460,7 +1503,7 @@ export class QmdClient implements SearchBackend {
1460
1503
  // Try fallback paths
1461
1504
  for (const fallbackPath of this.qmdFallbackPaths) {
1462
1505
  try {
1463
- const result = await runQmd(["--version"], QMD_PROBE_TIMEOUT_MS, fallbackPath, undefined, this.qmdRuntimeEnv);
1506
+ const result = await runQmd(["--version"], QMD_PROBE_TIMEOUT_MS, fallbackPath, options.signal, this.qmdRuntimeEnv);
1464
1507
  await recordProbeSuccess(result, fallbackPath, "auto-fallback");
1465
1508
  log.info(`QMD: found at ${fallbackPath}`);
1466
1509
  return true;
@@ -1469,8 +1512,12 @@ export class QmdClient implements SearchBackend {
1469
1512
  // Continue to next fallback
1470
1513
  }
1471
1514
  }
1472
- this.available = false;
1473
- restoreConfiguredProbeFailure();
1515
+ if (priorState) {
1516
+ restorePriorState();
1517
+ } else {
1518
+ this.available = false;
1519
+ restoreConfiguredProbeFailure();
1520
+ }
1474
1521
  return false;
1475
1522
  }
1476
1523
  }
@@ -42,6 +42,12 @@ export function resolveEnsureCollectionArgs(
42
42
  export interface SearchBackend {
43
43
  // ── Lifecycle ──
44
44
  probe(): Promise<boolean>;
45
+ /**
46
+ * Optional non-mutating availability probe for health/readiness checks.
47
+ * Implementations must avoid auto-upgrades, collection creation, daemon
48
+ * startup, or any other runtime-modifying side effects.
49
+ */
50
+ checkAvailability?(execution?: SearchExecutionOptions): Promise<boolean>;
45
51
  isAvailable(): boolean;
46
52
  debugStatus(): string;
47
53
 
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/namespaces/search.ts"],"sourcesContent":["import path from \"node:path\";\nimport type { PluginConfig, QmdSearchResult } from \"../types.js\";\nimport type {\n SearchBackend,\n SearchExecutionOptions,\n SearchQueryOptions,\n} from \"../search/port.js\";\nimport { createSearchBackend } from \"../search/factory.js\";\nimport { namespaceIdentityToken, normalizeNamespaceIdentity } from \"./identity.js\";\n\nconst NESTED_NAMESPACE_FILTER_OVERFETCH_FACTOR = 4;\nconst NESTED_NAMESPACE_FILTER_OVERFETCH_MIN = 50;\n\nexport function namespaceCollectionName(\n baseCollection: string,\n namespace: string,\n options?: {\n defaultNamespace?: string;\n useLegacyDefaultCollection?: boolean;\n },\n): string {\n const trimmed = normalizeNamespaceIdentity(namespace);\n const defaultNamespace = normalizeNamespaceIdentity(options?.defaultNamespace ?? \"\") || \"default\";\n if (\n options?.useLegacyDefaultCollection === true &&\n trimmed === defaultNamespace\n ) {\n return baseCollection;\n }\n\n return `${baseCollection}--${namespaceIdentityToken(trimmed || defaultNamespace)}`;\n}\n\ntype StorageRouterLike = {\n storageFor(namespace: string): Promise<{ dir: string }>;\n};\n\ntype NamespaceBackendRecord = {\n backend: SearchBackend;\n collection: string;\n memoryDir: string;\n available: boolean;\n collectionState: CollectionState;\n filtersNestedNamespaces: boolean;\n};\n\ntype CollectionState = \"present\" | \"missing\" | \"unknown\" | \"skipped\";\n\ntype NamespaceScopedSearchConfig = PluginConfig & {\n hostEmbeddingProviderScope?: string;\n};\n\nexport class NamespaceSearchRouter {\n private readonly cache = new Map<string, Promise<NamespaceBackendRecord>>();\n\n constructor(\n private readonly config: PluginConfig,\n private readonly storageRouter: StorageRouterLike,\n private readonly createBackend: (config: PluginConfig) => SearchBackend = createSearchBackend,\n ) {}\n\n async collectionForNamespace(namespace: string): Promise<string> {\n return (await this.backendRecordFor(namespace)).collection;\n }\n\n async searchAcrossNamespaces(options: {\n query: string;\n namespaces: string[];\n maxResults?: number;\n mode?: \"search\" | \"hybrid\" | \"bm25\" | \"vector\";\n searchOptions?: SearchQueryOptions;\n execution?: SearchExecutionOptions;\n }): Promise<QmdSearchResult[]> {\n const query = options.query.trim();\n if (!query) return [];\n const maxResults = Math.max(0, Math.floor(options.maxResults ?? this.config.qmdMaxResults));\n if (maxResults === 0) return [];\n\n const method = options.mode ?? \"search\";\n const namespaces = Array.from(new Set(options.namespaces.map((value) => value.trim()).filter(Boolean)));\n if (namespaces.length === 0) return [];\n\n const resultsByNamespace = await Promise.all(\n namespaces.map(async (namespace) => {\n const record = await this.backendRecordFor(namespace);\n if (!record.available || record.collectionState === \"missing\") {\n return { namespace, results: [] as QmdSearchResult[] };\n }\n const backendLimit = backendSearchLimit(record, maxResults);\n let results: QmdSearchResult[];\n switch (method) {\n case \"hybrid\":\n results = await record.backend.hybridSearch(\n query,\n record.collection,\n backendLimit,\n options.execution,\n );\n break;\n case \"bm25\":\n results = await record.backend.bm25Search(\n query,\n record.collection,\n backendLimit,\n options.execution,\n );\n break;\n case \"vector\":\n results = await record.backend.vectorSearch(\n query,\n record.collection,\n backendLimit,\n options.execution,\n );\n break;\n default:\n results = await record.backend.search(\n query,\n record.collection,\n backendLimit,\n options.searchOptions,\n options.execution,\n );\n break;\n }\n results = filterNamespaceSubtreeResults(record, results);\n return { namespace, results };\n }),\n );\n\n return mergeNamespaceSearchResults(resultsByNamespace, maxResults);\n }\n\n /**\n * Update all namespace backends.\n * Returns the number of backends for which an update was attempted\n * (i.e., available and collection present). Callers can treat 0 as a\n * signal that no backend was eligible — useful for success-verification in\n * startup-sync when namespacesEnabled is true.\n */\n async updateNamespaces(\n namespaces: string[],\n execution?: SearchExecutionOptions,\n ): Promise<number> {\n const unique = Array.from(new Set(namespaces.map((value) => value.trim()).filter(Boolean)));\n const eligible = (await Promise.all(\n unique.map(async (namespace) => {\n const record = await this.backendRecordFor(namespace);\n return record.available && record.collectionState !== \"missing\"\n ? record\n : null;\n }),\n )).filter((record): record is NamespaceBackendRecord => record !== null);\n\n const globalRecord = eligible.find((record) => record.backend.updatesAllCollections?.() === true);\n const scopedRecords = globalRecord\n ? eligible.filter((record) => record.backend.updatesAllCollections?.() !== true)\n : eligible;\n\n await Promise.all([\n globalRecord ? globalRecord.backend.update(execution) : Promise.resolve(),\n ...scopedRecords.map((record) => record.backend.update(execution)),\n ]);\n\n return (globalRecord ? 1 : 0) + scopedRecords.length;\n }\n\n async embedNamespaces(namespaces: string[]): Promise<void> {\n const unique = Array.from(new Set(namespaces.map((value) => value.trim()).filter(Boolean)));\n await Promise.all(\n unique.map(async (namespace) => {\n const record = await this.backendRecordFor(namespace);\n if (!record.available || record.collectionState === \"missing\") return;\n await record.backend.embed();\n }),\n );\n }\n\n async ensureNamespaceCollection(\n namespace: string,\n execution?: SearchExecutionOptions,\n ): Promise<\"present\" | \"missing\" | \"unknown\" | \"skipped\"> {\n const record = await this.backendRecordFor(namespace, execution);\n return record.collectionState;\n }\n\n /** Clear cached backend records so the next access re-probes availability. */\n clearCache(): void {\n this.cache.clear();\n }\n\n /** Release any per-namespace backend handles held by cached records. */\n async dispose(): Promise<void> {\n const pendingRecords = Array.from(this.cache.values());\n this.cache.clear();\n const settled = await Promise.allSettled(pendingRecords);\n await Promise.allSettled(\n settled.flatMap((entry) => {\n if (entry.status !== \"fulfilled\") return [];\n const dispose = (entry.value.backend as { dispose?: () => void | Promise<void> }).dispose;\n return dispose ? [dispose.call(entry.value.backend)] : [];\n }),\n );\n }\n\n private async backendRecordFor(\n namespace: string,\n execution?: SearchExecutionOptions,\n ): Promise<NamespaceBackendRecord> {\n const key = namespace.trim() || this.config.defaultNamespace;\n const existing = this.cache.get(key);\n if (existing) return await existing;\n\n const pending = (async (): Promise<NamespaceBackendRecord> => {\n const storage = await this.storageRouter.storageFor(key);\n const useLegacyDefaultCollection =\n key === this.config.defaultNamespace && storage.dir === this.config.memoryDir;\n const filtersNestedNamespaces =\n this.config.namespacesEnabled === true && useLegacyDefaultCollection;\n const rootHostEmbeddingScope =\n (this.config as NamespaceScopedSearchConfig).hostEmbeddingProviderScope ??\n this.config.memoryDir;\n const scopedConfig: NamespaceScopedSearchConfig = {\n ...this.config,\n memoryDir: storage.dir,\n hostEmbeddingProviderScope: rootHostEmbeddingScope,\n qmdCollection: namespaceCollectionName(this.config.qmdCollection, key, {\n defaultNamespace: this.config.defaultNamespace,\n useLegacyDefaultCollection,\n }),\n };\n\n const backend = this.createBackend(scopedConfig);\n const available = await backend.probe().catch(() => false);\n const collectionState = available\n ? await this.collectionStateForBackend(backend, storage.dir, scopedConfig.qmdCollection, {\n skipAutoCreate: filtersNestedNamespaces,\n execution,\n })\n : \"unknown\";\n return {\n backend,\n collection: scopedConfig.qmdCollection,\n memoryDir: storage.dir,\n available,\n collectionState,\n filtersNestedNamespaces,\n };\n })();\n\n this.cache.set(key, pending);\n return await pending;\n }\n\n private async collectionStateForBackend(\n backend: SearchBackend,\n memoryDir: string,\n collection: string,\n options: {\n skipAutoCreate: boolean;\n execution?: SearchExecutionOptions;\n },\n ): Promise<CollectionState> {\n if (options.skipAutoCreate) {\n if (!backend.checkCollection) return \"unknown\";\n const collectionState = await backend\n .checkCollection(collection, options.execution)\n .catch(() => \"unknown\" as const);\n return collectionState === \"missing\" ? \"unknown\" : collectionState;\n }\n return await backend.ensureCollection(memoryDir, collection, options.execution).catch(() => \"unknown\" as const);\n }\n}\n\nfunction filterNamespaceSubtreeResults(\n record: NamespaceBackendRecord,\n results: QmdSearchResult[],\n): QmdSearchResult[] {\n if (!record.filtersNestedNamespaces) return results;\n return results.filter((result) =>\n !pathIsInsideNamespaceSubtree(record.memoryDir, record.collection, result.path)\n );\n}\n\nfunction backendSearchLimit(\n record: NamespaceBackendRecord,\n maxResults: number,\n): number {\n if (!record.filtersNestedNamespaces) return maxResults;\n return Math.max(\n maxResults,\n maxResults * NESTED_NAMESPACE_FILTER_OVERFETCH_FACTOR,\n NESTED_NAMESPACE_FILTER_OVERFETCH_MIN,\n );\n}\n\nfunction pathIsInsideNamespaceSubtree(\n memoryDir: string,\n collection: string,\n resultPath: string | undefined,\n): boolean {\n if (!resultPath) return false;\n const normalizedResultPath = normalizeQmdResultPath(resultPath, collection);\n const memoryRoot = path.resolve(memoryDir);\n const namespacesRoot = path.join(memoryRoot, \"namespaces\");\n const candidate = path.isAbsolute(normalizedResultPath)\n ? path.normalize(normalizedResultPath)\n : path.resolve(memoryRoot, normalizedResultPath);\n const relative = path.relative(namespacesRoot, candidate);\n return relative === \"\" || (!relative.startsWith(\"..\") && !path.isAbsolute(relative));\n}\n\nfunction normalizeQmdResultPath(resultPath: string, collection: string): string {\n let value = resultPath.trim();\n if (value.startsWith(\"qmd://\")) {\n try {\n const parsed = new URL(value);\n if (parsed.protocol === \"qmd:\" && parsed.hostname === collection) {\n value = decodeURIComponent(parsed.pathname.replace(/^\\/+/, \"\"));\n }\n } catch {\n const remainder = value.slice(\"qmd://\".length);\n const slashIndex = remainder.indexOf(\"/\");\n if (slashIndex !== -1) {\n value = remainder.slice(slashIndex + 1);\n }\n }\n }\n\n const collectionPrefix = `${collection}/`;\n if (value.startsWith(collectionPrefix)) {\n value = value.slice(collectionPrefix.length);\n }\n return value;\n}\n\nfunction mergeNamespaceSearchResults(\n lists: Array<{ namespace: string; results: QmdSearchResult[] }>,\n maxResults: number,\n): QmdSearchResult[] {\n const merged = new Map<string, QmdSearchResult>();\n\n for (const { namespace, results } of lists) {\n for (const result of results) {\n const key = `${namespace}\\0${result.path || result.docid}`;\n const existing = merged.get(key);\n if (!existing) {\n merged.set(key, result);\n continue;\n }\n if (result.score > existing.score) {\n merged.set(key, {\n ...result,\n snippet: existing.snippet || result.snippet || \"\",\n });\n }\n }\n }\n\n return [...merged.values()]\n .sort((a, b) => b.score - a.score)\n .slice(0, maxResults);\n}\n"],"mappings":"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","names":[]}