@remnic/core 9.3.741 → 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/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
package/dist/operator-toolkit.js
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
summarizeProvenanceCoverage,
|
|
14
14
|
summarizeTierDistribution,
|
|
15
15
|
summarizeTombstoneStatus
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-AM7YKD7V.js";
|
|
17
17
|
import "./chunk-UYYSOMS2.js";
|
|
18
18
|
import "./chunk-7XH7VJN4.js";
|
|
19
19
|
import "./chunk-YBPYIAA5.js";
|
|
@@ -1080,6 +1080,63 @@ declare class RecallRerankCoordinator {
|
|
|
1080
1080
|
applyMmrToQmdResults(sectionId: string, results: QmdSearchResult[], caps?: CapabilitySet): QmdSearchResult[];
|
|
1081
1081
|
}
|
|
1082
1082
|
|
|
1083
|
+
/**
|
|
1084
|
+
* QMD result resolver — extracted from the orchestrator (issue #1526 seam 11).
|
|
1085
|
+
*
|
|
1086
|
+
* Owns the subsystem that resolves a QMD search-result path back to the
|
|
1087
|
+
* concrete `MemoryFile` (and its owning namespace/storage). Three concerns
|
|
1088
|
+
* live here:
|
|
1089
|
+
*
|
|
1090
|
+
* - `readQmdResultMemory` — resolve a result path to a `MemoryFile`,
|
|
1091
|
+
* handling cold-tier collections, namespace-prefixed collections, and
|
|
1092
|
+
* absolute vs relative paths.
|
|
1093
|
+
* - `resolveColdQmdResultForRecall` — wrap a result with its owner
|
|
1094
|
+
* namespace so the recall pipeline can scope-filter candidates.
|
|
1095
|
+
* - `storageForAbsoluteQmdResultPath` — find the `StorageManager` whose
|
|
1096
|
+
* directory tree contains an absolute result path.
|
|
1097
|
+
*
|
|
1098
|
+
* Behavior-preserving move from orchestrator.ts — no logic changes. The
|
|
1099
|
+
* orchestrator constructs one instance and keeps thin delegating methods so
|
|
1100
|
+
* existing call sites (recallInternal, applyColdFallbackPipeline,
|
|
1101
|
+
* loadSearchResultMemoryMap, suggestLinksForMemory, and the
|
|
1102
|
+
* RecallRerankCoordinator callback) continue to work.
|
|
1103
|
+
*
|
|
1104
|
+
* Config, the storage router, and the orchestrator's namespace-resolution
|
|
1105
|
+
* helpers are accessed through getter callbacks (not captured at
|
|
1106
|
+
* construction) so that post-construction reassignment of the
|
|
1107
|
+
* orchestrator's live fields is honored. This mirrors the
|
|
1108
|
+
* RecallRerankCoordinator / ConversationIndexCoordinator accessor pattern.
|
|
1109
|
+
*/
|
|
1110
|
+
|
|
1111
|
+
/**
|
|
1112
|
+
* Coordinator for resolving QMD search-result paths to concrete memory
|
|
1113
|
+
* files and their owning namespace/storage.
|
|
1114
|
+
*/
|
|
1115
|
+
declare class QmdResultResolver {
|
|
1116
|
+
private readonly getConfig;
|
|
1117
|
+
private readonly storageFor;
|
|
1118
|
+
private readonly storageDirNamespace;
|
|
1119
|
+
private readonly qmdCollectionNamespaceFromPrefix;
|
|
1120
|
+
private readonly namespaceFromPath;
|
|
1121
|
+
constructor(options: {
|
|
1122
|
+
getConfig: () => PluginConfig;
|
|
1123
|
+
storageFor: (namespace: string) => Promise<StorageManager>;
|
|
1124
|
+
storageDirNamespace: (storageDir: string) => string;
|
|
1125
|
+
qmdCollectionNamespaceFromPrefix: (collectionPrefix: string) => string | null;
|
|
1126
|
+
namespaceFromPath: (p: string) => string;
|
|
1127
|
+
});
|
|
1128
|
+
readQmdResultMemory(resultPath: string, fallbackStorage: StorageManager, recallNamespaces?: readonly string[]): Promise<MemoryFile | null>;
|
|
1129
|
+
resolveColdQmdResultForRecall(result: QmdSearchResult, fallbackStorage: StorageManager, recallNamespaces?: readonly string[]): Promise<{
|
|
1130
|
+
namespace: string;
|
|
1131
|
+
result: QmdSearchResult;
|
|
1132
|
+
} | null>;
|
|
1133
|
+
storageForAbsoluteQmdResultPath(resultPath: string, fallbackStorage: StorageManager, recallNamespaces?: readonly string[]): Promise<{
|
|
1134
|
+
storage: StorageManager;
|
|
1135
|
+
dir: string;
|
|
1136
|
+
namespace: string;
|
|
1137
|
+
} | null>;
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1083
1140
|
/**
|
|
1084
1141
|
* Pattern-reinforcement maintenance job (issue #687 PR 2/4).
|
|
1085
1142
|
*
|
|
@@ -1672,6 +1729,7 @@ declare class Orchestrator {
|
|
|
1672
1729
|
*/
|
|
1673
1730
|
readonly conversationIndexCoordinator: ConversationIndexCoordinator;
|
|
1674
1731
|
readonly recallRerankCoordinator: RecallRerankCoordinator;
|
|
1732
|
+
readonly qmdResultResolver: QmdResultResolver;
|
|
1675
1733
|
private heartbeatObserverChains;
|
|
1676
1734
|
private recentExtractionFingerprints;
|
|
1677
1735
|
private readonly consolidationObservers;
|
package/dist/orchestrator.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ import './summarizer.js';
|
|
|
8
8
|
import './local-llm.js';
|
|
9
9
|
import './fallback-llm.js';
|
|
10
10
|
import './orchestration/maintenance.js';
|
|
11
|
-
export { L as BulkImportBatchIngestResult, M as BulkImportBatchPartialFailureError, N as GraphRecallRankedResult, Q as GraphRecallShadowComparison, G as GraphRecallSnapshot, I as IntentDebugSnapshot, O as Orchestrator, T as QmdRecallSnapshot, U as RecallInvocationOptions, V as RecallModeDecision, X as appendMemoryToGraphContext, Y as blendGraphExpandedRecallScore, Z as buildCompressionGuidelinesMarkdown, _ as buildMemoryPathById, $ as computeArtifactCandidateFetchLimit, a0 as computeArtifactRecallLimit, a1 as computeQmdHybridFetchLimit, q as defaultWorkspaceDir, a2 as deriveTopicsFromExtraction, a3 as filterRecallCandidates, a4 as graphPathRelativeToStorage, a5 as hasIdentityRecoveryIntent, a6 as isArtifactMemoryPath, a7 as lifecycleRecallScoreAdjustment, a8 as mergeArtifactRecallCandidates, a9 as mergeGraphExpandedResults, aa as resolveEffectiveIdentityInjectionMode, ab as resolveEffectiveRecallMode, ac as resolvePersistedMemoryRelativePath, ad as resolveRecallModeDecision, ae as resolveRecallModeDecisionAsync, af as resolveRecentThreadMemoryPaths, A as sanitizeSessionKeyForFilename, ag as shouldFilterLifecycleRecallCandidate, ah as summarizeGraphShadowComparison } from './orchestrator-
|
|
11
|
+
export { L as BulkImportBatchIngestResult, M as BulkImportBatchPartialFailureError, N as GraphRecallRankedResult, Q as GraphRecallShadowComparison, G as GraphRecallSnapshot, I as IntentDebugSnapshot, O as Orchestrator, T as QmdRecallSnapshot, U as RecallInvocationOptions, V as RecallModeDecision, X as appendMemoryToGraphContext, Y as blendGraphExpandedRecallScore, Z as buildCompressionGuidelinesMarkdown, _ as buildMemoryPathById, $ as computeArtifactCandidateFetchLimit, a0 as computeArtifactRecallLimit, a1 as computeQmdHybridFetchLimit, q as defaultWorkspaceDir, a2 as deriveTopicsFromExtraction, a3 as filterRecallCandidates, a4 as graphPathRelativeToStorage, a5 as hasIdentityRecoveryIntent, a6 as isArtifactMemoryPath, a7 as lifecycleRecallScoreAdjustment, a8 as mergeArtifactRecallCandidates, a9 as mergeGraphExpandedResults, aa as resolveEffectiveIdentityInjectionMode, ab as resolveEffectiveRecallMode, ac as resolvePersistedMemoryRelativePath, ad as resolveRecallModeDecision, ae as resolveRecallModeDecisionAsync, af as resolveRecentThreadMemoryPaths, A as sanitizeSessionKeyForFilename, ag as shouldFilterLifecycleRecallCandidate, ah as summarizeGraphShadowComparison } from './orchestrator-Dr-1v5EL.js';
|
|
12
12
|
export { formatCompressionGuidelinesForRecall } from './orchestration/compression-guideline-coordinator.js';
|
|
13
13
|
import './live-connectors-runner.js';
|
|
14
14
|
import './model-registry.js';
|
package/dist/orchestrator.js
CHANGED
|
@@ -26,8 +26,8 @@ import {
|
|
|
26
26
|
sanitizeSessionKeyForFilename,
|
|
27
27
|
shouldFilterLifecycleRecallCandidate,
|
|
28
28
|
summarizeGraphShadowComparison
|
|
29
|
-
} from "./chunk-
|
|
30
|
-
import "./chunk-
|
|
29
|
+
} from "./chunk-7HUGLMSW.js";
|
|
30
|
+
import "./chunk-C7LDPZJJ.js";
|
|
31
31
|
import {
|
|
32
32
|
formatCompressionGuidelinesForRecall
|
|
33
33
|
} from "./chunk-WIMKOTNY.js";
|
|
@@ -38,12 +38,12 @@ import "./chunk-7HYPN2GC.js";
|
|
|
38
38
|
import "./chunk-666A3MOW.js";
|
|
39
39
|
import "./chunk-HHLLAQGZ.js";
|
|
40
40
|
import "./chunk-UWK5OXUJ.js";
|
|
41
|
-
import "./chunk-
|
|
41
|
+
import "./chunk-V5RVMULT.js";
|
|
42
42
|
import "./chunk-7N4KAIGN.js";
|
|
43
43
|
import "./chunk-TPDBFYEG.js";
|
|
44
44
|
import "./chunk-KCYE2MZM.js";
|
|
45
45
|
import "./chunk-KYYL4U6X.js";
|
|
46
|
-
import "./chunk-
|
|
46
|
+
import "./chunk-W4H5A3TJ.js";
|
|
47
47
|
import "./chunk-HW5EF6LV.js";
|
|
48
48
|
import "./chunk-TECVW3JP.js";
|
|
49
49
|
import "./chunk-O7GOFAM3.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remnic/core",
|
|
3
|
-
"version": "9.3.
|
|
3
|
+
"version": "9.3.742",
|
|
4
4
|
"description": "Framework-agnostic Remnic memory engine — orchestrator, storage, extraction, search, trust zones",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -3000,7 +3000,7 @@
|
|
|
3000
3000
|
"core"
|
|
3001
3001
|
],
|
|
3002
3002
|
"peerDependencies": {
|
|
3003
|
-
"@remnic/coding-graph": "^9.3.
|
|
3003
|
+
"@remnic/coding-graph": "^9.3.742"
|
|
3004
3004
|
},
|
|
3005
3005
|
"peerDependenciesMeta": {
|
|
3006
3006
|
"@remnic/coding-graph": {
|
package/src/cli.ts
CHANGED
|
@@ -2430,9 +2430,10 @@ export async function runPolicyStatusCliCommand(
|
|
|
2430
2430
|
const defaultWindowMs = Math.max(0, orchestrator.config.behaviorLoopLearningWindowDays) * 24 * 60 * 60 * 1000;
|
|
2431
2431
|
const cutoffIso = defaultWindowMs > 0 ? new Date(now.getTime() - defaultWindowMs).toISOString() : undefined;
|
|
2432
2432
|
|
|
2433
|
+
const { behaviorLoopAutoTuneEnabled } = orchestrator.config;
|
|
2433
2434
|
return {
|
|
2434
2435
|
generatedAt: now.toISOString(),
|
|
2435
|
-
autoTuneEnabled:
|
|
2436
|
+
autoTuneEnabled: behaviorLoopAutoTuneEnabled,
|
|
2436
2437
|
current: current
|
|
2437
2438
|
? {
|
|
2438
2439
|
...current,
|
package/src/graph-recall.ts
CHANGED
|
@@ -110,7 +110,8 @@ export function runGraphRecall(
|
|
|
110
110
|
config: GraphRecallConfig,
|
|
111
111
|
options: GraphRecallOptions,
|
|
112
112
|
): GraphRecallRun {
|
|
113
|
-
|
|
113
|
+
const { recallGraphEnabled } = config;
|
|
114
|
+
if (!recallGraphEnabled) {
|
|
114
115
|
return {
|
|
115
116
|
ran: false,
|
|
116
117
|
results: [],
|
package/src/lcm/engine.ts
CHANGED
|
@@ -117,6 +117,7 @@ export class LcmEngine {
|
|
|
117
117
|
const db = openLcmDatabase(this.memoryDir);
|
|
118
118
|
const archive = new LcmArchive(db);
|
|
119
119
|
const dag = new LcmDag(db);
|
|
120
|
+
const { telemetryPrefilterEnabled } = this.config;
|
|
120
121
|
const summarizer = new LcmSummarizer(
|
|
121
122
|
archive,
|
|
122
123
|
dag,
|
|
@@ -126,7 +127,7 @@ export class LcmEngine {
|
|
|
126
127
|
rollupFanIn: this.config.rollupFanIn,
|
|
127
128
|
maxDepth: this.config.maxDepth,
|
|
128
129
|
deterministicMaxTokens: this.config.deterministicMaxTokens,
|
|
129
|
-
telemetryPrefilterEnabled
|
|
130
|
+
telemetryPrefilterEnabled,
|
|
130
131
|
},
|
|
131
132
|
);
|
|
132
133
|
const observeQueue = new LcmWorkQueue({
|
|
@@ -229,17 +230,18 @@ export class LcmEngine {
|
|
|
229
230
|
if (messages.length === 0) return;
|
|
230
231
|
|
|
231
232
|
const currentMax = this.archive.getMaxTurnIndex(sessionId);
|
|
233
|
+
const { messagePartsEnabled } = this.config;
|
|
232
234
|
const newMessages = messages.map((m, i) => ({
|
|
233
235
|
turnIndex: currentMax + 1 + i,
|
|
234
236
|
role: m.role,
|
|
235
237
|
content: m.content,
|
|
236
|
-
parts:
|
|
237
|
-
rawContent:
|
|
238
|
-
sourceFormat:
|
|
238
|
+
parts: messagePartsEnabled ? m.parts : undefined,
|
|
239
|
+
rawContent: messagePartsEnabled ? m.rawContent : undefined,
|
|
240
|
+
sourceFormat: messagePartsEnabled ? m.sourceFormat : undefined,
|
|
239
241
|
}));
|
|
240
242
|
|
|
241
243
|
this.archive.appendMessages(sessionId, newMessages, {
|
|
242
|
-
messagePartsEnabled
|
|
244
|
+
messagePartsEnabled,
|
|
243
245
|
});
|
|
244
246
|
|
|
245
247
|
// Trigger incremental summarization inside the worker, after append.
|
|
@@ -346,7 +348,8 @@ export class LcmEngine {
|
|
|
346
348
|
query: string,
|
|
347
349
|
limit = this.config.messagePartsRecallMaxResults,
|
|
348
350
|
): Promise<LcmStructuredRecallMatch[]> {
|
|
349
|
-
|
|
351
|
+
const { messagePartsEnabled } = this.config;
|
|
352
|
+
if (!this.config.enabled || !messagePartsEnabled) return [];
|
|
350
353
|
const normalizedSessionId = normalizeLcmSessionId(sessionId);
|
|
351
354
|
if (!normalizedSessionId) return [];
|
|
352
355
|
await this.ensureInitialized();
|
package/src/lcm/summarizer.ts
CHANGED
|
@@ -153,8 +153,9 @@ export class LcmSummarizer {
|
|
|
153
153
|
text: string,
|
|
154
154
|
targetTokens: number,
|
|
155
155
|
): Promise<{ text: string; escalation: number }> {
|
|
156
|
+
const { telemetryPrefilterEnabled } = this.config;
|
|
156
157
|
if (
|
|
157
|
-
|
|
158
|
+
telemetryPrefilterEnabled &&
|
|
158
159
|
looksLikeMechanicalTelemetryTranscript(text)
|
|
159
160
|
) {
|
|
160
161
|
return {
|
package/src/operator-toolkit.ts
CHANGED
|
@@ -1617,7 +1617,8 @@ async function summarizeGraphEdgeDecayStatus(
|
|
|
1617
1617
|
): Promise<OperatorDoctorCheck> {
|
|
1618
1618
|
// Lazy import to keep the doctor fast when the feature is disabled.
|
|
1619
1619
|
const { readGraphEdgeDecayStatus } = await import("./maintenance/graph-edge-decay.js");
|
|
1620
|
-
const
|
|
1620
|
+
const { graphEdgeDecayEnabled } = config;
|
|
1621
|
+
const enabled = graphEdgeDecayEnabled === true;
|
|
1621
1622
|
if (!enabled) {
|
|
1622
1623
|
return {
|
|
1623
1624
|
key: "graph_edge_decay",
|
|
@@ -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
|
+
}
|