dsh-mnemon 0.4.7 → 0.5.0
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/README.md +10 -20
- package/README.zh-CN.md +10 -20
- package/SECURITY.md +8 -8
- package/cordis.patch.yml +42 -0
- package/lib/client.js +2360 -6843
- package/lib/contracts.js +16 -17
- package/lib/core.js +11 -0
- package/lib/definitions-j0FLgE0-.js +251 -0
- package/lib/extension-sdk.js +93 -229
- package/lib/index.js +3730 -6330
- package/lib/runtime-AtIOTeP1.js +1316 -0
- package/lib/storage-lock-Bqrhkyag.js +20 -0
- package/lib/testing.js +231 -0
- package/lib/types/src/client/extension-sdk.d.ts +10 -0
- package/lib/types/src/client/index.d.ts +1 -0
- package/lib/types/src/client/locales.d.ts +84 -0
- package/lib/types/src/client/page-client.d.ts +15 -0
- package/lib/types/src/client/page-kit.d.ts +40 -0
- package/lib/types/src/client/source-contracts.d.ts +46 -0
- package/lib/types/src/client/source-pages.d.ts +83 -0
- package/lib/types/src/core/contracts/index.d.ts +40 -0
- package/lib/types/src/core/contracts/plugin.d.ts +32 -0
- package/lib/types/src/core/contracts/view.d.ts +416 -0
- package/lib/types/src/core/definitions.d.ts +16 -0
- package/lib/types/src/core/plugin.d.ts +6 -0
- package/lib/types/src/{config-values.d.ts → host/config-values.d.ts} +7 -7
- package/lib/types/src/{config.d.ts → host/config.d.ts} +4 -1
- package/lib/types/src/{shared → host}/display-mode.d.ts +1 -1
- package/lib/types/src/host/plugin.d.ts +8 -0
- package/lib/types/src/host/protocol.d.ts +549 -0
- package/lib/types/src/host/view-protocol.d.ts +123 -0
- package/lib/types/src/index.d.ts +2 -21
- package/lib/types/src/sdk/index.d.ts +9 -0
- package/lib/types/src/sdk/input.d.ts +11 -0
- package/lib/types/src/sdk/install.d.ts +7 -0
- package/lib/types/src/sdk/service.d.ts +31 -0
- package/lib/types/src/sdk/storage-lock.d.ts +6 -0
- package/lib/types/src/sdk/strategy-configuration.d.ts +31 -0
- package/lib/types/src/sdk/testing.d.ts +70 -0
- package/package.json +46 -35
- package/lib/kernel.js +0 -1173
- package/lib/layers/documents.js +0 -31
- package/lib/layers/memory-spaces.js +0 -34
- package/lib/layers/runtime.js +0 -27
- package/lib/provider-sdk.js +0 -2
- package/lib/registry-B9SdBRLo.js +0 -2423
- package/lib/strategy-default-three-tier.js +0 -99
- package/lib/strategy-sdk.js +0 -56
- package/lib/types/packages/contracts/src/index.d.ts +0 -221
- package/lib/types/packages/extension-sdk/src/index.d.ts +0 -54
- package/lib/types/packages/kernel/src/access.d.ts +0 -14
- package/lib/types/packages/kernel/src/catalog.d.ts +0 -42
- package/lib/types/packages/kernel/src/index.d.ts +0 -5
- package/lib/types/packages/kernel/src/kernel.d.ts +0 -46
- package/lib/types/packages/kernel/src/topology.d.ts +0 -25
- package/lib/types/packages/kernel/src/view.d.ts +0 -89
- package/lib/types/packages/layer-documents/src/index.d.ts +0 -4
- package/lib/types/packages/layer-memory-spaces/src/index.d.ts +0 -4
- package/lib/types/packages/layer-runtime/src/index.d.ts +0 -4
- package/lib/types/packages/provider-sdk/src/index.d.ts +0 -19
- package/lib/types/packages/strategy-default-three-tier/src/index.d.ts +0 -7
- package/lib/types/packages/strategy-sdk/src/index.d.ts +0 -35
- package/lib/types/src/activity.d.ts +0 -16
- package/lib/types/src/agent-memory-turn.d.ts +0 -42
- package/lib/types/src/channels.d.ts +0 -1
- package/lib/types/src/client/GlobalLocationSetting.d.ts +0 -19
- package/lib/types/src/client/MnemonPackSection.d.ts +0 -13
- package/lib/types/src/client/MnemonSaveAction.d.ts +0 -13
- package/lib/types/src/client/MnemonSettingsCard.d.ts +0 -16
- package/lib/types/src/client/MnemonTurnTail.d.ts +0 -21
- package/lib/types/src/client/MnemonView.d.ts +0 -26
- package/lib/types/src/client/ProviderIcon.d.ts +0 -8
- package/lib/types/src/client/ProviderSettingsSection.d.ts +0 -16
- package/lib/types/src/client/anchor.d.ts +0 -27
- package/lib/types/src/client/api.d.ts +0 -83
- package/lib/types/src/client/better-sidebar.d.ts +0 -13
- package/lib/types/src/client/dsh-compat.d.ts +0 -51
- package/lib/types/src/client/settings.d.ts +0 -23
- package/lib/types/src/client/sidebar-entry.d.ts +0 -5
- package/lib/types/src/client/subagent-token-usage.d.ts +0 -23
- package/lib/types/src/client/workspace-controller.d.ts +0 -14
- package/lib/types/src/client/workspace-mount.d.ts +0 -20
- package/lib/types/src/commands.d.ts +0 -13
- package/lib/types/src/contracts.d.ts +0 -316
- package/lib/types/src/documents.d.ts +0 -77
- package/lib/types/src/git-branch.d.ts +0 -6
- package/lib/types/src/guidance.d.ts +0 -29
- package/lib/types/src/lifecycle.d.ts +0 -80
- package/lib/types/src/live-runtime.d.ts +0 -92
- package/lib/types/src/memory-bodies.d.ts +0 -57
- package/lib/types/src/memory-receipts.d.ts +0 -21
- package/lib/types/src/memory-system/access.d.ts +0 -2
- package/lib/types/src/memory-system/catalog.d.ts +0 -2
- package/lib/types/src/memory-system/contracts.d.ts +0 -2
- package/lib/types/src/memory-system/defaults.d.ts +0 -6
- package/lib/types/src/memory-system/index.d.ts +0 -7
- package/lib/types/src/memory-system/kernel.d.ts +0 -2
- package/lib/types/src/memory-system/topology.d.ts +0 -2
- package/lib/types/src/memory-system/view.d.ts +0 -2
- package/lib/types/src/memory-view.d.ts +0 -21
- package/lib/types/src/pack.d.ts +0 -29
- package/lib/types/src/process.d.ts +0 -16
- package/lib/types/src/provider-placement.d.ts +0 -26
- package/lib/types/src/provider-sdk.d.ts +0 -4
- package/lib/types/src/providers/byterover.d.ts +0 -32
- package/lib/types/src/providers/catalog.d.ts +0 -23
- package/lib/types/src/providers/hindsight.d.ts +0 -20
- package/lib/types/src/providers/holographic.d.ts +0 -23
- package/lib/types/src/providers/honcho.d.ts +0 -18
- package/lib/types/src/providers/http.d.ts +0 -32
- package/lib/types/src/providers/mem0.d.ts +0 -18
- package/lib/types/src/providers/memory-system.d.ts +0 -6
- package/lib/types/src/providers/openviking.d.ts +0 -32
- package/lib/types/src/providers/provider.d.ts +0 -46
- package/lib/types/src/providers/registry.d.ts +0 -23
- package/lib/types/src/providers/retaindb.d.ts +0 -17
- package/lib/types/src/providers/supermemory.d.ts +0 -17
- package/lib/types/src/recall-quality/contracts.d.ts +0 -32
- package/lib/types/src/recall-quality/engine.d.ts +0 -13
- package/lib/types/src/recall-quality/index.d.ts +0 -4
- package/lib/types/src/recall-quality/policies.d.ts +0 -5
- package/lib/types/src/recall-quality/registry.d.ts +0 -10
- package/lib/types/src/review-activity.d.ts +0 -19
- package/lib/types/src/rpc.d.ts +0 -22
- package/lib/types/src/runner.d.ts +0 -41
- package/lib/types/src/runtime-memory.d.ts +0 -90
- package/lib/types/src/search-tokens.d.ts +0 -5
- package/lib/types/src/service.d.ts +0 -113
- package/lib/types/src/session-events.d.ts +0 -8
- package/lib/types/src/settings.d.ts +0 -6
- package/lib/types/src/shared/contracts.d.ts +0 -1008
- package/lib/types/src/storage-scope.d.ts +0 -12
- package/lib/types/src/subagent-token-usage.d.ts +0 -53
- package/lib/types/src/subagent.d.ts +0 -124
- package/lib/types/src/tools.d.ts +0 -10
- package/lib/types/src/version-updates.d.ts +0 -37
|
@@ -0,0 +1,1316 @@
|
|
|
1
|
+
import { DEFAULT_MEMORY_VIEW_BUDGET } from "./contracts.js";
|
|
2
|
+
import { a as defineMemoryStrategy, c as jsonClone, d as uniqueIds, f as validateCapabilities, i as defineMemorySource, l as positiveInteger, n as deepFreeze, o as defineMemoryStrategyExtension, p as validateProvenance, r as defineMemoryPlugin, s as id, t as canonicalMemoryJson, u as requiredText } from "./definitions-j0FLgE0-.js";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import { setMaxListeners } from "node:events";
|
|
5
|
+
//#region src/core/contributions.ts
|
|
6
|
+
/** Normalize public definitions before the registry validates and captures them. */
|
|
7
|
+
function prepareMemoryContributions(contribution, options) {
|
|
8
|
+
const sources = [...contribution.sources ?? []];
|
|
9
|
+
const strategies = [...contribution.strategies ?? []];
|
|
10
|
+
const extensions = [...contribution.strategyExtensions ?? []];
|
|
11
|
+
const entryId = options.instanceId.trim();
|
|
12
|
+
if (entryId === "") throw new Error("installMemory requires a stable Loader Entry id");
|
|
13
|
+
const provenance = (packageName) => ({
|
|
14
|
+
packageName,
|
|
15
|
+
entryId,
|
|
16
|
+
...options.artifactDigest === void 0 ? {} : { artifactDigest: options.artifactDigest }
|
|
17
|
+
});
|
|
18
|
+
const plugins = contribution.plugin === void 0 ? [] : [{
|
|
19
|
+
kind: "plugin",
|
|
20
|
+
instanceKey: `plugin:${entryId}`,
|
|
21
|
+
provenance: provenance(contribution.plugin.packageName),
|
|
22
|
+
descriptor: contribution.plugin
|
|
23
|
+
}];
|
|
24
|
+
return {
|
|
25
|
+
sources: sources.map((definition) => ({
|
|
26
|
+
kind: "source",
|
|
27
|
+
instanceKey: `source:${entryId}${sources.length > 1 ? `/${definition.manifest.typeId}` : ""}`,
|
|
28
|
+
provenance: provenance(definition.manifest.packageName),
|
|
29
|
+
definition,
|
|
30
|
+
...options.effectiveDigest === void 0 ? {} : { effectiveDigest: options.effectiveDigest }
|
|
31
|
+
})),
|
|
32
|
+
strategies: strategies.map((definition) => ({
|
|
33
|
+
kind: "strategy",
|
|
34
|
+
instanceKey: `strategy:${entryId}${strategies.length > 1 ? `/${definition.manifest.typeId}` : ""}`,
|
|
35
|
+
provenance: provenance(definition.manifest.packageName),
|
|
36
|
+
definition
|
|
37
|
+
})),
|
|
38
|
+
strategyExtensions: extensions.map((definition) => ({
|
|
39
|
+
kind: "strategy-extension",
|
|
40
|
+
instanceKey: `strategy-extension:${entryId}${extensions.length > 1 ? `/${definition.manifest.typeId}` : ""}`,
|
|
41
|
+
provenance: provenance(definition.manifest.packageName),
|
|
42
|
+
definition
|
|
43
|
+
})),
|
|
44
|
+
plugins
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
//#endregion
|
|
48
|
+
//#region src/core/source-calls.ts
|
|
49
|
+
/** Sanitized public failure: never forward remote errors, paths or credentials. */
|
|
50
|
+
var SourceReadFailure = class extends Error {
|
|
51
|
+
diagnostic;
|
|
52
|
+
constructor(sourceInstanceKey, phase, timeout) {
|
|
53
|
+
super(`Memory Source ${sourceInstanceKey} ${phase}() ${timeout ? "timed out" : "failed"}`);
|
|
54
|
+
this.diagnostic = {
|
|
55
|
+
code: `source-${phase}-${timeout ? "timeout" : "failed"}`,
|
|
56
|
+
message: this.message,
|
|
57
|
+
contributionInstanceKey: sourceInstanceKey
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Bounds read-only composition work. Signals stay outside the JSON Strategy
|
|
63
|
+
* input. This is cooperative cancellation, not a sandbox; writes must not be
|
|
64
|
+
* raced against a timeout and then falsely reported as uncommitted.
|
|
65
|
+
*/
|
|
66
|
+
async function readSource(sourceInstanceKey, phase, timeoutMs, execute, signal) {
|
|
67
|
+
signal?.throwIfAborted();
|
|
68
|
+
const controller = new AbortController();
|
|
69
|
+
let interrupt;
|
|
70
|
+
const interrupted = new Promise((_resolve, reject) => {
|
|
71
|
+
interrupt = reject;
|
|
72
|
+
});
|
|
73
|
+
const cancel = (reason) => {
|
|
74
|
+
interrupt(reason);
|
|
75
|
+
controller.abort(reason);
|
|
76
|
+
};
|
|
77
|
+
const abort = () => cancel(signal.reason);
|
|
78
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
79
|
+
const timer = setTimeout(() => cancel(new SourceReadFailure(sourceInstanceKey, phase, true)), timeoutMs);
|
|
80
|
+
try {
|
|
81
|
+
const operation = Promise.resolve().then(() => {
|
|
82
|
+
controller.signal.throwIfAborted();
|
|
83
|
+
return execute(controller.signal);
|
|
84
|
+
}).catch(() => {
|
|
85
|
+
if (controller.signal.aborted) throw controller.signal.reason;
|
|
86
|
+
throw new SourceReadFailure(sourceInstanceKey, phase, false);
|
|
87
|
+
});
|
|
88
|
+
return await Promise.race([operation, interrupted]);
|
|
89
|
+
} finally {
|
|
90
|
+
clearTimeout(timer);
|
|
91
|
+
signal?.removeEventListener("abort", abort);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
//#endregion
|
|
95
|
+
//#region src/core/composition.ts
|
|
96
|
+
const INSTANCE_KEY = /^(?:source|strategy|strategy-extension):[a-zA-Z0-9][a-zA-Z0-9._:/-]{0,299}$/u;
|
|
97
|
+
const PLUGIN_INSTANCE_KEY = /^plugin:[a-zA-Z0-9][a-zA-Z0-9._:/-]{0,299}$/u;
|
|
98
|
+
function instanceKey(value, kind) {
|
|
99
|
+
const normalized = requiredText(value, `${kind} instanceKey`, 300);
|
|
100
|
+
if (!INSTANCE_KEY.test(normalized) || !normalized.startsWith(`${kind}:`)) throw new Error(`${kind} instanceKey must start with ${kind}: and contain only stable identifier characters`);
|
|
101
|
+
return normalized;
|
|
102
|
+
}
|
|
103
|
+
function digest(value) {
|
|
104
|
+
return createHash("sha256").update(canonicalMemoryJson(value)).digest("hex");
|
|
105
|
+
}
|
|
106
|
+
function captureSource(source) {
|
|
107
|
+
const definition = defineMemorySource(source.definition);
|
|
108
|
+
const key = instanceKey(source.instanceKey, "source");
|
|
109
|
+
const effectiveDigest = source.effectiveDigest === void 0 ? void 0 : requiredText(source.effectiveDigest, "memory Source effective digest", 500);
|
|
110
|
+
return Object.freeze({
|
|
111
|
+
kind: "source",
|
|
112
|
+
instanceKey: key,
|
|
113
|
+
provenance: validateProvenance(source.provenance, definition.manifest.packageName),
|
|
114
|
+
definition,
|
|
115
|
+
...effectiveDigest === void 0 ? {} : { effectiveDigest }
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
function captureStrategy(strategy) {
|
|
119
|
+
const definition = defineMemoryStrategy(strategy.definition);
|
|
120
|
+
return Object.freeze({
|
|
121
|
+
kind: "strategy",
|
|
122
|
+
instanceKey: instanceKey(strategy.instanceKey, "strategy"),
|
|
123
|
+
provenance: validateProvenance(strategy.provenance, definition.manifest.packageName),
|
|
124
|
+
definition
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
function captureExtension(extension) {
|
|
128
|
+
const definition = defineMemoryStrategyExtension(extension.definition);
|
|
129
|
+
return Object.freeze({
|
|
130
|
+
kind: "strategy-extension",
|
|
131
|
+
instanceKey: instanceKey(extension.instanceKey, "strategy-extension"),
|
|
132
|
+
provenance: validateProvenance(extension.provenance, definition.manifest.packageName),
|
|
133
|
+
definition
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
function capturePlugin(plugin) {
|
|
137
|
+
const descriptor = defineMemoryPlugin(plugin.descriptor);
|
|
138
|
+
const key = requiredText(plugin.instanceKey, "memory plugin instanceKey", 307);
|
|
139
|
+
if (!PLUGIN_INSTANCE_KEY.test(key)) throw new Error("memory plugin instanceKey must start with plugin: and contain only stable identifier characters");
|
|
140
|
+
return Object.freeze({
|
|
141
|
+
kind: "plugin",
|
|
142
|
+
instanceKey: key,
|
|
143
|
+
provenance: validateProvenance(plugin.provenance, descriptor.packageName),
|
|
144
|
+
descriptor
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
/** Validate active dependency and exclusivity edges before any Source factory runs. */
|
|
148
|
+
function validateMemoryPluginGraph(plugins, implicitCapabilities = []) {
|
|
149
|
+
const providers = /* @__PURE__ */ new Map();
|
|
150
|
+
for (const capability of implicitCapabilities) providers.set(capability, []);
|
|
151
|
+
for (const plugin of plugins) for (const capability of plugin.descriptor.provides) {
|
|
152
|
+
const values = providers.get(capability.id) ?? [];
|
|
153
|
+
values.push({
|
|
154
|
+
plugin,
|
|
155
|
+
exclusive: capability.exclusive === true
|
|
156
|
+
});
|
|
157
|
+
providers.set(capability.id, values);
|
|
158
|
+
}
|
|
159
|
+
for (const [capability, values] of providers) if (values.length > 1 && values.some((value) => value.exclusive)) throw new Error(`memory plugin capability conflict: ${capability} (${values.map((value) => value.plugin.descriptor.packageName).join(", ")})`);
|
|
160
|
+
for (const plugin of plugins) for (const requirement of plugin.descriptor.requires ?? []) if (!providers.has(requirement)) throw new Error(`memory plugin dependency unavailable: ${plugin.descriptor.packageName} requires ${requirement}`);
|
|
161
|
+
}
|
|
162
|
+
function captureMemoryContributionSnapshot(snapshot) {
|
|
163
|
+
if (!Number.isInteger(snapshot.revision) || snapshot.revision < 0) throw new Error("memory contribution revision must be a non-negative integer");
|
|
164
|
+
const sources = snapshot.sources.map(captureSource);
|
|
165
|
+
const strategies = snapshot.strategies.map(captureStrategy);
|
|
166
|
+
const extensions = (snapshot.strategyExtensions ?? []).map(captureExtension).sort((a, b) => a.instanceKey.localeCompare(b.instanceKey));
|
|
167
|
+
const plugins = (snapshot.plugins ?? []).map(capturePlugin).sort((a, b) => a.instanceKey.localeCompare(b.instanceKey));
|
|
168
|
+
const slots = /* @__PURE__ */ new Map();
|
|
169
|
+
for (const extension of extensions) {
|
|
170
|
+
const manifest = extension.definition.manifest;
|
|
171
|
+
const slot = `${manifest.strategyTypeId}/${manifest.slot}`;
|
|
172
|
+
const occupied = slots.get(slot);
|
|
173
|
+
if (occupied !== void 0) throw new Error(`memory Strategy extension slot conflict: ${slot} (${occupied}, ${extension.instanceKey})`);
|
|
174
|
+
slots.set(slot, extension.instanceKey);
|
|
175
|
+
}
|
|
176
|
+
const keys = /* @__PURE__ */ new Set();
|
|
177
|
+
for (const contribution of [
|
|
178
|
+
...sources,
|
|
179
|
+
...strategies,
|
|
180
|
+
...extensions
|
|
181
|
+
]) {
|
|
182
|
+
if (keys.has(contribution.instanceKey)) throw new Error(`memory contribution instanceKey is duplicated: ${contribution.instanceKey}`);
|
|
183
|
+
keys.add(contribution.instanceKey);
|
|
184
|
+
}
|
|
185
|
+
const pluginKeys = /* @__PURE__ */ new Set();
|
|
186
|
+
for (const plugin of plugins) {
|
|
187
|
+
if (pluginKeys.has(plugin.instanceKey)) throw new Error(`memory plugin instanceKey is duplicated: ${plugin.instanceKey}`);
|
|
188
|
+
pluginKeys.add(plugin.instanceKey);
|
|
189
|
+
const owned = [
|
|
190
|
+
...sources,
|
|
191
|
+
...strategies,
|
|
192
|
+
...extensions
|
|
193
|
+
].filter((contribution) => contribution.provenance.entryId === plugin.provenance.entryId);
|
|
194
|
+
if (owned.length === 0) throw new Error(`memory plugin descriptor has no contribution: ${plugin.instanceKey}`);
|
|
195
|
+
if (owned.some((contribution) => contribution.provenance.packageName !== plugin.descriptor.packageName)) throw new Error(`memory plugin descriptor package does not match an owned contribution: ${plugin.instanceKey}`);
|
|
196
|
+
const roles = new Set(owned.map((contribution) => contribution.kind));
|
|
197
|
+
for (const role of roles) if (!plugin.descriptor.roles.includes(role)) throw new Error(`memory plugin descriptor does not declare active role: ${role} (${plugin.instanceKey})`);
|
|
198
|
+
}
|
|
199
|
+
return Object.freeze({
|
|
200
|
+
revision: snapshot.revision,
|
|
201
|
+
sources: Object.freeze(sources),
|
|
202
|
+
strategies: Object.freeze(strategies),
|
|
203
|
+
...extensions.length === 0 ? {} : { strategyExtensions: Object.freeze(extensions) },
|
|
204
|
+
...plugins.length === 0 ? {} : { plugins: Object.freeze(plugins) }
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
function selectStrategy(strategies, options) {
|
|
208
|
+
if (options.strategyInstanceKey !== void 0) {
|
|
209
|
+
const key = instanceKey(options.strategyInstanceKey, "strategy");
|
|
210
|
+
const selected = strategies.find((strategy) => strategy.instanceKey === key);
|
|
211
|
+
if (selected === void 0) throw new Error(`selected memory Strategy instance is unavailable: ${key}`);
|
|
212
|
+
return selected;
|
|
213
|
+
}
|
|
214
|
+
if (options.strategyTypeId !== void 0) {
|
|
215
|
+
const type = id(options.strategyTypeId, "selected memory Strategy typeId");
|
|
216
|
+
const matches = strategies.filter((strategy) => strategy.definition.manifest.typeId === type);
|
|
217
|
+
if (matches.length === 0) throw new Error(`selected memory Strategy type is unavailable: ${type}`);
|
|
218
|
+
if (matches.length > 1) throw new Error(`selected memory Strategy type is ambiguous: ${type}`);
|
|
219
|
+
return matches[0];
|
|
220
|
+
}
|
|
221
|
+
if (strategies.length !== 1) throw new Error(`exactly one memory Strategy must be selected; found ${strategies.length}`);
|
|
222
|
+
return strategies[0];
|
|
223
|
+
}
|
|
224
|
+
function normalizeBudget(value) {
|
|
225
|
+
return deepFreeze({
|
|
226
|
+
maxProjectionCharacters: positiveInteger(value.maxProjectionCharacters, "memory View maxProjectionCharacters", 1e7),
|
|
227
|
+
maxRoutes: positiveInteger(value.maxRoutes, "memory View maxRoutes", 1e4),
|
|
228
|
+
maxActions: positiveInteger(value.maxActions, "memory View maxActions", 1e4),
|
|
229
|
+
maxEvidenceResults: positiveInteger(value.maxEvidenceResults, "memory View maxEvidenceResults", 1e4),
|
|
230
|
+
maxEvidenceCharacters: positiveInteger(value.maxEvidenceCharacters, "memory View maxEvidenceCharacters", 1e7)
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
function normalizeFacts(source, facts) {
|
|
234
|
+
const manifest = source.definition.manifest;
|
|
235
|
+
if (facts.sourceInstanceKey !== source.instanceKey) throw new Error(`memory Source facts instance mismatch: ${facts.sourceInstanceKey} != ${source.instanceKey}`);
|
|
236
|
+
if (facts.sourceTypeId !== manifest.typeId) throw new Error(`memory Source facts type mismatch: ${facts.sourceTypeId} != ${manifest.typeId}`);
|
|
237
|
+
if (facts.role !== manifest.role) throw new Error(`memory Source facts role mismatch: ${facts.role} != ${manifest.role}`);
|
|
238
|
+
if (![
|
|
239
|
+
"ready",
|
|
240
|
+
"degraded",
|
|
241
|
+
"unavailable"
|
|
242
|
+
].includes(facts.availability)) throw new Error(`unsupported memory Source availability: ${String(facts.availability)}`);
|
|
243
|
+
const capabilities = validateCapabilities(facts.capabilities, `memory Source facts capability for ${source.instanceKey}`);
|
|
244
|
+
const allowedCapabilities = new Set(manifest.capabilities);
|
|
245
|
+
for (const capability of capabilities) if (!allowedCapabilities.has(capability)) throw new Error(`memory Source facts expand manifest capability: ${source.instanceKey}/${capability}`);
|
|
246
|
+
const routeIds = uniqueIds(facts.routeIds, `memory Source facts route id for ${source.instanceKey}`);
|
|
247
|
+
const allowedRoutes = new Set((manifest.routes ?? []).map((route) => route.id));
|
|
248
|
+
for (const routeId of routeIds) if (!allowedRoutes.has(routeId)) throw new Error(`memory Source facts expose undeclared route: ${source.instanceKey}/${routeId}`);
|
|
249
|
+
const actionIds = uniqueIds(facts.actionIds, `memory Source facts action id for ${source.instanceKey}`);
|
|
250
|
+
const allowedActions = new Set((manifest.actions ?? []).map((action) => action.id));
|
|
251
|
+
for (const actionId of actionIds) if (!allowedActions.has(actionId)) throw new Error(`memory Source facts expose undeclared action: ${source.instanceKey}/${actionId}`);
|
|
252
|
+
return jsonClone({
|
|
253
|
+
sourceInstanceKey: source.instanceKey,
|
|
254
|
+
sourceTypeId: manifest.typeId,
|
|
255
|
+
role: manifest.role,
|
|
256
|
+
availability: facts.availability,
|
|
257
|
+
revision: requiredText(facts.revision, `memory Source facts revision for ${source.instanceKey}`, 500),
|
|
258
|
+
capabilities,
|
|
259
|
+
routeIds,
|
|
260
|
+
actionIds,
|
|
261
|
+
...facts.hints === void 0 ? {} : { hints: facts.hints }
|
|
262
|
+
}, `memory Source facts for ${source.instanceKey}`);
|
|
263
|
+
}
|
|
264
|
+
function normalizeViewSpec(value, strategy, facts, budget) {
|
|
265
|
+
if (value.strategyTypeId !== strategy.definition.manifest.typeId) throw new Error("memory ViewSpec strategyTypeId does not match the selected Strategy");
|
|
266
|
+
const maxSources = Math.min(strategy.definition.manifest.maxSources, facts.size);
|
|
267
|
+
if (!Array.isArray(value.sources) || value.sources.length > maxSources) throw new Error(`memory Strategy selected too many Sources (max ${maxSources})`);
|
|
268
|
+
const seen = /* @__PURE__ */ new Set();
|
|
269
|
+
let routes = 0;
|
|
270
|
+
let actions = 0;
|
|
271
|
+
const normalizedSources = value.sources.flatMap((source) => {
|
|
272
|
+
const key = instanceKey(source.sourceInstanceKey, "source");
|
|
273
|
+
if (seen.has(key)) throw new Error(`memory Strategy selected Source twice: ${key}`);
|
|
274
|
+
seen.add(key);
|
|
275
|
+
const sourceFacts = facts.get(key);
|
|
276
|
+
if (sourceFacts === void 0) throw new Error(`memory Strategy selected unavailable Source: ${key}`);
|
|
277
|
+
if (!strategy.definition.manifest.supportedSourceRoles.includes(sourceFacts.role)) throw new Error(`memory Strategy selected an unsupported Source role: ${sourceFacts.role}`);
|
|
278
|
+
if (source.required !== void 0 && typeof source.required !== "boolean") throw new Error(`memory Source requirement must be boolean: ${key}`);
|
|
279
|
+
if (sourceFacts.availability === "unavailable") {
|
|
280
|
+
if (source.required === false) return [];
|
|
281
|
+
throw new Error(`memory Strategy selected unavailable required Source runtime: ${key}`);
|
|
282
|
+
}
|
|
283
|
+
const routeIds = uniqueIds(source.routeIds ?? [], `memory ViewSpec route id for ${key}`);
|
|
284
|
+
const availableRoutes = new Set(sourceFacts.routeIds);
|
|
285
|
+
for (const routeId of routeIds) if (!availableRoutes.has(routeId)) throw new Error(`memory Strategy selected unavailable route: ${key}/${routeId}`);
|
|
286
|
+
const actionIds = uniqueIds(source.actionIds ?? [], `memory ViewSpec action id for ${key}`);
|
|
287
|
+
const availableActions = new Set(sourceFacts.actionIds);
|
|
288
|
+
for (const actionId of actionIds) if (!availableActions.has(actionId)) throw new Error(`memory Strategy selected unavailable action: ${key}/${actionId}`);
|
|
289
|
+
routes += routeIds.length;
|
|
290
|
+
actions += actionIds.length;
|
|
291
|
+
if (source.projection !== void 0 && !sourceFacts.capabilities.includes("project")) throw new Error(`memory Strategy selected unavailable projection: ${key}`);
|
|
292
|
+
const projection = source.projection === void 0 ? void 0 : {
|
|
293
|
+
mode: source.projection.mode,
|
|
294
|
+
maxCharacters: Math.min(positiveInteger(source.projection.maxCharacters, `memory projection budget for ${key}`, 1e7), budget.maxProjectionCharacters)
|
|
295
|
+
};
|
|
296
|
+
if (projection !== void 0 && projection.mode !== "eager" && projection.mode !== "routed") throw new Error(`unsupported memory projection mode: ${String(projection.mode)}`);
|
|
297
|
+
return [deepFreeze({
|
|
298
|
+
sourceInstanceKey: key,
|
|
299
|
+
required: source.required !== false,
|
|
300
|
+
...projection === void 0 ? {} : { projection },
|
|
301
|
+
routeIds,
|
|
302
|
+
actionIds
|
|
303
|
+
})];
|
|
304
|
+
});
|
|
305
|
+
if (routes > Math.min(strategy.definition.manifest.maxRoutes, budget.maxRoutes)) throw new Error("memory Strategy selected too many Routes");
|
|
306
|
+
if (actions > Math.min(strategy.definition.manifest.maxActions, budget.maxActions)) throw new Error("memory Strategy selected too many ActionOffers");
|
|
307
|
+
let guidance;
|
|
308
|
+
if (value.guidance !== void 0) {
|
|
309
|
+
const input = jsonClone(value.guidance, "Strategy guidance");
|
|
310
|
+
if (input === null || typeof input !== "object" || Array.isArray(input)) throw new Error("Strategy guidance must be an object");
|
|
311
|
+
guidance = {};
|
|
312
|
+
for (const key of ["system", "routing"]) if (input[key] !== void 0) guidance[key] = requiredText(input[key], "Strategy guidance " + key, 16384);
|
|
313
|
+
if (input.reminders !== void 0) {
|
|
314
|
+
if (input.reminders === null || typeof input.reminders !== "object" || Array.isArray(input.reminders)) throw new Error("Strategy guidance reminders must be an object");
|
|
315
|
+
guidance.reminders = {};
|
|
316
|
+
for (const key of [
|
|
317
|
+
"read",
|
|
318
|
+
"write",
|
|
319
|
+
"both"
|
|
320
|
+
]) if (input.reminders[key] !== void 0) guidance.reminders[key] = requiredText(input.reminders[key], "Strategy reminder " + key, 16384);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return deepFreeze({
|
|
324
|
+
strategyTypeId: value.strategyTypeId,
|
|
325
|
+
sources: normalizedSources,
|
|
326
|
+
explanation: requiredText(value.explanation, "memory ViewSpec explanation", 4e3),
|
|
327
|
+
...guidance === void 0 ? {} : { guidance }
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
function normalizeContribution(source, spec, facts, value) {
|
|
331
|
+
const fragments = value.fragments.map((fragment, index) => {
|
|
332
|
+
if (fragment.sourceInstanceKey !== source.installed.instanceKey) throw new Error(`memory View fragment Source mismatch: ${fragment.sourceInstanceKey}`);
|
|
333
|
+
if (spec.projection === void 0) throw new Error(`memory Source returned projection without a Strategy request: ${source.installed.instanceKey}`);
|
|
334
|
+
if (fragment.mode !== spec.projection.mode) throw new Error(`memory View fragment mode mismatch: ${source.installed.instanceKey}`);
|
|
335
|
+
const text = typeof fragment.text === "string" ? fragment.text : "";
|
|
336
|
+
if (text.length > spec.projection.maxCharacters) throw new Error(`memory View fragment exceeds Source projection budget: ${source.installed.instanceKey}`);
|
|
337
|
+
return jsonClone({
|
|
338
|
+
...fragment,
|
|
339
|
+
id: requiredText(fragment.id, `memory View fragment id ${index}`, 300),
|
|
340
|
+
text,
|
|
341
|
+
revision: requiredText(fragment.revision, "memory View fragment revision", 500)
|
|
342
|
+
}, `memory View fragment for ${source.installed.instanceKey}`);
|
|
343
|
+
});
|
|
344
|
+
let readGrant;
|
|
345
|
+
if (value.readGrant !== void 0) {
|
|
346
|
+
const grant = value.readGrant;
|
|
347
|
+
if (grant.sourceInstanceKey !== source.installed.instanceKey) throw new Error(`memory ReadGrant Source mismatch: ${grant.sourceInstanceKey}`);
|
|
348
|
+
if (grant.consistency !== source.installed.definition.manifest.consistency) throw new Error(`memory ReadGrant consistency expands Source manifest: ${source.installed.instanceKey}`);
|
|
349
|
+
readGrant = jsonClone({
|
|
350
|
+
...grant,
|
|
351
|
+
id: requiredText(grant.id, "memory ReadGrant id", 300),
|
|
352
|
+
schema: requiredText(grant.schema, "memory ReadGrant schema", 300),
|
|
353
|
+
revision: requiredText(grant.revision, "memory ReadGrant revision", 500)
|
|
354
|
+
}, `memory ReadGrant for ${source.installed.instanceKey}`);
|
|
355
|
+
}
|
|
356
|
+
if ((spec.routeIds?.length ?? 0) > 0 && readGrant === void 0) throw new Error(`memory Source did not return a ReadGrant for selected Routes: ${source.installed.instanceKey}`);
|
|
357
|
+
let presentation;
|
|
358
|
+
if (value.presentation !== void 0) {
|
|
359
|
+
const label = `memory Source presentation for ${source.installed.instanceKey}`;
|
|
360
|
+
const visibleItems = value.presentation.visibleItems;
|
|
361
|
+
const totalItems = value.presentation.totalItems;
|
|
362
|
+
if (!Number.isSafeInteger(visibleItems) || visibleItems < 0 || visibleItems > 1e9) throw new Error(`${label} visibleItems must be a non-negative safe integer`);
|
|
363
|
+
if (totalItems !== void 0 && (!Number.isSafeInteger(totalItems) || totalItems < visibleItems || totalItems > 1e9)) throw new Error(`${label} totalItems must be a safe integer no smaller than visibleItems`);
|
|
364
|
+
const sourceItems = value.presentation.items ?? [];
|
|
365
|
+
if (!Array.isArray(sourceItems) || sourceItems.length > 24 || sourceItems.length > visibleItems) throw new Error(`${label} items must be a preview of at most 24 visible items`);
|
|
366
|
+
const seen = /* @__PURE__ */ new Set();
|
|
367
|
+
const items = sourceItems.map((item, index) => {
|
|
368
|
+
const itemId = requiredText(item.id, `${label} item id ${index}`, 300);
|
|
369
|
+
if (seen.has(itemId)) throw new Error(`${label} item id is duplicated: ${itemId}`);
|
|
370
|
+
seen.add(itemId);
|
|
371
|
+
return {
|
|
372
|
+
id: itemId,
|
|
373
|
+
title: requiredText(item.title, `${label} item title ${index}`, 160),
|
|
374
|
+
...item.excerpt === void 0 ? {} : { excerpt: requiredText(item.excerpt, `${label} item excerpt ${index}`, 600) }
|
|
375
|
+
};
|
|
376
|
+
});
|
|
377
|
+
presentation = jsonClone({
|
|
378
|
+
visibleItems,
|
|
379
|
+
...totalItems === void 0 ? {} : { totalItems },
|
|
380
|
+
...items.length === 0 ? {} : { items }
|
|
381
|
+
}, label);
|
|
382
|
+
}
|
|
383
|
+
if (facts.availability === "unavailable" && (fragments.length > 0 || readGrant !== void 0 || presentation !== void 0)) throw new Error(`unavailable memory Source returned a View contribution: ${source.installed.instanceKey}`);
|
|
384
|
+
return deepFreeze({
|
|
385
|
+
fragments,
|
|
386
|
+
...readGrant === void 0 ? {} : { readGrant },
|
|
387
|
+
...presentation === void 0 ? {} : { presentation }
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
function routeFor(source, routeId, grant, budget) {
|
|
391
|
+
const manifest = source.installed.definition.manifest.routes?.find((route) => route.id === routeId);
|
|
392
|
+
if (manifest === void 0) throw new Error(`memory Source Route manifest disappeared: ${source.installed.instanceKey}/${routeId}`);
|
|
393
|
+
return deepFreeze({
|
|
394
|
+
id: `${source.installed.instanceKey}/${routeId}`,
|
|
395
|
+
sourceInstanceKey: source.installed.instanceKey,
|
|
396
|
+
sourceRouteId: routeId,
|
|
397
|
+
description: manifest.description,
|
|
398
|
+
capability: manifest.capability,
|
|
399
|
+
inputSchema: manifest.inputSchema,
|
|
400
|
+
readGrantId: grant.id,
|
|
401
|
+
maxCalls: manifest.maxCalls,
|
|
402
|
+
maxResults: Math.min(manifest.maxResults ?? budget.maxEvidenceResults, budget.maxEvidenceResults),
|
|
403
|
+
maxCharacters: Math.min(manifest.maxCharacters ?? budget.maxEvidenceCharacters, budget.maxEvidenceCharacters)
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
function actionFor(source, actionId) {
|
|
407
|
+
const manifest = source.installed.definition.manifest.actions?.find((action) => action.id === actionId);
|
|
408
|
+
if (manifest === void 0) throw new Error(`memory Source Action manifest disappeared: ${source.installed.instanceKey}/${actionId}`);
|
|
409
|
+
return deepFreeze({
|
|
410
|
+
id: `${source.installed.instanceKey}/${actionId}`,
|
|
411
|
+
sourceInstanceKey: source.installed.instanceKey,
|
|
412
|
+
sourceActionId: actionId,
|
|
413
|
+
description: manifest.description,
|
|
414
|
+
capability: manifest.capability,
|
|
415
|
+
inputSchema: manifest.inputSchema,
|
|
416
|
+
...manifest.authority === void 0 ? {} : { authority: manifest.authority }
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
function assertInputSchema(schemaValue, value, label) {
|
|
420
|
+
if (typeof schemaValue !== "object" || schemaValue === null || Array.isArray(schemaValue)) return;
|
|
421
|
+
const schema = schemaValue;
|
|
422
|
+
const expected = schema.type;
|
|
423
|
+
if (expected === "object") {
|
|
424
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${label} must be an object`);
|
|
425
|
+
const record = value;
|
|
426
|
+
const required = Array.isArray(schema.required) ? schema.required.filter((item) => typeof item === "string") : [];
|
|
427
|
+
for (const key of required) if (!(key in record)) throw new Error(`${label} is missing required property: ${key}`);
|
|
428
|
+
const properties = typeof schema.properties === "object" && schema.properties !== null && !Array.isArray(schema.properties) ? schema.properties : {};
|
|
429
|
+
if (schema.additionalProperties === false) {
|
|
430
|
+
for (const key of Object.keys(record)) if (!(key in properties)) throw new Error(`${label} contains unsupported property: ${key}`);
|
|
431
|
+
}
|
|
432
|
+
for (const [key, propertySchema] of Object.entries(properties)) if (key in record) assertInputSchema(propertySchema, record[key], `${label}.${key}`);
|
|
433
|
+
} else if (expected === "string" && typeof value !== "string") throw new Error(`${label} must be a string`);
|
|
434
|
+
else if ((expected === "number" || expected === "integer") && (typeof value !== "number" || !Number.isFinite(value) || expected === "integer" && !Number.isInteger(value))) throw new Error(`${label} must be ${expected}`);
|
|
435
|
+
else if (expected === "boolean" && typeof value !== "boolean") throw new Error(`${label} must be a boolean`);
|
|
436
|
+
else if (expected === "array" && !Array.isArray(value)) throw new Error(`${label} must be an array`);
|
|
437
|
+
if (Array.isArray(schema.enum) && !schema.enum.some((candidate) => canonicalMemoryJson(candidate) === canonicalMemoryJson(value))) throw new Error(`${label} is not an allowed value`);
|
|
438
|
+
}
|
|
439
|
+
function normalizeEvidence(value, view, route, budget, now) {
|
|
440
|
+
if (value.viewId !== view.id || value.routeId !== route.id || value.sourceInstanceKey !== route.sourceInstanceKey) throw new Error("memory Evidence is not bound to the requested View Route");
|
|
441
|
+
const resultLimit = Math.min(route.maxResults ?? budget.maxEvidenceResults, budget.maxEvidenceResults);
|
|
442
|
+
const characterLimit = Math.min(route.maxCharacters ?? budget.maxEvidenceCharacters, budget.maxEvidenceCharacters);
|
|
443
|
+
const items = [];
|
|
444
|
+
let characters = 0;
|
|
445
|
+
let truncated = value.truncated || value.items.length > resultLimit;
|
|
446
|
+
for (const item of value.items) {
|
|
447
|
+
if (items.length >= resultLimit) break;
|
|
448
|
+
if (typeof item.text !== "string") throw new Error("memory Evidence item text must be a string");
|
|
449
|
+
if (item.score !== void 0 && !Number.isFinite(item.score)) throw new Error("memory Evidence score must be finite");
|
|
450
|
+
const text = item.text;
|
|
451
|
+
if (characters + text.length > characterLimit) {
|
|
452
|
+
truncated = true;
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
items.push(jsonClone({
|
|
456
|
+
...item,
|
|
457
|
+
id: requiredText(item.id, "memory Evidence item id", 500),
|
|
458
|
+
text
|
|
459
|
+
}, "memory Evidence item"));
|
|
460
|
+
characters += text.length;
|
|
461
|
+
}
|
|
462
|
+
return jsonClone({
|
|
463
|
+
...value,
|
|
464
|
+
id: requiredText(value.id, "memory Evidence id", 500),
|
|
465
|
+
observedAt: typeof value.observedAt === "string" && value.observedAt.trim() !== "" ? value.observedAt : now().toISOString(),
|
|
466
|
+
items,
|
|
467
|
+
truncated,
|
|
468
|
+
...value.unavailable === void 0 ? {} : { unavailable: requiredText(value.unavailable, "memory Evidence unavailable reason", 2e3) }
|
|
469
|
+
}, "memory Evidence");
|
|
470
|
+
}
|
|
471
|
+
var MemoryCompositionGeneration = class {
|
|
472
|
+
id;
|
|
473
|
+
report;
|
|
474
|
+
strategy;
|
|
475
|
+
extensions;
|
|
476
|
+
sources;
|
|
477
|
+
permissions = /* @__PURE__ */ new Map();
|
|
478
|
+
now;
|
|
479
|
+
routeCalls = /* @__PURE__ */ new WeakMap();
|
|
480
|
+
strategyTurns = /* @__PURE__ */ new WeakMap();
|
|
481
|
+
sourceTimeoutMs;
|
|
482
|
+
disposed = false;
|
|
483
|
+
constructor(snapshotValue, options = {}) {
|
|
484
|
+
const snapshot = captureMemoryContributionSnapshot(snapshotValue);
|
|
485
|
+
if (snapshot.sources.length === 0) throw new Error("memory composition requires at least one Source");
|
|
486
|
+
if (snapshot.strategies.length === 0) throw new Error("memory composition requires a Strategy");
|
|
487
|
+
validateMemoryPluginGraph(snapshot.plugins ?? [], [
|
|
488
|
+
...snapshot.sources.length === 0 ? [] : ["source"],
|
|
489
|
+
...snapshot.sources.map((source) => `source.${source.definition.manifest.role}`),
|
|
490
|
+
...snapshot.strategies.length === 0 ? [] : ["strategy"]
|
|
491
|
+
]);
|
|
492
|
+
this.strategy = selectStrategy(snapshot.strategies, options);
|
|
493
|
+
this.extensions = (snapshot.strategyExtensions ?? []).filter((extension) => extension.definition.manifest.strategyTypeId === this.strategy.definition.manifest.typeId);
|
|
494
|
+
for (const extension of this.extensions) if (!this.strategy.definition.manifest.extensionSlots?.includes(extension.definition.manifest.slot)) throw new Error(`memory Strategy does not support extension slot: ${extension.definition.manifest.slot} (${extension.instanceKey})`);
|
|
495
|
+
this.now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
496
|
+
this.sourceTimeoutMs = positiveInteger(options.sourceTimeoutMs ?? 1e4, "memory Source timeoutMs", 3e5);
|
|
497
|
+
const configurations = /* @__PURE__ */ new Map();
|
|
498
|
+
const created = /* @__PURE__ */ new Map();
|
|
499
|
+
try {
|
|
500
|
+
for (const installed of snapshot.sources) {
|
|
501
|
+
const configuration = jsonClone(options.sourceConfiguration?.(installed) ?? {}, "Source configuration");
|
|
502
|
+
configurations.set(installed.instanceKey, configuration);
|
|
503
|
+
const capabilities = options.sourceCapabilities?.(installed);
|
|
504
|
+
if (capabilities !== void 0) this.permissions.set(installed.instanceKey, Object.freeze([...capabilities]));
|
|
505
|
+
const runtime = installed.definition.create({
|
|
506
|
+
sourceInstanceKey: installed.instanceKey,
|
|
507
|
+
provenance: installed.provenance,
|
|
508
|
+
configuration
|
|
509
|
+
});
|
|
510
|
+
if (typeof runtime !== "object" || runtime === null || typeof runtime.facts !== "function" || typeof runtime.project !== "function") throw new Error(`memory Source factory returned an invalid runtime: ${installed.instanceKey}`);
|
|
511
|
+
created.set(installed.instanceKey, {
|
|
512
|
+
installed,
|
|
513
|
+
runtime
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
} catch (error) {
|
|
517
|
+
for (const source of [...created.values()].reverse()) source.runtime.dispose?.();
|
|
518
|
+
throw error;
|
|
519
|
+
}
|
|
520
|
+
this.sources = created;
|
|
521
|
+
const generationInput = {
|
|
522
|
+
contributionRevision: snapshot.revision,
|
|
523
|
+
sourceTimeoutMs: this.sourceTimeoutMs,
|
|
524
|
+
strategy: {
|
|
525
|
+
instanceKey: this.strategy.instanceKey,
|
|
526
|
+
manifest: this.strategy.definition.manifest,
|
|
527
|
+
provenance: this.strategy.provenance
|
|
528
|
+
},
|
|
529
|
+
...this.extensions.length === 0 ? {} : { strategyExtensions: this.extensions.map((extension) => ({
|
|
530
|
+
instanceKey: extension.instanceKey,
|
|
531
|
+
manifest: extension.definition.manifest,
|
|
532
|
+
provenance: extension.provenance
|
|
533
|
+
})) },
|
|
534
|
+
sources: snapshot.sources.map((source) => ({
|
|
535
|
+
instanceKey: source.instanceKey,
|
|
536
|
+
manifest: source.definition.manifest,
|
|
537
|
+
provenance: source.provenance,
|
|
538
|
+
effectiveDigest: source.effectiveDigest ?? null,
|
|
539
|
+
configuration: configurations.get(source.instanceKey),
|
|
540
|
+
permissions: this.permissions.get(source.instanceKey) ?? null
|
|
541
|
+
}))
|
|
542
|
+
};
|
|
543
|
+
this.id = `generation:${digest(generationInput)}`;
|
|
544
|
+
this.report = deepFreeze({
|
|
545
|
+
state: "ready",
|
|
546
|
+
contributionRevision: snapshot.revision,
|
|
547
|
+
generationId: this.id,
|
|
548
|
+
strategyInstanceKey: this.strategy.instanceKey,
|
|
549
|
+
...this.extensions.length === 0 ? {} : { strategyExtensionInstanceKeys: this.extensions.map((extension) => extension.instanceKey) },
|
|
550
|
+
sourceInstanceKeys: [...this.sources.keys()],
|
|
551
|
+
diagnostics: (snapshot.strategyExtensions ?? []).filter((extension) => !this.extensions.includes(extension)).map((extension) => ({
|
|
552
|
+
code: "strategy-extension-inactive",
|
|
553
|
+
contributionInstanceKey: extension.instanceKey,
|
|
554
|
+
message: `Strategy extension targets ${extension.definition.manifest.strategyTypeId}; selected Strategy is ${this.strategy.definition.manifest.typeId}.`
|
|
555
|
+
}))
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
async compose(requestValue, signal) {
|
|
559
|
+
signal?.throwIfAborted();
|
|
560
|
+
const controller = new AbortController();
|
|
561
|
+
setMaxListeners(0, controller.signal);
|
|
562
|
+
const abort = () => controller.abort(signal.reason);
|
|
563
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
564
|
+
try {
|
|
565
|
+
return await this.composeView(requestValue, controller.signal);
|
|
566
|
+
} finally {
|
|
567
|
+
controller.abort();
|
|
568
|
+
signal?.removeEventListener("abort", abort);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
async sourceFacts(source, request, diagnostics, signal) {
|
|
572
|
+
let value;
|
|
573
|
+
try {
|
|
574
|
+
value = await readSource(source.installed.instanceKey, "facts", this.sourceTimeoutMs, (abort) => source.runtime.facts(request, abort), signal);
|
|
575
|
+
} catch (error) {
|
|
576
|
+
if (!(error instanceof SourceReadFailure)) throw error;
|
|
577
|
+
diagnostics.push(error.diagnostic);
|
|
578
|
+
return {
|
|
579
|
+
sourceInstanceKey: source.installed.instanceKey,
|
|
580
|
+
sourceTypeId: source.installed.definition.manifest.typeId,
|
|
581
|
+
role: source.installed.definition.manifest.role,
|
|
582
|
+
availability: "unavailable",
|
|
583
|
+
revision: "unavailable",
|
|
584
|
+
capabilities: [],
|
|
585
|
+
routeIds: [],
|
|
586
|
+
actionIds: []
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
return normalizeFacts(source.installed, value);
|
|
590
|
+
}
|
|
591
|
+
async composeView(requestValue, signal) {
|
|
592
|
+
this.assertOpen();
|
|
593
|
+
const request = jsonClone({
|
|
594
|
+
...requestValue,
|
|
595
|
+
scenario: requiredText(requestValue.scenario, "memory View scenario", 300),
|
|
596
|
+
budget: normalizeBudget(requestValue.budget)
|
|
597
|
+
}, "memory View request");
|
|
598
|
+
const diagnostics = [];
|
|
599
|
+
const entries = await Promise.all([...this.sources.values()].map(async (source) => {
|
|
600
|
+
const value = structuredClone(await this.sourceFacts(source, request, diagnostics, signal));
|
|
601
|
+
const permissions = this.permissions.get(source.installed.instanceKey);
|
|
602
|
+
if (permissions !== void 0) {
|
|
603
|
+
value.capabilities = value.capabilities.filter((capability) => permissions.includes(capability));
|
|
604
|
+
value.routeIds = value.routeIds.filter((id) => source.installed.definition.manifest.routes?.some((route) => route.id === id && permissions.includes(route.capability)));
|
|
605
|
+
value.actionIds = value.actionIds.filter((id) => source.installed.definition.manifest.actions?.some((action) => action.id === id && permissions.includes(action.capability)));
|
|
606
|
+
if (value.capabilities.length === 0) value.availability = "unavailable";
|
|
607
|
+
}
|
|
608
|
+
const manifest = source.installed.definition.manifest;
|
|
609
|
+
const routes = (manifest.routes ?? []).filter((route) => value.availability !== "unavailable" && value.routeIds.includes(route.id) && value.capabilities.includes(route.capability));
|
|
610
|
+
const actions = (manifest.actions ?? []).filter((action) => value.availability !== "unavailable" && value.actionIds.includes(action.id) && value.capabilities.includes(action.capability));
|
|
611
|
+
const descriptor = {
|
|
612
|
+
...value,
|
|
613
|
+
routes,
|
|
614
|
+
actions,
|
|
615
|
+
routeIds: routes.map((route) => route.id),
|
|
616
|
+
actionIds: actions.map((action) => action.id)
|
|
617
|
+
};
|
|
618
|
+
return [source.installed.instanceKey, descriptor];
|
|
619
|
+
}));
|
|
620
|
+
signal.throwIfAborted();
|
|
621
|
+
const facts = new Map(entries);
|
|
622
|
+
const factsList = deepFreeze([...facts.values()].map((value) => jsonClone(value, "memory Source facts")));
|
|
623
|
+
const contributions = this.strategyContributions(request, factsList);
|
|
624
|
+
const replayedContributions = this.strategyContributions(request, factsList);
|
|
625
|
+
if (canonicalMemoryJson(contributions) !== canonicalMemoryJson(replayedContributions)) throw new Error(`memory Strategy extensions are not deterministic: ${this.extensions.map((extension) => extension.instanceKey).join(", ")}`);
|
|
626
|
+
const proposed = this.strategy.definition.compose(request, factsList, contributions);
|
|
627
|
+
const replayed = this.strategy.definition.compose(request, factsList, replayedContributions);
|
|
628
|
+
if (canonicalMemoryJson(proposed, "memory ViewSpec") !== canonicalMemoryJson(replayed, "replayed memory ViewSpec")) throw new Error(`memory Strategy is not deterministic: ${this.strategy.instanceKey}`);
|
|
629
|
+
let spec;
|
|
630
|
+
try {
|
|
631
|
+
spec = normalizeViewSpec(proposed, this.strategy, facts, request.budget);
|
|
632
|
+
} catch (error) {
|
|
633
|
+
if (diagnostics.length === 0) throw error;
|
|
634
|
+
throw new Error(`${error instanceof Error ? error.message : "Memory View rejected"}; ${diagnostics.map((item) => item.message).sort().join("; ")}`);
|
|
635
|
+
}
|
|
636
|
+
const projection = [];
|
|
637
|
+
const grants = [];
|
|
638
|
+
const routes = [];
|
|
639
|
+
const actions = [];
|
|
640
|
+
const sourcePresentations = [];
|
|
641
|
+
let projectionCharacters = 0;
|
|
642
|
+
const projected = await Promise.all(spec.sources.map(async (sourceSpec) => {
|
|
643
|
+
const source = this.sources.get(sourceSpec.sourceInstanceKey);
|
|
644
|
+
const sourceFacts = facts.get(sourceSpec.sourceInstanceKey);
|
|
645
|
+
let value;
|
|
646
|
+
try {
|
|
647
|
+
value = await readSource(source.installed.instanceKey, "project", this.sourceTimeoutMs, (abort) => source.runtime.project({
|
|
648
|
+
scope: request.scope,
|
|
649
|
+
sourceInstanceKey: source.installed.instanceKey,
|
|
650
|
+
expectedRevision: sourceFacts.revision,
|
|
651
|
+
includeProjection: sourceSpec.projection !== void 0,
|
|
652
|
+
mode: sourceSpec.projection?.mode ?? "routed",
|
|
653
|
+
maxCharacters: sourceSpec.projection?.maxCharacters ?? 1
|
|
654
|
+
}, abort), signal);
|
|
655
|
+
} catch (error) {
|
|
656
|
+
if (!(error instanceof SourceReadFailure) || sourceSpec.required !== false) throw error;
|
|
657
|
+
diagnostics.push(error.diagnostic);
|
|
658
|
+
return null;
|
|
659
|
+
}
|
|
660
|
+
return {
|
|
661
|
+
source,
|
|
662
|
+
sourceSpec,
|
|
663
|
+
contribution: normalizeContribution(source, sourceSpec, sourceFacts, value)
|
|
664
|
+
};
|
|
665
|
+
}));
|
|
666
|
+
signal.throwIfAborted();
|
|
667
|
+
const successful = projected.filter((value) => value !== null);
|
|
668
|
+
for (const { source, sourceSpec, contribution } of successful) {
|
|
669
|
+
for (const fragment of contribution.fragments) {
|
|
670
|
+
projectionCharacters += fragment.text.length;
|
|
671
|
+
if (projectionCharacters > request.budget.maxProjectionCharacters) throw new Error("memory View projection exceeds the root-turn budget");
|
|
672
|
+
projection.push(fragment);
|
|
673
|
+
}
|
|
674
|
+
if (contribution.readGrant !== void 0) {
|
|
675
|
+
if (grants.some((grant) => grant.id === contribution.readGrant.id)) throw new Error(`memory ReadGrant id is duplicated: ${contribution.readGrant.id}`);
|
|
676
|
+
grants.push(contribution.readGrant);
|
|
677
|
+
for (const routeId of sourceSpec.routeIds ?? []) routes.push(routeFor(source, routeId, contribution.readGrant, request.budget));
|
|
678
|
+
}
|
|
679
|
+
if (contribution.presentation !== void 0) sourcePresentations.push(deepFreeze({
|
|
680
|
+
sourceInstanceKey: source.installed.instanceKey,
|
|
681
|
+
mode: sourceSpec.projection?.mode ?? "routed",
|
|
682
|
+
...contribution.presentation
|
|
683
|
+
}));
|
|
684
|
+
for (const actionId of sourceSpec.actionIds ?? []) actions.push(actionFor(source, actionId));
|
|
685
|
+
}
|
|
686
|
+
const sourceRevisions = Object.fromEntries([...facts].map(([key, value]) => [key, value.revision]));
|
|
687
|
+
const modes = new Set(successful.map(({ source }) => source.installed.definition.manifest.consistency));
|
|
688
|
+
const consistency = {
|
|
689
|
+
mode: modes.size === 1 ? [...modes][0] : "mixed",
|
|
690
|
+
sourceRevisions
|
|
691
|
+
};
|
|
692
|
+
const body = {
|
|
693
|
+
runtimeGeneration: this.id,
|
|
694
|
+
strategyInstanceKey: this.strategy.instanceKey,
|
|
695
|
+
strategyTypeId: this.strategy.definition.manifest.typeId,
|
|
696
|
+
...contributions.length === 0 ? {} : { strategyExtensions: contributions.map(({ value, ...identity }) => ({
|
|
697
|
+
...identity,
|
|
698
|
+
digest: digest(value)
|
|
699
|
+
})) },
|
|
700
|
+
scope: request.scope,
|
|
701
|
+
projection,
|
|
702
|
+
...sourcePresentations.length === 0 ? {} : { sourcePresentations },
|
|
703
|
+
routes,
|
|
704
|
+
readGrants: grants,
|
|
705
|
+
actionOffers: actions,
|
|
706
|
+
consistency,
|
|
707
|
+
explanation: spec.explanation,
|
|
708
|
+
...spec.guidance === void 0 ? {} : { guidance: spec.guidance },
|
|
709
|
+
...diagnostics.length === 0 ? {} : { diagnostics: diagnostics.sort((a, b) => (a.contributionInstanceKey ?? "").localeCompare(b.contributionInstanceKey ?? "") || a.code.localeCompare(b.code)) }
|
|
710
|
+
};
|
|
711
|
+
const viewDigest = digest(body);
|
|
712
|
+
return deepFreeze({
|
|
713
|
+
id: `view:${viewDigest}`,
|
|
714
|
+
digest: viewDigest,
|
|
715
|
+
createdAt: this.now().toISOString(),
|
|
716
|
+
...body
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
strategyContributions(request, sources) {
|
|
720
|
+
return deepFreeze(this.extensions.map((extension) => {
|
|
721
|
+
const label = `memory Strategy extension ${extension.instanceKey}`;
|
|
722
|
+
const value = jsonClone(extension.definition.contribute(request, sources), label);
|
|
723
|
+
if (canonicalMemoryJson(value, label).length > 64e3) throw new Error(`${label} exceeds 64000 characters`);
|
|
724
|
+
return {
|
|
725
|
+
instanceKey: extension.instanceKey,
|
|
726
|
+
typeId: extension.definition.manifest.typeId,
|
|
727
|
+
slot: extension.definition.manifest.slot,
|
|
728
|
+
value
|
|
729
|
+
};
|
|
730
|
+
}));
|
|
731
|
+
}
|
|
732
|
+
/**
|
|
733
|
+
* Project the current scope's visible Source instances for the human
|
|
734
|
+
* management plane. The returned catalog contains only JSON-safe manifest
|
|
735
|
+
* metadata and SourceFacts; runtime objects and grants remain Host-only.
|
|
736
|
+
*/
|
|
737
|
+
sourceInstances() {
|
|
738
|
+
this.assertOpen();
|
|
739
|
+
return [...this.sources.values()].map((source) => ({
|
|
740
|
+
sourceInstanceKey: source.installed.instanceKey,
|
|
741
|
+
sourceTypeId: source.installed.definition.manifest.typeId
|
|
742
|
+
}));
|
|
743
|
+
}
|
|
744
|
+
/** A caller without a cached human revision may fence just its selected Source. */
|
|
745
|
+
async managementRevision(sourceInstanceKey, scope, signal) {
|
|
746
|
+
this.assertOpen();
|
|
747
|
+
const source = this.sources.get(instanceKey(sourceInstanceKey, "source"));
|
|
748
|
+
if (source?.runtime.manage === void 0) throw new Error("Source does not expose management: " + sourceInstanceKey);
|
|
749
|
+
const facts = await this.sourceFacts(source, jsonClone({
|
|
750
|
+
scope,
|
|
751
|
+
scenario: "management.revision",
|
|
752
|
+
budget: DEFAULT_MEMORY_VIEW_BUDGET
|
|
753
|
+
}, "management revision request"), [], signal);
|
|
754
|
+
if (facts.availability === "unavailable") throw new Error("Source is unavailable in the requested management scope: " + sourceInstanceKey);
|
|
755
|
+
return facts.revision;
|
|
756
|
+
}
|
|
757
|
+
async managementCatalog(scope) {
|
|
758
|
+
this.assertOpen();
|
|
759
|
+
const request = jsonClone({
|
|
760
|
+
scope,
|
|
761
|
+
scenario: "management.catalog",
|
|
762
|
+
budget: DEFAULT_MEMORY_VIEW_BUDGET
|
|
763
|
+
}, "memory management catalog request");
|
|
764
|
+
const diagnostics = [];
|
|
765
|
+
const sources = await Promise.all([...this.sources.values()].map(async (source) => {
|
|
766
|
+
const descriptor = source.installed.definition.manifest.management;
|
|
767
|
+
if (descriptor === void 0) return null;
|
|
768
|
+
const facts = await this.sourceFacts(source, request, diagnostics);
|
|
769
|
+
return {
|
|
770
|
+
sourceInstanceKey: source.installed.instanceKey,
|
|
771
|
+
sourceTypeId: source.installed.definition.manifest.typeId,
|
|
772
|
+
packageName: source.installed.definition.manifest.packageName,
|
|
773
|
+
role: source.installed.definition.manifest.role,
|
|
774
|
+
availability: facts.availability,
|
|
775
|
+
revision: facts.revision,
|
|
776
|
+
capabilities: facts.capabilities,
|
|
777
|
+
management: descriptor,
|
|
778
|
+
...facts.hints === void 0 ? {} : { hints: facts.hints }
|
|
779
|
+
};
|
|
780
|
+
}));
|
|
781
|
+
return jsonClone({
|
|
782
|
+
generationId: this.id,
|
|
783
|
+
sources: sources.filter((source) => source !== null),
|
|
784
|
+
...diagnostics.length === 0 ? {} : { diagnostics: diagnostics.sort((a, b) => (a.contributionInstanceKey ?? "").localeCompare(b.contributionInstanceKey ?? "")) }
|
|
785
|
+
}, "memory Source management catalog");
|
|
786
|
+
}
|
|
787
|
+
/** Execute one short-lived, explicitly scoped management operation. */
|
|
788
|
+
async executeManagement(requestValue) {
|
|
789
|
+
this.assertOpen();
|
|
790
|
+
const sourceInstanceKey = instanceKey(requestValue.sourceInstanceKey, "source");
|
|
791
|
+
const operation = id(requestValue.operation, "memory Source management operation");
|
|
792
|
+
if (requestValue.mode !== "read" && requestValue.mode !== "mutate") throw new Error(`unsupported memory Source management mode: ${String(requestValue.mode)}`);
|
|
793
|
+
if (requestValue.mode === "mutate") {
|
|
794
|
+
if (requestValue.confirmed !== true) throw new Error("memory Source management mutation requires explicit confirmation");
|
|
795
|
+
requiredText(requestValue.expectedRevision, "memory Source management expectedRevision", 500);
|
|
796
|
+
}
|
|
797
|
+
const source = this.sources.get(sourceInstanceKey);
|
|
798
|
+
if (source?.runtime.manage === void 0 || source.installed.definition.manifest.management === void 0) throw new Error(`memory Source does not expose management operations: ${sourceInstanceKey}`);
|
|
799
|
+
const diagnostics = [];
|
|
800
|
+
const facts = await this.sourceFacts(source, jsonClone({
|
|
801
|
+
scope: requestValue.scope,
|
|
802
|
+
scenario: `management.${requestValue.mode}`,
|
|
803
|
+
budget: DEFAULT_MEMORY_VIEW_BUDGET
|
|
804
|
+
}, "memory Source management facts request"), diagnostics, requestValue.signal);
|
|
805
|
+
if (facts.availability === "unavailable") throw new Error(`memory Source is unavailable in the requested management scope: ${sourceInstanceKey}${diagnostics.length === 0 ? "" : "; " + diagnostics[0].message}`);
|
|
806
|
+
if (requestValue.mode === "mutate" && requestValue.expectedRevision !== facts.revision) throw new Error(`memory Source management revision conflict: expected ${requestValue.expectedRevision}, current ${facts.revision}`);
|
|
807
|
+
const request = {
|
|
808
|
+
scope: jsonClone(requestValue.scope, "memory Source management scope"),
|
|
809
|
+
sourceInstanceKey,
|
|
810
|
+
mode: requestValue.mode,
|
|
811
|
+
operation,
|
|
812
|
+
input: jsonClone(requestValue.input, "memory Source management input"),
|
|
813
|
+
...requestValue.expectedRevision === void 0 ? {} : { expectedRevision: requiredText(requestValue.expectedRevision, "memory Source management expectedRevision", 500) },
|
|
814
|
+
confirmed: requestValue.confirmed === true,
|
|
815
|
+
...requestValue.signal === void 0 ? {} : { signal: requestValue.signal }
|
|
816
|
+
};
|
|
817
|
+
const result = await source.runtime.manage(request);
|
|
818
|
+
return jsonClone({
|
|
819
|
+
revision: requiredText(result.revision, "memory Source management result revision", 500),
|
|
820
|
+
value: result.value
|
|
821
|
+
}, "memory Source management result");
|
|
822
|
+
}
|
|
823
|
+
async executeRoute(view, routeId, input, signal, budget = DEFAULT_MEMORY_VIEW_BUDGET, execution = view) {
|
|
824
|
+
this.assertOpen();
|
|
825
|
+
if (view.runtimeGeneration !== this.id) throw new Error("memory View belongs to a different runtime generation");
|
|
826
|
+
const route = view.routes.find((candidate) => candidate.id === routeId);
|
|
827
|
+
if (route === void 0) throw new Error(`memory View Route is unavailable: ${routeId}`);
|
|
828
|
+
const grant = view.readGrants.find((candidate) => candidate.id === route.readGrantId && candidate.sourceInstanceKey === route.sourceInstanceKey);
|
|
829
|
+
if (grant === void 0) throw new Error(`memory View Route has no valid ReadGrant: ${routeId}`);
|
|
830
|
+
assertInputSchema(route.inputSchema, input, "memory Route input");
|
|
831
|
+
const executionBudget = normalizeBudget(budget);
|
|
832
|
+
const maxCharacters = Math.min(route.maxCharacters ?? executionBudget.maxEvidenceCharacters, executionBudget.maxEvidenceCharacters);
|
|
833
|
+
const maxResults = Math.min(route.maxResults ?? executionBudget.maxEvidenceResults, executionBudget.maxEvidenceResults);
|
|
834
|
+
const boundedRoute = deepFreeze({
|
|
835
|
+
...route,
|
|
836
|
+
maxCharacters,
|
|
837
|
+
maxResults
|
|
838
|
+
});
|
|
839
|
+
signal?.throwIfAborted();
|
|
840
|
+
const source = this.sources.get(route.sourceInstanceKey);
|
|
841
|
+
if (source?.runtime.query === void 0) throw new Error(`memory Source cannot execute Route: ${route.sourceInstanceKey}`);
|
|
842
|
+
let active = true;
|
|
843
|
+
const read = async (selectedInput, limits = {}) => {
|
|
844
|
+
if (!active) throw new Error("memory Strategy read continuation is no longer active");
|
|
845
|
+
this.assertOpen();
|
|
846
|
+
signal?.throwIfAborted();
|
|
847
|
+
assertInputSchema(route.inputSchema, selectedInput, "memory Route input");
|
|
848
|
+
const selectedRoute = deepFreeze({
|
|
849
|
+
...boundedRoute,
|
|
850
|
+
maxResults: Math.min(maxResults, limits.maxResults === void 0 ? maxResults : positiveInteger(limits.maxResults, "Strategy maxResults", 1e4)),
|
|
851
|
+
maxCharacters: Math.min(maxCharacters, limits.maxCharacters === void 0 ? maxCharacters : positiveInteger(limits.maxCharacters, "Strategy maxCharacters", 1e7))
|
|
852
|
+
});
|
|
853
|
+
let ledger = this.routeCalls.get(execution);
|
|
854
|
+
if (ledger === void 0) {
|
|
855
|
+
ledger = /* @__PURE__ */ new Map();
|
|
856
|
+
this.routeCalls.set(execution, ledger);
|
|
857
|
+
}
|
|
858
|
+
const counter = `${view.id}\u0000${route.id}`;
|
|
859
|
+
const calls = ledger.get(counter) ?? 0;
|
|
860
|
+
if (calls >= route.maxCalls) throw new Error(`memory View Route call budget is exhausted: ${route.id}`);
|
|
861
|
+
ledger.set(counter, calls + 1);
|
|
862
|
+
return normalizeEvidence(await source.runtime.query({
|
|
863
|
+
view: deepFreeze({
|
|
864
|
+
id: view.id,
|
|
865
|
+
scope: view.scope
|
|
866
|
+
}),
|
|
867
|
+
route: selectedRoute,
|
|
868
|
+
grant,
|
|
869
|
+
input: jsonClone(selectedInput, "memory Route input"),
|
|
870
|
+
...signal === void 0 ? {} : { signal }
|
|
871
|
+
}), view, selectedRoute, executionBudget, this.now);
|
|
872
|
+
};
|
|
873
|
+
try {
|
|
874
|
+
let policy = this.strategyTurns.get(execution);
|
|
875
|
+
if (policy === void 0 && this.strategy.definition.createTurn !== void 0) {
|
|
876
|
+
policy = this.strategy.definition.createTurn(view);
|
|
877
|
+
if (typeof policy?.query !== "function") throw new Error("memory Strategy returned an invalid turn policy");
|
|
878
|
+
this.strategyTurns.set(execution, policy);
|
|
879
|
+
}
|
|
880
|
+
const value = policy === void 0 ? await read(input) : await policy.query({
|
|
881
|
+
route: boundedRoute,
|
|
882
|
+
input: jsonClone(input, "memory Strategy input"),
|
|
883
|
+
...signal === void 0 ? {} : { signal }
|
|
884
|
+
}, read);
|
|
885
|
+
signal?.throwIfAborted();
|
|
886
|
+
return normalizeEvidence(value, view, boundedRoute, executionBudget, this.now);
|
|
887
|
+
} finally {
|
|
888
|
+
active = false;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
async executeAction(view, offerId, input, authorize, signal) {
|
|
892
|
+
this.assertOpen();
|
|
893
|
+
if (view.runtimeGeneration !== this.id) throw new Error("memory View belongs to a different runtime generation");
|
|
894
|
+
const offer = view.actionOffers.find((candidate) => candidate.id === offerId);
|
|
895
|
+
if (offer === void 0) throw new Error(`memory ActionOffer is unavailable: ${offerId}`);
|
|
896
|
+
assertInputSchema(offer.inputSchema, input, "memory Action input");
|
|
897
|
+
if (!await authorize(offer)) throw new Error(`memory ActionOffer is not currently authorized: ${offer.id}`);
|
|
898
|
+
const source = this.sources.get(offer.sourceInstanceKey);
|
|
899
|
+
if (source?.runtime.mutate === void 0) throw new Error(`memory Source cannot execute ActionOffer: ${offer.sourceInstanceKey}`);
|
|
900
|
+
signal?.throwIfAborted();
|
|
901
|
+
const grant = view.readGrants.find((candidate) => candidate.sourceInstanceKey === offer.sourceInstanceKey);
|
|
902
|
+
const receipt = await source.runtime.mutate({
|
|
903
|
+
view: deepFreeze({
|
|
904
|
+
id: view.id,
|
|
905
|
+
scope: view.scope
|
|
906
|
+
}),
|
|
907
|
+
offer,
|
|
908
|
+
...grant === void 0 ? {} : { grant },
|
|
909
|
+
input: jsonClone(input, "memory Action input"),
|
|
910
|
+
...signal === void 0 ? {} : { signal }
|
|
911
|
+
});
|
|
912
|
+
if (receipt.viewId !== view.id || receipt.offerId !== offer.id || receipt.sourceInstanceKey !== offer.sourceInstanceKey) throw new Error("memory mutation Receipt is not bound to the requested ActionOffer");
|
|
913
|
+
if (![
|
|
914
|
+
"succeeded",
|
|
915
|
+
"partial",
|
|
916
|
+
"failed",
|
|
917
|
+
"cancelled"
|
|
918
|
+
].includes(receipt.status)) throw new Error("memory mutation Receipt has an invalid status");
|
|
919
|
+
if (![
|
|
920
|
+
"accepted",
|
|
921
|
+
"candidate",
|
|
922
|
+
"committed",
|
|
923
|
+
"partial",
|
|
924
|
+
"failed",
|
|
925
|
+
"unknown"
|
|
926
|
+
].includes(receipt.completion)) throw new Error("memory mutation Receipt has an invalid completion");
|
|
927
|
+
if (receipt.completion === "committed") {
|
|
928
|
+
if (receipt.status !== "succeeded" || typeof receipt.committedAt !== "string" || !Number.isFinite(Date.parse(receipt.committedAt))) throw new Error("committed memory Receipt requires successful status and an explicit commit timestamp");
|
|
929
|
+
} else if (receipt.committedAt !== void 0) throw new Error("uncommitted memory Receipt cannot claim a commit timestamp");
|
|
930
|
+
if (receipt.completion === "failed" && receipt.status === "succeeded") throw new Error("failed memory completion cannot have successful status");
|
|
931
|
+
return jsonClone({
|
|
932
|
+
...receipt,
|
|
933
|
+
id: requiredText(receipt.id, "memory mutation Receipt id", 500)
|
|
934
|
+
}, "memory mutation Receipt");
|
|
935
|
+
}
|
|
936
|
+
async dispose() {
|
|
937
|
+
if (this.disposed) return;
|
|
938
|
+
this.disposed = true;
|
|
939
|
+
this.routeCalls = /* @__PURE__ */ new WeakMap();
|
|
940
|
+
this.strategyTurns = /* @__PURE__ */ new WeakMap();
|
|
941
|
+
const failures = [];
|
|
942
|
+
for (const source of [...this.sources.values()].reverse()) try {
|
|
943
|
+
await source.runtime.dispose?.();
|
|
944
|
+
} catch (error) {
|
|
945
|
+
failures.push(error);
|
|
946
|
+
}
|
|
947
|
+
if (failures.length > 0) throw new AggregateError(failures, `memory generation disposal failed: ${this.id}`);
|
|
948
|
+
}
|
|
949
|
+
sourceRuntime(sourceInstanceKey) {
|
|
950
|
+
return this.sources.get(sourceInstanceKey)?.runtime;
|
|
951
|
+
}
|
|
952
|
+
assertOpen() {
|
|
953
|
+
if (this.disposed) throw new Error(`memory runtime generation is disposed: ${this.id}`);
|
|
954
|
+
}
|
|
955
|
+
};
|
|
956
|
+
//#endregion
|
|
957
|
+
//#region src/core/generation.ts
|
|
958
|
+
function report(state, snapshot, diagnostics) {
|
|
959
|
+
return Object.freeze({
|
|
960
|
+
state,
|
|
961
|
+
contributionRevision: snapshot.revision,
|
|
962
|
+
sourceInstanceKeys: snapshot.sources.map((source) => source.instanceKey),
|
|
963
|
+
diagnostics: Object.freeze(diagnostics.map((diagnostic) => Object.freeze({ ...diagnostic })))
|
|
964
|
+
});
|
|
965
|
+
}
|
|
966
|
+
/**
|
|
967
|
+
* Owns candidate → serving → draining generation transitions.
|
|
968
|
+
* Cordis owns definition registrations; this host owns factory-created runtime
|
|
969
|
+
* objects and keeps a retired generation alive only while turn leases exist.
|
|
970
|
+
*/
|
|
971
|
+
var MemoryGenerationHost = class {
|
|
972
|
+
options;
|
|
973
|
+
records = /* @__PURE__ */ new Map();
|
|
974
|
+
serving;
|
|
975
|
+
evaluation = Object.freeze({
|
|
976
|
+
state: "incomplete",
|
|
977
|
+
contributionRevision: 0,
|
|
978
|
+
sourceInstanceKeys: [],
|
|
979
|
+
diagnostics: [{
|
|
980
|
+
code: "not-composed",
|
|
981
|
+
message: "Memory contributions have not been composed."
|
|
982
|
+
}]
|
|
983
|
+
});
|
|
984
|
+
closed = false;
|
|
985
|
+
closing;
|
|
986
|
+
onDrained;
|
|
987
|
+
pendingDisposals = /* @__PURE__ */ new Set();
|
|
988
|
+
disposalFailures = [];
|
|
989
|
+
constructor(options = {}) {
|
|
990
|
+
this.options = options;
|
|
991
|
+
}
|
|
992
|
+
reconcile(snapshot) {
|
|
993
|
+
this.assertOpen();
|
|
994
|
+
if (snapshot.sources.length === 0 || snapshot.strategies.length === 0) {
|
|
995
|
+
const diagnostics = [];
|
|
996
|
+
if (snapshot.sources.length === 0) diagnostics.push({
|
|
997
|
+
code: "missing-source",
|
|
998
|
+
message: "No Memory Source contribution is installed."
|
|
999
|
+
});
|
|
1000
|
+
if (snapshot.strategies.length === 0) diagnostics.push({
|
|
1001
|
+
code: "missing-strategy",
|
|
1002
|
+
message: "No Memory Strategy contribution is installed."
|
|
1003
|
+
});
|
|
1004
|
+
this.evaluation = report("incomplete", snapshot, diagnostics);
|
|
1005
|
+
if (this.removesServingContribution(snapshot) || this.removesServingExtension(snapshot)) this.retireServing();
|
|
1006
|
+
return this.evaluation;
|
|
1007
|
+
}
|
|
1008
|
+
let candidate;
|
|
1009
|
+
try {
|
|
1010
|
+
candidate = new MemoryCompositionGeneration(snapshot, this.options);
|
|
1011
|
+
} catch (error) {
|
|
1012
|
+
this.evaluation = report("rejected", snapshot, [{
|
|
1013
|
+
code: "composition-rejected",
|
|
1014
|
+
message: error instanceof Error ? error.message : String(error)
|
|
1015
|
+
}]);
|
|
1016
|
+
if (this.removesServingContribution(snapshot) || this.removesServingExtension(snapshot)) this.retireServing();
|
|
1017
|
+
return this.evaluation;
|
|
1018
|
+
}
|
|
1019
|
+
const previous = this.serving;
|
|
1020
|
+
const existing = this.records.get(candidate.id);
|
|
1021
|
+
if (existing !== void 0) {
|
|
1022
|
+
this.disposeGeneration(candidate);
|
|
1023
|
+
existing.state = "serving";
|
|
1024
|
+
this.serving = existing;
|
|
1025
|
+
this.evaluation = existing.generation.report;
|
|
1026
|
+
if (previous !== void 0 && previous !== existing) this.retire(previous);
|
|
1027
|
+
return this.evaluation;
|
|
1028
|
+
}
|
|
1029
|
+
const record = {
|
|
1030
|
+
generation: candidate,
|
|
1031
|
+
leases: 0,
|
|
1032
|
+
state: "serving"
|
|
1033
|
+
};
|
|
1034
|
+
this.records.set(candidate.id, record);
|
|
1035
|
+
this.serving = record;
|
|
1036
|
+
this.evaluation = candidate.report;
|
|
1037
|
+
if (previous !== void 0) this.retire(previous);
|
|
1038
|
+
return this.evaluation;
|
|
1039
|
+
}
|
|
1040
|
+
acquire(generationId) {
|
|
1041
|
+
this.assertOpen();
|
|
1042
|
+
const record = generationId === void 0 ? this.serving : this.records.get(generationId);
|
|
1043
|
+
if (record === void 0 || generationId === void 0 && record.state !== "serving") {
|
|
1044
|
+
const reason = this.evaluation.diagnostics.map((diagnostic) => diagnostic.message).join("; ");
|
|
1045
|
+
throw new Error(`no Serving memory generation is available${reason === "" ? "" : `: ${reason}`}`);
|
|
1046
|
+
}
|
|
1047
|
+
record.leases += 1;
|
|
1048
|
+
let active = true;
|
|
1049
|
+
return Object.freeze({
|
|
1050
|
+
id: record.generation.id,
|
|
1051
|
+
generation: record.generation,
|
|
1052
|
+
release: () => {
|
|
1053
|
+
if (!active) return;
|
|
1054
|
+
active = false;
|
|
1055
|
+
record.leases -= 1;
|
|
1056
|
+
this.collect(record);
|
|
1057
|
+
}
|
|
1058
|
+
});
|
|
1059
|
+
}
|
|
1060
|
+
current() {
|
|
1061
|
+
return this.serving?.state === "serving" ? this.serving.generation : void 0;
|
|
1062
|
+
}
|
|
1063
|
+
generation(id) {
|
|
1064
|
+
return this.records.get(id)?.generation;
|
|
1065
|
+
}
|
|
1066
|
+
inspect() {
|
|
1067
|
+
return Object.freeze({
|
|
1068
|
+
...this.serving?.state === "serving" ? { servingGenerationId: this.serving.generation.id } : {},
|
|
1069
|
+
drainingGenerationIds: Object.freeze([...this.records.values()].filter((record) => record.state === "draining").map((record) => record.generation.id)),
|
|
1070
|
+
evaluation: this.evaluation
|
|
1071
|
+
});
|
|
1072
|
+
}
|
|
1073
|
+
dispose() {
|
|
1074
|
+
if (this.closing !== void 0) return this.closing;
|
|
1075
|
+
this.closed = true;
|
|
1076
|
+
this.serving = void 0;
|
|
1077
|
+
const drained = new Promise((resolve) => {
|
|
1078
|
+
this.onDrained = resolve;
|
|
1079
|
+
});
|
|
1080
|
+
for (const record of [...this.records.values()]) this.retire(record);
|
|
1081
|
+
if (this.records.size === 0) this.onDrained?.();
|
|
1082
|
+
this.closing = (async () => {
|
|
1083
|
+
await drained;
|
|
1084
|
+
await Promise.all([...this.pendingDisposals]);
|
|
1085
|
+
if (this.disposalFailures.length > 0) throw new AggregateError(this.disposalFailures, "memory generation host disposal failed");
|
|
1086
|
+
})();
|
|
1087
|
+
return this.closing;
|
|
1088
|
+
}
|
|
1089
|
+
disposeGeneration(generation) {
|
|
1090
|
+
const task = generation.dispose().catch((error) => {
|
|
1091
|
+
this.disposalFailures.push(error);
|
|
1092
|
+
});
|
|
1093
|
+
this.pendingDisposals.add(task);
|
|
1094
|
+
task.finally(() => this.pendingDisposals.delete(task));
|
|
1095
|
+
}
|
|
1096
|
+
removesServingContribution(snapshot) {
|
|
1097
|
+
if (this.serving === void 0) return false;
|
|
1098
|
+
const available = /* @__PURE__ */ new Set([...snapshot.sources.map((source) => source.instanceKey), ...snapshot.strategies.map((strategy) => strategy.instanceKey)]);
|
|
1099
|
+
return [...this.serving.generation.report.sourceInstanceKeys, ...this.serving.generation.report.strategyInstanceKey === void 0 ? [] : [this.serving.generation.report.strategyInstanceKey]].some((key) => !available.has(key));
|
|
1100
|
+
}
|
|
1101
|
+
/** A failed replacement must not keep applying an explicitly disabled policy. */
|
|
1102
|
+
removesServingExtension(snapshot) {
|
|
1103
|
+
const available = new Set((snapshot.strategyExtensions ?? []).map((extension) => extension.instanceKey));
|
|
1104
|
+
return this.serving?.generation.report.strategyExtensionInstanceKeys?.some((key) => !available.has(key)) ?? false;
|
|
1105
|
+
}
|
|
1106
|
+
retireServing() {
|
|
1107
|
+
if (this.serving === void 0) return;
|
|
1108
|
+
const previous = this.serving;
|
|
1109
|
+
this.serving = void 0;
|
|
1110
|
+
this.retire(previous);
|
|
1111
|
+
}
|
|
1112
|
+
retire(record) {
|
|
1113
|
+
record.state = "draining";
|
|
1114
|
+
this.collect(record);
|
|
1115
|
+
}
|
|
1116
|
+
collect(record) {
|
|
1117
|
+
if (record.state !== "draining" || record.leases !== 0) return;
|
|
1118
|
+
if (this.records.get(record.generation.id) !== record) return;
|
|
1119
|
+
this.records.delete(record.generation.id);
|
|
1120
|
+
this.disposeGeneration(record.generation);
|
|
1121
|
+
if (this.closed && this.records.size === 0) this.onDrained?.();
|
|
1122
|
+
}
|
|
1123
|
+
assertOpen() {
|
|
1124
|
+
if (this.closed) throw new Error("memory generation host is disposed");
|
|
1125
|
+
}
|
|
1126
|
+
};
|
|
1127
|
+
//#endregion
|
|
1128
|
+
//#region src/core/registry.ts
|
|
1129
|
+
/**
|
|
1130
|
+
* Scope-local definition registry behind ctx.mnemonMemory.
|
|
1131
|
+
*
|
|
1132
|
+
* It owns definitions only. Runtime objects are created and drained by a
|
|
1133
|
+
* generation host attached by dsh-mnemon core.
|
|
1134
|
+
*/
|
|
1135
|
+
var MemoryContributionRegistry = class {
|
|
1136
|
+
sources = /* @__PURE__ */ new Map();
|
|
1137
|
+
strategies = /* @__PURE__ */ new Map();
|
|
1138
|
+
extensions = /* @__PURE__ */ new Map();
|
|
1139
|
+
plugins = /* @__PURE__ */ new Map();
|
|
1140
|
+
listeners = /* @__PURE__ */ new Set();
|
|
1141
|
+
revision = 0;
|
|
1142
|
+
install(value) {
|
|
1143
|
+
const incomingSources = [...value.sources ?? []];
|
|
1144
|
+
const incomingStrategies = [...value.strategies ?? []];
|
|
1145
|
+
const incomingExtensions = [...value.strategyExtensions ?? []];
|
|
1146
|
+
const incomingPlugins = [...value.plugins ?? []];
|
|
1147
|
+
if (incomingSources.length + incomingStrategies.length + incomingExtensions.length === 0) throw new Error("installMemory requires one Source, Strategy or Strategy extension contribution");
|
|
1148
|
+
for (const source of incomingSources) if (this.sources.has(source.instanceKey)) throw new Error(`memory Source instance is already installed: ${source.instanceKey}`);
|
|
1149
|
+
for (const strategy of incomingStrategies) if (this.strategies.has(strategy.instanceKey)) throw new Error(`memory Strategy instance is already installed: ${strategy.instanceKey}`);
|
|
1150
|
+
for (const extension of incomingExtensions) if (this.extensions.has(extension.instanceKey)) throw new Error(`memory Strategy extension instance is already installed: ${extension.instanceKey}`);
|
|
1151
|
+
for (const plugin of incomingPlugins) if (this.plugins.has(plugin.instanceKey)) throw new Error(`memory plugin instance is already installed: ${plugin.instanceKey}`);
|
|
1152
|
+
const candidate = captureMemoryContributionSnapshot({
|
|
1153
|
+
revision: this.revision + 1,
|
|
1154
|
+
sources: [...this.sources.values(), ...incomingSources],
|
|
1155
|
+
strategies: [...this.strategies.values(), ...incomingStrategies],
|
|
1156
|
+
strategyExtensions: [...this.extensions.values(), ...incomingExtensions],
|
|
1157
|
+
plugins: [...this.plugins.values(), ...incomingPlugins]
|
|
1158
|
+
});
|
|
1159
|
+
const capturedSources = candidate.sources.filter((source) => incomingSources.some((incoming) => incoming.instanceKey === source.instanceKey));
|
|
1160
|
+
const capturedStrategies = candidate.strategies.filter((strategy) => incomingStrategies.some((incoming) => incoming.instanceKey === strategy.instanceKey));
|
|
1161
|
+
const capturedExtensions = (candidate.strategyExtensions ?? []).filter((extension) => incomingExtensions.some((incoming) => incoming.instanceKey === extension.instanceKey));
|
|
1162
|
+
const capturedPlugins = (candidate.plugins ?? []).filter((plugin) => incomingPlugins.some((incoming) => incoming.instanceKey === plugin.instanceKey));
|
|
1163
|
+
for (const source of capturedSources) this.sources.set(source.instanceKey, source);
|
|
1164
|
+
for (const strategy of capturedStrategies) this.strategies.set(strategy.instanceKey, strategy);
|
|
1165
|
+
for (const extension of capturedExtensions) this.extensions.set(extension.instanceKey, extension);
|
|
1166
|
+
for (const plugin of capturedPlugins) this.plugins.set(plugin.instanceKey, plugin);
|
|
1167
|
+
this.revision = candidate.revision;
|
|
1168
|
+
this.notify(candidate);
|
|
1169
|
+
let active = true;
|
|
1170
|
+
return () => {
|
|
1171
|
+
if (!active) return;
|
|
1172
|
+
active = false;
|
|
1173
|
+
let changed = false;
|
|
1174
|
+
for (const source of capturedSources) {
|
|
1175
|
+
if (this.sources.get(source.instanceKey) !== source) continue;
|
|
1176
|
+
this.sources.delete(source.instanceKey);
|
|
1177
|
+
changed = true;
|
|
1178
|
+
}
|
|
1179
|
+
for (const strategy of capturedStrategies) {
|
|
1180
|
+
if (this.strategies.get(strategy.instanceKey) !== strategy) continue;
|
|
1181
|
+
this.strategies.delete(strategy.instanceKey);
|
|
1182
|
+
changed = true;
|
|
1183
|
+
}
|
|
1184
|
+
for (const extension of capturedExtensions) {
|
|
1185
|
+
if (this.extensions.get(extension.instanceKey) !== extension) continue;
|
|
1186
|
+
this.extensions.delete(extension.instanceKey);
|
|
1187
|
+
changed = true;
|
|
1188
|
+
}
|
|
1189
|
+
for (const plugin of capturedPlugins) {
|
|
1190
|
+
if (this.plugins.get(plugin.instanceKey) !== plugin) continue;
|
|
1191
|
+
this.plugins.delete(plugin.instanceKey);
|
|
1192
|
+
changed = true;
|
|
1193
|
+
}
|
|
1194
|
+
if (!changed) return;
|
|
1195
|
+
this.revision += 1;
|
|
1196
|
+
this.notify(this.snapshot());
|
|
1197
|
+
};
|
|
1198
|
+
}
|
|
1199
|
+
snapshot() {
|
|
1200
|
+
return captureMemoryContributionSnapshot({
|
|
1201
|
+
revision: this.revision,
|
|
1202
|
+
sources: [...this.sources.values()],
|
|
1203
|
+
strategies: [...this.strategies.values()],
|
|
1204
|
+
strategyExtensions: [...this.extensions.values()],
|
|
1205
|
+
plugins: [...this.plugins.values()]
|
|
1206
|
+
});
|
|
1207
|
+
}
|
|
1208
|
+
subscribe(listener) {
|
|
1209
|
+
this.listeners.add(listener);
|
|
1210
|
+
let active = true;
|
|
1211
|
+
return () => {
|
|
1212
|
+
if (!active) return;
|
|
1213
|
+
active = false;
|
|
1214
|
+
this.listeners.delete(listener);
|
|
1215
|
+
};
|
|
1216
|
+
}
|
|
1217
|
+
notify(snapshot) {
|
|
1218
|
+
for (const listener of [...this.listeners]) listener(snapshot);
|
|
1219
|
+
}
|
|
1220
|
+
};
|
|
1221
|
+
//#endregion
|
|
1222
|
+
//#region src/core/runtime.ts
|
|
1223
|
+
/** Only the owning Core/Host receives the engine; plugins receive its service. */
|
|
1224
|
+
function provideMemoryRuntime(context) {
|
|
1225
|
+
const runtime = new MemoryRuntime();
|
|
1226
|
+
context.provide("mnemonMemory", runtime.service);
|
|
1227
|
+
context.effect(() => () => runtime.dispose(), "dsh-mnemon.core()");
|
|
1228
|
+
return runtime;
|
|
1229
|
+
}
|
|
1230
|
+
/** The single Cordis-owned registry of Source and Strategy definitions. */
|
|
1231
|
+
var MemoryRuntime = class {
|
|
1232
|
+
service = Object.freeze({ installContributions: (contribution, options) => {
|
|
1233
|
+
if (this.closed) throw new Error("Memory Runtime is disposed");
|
|
1234
|
+
return this.installContributions(prepareMemoryContributions(contribution, options));
|
|
1235
|
+
} });
|
|
1236
|
+
contributions = new MemoryContributionRegistry();
|
|
1237
|
+
generationAttachments = /* @__PURE__ */ new Set();
|
|
1238
|
+
closed = false;
|
|
1239
|
+
disposal;
|
|
1240
|
+
disposalFailures = [];
|
|
1241
|
+
batchDepth = 0;
|
|
1242
|
+
batchSnapshot;
|
|
1243
|
+
reconciliations = /* @__PURE__ */ new Map();
|
|
1244
|
+
/** Host assembly transaction: existing leases stay served until all Fibers settle. */
|
|
1245
|
+
async batch(operation) {
|
|
1246
|
+
if (this.closed) throw new Error("Memory Runtime is disposed");
|
|
1247
|
+
if (this.batchDepth++ === 0) this.batchSnapshot = this.contributions.snapshot();
|
|
1248
|
+
try {
|
|
1249
|
+
return await operation();
|
|
1250
|
+
} finally {
|
|
1251
|
+
if (--this.batchDepth === 0) {
|
|
1252
|
+
this.batchSnapshot = void 0;
|
|
1253
|
+
const pending = [...this.reconciliations];
|
|
1254
|
+
this.reconciliations.clear();
|
|
1255
|
+
for (const [host, snapshot] of pending) host.reconcile(snapshot);
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
installContributions(value) {
|
|
1260
|
+
if (this.closed) throw new Error("Memory Runtime is disposed");
|
|
1261
|
+
return this.contributions.install(value);
|
|
1262
|
+
}
|
|
1263
|
+
contributionSnapshot() {
|
|
1264
|
+
return this.contributions.snapshot();
|
|
1265
|
+
}
|
|
1266
|
+
onContributionsChanged(listener) {
|
|
1267
|
+
return this.contributions.subscribe(listener);
|
|
1268
|
+
}
|
|
1269
|
+
/** Attach one Host runtime graph to the current definition set. */
|
|
1270
|
+
attachGeneration(options = {}) {
|
|
1271
|
+
if (this.closed) throw new Error("Memory Runtime is disposed");
|
|
1272
|
+
const host = new MemoryGenerationHost(options);
|
|
1273
|
+
host.reconcile(this.batchSnapshot ?? this.contributions.snapshot());
|
|
1274
|
+
if (this.batchSnapshot !== void 0) this.reconciliations.set(host, this.contributions.snapshot());
|
|
1275
|
+
const unsubscribe = this.contributions.subscribe((snapshot) => {
|
|
1276
|
+
if (this.batchDepth) this.reconciliations.set(host, snapshot);
|
|
1277
|
+
else host.reconcile(snapshot);
|
|
1278
|
+
});
|
|
1279
|
+
let attached = true;
|
|
1280
|
+
const release = () => {
|
|
1281
|
+
if (!attached) return;
|
|
1282
|
+
attached = false;
|
|
1283
|
+
unsubscribe();
|
|
1284
|
+
this.reconciliations.delete(host);
|
|
1285
|
+
};
|
|
1286
|
+
let disposal;
|
|
1287
|
+
const attachment = {
|
|
1288
|
+
host,
|
|
1289
|
+
release,
|
|
1290
|
+
dispose: () => {
|
|
1291
|
+
if (disposal !== void 0) return disposal;
|
|
1292
|
+
release();
|
|
1293
|
+
disposal = host.dispose().finally(() => {
|
|
1294
|
+
this.generationAttachments.delete(attachment);
|
|
1295
|
+
});
|
|
1296
|
+
disposal.catch((error) => {
|
|
1297
|
+
this.disposalFailures.push(error);
|
|
1298
|
+
});
|
|
1299
|
+
return disposal;
|
|
1300
|
+
}
|
|
1301
|
+
};
|
|
1302
|
+
this.generationAttachments.add(attachment);
|
|
1303
|
+
return attachment;
|
|
1304
|
+
}
|
|
1305
|
+
/** Core-Fiber shutdown also closes attachments left by a host adapter. */
|
|
1306
|
+
dispose() {
|
|
1307
|
+
if (this.disposal !== void 0) return this.disposal;
|
|
1308
|
+
this.closed = true;
|
|
1309
|
+
this.disposal = Promise.allSettled([...this.generationAttachments].map((attachment) => attachment.dispose())).then(() => {
|
|
1310
|
+
if (this.disposalFailures.length > 0) throw new AggregateError(this.disposalFailures, "Memory Runtime cleanup failed");
|
|
1311
|
+
});
|
|
1312
|
+
return this.disposal;
|
|
1313
|
+
}
|
|
1314
|
+
};
|
|
1315
|
+
//#endregion
|
|
1316
|
+
export { prepareMemoryContributions as a, validateMemoryPluginGraph as i, MemoryCompositionGeneration as n, captureMemoryContributionSnapshot as r, provideMemoryRuntime as t };
|