dsh-mnemon-source-memory-spaces 0.5.0-rc.1
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/LICENSE +21 -0
- package/README.md +38 -0
- package/lib/client.js +3462 -0
- package/lib/contracts.d.ts +498 -0
- package/lib/contracts.js +41 -0
- package/lib/definitions-CEpa-tvm.d.ts +135 -0
- package/lib/definitions-DfmOqJjR.js +214 -0
- package/lib/index.d.ts +47 -0
- package/lib/index.js +3456 -0
- package/lib/plugin-DlsNeZbQ.js +388 -0
- package/lib/process-B-t3UflO.js +81 -0
- package/lib/provider-sdk.d.ts +54 -0
- package/lib/provider-sdk.js +105 -0
- package/lib/testing.d.ts +28 -0
- package/lib/testing.js +106 -0
- package/lib/types/client/ProviderIcon.d.ts +10 -0
- package/lib/types/client/api.d.ts +39 -0
- package/lib/types/client/pages.d.ts +72 -0
- package/lib/types/client/provider-presentation.d.ts +6 -0
- package/lib/types/client/ui.d.ts +17 -0
- package/lib/types/client.d.ts +3 -0
- package/lib/types/contracts.d.ts +496 -0
- package/package.json +103 -0
package/lib/index.js
ADDED
|
@@ -0,0 +1,3456 @@
|
|
|
1
|
+
import { CATEGORIES as CATEGORIES$1, EDGE_TYPES, INTENTS as INTENTS$1, MNEMON_EMBEDDING_PROTOCOLS, SOURCES as SOURCES$1 } from "./contracts.js";
|
|
2
|
+
import { a as MemoryProviderCatalog, i as EMPTY_MEMORY_PROVIDER_CATALOG, n as PrivateMemorySpaceProviderHost, r as MemoryProviderAdapterRegistry, t as createMemorySpaceProviderPlugin } from "./plugin-DlsNeZbQ.js";
|
|
3
|
+
import { t as runProcess } from "./process-B-t3UflO.js";
|
|
4
|
+
import { r as defineMemorySpaceProvider } from "./definitions-DfmOqJjR.js";
|
|
5
|
+
import z from "schemastery";
|
|
6
|
+
import { createMemoryMutationReceipt, defineMemoryPlugin, defineMemorySource, installMemory, memoryConfigurationDigest, memoryInputInteger, memoryInputRecord, memoryInputStringArray, memoryInputText, truncateMemoryText, withMemoryStorageLock } from "dsh-mnemon/extension-sdk";
|
|
7
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
8
|
+
import { COMPOSABLE_MEMORY_API_VERSION } from "dsh-mnemon/contracts";
|
|
9
|
+
import { accessSync, constants, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
10
|
+
import { homedir, tmpdir } from "node:os";
|
|
11
|
+
import { basename, dirname, isAbsolute, join, posix, resolve, win32 } from "node:path";
|
|
12
|
+
//#region src/view-model.ts
|
|
13
|
+
const MODEL_MEMORY_BODY_LIMIT = 16;
|
|
14
|
+
function boundedToolText(value, maximum) {
|
|
15
|
+
return value.length <= maximum ? value : `${value.slice(0, maximum - 1)}…`;
|
|
16
|
+
}
|
|
17
|
+
/** Strip control-plane paths, provider settings, and statistics from model output. */
|
|
18
|
+
function modelBodyCatalog(catalog) {
|
|
19
|
+
const items = catalog.items.slice(0, MODEL_MEMORY_BODY_LIMIT);
|
|
20
|
+
return {
|
|
21
|
+
items: items.map((body) => ({
|
|
22
|
+
id: body.id,
|
|
23
|
+
name: boundedToolText(body.name, 120),
|
|
24
|
+
description: boundedToolText(body.description, 500),
|
|
25
|
+
active: body.active,
|
|
26
|
+
providerEnabled: body.providerEnabled !== false,
|
|
27
|
+
status: body.providerEnabled === false ? "provider-disabled" : body.statusLoading === true ? "not-probed" : body.healthy ? "healthy" : "unhealthy",
|
|
28
|
+
...body.error === void 0 ? {} : { error: boundedToolText(body.error, 500) },
|
|
29
|
+
provider: {
|
|
30
|
+
id: body.provider.id,
|
|
31
|
+
label: boundedToolText(body.provider.label, 120),
|
|
32
|
+
capabilities: structuredClone(body.provider.capabilities)
|
|
33
|
+
}
|
|
34
|
+
})),
|
|
35
|
+
persistenceStrategy: catalog.persistenceStrategy,
|
|
36
|
+
total: catalog.total,
|
|
37
|
+
activeCount: catalog.activeCount,
|
|
38
|
+
omittedCount: Math.max(0, catalog.items.length - items.length)
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/** Keep health diagnostics useful without exposing complete control-plane state. */
|
|
42
|
+
function modelStatus(status) {
|
|
43
|
+
const active = status.memoryBodies.filter((body) => body.active && body.providerEnabled !== false);
|
|
44
|
+
const unhealthy = active.filter((body) => !body.healthy);
|
|
45
|
+
const relevantProviders = status.providerServices?.filter((provider) => provider.enabled || provider.configured || provider.memoryBodyCount > 0 || provider.status === "unhealthy") ?? [];
|
|
46
|
+
const providers = relevantProviders.slice(0, 16);
|
|
47
|
+
return {
|
|
48
|
+
healthy: status.healthy && unhealthy.length === 0,
|
|
49
|
+
...status.error === void 0 ? {} : { error: boundedToolText(status.error, 1e3) },
|
|
50
|
+
...status.version === void 0 ? {} : { version: boundedToolText(status.version, 120) },
|
|
51
|
+
commandFound: status.commandFound,
|
|
52
|
+
writeEnabled: status.writeEnabled,
|
|
53
|
+
memorySpaces: {
|
|
54
|
+
total: status.memoryBodies.length,
|
|
55
|
+
active: active.length,
|
|
56
|
+
healthy: active.length - unhealthy.length,
|
|
57
|
+
unhealthy: unhealthy.length,
|
|
58
|
+
providerDisabled: status.memoryBodies.filter((body) => body.providerEnabled === false).length
|
|
59
|
+
},
|
|
60
|
+
providers: providers.map((provider) => ({
|
|
61
|
+
providerId: provider.providerId,
|
|
62
|
+
label: boundedToolText(provider.label, 120),
|
|
63
|
+
enabled: provider.enabled,
|
|
64
|
+
configured: provider.configured,
|
|
65
|
+
status: provider.status,
|
|
66
|
+
memoryBodyCount: provider.memoryBodyCount,
|
|
67
|
+
activeMemoryBodyCount: provider.activeMemoryBodyCount,
|
|
68
|
+
...provider.error === void 0 ? {} : { error: boundedToolText(provider.error, 500) }
|
|
69
|
+
})),
|
|
70
|
+
omittedProviderCount: Math.max(0, relevantProviders.length - providers.length),
|
|
71
|
+
...status.stats === void 0 ? {} : { aggregate: {
|
|
72
|
+
totalInsights: status.stats.totalInsights,
|
|
73
|
+
deletedInsights: status.stats.deletedInsights,
|
|
74
|
+
edgeCount: status.stats.edgeCount,
|
|
75
|
+
oplogCount: status.stats.oplogCount,
|
|
76
|
+
dbSizeBytes: status.stats.dbSizeBytes
|
|
77
|
+
} }
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/** Preserve valid JSON when the Route's evidence budget is smaller than a directory. */
|
|
81
|
+
function modelJson(value, maximum) {
|
|
82
|
+
const bounded = structuredClone(value);
|
|
83
|
+
let result = JSON.stringify(bounded);
|
|
84
|
+
while (result.length > maximum) {
|
|
85
|
+
if (bounded.items !== void 0 && bounded.items.length > 0) {
|
|
86
|
+
bounded.items.pop();
|
|
87
|
+
bounded.omittedCount = (bounded.omittedCount ?? 0) + 1;
|
|
88
|
+
} else if (bounded.providers !== void 0 && bounded.providers.length > 0) {
|
|
89
|
+
bounded.providers.pop();
|
|
90
|
+
bounded.omittedProviderCount = (bounded.omittedProviderCount ?? 0) + 1;
|
|
91
|
+
} else return JSON.stringify({ unavailable: "Inspection summary exceeds the Route budget; narrow the query." });
|
|
92
|
+
result = JSON.stringify(bounded);
|
|
93
|
+
}
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
//#endregion
|
|
97
|
+
//#region src/memory-bodies.ts
|
|
98
|
+
const NATIVE_REGISTRY_VERSION = 1;
|
|
99
|
+
const PROVIDER_REGISTRY_VERSION = 4;
|
|
100
|
+
const ID_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9_-]*$/;
|
|
101
|
+
const PROVIDER_ID_PATTERN = /^[a-z][a-z0-9-]{0,127}$/u;
|
|
102
|
+
function storedProviderId(value) {
|
|
103
|
+
return typeof value === "string" && PROVIDER_ID_PATTERN.test(value);
|
|
104
|
+
}
|
|
105
|
+
function storedProviderConnection(value) {
|
|
106
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return {};
|
|
107
|
+
return Object.fromEntries(Object.entries(value).filter((entry) => {
|
|
108
|
+
const [key, item] = entry;
|
|
109
|
+
return /^[a-z][a-zA-Z0-9_-]{0,127}$/u.test(key) && (typeof item === "boolean" || typeof item === "number" && Number.isFinite(item) || typeof item === "string" && item.length <= 8e3);
|
|
110
|
+
}));
|
|
111
|
+
}
|
|
112
|
+
function requiredText(value, label, max) {
|
|
113
|
+
const normalized = value.trim();
|
|
114
|
+
if (normalized === "") throw new Error(`${label} is required`);
|
|
115
|
+
if (normalized.length > max) throw new Error(`${label} is too long (max ${max} characters)`);
|
|
116
|
+
return normalized;
|
|
117
|
+
}
|
|
118
|
+
function optionalText$1(value, label, max) {
|
|
119
|
+
const normalized = value?.trim() ?? "";
|
|
120
|
+
if (normalized.length > max) throw new Error(`${label} is too long (max ${max} characters)`);
|
|
121
|
+
return normalized;
|
|
122
|
+
}
|
|
123
|
+
const PROVIDER_METADATA_KEYS = [
|
|
124
|
+
"name",
|
|
125
|
+
"title",
|
|
126
|
+
"displayName",
|
|
127
|
+
"workspace",
|
|
128
|
+
"bankId",
|
|
129
|
+
"project",
|
|
130
|
+
"containerTag",
|
|
131
|
+
"userId",
|
|
132
|
+
"user",
|
|
133
|
+
"workingDirectory",
|
|
134
|
+
"targetUri"
|
|
135
|
+
];
|
|
136
|
+
function compactProviderMetadataValue(value) {
|
|
137
|
+
if (typeof value !== "string") return void 0;
|
|
138
|
+
const normalized = value.trim();
|
|
139
|
+
if (normalized === "" || normalized === "*") return void 0;
|
|
140
|
+
return (normalized.split(/[/:\\]+/u).filter(Boolean).at(-1) ?? normalized).trim() || void 0;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Normalize uneven provider discovery metadata at the projection boundary.
|
|
144
|
+
* Adapters map the richest native fields they know; the registry then tries
|
|
145
|
+
* the nearest namespace setting before falling back to a stable provider
|
|
146
|
+
* identity. This keeps every discovered namespace usable without teaching the
|
|
147
|
+
* Web UI each provider's response shape.
|
|
148
|
+
*/
|
|
149
|
+
function providerProjectionMetadata(providerCatalog, providerId, candidate) {
|
|
150
|
+
const descriptor = providerCatalog.descriptor(providerId);
|
|
151
|
+
const externalId = requiredText(candidate.externalId, "provider externalId", 2e3);
|
|
152
|
+
const mappedName = String(candidate.name ?? "").trim();
|
|
153
|
+
const nearestName = PROVIDER_METADATA_KEYS.map((key) => compactProviderMetadataValue(candidate.connection[key])).find((value) => value !== void 0);
|
|
154
|
+
const fallbackId = compactProviderMetadataValue(externalId) ?? externalId;
|
|
155
|
+
const name = (mappedName || nearestName || `${descriptor.label} ${fallbackId}`).slice(0, 100);
|
|
156
|
+
const description = (String(candidate.description ?? "").trim() || `${descriptor.label} memory namespace mapped from ${externalId}.`).slice(0, 1e3);
|
|
157
|
+
return {
|
|
158
|
+
name: requiredText(name, "name", 100),
|
|
159
|
+
description: optionalText$1(description, "description", 1e3)
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
function providerDisplayLocation(descriptor, connection) {
|
|
163
|
+
const populated = (field) => field.input !== "secret" && String(connection[field.key] ?? "").trim() !== "";
|
|
164
|
+
const field = descriptor.fields.find((candidate) => candidate.role === "global-location" && populated(candidate)) ?? descriptor.fields.find((candidate) => (candidate.input === "url" || candidate.input === "path") && populated(candidate)) ?? descriptor.fields.find(populated);
|
|
165
|
+
return field === void 0 ? "" : String(connection[field.key]);
|
|
166
|
+
}
|
|
167
|
+
function legacyOpenVikingConnection(connection) {
|
|
168
|
+
return Object.fromEntries(Object.entries(connection).filter((entry) => typeof entry[1] === "string"));
|
|
169
|
+
}
|
|
170
|
+
function normalizePlacementDecision(value, providerId, providerCatalog = EMPTY_MEMORY_PROVIDER_CATALOG) {
|
|
171
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
|
|
172
|
+
const placement = value;
|
|
173
|
+
if (placement.mode !== "automatic" || placement.providerId !== providerId) return void 0;
|
|
174
|
+
if (placement.decidedBy !== "rules" && placement.decidedBy !== "llm") return void 0;
|
|
175
|
+
if (placement.confidence !== "high" && placement.confidence !== "medium" && placement.confidence !== "low") return void 0;
|
|
176
|
+
if (typeof placement.reason !== "string" || placement.reason.trim() === "" || placement.reason.length > 1e3) return void 0;
|
|
177
|
+
if (!Array.isArray(placement.candidateProviderIds) || !placement.candidateProviderIds.every((id) => providerCatalog.has(id)) || !placement.candidateProviderIds.includes(providerId)) return void 0;
|
|
178
|
+
if (!Array.isArray(placement.appliedRules) || !placement.appliedRules.every((rule) => typeof rule === "string" && rule.length <= 500)) return void 0;
|
|
179
|
+
if (typeof placement.decidedAt !== "string" || placement.decidedAt.trim() === "") return void 0;
|
|
180
|
+
if (placement.runId !== void 0 && typeof placement.runId !== "string") return void 0;
|
|
181
|
+
if (placement.subagentProvider !== void 0 && typeof placement.subagentProvider !== "string") return void 0;
|
|
182
|
+
return {
|
|
183
|
+
mode: "automatic",
|
|
184
|
+
providerId,
|
|
185
|
+
decidedBy: placement.decidedBy,
|
|
186
|
+
reason: placement.reason.trim(),
|
|
187
|
+
confidence: placement.confidence,
|
|
188
|
+
candidateProviderIds: [...new Set(placement.candidateProviderIds)],
|
|
189
|
+
appliedRules: [...placement.appliedRules],
|
|
190
|
+
decidedAt: placement.decidedAt,
|
|
191
|
+
...placement.runId === void 0 ? {} : { runId: placement.runId },
|
|
192
|
+
...placement.subagentProvider === void 0 ? {} : { subagentProvider: placement.subagentProvider }
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
function validateMemoryBodyId(value) {
|
|
196
|
+
const normalized = value.trim();
|
|
197
|
+
if (!ID_PATTERN.test(normalized)) throw new Error("memoryBodyId must match [a-zA-Z0-9][a-zA-Z0-9_-]*");
|
|
198
|
+
return normalized;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Persistent metadata layered over Mnemon's native named stores.
|
|
202
|
+
*
|
|
203
|
+
* Native metadata lives beside Store directories so existing Mnemon Packs stay
|
|
204
|
+
* compatible. External connection metadata lives under state and is never
|
|
205
|
+
* included in Memory Space Packs.
|
|
206
|
+
*/
|
|
207
|
+
var MemoryBodyRegistry = class MemoryBodyRegistry {
|
|
208
|
+
runner;
|
|
209
|
+
persistent;
|
|
210
|
+
now;
|
|
211
|
+
providerCatalog;
|
|
212
|
+
directory;
|
|
213
|
+
registryPath;
|
|
214
|
+
providerRegistryPath;
|
|
215
|
+
refreshing = false;
|
|
216
|
+
state;
|
|
217
|
+
constructor(runner, persistent = true, now = () => /* @__PURE__ */ new Date(), providerCatalog = EMPTY_MEMORY_PROVIDER_CATALOG, sharedState) {
|
|
218
|
+
this.runner = runner;
|
|
219
|
+
this.persistent = persistent;
|
|
220
|
+
this.now = now;
|
|
221
|
+
this.providerCatalog = providerCatalog;
|
|
222
|
+
this.state = sharedState ?? {
|
|
223
|
+
bodies: [],
|
|
224
|
+
services: {},
|
|
225
|
+
serviceEnabled: {}
|
|
226
|
+
};
|
|
227
|
+
this.directory = join(runner.effectiveDataDir(), "data");
|
|
228
|
+
this.registryPath = join(this.directory, ".dsh-memory-bodies.json");
|
|
229
|
+
this.providerRegistryPath = join(runner.effectiveDataDir(), "state", "memory-providers.json");
|
|
230
|
+
if (sharedState === void 0) this.reload();
|
|
231
|
+
}
|
|
232
|
+
get bodies() {
|
|
233
|
+
return this.state.bodies;
|
|
234
|
+
}
|
|
235
|
+
set bodies(value) {
|
|
236
|
+
this.state.bodies = value;
|
|
237
|
+
}
|
|
238
|
+
get services() {
|
|
239
|
+
return this.state.services;
|
|
240
|
+
}
|
|
241
|
+
set services(value) {
|
|
242
|
+
this.state.services = value;
|
|
243
|
+
}
|
|
244
|
+
get serviceEnabled() {
|
|
245
|
+
return this.state.serviceEnabled;
|
|
246
|
+
}
|
|
247
|
+
set serviceEnabled(value) {
|
|
248
|
+
this.state.serviceEnabled = value;
|
|
249
|
+
}
|
|
250
|
+
/** One generation-local descriptor view over the same persistent authority. */
|
|
251
|
+
withProviderCatalog(providerCatalog) {
|
|
252
|
+
return new MemoryBodyRegistry(this.runner, this.persistent, this.now, providerCatalog, this.state);
|
|
253
|
+
}
|
|
254
|
+
isNative(providerId) {
|
|
255
|
+
return this.providerTypeId(providerId) === "mnemon-native";
|
|
256
|
+
}
|
|
257
|
+
providerTypeId(providerId) {
|
|
258
|
+
const descriptor = this.providerCatalog.descriptor(providerId);
|
|
259
|
+
return descriptor.typeId ?? descriptor.id;
|
|
260
|
+
}
|
|
261
|
+
list() {
|
|
262
|
+
this.refreshIfChanged();
|
|
263
|
+
this.reconcileDiscoveredStores();
|
|
264
|
+
return this.bodies.filter((body) => this.providerCatalog.has(body.providerId)).map((body) => this.view(body));
|
|
265
|
+
}
|
|
266
|
+
active() {
|
|
267
|
+
return this.list().filter((body) => body.active && (this.isNative(body.provider.id) || Object.hasOwn(this.services, body.provider.id) && this.serviceEnabled[body.provider.id] === true));
|
|
268
|
+
}
|
|
269
|
+
get(id) {
|
|
270
|
+
const normalized = validateMemoryBodyId(id);
|
|
271
|
+
const body = this.list().find((entry) => entry.id === normalized);
|
|
272
|
+
if (body === void 0) throw new Error(`unknown memory body: ${normalized}`);
|
|
273
|
+
return body;
|
|
274
|
+
}
|
|
275
|
+
openVikingConnection(id) {
|
|
276
|
+
const connection = this.providerConnection(id, "openviking");
|
|
277
|
+
return {
|
|
278
|
+
endpoint: String(connection.endpoint ?? ""),
|
|
279
|
+
targetUri: String(connection.targetUri ?? ""),
|
|
280
|
+
apiKey: String(connection.apiKey ?? ""),
|
|
281
|
+
account: String(connection.account ?? ""),
|
|
282
|
+
user: String(connection.user ?? ""),
|
|
283
|
+
actorPeerId: String(connection.actorPeerId ?? "")
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
providerConnection(id, expectedProviderId) {
|
|
287
|
+
this.refreshIfChanged();
|
|
288
|
+
const normalized = validateMemoryBodyId(id);
|
|
289
|
+
const body = this.bodies.find((entry) => entry.id === normalized);
|
|
290
|
+
if (body === void 0 || this.isNative(body.providerId)) throw new Error(`memory body has no external provider connection: ${normalized}`);
|
|
291
|
+
if (expectedProviderId !== void 0 && body.providerId !== expectedProviderId) throw new Error(`memory body ${normalized} uses ${body.providerId}, not ${expectedProviderId}`);
|
|
292
|
+
return this.connectionFor(body);
|
|
293
|
+
}
|
|
294
|
+
/** Used only after a public operation has refreshed the registry authority. */
|
|
295
|
+
connectionFor(body) {
|
|
296
|
+
const legacy = this.providerTypeId(body.providerId) === "openviking" && body.openViking !== void 0 ? legacyOpenVikingConnection(body.openViking) : void 0;
|
|
297
|
+
return this.providerCatalog.normalize(body.providerId, {
|
|
298
|
+
...this.services[body.providerId] ?? {},
|
|
299
|
+
...legacy ?? {},
|
|
300
|
+
...body.connection ?? {}
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
providerServiceConfigured(providerId) {
|
|
304
|
+
this.refreshIfChanged();
|
|
305
|
+
return this.isNative(providerId) ? this.runner.commandFound : Object.hasOwn(this.services, providerId);
|
|
306
|
+
}
|
|
307
|
+
providerServiceEnabled(providerId) {
|
|
308
|
+
return this.isNative(providerId) ? this.runner.commandFound : this.providerServiceConfigured(providerId) && this.serviceEnabled[providerId] === true;
|
|
309
|
+
}
|
|
310
|
+
providerServices(options = {}) {
|
|
311
|
+
this.refreshIfChanged();
|
|
312
|
+
const providers = this.providerCatalog.providers.filter((provider) => (provider.typeId ?? provider.id) !== "mnemon-native");
|
|
313
|
+
const items = providers.map((provider) => {
|
|
314
|
+
const connection = this.services[provider.id];
|
|
315
|
+
const publicConnection = this.providerCatalog.publicScoped(provider.id, "service", connection ?? {});
|
|
316
|
+
return {
|
|
317
|
+
providerId: provider.id,
|
|
318
|
+
enabled: Object.hasOwn(this.services, provider.id) && this.serviceEnabled[provider.id] === true,
|
|
319
|
+
configured: connection !== void 0,
|
|
320
|
+
...publicConnection,
|
|
321
|
+
...options.includeSecrets === true && connection !== void 0 ? { secretValues: Object.fromEntries(publicConnection.configuredSecrets.map((key) => [key, connection[key]])) } : {}
|
|
322
|
+
};
|
|
323
|
+
});
|
|
324
|
+
return {
|
|
325
|
+
providers: [...providers],
|
|
326
|
+
items,
|
|
327
|
+
generatedAt: this.now().toISOString()
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
updateProviderService(providerId, settings, clearSecrets = [], enabled = true) {
|
|
331
|
+
this.refreshIfChanged();
|
|
332
|
+
if (this.isNative(providerId)) throw new Error("Mnemon Native service settings are managed by the native configuration");
|
|
333
|
+
const previous = this.services[providerId] ?? {};
|
|
334
|
+
this.services[providerId] = this.providerCatalog.normalizeService(providerId, settings, previous, clearSecrets);
|
|
335
|
+
this.serviceEnabled[providerId] = enabled;
|
|
336
|
+
if (!enabled) this.bodies = this.bodies.filter((body) => body.providerId !== providerId);
|
|
337
|
+
this.save();
|
|
338
|
+
return this.providerServices().items.find((item) => item.providerId === providerId);
|
|
339
|
+
}
|
|
340
|
+
resolveProviderService(providerId, settings, clearSecrets = []) {
|
|
341
|
+
this.refreshIfChanged();
|
|
342
|
+
if (this.isNative(providerId)) throw new Error("Mnemon Native service settings are managed by the native configuration");
|
|
343
|
+
return this.providerCatalog.normalizeService(providerId, settings, this.services[providerId] ?? {}, clearSecrets);
|
|
344
|
+
}
|
|
345
|
+
/** Atomically replace one provider's local projections after authoritative discovery. */
|
|
346
|
+
syncProviderService(providerId, service, discovered) {
|
|
347
|
+
this.refreshIfChanged();
|
|
348
|
+
if (this.isNative(providerId)) throw new Error("Mnemon Native Stores are discovered from disk");
|
|
349
|
+
let normalizedService = this.providerCatalog.normalizeService(providerId, service);
|
|
350
|
+
const seen = /* @__PURE__ */ new Set();
|
|
351
|
+
const existing = this.bodies.filter((body) => body.providerId === providerId);
|
|
352
|
+
const reservedIds = new Set(this.bodies.filter((body) => body.providerId !== providerId).map((body) => body.id));
|
|
353
|
+
const timestamp = this.now().toISOString();
|
|
354
|
+
const projections = discovered.map((candidate) => {
|
|
355
|
+
const externalId = requiredText(candidate.externalId, "provider externalId", 2e3);
|
|
356
|
+
if (seen.has(externalId)) throw new Error(`${this.providerCatalog.descriptor(providerId).label} returned a duplicate memory namespace: ${externalId}`);
|
|
357
|
+
seen.add(externalId);
|
|
358
|
+
const connection = this.providerCatalog.normalizeMemory(providerId, candidate.connection);
|
|
359
|
+
this.providerCatalog.normalize(providerId, {
|
|
360
|
+
...normalizedService,
|
|
361
|
+
...connection
|
|
362
|
+
});
|
|
363
|
+
const previous = existing.find((body) => body.externalId === externalId);
|
|
364
|
+
let id = previous?.id ?? validateMemoryBodyId(`${providerId}-${createHash("sha256").update(externalId).digest("hex").slice(0, 24)}`);
|
|
365
|
+
let suffix = 1;
|
|
366
|
+
while (reservedIds.has(id)) {
|
|
367
|
+
id = validateMemoryBodyId(`${providerId}-${createHash("sha256").update(`${externalId}:${suffix}`).digest("hex").slice(0, 24)}`);
|
|
368
|
+
suffix += 1;
|
|
369
|
+
}
|
|
370
|
+
reservedIds.add(id);
|
|
371
|
+
const metadata = providerProjectionMetadata(this.providerCatalog, providerId, candidate);
|
|
372
|
+
const metadataSource = previous?.metadataSource ?? (previous === void 0 ? "provider" : "manual");
|
|
373
|
+
const preserveLocalMetadata = previous !== void 0 && metadataSource !== "provider";
|
|
374
|
+
return {
|
|
375
|
+
id,
|
|
376
|
+
externalId,
|
|
377
|
+
name: preserveLocalMetadata ? previous.name : metadata.name,
|
|
378
|
+
description: preserveLocalMetadata ? previous.description : metadata.description,
|
|
379
|
+
metadataSource,
|
|
380
|
+
active: previous?.active ?? true,
|
|
381
|
+
providerId,
|
|
382
|
+
connection,
|
|
383
|
+
createdAt: previous?.createdAt ?? timestamp,
|
|
384
|
+
updatedAt: this.nextTimestamp(previous?.updatedAt, timestamp)
|
|
385
|
+
};
|
|
386
|
+
});
|
|
387
|
+
for (const field of this.providerCatalog.serviceFields(providerId)) {
|
|
388
|
+
if (field.discoveryDefaultFrom === void 0 || String(normalizedService[field.key] ?? "").trim() !== "") continue;
|
|
389
|
+
const discoveredValue = projections[0]?.connection?.[field.discoveryDefaultFrom];
|
|
390
|
+
if (discoveredValue !== void 0 && String(discoveredValue).trim() !== "") normalizedService = this.providerCatalog.normalizeService(providerId, {
|
|
391
|
+
...normalizedService,
|
|
392
|
+
[field.key]: discoveredValue
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
this.services[providerId] = normalizedService;
|
|
396
|
+
this.serviceEnabled[providerId] = true;
|
|
397
|
+
this.bodies = [...this.bodies.filter((body) => body.providerId !== providerId), ...projections];
|
|
398
|
+
this.save();
|
|
399
|
+
return this.providerServices().items.find((item) => item.providerId === providerId);
|
|
400
|
+
}
|
|
401
|
+
placementCandidates(request) {
|
|
402
|
+
return this.providerCatalog.providers.map((descriptor) => {
|
|
403
|
+
const providerTypeId = descriptor.typeId ?? descriptor.id;
|
|
404
|
+
const requestConnection = request.providerConnections?.[descriptor.id] ?? (providerTypeId === "openviking" && request.connection === void 0 && request.openViking !== void 0 ? request.openViking : request.connection);
|
|
405
|
+
let configured = providerTypeId === "mnemon-native" ? this.runner.commandFound : false;
|
|
406
|
+
if (providerTypeId !== "mnemon-native" && (requestConnection !== void 0 || this.providerServiceConfigured(descriptor.id))) try {
|
|
407
|
+
const split = this.providerCatalog.split(descriptor.id, requestConnection);
|
|
408
|
+
this.providerCatalog.normalize(descriptor.id, {
|
|
409
|
+
...this.services[descriptor.id] ?? {},
|
|
410
|
+
...split.service,
|
|
411
|
+
...split.memory
|
|
412
|
+
});
|
|
413
|
+
configured = Object.keys(split.service).length > 0 || this.providerServiceEnabled(descriptor.id);
|
|
414
|
+
} catch {
|
|
415
|
+
configured = false;
|
|
416
|
+
}
|
|
417
|
+
return {
|
|
418
|
+
id: descriptor.id,
|
|
419
|
+
label: descriptor.label,
|
|
420
|
+
kind: descriptor.kind,
|
|
421
|
+
configured,
|
|
422
|
+
summary: descriptor.summary,
|
|
423
|
+
capabilities: descriptor.capabilities
|
|
424
|
+
};
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
async create(request, signal, placement) {
|
|
428
|
+
const name = requiredText(request.name, "name", 100);
|
|
429
|
+
const description = requiredText(request.description, "description", 1e3);
|
|
430
|
+
if (request.placement !== void 0 && placement === void 0) throw new Error("automatic provider placement must be resolved before creating a Memory Space");
|
|
431
|
+
if (placement !== void 0 && request.providerId !== void 0 && request.providerId !== placement.providerId) throw new Error("resolved provider placement conflicts with providerId");
|
|
432
|
+
const providerId = placement?.providerId ?? request.providerId ?? "mnemon-native";
|
|
433
|
+
if (!this.providerCatalog.has(providerId)) throw new Error(`unsupported memory provider: ${String(providerId)}`);
|
|
434
|
+
const normalizedPlacement = placement === void 0 ? void 0 : normalizePlacementDecision(placement, providerId, this.providerCatalog);
|
|
435
|
+
if (placement !== void 0 && normalizedPlacement === void 0) throw new Error("resolved provider placement is invalid");
|
|
436
|
+
const reservedIds = new Set(this.list().map((body) => body.id));
|
|
437
|
+
const nativeStoreIds = this.nativeStoreIds();
|
|
438
|
+
const nativeProvider = this.isNative(providerId);
|
|
439
|
+
let id = nativeProvider && nativeStoreIds.length === 0 && !reservedIds.has("default") ? "default" : validateMemoryBodyId(nativeProvider ? randomUUID() : `${providerId}-${randomUUID()}`);
|
|
440
|
+
while (reservedIds.has(id) || nativeStoreIds.includes(id)) id = validateMemoryBodyId(randomUUID());
|
|
441
|
+
const connectionInput = request.providerConnections?.[providerId] ?? (this.providerTypeId(providerId) === "openviking" && request.connection === void 0 && request.openViking !== void 0 ? request.openViking : request.connection);
|
|
442
|
+
let connection;
|
|
443
|
+
if (!nativeProvider) {
|
|
444
|
+
const split = this.providerCatalog.split(providerId, connectionInput);
|
|
445
|
+
if (Object.keys(split.service).length > 0) {
|
|
446
|
+
this.services[providerId] = this.providerCatalog.normalizeService(providerId, split.service, this.services[providerId] ?? {});
|
|
447
|
+
this.serviceEnabled[providerId] = true;
|
|
448
|
+
}
|
|
449
|
+
if (!this.providerServiceEnabled(providerId)) throw new Error(`${this.providerCatalog.descriptor(providerId).label} service is not enabled; enable it in Settings first`);
|
|
450
|
+
connection = this.providerCatalog.normalizeMemory(providerId, split.memory);
|
|
451
|
+
this.providerCatalog.normalize(providerId, {
|
|
452
|
+
...this.services[providerId],
|
|
453
|
+
...connection
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
if (nativeProvider) await this.runner.runText([
|
|
457
|
+
"store",
|
|
458
|
+
"create",
|
|
459
|
+
id
|
|
460
|
+
], {
|
|
461
|
+
...signal === void 0 ? {} : { signal },
|
|
462
|
+
store: id
|
|
463
|
+
});
|
|
464
|
+
const timestamp = this.now().toISOString();
|
|
465
|
+
const body = {
|
|
466
|
+
id,
|
|
467
|
+
name,
|
|
468
|
+
description,
|
|
469
|
+
active: request.active ?? false,
|
|
470
|
+
providerId,
|
|
471
|
+
...nativeProvider ? {} : { metadataSource: "manual" },
|
|
472
|
+
...normalizedPlacement === void 0 ? {} : { placement: normalizedPlacement },
|
|
473
|
+
...connection === void 0 ? {} : { connection },
|
|
474
|
+
createdAt: timestamp,
|
|
475
|
+
updatedAt: timestamp
|
|
476
|
+
};
|
|
477
|
+
this.bodies.push(body);
|
|
478
|
+
this.save();
|
|
479
|
+
return this.view(body);
|
|
480
|
+
}
|
|
481
|
+
update(id, request) {
|
|
482
|
+
this.refreshIfChanged();
|
|
483
|
+
const normalized = validateMemoryBodyId(id);
|
|
484
|
+
const index = this.bodies.findIndex((body) => body.id === normalized);
|
|
485
|
+
if (index < 0) throw new Error(`unknown memory body: ${normalized}`);
|
|
486
|
+
const current = this.bodies[index];
|
|
487
|
+
if (request.openViking !== void 0 && this.providerTypeId(current.providerId) !== "openviking") throw new Error("OpenViking connection settings only apply to OpenViking memory bodies");
|
|
488
|
+
const nativeProvider = this.isNative(current.providerId);
|
|
489
|
+
if ((request.connection !== void 0 || request.clearSecrets !== void 0) && nativeProvider) throw new Error("Mnemon Native memory bodies do not have provider connection settings");
|
|
490
|
+
const legacyPatch = request.openViking === void 0 ? void 0 : {
|
|
491
|
+
...request.openViking,
|
|
492
|
+
...request.openViking.clearApiKey === true ? { apiKey: "" } : {}
|
|
493
|
+
};
|
|
494
|
+
const previousConnection = nativeProvider ? {} : current.connection ?? {};
|
|
495
|
+
const connectionPatch = request.connection ?? legacyPatch;
|
|
496
|
+
let connection;
|
|
497
|
+
if (!nativeProvider) {
|
|
498
|
+
const split = this.providerCatalog.split(current.providerId, connectionPatch);
|
|
499
|
+
const clearSecrets = [...request.clearSecrets ?? [], ...request.openViking?.clearApiKey === true ? ["apiKey"] : []];
|
|
500
|
+
if (Object.keys(split.service).length > 0 || clearSecrets.length > 0) {
|
|
501
|
+
this.services[current.providerId] = this.providerCatalog.normalizeService(current.providerId, split.service, this.services[current.providerId] ?? {}, clearSecrets);
|
|
502
|
+
this.serviceEnabled[current.providerId] = true;
|
|
503
|
+
}
|
|
504
|
+
if (!this.providerServiceEnabled(current.providerId)) throw new Error(`${this.providerCatalog.descriptor(current.providerId).label} service is not enabled; enable it in Settings first`);
|
|
505
|
+
connection = this.providerCatalog.normalizeMemory(current.providerId, split.memory, previousConnection);
|
|
506
|
+
this.providerCatalog.normalize(current.providerId, {
|
|
507
|
+
...this.services[current.providerId],
|
|
508
|
+
...connection
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
const { openViking: _legacyOpenViking, ...currentBody } = current;
|
|
512
|
+
const body = {
|
|
513
|
+
...currentBody,
|
|
514
|
+
...request.name === void 0 ? {} : { name: requiredText(request.name, "name", 100) },
|
|
515
|
+
...request.description === void 0 ? {} : { description: optionalText$1(request.description, "description", 1e3) },
|
|
516
|
+
...request.active === void 0 ? {} : { active: request.active },
|
|
517
|
+
...nativeProvider || request.name === void 0 && request.description === void 0 ? {} : { metadataSource: "manual" },
|
|
518
|
+
...connection === void 0 ? {} : { connection },
|
|
519
|
+
updatedAt: this.nextTimestamp(current.updatedAt)
|
|
520
|
+
};
|
|
521
|
+
this.bodies[index] = body;
|
|
522
|
+
this.save();
|
|
523
|
+
return this.view(body);
|
|
524
|
+
}
|
|
525
|
+
/** Validate every model-authored update before committing the batch. */
|
|
526
|
+
updateMetadata(updates) {
|
|
527
|
+
this.refreshIfChanged();
|
|
528
|
+
if (updates.length === 0 || updates.length > 20) throw new Error("metadata maintenance requires 1 through 20 Memory Spaces");
|
|
529
|
+
const seen = /* @__PURE__ */ new Set();
|
|
530
|
+
const replacements = updates.map((update) => {
|
|
531
|
+
const id = validateMemoryBodyId(update.memoryBodyId);
|
|
532
|
+
if (seen.has(id)) throw new Error(`duplicate metadata update: ${id}`);
|
|
533
|
+
seen.add(id);
|
|
534
|
+
const index = this.bodies.findIndex((body) => body.id === id);
|
|
535
|
+
if (index < 0) throw new Error(`unknown memory body: ${id}`);
|
|
536
|
+
return {
|
|
537
|
+
index,
|
|
538
|
+
body: {
|
|
539
|
+
...this.bodies[index],
|
|
540
|
+
name: requiredText(update.title, "title", 48),
|
|
541
|
+
description: requiredText(update.description, "description", 200),
|
|
542
|
+
metadataSource: "ai",
|
|
543
|
+
updatedAt: this.nextTimestamp(this.bodies[index].updatedAt)
|
|
544
|
+
}
|
|
545
|
+
};
|
|
546
|
+
});
|
|
547
|
+
for (const replacement of replacements) this.bodies[replacement.index] = replacement.body;
|
|
548
|
+
this.save();
|
|
549
|
+
return replacements.map((replacement) => this.view(replacement.body));
|
|
550
|
+
}
|
|
551
|
+
async remove(id, signal) {
|
|
552
|
+
const body = this.get(id);
|
|
553
|
+
if (!this.isNative(body.provider.id)) {
|
|
554
|
+
this.bodies = this.bodies.filter((entry) => entry.id !== body.id);
|
|
555
|
+
this.save();
|
|
556
|
+
return body;
|
|
557
|
+
}
|
|
558
|
+
const nativeStoreIds = this.nativeStoreIds();
|
|
559
|
+
if (nativeStoreIds.includes(body.id) && nativeStoreIds.length === 1) throw new Error(`cannot delete the last Mnemon Store "${body.id}"; disable it for DSH or create another Memory Space first`);
|
|
560
|
+
const persistedStore = this.runner.persistedStore();
|
|
561
|
+
const commands = [];
|
|
562
|
+
let commandStore = persistedStore;
|
|
563
|
+
if (persistedStore === body.id) {
|
|
564
|
+
const nativeIds = new Set(nativeStoreIds);
|
|
565
|
+
const replacement = this.list().filter((candidate) => candidate.id !== body.id && nativeIds.has(candidate.id)).sort((left, right) => Number(right.active) - Number(left.active) || left.id.localeCompare(right.id))[0]?.id ?? nativeStoreIds.filter((candidate) => candidate !== body.id).sort()[0];
|
|
566
|
+
if (replacement === void 0) throw new Error(`cannot switch away from Mnemon Store "${body.id}" before deleting it`);
|
|
567
|
+
commandStore = replacement;
|
|
568
|
+
commands.push({
|
|
569
|
+
args: [
|
|
570
|
+
"store",
|
|
571
|
+
"set",
|
|
572
|
+
replacement
|
|
573
|
+
],
|
|
574
|
+
options: {
|
|
575
|
+
...signal === void 0 ? {} : { signal },
|
|
576
|
+
store: replacement
|
|
577
|
+
}
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
commands.push({
|
|
581
|
+
args: [
|
|
582
|
+
"store",
|
|
583
|
+
"remove",
|
|
584
|
+
body.id
|
|
585
|
+
],
|
|
586
|
+
options: {
|
|
587
|
+
...signal === void 0 ? {} : { signal },
|
|
588
|
+
store: commandStore
|
|
589
|
+
}
|
|
590
|
+
});
|
|
591
|
+
await this.runner.runTextBatch(commands);
|
|
592
|
+
this.bodies = this.bodies.filter((entry) => entry.id !== body.id);
|
|
593
|
+
this.save();
|
|
594
|
+
return body;
|
|
595
|
+
}
|
|
596
|
+
setActive(id, active) {
|
|
597
|
+
return this.update(id, { active });
|
|
598
|
+
}
|
|
599
|
+
/** Advance the safe catalog checkpoint after provider-backed content changes. */
|
|
600
|
+
touch(id) {
|
|
601
|
+
const normalized = validateMemoryBodyId(id);
|
|
602
|
+
const index = this.bodies.findIndex((body) => body.id === normalized);
|
|
603
|
+
if (index < 0) throw new Error(`unknown memory body: ${normalized}`);
|
|
604
|
+
const body = {
|
|
605
|
+
...this.bodies[index],
|
|
606
|
+
updatedAt: this.nextTimestamp(this.bodies[index].updatedAt)
|
|
607
|
+
};
|
|
608
|
+
this.bodies[index] = body;
|
|
609
|
+
this.save();
|
|
610
|
+
return this.view(body);
|
|
611
|
+
}
|
|
612
|
+
/** Refresh metadata after an atomic Pack import replaced the data component. */
|
|
613
|
+
reload() {
|
|
614
|
+
this.refreshing = true;
|
|
615
|
+
try {
|
|
616
|
+
this.bodies = [];
|
|
617
|
+
this.services = {};
|
|
618
|
+
this.serviceEnabled = {};
|
|
619
|
+
this.loadAndReconcile();
|
|
620
|
+
this.state.diskRevision = this.diskRevision();
|
|
621
|
+
} finally {
|
|
622
|
+
this.refreshing = false;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
diskRevision() {
|
|
626
|
+
if (!this.persistent) return "ephemeral";
|
|
627
|
+
return [this.registryPath, this.providerRegistryPath].map((path) => {
|
|
628
|
+
try {
|
|
629
|
+
const stat = statSync(path, { bigint: true });
|
|
630
|
+
return `${stat.ino}:${stat.mtimeNs}:${stat.size}`;
|
|
631
|
+
} catch (error) {
|
|
632
|
+
if (error.code === "ENOENT") return "missing";
|
|
633
|
+
throw error;
|
|
634
|
+
}
|
|
635
|
+
}).join("/");
|
|
636
|
+
}
|
|
637
|
+
/** Separate generations and legacy facades observe the same durable authority. */
|
|
638
|
+
refreshIfChanged() {
|
|
639
|
+
if (!this.refreshing && this.persistent && this.state.diskRevision !== this.diskRevision()) this.reload();
|
|
640
|
+
}
|
|
641
|
+
loadAndReconcile() {
|
|
642
|
+
let migratedSyntheticDefault = false;
|
|
643
|
+
let migratedProviderRegistry = false;
|
|
644
|
+
if (this.persistent && existsSync(this.registryPath)) try {
|
|
645
|
+
const parsed = JSON.parse(readFileSync(this.registryPath, "utf8"));
|
|
646
|
+
if ((parsed.version === NATIVE_REGISTRY_VERSION || parsed.version === 2) && Array.isArray(parsed.bodies)) {
|
|
647
|
+
migratedProviderRegistry = parsed.version === 2;
|
|
648
|
+
this.bodies = parsed.bodies.filter((body) => ID_PATTERN.test(body.id)).map((body) => {
|
|
649
|
+
const syntheticDefault = body.id === "default" && body.name === "默认记忆体" && body.description === "从现有 Mnemon Store 自动接入。";
|
|
650
|
+
migratedSyntheticDefault ||= syntheticDefault;
|
|
651
|
+
const providerId = "providerId" in body && storedProviderId(body.providerId) ? body.providerId : "mnemon-native";
|
|
652
|
+
if (!this.providerCatalog.has(providerId)) return {
|
|
653
|
+
id: body.id,
|
|
654
|
+
name: requiredText(body.name || body.id, "name", 100),
|
|
655
|
+
description: optionalText$1(body.description, "description", 1e3),
|
|
656
|
+
active: body.active === true,
|
|
657
|
+
providerId,
|
|
658
|
+
connection: storedProviderConnection("connection" in body ? body.connection : void 0),
|
|
659
|
+
createdAt: body.createdAt,
|
|
660
|
+
updatedAt: body.updatedAt
|
|
661
|
+
};
|
|
662
|
+
const placement = "placement" in body ? normalizePlacementDecision(body.placement, providerId, this.providerCatalog) : void 0;
|
|
663
|
+
const rawConnection = "connection" in body && body.connection != null ? body.connection : this.providerTypeId(providerId) === "openviking" && "openViking" in body && body.openViking != null ? body.openViking : void 0;
|
|
664
|
+
const split = this.isNative(providerId) ? void 0 : this.providerCatalog.split(providerId, rawConnection);
|
|
665
|
+
if (split !== void 0 && this.services[providerId] === void 0) {
|
|
666
|
+
this.services[providerId] = this.providerCatalog.normalizeService(providerId, split.service);
|
|
667
|
+
this.serviceEnabled[providerId] = true;
|
|
668
|
+
}
|
|
669
|
+
const connection = split === void 0 ? void 0 : this.providerCatalog.normalizeMemory(providerId, split.memory);
|
|
670
|
+
if (connection !== void 0) this.providerCatalog.normalize(providerId, {
|
|
671
|
+
...this.services[providerId],
|
|
672
|
+
...connection
|
|
673
|
+
});
|
|
674
|
+
return {
|
|
675
|
+
id: body.id,
|
|
676
|
+
name: requiredText(syntheticDefault ? body.id : body.name || body.id, "name", 100),
|
|
677
|
+
description: optionalText$1(syntheticDefault ? "Existing Mnemon Store discovered on disk." : body.description, "description", 1e3),
|
|
678
|
+
active: body.active === true,
|
|
679
|
+
providerId,
|
|
680
|
+
...placement === void 0 ? {} : { placement },
|
|
681
|
+
...connection === void 0 ? {} : { connection },
|
|
682
|
+
createdAt: body.createdAt,
|
|
683
|
+
updatedAt: body.updatedAt
|
|
684
|
+
};
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
} catch {
|
|
688
|
+
this.bodies = [];
|
|
689
|
+
}
|
|
690
|
+
if (this.persistent && existsSync(this.providerRegistryPath)) try {
|
|
691
|
+
const parsed = JSON.parse(readFileSync(this.providerRegistryPath, "utf8"));
|
|
692
|
+
if ((parsed.version === 3 || parsed.version === PROVIDER_REGISTRY_VERSION) && typeof parsed.services === "object" && parsed.services !== null) for (const [providerId, settings] of Object.entries(parsed.services)) {
|
|
693
|
+
if (!storedProviderId(providerId) || typeof settings !== "object" || settings === null) continue;
|
|
694
|
+
if (this.providerCatalog.has(providerId) && this.isNative(providerId)) continue;
|
|
695
|
+
this.services[providerId] = this.providerCatalog.has(providerId) ? this.providerCatalog.normalizeService(providerId, settings) : storedProviderConnection(settings);
|
|
696
|
+
this.serviceEnabled[providerId] = parsed.enabled === void 0 ? true : parsed.enabled[providerId] === true;
|
|
697
|
+
}
|
|
698
|
+
if ((parsed.version === 1 || parsed.version === 2 || parsed.version === 3 || parsed.version === PROVIDER_REGISTRY_VERSION) && Array.isArray(parsed.bodies)) {
|
|
699
|
+
migratedProviderRegistry ||= parsed.version !== PROVIDER_REGISTRY_VERSION;
|
|
700
|
+
const existingIds = new Set(this.bodies.map((body) => body.id));
|
|
701
|
+
this.bodies.push(...parsed.bodies.filter((body) => storedProviderId(body.providerId) && (!this.providerCatalog.has(body.providerId) || !this.isNative(body.providerId)) && ID_PATTERN.test(body.id) && !existingIds.has(body.id)).map((body) => {
|
|
702
|
+
const providerId = body.providerId;
|
|
703
|
+
if (!this.providerCatalog.has(providerId)) return {
|
|
704
|
+
id: body.id,
|
|
705
|
+
name: requiredText(body.name || body.id, "name", 100),
|
|
706
|
+
description: optionalText$1(body.description, "description", 1e3),
|
|
707
|
+
active: body.active === true,
|
|
708
|
+
providerId,
|
|
709
|
+
...typeof body.externalId !== "string" || body.externalId.trim() === "" ? {} : { externalId: body.externalId.trim() },
|
|
710
|
+
...body.metadataSource === "provider" || body.metadataSource === "manual" || body.metadataSource === "ai" ? { metadataSource: body.metadataSource } : {},
|
|
711
|
+
connection: storedProviderConnection(body.connection ?? body.openViking),
|
|
712
|
+
createdAt: body.createdAt,
|
|
713
|
+
updatedAt: body.updatedAt
|
|
714
|
+
};
|
|
715
|
+
const placement = normalizePlacementDecision(body.placement, providerId, this.providerCatalog);
|
|
716
|
+
const rawConnection = body.connection ?? (this.providerTypeId(providerId) === "openviking" && body.openViking !== void 0 ? body.openViking : void 0);
|
|
717
|
+
const split = parsed.version === 3 || parsed.version === PROVIDER_REGISTRY_VERSION ? {
|
|
718
|
+
service: {},
|
|
719
|
+
memory: rawConnection ?? {}
|
|
720
|
+
} : this.providerCatalog.split(providerId, rawConnection);
|
|
721
|
+
if (parsed.version !== 3 && parsed.version !== PROVIDER_REGISTRY_VERSION && this.services[providerId] === void 0) {
|
|
722
|
+
this.services[providerId] = this.providerCatalog.normalizeService(providerId, split.service);
|
|
723
|
+
this.serviceEnabled[providerId] = true;
|
|
724
|
+
}
|
|
725
|
+
const connection = this.providerCatalog.normalizeMemory(providerId, split.memory);
|
|
726
|
+
this.providerCatalog.normalize(providerId, {
|
|
727
|
+
...this.services[providerId],
|
|
728
|
+
...connection
|
|
729
|
+
});
|
|
730
|
+
return {
|
|
731
|
+
id: body.id,
|
|
732
|
+
name: requiredText(body.name || body.id, "name", 100),
|
|
733
|
+
description: optionalText$1(body.description, "description", 1e3),
|
|
734
|
+
active: body.active === true,
|
|
735
|
+
providerId,
|
|
736
|
+
...typeof body.externalId !== "string" || body.externalId.trim() === "" ? {} : { externalId: body.externalId.trim() },
|
|
737
|
+
...body.metadataSource === "provider" || body.metadataSource === "manual" || body.metadataSource === "ai" ? { metadataSource: body.metadataSource } : {},
|
|
738
|
+
...placement === void 0 ? {} : { placement },
|
|
739
|
+
connection,
|
|
740
|
+
createdAt: body.createdAt,
|
|
741
|
+
updatedAt: body.updatedAt
|
|
742
|
+
};
|
|
743
|
+
}));
|
|
744
|
+
}
|
|
745
|
+
} catch {}
|
|
746
|
+
const retainedBodies = this.bodies.filter((body) => !this.providerCatalog.has(body.providerId) || this.isNative(body.providerId) || this.providerServiceEnabled(body.providerId));
|
|
747
|
+
if (retainedBodies.length !== this.bodies.length) {
|
|
748
|
+
this.bodies = retainedBodies;
|
|
749
|
+
migratedProviderRegistry = true;
|
|
750
|
+
}
|
|
751
|
+
this.reconcileDiscoveredStores();
|
|
752
|
+
if (migratedSyntheticDefault || migratedProviderRegistry) this.save();
|
|
753
|
+
}
|
|
754
|
+
reconcileDiscoveredStores() {
|
|
755
|
+
if (!this.persistent || !existsSync(this.directory)) return;
|
|
756
|
+
const timestamp = this.now().toISOString();
|
|
757
|
+
const legacyActive = this.runner.effectiveStore();
|
|
758
|
+
let changed = false;
|
|
759
|
+
for (const entry of readdirSync(this.directory, { withFileTypes: true })) {
|
|
760
|
+
if (!entry.isDirectory() || !ID_PATTERN.test(entry.name) || !existsSync(join(this.directory, entry.name, "mnemon.db"))) continue;
|
|
761
|
+
if (this.bodies.some((body) => body.id === entry.name)) continue;
|
|
762
|
+
this.bodies.push({
|
|
763
|
+
id: entry.name,
|
|
764
|
+
name: entry.name,
|
|
765
|
+
description: "Existing Mnemon Store discovered on disk.",
|
|
766
|
+
active: this.bodies.length === 0 || entry.name === legacyActive,
|
|
767
|
+
providerId: "mnemon-native",
|
|
768
|
+
createdAt: timestamp,
|
|
769
|
+
updatedAt: timestamp
|
|
770
|
+
});
|
|
771
|
+
changed = true;
|
|
772
|
+
}
|
|
773
|
+
if (changed) this.save();
|
|
774
|
+
}
|
|
775
|
+
nativeStoreIds() {
|
|
776
|
+
if (!existsSync(this.directory)) return [];
|
|
777
|
+
return readdirSync(this.directory, { withFileTypes: true }).filter((entry) => entry.isDirectory() && ID_PATTERN.test(entry.name)).map((entry) => entry.name).sort();
|
|
778
|
+
}
|
|
779
|
+
nextTimestamp(previous, candidate = this.now().toISOString()) {
|
|
780
|
+
if (previous === void 0) return candidate;
|
|
781
|
+
const previousTime = Date.parse(previous);
|
|
782
|
+
const candidateTime = Date.parse(candidate);
|
|
783
|
+
if (!Number.isFinite(previousTime) || !Number.isFinite(candidateTime) || candidateTime > previousTime) return candidate;
|
|
784
|
+
return new Date(previousTime + 1).toISOString();
|
|
785
|
+
}
|
|
786
|
+
view(body) {
|
|
787
|
+
const descriptor = this.providerCatalog.descriptor(body.providerId);
|
|
788
|
+
const nativeProvider = this.isNative(body.providerId);
|
|
789
|
+
const connection = nativeProvider ? {} : this.connectionFor(body);
|
|
790
|
+
const effectivePublicConnection = this.providerCatalog.public(body.providerId, connection);
|
|
791
|
+
const publicConnection = nativeProvider ? effectivePublicConnection : this.providerCatalog.publicScoped(body.providerId, "memory", body.connection ?? {});
|
|
792
|
+
const location = nativeProvider ? join(this.directory, body.id, "mnemon.db") : providerDisplayLocation(descriptor, connection);
|
|
793
|
+
const provider = {
|
|
794
|
+
id: descriptor.id,
|
|
795
|
+
...descriptor.typeId === void 0 || descriptor.typeId === descriptor.id ? {} : { typeId: descriptor.typeId },
|
|
796
|
+
label: descriptor.label,
|
|
797
|
+
...descriptor.icon === void 0 ? {} : { icon: descriptor.icon },
|
|
798
|
+
origin: descriptor.origin,
|
|
799
|
+
kind: descriptor.kind,
|
|
800
|
+
location,
|
|
801
|
+
...typeof connection.targetUri === "string" && connection.targetUri !== "" ? { targetUri: connection.targetUri } : {},
|
|
802
|
+
...typeof connection.account === "string" && connection.account !== "" ? { account: connection.account } : {},
|
|
803
|
+
...typeof connection.user === "string" && connection.user !== "" ? { user: connection.user } : {},
|
|
804
|
+
...typeof connection.actorPeerId === "string" && connection.actorPeerId !== "" ? { actorPeerId: connection.actorPeerId } : {},
|
|
805
|
+
apiKeyConfigured: effectivePublicConnection.configuredSecrets.includes("apiKey"),
|
|
806
|
+
...publicConnection,
|
|
807
|
+
capabilities: descriptor.capabilities
|
|
808
|
+
};
|
|
809
|
+
const { providerId: _providerId, externalId: _externalId, metadataSource: _metadataSource, connection: _connection, openViking: _openViking, ...metadata } = body;
|
|
810
|
+
return {
|
|
811
|
+
...metadata,
|
|
812
|
+
dbPath: nativeProvider ? provider.location : "",
|
|
813
|
+
provider
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
save() {
|
|
817
|
+
if (!this.persistent) return;
|
|
818
|
+
mkdirSync(this.directory, {
|
|
819
|
+
recursive: true,
|
|
820
|
+
mode: 448
|
|
821
|
+
});
|
|
822
|
+
const nativeBodies = this.bodies.filter((body) => body.providerId === "mnemon-native" || this.providerCatalog.has(body.providerId) && this.isNative(body.providerId)).map(({ providerId: _providerId, connection: _connection, openViking: _openViking, ...body }) => body);
|
|
823
|
+
this.writeRegistry(this.registryPath, {
|
|
824
|
+
version: NATIVE_REGISTRY_VERSION,
|
|
825
|
+
bodies: nativeBodies
|
|
826
|
+
});
|
|
827
|
+
const providerBodies = this.bodies.filter((body) => body.providerId !== "mnemon-native" && (!this.providerCatalog.has(body.providerId) || !this.isNative(body.providerId)));
|
|
828
|
+
if (providerBodies.length === 0 && Object.keys(this.services).length === 0) {
|
|
829
|
+
rmSync(this.providerRegistryPath, { force: true });
|
|
830
|
+
this.state.diskRevision = this.diskRevision();
|
|
831
|
+
return;
|
|
832
|
+
}
|
|
833
|
+
mkdirSync(join(this.runner.effectiveDataDir(), "state"), {
|
|
834
|
+
recursive: true,
|
|
835
|
+
mode: 448
|
|
836
|
+
});
|
|
837
|
+
this.writeRegistry(this.providerRegistryPath, {
|
|
838
|
+
version: PROVIDER_REGISTRY_VERSION,
|
|
839
|
+
services: this.services,
|
|
840
|
+
enabled: this.serviceEnabled,
|
|
841
|
+
bodies: providerBodies
|
|
842
|
+
});
|
|
843
|
+
this.state.diskRevision = this.diskRevision();
|
|
844
|
+
}
|
|
845
|
+
writeRegistry(path, file) {
|
|
846
|
+
const temporary = join(dirname(path), `.${basename(path)}.${process.pid}.tmp`);
|
|
847
|
+
writeFileSync(temporary, `${JSON.stringify(file, null, 2)}\n`, {
|
|
848
|
+
encoding: "utf8",
|
|
849
|
+
mode: 384
|
|
850
|
+
});
|
|
851
|
+
renameSync(temporary, path);
|
|
852
|
+
}
|
|
853
|
+
};
|
|
854
|
+
//#endregion
|
|
855
|
+
//#region src/provider-placement.ts
|
|
856
|
+
const CAPABILITY_LABELS = {
|
|
857
|
+
graph: "typed graph",
|
|
858
|
+
entities: "entity index",
|
|
859
|
+
related: "related-memory traversal",
|
|
860
|
+
"exact-write": "exact writes",
|
|
861
|
+
link: "explicit links",
|
|
862
|
+
forget: "safe forget"
|
|
863
|
+
};
|
|
864
|
+
const CAPABILITIES = new Set(Object.keys(CAPABILITY_LABELS));
|
|
865
|
+
const PREFERENCES = /* @__PURE__ */ new Set([
|
|
866
|
+
"balanced",
|
|
867
|
+
"local-first",
|
|
868
|
+
"shared-first"
|
|
869
|
+
]);
|
|
870
|
+
function supports(candidate, capability) {
|
|
871
|
+
if (capability === "exact-write") return candidate.capabilities.writeMode === "exact";
|
|
872
|
+
return candidate.capabilities[capability];
|
|
873
|
+
}
|
|
874
|
+
function boundedPrompt(value) {
|
|
875
|
+
const normalized = value?.trim() ?? "";
|
|
876
|
+
if (normalized.length > 4e3) throw new Error("provider placement prompt is too long (max 4000 characters)");
|
|
877
|
+
return normalized;
|
|
878
|
+
}
|
|
879
|
+
function uniqueProviderIds(ids) {
|
|
880
|
+
if (ids === void 0) return void 0;
|
|
881
|
+
return [...new Set(ids)];
|
|
882
|
+
}
|
|
883
|
+
function prepareMemoryPlacement(request, candidates) {
|
|
884
|
+
if (request.mode !== "automatic") throw new Error(`unsupported provider placement mode: ${String(request.mode)}`);
|
|
885
|
+
const prompt = boundedPrompt(request.prompt);
|
|
886
|
+
const rules = request.rules ?? {};
|
|
887
|
+
const allowed = uniqueProviderIds(rules.allowedProviderIds);
|
|
888
|
+
const required = [...new Set(rules.requiredCapabilities ?? [])];
|
|
889
|
+
const candidateIds = new Set(candidates.map((candidate) => candidate.id));
|
|
890
|
+
for (const providerId of allowed ?? []) if (!candidateIds.has(providerId)) throw new Error(`unsupported memory provider in placement rules: ${String(providerId)}`);
|
|
891
|
+
if (rules.dataBoundary !== void 0 && rules.dataBoundary !== "allow-remote" && rules.dataBoundary !== "local-only") throw new Error(`unsupported data boundary: ${String(rules.dataBoundary)}`);
|
|
892
|
+
for (const capability of required) if (!CAPABILITIES.has(capability)) throw new Error(`unsupported required memory capability: ${String(capability)}`);
|
|
893
|
+
if (rules.preference !== void 0 && !PREFERENCES.has(rules.preference)) throw new Error(`unsupported provider placement preference: ${String(rules.preference)}`);
|
|
894
|
+
const appliedRules = [];
|
|
895
|
+
let eligible = candidates.filter((candidate) => candidate.configured);
|
|
896
|
+
if (allowed !== void 0) {
|
|
897
|
+
if (allowed.length === 0) throw new Error("automatic provider placement requires at least one allowed provider");
|
|
898
|
+
eligible = eligible.filter((candidate) => allowed.includes(candidate.id));
|
|
899
|
+
appliedRules.push(`allowed:${allowed.join(",")}`);
|
|
900
|
+
}
|
|
901
|
+
if (rules.dataBoundary === "local-only") {
|
|
902
|
+
eligible = eligible.filter((candidate) => candidate.kind === "local");
|
|
903
|
+
appliedRules.push("data-boundary:local-only");
|
|
904
|
+
}
|
|
905
|
+
for (const capability of required) {
|
|
906
|
+
eligible = eligible.filter((candidate) => supports(candidate, capability));
|
|
907
|
+
appliedRules.push(`requires:${capability}`);
|
|
908
|
+
}
|
|
909
|
+
const preference = rules.preference ?? "balanced";
|
|
910
|
+
appliedRules.push(`preference:${preference}`);
|
|
911
|
+
if (eligible.length === 0) {
|
|
912
|
+
const requirements = required.map((value) => CAPABILITY_LABELS[value]).join(", ");
|
|
913
|
+
throw new Error(`no configured memory provider satisfies the placement rules${requirements === "" ? "" : ` (${requirements})`}`);
|
|
914
|
+
}
|
|
915
|
+
const selectorBrief = [
|
|
916
|
+
`Soft preference: ${preference}.`,
|
|
917
|
+
"Eligible providers after host-enforced rules:",
|
|
918
|
+
...eligible.map((candidate) => {
|
|
919
|
+
const capabilities = [
|
|
920
|
+
...Object.entries(candidate.capabilities).filter(([key, value]) => typeof value === "boolean" && value).map(([key]) => key),
|
|
921
|
+
`writeMode=${candidate.capabilities.writeMode}`,
|
|
922
|
+
`deletionMode=${candidate.capabilities.deletionMode}`
|
|
923
|
+
];
|
|
924
|
+
return `- ${candidate.id} (${candidate.label}, ${candidate.kind}): ${candidate.summary} Capabilities: ${capabilities.join(", ")}.`;
|
|
925
|
+
})
|
|
926
|
+
].join("\n");
|
|
927
|
+
return {
|
|
928
|
+
prompt,
|
|
929
|
+
candidates: eligible,
|
|
930
|
+
appliedRules,
|
|
931
|
+
selectorBrief
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
function rulesOnlyPlacement(prepared, now = () => /* @__PURE__ */ new Date()) {
|
|
935
|
+
const candidate = prepared.candidates.length === 1 ? prepared.candidates[0] : void 0;
|
|
936
|
+
if (candidate === void 0) return void 0;
|
|
937
|
+
return {
|
|
938
|
+
mode: "automatic",
|
|
939
|
+
providerId: candidate.id,
|
|
940
|
+
decidedBy: "rules",
|
|
941
|
+
reason: `Only ${candidate.label} satisfies the configured placement rules.`,
|
|
942
|
+
confidence: "high",
|
|
943
|
+
candidateProviderIds: [candidate.id],
|
|
944
|
+
appliedRules: prepared.appliedRules,
|
|
945
|
+
decidedAt: now().toISOString()
|
|
946
|
+
};
|
|
947
|
+
}
|
|
948
|
+
function finalizeLlmPlacement(prepared, selection, delegation, now = () => /* @__PURE__ */ new Date()) {
|
|
949
|
+
const providerId = selection.providerId.trim();
|
|
950
|
+
if (!prepared.candidates.some((candidate) => candidate.id === providerId)) throw new Error(`memory placement model selected an ineligible provider: ${selection.providerId}`);
|
|
951
|
+
const reason = selection.reason.trim();
|
|
952
|
+
if (reason === "") throw new Error("memory placement model returned no reason");
|
|
953
|
+
if (reason.length > 1e3) throw new Error("memory placement reason is too long (max 1000 characters)");
|
|
954
|
+
const confidence = selection.confidence.trim();
|
|
955
|
+
if (confidence !== "high" && confidence !== "medium" && confidence !== "low") throw new Error(`memory placement model returned invalid confidence: ${selection.confidence}`);
|
|
956
|
+
return {
|
|
957
|
+
mode: "automatic",
|
|
958
|
+
providerId,
|
|
959
|
+
decidedBy: "llm",
|
|
960
|
+
reason,
|
|
961
|
+
confidence,
|
|
962
|
+
candidateProviderIds: prepared.candidates.map((candidate) => candidate.id),
|
|
963
|
+
appliedRules: prepared.appliedRules,
|
|
964
|
+
decidedAt: now().toISOString(),
|
|
965
|
+
runId: delegation.runId,
|
|
966
|
+
subagentProvider: delegation.provider
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
//#endregion
|
|
970
|
+
//#region src/search-tokens.ts
|
|
971
|
+
/** Memory Spaces lexical fallback; no Core dependency. */
|
|
972
|
+
/** Small deterministic tokenizer shared by local Document and Native recovery. */
|
|
973
|
+
function lexicalSearchTokens(value, maximum = 64) {
|
|
974
|
+
const normalized = value.normalize("NFKC").toLocaleLowerCase();
|
|
975
|
+
const tokens = [];
|
|
976
|
+
for (const segment of normalized.split(/(\p{Script=Han}+)/gu)) {
|
|
977
|
+
if (/^\p{Script=Han}+$/u.test(segment)) {
|
|
978
|
+
const characters = [...segment];
|
|
979
|
+
if (characters.length <= 2) tokens.push(segment);
|
|
980
|
+
else for (let index = 0; index < characters.length - 1; index += 1) tokens.push(`${characters[index]}${characters[index + 1]}`);
|
|
981
|
+
continue;
|
|
982
|
+
}
|
|
983
|
+
tokens.push(...(segment.match(/[\p{L}\p{N}_-]+/gu) ?? []).filter((token) => token.length >= 2));
|
|
984
|
+
}
|
|
985
|
+
return [...new Set(tokens)].slice(0, maximum);
|
|
986
|
+
}
|
|
987
|
+
function lexicalTokenMatchCount(value, tokens) {
|
|
988
|
+
const available = new Set(lexicalSearchTokens(value, 512));
|
|
989
|
+
return tokens.filter((token) => available.has(token)).length;
|
|
990
|
+
}
|
|
991
|
+
/** Require broader coverage only after a query is focused enough to support it. */
|
|
992
|
+
function lexicalRequiredMatchCount(tokens) {
|
|
993
|
+
if (tokens.length === 0) return 0;
|
|
994
|
+
if (tokens.length < 4) return 1;
|
|
995
|
+
return Math.max(2, Math.ceil(tokens.length / 4));
|
|
996
|
+
}
|
|
997
|
+
//#endregion
|
|
998
|
+
//#region src/recall-quality/policies.ts
|
|
999
|
+
function expandedLimit(context) {
|
|
1000
|
+
return Math.min(50, Math.max(context.requestedLimit, Math.ceil(context.requestedLimit * context.config.candidateMultiplier)));
|
|
1001
|
+
}
|
|
1002
|
+
function scoreDecision(candidate, context, lowScoreAction, nonPositiveAction) {
|
|
1003
|
+
const score = candidate.insight.score;
|
|
1004
|
+
if (score === void 0) return {
|
|
1005
|
+
action: "keep",
|
|
1006
|
+
tier: "unknown",
|
|
1007
|
+
reason: "unscored"
|
|
1008
|
+
};
|
|
1009
|
+
if (!Number.isFinite(score)) return {
|
|
1010
|
+
action: "drop",
|
|
1011
|
+
tier: "unknown",
|
|
1012
|
+
reason: "invalid-score"
|
|
1013
|
+
};
|
|
1014
|
+
if (candidate.scoreSemantics?.kind !== "normalized-relevance") return {
|
|
1015
|
+
action: "keep",
|
|
1016
|
+
tier: "unknown",
|
|
1017
|
+
reason: "unscaled-score"
|
|
1018
|
+
};
|
|
1019
|
+
if (score <= 0) return {
|
|
1020
|
+
action: nonPositiveAction,
|
|
1021
|
+
tier: "low",
|
|
1022
|
+
reason: "non-positive-score",
|
|
1023
|
+
normalizedScore: 0
|
|
1024
|
+
};
|
|
1025
|
+
if (score > 1) return {
|
|
1026
|
+
action: "keep",
|
|
1027
|
+
tier: "unknown",
|
|
1028
|
+
reason: "unscaled-score"
|
|
1029
|
+
};
|
|
1030
|
+
if (score < context.config.lowScoreThreshold) return {
|
|
1031
|
+
action: lowScoreAction,
|
|
1032
|
+
tier: "low",
|
|
1033
|
+
reason: "low-score",
|
|
1034
|
+
normalizedScore: score
|
|
1035
|
+
};
|
|
1036
|
+
if (score < context.config.highScoreThreshold) return {
|
|
1037
|
+
action: "keep",
|
|
1038
|
+
tier: "medium",
|
|
1039
|
+
reason: "medium-score",
|
|
1040
|
+
normalizedScore: score
|
|
1041
|
+
};
|
|
1042
|
+
return {
|
|
1043
|
+
action: "keep",
|
|
1044
|
+
tier: "high",
|
|
1045
|
+
reason: "high-score",
|
|
1046
|
+
normalizedScore: score
|
|
1047
|
+
};
|
|
1048
|
+
}
|
|
1049
|
+
function primarySelection(candidates, context) {
|
|
1050
|
+
return candidates.filter((candidate) => candidate.decision.action === "keep").slice(0, context.requestedLimit);
|
|
1051
|
+
}
|
|
1052
|
+
const STRICT_RECALL_QUALITY_POLICY = {
|
|
1053
|
+
id: "strict-v1",
|
|
1054
|
+
candidateLimit: expandedLimit,
|
|
1055
|
+
evaluate: (candidate, context) => scoreDecision(candidate, context, "drop", "drop"),
|
|
1056
|
+
select(candidates, context) {
|
|
1057
|
+
const kept = candidates.filter((candidate) => candidate.decision.action === "keep");
|
|
1058
|
+
return [
|
|
1059
|
+
...kept.filter((candidate) => candidate.decision.tier === "high"),
|
|
1060
|
+
...kept.filter((candidate) => candidate.decision.tier === "medium").slice(0, context.config.maxMediumResults),
|
|
1061
|
+
...kept.filter((candidate) => candidate.decision.tier === "unknown").slice(0, context.config.maxUnknownResults)
|
|
1062
|
+
].slice(0, context.requestedLimit);
|
|
1063
|
+
}
|
|
1064
|
+
};
|
|
1065
|
+
const BUILTIN_RECALL_QUALITY_POLICIES = [
|
|
1066
|
+
STRICT_RECALL_QUALITY_POLICY,
|
|
1067
|
+
{
|
|
1068
|
+
id: "balanced-v1",
|
|
1069
|
+
candidateLimit: expandedLimit,
|
|
1070
|
+
evaluate: (candidate, context) => scoreDecision(candidate, context, "keep", "drop"),
|
|
1071
|
+
select(candidates, context) {
|
|
1072
|
+
const kept = candidates.filter((candidate) => candidate.decision.action === "keep");
|
|
1073
|
+
return [...kept.filter((candidate) => candidate.decision.tier !== "low"), ...kept.filter((candidate) => candidate.decision.tier === "low")].slice(0, context.requestedLimit);
|
|
1074
|
+
}
|
|
1075
|
+
},
|
|
1076
|
+
{
|
|
1077
|
+
id: "exhaustive-v1",
|
|
1078
|
+
candidateLimit: expandedLimit,
|
|
1079
|
+
evaluate: (candidate, context) => scoreDecision(candidate, context, "keep", "keep"),
|
|
1080
|
+
select: primarySelection
|
|
1081
|
+
}
|
|
1082
|
+
];
|
|
1083
|
+
//#endregion
|
|
1084
|
+
//#region src/recall-quality/engine.ts
|
|
1085
|
+
const ACTIONS = /* @__PURE__ */ new Set(["keep", "drop"]);
|
|
1086
|
+
const TIERS = /* @__PURE__ */ new Set([
|
|
1087
|
+
"high",
|
|
1088
|
+
"medium",
|
|
1089
|
+
"low",
|
|
1090
|
+
"unknown"
|
|
1091
|
+
]);
|
|
1092
|
+
const REASONS = /* @__PURE__ */ new Set([
|
|
1093
|
+
"high-score",
|
|
1094
|
+
"medium-score",
|
|
1095
|
+
"low-score",
|
|
1096
|
+
"non-positive-score",
|
|
1097
|
+
"invalid-score",
|
|
1098
|
+
"unscaled-score",
|
|
1099
|
+
"unscored"
|
|
1100
|
+
]);
|
|
1101
|
+
function assertDecision(decision) {
|
|
1102
|
+
if (typeof decision !== "object" || decision === null || !ACTIONS.has(decision.action) || !TIERS.has(decision.tier) || !REASONS.has(decision.reason)) throw new Error("recall quality policy returned an invalid decision");
|
|
1103
|
+
if (decision.normalizedScore !== void 0 && (!Number.isFinite(decision.normalizedScore) || decision.normalizedScore < 0 || decision.normalizedScore > 1)) throw new Error("recall quality policy returned an invalid normalized score");
|
|
1104
|
+
}
|
|
1105
|
+
function runPolicy(policy, candidates, context) {
|
|
1106
|
+
const evaluated = candidates.map((candidate) => {
|
|
1107
|
+
const decision = policy.evaluate(candidate, context);
|
|
1108
|
+
assertDecision(decision);
|
|
1109
|
+
return {
|
|
1110
|
+
candidate,
|
|
1111
|
+
decision
|
|
1112
|
+
};
|
|
1113
|
+
});
|
|
1114
|
+
const eligible = new Set(evaluated.filter((candidate) => candidate.decision.action === "keep"));
|
|
1115
|
+
const selected = policy.select(evaluated, context);
|
|
1116
|
+
if (!Array.isArray(selected) || selected.length > context.requestedLimit) throw new Error("recall quality policy returned too many results");
|
|
1117
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1118
|
+
for (const candidate of selected) {
|
|
1119
|
+
if (!eligible.has(candidate) || seen.has(candidate)) throw new Error("recall quality policy selected an ineligible or duplicate result");
|
|
1120
|
+
seen.add(candidate);
|
|
1121
|
+
}
|
|
1122
|
+
return {
|
|
1123
|
+
evaluated,
|
|
1124
|
+
selected
|
|
1125
|
+
};
|
|
1126
|
+
}
|
|
1127
|
+
function prepareRecallQualityPolicy(policy, context, fallback = STRICT_RECALL_QUALITY_POLICY) {
|
|
1128
|
+
try {
|
|
1129
|
+
const candidateLimit = policy.candidateLimit(context);
|
|
1130
|
+
if (!Number.isInteger(candidateLimit) || candidateLimit < context.requestedLimit || candidateLimit > 50) throw new Error("invalid candidate limit");
|
|
1131
|
+
return {
|
|
1132
|
+
policy,
|
|
1133
|
+
candidateLimit
|
|
1134
|
+
};
|
|
1135
|
+
} catch {
|
|
1136
|
+
if (policy === fallback) throw new Error(`recall quality policy ${policy.id} returned an invalid candidate limit`);
|
|
1137
|
+
return {
|
|
1138
|
+
policy: fallback,
|
|
1139
|
+
candidateLimit: fallback.candidateLimit(context),
|
|
1140
|
+
fallbackFrom: policy.id
|
|
1141
|
+
};
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
function applyRecallQualityPolicy(prepared, candidates, context, fallback = STRICT_RECALL_QUALITY_POLICY) {
|
|
1145
|
+
try {
|
|
1146
|
+
return {
|
|
1147
|
+
policyId: prepared.policy.id,
|
|
1148
|
+
...runPolicy(prepared.policy, candidates, context),
|
|
1149
|
+
...prepared.fallbackFrom === void 0 ? {} : { fallbackFrom: prepared.fallbackFrom }
|
|
1150
|
+
};
|
|
1151
|
+
} catch {
|
|
1152
|
+
if (prepared.policy === fallback) throw new Error(`recall quality policy ${prepared.policy.id} failed`);
|
|
1153
|
+
return {
|
|
1154
|
+
policyId: fallback.id,
|
|
1155
|
+
fallbackFrom: prepared.policy.id,
|
|
1156
|
+
...runPolicy(fallback, candidates, context)
|
|
1157
|
+
};
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
//#endregion
|
|
1161
|
+
//#region src/recall-quality/registry.ts
|
|
1162
|
+
var RecallQualityPolicyRegistry = class {
|
|
1163
|
+
policies = /* @__PURE__ */ new Map();
|
|
1164
|
+
constructor(policies = BUILTIN_RECALL_QUALITY_POLICIES) {
|
|
1165
|
+
for (const policy of policies) this.register(policy);
|
|
1166
|
+
}
|
|
1167
|
+
register(policy) {
|
|
1168
|
+
const id = policy.id.trim();
|
|
1169
|
+
if (!/^[a-z][a-z0-9-]{0,63}$/u.test(id)) throw new Error("recall quality policy id must match [a-z][a-z0-9-]{0,63}");
|
|
1170
|
+
if (this.policies.has(id)) throw new Error(`recall quality policy is already registered: ${id}`);
|
|
1171
|
+
this.policies.set(id, policy);
|
|
1172
|
+
return () => {
|
|
1173
|
+
if (this.policies.get(id) === policy) this.policies.delete(id);
|
|
1174
|
+
};
|
|
1175
|
+
}
|
|
1176
|
+
resolve(id) {
|
|
1177
|
+
const policy = this.policies.get(id);
|
|
1178
|
+
if (policy === void 0) throw new Error(`unknown recall quality policy: ${id}`);
|
|
1179
|
+
return policy;
|
|
1180
|
+
}
|
|
1181
|
+
ids() {
|
|
1182
|
+
return [...this.policies.keys()];
|
|
1183
|
+
}
|
|
1184
|
+
};
|
|
1185
|
+
const recallQualityPolicies = new RecallQualityPolicyRegistry();
|
|
1186
|
+
//#endregion
|
|
1187
|
+
//#region src/service.ts
|
|
1188
|
+
/**
|
|
1189
|
+
* Providers whose native search is a single bounded request while their browse
|
|
1190
|
+
* projection fans out to multiple resources or collections. Prefer search for
|
|
1191
|
+
* metadata sampling so AI maintenance never pays for a detailed projection.
|
|
1192
|
+
*/
|
|
1193
|
+
const METADATA_SEARCH_FIRST_PROVIDERS = /* @__PURE__ */ new Set([
|
|
1194
|
+
"openviking",
|
|
1195
|
+
"supermemory",
|
|
1196
|
+
"byterover"
|
|
1197
|
+
]);
|
|
1198
|
+
function record(value) {
|
|
1199
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
1200
|
+
}
|
|
1201
|
+
function text(value) {
|
|
1202
|
+
return typeof value === "string" ? value : void 0;
|
|
1203
|
+
}
|
|
1204
|
+
function number(value) {
|
|
1205
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
1206
|
+
}
|
|
1207
|
+
function readSource(body, mode, status, itemCount, options = {}) {
|
|
1208
|
+
return {
|
|
1209
|
+
memoryBodyId: body.id,
|
|
1210
|
+
memoryBodyName: body.name,
|
|
1211
|
+
providerId: body.provider.id,
|
|
1212
|
+
providerLabel: body.provider.label,
|
|
1213
|
+
mode,
|
|
1214
|
+
status,
|
|
1215
|
+
itemCount,
|
|
1216
|
+
...options
|
|
1217
|
+
};
|
|
1218
|
+
}
|
|
1219
|
+
const MAX_EXACT_SEARCH_ANCHORS = 8;
|
|
1220
|
+
const EXACT_SEARCH_ANCHOR = /(?<!\d)\d{4}-\d{1,2}-\d{1,2}(?!\d)|(?<![A-Za-z0-9])[A-Za-z][A-Za-z0-9]*(?:[-_][A-Za-z0-9]+)+(?![A-Za-z0-9])|(?<!\d)\d+(?:[.,]\d+)?\s*(?:[%%]|percent(?:age)?)(?![A-Za-z])|百分之\s*\d+(?:[.,]\d+)?|(?<!\d)\d{1,2}:\d{2}(?!\d)|(?<![A-Za-z0-9])v?\d+\.\d+(?:\.\d+)*(?![A-Za-z0-9])|(?<![\d.])\d+(?![\d.%%:-])/giu;
|
|
1221
|
+
function normalizedExactText(value) {
|
|
1222
|
+
return value.normalize("NFKC").toLocaleLowerCase().replace(/百分之\s*(\d+(?:[.,]\d+)?)/gu, "$1%").replace(/(\d+(?:[.,]\d+)?)\s*percent(?:age)?/giu, "$1%").replace(/\s+/gu, "");
|
|
1223
|
+
}
|
|
1224
|
+
/**
|
|
1225
|
+
* Extract only high-information lexical values that a semantic paraphrase
|
|
1226
|
+
* should not be allowed to erase. This is a deterministic fallback inside an
|
|
1227
|
+
* already-authorized search, not another Recall trigger or model decision.
|
|
1228
|
+
*/
|
|
1229
|
+
function exactSearchAnchorPlan(query) {
|
|
1230
|
+
const anchors = [...new Set([...query.matchAll(EXACT_SEARCH_ANCHOR)].map((match) => normalizedExactText(match[0])))].slice(0, MAX_EXACT_SEARCH_ANCHORS);
|
|
1231
|
+
if (anchors.length < 2) return void 0;
|
|
1232
|
+
return {
|
|
1233
|
+
kind: "exact",
|
|
1234
|
+
terms: anchors,
|
|
1235
|
+
query: anchors.join(" "),
|
|
1236
|
+
requiredMatches: Math.min(4, anchors.length)
|
|
1237
|
+
};
|
|
1238
|
+
}
|
|
1239
|
+
function lexicalSearchRecoveryPlan(query) {
|
|
1240
|
+
const tokens = lexicalSearchTokens(query, 32);
|
|
1241
|
+
if (tokens.length < 4) return void 0;
|
|
1242
|
+
return {
|
|
1243
|
+
kind: "lexical",
|
|
1244
|
+
terms: tokens,
|
|
1245
|
+
query,
|
|
1246
|
+
requiredMatches: lexicalRequiredMatchCount(tokens)
|
|
1247
|
+
};
|
|
1248
|
+
}
|
|
1249
|
+
function recoveryMatchCount(content, plan) {
|
|
1250
|
+
if (plan.kind === "lexical") return lexicalTokenMatchCount(content, plan.terms);
|
|
1251
|
+
const normalized = normalizedExactText(content);
|
|
1252
|
+
return plan.terms.filter((anchor) => normalized.includes(anchor)).length;
|
|
1253
|
+
}
|
|
1254
|
+
function mergeRecoveryResults(original, recovered, plan, limit) {
|
|
1255
|
+
const admitted = recovered.map((insight) => ({
|
|
1256
|
+
insight,
|
|
1257
|
+
matches: recoveryMatchCount(insight.content, plan)
|
|
1258
|
+
})).filter((candidate) => candidate.matches >= plan.requiredMatches).sort((left, right) => right.matches - left.matches || (right.insight.score ?? 0) - (left.insight.score ?? 0)).map((candidate) => candidate.insight);
|
|
1259
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1260
|
+
return [...admitted, ...original].filter((insight) => {
|
|
1261
|
+
if (seen.has(insight.id)) return false;
|
|
1262
|
+
seen.add(insight.id);
|
|
1263
|
+
return true;
|
|
1264
|
+
}).slice(0, limit);
|
|
1265
|
+
}
|
|
1266
|
+
/** Put query-covering evidence first before the smaller model envelope runs. */
|
|
1267
|
+
function prioritizeRecoveryEvidence(selected, plan) {
|
|
1268
|
+
return selected.map((entry, index) => ({
|
|
1269
|
+
entry,
|
|
1270
|
+
index,
|
|
1271
|
+
matches: recoveryMatchCount(entry.candidate.insight.content, plan)
|
|
1272
|
+
})).sort((left, right) => {
|
|
1273
|
+
const leftAdmitted = left.matches >= plan.requiredMatches;
|
|
1274
|
+
const rightAdmitted = right.matches >= plan.requiredMatches;
|
|
1275
|
+
if (leftAdmitted !== rightAdmitted) return rightAdmitted ? 1 : -1;
|
|
1276
|
+
if (leftAdmitted && left.matches !== right.matches) return right.matches - left.matches;
|
|
1277
|
+
return left.index - right.index;
|
|
1278
|
+
}).map((candidate) => candidate.entry);
|
|
1279
|
+
}
|
|
1280
|
+
function insightColor(category) {
|
|
1281
|
+
if (category === "preference") return "#9b59b6";
|
|
1282
|
+
if (category === "decision") return "#e74c3c";
|
|
1283
|
+
if (category === "fact") return "#3498db";
|
|
1284
|
+
if (category === "insight") return "#2ecc71";
|
|
1285
|
+
if (category === "context") return "#f39c12";
|
|
1286
|
+
return "#6574d9";
|
|
1287
|
+
}
|
|
1288
|
+
function boundedInteger(value, fallback, min, max) {
|
|
1289
|
+
if (value === void 0) return fallback;
|
|
1290
|
+
if (!Number.isInteger(value) || value < min || value > max) throw new Error(`value must be an integer within ${min}..${max}`);
|
|
1291
|
+
return value;
|
|
1292
|
+
}
|
|
1293
|
+
function required(value, label, max) {
|
|
1294
|
+
const normalized = value.trim();
|
|
1295
|
+
if (normalized === "") throw new Error(`${label} is required`);
|
|
1296
|
+
if (normalized.length > max) throw new Error(`${label} is too long (max ${max} characters)`);
|
|
1297
|
+
return normalized;
|
|
1298
|
+
}
|
|
1299
|
+
function allowed(value, values, label) {
|
|
1300
|
+
if (value !== void 0 && !values.includes(value)) throw new Error(`${label} must be one of: ${values.join(", ")}`);
|
|
1301
|
+
return value;
|
|
1302
|
+
}
|
|
1303
|
+
function commaList(values, label, limit) {
|
|
1304
|
+
if (values === void 0) return void 0;
|
|
1305
|
+
const normalized = values.map((value) => value.trim()).filter((value) => value !== "");
|
|
1306
|
+
if (normalized.length > limit) throw new Error(`${label} accepts at most ${limit} values`);
|
|
1307
|
+
if (normalized.some((value) => value.includes(","))) throw new Error(`${label} values cannot contain commas`);
|
|
1308
|
+
return normalized.length === 0 ? void 0 : normalized.join(",");
|
|
1309
|
+
}
|
|
1310
|
+
const COMMITTED_MUTATION_STATES = /* @__PURE__ */ new Set([
|
|
1311
|
+
"added",
|
|
1312
|
+
"committed",
|
|
1313
|
+
"completed",
|
|
1314
|
+
"created",
|
|
1315
|
+
"deleted",
|
|
1316
|
+
"forgotten",
|
|
1317
|
+
"imported",
|
|
1318
|
+
"invalidated",
|
|
1319
|
+
"linked",
|
|
1320
|
+
"merged",
|
|
1321
|
+
"removed",
|
|
1322
|
+
"replaced",
|
|
1323
|
+
"stored",
|
|
1324
|
+
"succeeded",
|
|
1325
|
+
"success",
|
|
1326
|
+
"updated"
|
|
1327
|
+
]);
|
|
1328
|
+
const PENDING_MUTATION_STATES = /* @__PURE__ */ new Set([
|
|
1329
|
+
"accepted",
|
|
1330
|
+
"pending",
|
|
1331
|
+
"processing",
|
|
1332
|
+
"queued",
|
|
1333
|
+
"running"
|
|
1334
|
+
]);
|
|
1335
|
+
const FAILED_MUTATION_STATES = /* @__PURE__ */ new Set([
|
|
1336
|
+
"canceled",
|
|
1337
|
+
"cancelled",
|
|
1338
|
+
"error",
|
|
1339
|
+
"failed"
|
|
1340
|
+
]);
|
|
1341
|
+
const COMMITTED_MUTATION_COUNTS = [
|
|
1342
|
+
"created",
|
|
1343
|
+
"deleted",
|
|
1344
|
+
"edges_inserted",
|
|
1345
|
+
"imported",
|
|
1346
|
+
"removed",
|
|
1347
|
+
"stored",
|
|
1348
|
+
"updated"
|
|
1349
|
+
];
|
|
1350
|
+
/** Source-private translation of Provider receipts; Core does not interpret Provider payloads. */
|
|
1351
|
+
function mutationResultCompletion(result) {
|
|
1352
|
+
if (typeof result !== "object" || result === null || Array.isArray(result)) return "unknown";
|
|
1353
|
+
const value = result;
|
|
1354
|
+
const states = [
|
|
1355
|
+
value.action,
|
|
1356
|
+
value.status,
|
|
1357
|
+
value.state
|
|
1358
|
+
].filter((entry) => typeof entry === "string").map((entry) => entry.trim().toLocaleLowerCase());
|
|
1359
|
+
const changed = COMMITTED_MUTATION_COUNTS.some((key) => typeof value[key] === "number" && Number.isFinite(value[key]) && value[key] > 0);
|
|
1360
|
+
const errors = Array.isArray(value.errors) ? value.errors.length > 0 : typeof value.errors === "number" && value.errors > 0;
|
|
1361
|
+
if (states.includes("partial") || errors && changed) return "partial";
|
|
1362
|
+
if (value.success === false || value.ok === false || errors || states.some((state) => FAILED_MUTATION_STATES.has(state))) return "failed";
|
|
1363
|
+
if (states.includes("candidate")) return "candidate";
|
|
1364
|
+
if (states.some((state) => PENDING_MUTATION_STATES.has(state))) return "accepted";
|
|
1365
|
+
if (value.committed === false || value.durable === false || states.includes("skipped")) return "unknown";
|
|
1366
|
+
if (value.committed === true || value.durable === true || states.some((state) => COMMITTED_MUTATION_STATES.has(state)) || changed) return "committed";
|
|
1367
|
+
return "unknown";
|
|
1368
|
+
}
|
|
1369
|
+
function mutationResultCommitted(result) {
|
|
1370
|
+
return mutationResultCompletion(result) === "committed";
|
|
1371
|
+
}
|
|
1372
|
+
var MemorySpacesService = class {
|
|
1373
|
+
runner;
|
|
1374
|
+
config;
|
|
1375
|
+
recallQualityPolicyRegistry;
|
|
1376
|
+
providerCatalog;
|
|
1377
|
+
memoryBodies;
|
|
1378
|
+
providers;
|
|
1379
|
+
recallQualityPolicy;
|
|
1380
|
+
bodiesInFlight;
|
|
1381
|
+
providersDisposed = false;
|
|
1382
|
+
providerTypeId(providerId) {
|
|
1383
|
+
const catalog = this.providerCatalog;
|
|
1384
|
+
if (!catalog.has(providerId)) return providerId;
|
|
1385
|
+
const descriptor = catalog.descriptor(providerId);
|
|
1386
|
+
return descriptor.typeId ?? descriptor.id;
|
|
1387
|
+
}
|
|
1388
|
+
isNativeProvider(providerId) {
|
|
1389
|
+
return this.providerTypeId(providerId) === "mnemon-native";
|
|
1390
|
+
}
|
|
1391
|
+
isNativeBody(body) {
|
|
1392
|
+
return (body.provider.typeId ?? body.provider.id) === "mnemon-native";
|
|
1393
|
+
}
|
|
1394
|
+
constructor(runner, config, memoryBodies, recallQualityPolicyRegistry = recallQualityPolicies, providerAdapterRegistry = new MemoryProviderAdapterRegistry(), providerCatalog = EMPTY_MEMORY_PROVIDER_CATALOG) {
|
|
1395
|
+
this.runner = runner;
|
|
1396
|
+
this.config = config;
|
|
1397
|
+
this.recallQualityPolicyRegistry = recallQualityPolicyRegistry;
|
|
1398
|
+
this.providerCatalog = providerCatalog;
|
|
1399
|
+
this.memoryBodies = memoryBodies === void 0 ? new MemoryBodyRegistry(runner, true, () => /* @__PURE__ */ new Date(), providerCatalog) : providerCatalog === EMPTY_MEMORY_PROVIDER_CATALOG ? memoryBodies : memoryBodies.withProviderCatalog(providerCatalog);
|
|
1400
|
+
this.recallQualityPolicy = recallQualityPolicyRegistry.resolve(config.recallQuality.policy);
|
|
1401
|
+
this.providers = providerAdapterRegistry.create({
|
|
1402
|
+
memoryBodies: this.memoryBodies,
|
|
1403
|
+
config: this.config,
|
|
1404
|
+
nativeRunner: this.runner
|
|
1405
|
+
});
|
|
1406
|
+
}
|
|
1407
|
+
/** Release clients owned by one composable Memory Spaces generation. */
|
|
1408
|
+
async dispose() {
|
|
1409
|
+
if (this.providersDisposed) return;
|
|
1410
|
+
this.providersDisposed = true;
|
|
1411
|
+
const failures = [];
|
|
1412
|
+
for (const provider of [...this.providers.values()].reverse()) try {
|
|
1413
|
+
await provider.dispose?.();
|
|
1414
|
+
} catch (error) {
|
|
1415
|
+
failures.push(error);
|
|
1416
|
+
}
|
|
1417
|
+
this.providers.clear();
|
|
1418
|
+
if (failures.length > 0) throw new AggregateError(failures, "Memory Space Provider disposal failed");
|
|
1419
|
+
}
|
|
1420
|
+
async bodies(signal) {
|
|
1421
|
+
if (signal !== void 0) return this.collectBodies(signal);
|
|
1422
|
+
if (this.bodiesInFlight !== void 0) return this.bodiesInFlight;
|
|
1423
|
+
const pending = this.collectBodies();
|
|
1424
|
+
this.bodiesInFlight = pending;
|
|
1425
|
+
try {
|
|
1426
|
+
return await pending;
|
|
1427
|
+
} finally {
|
|
1428
|
+
if (this.bodiesInFlight === pending) this.bodiesInFlight = void 0;
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
/** Coalesce simultaneous Status/Memory-page probes without caching mutations. */
|
|
1432
|
+
async collectBodies(signal) {
|
|
1433
|
+
const directory = this.bodyDirectory();
|
|
1434
|
+
const items = await Promise.all(directory.items.map(async (body) => {
|
|
1435
|
+
let status;
|
|
1436
|
+
if (!(body.providerEnabled !== false)) status = {
|
|
1437
|
+
healthy: false,
|
|
1438
|
+
error: `${body.provider.label} is disabled in Settings`
|
|
1439
|
+
};
|
|
1440
|
+
else try {
|
|
1441
|
+
status = await this.providerFor(body).status(body, signal);
|
|
1442
|
+
} catch (error) {
|
|
1443
|
+
status = {
|
|
1444
|
+
healthy: false,
|
|
1445
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1446
|
+
};
|
|
1447
|
+
}
|
|
1448
|
+
const { statusLoading: _statusLoading, ...metadata } = body;
|
|
1449
|
+
return {
|
|
1450
|
+
...metadata,
|
|
1451
|
+
...status
|
|
1452
|
+
};
|
|
1453
|
+
}));
|
|
1454
|
+
return {
|
|
1455
|
+
...directory,
|
|
1456
|
+
items,
|
|
1457
|
+
activeCount: items.filter((body) => body.active && body.providerEnabled !== false).length,
|
|
1458
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1459
|
+
};
|
|
1460
|
+
}
|
|
1461
|
+
/** Return the control-plane directory without waiting for provider I/O. */
|
|
1462
|
+
bodyDirectory() {
|
|
1463
|
+
const mnemonDefaultStore = this.runner.persistedStore();
|
|
1464
|
+
const all = this.memoryBodies.list();
|
|
1465
|
+
const enabled = new Set(this.memoryBodies.providerServices().items.filter((service) => service.enabled).map((service) => service.providerId));
|
|
1466
|
+
const items = all.map((body) => {
|
|
1467
|
+
const nativeProvider = this.isNativeBody(body);
|
|
1468
|
+
const providerEnabled = nativeProvider || enabled.has(body.provider.id);
|
|
1469
|
+
return {
|
|
1470
|
+
...body,
|
|
1471
|
+
providerEnabled,
|
|
1472
|
+
mnemonDefault: nativeProvider && body.id === mnemonDefaultStore,
|
|
1473
|
+
healthy: false,
|
|
1474
|
+
statusLoading: true
|
|
1475
|
+
};
|
|
1476
|
+
});
|
|
1477
|
+
return {
|
|
1478
|
+
items,
|
|
1479
|
+
providers: this.providerCatalog.providers.map((provider) => ({
|
|
1480
|
+
...provider,
|
|
1481
|
+
serviceConfigured: (provider.typeId ?? provider.id) === "mnemon-native" || enabled.has(provider.id)
|
|
1482
|
+
})),
|
|
1483
|
+
persistenceStrategy: {
|
|
1484
|
+
mode: this.config.persistenceStrategy.mode,
|
|
1485
|
+
providerId: this.config.persistenceStrategy.providerId,
|
|
1486
|
+
prompt: this.config.persistenceStrategy.prompt,
|
|
1487
|
+
rules: { ...this.config.persistenceStrategy.rules }
|
|
1488
|
+
},
|
|
1489
|
+
total: items.length,
|
|
1490
|
+
activeCount: items.filter((body) => body.active && body.providerEnabled !== false).length,
|
|
1491
|
+
directory: this.memoryBodies.directory,
|
|
1492
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1493
|
+
};
|
|
1494
|
+
}
|
|
1495
|
+
/** Stable, secret-free checkpoint for the projected Memory Space authority. */
|
|
1496
|
+
memoryRevision() {
|
|
1497
|
+
return this.memoryState().revision;
|
|
1498
|
+
}
|
|
1499
|
+
/** One local metadata observation supplies membership and its revision. */
|
|
1500
|
+
memoryState() {
|
|
1501
|
+
const all = this.memoryBodies.list();
|
|
1502
|
+
const serviceItems = this.memoryBodies.providerServices().items;
|
|
1503
|
+
const enabled = new Set(serviceItems.filter((service) => service.enabled).map((service) => service.providerId));
|
|
1504
|
+
const active = all.filter((body) => body.active && (this.isNativeBody(body) || enabled.has(body.provider.id))).sort((left, right) => left.id.localeCompare(right.id));
|
|
1505
|
+
const bodies = [...all].sort((left, right) => left.id.localeCompare(right.id)).map((body) => ({
|
|
1506
|
+
id: body.id,
|
|
1507
|
+
name: body.name,
|
|
1508
|
+
description: body.description,
|
|
1509
|
+
active: body.active,
|
|
1510
|
+
providerId: body.provider.id,
|
|
1511
|
+
updatedAt: body.updatedAt,
|
|
1512
|
+
capabilities: body.provider.capabilities
|
|
1513
|
+
}));
|
|
1514
|
+
const services = serviceItems.map((service) => ({
|
|
1515
|
+
providerId: service.providerId,
|
|
1516
|
+
enabled: service.enabled,
|
|
1517
|
+
configured: service.configured
|
|
1518
|
+
})).sort((left, right) => left.providerId.localeCompare(right.providerId));
|
|
1519
|
+
return {
|
|
1520
|
+
all,
|
|
1521
|
+
active,
|
|
1522
|
+
revision: createHash("sha256").update(JSON.stringify({
|
|
1523
|
+
bodies,
|
|
1524
|
+
services
|
|
1525
|
+
})).digest("hex")
|
|
1526
|
+
};
|
|
1527
|
+
}
|
|
1528
|
+
/** Return a usable system snapshot without waiting for any Provider I/O. */
|
|
1529
|
+
statusSummary() {
|
|
1530
|
+
const catalog = this.bodyDirectory();
|
|
1531
|
+
const dshActiveStores = catalog.items.filter((body) => body.active && body.providerEnabled !== false).map((body) => body.id);
|
|
1532
|
+
const providerServices = this.memoryBodies.providerServices().items.map((service) => {
|
|
1533
|
+
const descriptor = this.providerCatalog.descriptor(service.providerId);
|
|
1534
|
+
const bodies = catalog.items.filter((body) => body.provider.id === service.providerId);
|
|
1535
|
+
const activeBodies = bodies.filter((body) => body.active && body.providerEnabled !== false);
|
|
1536
|
+
return {
|
|
1537
|
+
providerId: service.providerId,
|
|
1538
|
+
label: descriptor.label,
|
|
1539
|
+
...descriptor.icon === void 0 ? {} : { icon: descriptor.icon },
|
|
1540
|
+
enabled: service.enabled,
|
|
1541
|
+
configured: service.configured,
|
|
1542
|
+
status: !service.enabled ? "disabled" : "idle",
|
|
1543
|
+
memoryBodyCount: bodies.length,
|
|
1544
|
+
activeMemoryBodyCount: activeBodies.length
|
|
1545
|
+
};
|
|
1546
|
+
});
|
|
1547
|
+
return {
|
|
1548
|
+
healthy: true,
|
|
1549
|
+
cliPath: this.runner.command,
|
|
1550
|
+
commandFound: this.runner.commandFound,
|
|
1551
|
+
dataDir: this.runner.effectiveDataDir(),
|
|
1552
|
+
store: dshActiveStores.join(", ") || "none",
|
|
1553
|
+
mnemonDefaultStore: this.runner.persistedStore(),
|
|
1554
|
+
dshActiveStores,
|
|
1555
|
+
writeEnabled: this.config.writeEnabled,
|
|
1556
|
+
timeoutMs: this.config.timeoutMs,
|
|
1557
|
+
defaultRecallLimit: this.config.defaultRecallLimit,
|
|
1558
|
+
recallQuality: this.config.recallQuality,
|
|
1559
|
+
memoryBodyDirectory: catalog.directory,
|
|
1560
|
+
memoryBodies: catalog.items,
|
|
1561
|
+
providerServices
|
|
1562
|
+
};
|
|
1563
|
+
}
|
|
1564
|
+
/** Probe the effective Mnemon embedding runtime and its default Store coverage. */
|
|
1565
|
+
async embeddingStatus(signal) {
|
|
1566
|
+
const output = record(await this.runner.runJson(["embed", "--status"], signal === void 0 ? {} : { signal }));
|
|
1567
|
+
const available = output?.embedding_available ?? output?.ollama_available;
|
|
1568
|
+
const model = text(output?.model)?.trim();
|
|
1569
|
+
const protocol = text(output?.protocol)?.trim();
|
|
1570
|
+
const totalInsights = number(output?.total_insights);
|
|
1571
|
+
const embedded = number(output?.embedded);
|
|
1572
|
+
const coverage = text(output?.coverage)?.trim();
|
|
1573
|
+
if (typeof available !== "boolean" || model === void 0 || model === "" || model.length > 200 || /[\u0000-\u001f\u007f]/u.test(model) || !Number.isInteger(totalInsights) || totalInsights < 0 || !Number.isInteger(embedded) || embedded < 0 || embedded > totalInsights || coverage === void 0 || !/^(?:100|\d{1,2})%$/u.test(coverage)) throw new Error("mnemon embed --status returned an invalid response");
|
|
1574
|
+
return {
|
|
1575
|
+
available,
|
|
1576
|
+
model,
|
|
1577
|
+
totalInsights,
|
|
1578
|
+
embedded,
|
|
1579
|
+
coverage,
|
|
1580
|
+
...protocol !== void 0 && protocol.length <= 32 && !/[\u0000-\u001f\u007f]/u.test(protocol) ? { protocol } : {}
|
|
1581
|
+
};
|
|
1582
|
+
}
|
|
1583
|
+
async status(signal) {
|
|
1584
|
+
const hasNativeBody = this.memoryBodies.list().some((body) => this.isNativeBody(body));
|
|
1585
|
+
let versionError;
|
|
1586
|
+
const [catalog, rawVersion] = await Promise.all([this.bodies(signal), hasNativeBody ? this.runner.runText(["--version"], signal === void 0 ? { globalFlags: false } : {
|
|
1587
|
+
signal,
|
|
1588
|
+
globalFlags: false
|
|
1589
|
+
}).catch((error) => {
|
|
1590
|
+
versionError = error;
|
|
1591
|
+
}) : Promise.resolve(void 0)]);
|
|
1592
|
+
const active = catalog.items.filter((body) => body.active && body.providerEnabled !== false);
|
|
1593
|
+
const dshActiveStores = active.map((body) => body.id);
|
|
1594
|
+
const providerServices = this.memoryBodies.providerServices().items.map((service) => {
|
|
1595
|
+
const descriptor = this.providerCatalog.descriptor(service.providerId);
|
|
1596
|
+
const bodies = catalog.items.filter((body) => body.provider.id === service.providerId);
|
|
1597
|
+
const activeBodies = bodies.filter((body) => body.active && body.providerEnabled !== false);
|
|
1598
|
+
const failed = activeBodies.filter((body) => !body.healthy);
|
|
1599
|
+
const status = !service.enabled ? "disabled" : activeBodies.length === 0 ? "idle" : failed.length === 0 ? "healthy" : "unhealthy";
|
|
1600
|
+
return {
|
|
1601
|
+
providerId: service.providerId,
|
|
1602
|
+
label: descriptor.label,
|
|
1603
|
+
...descriptor.icon === void 0 ? {} : { icon: descriptor.icon },
|
|
1604
|
+
enabled: service.enabled,
|
|
1605
|
+
configured: service.configured,
|
|
1606
|
+
status,
|
|
1607
|
+
memoryBodyCount: bodies.length,
|
|
1608
|
+
activeMemoryBodyCount: activeBodies.length,
|
|
1609
|
+
...failed.length === 0 ? {} : { error: failed.map((body) => `${body.name}: ${body.error ?? "unavailable"}`).join("; ") }
|
|
1610
|
+
};
|
|
1611
|
+
});
|
|
1612
|
+
const base = {
|
|
1613
|
+
cliPath: this.runner.command,
|
|
1614
|
+
commandFound: this.runner.commandFound,
|
|
1615
|
+
dataDir: this.runner.effectiveDataDir(),
|
|
1616
|
+
store: dshActiveStores.join(", ") || "none",
|
|
1617
|
+
mnemonDefaultStore: this.runner.persistedStore(),
|
|
1618
|
+
dshActiveStores,
|
|
1619
|
+
writeEnabled: this.config.writeEnabled,
|
|
1620
|
+
timeoutMs: this.config.timeoutMs,
|
|
1621
|
+
defaultRecallLimit: this.config.defaultRecallLimit,
|
|
1622
|
+
recallQuality: this.config.recallQuality,
|
|
1623
|
+
memoryBodyDirectory: catalog.directory,
|
|
1624
|
+
memoryBodies: catalog.items,
|
|
1625
|
+
providerServices
|
|
1626
|
+
};
|
|
1627
|
+
try {
|
|
1628
|
+
if (versionError !== void 0) throw versionError;
|
|
1629
|
+
const healthyBodies = active.filter((body) => body.healthy && body.stats !== void 0);
|
|
1630
|
+
const topEntities = /* @__PURE__ */ new Map();
|
|
1631
|
+
const byCategory = {};
|
|
1632
|
+
for (const body of healthyBodies) {
|
|
1633
|
+
for (const [category, count] of Object.entries(body.stats.byCategory)) byCategory[category] = (byCategory[category] ?? 0) + count;
|
|
1634
|
+
for (const entity of body.stats.topEntities) topEntities.set(entity.entity, (topEntities.get(entity.entity) ?? 0) + entity.count);
|
|
1635
|
+
}
|
|
1636
|
+
const stats = {
|
|
1637
|
+
totalInsights: healthyBodies.reduce((total, body) => total + body.stats.totalInsights, 0),
|
|
1638
|
+
deletedInsights: healthyBodies.reduce((total, body) => total + body.stats.deletedInsights, 0),
|
|
1639
|
+
edgeCount: healthyBodies.reduce((total, body) => total + body.stats.edgeCount, 0),
|
|
1640
|
+
oplogCount: healthyBodies.reduce((total, body) => total + body.stats.oplogCount, 0),
|
|
1641
|
+
dbSizeBytes: healthyBodies.reduce((total, body) => total + body.stats.dbSizeBytes, 0),
|
|
1642
|
+
byCategory,
|
|
1643
|
+
topEntities: [...topEntities].map(([entity, count]) => ({
|
|
1644
|
+
entity,
|
|
1645
|
+
count
|
|
1646
|
+
})).sort((left, right) => right.count - left.count),
|
|
1647
|
+
...active.length === 1 ? { dbPath: active[0].dbPath } : {}
|
|
1648
|
+
};
|
|
1649
|
+
const failed = active.filter((body) => !body.healthy);
|
|
1650
|
+
return {
|
|
1651
|
+
healthy: true,
|
|
1652
|
+
...base,
|
|
1653
|
+
...rawVersion === void 0 ? {} : { version: rawVersion.trim().replace(/^mnemon version\s+/i, "") },
|
|
1654
|
+
stats,
|
|
1655
|
+
...failed.length === 0 ? {} : { error: failed.map((body) => `${body.name}: ${body.error ?? "unavailable"}`).join("; ") }
|
|
1656
|
+
};
|
|
1657
|
+
} catch (error) {
|
|
1658
|
+
return {
|
|
1659
|
+
healthy: true,
|
|
1660
|
+
...base,
|
|
1661
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1662
|
+
};
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
async reconnectBody(id, signal) {
|
|
1666
|
+
const body = this.memoryBodies.list().find((candidate) => candidate.id === id);
|
|
1667
|
+
if (body === void 0) throw new Error(`unknown memory body: ${id}`);
|
|
1668
|
+
if (!this.isNativeBody(body)) {
|
|
1669
|
+
if (!this.memoryBodies.providerServiceEnabled(body.provider.id)) throw new Error(`${body.provider.label} is disabled in Settings`);
|
|
1670
|
+
}
|
|
1671
|
+
const provider = this.providerFor(body);
|
|
1672
|
+
provider.invalidateStatus?.(body.id);
|
|
1673
|
+
const status = await provider.status(body, signal);
|
|
1674
|
+
return {
|
|
1675
|
+
...body,
|
|
1676
|
+
providerEnabled: true,
|
|
1677
|
+
mnemonDefault: this.isNativeBody(body) && body.id === this.runner.persistedStore(),
|
|
1678
|
+
...status
|
|
1679
|
+
};
|
|
1680
|
+
}
|
|
1681
|
+
async search(request, signal) {
|
|
1682
|
+
const query = required(request.query, "query", 2e3);
|
|
1683
|
+
const limit = boundedInteger(request.limit, this.config.defaultRecallLimit, 1, 50);
|
|
1684
|
+
const qualityContext = {
|
|
1685
|
+
requestedLimit: limit,
|
|
1686
|
+
config: this.config.recallQuality
|
|
1687
|
+
};
|
|
1688
|
+
const preparedPolicy = prepareRecallQualityPolicy(this.recallQualityPolicy, qualityContext);
|
|
1689
|
+
const mode = allowed(request.mode, [
|
|
1690
|
+
"smart",
|
|
1691
|
+
"keyword",
|
|
1692
|
+
"basic"
|
|
1693
|
+
], "mode") ?? "smart";
|
|
1694
|
+
const category = allowed(request.category, CATEGORIES$1, "category");
|
|
1695
|
+
const source = allowed(request.source, SOURCES$1, "source");
|
|
1696
|
+
const intent = allowed(request.intent, INTENTS$1, "intent");
|
|
1697
|
+
const bodies = this.readBodies(request.memoryBodyIds);
|
|
1698
|
+
const normalizedRequest = {
|
|
1699
|
+
query,
|
|
1700
|
+
mode,
|
|
1701
|
+
limit: preparedPolicy.candidateLimit,
|
|
1702
|
+
...category === void 0 ? {} : { category },
|
|
1703
|
+
...source === void 0 ? {} : { source },
|
|
1704
|
+
...intent === void 0 ? {} : { intent }
|
|
1705
|
+
};
|
|
1706
|
+
let batches = await Promise.all(bodies.map(async (body) => {
|
|
1707
|
+
if (!body.provider.capabilities.search) return {
|
|
1708
|
+
body,
|
|
1709
|
+
result: {
|
|
1710
|
+
results: [],
|
|
1711
|
+
hint: "search is not supported"
|
|
1712
|
+
},
|
|
1713
|
+
source: readSource(body, "unsupported", "unsupported", 0, { hint: "This provider does not expose search." })
|
|
1714
|
+
};
|
|
1715
|
+
try {
|
|
1716
|
+
const result = await this.providerFor(body).search(body, normalizedRequest, signal);
|
|
1717
|
+
return {
|
|
1718
|
+
body,
|
|
1719
|
+
result,
|
|
1720
|
+
source: readSource(body, "search", result.results.length === 0 ? "empty" : "ready", result.results.length, result.hint === void 0 ? {} : { hint: result.hint })
|
|
1721
|
+
};
|
|
1722
|
+
} catch (error) {
|
|
1723
|
+
const hint = error instanceof Error ? error.message : String(error);
|
|
1724
|
+
return {
|
|
1725
|
+
body,
|
|
1726
|
+
result: {
|
|
1727
|
+
results: [],
|
|
1728
|
+
hint: `unavailable: ${hint}`
|
|
1729
|
+
},
|
|
1730
|
+
source: readSource(body, "search", "unavailable", 0, { hint })
|
|
1731
|
+
};
|
|
1732
|
+
}
|
|
1733
|
+
}));
|
|
1734
|
+
const recoveryPlan = mode === "smart" && category === void 0 && source === void 0 && intent === void 0 ? exactSearchAnchorPlan(query) ?? lexicalSearchRecoveryPlan(query) : void 0;
|
|
1735
|
+
const evaluate = (selectedBatches) => {
|
|
1736
|
+
const candidates = [];
|
|
1737
|
+
const hints = [];
|
|
1738
|
+
for (const [bodyOrder, { body, result }] of selectedBatches.entries()) {
|
|
1739
|
+
const scoreSemantics = this.providerFor(body).scoreSemantics;
|
|
1740
|
+
candidates.push(...result.results.map((entry, index) => ({
|
|
1741
|
+
insight: this.annotate(entry, body),
|
|
1742
|
+
memoryBodyId: body.id,
|
|
1743
|
+
providerId: body.provider.id,
|
|
1744
|
+
providerRank: index + 1,
|
|
1745
|
+
bodyOrder,
|
|
1746
|
+
...scoreSemantics === void 0 ? {} : { scoreSemantics }
|
|
1747
|
+
})));
|
|
1748
|
+
if (result.hint !== void 0) hints.push(`${body.name}: ${result.hint}`);
|
|
1749
|
+
}
|
|
1750
|
+
const heterogeneous = new Set(bodies.map((body) => body.provider.id)).size > 1;
|
|
1751
|
+
if (heterogeneous) for (const candidate of candidates) candidate.insight.federatedScore = 1 / (60 + candidate.providerRank);
|
|
1752
|
+
candidates.sort((left, right) => heterogeneous ? (right.insight.federatedScore ?? 0) - (left.insight.federatedScore ?? 0) || left.bodyOrder - right.bodyOrder : (right.insight.score ?? 0) - (left.insight.score ?? 0));
|
|
1753
|
+
return {
|
|
1754
|
+
hints,
|
|
1755
|
+
quality: applyRecallQualityPolicy(preparedPolicy, candidates, qualityContext)
|
|
1756
|
+
};
|
|
1757
|
+
};
|
|
1758
|
+
let evaluation = evaluate(batches);
|
|
1759
|
+
const hasRecoveryEvidence = recoveryPlan !== void 0 && evaluation.quality.selected.some((candidate) => recoveryMatchCount(candidate.candidate.insight.content, recoveryPlan) >= recoveryPlan.requiredMatches);
|
|
1760
|
+
if (recoveryPlan !== void 0 && !hasRecoveryEvidence) {
|
|
1761
|
+
batches = await Promise.all(batches.map(async (batch) => {
|
|
1762
|
+
if (!this.isNativeBody(batch.body) || batch.source.status === "unsupported" || batch.source.status === "unavailable") return batch;
|
|
1763
|
+
try {
|
|
1764
|
+
const recovered = await this.providerFor(batch.body).search(batch.body, {
|
|
1765
|
+
query: recoveryPlan.query,
|
|
1766
|
+
mode: "keyword",
|
|
1767
|
+
limit: Math.min(limit, preparedPolicy.candidateLimit)
|
|
1768
|
+
}, signal);
|
|
1769
|
+
const admitted = recovered.results.some((insight) => recoveryMatchCount(insight.content, recoveryPlan) >= recoveryPlan.requiredMatches);
|
|
1770
|
+
const results = mergeRecoveryResults(batch.result.results, recovered.results, recoveryPlan, preparedPolicy.candidateLimit);
|
|
1771
|
+
return {
|
|
1772
|
+
...batch,
|
|
1773
|
+
result: {
|
|
1774
|
+
results,
|
|
1775
|
+
...admitted || batch.result.hint === void 0 ? {} : { hint: batch.result.hint }
|
|
1776
|
+
}
|
|
1777
|
+
};
|
|
1778
|
+
} catch {
|
|
1779
|
+
return batch;
|
|
1780
|
+
}
|
|
1781
|
+
}));
|
|
1782
|
+
evaluation = evaluate(batches);
|
|
1783
|
+
}
|
|
1784
|
+
const { hints, quality } = evaluation;
|
|
1785
|
+
const selected = recoveryPlan === void 0 ? quality.selected : prioritizeRecoveryEvidence(quality.selected, recoveryPlan);
|
|
1786
|
+
const qualityStats = (memoryBodyId) => {
|
|
1787
|
+
const evaluated = quality.evaluated.filter((candidate) => candidate.candidate.memoryBodyId === memoryBodyId);
|
|
1788
|
+
const selected = quality.selected.filter((candidate) => candidate.candidate.memoryBodyId === memoryBodyId);
|
|
1789
|
+
return {
|
|
1790
|
+
policyId: quality.policyId,
|
|
1791
|
+
...quality.fallbackFrom === void 0 ? {} : { fallbackFrom: quality.fallbackFrom },
|
|
1792
|
+
fetched: evaluated.length,
|
|
1793
|
+
retained: evaluated.filter((candidate) => candidate.decision.action === "keep").length,
|
|
1794
|
+
selected: selected.length,
|
|
1795
|
+
droppedLowScore: evaluated.filter((candidate) => candidate.decision.action === "drop" && candidate.decision.reason === "low-score").length,
|
|
1796
|
+
droppedNonPositiveScore: evaluated.filter((candidate) => candidate.decision.action === "drop" && candidate.decision.reason === "non-positive-score").length,
|
|
1797
|
+
droppedInvalidScore: evaluated.filter((candidate) => candidate.decision.action === "drop" && candidate.decision.reason === "invalid-score").length,
|
|
1798
|
+
unscored: evaluated.filter((candidate) => candidate.decision.reason === "unscored").length,
|
|
1799
|
+
unscaled: evaluated.filter((candidate) => candidate.decision.reason === "unscaled-score").length
|
|
1800
|
+
};
|
|
1801
|
+
};
|
|
1802
|
+
return {
|
|
1803
|
+
query,
|
|
1804
|
+
mode,
|
|
1805
|
+
results: selected.map(({ candidate, decision }) => ({
|
|
1806
|
+
...candidate.insight,
|
|
1807
|
+
relevanceTier: decision.tier,
|
|
1808
|
+
...decision.normalizedScore === void 0 ? {} : { normalizedScore: decision.normalizedScore }
|
|
1809
|
+
})),
|
|
1810
|
+
sources: batches.map((batch) => {
|
|
1811
|
+
const stats = qualityStats(batch.body.id);
|
|
1812
|
+
if (batch.source.status === "unavailable" || batch.source.status === "unsupported") return {
|
|
1813
|
+
...batch.source,
|
|
1814
|
+
quality: stats
|
|
1815
|
+
};
|
|
1816
|
+
return {
|
|
1817
|
+
...batch.source,
|
|
1818
|
+
status: stats.retained === 0 ? "empty" : "ready",
|
|
1819
|
+
itemCount: stats.retained,
|
|
1820
|
+
quality: stats
|
|
1821
|
+
};
|
|
1822
|
+
}),
|
|
1823
|
+
...hints.length === 0 ? {} : { hint: hints.join("\n") }
|
|
1824
|
+
};
|
|
1825
|
+
}
|
|
1826
|
+
/**
|
|
1827
|
+
* Read a deliberately small metadata sample through the cheapest useful path
|
|
1828
|
+
* exposed by the owning Provider. This avoids federated ranking, graph
|
|
1829
|
+
* expansion, and large browse projections before an LLM metadata pass.
|
|
1830
|
+
*/
|
|
1831
|
+
async metadataSample(memoryBodyId, signal) {
|
|
1832
|
+
const body = this.readBodies([memoryBodyId])[0];
|
|
1833
|
+
const provider = this.providerFor(body);
|
|
1834
|
+
const limit = 6;
|
|
1835
|
+
let method;
|
|
1836
|
+
let items;
|
|
1837
|
+
if (this.isNativeBody(body)) {
|
|
1838
|
+
method = "native-basic";
|
|
1839
|
+
items = provider.metadataSample === void 0 ? await provider.list(body, { limit }, signal) : await provider.metadataSample(body, limit, signal);
|
|
1840
|
+
} else if (METADATA_SEARCH_FIRST_PROVIDERS.has(body.provider.typeId ?? body.provider.id) || !body.provider.capabilities.browse) {
|
|
1841
|
+
method = "search";
|
|
1842
|
+
const query = (body.description.trim() || body.name.trim()).slice(0, 400);
|
|
1843
|
+
items = (await provider.search(body, {
|
|
1844
|
+
query,
|
|
1845
|
+
mode: "basic",
|
|
1846
|
+
limit
|
|
1847
|
+
}, signal)).results;
|
|
1848
|
+
} else {
|
|
1849
|
+
method = "browse";
|
|
1850
|
+
items = await provider.list(body, { limit }, signal);
|
|
1851
|
+
}
|
|
1852
|
+
return {
|
|
1853
|
+
memoryBodyId: body.id,
|
|
1854
|
+
name: body.name,
|
|
1855
|
+
description: body.description,
|
|
1856
|
+
providerId: body.provider.id,
|
|
1857
|
+
providerLabel: body.provider.label,
|
|
1858
|
+
method,
|
|
1859
|
+
evidence: items.slice(0, limit).map((item) => ({
|
|
1860
|
+
content: item.content.length > 720 ? `${item.content.slice(0, 719)}…` : item.content,
|
|
1861
|
+
...item.category === void 0 ? {} : { category: item.category },
|
|
1862
|
+
...item.entities === void 0 ? {} : { entities: item.entities.slice(0, 8) }
|
|
1863
|
+
}))
|
|
1864
|
+
};
|
|
1865
|
+
}
|
|
1866
|
+
async graph(signal, memoryBodyIds) {
|
|
1867
|
+
const bodies = this.readBodies(memoryBodyIds);
|
|
1868
|
+
const nodes = [];
|
|
1869
|
+
const edges = [];
|
|
1870
|
+
const sources = [];
|
|
1871
|
+
const snapshots = await Promise.all(bodies.map(async (body) => {
|
|
1872
|
+
const mode = body.provider.capabilities.graph ? "graph" : body.provider.capabilities.browse ? "projection" : body.provider.capabilities.search ? "query-only" : "unsupported";
|
|
1873
|
+
if (mode === "query-only") return {
|
|
1874
|
+
body,
|
|
1875
|
+
source: readSource(body, mode, "query-required", 0, {
|
|
1876
|
+
edgeCount: 0,
|
|
1877
|
+
hint: "Use Recall to query this provider."
|
|
1878
|
+
})
|
|
1879
|
+
};
|
|
1880
|
+
if (mode === "unsupported") return {
|
|
1881
|
+
body,
|
|
1882
|
+
source: readSource(body, mode, "unsupported", 0, {
|
|
1883
|
+
edgeCount: 0,
|
|
1884
|
+
hint: "This provider exposes neither graph nor browse projection."
|
|
1885
|
+
})
|
|
1886
|
+
};
|
|
1887
|
+
try {
|
|
1888
|
+
const snapshot = await this.providerFor(body).graph(body, signal);
|
|
1889
|
+
return {
|
|
1890
|
+
body,
|
|
1891
|
+
snapshot,
|
|
1892
|
+
source: readSource(body, mode, snapshot.nodes.length === 0 ? "empty" : "ready", snapshot.nodes.length, { edgeCount: snapshot.edges.length })
|
|
1893
|
+
};
|
|
1894
|
+
} catch (error) {
|
|
1895
|
+
return {
|
|
1896
|
+
body,
|
|
1897
|
+
source: readSource(body, mode, "unavailable", 0, {
|
|
1898
|
+
edgeCount: 0,
|
|
1899
|
+
hint: error instanceof Error ? error.message : String(error)
|
|
1900
|
+
})
|
|
1901
|
+
};
|
|
1902
|
+
}
|
|
1903
|
+
}));
|
|
1904
|
+
for (const item of snapshots) {
|
|
1905
|
+
sources.push(item.source);
|
|
1906
|
+
if (item.snapshot === void 0) continue;
|
|
1907
|
+
const { body, snapshot } = item;
|
|
1908
|
+
const graphId = (id) => `${body.id}:${id}`;
|
|
1909
|
+
nodes.push(...snapshot.nodes.map((node) => ({
|
|
1910
|
+
...this.annotate(node, body),
|
|
1911
|
+
color: node.color,
|
|
1912
|
+
graphId: graphId(node.id)
|
|
1913
|
+
})));
|
|
1914
|
+
edges.push(...snapshot.edges.map((edge) => ({
|
|
1915
|
+
...edge,
|
|
1916
|
+
sourceId: graphId(edge.sourceId),
|
|
1917
|
+
targetId: graphId(edge.targetId)
|
|
1918
|
+
})));
|
|
1919
|
+
}
|
|
1920
|
+
return {
|
|
1921
|
+
nodes,
|
|
1922
|
+
edges,
|
|
1923
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1924
|
+
memoryBodies: bodies.map(({ id, name, active }) => ({
|
|
1925
|
+
id,
|
|
1926
|
+
name,
|
|
1927
|
+
active
|
|
1928
|
+
})),
|
|
1929
|
+
sources
|
|
1930
|
+
};
|
|
1931
|
+
}
|
|
1932
|
+
async list(request = {}, signal) {
|
|
1933
|
+
const rawQuery = request.query?.trim() ?? "";
|
|
1934
|
+
const query = rawQuery.toLocaleLowerCase();
|
|
1935
|
+
if (rawQuery.length > 500) throw new Error("query is too long (max 500 characters)");
|
|
1936
|
+
const category = allowed(request.category, CATEGORIES$1, "category");
|
|
1937
|
+
const limit = boundedInteger(request.limit, 200, 1, 1e3);
|
|
1938
|
+
const bodies = this.readBodies(request.memoryBodyIds);
|
|
1939
|
+
const batches = await Promise.all(bodies.map(async (body) => {
|
|
1940
|
+
const mode = body.provider.capabilities.browse ? "enumerable" : body.provider.capabilities.search ? "query-only" : "unsupported";
|
|
1941
|
+
if (mode === "query-only" && rawQuery === "") return {
|
|
1942
|
+
body,
|
|
1943
|
+
items: [],
|
|
1944
|
+
source: readSource(body, mode, "query-required", 0, { hint: "Enter a query to inspect this provider." })
|
|
1945
|
+
};
|
|
1946
|
+
if (mode === "unsupported") return {
|
|
1947
|
+
body,
|
|
1948
|
+
items: [],
|
|
1949
|
+
source: readSource(body, mode, "unsupported", 0, { hint: "This provider does not expose content browsing." })
|
|
1950
|
+
};
|
|
1951
|
+
try {
|
|
1952
|
+
const provider = this.providerFor(body);
|
|
1953
|
+
const items = (mode === "query-only" ? (await provider.search(body, {
|
|
1954
|
+
query: rawQuery,
|
|
1955
|
+
limit
|
|
1956
|
+
}, signal)).results : await provider.list(body, {
|
|
1957
|
+
...request,
|
|
1958
|
+
limit
|
|
1959
|
+
}, signal)).filter((item) => (category === void 0 || item.category === category) && (query === "" || item.content.toLocaleLowerCase().includes(query) || item.id.toLocaleLowerCase().includes(query)));
|
|
1960
|
+
return {
|
|
1961
|
+
body,
|
|
1962
|
+
items,
|
|
1963
|
+
source: readSource(body, mode, items.length === 0 ? "empty" : "ready", items.length)
|
|
1964
|
+
};
|
|
1965
|
+
} catch (error) {
|
|
1966
|
+
return {
|
|
1967
|
+
body,
|
|
1968
|
+
items: [],
|
|
1969
|
+
source: readSource(body, mode, "unavailable", 0, { hint: error instanceof Error ? error.message : String(error) })
|
|
1970
|
+
};
|
|
1971
|
+
}
|
|
1972
|
+
}));
|
|
1973
|
+
const items = batches.flatMap(({ body, items: bodyItems }) => bodyItems.map((item) => ({
|
|
1974
|
+
...this.annotate(item, body),
|
|
1975
|
+
color: insightColor(item.category)
|
|
1976
|
+
})));
|
|
1977
|
+
return {
|
|
1978
|
+
items: items.slice(0, limit),
|
|
1979
|
+
total: items.length,
|
|
1980
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1981
|
+
sources: batches.map((batch) => batch.source)
|
|
1982
|
+
};
|
|
1983
|
+
}
|
|
1984
|
+
async entities(entity, limit, signal) {
|
|
1985
|
+
const active = (await this.bodies(signal)).items.filter((body) => body.active);
|
|
1986
|
+
const capable = active.filter((body) => body.provider.capabilities.entities);
|
|
1987
|
+
const entityCounts = /* @__PURE__ */ new Map();
|
|
1988
|
+
for (const body of capable) for (const item of body.stats?.topEntities ?? []) entityCounts.set(item.entity, (entityCounts.get(item.entity) ?? 0) + item.count);
|
|
1989
|
+
const items = [...entityCounts].map(([name, count]) => ({
|
|
1990
|
+
entity: name,
|
|
1991
|
+
count
|
|
1992
|
+
})).sort((left, right) => right.count - left.count);
|
|
1993
|
+
const sources = active.map((body) => {
|
|
1994
|
+
if (!body.provider.capabilities.entities) return readSource(body, "unsupported", "unsupported", 0, { hint: "This provider does not expose an entity index." });
|
|
1995
|
+
if (!body.healthy) return readSource(body, "entities", "unavailable", 0, { hint: body.error ?? "Provider unavailable." });
|
|
1996
|
+
const count = body.stats?.topEntities.length ?? 0;
|
|
1997
|
+
return readSource(body, "entities", count === 0 ? "empty" : "ready", count);
|
|
1998
|
+
});
|
|
1999
|
+
const selected = entity?.trim() ?? "";
|
|
2000
|
+
if (selected === "") return {
|
|
2001
|
+
items,
|
|
2002
|
+
insights: [],
|
|
2003
|
+
sources
|
|
2004
|
+
};
|
|
2005
|
+
if (selected.length > 200) throw new Error("entity is too long (max 200 characters)");
|
|
2006
|
+
const readableIds = capable.filter((body) => body.healthy).map((body) => body.id);
|
|
2007
|
+
return {
|
|
2008
|
+
items,
|
|
2009
|
+
selected,
|
|
2010
|
+
insights: readableIds.length === 0 ? [] : (await this.search({
|
|
2011
|
+
query: selected,
|
|
2012
|
+
intent: "ENTITY",
|
|
2013
|
+
limit: boundedInteger(limit, 20, 1, 50),
|
|
2014
|
+
memoryBodyIds: readableIds
|
|
2015
|
+
}, signal)).results,
|
|
2016
|
+
sources
|
|
2017
|
+
};
|
|
2018
|
+
}
|
|
2019
|
+
async remember(request, signal) {
|
|
2020
|
+
this.assertWritable();
|
|
2021
|
+
const prepared = this.prepareRemember(request);
|
|
2022
|
+
const result = await this.providerFor(prepared.body).remember(prepared.body, prepared.request, signal);
|
|
2023
|
+
this.activateAfterWrite(prepared.body, mutationResultCommitted(result));
|
|
2024
|
+
return this.annotateResult(result, prepared.body);
|
|
2025
|
+
}
|
|
2026
|
+
/**
|
|
2027
|
+
* Persist a host-authorized set of exact memories without involving a model
|
|
2028
|
+
* in the data plane. Mnemon Native requests share one import per destination;
|
|
2029
|
+
* other Providers retain their adapter-defined write semantics.
|
|
2030
|
+
*/
|
|
2031
|
+
async rememberMany(requests, signal) {
|
|
2032
|
+
this.assertWritable();
|
|
2033
|
+
const prepared = requests.map((request) => this.prepareRemember(request));
|
|
2034
|
+
const results = new Array(prepared.length);
|
|
2035
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2036
|
+
for (const [index, entry] of prepared.entries()) groups.set(entry.body.id, [...groups.get(entry.body.id) ?? [], {
|
|
2037
|
+
...entry,
|
|
2038
|
+
index
|
|
2039
|
+
}]);
|
|
2040
|
+
for (const group of groups.values()) {
|
|
2041
|
+
const body = group[0].body;
|
|
2042
|
+
const provider = this.providerFor(body);
|
|
2043
|
+
let providerChanged = false;
|
|
2044
|
+
const batchWriter = provider.rememberMany;
|
|
2045
|
+
const batch = batchWriter === void 0 ? [] : group.filter((entry) => !this.isNativeBody(body) || entry.request.content.length <= 8e3);
|
|
2046
|
+
if (batchWriter !== void 0 && batch.length > 0) {
|
|
2047
|
+
const written = await batchWriter.call(provider, body, batch.map((entry) => entry.request), signal);
|
|
2048
|
+
if (written.length !== batch.length) throw new Error(`batch remember did not return one receipt per request for Memory Space ${body.id}`);
|
|
2049
|
+
for (const [offset, result] of written.entries()) {
|
|
2050
|
+
const entry = batch[offset];
|
|
2051
|
+
results[entry.index] = this.annotateResult(result, body);
|
|
2052
|
+
providerChanged ||= mutationResultCommitted(result);
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
for (const entry of group) {
|
|
2056
|
+
if (batch.includes(entry)) continue;
|
|
2057
|
+
const result = await provider.remember(body, entry.request, signal);
|
|
2058
|
+
results[entry.index] = this.annotateResult(result, body);
|
|
2059
|
+
providerChanged ||= mutationResultCommitted(result);
|
|
2060
|
+
}
|
|
2061
|
+
this.activateAfterWrite(body, providerChanged);
|
|
2062
|
+
}
|
|
2063
|
+
return results;
|
|
2064
|
+
}
|
|
2065
|
+
async related(id, depth = 2, edge, signal, memoryBodyId) {
|
|
2066
|
+
const body = this.readBody(memoryBodyId);
|
|
2067
|
+
const selectedEdge = allowed(edge, EDGE_TYPES, "edge");
|
|
2068
|
+
const provider = this.providerFor(body);
|
|
2069
|
+
if (provider.related === void 0 || !body.provider.capabilities.related) throw new Error(`${body.provider.label} does not support related-memory traversal`);
|
|
2070
|
+
return (await provider.related(body, required(id, "id", 2e3), boundedInteger(depth, 2, 1, 5), selectedEdge, signal)).map((entry) => this.annotate(entry, body));
|
|
2071
|
+
}
|
|
2072
|
+
async link(sourceId, targetId, type = "semantic", weight = .5, reason, signal, memoryBodyId) {
|
|
2073
|
+
this.assertWritable();
|
|
2074
|
+
const body = this.writeBody(memoryBodyId);
|
|
2075
|
+
if (!Number.isFinite(weight) || weight < 0 || weight > 1) throw new Error("weight must be within 0..1");
|
|
2076
|
+
const selectedType = allowed(type, EDGE_TYPES, "type") ?? "semantic";
|
|
2077
|
+
const provider = this.providerFor(body);
|
|
2078
|
+
if (provider.link === void 0 || !body.provider.capabilities.link) throw new Error(`${body.provider.label} does not support explicit memory links`);
|
|
2079
|
+
const result = await provider.link(body, required(sourceId, "sourceId", 2e3), required(targetId, "targetId", 2e3), selectedType, weight, reason === void 0 || reason.trim() === "" ? void 0 : required(reason, "reason", 1e3), signal);
|
|
2080
|
+
this.activateAfterWrite(body, mutationResultCommitted(result));
|
|
2081
|
+
return this.annotateResult(result, body);
|
|
2082
|
+
}
|
|
2083
|
+
async forget(id, signal, memoryBodyId) {
|
|
2084
|
+
this.assertWritable();
|
|
2085
|
+
const body = this.writeBody(memoryBodyId);
|
|
2086
|
+
const provider = this.providerFor(body);
|
|
2087
|
+
if (provider.forget === void 0 || !body.provider.capabilities.forget) throw new Error(`${body.provider.label} does not expose safe forget semantics in this integration`);
|
|
2088
|
+
const result = await provider.forget(body, required(id, "id", 2e3), signal);
|
|
2089
|
+
this.activateAfterWrite(body, mutationResultCommitted(result));
|
|
2090
|
+
return this.annotateResult(result, body);
|
|
2091
|
+
}
|
|
2092
|
+
prepareBodyPlacement(request) {
|
|
2093
|
+
if (request.placement === void 0) throw new Error("automatic provider placement request is required");
|
|
2094
|
+
if (request.providerId !== void 0) throw new Error("automatic provider placement cannot include a fixed providerId");
|
|
2095
|
+
return prepareMemoryPlacement(request.placement, this.memoryBodies.placementCandidates(request));
|
|
2096
|
+
}
|
|
2097
|
+
async createBody(request, signal, placement) {
|
|
2098
|
+
this.assertWritable();
|
|
2099
|
+
return await this.memoryBodies.create(request, signal, placement);
|
|
2100
|
+
}
|
|
2101
|
+
/**
|
|
2102
|
+
* Create a Memory Space from the configured distillation policy. The model
|
|
2103
|
+
* may choose only among candidates already filtered by the host; manual mode
|
|
2104
|
+
* ignores model preference and always uses the configured fixed provider.
|
|
2105
|
+
*/
|
|
2106
|
+
async createBodyForPersistence(body, selection, signal, delegation = {
|
|
2107
|
+
runId: "memory-write",
|
|
2108
|
+
provider: "task-agent"
|
|
2109
|
+
}) {
|
|
2110
|
+
const strategy = this.config.persistenceStrategy;
|
|
2111
|
+
if (strategy.mode === "manual") {
|
|
2112
|
+
const connection = strategy.providerConnections[strategy.providerId];
|
|
2113
|
+
return this.createBody({
|
|
2114
|
+
...body,
|
|
2115
|
+
providerId: strategy.providerId,
|
|
2116
|
+
...this.isNativeProvider(strategy.providerId) || connection === void 0 ? {} : { connection }
|
|
2117
|
+
}, signal);
|
|
2118
|
+
}
|
|
2119
|
+
const request = {
|
|
2120
|
+
...body,
|
|
2121
|
+
placement: {
|
|
2122
|
+
mode: "automatic",
|
|
2123
|
+
...strategy.prompt === "" ? {} : { prompt: strategy.prompt },
|
|
2124
|
+
rules: { ...strategy.rules }
|
|
2125
|
+
},
|
|
2126
|
+
...Object.keys(strategy.providerConnections).length === 0 ? {} : { providerConnections: strategy.providerConnections }
|
|
2127
|
+
};
|
|
2128
|
+
const prepared = this.prepareBodyPlacement(request);
|
|
2129
|
+
const decision = rulesOnlyPlacement(prepared) ?? finalizeLlmPlacement(prepared, selection ?? {
|
|
2130
|
+
providerId: "",
|
|
2131
|
+
reason: "",
|
|
2132
|
+
confidence: ""
|
|
2133
|
+
}, delegation);
|
|
2134
|
+
return this.createBody(request, signal, decision);
|
|
2135
|
+
}
|
|
2136
|
+
async updateProviderService(providerId, settings, clearSecrets = [], enabled = true, signal) {
|
|
2137
|
+
this.assertWritable();
|
|
2138
|
+
if (this.isNativeProvider(providerId)) throw new Error("Mnemon Native service settings are managed by the native configuration");
|
|
2139
|
+
if (!enabled) return this.memoryBodies.updateProviderService(providerId, settings, clearSecrets, false);
|
|
2140
|
+
const connection = this.memoryBodies.resolveProviderService(providerId, settings, clearSecrets);
|
|
2141
|
+
const provider = this.providers.get(providerId);
|
|
2142
|
+
if (provider?.discover === void 0) throw new Error(`${this.providerCatalog.descriptor(providerId).label} does not support Memory Space discovery`);
|
|
2143
|
+
const discovered = await provider.discover(connection, signal);
|
|
2144
|
+
return this.memoryBodies.syncProviderService(providerId, connection, discovered);
|
|
2145
|
+
}
|
|
2146
|
+
updateBody(id, request) {
|
|
2147
|
+
this.assertWritable();
|
|
2148
|
+
return this.memoryBodies.update(id, request);
|
|
2149
|
+
}
|
|
2150
|
+
updateBodyMetadata(updates) {
|
|
2151
|
+
this.assertWritable();
|
|
2152
|
+
return this.memoryBodies.updateMetadata(updates);
|
|
2153
|
+
}
|
|
2154
|
+
async deleteBody(id, signal) {
|
|
2155
|
+
this.assertWritable();
|
|
2156
|
+
return await this.memoryBodies.remove(id, signal);
|
|
2157
|
+
}
|
|
2158
|
+
async mergeBodies(targetBodyId, sourceBodyIds, deactivateSources = true, signal) {
|
|
2159
|
+
this.assertWritable();
|
|
2160
|
+
const target = this.memoryBodies.get(targetBodyId);
|
|
2161
|
+
if (!this.isNativeBody(target)) throw new Error("memory-body merge currently requires a Mnemon Native target");
|
|
2162
|
+
const sourceIds = [...new Set(sourceBodyIds.map((id) => id.trim()).filter((id) => id !== ""))];
|
|
2163
|
+
if (sourceIds.length === 0) throw new Error("sourceMemoryBodyIds requires at least one memory body");
|
|
2164
|
+
if (sourceIds.includes(target.id)) throw new Error("target memory body cannot also be a merge source");
|
|
2165
|
+
const sources = sourceIds.map((id) => this.memoryBodies.get(id));
|
|
2166
|
+
if (sources.some((source) => !this.isNativeBody(source))) throw new Error("memory-body merge currently supports Mnemon Native sources only");
|
|
2167
|
+
const insights = [];
|
|
2168
|
+
const edges = [];
|
|
2169
|
+
for (const source of sources) {
|
|
2170
|
+
const offset = insights.length;
|
|
2171
|
+
const sourceInsights = await this.providerFor(source).list(source, { limit: 1e5 }, signal);
|
|
2172
|
+
const indexById = new Map(sourceInsights.map((insight, index) => [insight.id, offset + index]));
|
|
2173
|
+
for (const insight of sourceInsights) insights.push({
|
|
2174
|
+
content: insight.content,
|
|
2175
|
+
...insight.category === void 0 ? {} : { category: insight.category },
|
|
2176
|
+
...insight.importance === void 0 ? {} : { importance: insight.importance },
|
|
2177
|
+
...insight.tags === void 0 ? {} : { tags: insight.tags },
|
|
2178
|
+
...insight.entities === void 0 ? {} : { entities: insight.entities },
|
|
2179
|
+
...insight.source === void 0 ? {} : { source: insight.source },
|
|
2180
|
+
...insight.createdAt === void 0 ? {} : { created_at: insight.createdAt }
|
|
2181
|
+
});
|
|
2182
|
+
const graph = await this.providerFor(source).graph(source, signal);
|
|
2183
|
+
for (const edge of graph.edges) {
|
|
2184
|
+
const sourceIndex = indexById.get(edge.sourceId);
|
|
2185
|
+
const targetIndex = indexById.get(edge.targetId);
|
|
2186
|
+
if (sourceIndex === void 0 || targetIndex === void 0 || edge.type === void 0) continue;
|
|
2187
|
+
edges.push({
|
|
2188
|
+
source_index: sourceIndex,
|
|
2189
|
+
target_index: targetIndex,
|
|
2190
|
+
edge_type: edge.type,
|
|
2191
|
+
weight: .5,
|
|
2192
|
+
reason: edge.label
|
|
2193
|
+
});
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2196
|
+
if (insights.length === 0) {
|
|
2197
|
+
if (deactivateSources) for (const source of sources) {
|
|
2198
|
+
if (!source.active) continue;
|
|
2199
|
+
this.memoryBodies.setActive(source.id, false);
|
|
2200
|
+
}
|
|
2201
|
+
return {
|
|
2202
|
+
action: "merged",
|
|
2203
|
+
imported: 0,
|
|
2204
|
+
updated: 0,
|
|
2205
|
+
skipped: 0,
|
|
2206
|
+
edges_inserted: 0,
|
|
2207
|
+
targetMemoryBodyId: target.id
|
|
2208
|
+
};
|
|
2209
|
+
}
|
|
2210
|
+
const temporary = mkdtempSync(join(tmpdir(), "dsh-mnemon-merge-"));
|
|
2211
|
+
const draftPath = join(temporary, "memory-draft.json");
|
|
2212
|
+
try {
|
|
2213
|
+
writeFileSync(draftPath, JSON.stringify({
|
|
2214
|
+
schema_version: "1",
|
|
2215
|
+
source: "dsh-mnemon-merge",
|
|
2216
|
+
insights,
|
|
2217
|
+
edges
|
|
2218
|
+
}), {
|
|
2219
|
+
encoding: "utf8",
|
|
2220
|
+
mode: 384
|
|
2221
|
+
});
|
|
2222
|
+
const result = await this.runner.runJson(["import", draftPath], {
|
|
2223
|
+
...signal === void 0 ? {} : { signal },
|
|
2224
|
+
store: target.id
|
|
2225
|
+
});
|
|
2226
|
+
const summary = record(result);
|
|
2227
|
+
const counts = [
|
|
2228
|
+
summary?.imported,
|
|
2229
|
+
summary?.updated,
|
|
2230
|
+
summary?.skipped
|
|
2231
|
+
];
|
|
2232
|
+
const complete = mutationResultCommitted(result) && summary?.errors === 0 && counts.every((value) => typeof value === "number" && Number.isInteger(value) && value >= 0) && counts.reduce((sum, value) => sum + Number(value), 0) === insights.length;
|
|
2233
|
+
this.activateAfterWrite(target, complete);
|
|
2234
|
+
if (deactivateSources && complete) for (const source of sources) {
|
|
2235
|
+
if (!source.active) continue;
|
|
2236
|
+
this.memoryBodies.setActive(source.id, false);
|
|
2237
|
+
}
|
|
2238
|
+
const completion = mutationResultCompletion(result);
|
|
2239
|
+
return this.annotateResult({
|
|
2240
|
+
...summary,
|
|
2241
|
+
status: complete ? "committed" : completion === "committed" ? "partial" : completion
|
|
2242
|
+
}, target);
|
|
2243
|
+
} finally {
|
|
2244
|
+
rmSync(temporary, {
|
|
2245
|
+
recursive: true,
|
|
2246
|
+
force: true
|
|
2247
|
+
});
|
|
2248
|
+
}
|
|
2249
|
+
}
|
|
2250
|
+
providerFor(body) {
|
|
2251
|
+
const provider = this.providers.get(body.provider.id);
|
|
2252
|
+
if (provider === void 0) throw new Error(`unsupported memory provider: ${body.provider.id}`);
|
|
2253
|
+
return provider;
|
|
2254
|
+
}
|
|
2255
|
+
readBodies(ids) {
|
|
2256
|
+
const active = this.memoryBodies.active();
|
|
2257
|
+
if (ids === void 0 || ids.length === 0) return active;
|
|
2258
|
+
return [...new Set(ids.map((id) => id.trim()).filter((id) => id !== ""))].map((id) => {
|
|
2259
|
+
const body = this.memoryBodies.get(id);
|
|
2260
|
+
if (!body.active) throw new Error(`memory body is not active for reading: ${id}`);
|
|
2261
|
+
if (!this.isNativeBody(body) && !this.memoryBodies.providerServiceEnabled(body.provider.id)) throw new Error(`${body.provider.label} is disabled in Settings`);
|
|
2262
|
+
return body;
|
|
2263
|
+
});
|
|
2264
|
+
}
|
|
2265
|
+
readBody(id) {
|
|
2266
|
+
if (id !== void 0 && id.trim() !== "") {
|
|
2267
|
+
const body = this.memoryBodies.get(id);
|
|
2268
|
+
if (!body.active) throw new Error(`memory body is not active for reading: ${body.id}`);
|
|
2269
|
+
if (!this.isNativeBody(body) && !this.memoryBodies.providerServiceEnabled(body.provider.id)) throw new Error(`${body.provider.label} is disabled in Settings`);
|
|
2270
|
+
return body;
|
|
2271
|
+
}
|
|
2272
|
+
const active = this.memoryBodies.active();
|
|
2273
|
+
if (active.length !== 1) throw new Error("memoryBodyId is required when the number of active memory bodies is not exactly one");
|
|
2274
|
+
return active[0];
|
|
2275
|
+
}
|
|
2276
|
+
writeBody(id) {
|
|
2277
|
+
if (id !== void 0 && id.trim() !== "") {
|
|
2278
|
+
const body = this.memoryBodies.get(id);
|
|
2279
|
+
if (!this.isNativeBody(body) && !this.memoryBodies.providerServiceEnabled(body.provider.id)) throw new Error(`${body.provider.label} is disabled in Settings`);
|
|
2280
|
+
return body;
|
|
2281
|
+
}
|
|
2282
|
+
const active = this.memoryBodies.active();
|
|
2283
|
+
if (active.length !== 1) throw new Error("memoryBodyId is required when the number of active memory bodies is not exactly one");
|
|
2284
|
+
return active[0];
|
|
2285
|
+
}
|
|
2286
|
+
prepareRemember(request) {
|
|
2287
|
+
const body = this.writeBody(request.memoryBodyId);
|
|
2288
|
+
const content = required(request.content, "content", 8192);
|
|
2289
|
+
const importance = boundedInteger(request.importance, 3, 1, 5);
|
|
2290
|
+
const category = allowed(request.category, CATEGORIES$1, "category") ?? "general";
|
|
2291
|
+
const source = allowed(request.source, SOURCES$1, "source") ?? "user";
|
|
2292
|
+
const tags = commaList(request.tags, "tags", 20)?.split(",");
|
|
2293
|
+
const entities = commaList(request.entities, "entities", 50)?.split(",");
|
|
2294
|
+
return {
|
|
2295
|
+
body,
|
|
2296
|
+
request: {
|
|
2297
|
+
content,
|
|
2298
|
+
importance,
|
|
2299
|
+
category,
|
|
2300
|
+
source,
|
|
2301
|
+
memoryBodyId: body.id,
|
|
2302
|
+
...tags === void 0 ? {} : { tags },
|
|
2303
|
+
...entities === void 0 ? {} : { entities }
|
|
2304
|
+
}
|
|
2305
|
+
};
|
|
2306
|
+
}
|
|
2307
|
+
annotate(insight, body) {
|
|
2308
|
+
return {
|
|
2309
|
+
...insight,
|
|
2310
|
+
memoryBodyId: body.id,
|
|
2311
|
+
memoryBodyName: body.name,
|
|
2312
|
+
memoryProviderId: body.provider.id,
|
|
2313
|
+
memoryProviderLabel: body.provider.label,
|
|
2314
|
+
memoryCapabilities: body.provider.capabilities
|
|
2315
|
+
};
|
|
2316
|
+
}
|
|
2317
|
+
annotateResult(result, body) {
|
|
2318
|
+
const value = record(result);
|
|
2319
|
+
return value === void 0 ? result : {
|
|
2320
|
+
...value,
|
|
2321
|
+
memoryBodyId: body.id,
|
|
2322
|
+
memoryBodyName: body.name,
|
|
2323
|
+
memoryProviderId: body.provider.id,
|
|
2324
|
+
memoryProviderLabel: body.provider.label
|
|
2325
|
+
};
|
|
2326
|
+
}
|
|
2327
|
+
activateAfterWrite(body, providerChanged) {
|
|
2328
|
+
if (!providerChanged) return;
|
|
2329
|
+
if (!body.active) this.memoryBodies.setActive(body.id, true);
|
|
2330
|
+
else this.memoryBodies.touch(body.id);
|
|
2331
|
+
}
|
|
2332
|
+
assertWritable() {
|
|
2333
|
+
if (!this.config.writeEnabled) throw new Error("dsh-mnemon is configured read-only (writeEnabled: false)");
|
|
2334
|
+
}
|
|
2335
|
+
};
|
|
2336
|
+
//#endregion
|
|
2337
|
+
//#region src/runner.ts
|
|
2338
|
+
const UNIX_COMMON_CLI_PATHS = [
|
|
2339
|
+
"~/.local/bin/mnemon",
|
|
2340
|
+
"/opt/homebrew/bin/mnemon",
|
|
2341
|
+
"/usr/local/bin/mnemon",
|
|
2342
|
+
"/usr/bin/mnemon"
|
|
2343
|
+
];
|
|
2344
|
+
function pathApi(platform) {
|
|
2345
|
+
return platform === "win32" ? win32 : posix;
|
|
2346
|
+
}
|
|
2347
|
+
function expandHome(path, home = homedir(), platform = process.platform) {
|
|
2348
|
+
if (path === "~") return home;
|
|
2349
|
+
return path.startsWith("~/") || path.startsWith("~\\") ? pathApi(platform).join(home, path.slice(2)) : path;
|
|
2350
|
+
}
|
|
2351
|
+
function envValue(env, name, platform) {
|
|
2352
|
+
if (platform !== "win32") return env[name];
|
|
2353
|
+
const key = Object.keys(env).find((candidate) => candidate.toLowerCase() === name.toLowerCase());
|
|
2354
|
+
return key === void 0 ? void 0 : env[key];
|
|
2355
|
+
}
|
|
2356
|
+
function executable(path, platform = process.platform) {
|
|
2357
|
+
if (platform === "win32" && win32.extname(path).toLowerCase() !== ".exe") return false;
|
|
2358
|
+
try {
|
|
2359
|
+
accessSync(path, platform === "win32" ? constants.F_OK : constants.X_OK);
|
|
2360
|
+
return statSync(path).isFile();
|
|
2361
|
+
} catch {
|
|
2362
|
+
return false;
|
|
2363
|
+
}
|
|
2364
|
+
}
|
|
2365
|
+
function windowsCommonCliPaths(env, home) {
|
|
2366
|
+
const candidates = [];
|
|
2367
|
+
const goBin = envValue(env, "GOBIN", "win32")?.trim();
|
|
2368
|
+
if (goBin !== void 0 && win32.isAbsolute(goBin)) candidates.push(win32.join(goBin, "mnemon.exe"));
|
|
2369
|
+
const goInstallRoot = (envValue(env, "GOPATH", "win32")?.trim())?.split(win32.delimiter).map((candidate) => candidate.trim()).find((candidate) => candidate !== "" && win32.isAbsolute(candidate)) ?? win32.join(home, "go");
|
|
2370
|
+
candidates.push(win32.join(goInstallRoot, "bin", "mnemon.exe"));
|
|
2371
|
+
const localAppData = envValue(env, "LOCALAPPDATA", "win32")?.trim();
|
|
2372
|
+
if (localAppData !== void 0 && win32.isAbsolute(localAppData)) candidates.push(win32.join(localAppData, "Programs", "mnemon", "mnemon.exe"));
|
|
2373
|
+
const programFiles = envValue(env, "ProgramFiles", "win32")?.trim();
|
|
2374
|
+
if (programFiles !== void 0 && win32.isAbsolute(programFiles)) candidates.push(win32.join(programFiles, "mnemon", "mnemon.exe"));
|
|
2375
|
+
return [...new Set(candidates)];
|
|
2376
|
+
}
|
|
2377
|
+
function commonCliPaths(platform, env, home) {
|
|
2378
|
+
if (platform === "win32") return windowsCommonCliPaths(env, home);
|
|
2379
|
+
return UNIX_COMMON_CLI_PATHS.map((candidate) => expandHome(candidate, home, platform));
|
|
2380
|
+
}
|
|
2381
|
+
/** Locate the local Mnemon binary without invoking a shell. */
|
|
2382
|
+
function findMnemonCommand(config, options = {}) {
|
|
2383
|
+
const platform = options.platform ?? process.platform;
|
|
2384
|
+
const env = options.env ?? process.env;
|
|
2385
|
+
const home = options.home ?? homedir();
|
|
2386
|
+
const isExecutable = options.isExecutable ?? ((path) => executable(path, platform));
|
|
2387
|
+
const paths = pathApi(platform);
|
|
2388
|
+
const resolveCommand = (command) => {
|
|
2389
|
+
const expanded = expandHome(command, home, platform);
|
|
2390
|
+
if (expanded.includes("/") || expanded.includes("\\")) return expanded;
|
|
2391
|
+
const name = platform === "win32" && paths.extname(expanded) === "" ? `${expanded}.exe` : expanded;
|
|
2392
|
+
for (const directory of (envValue(env, "PATH", platform) ?? "").split(paths.delimiter)) {
|
|
2393
|
+
if (directory === "") continue;
|
|
2394
|
+
const path = paths.join(directory, name);
|
|
2395
|
+
if (isExecutable(path)) return path;
|
|
2396
|
+
}
|
|
2397
|
+
};
|
|
2398
|
+
if (config.cliPath !== void 0) return resolveCommand(config.cliPath);
|
|
2399
|
+
const envPath = envValue(env, "MNEMON_CLI_PATH", platform)?.trim();
|
|
2400
|
+
if (envPath !== void 0 && envPath !== "") {
|
|
2401
|
+
const path = resolveCommand(envPath);
|
|
2402
|
+
if (path !== void 0 && isExecutable(path)) return path;
|
|
2403
|
+
}
|
|
2404
|
+
const fromPath = resolveCommand("mnemon");
|
|
2405
|
+
if (fromPath !== void 0) return fromPath;
|
|
2406
|
+
for (const path of commonCliPaths(platform, env, home)) if (isExecutable(path)) return path;
|
|
2407
|
+
}
|
|
2408
|
+
var MnemonCliError = class extends Error {
|
|
2409
|
+
exitCode;
|
|
2410
|
+
stderr;
|
|
2411
|
+
constructor(message, exitCode = null, stderr = "") {
|
|
2412
|
+
super(message);
|
|
2413
|
+
this.name = "MnemonCliError";
|
|
2414
|
+
this.exitCode = exitCode;
|
|
2415
|
+
this.stderr = stderr;
|
|
2416
|
+
}
|
|
2417
|
+
};
|
|
2418
|
+
const EMBEDDING_ENVIRONMENT_KEYS = /* @__PURE__ */ new Set([
|
|
2419
|
+
"MNEMON_EMBED_ENDPOINT",
|
|
2420
|
+
"MNEMON_EMBED_MODEL",
|
|
2421
|
+
"MNEMON_EMBED_API_KEY",
|
|
2422
|
+
"MNEMON_EMBED_PROTOCOL"
|
|
2423
|
+
]);
|
|
2424
|
+
/** Preserve the Host environment while making saved embedding overrides authoritative. */
|
|
2425
|
+
function processEnvironment(config) {
|
|
2426
|
+
if (!config.embedding.enabled) return void 0;
|
|
2427
|
+
return {
|
|
2428
|
+
...Object.fromEntries(Object.entries(process.env).filter(([key]) => !EMBEDDING_ENVIRONMENT_KEYS.has(key.toUpperCase()))),
|
|
2429
|
+
MNEMON_EMBED_ENDPOINT: config.embedding.endpoint,
|
|
2430
|
+
MNEMON_EMBED_MODEL: config.embedding.model,
|
|
2431
|
+
MNEMON_EMBED_API_KEY: config.embedding.apiKey,
|
|
2432
|
+
...config.embedding.protocol === "auto" ? {} : { MNEMON_EMBED_PROTOCOL: config.embedding.protocol }
|
|
2433
|
+
};
|
|
2434
|
+
}
|
|
2435
|
+
function createRunner(config, processRunner = runProcess, workspaceRoot) {
|
|
2436
|
+
const currentCommand = () => findMnemonCommand(config) ?? config.cliPath ?? "mnemon";
|
|
2437
|
+
const globalArgs = (store) => {
|
|
2438
|
+
const args = [];
|
|
2439
|
+
if (config.storageScope !== "global" || config.dataDir !== void 0) args.push("--data-dir", effectiveDataDir());
|
|
2440
|
+
if (store !== void 0) args.push("--store", store);
|
|
2441
|
+
else if (config.store !== void 0) args.push("--store", config.store);
|
|
2442
|
+
return args;
|
|
2443
|
+
};
|
|
2444
|
+
const effectiveDataDir = () => {
|
|
2445
|
+
if (config.storageScope === "workspace") return resolve(workspaceRoot ?? process.cwd(), ".mnemon");
|
|
2446
|
+
if (config.storageScope === "custom") return expandHome(config.dataDir);
|
|
2447
|
+
return expandHome(process.env.MNEMON_DATA_DIR?.trim() || "~/.mnemon");
|
|
2448
|
+
};
|
|
2449
|
+
const persistedStore = () => {
|
|
2450
|
+
const active = join(effectiveDataDir(), "active");
|
|
2451
|
+
if (existsSync(active)) try {
|
|
2452
|
+
const value = readFileSync(active, "utf8").trim();
|
|
2453
|
+
if (/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(value)) return value;
|
|
2454
|
+
} catch {}
|
|
2455
|
+
return "default";
|
|
2456
|
+
};
|
|
2457
|
+
const launch = async (args, options = {}) => {
|
|
2458
|
+
if (options.signal?.aborted === true) throw new MnemonCliError(`mnemon command aborted: ${String(options.signal.reason ?? "cancelled")}`);
|
|
2459
|
+
const argv = options.globalFlags === false ? [...args] : [...globalArgs(options.store), ...args];
|
|
2460
|
+
const environment = processEnvironment(config);
|
|
2461
|
+
const processOptions = {
|
|
2462
|
+
timeoutMs: config.timeoutMs,
|
|
2463
|
+
...environment === void 0 ? {} : { env: environment },
|
|
2464
|
+
...options.signal === void 0 ? {} : { signal: options.signal }
|
|
2465
|
+
};
|
|
2466
|
+
let result;
|
|
2467
|
+
try {
|
|
2468
|
+
result = await processRunner(currentCommand(), argv, processOptions);
|
|
2469
|
+
} catch (error) {
|
|
2470
|
+
throw new MnemonCliError(`${error instanceof Error ? error.message : String(error)}. ${process.platform === "win32" ? "Install the official Mnemon Windows release, ensure mnemon.exe is on PATH or under %LOCALAPPDATA%\\Programs\\mnemon, or set MNEMON_CLI_PATH or mnemon.cliPath to its absolute path." : "Install Mnemon and ensure \"mnemon\" is on PATH, or set MNEMON_CLI_PATH or mnemon.cliPath."}`);
|
|
2471
|
+
}
|
|
2472
|
+
if (result.exitCode !== 0) {
|
|
2473
|
+
const detail = result.stderr.trim() || result.stdout.trim() || "no output";
|
|
2474
|
+
throw new MnemonCliError(`mnemon ${args.join(" ")} exited ${String(result.exitCode)}: ${detail}`, result.exitCode, result.stderr);
|
|
2475
|
+
}
|
|
2476
|
+
return result.stdout;
|
|
2477
|
+
};
|
|
2478
|
+
const execute = (args, options = {}) => {
|
|
2479
|
+
return withMemoryStorageLock(effectiveDataDir(), () => launch(args, options));
|
|
2480
|
+
};
|
|
2481
|
+
return {
|
|
2482
|
+
get command() {
|
|
2483
|
+
return currentCommand();
|
|
2484
|
+
},
|
|
2485
|
+
get commandFound() {
|
|
2486
|
+
const found = findMnemonCommand(config);
|
|
2487
|
+
return found !== void 0 && executable(found);
|
|
2488
|
+
},
|
|
2489
|
+
config,
|
|
2490
|
+
async runJson(args, options) {
|
|
2491
|
+
const stdout = await execute(args, options);
|
|
2492
|
+
try {
|
|
2493
|
+
return JSON.parse(stdout);
|
|
2494
|
+
} catch {
|
|
2495
|
+
throw new MnemonCliError(`mnemon ${args.join(" ")} returned invalid JSON`);
|
|
2496
|
+
}
|
|
2497
|
+
},
|
|
2498
|
+
runText: execute,
|
|
2499
|
+
runTextBatch(commands) {
|
|
2500
|
+
return withMemoryStorageLock(effectiveDataDir(), async () => {
|
|
2501
|
+
const outputs = [];
|
|
2502
|
+
for (const command of commands) outputs.push(await launch(command.args, command.options));
|
|
2503
|
+
return outputs;
|
|
2504
|
+
});
|
|
2505
|
+
},
|
|
2506
|
+
withExclusive(operation) {
|
|
2507
|
+
return withMemoryStorageLock(effectiveDataDir(), operation);
|
|
2508
|
+
},
|
|
2509
|
+
effectiveDataDir() {
|
|
2510
|
+
return effectiveDataDir();
|
|
2511
|
+
},
|
|
2512
|
+
persistedStore() {
|
|
2513
|
+
return persistedStore();
|
|
2514
|
+
},
|
|
2515
|
+
effectiveStore() {
|
|
2516
|
+
if (config.store !== void 0) return config.store;
|
|
2517
|
+
const fromEnvironment = process.env.MNEMON_STORE?.trim();
|
|
2518
|
+
if (fromEnvironment !== void 0 && fromEnvironment !== "") return fromEnvironment;
|
|
2519
|
+
return persistedStore();
|
|
2520
|
+
}
|
|
2521
|
+
};
|
|
2522
|
+
}
|
|
2523
|
+
//#endregion
|
|
2524
|
+
//#region src/config.ts
|
|
2525
|
+
const MemorySpacesConfig = z.object({
|
|
2526
|
+
dataDir: z.string(),
|
|
2527
|
+
cliPath: z.string(),
|
|
2528
|
+
store: z.string(),
|
|
2529
|
+
timeoutMs: z.number().step(1).min(1).max(6e5),
|
|
2530
|
+
defaultRecallLimit: z.number().step(1).min(1).max(50),
|
|
2531
|
+
writeEnabled: z.boolean(),
|
|
2532
|
+
embedding: z.any(),
|
|
2533
|
+
recallQuality: z.any(),
|
|
2534
|
+
persistenceStrategy: z.any()
|
|
2535
|
+
});
|
|
2536
|
+
function optionalText(value) {
|
|
2537
|
+
return value?.trim() || void 0;
|
|
2538
|
+
}
|
|
2539
|
+
const MEMORY_PROVIDER_ID = /^[a-z][a-z0-9-]{0,127}$/u;
|
|
2540
|
+
const MEMORY_PLACEMENT_CAPABILITY_SET = /* @__PURE__ */ new Set([
|
|
2541
|
+
"graph",
|
|
2542
|
+
"entities",
|
|
2543
|
+
"related",
|
|
2544
|
+
"exact-write",
|
|
2545
|
+
"link",
|
|
2546
|
+
"forget"
|
|
2547
|
+
]);
|
|
2548
|
+
const MEMORY_PLACEMENT_PREFERENCE_SET = /* @__PURE__ */ new Set([
|
|
2549
|
+
"balanced",
|
|
2550
|
+
"local-first",
|
|
2551
|
+
"shared-first"
|
|
2552
|
+
]);
|
|
2553
|
+
function resolvePersistenceStrategy(value) {
|
|
2554
|
+
const mode = value?.mode ?? "manual";
|
|
2555
|
+
if (mode !== "manual" && mode !== "automatic") throw new Error(`dsh-mnemon: unsupported persistence strategy mode: ${String(mode)}`);
|
|
2556
|
+
const providerId = value?.providerId ?? "mnemon-native";
|
|
2557
|
+
if (!MEMORY_PROVIDER_ID.test(providerId)) throw new Error(`dsh-mnemon: invalid persistence strategy provider: ${String(providerId)}`);
|
|
2558
|
+
const prompt = value?.prompt?.trim() ?? "";
|
|
2559
|
+
if (prompt.length > 4e3) throw new Error("dsh-mnemon: persistence strategy prompt is too long (max 4000 characters)");
|
|
2560
|
+
const configuredProviderIds = value?.rules?.allowedProviderIds;
|
|
2561
|
+
const allowedProviderIds = [...new Set(configuredProviderIds === void 0 || configuredProviderIds.length === 0 && mode === "manual" ? ["mnemon-native"] : configuredProviderIds)];
|
|
2562
|
+
if (allowedProviderIds.length === 0) throw new Error("dsh-mnemon: persistence strategy requires at least one allowed provider");
|
|
2563
|
+
for (const id of allowedProviderIds) if (!MEMORY_PROVIDER_ID.test(id)) throw new Error(`dsh-mnemon: invalid persistence strategy provider: ${String(id)}`);
|
|
2564
|
+
const dataBoundary = value?.rules?.dataBoundary ?? "allow-remote";
|
|
2565
|
+
if (dataBoundary !== "allow-remote" && dataBoundary !== "local-only") throw new Error(`dsh-mnemon: unsupported persistence data boundary: ${String(dataBoundary)}`);
|
|
2566
|
+
const requiredCapabilities = [...new Set(value?.rules?.requiredCapabilities ?? [])];
|
|
2567
|
+
for (const capability of requiredCapabilities) if (!MEMORY_PLACEMENT_CAPABILITY_SET.has(capability)) throw new Error(`dsh-mnemon: unsupported persistence capability: ${String(capability)}`);
|
|
2568
|
+
const preference = value?.rules?.preference ?? "balanced";
|
|
2569
|
+
if (!MEMORY_PLACEMENT_PREFERENCE_SET.has(preference)) throw new Error(`dsh-mnemon: unsupported persistence preference: ${String(preference)}`);
|
|
2570
|
+
const providerConnections = Object.fromEntries(Object.entries(value?.providerConnections ?? {}).flatMap(([id, connection]) => {
|
|
2571
|
+
if (!MEMORY_PROVIDER_ID.test(id) || connection === void 0) return [];
|
|
2572
|
+
return [[id, Object.fromEntries(Object.entries(connection).filter((entry) => [
|
|
2573
|
+
"string",
|
|
2574
|
+
"number",
|
|
2575
|
+
"boolean"
|
|
2576
|
+
].includes(typeof entry[1])))]];
|
|
2577
|
+
}));
|
|
2578
|
+
return {
|
|
2579
|
+
mode,
|
|
2580
|
+
providerId,
|
|
2581
|
+
prompt,
|
|
2582
|
+
rules: {
|
|
2583
|
+
allowedProviderIds,
|
|
2584
|
+
dataBoundary,
|
|
2585
|
+
requiredCapabilities,
|
|
2586
|
+
preference
|
|
2587
|
+
},
|
|
2588
|
+
providerConnections
|
|
2589
|
+
};
|
|
2590
|
+
}
|
|
2591
|
+
function resolveEmbedding(value) {
|
|
2592
|
+
const endpoint = optionalText(value?.endpoint) ?? "http://localhost:11434";
|
|
2593
|
+
if (endpoint.length > 2048) throw new Error("dsh-mnemon: embedding endpoint is too long");
|
|
2594
|
+
let parsed;
|
|
2595
|
+
try {
|
|
2596
|
+
parsed = new URL(endpoint);
|
|
2597
|
+
} catch {
|
|
2598
|
+
throw new Error("dsh-mnemon: embedding endpoint must be an absolute HTTP or HTTPS URL");
|
|
2599
|
+
}
|
|
2600
|
+
if (!["http:", "https:"].includes(parsed.protocol) || parsed.username !== "" || parsed.password !== "" || endpoint.includes("?") || endpoint.includes("#")) throw new Error("dsh-mnemon: embedding endpoint must be an absolute HTTP or HTTPS URL without credentials, query, or fragment");
|
|
2601
|
+
const normalizedEndpoint = endpoint.replace(/\/+$/u, "");
|
|
2602
|
+
const model = optionalText(value?.model) ?? "nomic-embed-text";
|
|
2603
|
+
if (model.length > 200 || /[\u0000-\u001f\u007f]/u.test(model)) throw new Error("dsh-mnemon: embedding model must contain 1..200 characters without control characters");
|
|
2604
|
+
const apiKey = optionalText(value?.apiKey) ?? "";
|
|
2605
|
+
if (apiKey.length > 2048 || /[\u0000-\u001f\u007f]/u.test(apiKey)) throw new Error("dsh-mnemon: embedding API key must contain 0..2048 characters without control characters");
|
|
2606
|
+
const protocol = value?.protocol ?? "auto";
|
|
2607
|
+
if (!MNEMON_EMBEDDING_PROTOCOLS.includes(protocol)) throw new Error(`dsh-mnemon: unsupported embedding protocol: ${String(protocol)}`);
|
|
2608
|
+
return {
|
|
2609
|
+
enabled: value?.enabled === true,
|
|
2610
|
+
endpoint: normalizedEndpoint,
|
|
2611
|
+
model,
|
|
2612
|
+
apiKey,
|
|
2613
|
+
protocol
|
|
2614
|
+
};
|
|
2615
|
+
}
|
|
2616
|
+
function resolveRecallQuality(value) {
|
|
2617
|
+
const policy = optionalText(value?.policy) ?? "strict-v1";
|
|
2618
|
+
if (!/^[a-z][a-z0-9-]{0,63}$/u.test(policy)) throw new Error("dsh-mnemon: recall quality policy id must match [a-z][a-z0-9-]{0,63}");
|
|
2619
|
+
const lowScoreThreshold = value?.lowScoreThreshold ?? .25;
|
|
2620
|
+
const highScoreThreshold = value?.highScoreThreshold ?? .6;
|
|
2621
|
+
const candidateMultiplier = value?.candidateMultiplier ?? 3;
|
|
2622
|
+
const maxMediumResults = value?.maxMediumResults ?? 4;
|
|
2623
|
+
const maxUnknownResults = value?.maxUnknownResults ?? 2;
|
|
2624
|
+
if (!Number.isFinite(lowScoreThreshold) || lowScoreThreshold < 0 || lowScoreThreshold > 1) throw new Error("dsh-mnemon: recall low score threshold must be within 0..1");
|
|
2625
|
+
if (!Number.isFinite(highScoreThreshold) || highScoreThreshold < 0 || highScoreThreshold > 1) throw new Error("dsh-mnemon: recall high score threshold must be within 0..1");
|
|
2626
|
+
if (lowScoreThreshold >= highScoreThreshold) throw new Error("dsh-mnemon: recall low score threshold must be less than the high score threshold");
|
|
2627
|
+
if (!Number.isInteger(candidateMultiplier) || candidateMultiplier < 1 || candidateMultiplier > 5) throw new Error("dsh-mnemon: recall candidate multiplier must be an integer within 1..5");
|
|
2628
|
+
if (!Number.isInteger(maxMediumResults) || maxMediumResults < 0 || maxMediumResults > 50) throw new Error("dsh-mnemon: recall max medium results must be an integer within 0..50");
|
|
2629
|
+
if (!Number.isInteger(maxUnknownResults) || maxUnknownResults < 0 || maxUnknownResults > 50) throw new Error("dsh-mnemon: recall max unknown results must be an integer within 0..50");
|
|
2630
|
+
return {
|
|
2631
|
+
policy,
|
|
2632
|
+
lowScoreThreshold,
|
|
2633
|
+
highScoreThreshold,
|
|
2634
|
+
candidateMultiplier,
|
|
2635
|
+
maxMediumResults,
|
|
2636
|
+
maxUnknownResults
|
|
2637
|
+
};
|
|
2638
|
+
}
|
|
2639
|
+
/** Capture only this Source's settings, with an instance-local default authority. */
|
|
2640
|
+
function resolveMemorySpacesConfig(value = {}, sourceInstanceKey = "standalone") {
|
|
2641
|
+
const config = MemorySpacesConfig(value);
|
|
2642
|
+
const requested = optionalText(config.dataDir);
|
|
2643
|
+
const dataDir = requested === void 0 ? join(homedir(), ".mnemon", "sources", encodeURIComponent(sourceInstanceKey)) : requested === "~" ? homedir() : requested.startsWith("~/") ? join(homedir(), requested.slice(2)) : requested;
|
|
2644
|
+
if (!isAbsolute(dataDir)) throw new Error("Memory Spaces dataDir must be absolute or start with ~/");
|
|
2645
|
+
const store = optionalText(config.store);
|
|
2646
|
+
if (store !== void 0 && !/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/u.test(store)) throw new Error("Memory Spaces store has an invalid identifier");
|
|
2647
|
+
const cliPath = optionalText(config.cliPath);
|
|
2648
|
+
return {
|
|
2649
|
+
storageScope: "custom",
|
|
2650
|
+
dataDir,
|
|
2651
|
+
...cliPath === void 0 ? {} : { cliPath },
|
|
2652
|
+
...store === void 0 ? {} : { store },
|
|
2653
|
+
timeoutMs: config.timeoutMs ?? 1e4,
|
|
2654
|
+
defaultRecallLimit: config.defaultRecallLimit ?? 10,
|
|
2655
|
+
writeEnabled: config.writeEnabled ?? true,
|
|
2656
|
+
embedding: resolveEmbedding(config.embedding),
|
|
2657
|
+
recallQuality: resolveRecallQuality(config.recallQuality),
|
|
2658
|
+
persistenceStrategy: resolvePersistenceStrategy(config.persistenceStrategy)
|
|
2659
|
+
};
|
|
2660
|
+
}
|
|
2661
|
+
//#endregion
|
|
2662
|
+
//#region src/source.ts
|
|
2663
|
+
const CATEGORIES = /* @__PURE__ */ new Set([
|
|
2664
|
+
"preference",
|
|
2665
|
+
"decision",
|
|
2666
|
+
"fact",
|
|
2667
|
+
"insight",
|
|
2668
|
+
"context",
|
|
2669
|
+
"general"
|
|
2670
|
+
]);
|
|
2671
|
+
const SOURCES = /* @__PURE__ */ new Set([
|
|
2672
|
+
"user",
|
|
2673
|
+
"agent",
|
|
2674
|
+
"external"
|
|
2675
|
+
]);
|
|
2676
|
+
const INTENTS = /* @__PURE__ */ new Set([
|
|
2677
|
+
"WHY",
|
|
2678
|
+
"WHEN",
|
|
2679
|
+
"ENTITY",
|
|
2680
|
+
"GENERAL"
|
|
2681
|
+
]);
|
|
2682
|
+
const EDGES = /* @__PURE__ */ new Set([
|
|
2683
|
+
"temporal",
|
|
2684
|
+
"semantic",
|
|
2685
|
+
"causal",
|
|
2686
|
+
"entity"
|
|
2687
|
+
]);
|
|
2688
|
+
function grantIds(grant) {
|
|
2689
|
+
return memoryInputStringArray(memoryInputRecord(grant.value, "Memory Spaces ReadGrant").memoryBodyIds, "memoryBodyIds", 1e4) ?? [];
|
|
2690
|
+
}
|
|
2691
|
+
function scalarRecord(value, label) {
|
|
2692
|
+
if (value === void 0) return void 0;
|
|
2693
|
+
const input = memoryInputRecord(value, label);
|
|
2694
|
+
const result = {};
|
|
2695
|
+
for (const [key, item] of Object.entries(input)) {
|
|
2696
|
+
if (!/^[a-z][a-zA-Z0-9_-]{0,127}$/u.test(key)) throw new Error(`${label} contains an invalid field: ${key}`);
|
|
2697
|
+
if (typeof item !== "string" && typeof item !== "number" && typeof item !== "boolean") throw new Error(`${label}.${key} must be a string, number, or boolean`);
|
|
2698
|
+
result[key] = item;
|
|
2699
|
+
}
|
|
2700
|
+
return result;
|
|
2701
|
+
}
|
|
2702
|
+
function providerConnections(value) {
|
|
2703
|
+
if (value === void 0) return void 0;
|
|
2704
|
+
const input = memoryInputRecord(value, "providerConnections");
|
|
2705
|
+
return Object.fromEntries(Object.entries(input).map(([providerId, connection]) => [memoryInputText(providerId, "providerId", 128), scalarRecord(connection, `providerConnections.${providerId}`)]));
|
|
2706
|
+
}
|
|
2707
|
+
function createBodyRequest(value) {
|
|
2708
|
+
const input = memoryInputRecord(value, "Memory Space body-create request");
|
|
2709
|
+
const providerId = memoryInputText(input.providerId, "providerId", 128, false);
|
|
2710
|
+
const connection = scalarRecord(input.connection, "connection");
|
|
2711
|
+
const connections = providerConnections(input.providerConnections);
|
|
2712
|
+
const placementValue = input.placement === void 0 ? void 0 : memoryInputRecord(input.placement, "placement");
|
|
2713
|
+
const rulesValue = placementValue?.rules === void 0 ? void 0 : memoryInputRecord(placementValue.rules, "placement.rules");
|
|
2714
|
+
const allowedProviderIds = rulesValue === void 0 ? void 0 : memoryInputStringArray(rulesValue.allowedProviderIds, "placement.rules.allowedProviderIds", 100);
|
|
2715
|
+
const requiredCapabilities = rulesValue === void 0 ? void 0 : memoryInputStringArray(rulesValue.requiredCapabilities, "placement.rules.requiredCapabilities", 20);
|
|
2716
|
+
const placement = placementValue === void 0 ? void 0 : {
|
|
2717
|
+
mode: memoryInputText(placementValue.mode, "placement.mode", 20),
|
|
2718
|
+
...memoryInputText(placementValue.prompt, "placement.prompt", 4e3, false) === void 0 ? {} : { prompt: memoryInputText(placementValue.prompt, "placement.prompt", 4e3, false) },
|
|
2719
|
+
...rulesValue === void 0 ? {} : { rules: {
|
|
2720
|
+
...allowedProviderIds === void 0 ? {} : { allowedProviderIds },
|
|
2721
|
+
...memoryInputText(rulesValue.dataBoundary, "placement.rules.dataBoundary", 30, false) === void 0 ? {} : { dataBoundary: memoryInputText(rulesValue.dataBoundary, "placement.rules.dataBoundary", 30, false) },
|
|
2722
|
+
...requiredCapabilities === void 0 ? {} : { requiredCapabilities },
|
|
2723
|
+
...memoryInputText(rulesValue.preference, "placement.rules.preference", 30, false) === void 0 ? {} : { preference: memoryInputText(rulesValue.preference, "placement.rules.preference", 30, false) }
|
|
2724
|
+
} }
|
|
2725
|
+
};
|
|
2726
|
+
return {
|
|
2727
|
+
name: memoryInputText(input.name, "name", 100),
|
|
2728
|
+
description: memoryInputText(input.description, "description", 1e3),
|
|
2729
|
+
...typeof input.active !== "boolean" ? {} : { active: input.active },
|
|
2730
|
+
...providerId === void 0 ? {} : { providerId },
|
|
2731
|
+
...connection === void 0 ? {} : { connection },
|
|
2732
|
+
...connections === void 0 ? {} : { providerConnections: connections },
|
|
2733
|
+
...placement === void 0 ? {} : { placement }
|
|
2734
|
+
};
|
|
2735
|
+
}
|
|
2736
|
+
function updateBodyRequest(value) {
|
|
2737
|
+
const input = memoryInputRecord(value, "Memory Space body-update request");
|
|
2738
|
+
const connection = scalarRecord(input.connection, "connection");
|
|
2739
|
+
return {
|
|
2740
|
+
memoryBodyId: memoryInputText(input.memoryBodyId, "memoryBodyId", 300),
|
|
2741
|
+
request: {
|
|
2742
|
+
...memoryInputText(input.name, "name", 100, false) === void 0 ? {} : { name: memoryInputText(input.name, "name", 100, false) },
|
|
2743
|
+
...memoryInputText(input.description, "description", 1e3, false) === void 0 ? {} : { description: memoryInputText(input.description, "description", 1e3, false) },
|
|
2744
|
+
...typeof input.active !== "boolean" ? {} : { active: input.active },
|
|
2745
|
+
...connection === void 0 ? {} : { connection },
|
|
2746
|
+
...memoryInputStringArray(input.clearSecrets, "clearSecrets", 100) === void 0 ? {} : { clearSecrets: memoryInputStringArray(input.clearSecrets, "clearSecrets", 100) }
|
|
2747
|
+
}
|
|
2748
|
+
};
|
|
2749
|
+
}
|
|
2750
|
+
function placementDecision(value) {
|
|
2751
|
+
if (value === void 0) return void 0;
|
|
2752
|
+
const input = memoryInputRecord(value, "Memory Space placement decision");
|
|
2753
|
+
return {
|
|
2754
|
+
mode: memoryInputText(input.mode, "placement decision mode", 20),
|
|
2755
|
+
providerId: memoryInputText(input.providerId, "placement decision providerId", 128),
|
|
2756
|
+
decidedBy: memoryInputText(input.decidedBy, "placement decision decidedBy", 20),
|
|
2757
|
+
reason: memoryInputText(input.reason, "placement decision reason", 1e3),
|
|
2758
|
+
confidence: memoryInputText(input.confidence, "placement decision confidence", 20),
|
|
2759
|
+
candidateProviderIds: memoryInputStringArray(input.candidateProviderIds, "placement decision candidateProviderIds", 100),
|
|
2760
|
+
appliedRules: memoryInputStringArray(input.appliedRules, "placement decision appliedRules", 100),
|
|
2761
|
+
decidedAt: memoryInputText(input.decidedAt, "placement decision decidedAt", 100),
|
|
2762
|
+
...memoryInputText(input.runId, "placement decision runId", 500, false) === void 0 ? {} : { runId: memoryInputText(input.runId, "placement decision runId", 500, false) },
|
|
2763
|
+
...memoryInputText(input.subagentProvider, "placement decision subagentProvider", 500, false) === void 0 ? {} : { subagentProvider: memoryInputText(input.subagentProvider, "placement decision subagentProvider", 500, false) }
|
|
2764
|
+
};
|
|
2765
|
+
}
|
|
2766
|
+
function managementResult(service, value) {
|
|
2767
|
+
return {
|
|
2768
|
+
revision: service.memoryRevision(),
|
|
2769
|
+
value
|
|
2770
|
+
};
|
|
2771
|
+
}
|
|
2772
|
+
async function manageMemorySpaces(service, request) {
|
|
2773
|
+
const input = request.input === null ? {} : memoryInputRecord(request.input, `Memory Spaces management ${request.operation}`);
|
|
2774
|
+
if (request.mode === "read") switch (request.operation) {
|
|
2775
|
+
case "embedding-status": return managementResult(service, await service.embeddingStatus(request.signal));
|
|
2776
|
+
case "status-summary": return managementResult(service, service.statusSummary());
|
|
2777
|
+
case "status": return managementResult(service, await service.status(request.signal));
|
|
2778
|
+
case "body-directory": return managementResult(service, service.bodyDirectory());
|
|
2779
|
+
case "bodies": return managementResult(service, await service.bodies(request.signal));
|
|
2780
|
+
case "provider-services": return managementResult(service, service.memoryBodies.providerServices());
|
|
2781
|
+
case "graph": return managementResult(service, await service.graph(request.signal, memoryInputStringArray(input.memoryBodyIds, "memoryBodyIds", 1e4)));
|
|
2782
|
+
case "list": return managementResult(service, await service.list({
|
|
2783
|
+
...memoryInputText(input.query, "query", 2e3, false) === void 0 ? {} : { query: memoryInputText(input.query, "query", 2e3, false) },
|
|
2784
|
+
...memoryInputText(input.category, "category", 30, false) === void 0 ? {} : { category: memoryInputText(input.category, "category", 30, false) },
|
|
2785
|
+
...input.limit === void 0 ? {} : { limit: memoryInputInteger(input.limit, 100, 1, 1e4) },
|
|
2786
|
+
...memoryInputStringArray(input.memoryBodyIds, "memoryBodyIds", 1e4) === void 0 ? {} : { memoryBodyIds: memoryInputStringArray(input.memoryBodyIds, "memoryBodyIds", 1e4) }
|
|
2787
|
+
}, request.signal));
|
|
2788
|
+
case "entities": return managementResult(service, await service.entities(memoryInputText(input.entity, "entity", 500, false), input.limit === void 0 ? void 0 : memoryInputInteger(input.limit, 100, 1, 1e4), request.signal));
|
|
2789
|
+
case "search": return managementResult(service, await service.search({
|
|
2790
|
+
query: memoryInputText(input.query, "query", 2e3),
|
|
2791
|
+
...memoryInputText(input.mode, "mode", 20, false) === void 0 ? {} : { mode: memoryInputText(input.mode, "mode", 20, false) },
|
|
2792
|
+
...input.limit === void 0 ? {} : { limit: memoryInputInteger(input.limit, 10, 1, 1e3) },
|
|
2793
|
+
...memoryInputText(input.category, "category", 30, false) === void 0 ? {} : { category: memoryInputText(input.category, "category", 30, false) },
|
|
2794
|
+
...memoryInputText(input.source, "source", 30, false) === void 0 ? {} : { source: memoryInputText(input.source, "source", 30, false) },
|
|
2795
|
+
...memoryInputText(input.intent, "intent", 30, false) === void 0 ? {} : { intent: memoryInputText(input.intent, "intent", 30, false) },
|
|
2796
|
+
...memoryInputStringArray(input.memoryBodyIds, "memoryBodyIds", 1e4) === void 0 ? {} : { memoryBodyIds: memoryInputStringArray(input.memoryBodyIds, "memoryBodyIds", 1e4) }
|
|
2797
|
+
}, request.signal));
|
|
2798
|
+
case "related": return managementResult(service, await service.related(memoryInputText(input.id, "id", 2e3), memoryInputInteger(input.depth, 2, 1, 5), memoryInputText(input.edge, "edge", 30, false), request.signal, memoryInputText(input.memoryBodyId, "memoryBodyId", 300, false)));
|
|
2799
|
+
case "body-reconnect": return managementResult(service, await service.reconnectBody(memoryInputText(input.memoryBodyId, "memoryBodyId", 300), request.signal));
|
|
2800
|
+
case "prepare-body-placement": return managementResult(service, service.prepareBodyPlacement(createBodyRequest(request.input)));
|
|
2801
|
+
case "finalize-placement": {
|
|
2802
|
+
const prepared = memoryInputRecord(input.prepared, "prepared placement");
|
|
2803
|
+
if (!Array.isArray(prepared.candidates) || prepared.candidates.length === 0) throw new Error("placement candidates are required");
|
|
2804
|
+
const selection = input.selection === void 0 ? void 0 : memoryInputRecord(input.selection, "placement selection");
|
|
2805
|
+
return managementResult(service, selection === void 0 ? rulesOnlyPlacement(prepared) ?? null : finalizeLlmPlacement(prepared, {
|
|
2806
|
+
providerId: memoryInputText(selection.providerId, "providerId", 128),
|
|
2807
|
+
reason: memoryInputText(selection.reason, "reason", 4e3),
|
|
2808
|
+
confidence: memoryInputText(selection.confidence, "confidence", 40)
|
|
2809
|
+
}, {
|
|
2810
|
+
runId: memoryInputText(input.runId, "runId", 300),
|
|
2811
|
+
provider: memoryInputText(input.provider, "provider", 300)
|
|
2812
|
+
}));
|
|
2813
|
+
}
|
|
2814
|
+
case "metadata-sample": return managementResult(service, await service.metadataSample(memoryInputText(input.memoryBodyId, "memoryBodyId", 300), request.signal));
|
|
2815
|
+
default: throw new Error(`unsupported Memory Spaces management read operation: ${request.operation}`);
|
|
2816
|
+
}
|
|
2817
|
+
if (!request.confirmed) throw new Error("Memory Spaces management mutation requires explicit confirmation");
|
|
2818
|
+
switch (request.operation) {
|
|
2819
|
+
case "provider-service-update": {
|
|
2820
|
+
const providerId = memoryInputText(input.providerId, "providerId", 128);
|
|
2821
|
+
await service.updateProviderService(providerId, scalarRecord(input.settings, "settings") ?? {}, memoryInputStringArray(input.clearSecrets, "clearSecrets", 100) ?? [], input.enabled === void 0 ? true : input.enabled === true, request.signal);
|
|
2822
|
+
return managementResult(service, service.memoryBodies.providerServices().items.find((item) => item.providerId === providerId));
|
|
2823
|
+
}
|
|
2824
|
+
case "remember": return managementResult(service, await service.remember({
|
|
2825
|
+
content: memoryInputText(input.content, "content", 1e5),
|
|
2826
|
+
...memoryInputText(input.category, "category", 30, false) === void 0 ? {} : { category: memoryInputText(input.category, "category", 30, false) },
|
|
2827
|
+
...typeof input.importance !== "number" ? {} : { importance: input.importance },
|
|
2828
|
+
...memoryInputStringArray(input.tags, "tags", 100) === void 0 ? {} : { tags: memoryInputStringArray(input.tags, "tags", 100) },
|
|
2829
|
+
...memoryInputStringArray(input.entities, "entities", 100) === void 0 ? {} : { entities: memoryInputStringArray(input.entities, "entities", 100) },
|
|
2830
|
+
...memoryInputText(input.source, "source", 30, false) === void 0 ? {} : { source: memoryInputText(input.source, "source", 30, false) },
|
|
2831
|
+
...memoryInputText(input.memoryBodyId, "memoryBodyId", 300, false) === void 0 ? {} : { memoryBodyId: memoryInputText(input.memoryBodyId, "memoryBodyId", 300, false) }
|
|
2832
|
+
}, request.signal));
|
|
2833
|
+
case "link": return managementResult(service, await service.link(memoryInputText(input.sourceId, "sourceId", 2e3), memoryInputText(input.targetId, "targetId", 2e3), memoryInputText(input.type, "type", 30, false) ?? "semantic", typeof input.weight === "number" ? input.weight : .5, memoryInputText(input.reason, "reason", 1e3, false), request.signal, memoryInputText(input.memoryBodyId, "memoryBodyId", 300, false)));
|
|
2834
|
+
case "forget": return managementResult(service, await service.forget(memoryInputText(input.id, "id", 2e3), request.signal, memoryInputText(input.memoryBodyId, "memoryBodyId", 300, false)));
|
|
2835
|
+
case "body-create": {
|
|
2836
|
+
const bodyInput = input.request ?? request.input;
|
|
2837
|
+
return managementResult(service, await service.createBody(createBodyRequest(bodyInput), request.signal, placementDecision(input.placementDecision)));
|
|
2838
|
+
}
|
|
2839
|
+
case "body-update": {
|
|
2840
|
+
const parsed = updateBodyRequest(request.input);
|
|
2841
|
+
return managementResult(service, service.updateBody(parsed.memoryBodyId, parsed.request));
|
|
2842
|
+
}
|
|
2843
|
+
case "remember-many":
|
|
2844
|
+
if (!Array.isArray(input.requests) || input.requests.length > 1e3) throw new Error("remember-many requires at most 1000 requests");
|
|
2845
|
+
return managementResult(service, await service.rememberMany(input.requests.map((value) => memoryInputRecord(value, "remember request")), request.signal));
|
|
2846
|
+
case "body-create-for-persistence": {
|
|
2847
|
+
const selection = input.selection === void 0 ? void 0 : memoryInputRecord(input.selection, "placement selection");
|
|
2848
|
+
return managementResult(service, await service.createBodyForPersistence(createBodyRequest(input.request ?? request.input), selection === void 0 ? void 0 : {
|
|
2849
|
+
providerId: memoryInputText(selection.providerId, "providerId", 128),
|
|
2850
|
+
reason: memoryInputText(selection.reason, "reason", 4e3),
|
|
2851
|
+
confidence: memoryInputText(selection.confidence, "confidence", 40)
|
|
2852
|
+
}, request.signal));
|
|
2853
|
+
}
|
|
2854
|
+
case "body-metadata-update": {
|
|
2855
|
+
if (!Array.isArray(input.updates) || input.updates.length > 20) throw new Error("metadata update requires at most 20 entries");
|
|
2856
|
+
const updates = input.updates.map((value) => {
|
|
2857
|
+
const update = memoryInputRecord(value, "metadata update");
|
|
2858
|
+
return {
|
|
2859
|
+
memoryBodyId: memoryInputText(update.memoryBodyId, "memoryBodyId", 300),
|
|
2860
|
+
title: memoryInputText(update.title, "title", 48),
|
|
2861
|
+
description: memoryInputText(update.description, "description", 200)
|
|
2862
|
+
};
|
|
2863
|
+
});
|
|
2864
|
+
return managementResult(service, service.updateBodyMetadata(updates));
|
|
2865
|
+
}
|
|
2866
|
+
case "body-merge": return managementResult(service, await service.mergeBodies(memoryInputText(input.targetMemoryBodyId, "targetMemoryBodyId", 300), memoryInputStringArray(input.sourceMemoryBodyIds, "sourceMemoryBodyIds", 1e3) ?? [], input.deactivateSources !== false, request.signal));
|
|
2867
|
+
case "reload":
|
|
2868
|
+
service.memoryBodies.reload();
|
|
2869
|
+
return managementResult(service, { reloaded: true });
|
|
2870
|
+
case "body-delete": return managementResult(service, await service.deleteBody(memoryInputText(input.memoryBodyId, "memoryBodyId", 300), request.signal));
|
|
2871
|
+
default: throw new Error(`unsupported Memory Spaces management mutation operation: ${request.operation}`);
|
|
2872
|
+
}
|
|
2873
|
+
}
|
|
2874
|
+
function createMemorySpacesSource(providerSnapshot, config = {}) {
|
|
2875
|
+
const capturedConfig = structuredClone(config);
|
|
2876
|
+
return defineMemorySource({
|
|
2877
|
+
manifest: {
|
|
2878
|
+
apiVersion: COMPOSABLE_MEMORY_API_VERSION,
|
|
2879
|
+
kind: "source",
|
|
2880
|
+
typeId: "memory-spaces",
|
|
2881
|
+
packageName: "dsh-mnemon-source-memory-spaces",
|
|
2882
|
+
role: "durable-evidence",
|
|
2883
|
+
capabilities: [
|
|
2884
|
+
"status",
|
|
2885
|
+
"project",
|
|
2886
|
+
"recall",
|
|
2887
|
+
"related",
|
|
2888
|
+
"write",
|
|
2889
|
+
"link",
|
|
2890
|
+
"forget"
|
|
2891
|
+
],
|
|
2892
|
+
consistency: "namespace-pinned-live-read",
|
|
2893
|
+
routes: [
|
|
2894
|
+
{
|
|
2895
|
+
id: "inspect",
|
|
2896
|
+
description: "Inspect bounded Memory Space health or routing metadata without exposing storage paths or credentials.",
|
|
2897
|
+
capability: "status",
|
|
2898
|
+
inputSchema: {
|
|
2899
|
+
type: "object",
|
|
2900
|
+
required: ["section"],
|
|
2901
|
+
additionalProperties: false,
|
|
2902
|
+
properties: { section: {
|
|
2903
|
+
type: "string",
|
|
2904
|
+
enum: ["directory", "health"]
|
|
2905
|
+
} }
|
|
2906
|
+
},
|
|
2907
|
+
maxCalls: 4,
|
|
2908
|
+
maxResults: 1,
|
|
2909
|
+
maxCharacters: 12e3
|
|
2910
|
+
},
|
|
2911
|
+
{
|
|
2912
|
+
id: "recall",
|
|
2913
|
+
description: "Recall evidence only from Memory Spaces pinned into this View.",
|
|
2914
|
+
capability: "recall",
|
|
2915
|
+
inputSchema: {
|
|
2916
|
+
type: "object",
|
|
2917
|
+
required: ["query"],
|
|
2918
|
+
additionalProperties: false,
|
|
2919
|
+
properties: {
|
|
2920
|
+
query: { type: "string" },
|
|
2921
|
+
mode: {
|
|
2922
|
+
type: "string",
|
|
2923
|
+
enum: [
|
|
2924
|
+
"smart",
|
|
2925
|
+
"keyword",
|
|
2926
|
+
"basic"
|
|
2927
|
+
]
|
|
2928
|
+
},
|
|
2929
|
+
limit: { type: "integer" },
|
|
2930
|
+
category: { type: "string" },
|
|
2931
|
+
source: { type: "string" },
|
|
2932
|
+
intent: { type: "string" },
|
|
2933
|
+
memoryBodyIds: { type: "array" }
|
|
2934
|
+
}
|
|
2935
|
+
},
|
|
2936
|
+
maxCalls: 4,
|
|
2937
|
+
maxResults: 20,
|
|
2938
|
+
maxCharacters: 16e3
|
|
2939
|
+
},
|
|
2940
|
+
{
|
|
2941
|
+
id: "related",
|
|
2942
|
+
description: "Traverse related memories only from evidence already admitted by this View.",
|
|
2943
|
+
capability: "related",
|
|
2944
|
+
inputSchema: {
|
|
2945
|
+
type: "object",
|
|
2946
|
+
required: ["id"],
|
|
2947
|
+
additionalProperties: false,
|
|
2948
|
+
properties: {
|
|
2949
|
+
id: { type: "string" },
|
|
2950
|
+
depth: { type: "integer" },
|
|
2951
|
+
edge: { type: "string" },
|
|
2952
|
+
memoryBodyId: { type: "string" }
|
|
2953
|
+
}
|
|
2954
|
+
},
|
|
2955
|
+
maxCalls: 4,
|
|
2956
|
+
maxResults: 20,
|
|
2957
|
+
maxCharacters: 16e3
|
|
2958
|
+
}
|
|
2959
|
+
],
|
|
2960
|
+
actions: [
|
|
2961
|
+
{
|
|
2962
|
+
id: "manage-spaces",
|
|
2963
|
+
description: "Create a Memory Space under the configured persistence policy, or update/merge spaces in this View scope.",
|
|
2964
|
+
capability: "write",
|
|
2965
|
+
inputSchema: {
|
|
2966
|
+
type: "object",
|
|
2967
|
+
required: ["operation"],
|
|
2968
|
+
additionalProperties: false,
|
|
2969
|
+
properties: {
|
|
2970
|
+
operation: {
|
|
2971
|
+
type: "string",
|
|
2972
|
+
enum: [
|
|
2973
|
+
"create",
|
|
2974
|
+
"update",
|
|
2975
|
+
"merge"
|
|
2976
|
+
]
|
|
2977
|
+
},
|
|
2978
|
+
request: { type: "object" },
|
|
2979
|
+
selection: { type: "object" },
|
|
2980
|
+
memoryBodyId: { type: "string" },
|
|
2981
|
+
name: { type: "string" },
|
|
2982
|
+
description: { type: "string" },
|
|
2983
|
+
active: { type: "boolean" },
|
|
2984
|
+
targetMemoryBodyId: { type: "string" },
|
|
2985
|
+
sourceMemoryBodyIds: { type: "array" },
|
|
2986
|
+
deactivateSources: { type: "boolean" }
|
|
2987
|
+
}
|
|
2988
|
+
}
|
|
2989
|
+
},
|
|
2990
|
+
{
|
|
2991
|
+
id: "remember",
|
|
2992
|
+
description: "Record memory using an authorized Space and its Provider; the receipt distinguishes accepted extraction from commitment.",
|
|
2993
|
+
capability: "write",
|
|
2994
|
+
inputSchema: {
|
|
2995
|
+
type: "object",
|
|
2996
|
+
required: ["content"],
|
|
2997
|
+
additionalProperties: false,
|
|
2998
|
+
properties: {
|
|
2999
|
+
content: { type: "string" },
|
|
3000
|
+
category: { type: "string" },
|
|
3001
|
+
importance: { type: "number" },
|
|
3002
|
+
tags: { type: "array" },
|
|
3003
|
+
entities: { type: "array" },
|
|
3004
|
+
source: { type: "string" },
|
|
3005
|
+
memoryBodyId: { type: "string" }
|
|
3006
|
+
}
|
|
3007
|
+
}
|
|
3008
|
+
},
|
|
3009
|
+
{
|
|
3010
|
+
id: "link",
|
|
3011
|
+
description: "Link two evidence items admitted by this View and owned by the same Memory Space.",
|
|
3012
|
+
capability: "link",
|
|
3013
|
+
inputSchema: {
|
|
3014
|
+
type: "object",
|
|
3015
|
+
required: ["sourceId", "targetId"],
|
|
3016
|
+
additionalProperties: false,
|
|
3017
|
+
properties: {
|
|
3018
|
+
sourceId: { type: "string" },
|
|
3019
|
+
targetId: { type: "string" },
|
|
3020
|
+
memoryBodyId: { type: "string" },
|
|
3021
|
+
type: { type: "string" },
|
|
3022
|
+
weight: { type: "number" },
|
|
3023
|
+
reason: { type: "string" }
|
|
3024
|
+
}
|
|
3025
|
+
}
|
|
3026
|
+
},
|
|
3027
|
+
{
|
|
3028
|
+
id: "forget",
|
|
3029
|
+
description: "Forget one returned evidence item using its Provider deletion mode (soft or hard); not a guarantee of universal erasure.",
|
|
3030
|
+
capability: "forget",
|
|
3031
|
+
inputSchema: {
|
|
3032
|
+
type: "object",
|
|
3033
|
+
required: ["id"],
|
|
3034
|
+
additionalProperties: false,
|
|
3035
|
+
properties: {
|
|
3036
|
+
id: { type: "string" },
|
|
3037
|
+
memoryBodyId: { type: "string" }
|
|
3038
|
+
}
|
|
3039
|
+
}
|
|
3040
|
+
}
|
|
3041
|
+
],
|
|
3042
|
+
management: {
|
|
3043
|
+
label: "Memory Spaces",
|
|
3044
|
+
description: "Durable evidence across local and remote Provider-backed namespaces."
|
|
3045
|
+
}
|
|
3046
|
+
},
|
|
3047
|
+
create(context) {
|
|
3048
|
+
const resolved = resolveMemorySpacesConfig({
|
|
3049
|
+
...context.configuration,
|
|
3050
|
+
...capturedConfig
|
|
3051
|
+
}, context.sourceInstanceKey);
|
|
3052
|
+
const service = new MemorySpacesService(createRunner(resolved), resolved, void 0, void 0, providerSnapshot.adapterRegistry(), new MemoryProviderCatalog(providerSnapshot.descriptors()));
|
|
3053
|
+
const canRemember = providerSnapshot.entries.some((entry) => entry.definition.manifest.capabilities.remember);
|
|
3054
|
+
const prepared = /* @__PURE__ */ new WeakMap();
|
|
3055
|
+
const sourceState = (scope) => {
|
|
3056
|
+
const value = service.memoryState();
|
|
3057
|
+
prepared.set(scope, value);
|
|
3058
|
+
return value;
|
|
3059
|
+
};
|
|
3060
|
+
const admittedByView = /* @__PURE__ */ new Map();
|
|
3061
|
+
const createdByView = /* @__PURE__ */ new Map();
|
|
3062
|
+
const admit = (viewId, entries) => {
|
|
3063
|
+
const current = admittedByView.get(viewId) ?? /* @__PURE__ */ new Map();
|
|
3064
|
+
for (const entry of entries) if (entry.memoryBodyId !== void 0) current.set(entry.memoryBodyId + "/" + entry.id, entry.memoryBodyId);
|
|
3065
|
+
admittedByView.delete(viewId);
|
|
3066
|
+
admittedByView.set(viewId, current);
|
|
3067
|
+
while (admittedByView.size > 128) admittedByView.delete(admittedByView.keys().next().value);
|
|
3068
|
+
};
|
|
3069
|
+
const evidence = (request, items, unavailable) => {
|
|
3070
|
+
const catalog = request.route.sourceRouteId === "inspect";
|
|
3071
|
+
let remaining = request.route.maxCharacters ?? 16e3;
|
|
3072
|
+
let truncated = false;
|
|
3073
|
+
const visible = items.slice(0, request.route.maxResults ?? 20).flatMap((item) => {
|
|
3074
|
+
if (remaining <= 0 || catalog && item.content.length > remaining) {
|
|
3075
|
+
truncated = true;
|
|
3076
|
+
return [];
|
|
3077
|
+
}
|
|
3078
|
+
const content = catalog ? item.content : truncateMemoryText(item.content, remaining);
|
|
3079
|
+
remaining -= content.length;
|
|
3080
|
+
const clipped = content !== item.content;
|
|
3081
|
+
truncated ||= clipped;
|
|
3082
|
+
return [{
|
|
3083
|
+
item,
|
|
3084
|
+
content,
|
|
3085
|
+
clipped
|
|
3086
|
+
}];
|
|
3087
|
+
});
|
|
3088
|
+
truncated ||= visible.length < items.length;
|
|
3089
|
+
if (!catalog) admit(request.view.id, visible.map(({ item }) => item));
|
|
3090
|
+
return {
|
|
3091
|
+
id: `evidence:${randomUUID()}`,
|
|
3092
|
+
viewId: request.view.id,
|
|
3093
|
+
routeId: request.route.id,
|
|
3094
|
+
sourceInstanceKey: context.sourceInstanceKey,
|
|
3095
|
+
observedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3096
|
+
items: visible.map(({ item, content, clipped }) => ({
|
|
3097
|
+
id: item.id,
|
|
3098
|
+
text: content,
|
|
3099
|
+
...(item.normalizedScore ?? item.score) === void 0 ? {} : { score: item.normalizedScore ?? item.score },
|
|
3100
|
+
...item.createdAt === void 0 ? {} : { revision: item.createdAt },
|
|
3101
|
+
provenance: {
|
|
3102
|
+
...item.memoryBodyId === void 0 ? {} : { memoryBodyId: item.memoryBodyId },
|
|
3103
|
+
...item.memoryBodyName === void 0 ? {} : { memoryBodyName: item.memoryBodyName },
|
|
3104
|
+
...item.memoryProviderId === void 0 ? {} : { memoryProviderId: item.memoryProviderId },
|
|
3105
|
+
...item.memoryProviderLabel === void 0 ? {} : { memoryProviderLabel: item.memoryProviderLabel },
|
|
3106
|
+
...item.memoryCapabilities === void 0 ? {} : { memoryCapabilities: { ...item.memoryCapabilities } },
|
|
3107
|
+
...item.relevanceTier === void 0 ? {} : { relevanceTier: item.relevanceTier },
|
|
3108
|
+
...item.category === void 0 ? {} : { category: item.category },
|
|
3109
|
+
...item.importance === void 0 ? {} : { importance: item.importance },
|
|
3110
|
+
...item.createdAt === void 0 ? {} : { createdAt: item.createdAt },
|
|
3111
|
+
...item.tags === void 0 ? {} : { tags: item.tags },
|
|
3112
|
+
...item.entities === void 0 ? {} : { entities: item.entities },
|
|
3113
|
+
...item.source === void 0 ? {} : { source: item.source },
|
|
3114
|
+
...item.depth === void 0 ? {} : { depth: item.depth },
|
|
3115
|
+
...item.edgeType === void 0 ? {} : { edgeType: item.edgeType },
|
|
3116
|
+
...item.externalUri === void 0 ? {} : { externalUri: item.externalUri }
|
|
3117
|
+
}
|
|
3118
|
+
})),
|
|
3119
|
+
truncated,
|
|
3120
|
+
...unavailable === void 0 ? {} : { unavailable },
|
|
3121
|
+
...catalog && visible.length === 0 ? { unavailable: "Inspection cannot fit the View budget as valid catalog JSON." } : {}
|
|
3122
|
+
};
|
|
3123
|
+
};
|
|
3124
|
+
return {
|
|
3125
|
+
facts(request) {
|
|
3126
|
+
const { active, revision } = sourceState(request.scope);
|
|
3127
|
+
const routeIds = [
|
|
3128
|
+
"inspect",
|
|
3129
|
+
...active.some((body) => body.provider.capabilities.search) ? ["recall"] : [],
|
|
3130
|
+
...active.some((body) => body.provider.capabilities.related) ? ["related"] : []
|
|
3131
|
+
];
|
|
3132
|
+
const actionIds = service.config.writeEnabled ? [
|
|
3133
|
+
"manage-spaces",
|
|
3134
|
+
...canRemember ? ["remember"] : [],
|
|
3135
|
+
...active.some((body) => body.provider.capabilities.link) ? ["link"] : [],
|
|
3136
|
+
...active.some((body) => body.provider.capabilities.forget) ? ["forget"] : []
|
|
3137
|
+
] : [];
|
|
3138
|
+
const capabilities = ["status", "project"];
|
|
3139
|
+
if (routeIds.includes("recall")) capabilities.push("recall");
|
|
3140
|
+
if (routeIds.includes("related")) capabilities.push("related");
|
|
3141
|
+
if (actionIds.includes("manage-spaces") || actionIds.includes("remember")) capabilities.push("write");
|
|
3142
|
+
if (actionIds.includes("link")) capabilities.push("link");
|
|
3143
|
+
if (actionIds.includes("forget")) capabilities.push("forget");
|
|
3144
|
+
return {
|
|
3145
|
+
sourceInstanceKey: context.sourceInstanceKey,
|
|
3146
|
+
sourceTypeId: "memory-spaces",
|
|
3147
|
+
role: "durable-evidence",
|
|
3148
|
+
availability: active.length === 0 ? "degraded" : "ready",
|
|
3149
|
+
revision,
|
|
3150
|
+
capabilities: [...capabilities],
|
|
3151
|
+
routeIds,
|
|
3152
|
+
actionIds,
|
|
3153
|
+
hints: {
|
|
3154
|
+
activeCount: active.length,
|
|
3155
|
+
providerCount: new Set(active.map((body) => body.provider.id)).size
|
|
3156
|
+
}
|
|
3157
|
+
};
|
|
3158
|
+
},
|
|
3159
|
+
project(request) {
|
|
3160
|
+
const { all, active, revision } = prepared.get(request.scope) ?? sourceState(request.scope);
|
|
3161
|
+
prepared.delete(request.scope);
|
|
3162
|
+
if (revision !== request.expectedRevision) throw new Error("Memory Spaces projection revision changed during composition");
|
|
3163
|
+
const cover = `${active.length} active of ${all.length} configured Memory Space${all.length === 1 ? "" : "s"} available through scoped recall.`;
|
|
3164
|
+
return {
|
|
3165
|
+
fragments: request.includeProjection ? [{
|
|
3166
|
+
id: `${context.sourceInstanceKey}/projection`,
|
|
3167
|
+
sourceInstanceKey: context.sourceInstanceKey,
|
|
3168
|
+
mode: request.mode,
|
|
3169
|
+
text: truncateMemoryText(cover, request.maxCharacters),
|
|
3170
|
+
revision,
|
|
3171
|
+
provenance: { sourceTypeId: "memory-spaces" }
|
|
3172
|
+
}] : [],
|
|
3173
|
+
readGrant: {
|
|
3174
|
+
id: `${context.sourceInstanceKey}/grant/${revision}`,
|
|
3175
|
+
sourceInstanceKey: context.sourceInstanceKey,
|
|
3176
|
+
schema: "dsh-mnemon.memory-spaces/v1",
|
|
3177
|
+
value: {
|
|
3178
|
+
memoryBodyIds: active.map((body) => body.id),
|
|
3179
|
+
knownMemoryBodyIds: all.map((body) => body.id)
|
|
3180
|
+
},
|
|
3181
|
+
revision,
|
|
3182
|
+
consistency: "namespace-pinned-live-read"
|
|
3183
|
+
},
|
|
3184
|
+
presentation: {
|
|
3185
|
+
visibleItems: active.length,
|
|
3186
|
+
totalItems: all.length,
|
|
3187
|
+
items: active.slice(0, 24).map((body) => ({
|
|
3188
|
+
id: body.id,
|
|
3189
|
+
title: truncateMemoryText(body.name, 160),
|
|
3190
|
+
...body.description.trim() === "" ? {} : { excerpt: truncateMemoryText(body.description, 600) }
|
|
3191
|
+
}))
|
|
3192
|
+
}
|
|
3193
|
+
};
|
|
3194
|
+
},
|
|
3195
|
+
async query(request) {
|
|
3196
|
+
const allowedBodies = grantIds(request.grant);
|
|
3197
|
+
const input = memoryInputRecord(request.input, `Memory Spaces ${request.route.sourceRouteId}`);
|
|
3198
|
+
if (request.route.sourceRouteId === "inspect") {
|
|
3199
|
+
const known = memoryInputStringArray(memoryInputRecord(request.grant.value, "Memory Spaces scope").knownMemoryBodyIds, "knownMemoryBodyIds", 1e4) ?? allowedBodies;
|
|
3200
|
+
let value;
|
|
3201
|
+
if (input.section === "directory") {
|
|
3202
|
+
const catalog = service.bodyDirectory();
|
|
3203
|
+
const owned = createdByView.get(request.view.id);
|
|
3204
|
+
value = modelBodyCatalog({
|
|
3205
|
+
...catalog,
|
|
3206
|
+
items: catalog.items.filter((body) => known.includes(body.id) || owned?.has(body.id))
|
|
3207
|
+
});
|
|
3208
|
+
} else if (input.section === "health") value = modelStatus(await service.status(request.signal));
|
|
3209
|
+
else throw new Error("unknown Memory Spaces inspection section");
|
|
3210
|
+
return evidence(request, [{
|
|
3211
|
+
id: "memory-spaces:" + String(input.section),
|
|
3212
|
+
content: modelJson(value, request.route.maxCharacters ?? 12e3)
|
|
3213
|
+
}]);
|
|
3214
|
+
}
|
|
3215
|
+
if (request.route.sourceRouteId === "recall") {
|
|
3216
|
+
const category = memoryInputText(input.category, "category", 30, false);
|
|
3217
|
+
const source = memoryInputText(input.source, "source", 30, false);
|
|
3218
|
+
const intent = memoryInputText(input.intent, "intent", 30, false);
|
|
3219
|
+
if (category !== void 0 && !CATEGORIES.has(category)) throw new Error(`unsupported category: ${category}`);
|
|
3220
|
+
if (source !== void 0 && !SOURCES.has(source)) throw new Error(`unsupported source: ${source}`);
|
|
3221
|
+
if (intent !== void 0 && !INTENTS.has(intent)) throw new Error(`unsupported intent: ${intent}`);
|
|
3222
|
+
const requestedBodies = memoryInputStringArray(input.memoryBodyIds, "memoryBodyIds", 1e4) ?? allowedBodies;
|
|
3223
|
+
if (requestedBodies.some((id) => !allowedBodies.includes(id))) throw new Error("Recall requested a Memory Space outside this View ReadGrant");
|
|
3224
|
+
const mode = memoryInputText(input.mode, "mode", 20, false);
|
|
3225
|
+
const result = await service.search({
|
|
3226
|
+
query: memoryInputText(input.query, "query", 2e3),
|
|
3227
|
+
...mode === void 0 ? {} : { mode },
|
|
3228
|
+
limit: Math.min(request.route.maxResults ?? 20, memoryInputInteger(input.limit, 10, 1, 20)),
|
|
3229
|
+
memoryBodyIds: requestedBodies,
|
|
3230
|
+
...category === void 0 ? {} : { category },
|
|
3231
|
+
...source === void 0 ? {} : { source },
|
|
3232
|
+
...intent === void 0 ? {} : { intent }
|
|
3233
|
+
}, request.signal);
|
|
3234
|
+
return evidence(request, result.results, result.results.length === 0 ? result.hint : void 0);
|
|
3235
|
+
}
|
|
3236
|
+
if (request.route.sourceRouteId === "related") {
|
|
3237
|
+
const id = memoryInputText(input.id, "id", 2e3);
|
|
3238
|
+
const requestedBody = memoryInputText(input.memoryBodyId, "memoryBodyId", 300, false);
|
|
3239
|
+
const admitted = admittedByView.get(request.view.id);
|
|
3240
|
+
const owners = [...admitted?.entries() ?? []].filter(([reference]) => reference.endsWith("/" + id)).map(([, bodyId]) => bodyId);
|
|
3241
|
+
const owner = requestedBody === void 0 ? owners.length === 1 ? owners[0] : void 0 : admitted?.get(requestedBody + "/" + id);
|
|
3242
|
+
if (owner === void 0) throw new Error("related-memory traversal requires evidence already admitted by this View");
|
|
3243
|
+
if (!allowedBodies.includes(owner)) throw new Error("related-memory owner is outside this View ReadGrant");
|
|
3244
|
+
const edge = memoryInputText(input.edge, "edge", 30, false);
|
|
3245
|
+
if (edge !== void 0 && !EDGES.has(edge)) throw new Error(`unsupported edge: ${edge}`);
|
|
3246
|
+
const results = await service.related(id, memoryInputInteger(input.depth, 2, 1, 5), edge, request.signal, owner);
|
|
3247
|
+
return evidence(request, results);
|
|
3248
|
+
}
|
|
3249
|
+
throw new Error(`unsupported Memory Spaces Route: ${request.route.sourceRouteId}`);
|
|
3250
|
+
},
|
|
3251
|
+
async mutate(request) {
|
|
3252
|
+
const input = memoryInputRecord(request.input, `Memory Spaces ${request.offer.sourceActionId}`);
|
|
3253
|
+
if (!service.config.writeEnabled) throw new Error("Memory Spaces is configured read-only");
|
|
3254
|
+
const grant = request.grant;
|
|
3255
|
+
if (grant === void 0) throw new Error("Memory Spaces action has no View ReadGrant");
|
|
3256
|
+
const allowedBodies = grantIds(grant);
|
|
3257
|
+
const knownBodies = memoryInputStringArray(memoryInputRecord(grant.value, "Memory Spaces scope").knownMemoryBodyIds, "knownMemoryBodyIds", 1e4) ?? allowedBodies;
|
|
3258
|
+
const created = createdByView.get(request.view.id) ?? /* @__PURE__ */ new Set();
|
|
3259
|
+
const writeBodies = [.../* @__PURE__ */ new Set([...knownBodies, ...created])];
|
|
3260
|
+
const admittedOwner = (id) => {
|
|
3261
|
+
const requestedBody = memoryInputText(input.memoryBodyId, "memoryBodyId", 300, false);
|
|
3262
|
+
const entries = admittedByView.get(request.view.id);
|
|
3263
|
+
if (requestedBody !== void 0) return entries?.get(requestedBody + "/" + id);
|
|
3264
|
+
const owners = [...entries?.entries() ?? []].filter(([reference]) => reference.endsWith("/" + id)).map(([, owner]) => owner);
|
|
3265
|
+
return owners.length === 1 ? owners[0] : void 0;
|
|
3266
|
+
};
|
|
3267
|
+
let result;
|
|
3268
|
+
let bodyId;
|
|
3269
|
+
if (request.offer.sourceActionId === "manage-spaces") {
|
|
3270
|
+
if (input.operation === "create") {
|
|
3271
|
+
const selection = input.selection === void 0 ? void 0 : memoryInputRecord(input.selection, "placement selection");
|
|
3272
|
+
const body = await service.createBodyForPersistence(createBodyRequest(input.request), selection === void 0 ? void 0 : {
|
|
3273
|
+
providerId: memoryInputText(selection.providerId, "providerId", 128),
|
|
3274
|
+
reason: memoryInputText(selection.reason, "reason", 4e3),
|
|
3275
|
+
confidence: memoryInputText(selection.confidence, "confidence", 40)
|
|
3276
|
+
}, request.signal);
|
|
3277
|
+
created.add(body.id);
|
|
3278
|
+
createdByView.set(request.view.id, created);
|
|
3279
|
+
while (createdByView.size > 128) createdByView.delete(createdByView.keys().next().value);
|
|
3280
|
+
bodyId = body.id;
|
|
3281
|
+
result = {
|
|
3282
|
+
action: "created",
|
|
3283
|
+
memoryBodyId: body.id,
|
|
3284
|
+
name: body.name,
|
|
3285
|
+
description: body.description
|
|
3286
|
+
};
|
|
3287
|
+
} else if (input.operation === "update") {
|
|
3288
|
+
bodyId = memoryInputText(input.memoryBodyId, "memoryBodyId", 300);
|
|
3289
|
+
if (!writeBodies.includes(bodyId)) throw new Error("Memory Space update is outside this View scope");
|
|
3290
|
+
const body = service.updateBody(bodyId, {
|
|
3291
|
+
...input.name === void 0 ? {} : { name: memoryInputText(input.name, "name", 100) },
|
|
3292
|
+
...input.description === void 0 ? {} : { description: memoryInputText(input.description, "description", 1e3) },
|
|
3293
|
+
...typeof input.active === "boolean" ? { active: input.active } : {}
|
|
3294
|
+
});
|
|
3295
|
+
result = {
|
|
3296
|
+
action: "updated",
|
|
3297
|
+
memoryBodyId: body.id,
|
|
3298
|
+
name: body.name,
|
|
3299
|
+
description: body.description,
|
|
3300
|
+
active: body.active
|
|
3301
|
+
};
|
|
3302
|
+
} else if (input.operation === "merge") {
|
|
3303
|
+
const target = memoryInputText(input.targetMemoryBodyId, "targetMemoryBodyId", 300);
|
|
3304
|
+
const sources = memoryInputStringArray(input.sourceMemoryBodyIds, "sourceMemoryBodyIds", 20) ?? [];
|
|
3305
|
+
if ([target, ...sources].some((id) => !writeBodies.includes(id))) throw new Error("Memory Space merge is outside this View scope");
|
|
3306
|
+
bodyId = target;
|
|
3307
|
+
result = await service.mergeBodies(target, sources, input.deactivateSources !== false, request.signal);
|
|
3308
|
+
} else throw new Error("unsupported Memory Space management action");
|
|
3309
|
+
} else if (request.offer.sourceActionId === "remember") {
|
|
3310
|
+
bodyId = memoryInputText(input.memoryBodyId, "memoryBodyId", 300, false);
|
|
3311
|
+
if (bodyId === void 0) {
|
|
3312
|
+
const defaults = [.../* @__PURE__ */ new Set([...allowedBodies, ...created])];
|
|
3313
|
+
if (defaults.length !== 1) throw new Error("remember requires an explicit Memory Space in this View scope");
|
|
3314
|
+
bodyId = defaults[0];
|
|
3315
|
+
}
|
|
3316
|
+
if (!writeBodies.includes(bodyId)) throw new Error("remember destination is outside this View scope");
|
|
3317
|
+
const category = memoryInputText(input.category, "category", 30, false);
|
|
3318
|
+
const source = memoryInputText(input.source, "source", 30, false);
|
|
3319
|
+
if (category !== void 0 && !CATEGORIES.has(category)) throw new Error(`unsupported category: ${category}`);
|
|
3320
|
+
if (source !== void 0 && !SOURCES.has(source)) throw new Error(`unsupported source: ${source}`);
|
|
3321
|
+
result = await service.remember({
|
|
3322
|
+
content: memoryInputText(input.content, "content", 1e5),
|
|
3323
|
+
...category === void 0 ? {} : { category },
|
|
3324
|
+
...source === void 0 ? {} : { source },
|
|
3325
|
+
...typeof input.importance !== "number" ? {} : { importance: input.importance },
|
|
3326
|
+
...input.tags === void 0 ? {} : { tags: memoryInputStringArray(input.tags, "tags") ?? [] },
|
|
3327
|
+
...input.entities === void 0 ? {} : { entities: memoryInputStringArray(input.entities, "entities") ?? [] },
|
|
3328
|
+
...bodyId === void 0 ? {} : { memoryBodyId: bodyId }
|
|
3329
|
+
}, request.signal);
|
|
3330
|
+
} else if (request.offer.sourceActionId === "link") {
|
|
3331
|
+
const sourceId = memoryInputText(input.sourceId, "sourceId", 2e3);
|
|
3332
|
+
const targetId = memoryInputText(input.targetId, "targetId", 2e3);
|
|
3333
|
+
const sourceBody = admittedOwner(sourceId);
|
|
3334
|
+
const targetBody = admittedOwner(targetId);
|
|
3335
|
+
if (sourceBody === void 0 || targetBody === void 0 || sourceBody !== targetBody || !allowedBodies.includes(sourceBody)) throw new Error("link requires two evidence items admitted by this View from the same Memory Space");
|
|
3336
|
+
bodyId = sourceBody;
|
|
3337
|
+
const edge = memoryInputText(input.type, "type", 30, false);
|
|
3338
|
+
if (edge !== void 0 && !EDGES.has(edge)) throw new Error(`unsupported edge: ${edge}`);
|
|
3339
|
+
const weight = typeof input.weight === "number" ? input.weight : .5;
|
|
3340
|
+
result = await service.link(sourceId, targetId, edge, weight, memoryInputText(input.reason, "reason", 1e3, false), request.signal, bodyId);
|
|
3341
|
+
} else if (request.offer.sourceActionId === "forget") {
|
|
3342
|
+
const id = memoryInputText(input.id, "id", 2e3);
|
|
3343
|
+
bodyId = admittedOwner(id);
|
|
3344
|
+
if (bodyId === void 0 || !allowedBodies.includes(bodyId)) throw new Error("forget requires evidence already admitted by this View");
|
|
3345
|
+
result = await service.forget(id, request.signal, bodyId);
|
|
3346
|
+
} else throw new Error(`unsupported Memory Spaces action: ${request.offer.sourceActionId}`);
|
|
3347
|
+
return createMemoryMutationReceipt(request.view.id, request.offer.id, context.sourceInstanceKey, service.memoryRevision(), {
|
|
3348
|
+
memoryBodyId: bodyId ?? null,
|
|
3349
|
+
result
|
|
3350
|
+
}, mutationResultCompletion(result));
|
|
3351
|
+
},
|
|
3352
|
+
manage(request) {
|
|
3353
|
+
return manageMemorySpaces(service, request);
|
|
3354
|
+
},
|
|
3355
|
+
async dispose() {
|
|
3356
|
+
admittedByView.clear();
|
|
3357
|
+
createdByView.clear();
|
|
3358
|
+
await service.dispose();
|
|
3359
|
+
}
|
|
3360
|
+
};
|
|
3361
|
+
}
|
|
3362
|
+
});
|
|
3363
|
+
}
|
|
3364
|
+
/** Empty template; actual installations must provide explicit Provider children. */
|
|
3365
|
+
//#endregion
|
|
3366
|
+
//#region src/index.ts
|
|
3367
|
+
const name = "dsh-mnemon-source-memory-spaces";
|
|
3368
|
+
const inject = ["mnemonMemory"];
|
|
3369
|
+
const memoryPlugin = defineMemoryPlugin({
|
|
3370
|
+
packageName: name,
|
|
3371
|
+
label: {
|
|
3372
|
+
en: "Memory Spaces",
|
|
3373
|
+
"zh-CN": "记忆空间"
|
|
3374
|
+
},
|
|
3375
|
+
description: {
|
|
3376
|
+
en: "Durable evidence backed by explicitly configured Provider children.",
|
|
3377
|
+
"zh-CN": "由显式配置的 Provider 子插件承载的长期证据。"
|
|
3378
|
+
},
|
|
3379
|
+
roles: ["source"],
|
|
3380
|
+
provides: [{ id: "source" }, { id: "source.durable-evidence" }]
|
|
3381
|
+
});
|
|
3382
|
+
const MemorySpaceProviderDeclarationSchema = z.object({
|
|
3383
|
+
use: z.string().required(),
|
|
3384
|
+
instanceId: z.string(),
|
|
3385
|
+
config: z.any()
|
|
3386
|
+
});
|
|
3387
|
+
const Config = z.intersect([MemorySpacesConfig, z.object({ providers: z.array(z.union([z.string(), MemorySpaceProviderDeclarationSchema])).default([]) })]);
|
|
3388
|
+
function sourceInstanceId(ctx, explicit) {
|
|
3389
|
+
const configured = explicit?.trim();
|
|
3390
|
+
if (configured !== void 0 && configured !== "") return configured;
|
|
3391
|
+
const located = (typeof ctx.get === "function" ? ctx.get("loader", false) : void 0)?.locate(ctx.fiber)?.trim();
|
|
3392
|
+
if (located !== void 0 && located !== "") return located;
|
|
3393
|
+
const entryId = ctx.fiber.entry?.options?.id;
|
|
3394
|
+
if (typeof entryId === "string" && entryId.trim() !== "") return entryId.trim();
|
|
3395
|
+
throw new Error("Memory Spaces requires a stable Loader Entry id; pass instanceId for a direct mount");
|
|
3396
|
+
}
|
|
3397
|
+
function importedProviderModule(loader, value, specifier) {
|
|
3398
|
+
const unwrapped = loader.unwrapExports?.(value) ?? (typeof value === "object" && value !== null && "default" in value ? value.default : value);
|
|
3399
|
+
if (typeof unwrapped !== "object" || unwrapped === null) throw new Error(`Memory Space Provider module ${specifier} did not export a typed child module`);
|
|
3400
|
+
return defineMemorySpaceProvider(unwrapped);
|
|
3401
|
+
}
|
|
3402
|
+
/** Resolve only the explicitly listed children; no dependency scanning occurs. */
|
|
3403
|
+
async function resolveMemorySpaceProviderEntries(ctx, declarations) {
|
|
3404
|
+
if (declarations.length === 0) throw new Error("Memory Spaces requires at least one explicit Provider child");
|
|
3405
|
+
const loader = typeof ctx.get === "function" ? ctx.get("loader", false) : void 0;
|
|
3406
|
+
const entries = [];
|
|
3407
|
+
const instanceIds = /* @__PURE__ */ new Set();
|
|
3408
|
+
for (const declaration of declarations) {
|
|
3409
|
+
const use = (typeof declaration === "string" ? declaration : declaration.use).trim();
|
|
3410
|
+
if (use === "") throw new Error("Memory Space Provider declaration use is required");
|
|
3411
|
+
if (loader?.import === void 0) throw new Error("cannot resolve installed Memory Space Provider module without the DSH Loader: " + use);
|
|
3412
|
+
const module = importedProviderModule(loader, await loader.import(use), use);
|
|
3413
|
+
const entry = {
|
|
3414
|
+
instanceId: typeof declaration === "string" ? module.id : declaration.instanceId?.trim() || module.id,
|
|
3415
|
+
module,
|
|
3416
|
+
config: typeof declaration === "string" ? void 0 : declaration.config
|
|
3417
|
+
};
|
|
3418
|
+
if (instanceIds.has(entry.instanceId)) throw new Error(`duplicate Memory Space Provider child: ${entry.instanceId}`);
|
|
3419
|
+
instanceIds.add(entry.instanceId);
|
|
3420
|
+
entries.push(entry);
|
|
3421
|
+
}
|
|
3422
|
+
return entries;
|
|
3423
|
+
}
|
|
3424
|
+
/** Compose an explicit Provider child list into one effective Source. */
|
|
3425
|
+
async function installMemorySpaces(ctx, entries, options = {}) {
|
|
3426
|
+
if (entries.length === 0) throw new Error("Memory Spaces requires at least one explicit Provider child");
|
|
3427
|
+
const entryId = sourceInstanceId(ctx, options.instanceId);
|
|
3428
|
+
resolveMemorySpacesConfig(options.config, entryId);
|
|
3429
|
+
const privateHost = new PrivateMemorySpaceProviderHost(entryId);
|
|
3430
|
+
const children = [];
|
|
3431
|
+
try {
|
|
3432
|
+
for (const entry of entries) children.push(ctx.plugin(createMemorySpaceProviderPlugin(entry, privateHost), entry.config));
|
|
3433
|
+
await Promise.all(children.map((child) => child.await()));
|
|
3434
|
+
for (const entry of entries) if (!privateHost.has(entry.instanceId)) throw new Error(`Memory Space Provider child did not install a definition: ${entry.instanceId}`);
|
|
3435
|
+
const snapshot = privateHost.snapshot();
|
|
3436
|
+
installMemory(ctx, {
|
|
3437
|
+
plugin: memoryPlugin,
|
|
3438
|
+
sources: [createMemorySpacesSource(snapshot, options.config)]
|
|
3439
|
+
}, {
|
|
3440
|
+
instanceId: entryId,
|
|
3441
|
+
effectiveDigest: "providers:" + memoryConfigurationDigest({
|
|
3442
|
+
providers: snapshot.digest,
|
|
3443
|
+
config: options.config ?? {}
|
|
3444
|
+
}).slice(7)
|
|
3445
|
+
});
|
|
3446
|
+
} catch (error) {
|
|
3447
|
+
await Promise.allSettled(children.reverse().map((child) => child.dispose()));
|
|
3448
|
+
throw error;
|
|
3449
|
+
}
|
|
3450
|
+
}
|
|
3451
|
+
async function apply(ctx, config = { providers: [] }) {
|
|
3452
|
+
const { providers, ...sourceConfig } = config;
|
|
3453
|
+
await installMemorySpaces(ctx, await resolveMemorySpaceProviderEntries(ctx, providers), { config: sourceConfig });
|
|
3454
|
+
}
|
|
3455
|
+
//#endregion
|
|
3456
|
+
export { Config, MemorySpaceProviderDeclarationSchema, apply, inject, installMemorySpaces, memoryPlugin, name, resolveMemorySpaceProviderEntries };
|