@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.
- package/dist/access-boundary.d.ts +2 -2
- package/dist/access-boundary.js +2 -2
- package/dist/access-cli.js +8 -8
- package/dist/access-http.d.ts +2 -2
- package/dist/access-http.js +6 -6
- package/dist/access-mcp.d.ts +2 -2
- package/dist/access-mcp.js +5 -5
- package/dist/access-operations-batch.js +3 -3
- package/dist/access-operations.d.ts +2 -2
- package/dist/access-operations.js +4 -4
- package/dist/{access-service-Cbk-Kv_A.d.ts → access-service-K8n12dXu.d.ts} +1 -1
- package/dist/access-service.d.ts +2 -2
- package/dist/access-service.js +1 -1
- package/dist/access-surface-catalog.d.ts +2 -2
- package/dist/{auto-sync-G3V5AN4C.js → auto-sync-XHSKKQAH.js} +2 -2
- package/dist/bootstrap.d.ts +1 -1
- package/dist/{chunk-DBVBV2RF.js → chunk-7ADHJ6SU.js} +3 -3
- package/dist/{chunk-VC7QELR6.js → chunk-7HUGLMSW.js} +346 -305
- package/dist/chunk-7HUGLMSW.js.map +1 -0
- package/dist/{chunk-QCO2QFLZ.js → chunk-AM7YKD7V.js} +3 -2
- package/dist/{chunk-QCO2QFLZ.js.map → chunk-AM7YKD7V.js.map} +1 -1
- package/dist/{chunk-OZECYVGI.js → chunk-C7LDPZJJ.js} +6 -4
- package/dist/{chunk-OZECYVGI.js.map → chunk-C7LDPZJJ.js.map} +1 -1
- package/dist/{chunk-4J53ZVPQ.js → chunk-EZZUNKMJ.js} +3 -3
- package/dist/{chunk-H3WSZWHK.js → chunk-GFJ5ABKS.js} +7 -6
- package/dist/chunk-GFJ5ABKS.js.map +1 -0
- package/dist/{chunk-LD6A7XG5.js → chunk-JMMSVRF4.js} +4 -4
- package/dist/{chunk-7Y5KKMIP.js → chunk-NHJYUIII.js} +3 -2
- package/dist/{chunk-7Y5KKMIP.js.map → chunk-NHJYUIII.js.map} +1 -1
- package/dist/{chunk-DGVM5SFL.js → chunk-PHRLJFWZ.js} +3 -2
- package/dist/{chunk-DGVM5SFL.js.map → chunk-PHRLJFWZ.js.map} +1 -1
- package/dist/{chunk-M5KLYHHI.js → chunk-QUNH5UZH.js} +2 -2
- package/dist/{chunk-3TUDKDTQ.js → chunk-UEIF7RWQ.js} +3 -3
- package/dist/{chunk-773DPI6T.js → chunk-V5RVMULT.js} +11 -8
- package/dist/chunk-V5RVMULT.js.map +1 -0
- package/dist/{chunk-XPOOY35N.js → chunk-W4H5A3TJ.js} +3 -2
- package/dist/chunk-W4H5A3TJ.js.map +1 -0
- package/dist/{cli-BQW2xKdb.d.ts → cli-BEOLy1xo.d.ts} +2 -2
- package/dist/cli.d.ts +3 -3
- package/dist/cli.js +8 -8
- package/dist/explicit-capture.d.ts +1 -1
- package/dist/graph-recall.js +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +13 -13
- package/dist/lcm/engine.js +2 -2
- package/dist/lcm/index.js +2 -2
- package/dist/lcm/summarizer.js +1 -1
- package/dist/mcp-memory-inspector-app.d.ts +2 -2
- package/dist/operator-toolkit.js +1 -1
- package/dist/{orchestrator-BOAFN_Yr.d.ts → orchestrator-Dr-1v5EL.d.ts} +58 -0
- package/dist/orchestrator.d.ts +1 -1
- package/dist/orchestrator.js +4 -4
- package/dist/schemas.d.ts +22 -22
- package/dist/transfer/types.d.ts +12 -12
- package/package.json +2 -2
- package/src/cli.ts +2 -1
- package/src/graph-recall.ts +2 -1
- package/src/lcm/engine.ts +9 -6
- package/src/lcm/summarizer.ts +2 -1
- package/src/operator-toolkit.ts +2 -1
- package/src/orchestration/qmd-result-resolver.ts +377 -0
- package/src/orchestrator.ts +14 -279
- package/src/procedural/procedure-stats.ts +2 -1
- package/src/wearables/pipeline.ts +5 -3
- package/dist/chunk-773DPI6T.js.map +0 -1
- package/dist/chunk-H3WSZWHK.js.map +0 -1
- package/dist/chunk-VC7QELR6.js.map +0 -1
- package/dist/chunk-XPOOY35N.js.map +0 -1
- /package/dist/{auto-sync-G3V5AN4C.js.map → auto-sync-XHSKKQAH.js.map} +0 -0
- /package/dist/{chunk-DBVBV2RF.js.map → chunk-7ADHJ6SU.js.map} +0 -0
- /package/dist/{chunk-4J53ZVPQ.js.map → chunk-EZZUNKMJ.js.map} +0 -0
- /package/dist/{chunk-LD6A7XG5.js.map → chunk-JMMSVRF4.js.map} +0 -0
- /package/dist/{chunk-M5KLYHHI.js.map → chunk-QUNH5UZH.js.map} +0 -0
- /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
|
+
}
|