dsh-mnemon 0.5.15 → 0.5.17

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/lib/index.js CHANGED
@@ -1,9 +1,9 @@
1
- import { f as withMemoryStorageLock, n as memoryStrategyConfigurationValues, r as readMemoryStrategyConfiguration, s as receipt } from "./strategy-configuration-D8IEKZdS.js";
2
- import { a as prepareMemoryContributions, i as validateMemoryPluginGraph, n as MemoryCompositionGeneration, r as captureMemoryContributionSnapshot, t as provideMemoryRuntime } from "./runtime-BEX0sc1z.js";
3
- import { i as DEFAULT_MEMORY_VIEW_BUDGET, t as MEMORY_CAPABILITIES } from "./contracts-D4ghDRK4.js";
4
- import { r as defineMemoryPlugin, t as canonicalMemoryJson } from "./definitions-CSPV0ER1.js";
1
+ import { f as withMemoryStorageLock, n as memoryStrategyConfigurationValues, r as readMemoryStrategyConfiguration, s as receipt } from "./strategy-configuration-sVkKToSW.js";
2
+ import { a as validateMemoryPluginGraph, i as captureMemoryContributionSnapshot, n as isMemoryCompositionUnavailable, o as prepareMemoryContributions, r as MemoryCompositionGeneration, t as provideMemoryRuntime } from "./runtime-Bk6lFgxd.js";
3
+ import { DEFAULT_MEMORY_VIEW_BUDGET, MEMORY_CAPABILITIES } from "./contracts.js";
4
+ import { r as defineMemoryPlugin, t as canonicalMemoryJson } from "./definitions-j9QPbrXb.js";
5
5
  import { Context } from "@deepseek-ai/cordis";
6
- import z from "schemastery";
6
+ import z from "@deepseek-ai/schemastery";
7
7
  import { basename, delimiter, dirname, isAbsolute, join, normalize, resolve } from "node:path";
8
8
  import { resolveEmbedding, resolvePersistenceStrategy, resolveRecallQuality } from "dsh-mnemon-source-memory-spaces";
9
9
  import { createHash, randomUUID } from "node:crypto";
@@ -19,10 +19,10 @@ import { findMnemonCommand, mnemonNpmLauncher, nodeLauncherEnvironment } from "d
19
19
  import { spawn } from "node:child_process";
20
20
  import { StringDecoder } from "node:string_decoder";
21
21
  import { z as z$1 } from "zod";
22
+ import { Remote, TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
23
+ import { isVolatile } from "@deepseek-ai/cosmokit";
22
24
  import { readFile, stat } from "node:fs/promises";
23
25
  import { parseDocument } from "yaml";
24
- import * as cosmokit from "@deepseek-ai/cosmokit";
25
- import { Remote, TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
26
26
  //#region src/host/view-protocol.ts
27
27
  const MNEMON_VIEW_CHANNEL = "/dsh-mnemon-view";
28
28
  const MNEMON_VIEW_WRITE_CHANNEL = "/dsh-mnemon-view-settings";
@@ -61,6 +61,12 @@ const DEFAULT_IDLE_REVIEW = {
61
61
  maxContextChars: 24e3,
62
62
  maxTokens: 4096
63
63
  };
64
+ /** The Sources that keep their data in Mnemon's data directory, in the order a backup lists them. */
65
+ const MNEMON_PACK_COMPONENTS = [
66
+ "runtime",
67
+ "documents",
68
+ "memory-spaces"
69
+ ];
64
70
  const CATEGORIES = [
65
71
  "preference",
66
72
  "decision",
@@ -119,7 +125,7 @@ function preferences(value) {
119
125
  for (const [entryId, item] of Object.entries(entries)) {
120
126
  if (!ENTRY_ID$1.test(entryId)) throw new Error("Invalid memory plugin Entry id");
121
127
  const candidate = record$3(item);
122
- if (typeof candidate.enabled !== "boolean") throw new Error("Plugin enabled must be boolean");
128
+ if (candidate.enabled !== void 0 && typeof candidate.enabled !== "boolean") throw new Error("Plugin enabled must be boolean");
123
129
  record$3(candidate.config);
124
130
  }
125
131
  return clone$2(value);
@@ -474,6 +480,59 @@ function resolveConfig(config = {}) {
474
480
  };
475
481
  }
476
482
  //#endregion
483
+ //#region src/host/workspace-storage.ts
484
+ /** Host workspace identity, including aliases with not-yet-created descendants. */
485
+ function canonicalWorkspacePath(workspacePath) {
486
+ if (!isAbsolute(workspacePath) || workspacePath.includes("\0")) throw new Error("Workspace path must be absolute");
487
+ let parent = resolve(workspacePath);
488
+ const suffix = [];
489
+ for (;;) {
490
+ let real;
491
+ try {
492
+ real = realpathSync.native(parent);
493
+ } catch (error) {
494
+ if (error.code !== "ENOENT") throw error;
495
+ const next = dirname(parent);
496
+ if (next === parent) throw error;
497
+ suffix.unshift(parent.slice(next.length).replace(/^[/\\]+/u, ""));
498
+ parent = next;
499
+ continue;
500
+ }
501
+ if (suffix.length > 0 && !statSync(real).isDirectory()) throw Object.assign(/* @__PURE__ */ new Error(`ENOTDIR: not a directory, realpath '${parent}'`), {
502
+ code: "ENOTDIR",
503
+ path: parent,
504
+ syscall: "realpath"
505
+ });
506
+ return join(real, ...suffix);
507
+ }
508
+ }
509
+ /** A rename or move selects a new directory; resolving identity never writes. */
510
+ function workspaceStorageId(workspacePath) {
511
+ return createHash("sha256").update(canonicalWorkspacePath(workspacePath)).digest("hex");
512
+ }
513
+ //#endregion
514
+ //#region src/host/storage-root.ts
515
+ function expandDirectory(value) {
516
+ return resolve(value === "~" ? homedir() : value.startsWith("~/") ? join(homedir(), value.slice(2)) : value);
517
+ }
518
+ /** Host owns every built-in layout; Sources own the data within the chosen root. */
519
+ function storageDirectory(config, workspaceRoot) {
520
+ const globalRoot = process.env.MNEMON_DATA_DIR?.trim() || "~/.mnemon";
521
+ switch (config.storageScope) {
522
+ case "global": return expandDirectory(globalRoot);
523
+ case "custom": return expandDirectory(config.dataDir);
524
+ case "workspace": return resolve(workspaceRoot ?? process.cwd(), ".mnemon");
525
+ case "workspaces": return join(expandDirectory(config.dataDir ?? globalRoot), "workspaces", workspaceStorageId(resolve(workspaceRoot ?? process.cwd())));
526
+ }
527
+ }
528
+ function createStorageRoot(config, workspaceRoot) {
529
+ const directory = storageDirectory(config, workspaceRoot);
530
+ return {
531
+ effectiveDataDir: () => directory,
532
+ withExclusive: (operation) => withMemoryStorageLock(directory, operation)
533
+ };
534
+ }
535
+ //#endregion
477
536
  //#region src/host/pack.ts
478
537
  const MNEMON_PACK_FORMAT = "mnemonpack";
479
538
  const MNEMON_PACK_MIME = "application/zip";
@@ -487,11 +546,7 @@ const COMPONENT_DIRECTORIES = {
487
546
  documents: "documents",
488
547
  "memory-spaces": "data"
489
548
  };
490
- const COMPONENT_ORDER = [
491
- "runtime",
492
- "documents",
493
- "memory-spaces"
494
- ];
549
+ const COMPONENT_ORDER = MNEMON_PACK_COMPONENTS;
495
550
  const BODY_ID = /^[a-zA-Z0-9][a-zA-Z0-9_-]*$/;
496
551
  const SQLITE_HEADER = Buffer.from("SQLite format 3\0", "binary");
497
552
  function record$2(value) {
@@ -1175,7 +1230,8 @@ var MnemonPackManager = class {
1175
1230
  target() {
1176
1231
  return {
1177
1232
  root: this.root,
1178
- scope: this.config.storageScope
1233
+ scope: this.config.storageScope,
1234
+ defaultRoot: resolve(createStorageRoot({ storageScope: "global" }).effectiveDataDir())
1179
1235
  };
1180
1236
  }
1181
1237
  async exportPack(scope) {
@@ -1264,59 +1320,6 @@ var MnemonPackManager = class {
1264
1320
  }
1265
1321
  };
1266
1322
  //#endregion
1267
- //#region src/host/workspace-storage.ts
1268
- /** Host workspace identity, including aliases with not-yet-created descendants. */
1269
- function canonicalWorkspacePath(workspacePath) {
1270
- if (!isAbsolute(workspacePath) || workspacePath.includes("\0")) throw new Error("Workspace path must be absolute");
1271
- let parent = resolve(workspacePath);
1272
- const suffix = [];
1273
- for (;;) {
1274
- let real;
1275
- try {
1276
- real = realpathSync.native(parent);
1277
- } catch (error) {
1278
- if (error.code !== "ENOENT") throw error;
1279
- const next = dirname(parent);
1280
- if (next === parent) throw error;
1281
- suffix.unshift(parent.slice(next.length).replace(/^[/\\]+/u, ""));
1282
- parent = next;
1283
- continue;
1284
- }
1285
- if (suffix.length > 0 && !statSync(real).isDirectory()) throw Object.assign(/* @__PURE__ */ new Error(`ENOTDIR: not a directory, realpath '${parent}'`), {
1286
- code: "ENOTDIR",
1287
- path: parent,
1288
- syscall: "realpath"
1289
- });
1290
- return join(real, ...suffix);
1291
- }
1292
- }
1293
- /** A rename or move selects a new directory; resolving identity never writes. */
1294
- function workspaceStorageId(workspacePath) {
1295
- return createHash("sha256").update(canonicalWorkspacePath(workspacePath)).digest("hex");
1296
- }
1297
- //#endregion
1298
- //#region src/host/storage-root.ts
1299
- function expandDirectory(value) {
1300
- return resolve(value === "~" ? homedir() : value.startsWith("~/") ? join(homedir(), value.slice(2)) : value);
1301
- }
1302
- /** Host owns every built-in layout; Sources own the data within the chosen root. */
1303
- function storageDirectory(config, workspaceRoot) {
1304
- const globalRoot = process.env.MNEMON_DATA_DIR?.trim() || "~/.mnemon";
1305
- switch (config.storageScope) {
1306
- case "global": return expandDirectory(globalRoot);
1307
- case "custom": return expandDirectory(config.dataDir);
1308
- case "workspace": return resolve(workspaceRoot ?? process.cwd(), ".mnemon");
1309
- case "workspaces": return join(expandDirectory(config.dataDir ?? globalRoot), "workspaces", workspaceStorageId(resolve(workspaceRoot ?? process.cwd())));
1310
- }
1311
- }
1312
- function createStorageRoot(config, workspaceRoot) {
1313
- const directory = storageDirectory(config, workspaceRoot);
1314
- return {
1315
- effectiveDataDir: () => directory,
1316
- withExclusive: (operation) => withMemoryStorageLock(directory, operation)
1317
- };
1318
- }
1319
- //#endregion
1320
1323
  //#region src/host/storage-scope.ts
1321
1324
  function expandHome(path) {
1322
1325
  if (path === "~") return homedir();
@@ -2184,6 +2187,7 @@ function memoryGenerationOptions(config, workspaceRoot) {
2184
2187
  const userDirectory = config.runtimeUserScope === "global" ? createStorageRoot({ storageScope: "global" }).effectiveDataDir() : directory;
2185
2188
  return {
2186
2189
  strategyTypeId: config.memoryTopology.strategyId,
2190
+ strategyFallback: "sole-strategy",
2187
2191
  sourceTimeoutMs: config.timeoutMs,
2188
2192
  sourceCapabilities: (installed) => MEMORY_CAPABILITIES.filter((capability) => (config.writeEnabled || ![
2189
2193
  "write",
@@ -2474,12 +2478,13 @@ async function execute(serviceOrSource, coordinator, invocation) {
2474
2478
  text: `Mnemon 不可用:${status.error ?? "未知错误"}`
2475
2479
  };
2476
2480
  const stats = status.stats;
2481
+ const nativeInstalled = status.commandFound !== false;
2477
2482
  return {
2478
2483
  kind: "success",
2479
2484
  text: [
2480
- `Mnemon ${status.version ?? ""} · default=${status.mnemonDefaultStore}`.trim(),
2485
+ nativeInstalled ? `Mnemon ${status.version ?? ""} · default=${status.mnemonDefaultStore}`.trim() : "Mnemon Native: 未安装 CLI(可选,其他 Provider 不受影响)",
2481
2486
  `DSH 已激活: ${status.dshActiveStores.join(", ") || "none"}`,
2482
- `CLI: ${status.cliPath}`,
2487
+ ...nativeInstalled ? [`CLI: ${status.cliPath}`] : [],
2483
2488
  `数据目录: ${status.dataDir}`,
2484
2489
  `有效记忆: ${stats?.totalInsights ?? 0} · 连接: ${stats?.edgeCount ?? 0} · 已删除: ${stats?.deletedInsights ?? 0}`,
2485
2490
  `模式: ${status.writeEnabled ? "读写" : "只读"} · 默认召回: ${status.defaultRecallLimit}`
@@ -2595,11 +2600,14 @@ function applyMemoryViewGuidance(assembly, view, routingEnabled = true) {
2595
2600
  sections
2596
2601
  });
2597
2602
  }
2603
+ /** Register once the prompt service is active; Cordis re-runs this if the service is replaced. */
2598
2604
  function registerGuidance(ctx, config) {
2599
- ctx.get("systemPrompt")?.section?.({
2600
- name: GUIDANCE_SECTION_NAME,
2601
- order: 150,
2602
- text: () => config?.routingGuidance === false ? "" : ROUTING_GUIDANCE
2605
+ ctx.inject(["systemPrompt"], (prompted) => {
2606
+ prompted.get("systemPrompt").section({
2607
+ name: GUIDANCE_SECTION_NAME,
2608
+ order: 150,
2609
+ text: () => config?.routingGuidance === false ? "" : ROUTING_GUIDANCE
2610
+ });
2603
2611
  });
2604
2612
  }
2605
2613
  //#endregion
@@ -2669,36 +2677,13 @@ function mutationResultCommitted(result) {
2669
2677
  return COMMITTED_MUTATION_COUNTS.some((key) => typeof value[key] === "number" && Number.isFinite(value[key]) && value[key] > 0);
2670
2678
  }
2671
2679
  //#endregion
2672
- //#region src/host/session-events.ts
2673
- function eventArray(value) {
2674
- return Array.isArray(value) ? value : void 0;
2675
- }
2676
- /**
2677
- * Materialize one immutable event-log snapshot across supported DSH releases.
2678
- * Capability detection keeps alpha and stable rc behavior on one code path.
2679
- */
2680
- function hostSessionEvents(session) {
2681
- if (typeof session.snapshotEvents === "function") {
2682
- const events = eventArray(session.snapshotEvents());
2683
- if (events !== void 0) return events;
2684
- }
2685
- const events = eventArray(session.events);
2686
- if (events !== void 0) return events;
2687
- throw new TypeError("Unsupported DSH Session event API: expected snapshotEvents() or events[]");
2688
- }
2689
- /** Read one exact event without materializing the alpha log when possible. */
2690
- function hostSessionEventAt(session, seq) {
2691
- if (typeof session.eventAt === "function") return session.eventAt(seq);
2692
- return hostSessionEvents(session)[seq];
2693
- }
2694
- //#endregion
2695
2680
  //#region src/host/review-tools.ts
2696
2681
  const startingReview = new AsyncLocalStorage();
2697
2682
  /**
2698
- * Older published Team tools evaluate membership after a child loses its
2699
- * temporary Lead identity. Keep their conservative pause unless the operator
2700
- * selects scoped review (verified with DSH/Teams 0.1.7-rc.1). That opt-in still
2701
- * requires startGuardedReview; it never removes another plugin's policy.
2683
+ * Pausing stays the conservative default while Team tools are installed; the
2684
+ * operator can select scoped review instead (verified with DSH/Teams
2685
+ * 0.1.7-rc.2). That opt-in still requires startGuardedReview; it never removes
2686
+ * another plugin's policy.
2702
2687
  */
2703
2688
  function idleReviewBlockReason(parent, agentTeams = "pause") {
2704
2689
  return agentTeams !== "scoped" && parent.ctx?.get?.("agentTeams") !== void 0 && parent.ctx.tools?.get?.("spawn_teammate", parent) !== void 0 ? "agent-team" : void 0;
@@ -2797,9 +2782,8 @@ function textContent(content, toolResult = false) {
2797
2782
  }
2798
2783
  /** Use only the current public surface, so rewound or compacted evidence cannot reappear. */
2799
2784
  function reviewCheckpoint(session, maxChars) {
2800
- const nodes = session.surface?.nodes;
2801
- if (nodes === void 0) return "No current checkpoint surface is available. Skip this review without a mutation.";
2802
- const events = hostSessionEvents(session);
2785
+ const nodes = session.surface.nodes;
2786
+ const events = session.snapshotEvents();
2803
2787
  const completed = [...events].reverse().find((event) => event.type === "turn/end");
2804
2788
  if (completed === void 0) return "No completed checkpoint is available. Skip this review without a mutation.";
2805
2789
  const end = completed.seq ?? events.indexOf(completed);
@@ -2808,7 +2792,7 @@ function reviewCheckpoint(session, maxChars) {
2808
2792
  let remaining = maxChars - 122;
2809
2793
  const candidates = nodes.filter((seq) => seq < end);
2810
2794
  for (const seq of candidates.slice(-128).reverse()) {
2811
- const event = hostSessionEventAt(session, seq);
2795
+ const event = session.eventAt(seq);
2812
2796
  if (event === void 0 || ![
2813
2797
  "user/message",
2814
2798
  "assistant/message",
@@ -2834,8 +2818,8 @@ function evidenceInsights(evidence) {
2834
2818
  ...optionalObject(item.provenance) ?? {},
2835
2819
  id: item.id,
2836
2820
  content: item.text,
2837
- score: item.score,
2838
- revision: item.revision
2821
+ ...item.score === void 0 ? {} : { score: item.score },
2822
+ ...item.revision === void 0 ? {} : { revision: item.revision }
2839
2823
  };
2840
2824
  });
2841
2825
  }
@@ -3207,7 +3191,7 @@ function subagentFailureDetail(run, result) {
3207
3191
  const diagnostic = safeFailureDetail(result.diagnostic);
3208
3192
  if (diagnostic !== "") return diagnostic;
3209
3193
  }
3210
- const events = run.localAgent === void 0 ? [] : hostSessionEvents(run.localAgent.session);
3194
+ const events = run.localAgent === void 0 ? [] : run.localAgent.session.snapshotEvents();
3211
3195
  for (let index = events.length - 1; index >= 0; index -= 1) {
3212
3196
  const event = events[index];
3213
3197
  if (event?.type !== "turn/end") continue;
@@ -3753,7 +3737,7 @@ var MnemonSubagentCoordinator = class {
3753
3737
  const context = {
3754
3738
  runtime,
3755
3739
  inspectRuntime: runtime,
3756
- maintain: threeTierActionWorkflow(graph.config.memoryTopology.strategyId, "runtime", "mutate") !== void 0,
3740
+ maintain: threeTierActionWorkflow(lease.generation.strategy.definition.manifest.typeId, "runtime", "mutate") !== void 0,
3757
3741
  commit: () => runtime.mutate("mutate", request, signal),
3758
3742
  memorySpaces: async () => {
3759
3743
  if (!graph.config.writeEnabled || !this.runtimeSource.config.writeEnabled) throw new Error("dsh-mnemon is configured read-only");
@@ -5071,7 +5055,7 @@ function inspectMemoryView(generation, view, state, turn, memoryText) {
5071
5055
  /** The durable log, not a parent session id, identifies the executing turn. */
5072
5056
  function openAgentTurn(agent) {
5073
5057
  let open;
5074
- for (const event of hostSessionEvents(agent.session)) {
5058
+ for (const event of agent.session.snapshotEvents()) {
5075
5059
  const turn = typeof event.data.turn === "number" ? event.data.turn : void 0;
5076
5060
  if (event.type === "turn/start" && turn !== void 0) open = turn;
5077
5061
  else if (event.type === "turn/end" && turn === open) open = void 0;
@@ -5087,6 +5071,9 @@ var AgentMemoryTurn = class {
5087
5071
  runtime;
5088
5072
  delegation;
5089
5073
  pinned;
5074
+ /** A turn that runs without memory because no composition was serving when it began. */
5075
+ unavailable;
5076
+ warned;
5090
5077
  pending;
5091
5078
  generation = 0;
5092
5079
  closed = false;
@@ -5103,6 +5090,10 @@ var AgentMemoryTurn = class {
5103
5090
  get current() {
5104
5091
  return this.pinned;
5105
5092
  }
5093
+ /** Why the current turn has no View, when memory could not be composed for it. */
5094
+ get unavailableReason() {
5095
+ return this.unavailable?.reason;
5096
+ }
5106
5097
  inspect(workspaceRoot) {
5107
5098
  if (this.lastInspection === void 0) return void 0;
5108
5099
  if (workspaceRoot !== void 0 && resolve(workspaceRoot) !== this.lastWorkspace) return void 0;
@@ -5133,14 +5124,23 @@ var AgentMemoryTurn = class {
5133
5124
  async begin(turn, signal) {
5134
5125
  if (this.closed) throw new Error("memory Agent lifetime has ended");
5135
5126
  signal?.throwIfAborted();
5136
- if (this.pinned?.turn === turn) return;
5127
+ if (this.pinned?.turn === turn || this.unavailable?.turn === turn) return;
5137
5128
  if (this.pending?.turn === turn) return this.pending.result;
5138
5129
  this.end();
5139
5130
  const generation = this.generation;
5140
5131
  const controller = new AbortController();
5141
5132
  const abort = () => controller.abort(signal.reason);
5142
5133
  signal?.addEventListener("abort", abort, { once: true });
5143
- const result = this.pin(turn, generation, controller.signal);
5134
+ const result = this.pin(turn, generation, controller.signal).catch((error) => {
5135
+ if (!isMemoryCompositionUnavailable(error) || this.closed || this.generation !== generation) throw error;
5136
+ this.unavailable = {
5137
+ turn,
5138
+ reason: error.message
5139
+ };
5140
+ this.clearInspection();
5141
+ if (this.warned !== error.message) console.warn(`[dsh-mnemon] this turn runs without memory: ${error.message}`);
5142
+ this.warned = error.message;
5143
+ });
5144
5144
  this.pending = {
5145
5145
  turn,
5146
5146
  generation,
@@ -5155,8 +5155,9 @@ var AgentMemoryTurn = class {
5155
5155
  }
5156
5156
  }
5157
5157
  end(turn) {
5158
- if (turn !== void 0 && this.pinned?.turn !== turn && this.pending?.turn !== turn) return;
5158
+ if (turn !== void 0 && this.pinned?.turn !== turn && this.pending?.turn !== turn && this.unavailable?.turn !== turn) return;
5159
5159
  this.generation += 1;
5160
+ this.unavailable = void 0;
5160
5161
  this.pending?.controller.abort(/* @__PURE__ */ new Error("memory turn ended during View preparation"));
5161
5162
  this.pending = void 0;
5162
5163
  const pinned = this.pinned;
@@ -5186,6 +5187,7 @@ var AgentMemoryTurn = class {
5186
5187
  context,
5187
5188
  release: execution.release
5188
5189
  };
5190
+ this.warned = void 0;
5189
5191
  this.lastInspection = inspection;
5190
5192
  this.lastWorkspace = context.scope.workspaceId === void 0 ? void 0 : resolve(context.scope.workspaceId);
5191
5193
  } catch (error) {
@@ -5309,12 +5311,6 @@ var MnemonAgentLifecycle = class {
5309
5311
  memoryActivity = new TurnActivityProjection();
5310
5312
  turnActivity = /* @__PURE__ */ new Map();
5311
5313
  /**
5312
- * Fallback presence marker for hosts that publish no surface projection.
5313
- * A host with a surface answers the question from what the model can
5314
- * actually see, which is what makes a rewind self-correcting.
5315
- */
5316
- cueInjected = false;
5317
- /**
5318
5314
  * Text of the runtime memory snapshot most recently injected as this
5319
5315
  * plugin's own message. `.context()` used to get supersede-on-change for free
5320
5316
  * from the host's runtime-context projection; carrying the snapshot as an own
@@ -5335,6 +5331,7 @@ var MnemonAgentLifecycle = class {
5335
5331
  lastPhase = "idle";
5336
5332
  lastAt;
5337
5333
  lastError;
5334
+ composedTurn;
5338
5335
  constructor(agent, coordinator, config, counters, source, memoryTurn) {
5339
5336
  this.agent = agent;
5340
5337
  this.coordinator = coordinator;
@@ -5345,20 +5342,6 @@ var MnemonAgentLifecycle = class {
5345
5342
  }
5346
5343
  start() {
5347
5344
  const disposers = [
5348
- this.agent.ctx.on("agent/session-start", ((payload) => {
5349
- this.releaseView();
5350
- this.memoryTurn?.clearInspection();
5351
- this.cancelIdleReview(true);
5352
- this.guidedTurns.clear();
5353
- this.turnActivity.clear();
5354
- this.memoryActivity.reset();
5355
- this.startSource = payload.source;
5356
- this.primePending = true;
5357
- this.cueInjected = false;
5358
- this.injectedMemoryText = void 0;
5359
- this.lastError = void 0;
5360
- this.mark("prime");
5361
- })),
5362
5345
  this.agent.ctx.on("session/event", ((session, event) => this.sessionEvent(session, event))),
5363
5346
  this.agent.ctx.on("system-prompt/assemble", ((assembly, context, next) => this.assemblePrompt(assembly, context, next))),
5364
5347
  this.agent.ctx.on("agent/pre-step", ((payload, next) => this.preStep(payload, next)), { prepend: true }),
@@ -5377,7 +5360,7 @@ var MnemonAgentLifecycle = class {
5377
5360
  startSource: this.startSource,
5378
5361
  primePending: this.primePending,
5379
5362
  guidedTurns: this.guidedTurns.size,
5380
- memoryToolCalls: memoryToolCalls(hostSessionEvents(this.agent.session)),
5363
+ memoryToolCalls: memoryToolCalls(this.agent.session.snapshotEvents()),
5381
5364
  idleReviewPending: this.idleReviewTimer !== void 0,
5382
5365
  reviewRunning: this.reviewRunning,
5383
5366
  reviewActivity: this.reviewActivity(),
@@ -5400,11 +5383,11 @@ var MnemonAgentLifecycle = class {
5400
5383
  }
5401
5384
  /** Incremental snapshot of settled Mnemon activity in this durable log. */
5402
5385
  turnMemoryActivities() {
5403
- return this.memoryActivity.snapshot(hostSessionEvents(this.agent.session));
5386
+ return this.memoryActivity.snapshot(this.agent.session.snapshotEvents());
5404
5387
  }
5405
5388
  /** Plain text of one finalized assistant message, from this agent's session log. */
5406
5389
  assistantMessageText(messageId) {
5407
- return assistantMessageText(hostSessionEvents(this.agent.session), messageId);
5390
+ return assistantMessageText(this.agent.session.snapshotEvents(), messageId);
5408
5391
  }
5409
5392
  memoryWake() {
5410
5393
  const pinned = this.memoryTurn?.current;
@@ -5414,19 +5397,17 @@ var MnemonAgentLifecycle = class {
5414
5397
  /**
5415
5398
  * Whether the reminder is still visible to the model.
5416
5399
  *
5417
- * Read from the surface rather than from `cueInjected`, because a rewind is a
5418
- * surface replacement inside the same live session: it does not emit
5419
- * `agent/session-start`, so a session-scoped flag stays set and the reminder
5420
- * never returns. The durable event log cannot answer this either, since it is
5421
- * append-only and still contains the discarded message.
5400
+ * Read from the surface rather than an Agent-scoped flag, because a rewind
5401
+ * is a surface replacement inside the same live Agent: DSH does not recreate
5402
+ * the Agent, so a flag would stay set and the reminder would never return.
5403
+ * The durable event log cannot answer this either, since it is append-only
5404
+ * and still contains the discarded message.
5422
5405
  *
5423
5406
  * Scanning forward is cheap: the reminder sits near the head of the surface,
5424
5407
  * so the loop exits after a few nodes even on a long session.
5425
5408
  */
5426
5409
  cueAlreadyVisible() {
5427
- const nodes = this.agent.session.surface?.nodes;
5428
- if (nodes === void 0) return this.cueInjected;
5429
- for (const seq of nodes) if (isOwnUserMessageEvent(hostSessionEventAt(this.agent.session, seq))) return true;
5410
+ for (const seq of this.agent.session.surface.nodes) if (isOwnUserMessageEvent(this.agent.session.eventAt(seq))) return true;
5430
5411
  return false;
5431
5412
  }
5432
5413
  /**
@@ -5474,7 +5455,6 @@ var MnemonAgentLifecycle = class {
5474
5455
  kind: "enter",
5475
5456
  messages: withSnapshot(decision.messages)
5476
5457
  };
5477
- this.cueInjected = true;
5478
5458
  this.guidedTurns.add(payload.turn);
5479
5459
  if (this.config.recallMode === "guided") this.counters.recallCues += 1;
5480
5460
  if (this.config.writebackMode === "guided" && this.config.writeEnabled) this.counters.writebackCues += 1;
@@ -5508,10 +5488,9 @@ var MnemonAgentLifecycle = class {
5508
5488
  }
5509
5489
  scheduleIdleReview(turn) {
5510
5490
  this.cancelIdleReview(true);
5511
- if (!this.idleReviewAllowed()) return;
5512
- if (this.config.memoryTopology.strategyId !== "default-three-tier") return;
5491
+ if (!this.idleReviewAllowed() || !this.composedByDefault(turn)) return;
5513
5492
  const activity = this.ensureTurnActivity(turn);
5514
- const tools = completedToolActivity(hostSessionEvents(this.agent.session), turn);
5493
+ const tools = completedToolActivity(this.agent.session.snapshotEvents(), turn);
5515
5494
  activity.toolCallCount = tools.count;
5516
5495
  activity.toolNames = tools.names;
5517
5496
  if (!this.reviewActivity().eligible || !this.reviewAdmitted(turn)) return;
@@ -5523,10 +5502,26 @@ var MnemonAgentLifecycle = class {
5523
5502
  return;
5524
5503
  }
5525
5504
  if (this.agent.status !== "idle") return;
5526
- if (!hostSessionEvents(this.agent.session).some((event) => event.type === "turn/end" && eventTurn(event) === turn) || !this.reviewActivity().eligible || !this.reviewAdmitted(turn)) return;
5505
+ if (!this.agent.session.snapshotEvents().some((event) => event.type === "turn/end" && eventTurn(event) === turn) || !this.reviewActivity().eligible || !this.reviewAdmitted(turn)) return;
5527
5506
  this.runIdleReview();
5528
5507
  }, Math.max(this.config.idleReviewMs, (this.lastReviewAttemptAt ?? -Infinity) + this.config.idleReview.minIntervalMs - Date.now()));
5529
5508
  }
5509
+ /**
5510
+ * Automatic three-tier maintenance belongs to the default product, not to
5511
+ * every third-party View Strategy, and only to turns that Strategy actually
5512
+ * composed: a turn served by a fallback Strategy, or run without memory, has
5513
+ * nothing for it to maintain. Explicit management remains available.
5514
+ */
5515
+ composedByDefault(turn) {
5516
+ if (this.memoryTurn === void 0) return true;
5517
+ const pinned = this.memoryTurn.current;
5518
+ if (pinned?.turn === turn) this.composedTurn = {
5519
+ turn,
5520
+ strategyTypeId: pinned.context.view.strategyTypeId
5521
+ };
5522
+ else if (this.composedTurn?.turn !== turn) this.composedTurn = void 0;
5523
+ return this.composedTurn?.strategyTypeId === "default-three-tier";
5524
+ }
5530
5525
  idleReviewAllowed() {
5531
5526
  return this.config.lifecycleEnabled && this.config.writeEnabled && this.config.writebackMode === "guided" && this.config.idleReview.enabled && this.idleReviewAttempts < this.config.idleReview.maxPerSession && idleReviewBlockReason(this.agent, this.config.idleReview.agentTeams) === void 0 && this.config.memoryTopology.strategyId === "default-three-tier";
5532
5527
  }
@@ -5603,7 +5598,7 @@ var MnemonAgentLifecycle = class {
5603
5598
  explicitCandidate ||= activity.explicitCandidate;
5604
5599
  completedNonMemoryTool ||= activity.toolCallCount > 0 && [...activity.toolNames].some((name) => !name.startsWith("mnemon_"));
5605
5600
  }
5606
- const assistantTextLength = hostSessionEvents(this.agent.session).filter((event) => {
5601
+ const assistantTextLength = this.agent.session.snapshotEvents().filter((event) => {
5607
5602
  const turn = eventTurn(event);
5608
5603
  return turn !== void 0 && turns.has(turn);
5609
5604
  }).map(assistantEventTextLength).reduce((total, length) => total + length, 0);
@@ -5664,8 +5659,8 @@ var MnemonLifecycle = class {
5664
5659
  this.runtimeSource = runtimeSource;
5665
5660
  }
5666
5661
  start() {
5667
- const stopCreated = this.ctx.on("agent/created", (({ agent }) => {
5668
- this.install(agent, "startup");
5662
+ const stopCreated = this.ctx.on("agent/created", (({ agent, source }) => {
5663
+ this.install(agent, source);
5669
5664
  }));
5670
5665
  for (const agent of this.ctx.agents.roots()) this.install(agent, "adopted");
5671
5666
  return () => {
@@ -5689,7 +5684,7 @@ var MnemonLifecycle = class {
5689
5684
  idleReviewMs: this.config.idleReviewMs,
5690
5685
  activeAgents: this.owners.size,
5691
5686
  sessionAvailable: agent !== void 0,
5692
- taskAgentAvailable: this.ctx.agents.create === void 0 ? agent !== void 0 : this.taskAgentModelOptions(requestedId ?? "", workspaceRoot) !== void 0,
5687
+ taskAgentAvailable: this.taskAgentModelOptions(requestedId ?? "", workspaceRoot) !== void 0,
5693
5688
  counters: { ...this.counters },
5694
5689
  subagents: this.coordinator.snapshot(),
5695
5690
  ...owner === void 0 ? {} : { current: owner.snapshot() }
@@ -5937,20 +5932,15 @@ var MnemonLifecycle = class {
5937
5932
  * workspace graph without borrowing conversation history or ownership.
5938
5933
  */
5939
5934
  async runTaskAgent(fallbackSessionId, workspaceRoot, signal, operation) {
5940
- const create = this.ctx.agents.create?.bind(this.ctx.agents);
5941
- if (create === void 0) {
5942
- const fallback = workspaceRoot === void 0 ? this.ctx.agents.get(fallbackSessionId.trim()) ?? this.availableAgent() : this.availableAgent(workspaceRoot);
5943
- if (fallback === void 0) throw new Error("current DSH host cannot create a task Agent and no matching live Agent is available");
5944
- return operation(fallback);
5945
- }
5946
5935
  const sessionId = randomUUID();
5947
5936
  this.taskAgentIds.add(sessionId);
5948
5937
  let handle;
5949
5938
  let failure;
5950
5939
  try {
5951
- handle = await create({
5940
+ const creation = await this.taskAgentCreation(fallbackSessionId, workspaceRoot);
5941
+ handle = await this.ctx.agents.create({
5952
5942
  sessionId,
5953
- ...await this.taskAgentCreation(fallbackSessionId, workspaceRoot),
5943
+ ...creation,
5954
5944
  signal
5955
5945
  });
5956
5946
  return await operation(handle.agent);
@@ -6089,7 +6079,6 @@ var MnemonLifecycle = class {
6089
6079
  }
6090
6080
  };
6091
6081
  try {
6092
- stops.push(agent.ctx.on("agent/session-start", (() => memory.end())));
6093
6082
  stops.push(agent.ctx.on("system-prompt/assemble", (async (_assembly, context, next) => {
6094
6083
  if (context.agent !== void 0 && context.agent !== agent) return next();
6095
6084
  const turn = openAgentTurn(agent);
@@ -6662,17 +6651,6 @@ var VersionUpdateManager = class {
6662
6651
  return {
6663
6652
  checkedAt: (/* @__PURE__ */ new Date()).toISOString(),
6664
6653
  components: [{
6665
- id: "mnemon",
6666
- name: "Mnemon CLI",
6667
- ...mnemonLocal.install.command === void 0 ? {} : { executablePath: mnemonLocal.install.command },
6668
- ...mnemonLocal.current === void 0 ? {} : { current: mnemonLocal.current },
6669
- ...mnemonLatest === void 0 ? {} : { latest: mnemonLatest },
6670
- outdated: mnemonOutdated,
6671
- installMode: mnemonLocal.install.mode,
6672
- updateSupported: mnemonSupported,
6673
- updateHint: mnemonLocal.install.hint ?? (mnemonLocal.install.mode === "homebrew" ? mnemonSupported ? "brew" : "brew-missing" : mnemonLocal.install.mode === "go" ? "go" : mnemonLocal.install.mode === "missing" ? "install" : "manual"),
6674
- ...mnemonLatest === void 0 ? { checkError: "latest-unavailable" } : {}
6675
- }, {
6676
6654
  id: "dsh-mnemon",
6677
6655
  name: "dsh-mnemon",
6678
6656
  ...dshInstall.profileName === void 0 ? {} : { installProfile: dshInstall.profileName },
@@ -6686,6 +6664,17 @@ var VersionUpdateManager = class {
6686
6664
  restartRequired: this.pendingRestart.size > 0,
6687
6665
  updateHint: dshInstall.mode === "npm" ? dshSupported ? "pnpm" : "pnpm-missing" : dshInstall.mode === "link" ? "link" : "manual",
6688
6666
  ...dshLatest === void 0 ? { checkError: "latest-unavailable" } : {}
6667
+ }, {
6668
+ id: "mnemon",
6669
+ name: "Mnemon CLI",
6670
+ ...mnemonLocal.install.command === void 0 ? {} : { executablePath: mnemonLocal.install.command },
6671
+ ...mnemonLocal.current === void 0 ? {} : { current: mnemonLocal.current },
6672
+ ...mnemonLatest === void 0 ? {} : { latest: mnemonLatest },
6673
+ outdated: mnemonOutdated,
6674
+ installMode: mnemonLocal.install.mode,
6675
+ updateSupported: mnemonSupported,
6676
+ updateHint: mnemonLocal.install.hint ?? (mnemonLocal.install.mode === "homebrew" ? mnemonSupported ? "brew" : "brew-missing" : mnemonLocal.install.mode === "go" ? "go" : mnemonLocal.install.mode === "missing" ? "install" : "manual"),
6677
+ ...mnemonLatest === void 0 ? { checkError: "latest-unavailable" } : {}
6689
6678
  }]
6690
6679
  };
6691
6680
  }
@@ -6799,6 +6788,13 @@ function requireWritable(runtime) {
6799
6788
  function requireCapability(runtime, typeId, capability) {
6800
6789
  assertParticipation(runtime.graph.config, typeId, capability, "manual");
6801
6790
  }
6791
+ /** A memory layer that is off takes no part in reads or writes, management and assistance included. */
6792
+ function requireLayerOn(runtime, sourceTypeId) {
6793
+ if (sourceTypeId !== void 0 && runtime.graph.config.memoryTopology.layers[sourceTypeId]?.enabled === false) throw new Error(`Memory layer ${sourceTypeId} is off; turn it on to read or change it`);
6794
+ }
6795
+ function sourceTypeOf(generation, sourceInstanceKey) {
6796
+ return generation?.sourceInstances().find((source) => source.sourceInstanceKey === sourceInstanceKey)?.sourceTypeId;
6797
+ }
6802
6798
  function success$1(value) {
6803
6799
  return {
6804
6800
  ok: true,
@@ -6883,8 +6879,12 @@ async function catalog(runtime, lifecycle) {
6883
6879
  }
6884
6880
  }
6885
6881
  async function compositionStatus(runtime) {
6882
+ const composition = runtime.graph.memoryComposition.inspect();
6883
+ const strategyTypeId = runtime.graph.memoryComposition.current()?.strategy.definition.manifest.typeId;
6886
6884
  return {
6887
- evaluation: runtime.graph.memoryComposition.inspect().evaluation,
6885
+ serving: composition.servingGenerationId !== void 0,
6886
+ ...strategyTypeId === void 0 ? {} : { strategyTypeId },
6887
+ evaluation: composition.evaluation,
6888
6888
  sources: (await catalog(runtime)).sources,
6889
6889
  configuration: runtime.graph.config.memoryTopology
6890
6890
  };
@@ -7010,6 +7010,7 @@ function createReadHandler(input, lifecycle, versions) {
7010
7010
  case "source-management-read": {
7011
7011
  const lease = runtime.graph.memoryComposition.acquire();
7012
7012
  try {
7013
+ requireLayerOn(runtime, sourceTypeOf(lease.generation, String(payload.sourceInstanceKey ?? "")));
7013
7014
  return success$1(await lease.generation.executeManagement({
7014
7015
  scope: runtime.scope,
7015
7016
  sourceInstanceKey: String(payload.sourceInstanceKey ?? ""),
@@ -7104,6 +7105,7 @@ function createActivationHandler(input) {
7104
7105
  try {
7105
7106
  const source = (await lease.generation.managementCatalog(runtime.scope)).sources.find((item) => item.sourceInstanceKey === payload.sourceInstanceKey && item.sourceTypeId === "memory-spaces");
7106
7107
  if (source === void 0) throw new Error("Memory Spaces Source instance is unavailable");
7108
+ requireLayerOn(runtime, source.sourceTypeId);
7107
7109
  return success$1(await lease.generation.executeManagement({
7108
7110
  scope: runtime.scope,
7109
7111
  sourceInstanceKey: source.sourceInstanceKey,
@@ -7158,6 +7160,7 @@ function createWriteHandler(input, lifecycle, versions) {
7158
7160
  ...typeof payload.expectedRevision === "string" ? { expectedRevision: payload.expectedRevision } : {},
7159
7161
  ...signal === void 0 ? {} : { signal }
7160
7162
  };
7163
+ requireLayerOn(runtime, sourceTypeOf(runtime.graph.memoryComposition.current(), request.sourceInstanceKey));
7161
7164
  if (lifecycle !== void 0) return success$1(await lifecycle.manageSource(runtime.graph, request));
7162
7165
  const lease = runtime.graph.memoryComposition.acquire();
7163
7166
  try {
@@ -7171,6 +7174,7 @@ function createWriteHandler(input, lifecycle, versions) {
7171
7174
  const source = (await catalog(runtime, lifecycle)).sources.find((item) => item.sourceInstanceKey === payload.sourceInstanceKey);
7172
7175
  const operation = String(payload.operation ?? "");
7173
7176
  if (source === void 0 || !source.assistance.includes(operation)) throw new Error("Host assistance is not available for this Source instance");
7177
+ requireLayerOn(runtime, source.sourceTypeId);
7174
7178
  if (operation !== "agent-search" && (payload.confirmed !== true || payload.expectedRevision !== source.revision)) throw new Error("Host assistance requires confirmation of the current Source revision");
7175
7179
  const value = await assisted(runtime, lifecycle, source.sourceTypeId, operation, object$4(payload.input), signal, {
7176
7180
  sourceInstanceKey: source.sourceInstanceKey,
@@ -7239,16 +7243,16 @@ function createPackHandler(input) {
7239
7243
  }
7240
7244
  };
7241
7245
  }
7242
- function registerRpc(connection, input, lifecycle, versions, managementAuthority = "loopback") {
7246
+ function registerRpc(connection, input, lifecycle, versions) {
7243
7247
  const versionManager = versions ?? new VersionUpdateManager({ mnemonCliPath: () => input.config.cliPath });
7244
7248
  const readHandler = createReadHandler(input, lifecycle, versionManager);
7245
7249
  const activationHandler = createActivationHandler(input);
7246
7250
  const writeHandler = createWriteHandler(input, lifecycle, versionManager);
7247
7251
  const packHandler = createPackHandler(input);
7248
- connection.rpc.handle(MNEMON_READ_CHANNEL, readHandler, { authority: "trusted-host" });
7249
- connection.rpc.handle(MNEMON_ACTIVATION_CHANNEL, activationHandler, { authority: "trusted-host" });
7250
- connection.rpc.handle(MNEMON_WRITE_CHANNEL, writeHandler, { authority: managementAuthority });
7251
- connection.rpc.handle(MNEMON_PACK_CHANNEL, packHandler, { authority: managementAuthority });
7252
+ connection.rpc.handle(MNEMON_READ_CHANNEL, readHandler);
7253
+ connection.rpc.handle(MNEMON_ACTIVATION_CHANNEL, activationHandler);
7254
+ connection.rpc.handle(MNEMON_WRITE_CHANNEL, writeHandler);
7255
+ connection.rpc.handle(MNEMON_PACK_CHANNEL, packHandler);
7252
7256
  return {
7253
7257
  read: readHandler,
7254
7258
  activation: activationHandler,
@@ -7403,9 +7407,9 @@ function createSettingsHandler(settings) {
7403
7407
  }
7404
7408
  };
7405
7409
  }
7406
- function registerSettingsRpc(connection, settings, authority = "loopback") {
7410
+ function registerSettingsRpc(connection, settings) {
7407
7411
  const handler = createSettingsHandler(settings);
7408
- connection.rpc.handle(MNEMON_SETTINGS_CHANNEL, handler, { authority });
7412
+ connection.rpc.handle(MNEMON_SETTINGS_CHANNEL, handler);
7409
7413
  return handler;
7410
7414
  }
7411
7415
  //#endregion
@@ -8328,7 +8332,6 @@ const mnemonSubagentTokenUsageProjectionDefinition = {
8328
8332
  key: "mnemonSubagentTokenUsage",
8329
8333
  stateVersion: 1,
8330
8334
  stateSchema: tokenUsageStateSchema,
8331
- schema: tokenUsageWire.viewSchema,
8332
8335
  init: () => ({
8333
8336
  descriptorSeen: false,
8334
8337
  totals: emptyTokenUsage(),
@@ -8351,7 +8354,6 @@ const mnemonSubagentTokenUsageProjectionDefinition = {
8351
8354
  last: sample
8352
8355
  };
8353
8356
  },
8354
- view: tokenUsageWire.view,
8355
8357
  wire: tokenUsageWire
8356
8358
  };
8357
8359
  /** Register lazily when the optional DSH projection service is present. */
@@ -8361,722 +8363,224 @@ function registerMnemonSubagentTokenUsageProjection(ctx) {
8361
8363
  });
8362
8364
  }
8363
8365
  //#endregion
8364
- //#region src/host/profile-schema.mjs
8365
- const predicate = Reflect.get(cosmokit, "isVolatile");
8366
- const supportsLiveConfig = typeof predicate === "function" && typeof Reflect.get(cosmokit, "createVolatile") === "function";
8367
- const isVolatile = (value) => supportsLiveConfig && predicate(value);
8368
- var profile_schema_default = supportsLiveConfig ? (await import("schemastery-live")).default : z;
8369
- //#endregion
8370
- //#region src/host/live-config.ts
8371
- function plain(value) {
8372
- if (isVolatile(value)) return plain(value.get());
8373
- if (Array.isArray(value)) return value.map(plain);
8374
- if (value === null || typeof value !== "object") return value;
8375
- const prototype = Object.getPrototypeOf(value);
8376
- if (prototype !== Object.prototype && prototype !== null) return value;
8377
- return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, plain(child)]));
8366
+ //#region src/host/plugin-management.ts
8367
+ /** Saved choices keep only configuration once the profile patch owns enablement. */
8368
+ function configurationOnly(entries) {
8369
+ return Object.fromEntries(Object.entries(entries).map(([entryId, value]) => [entryId, { config: value.config }]));
8378
8370
  }
8379
- /** Read a detached configuration snapshot from either DSH generation. */
8380
- function plainHostConfig(value = {}) {
8381
- return plain(value);
8371
+ const PACKAGE$1 = /^(?:@[a-z0-9._-]+\/)?dsh-mnemon-(source|strategy)-[a-z0-9._-]+$/u;
8372
+ const hash = (value) => createHash("sha256").update(canonicalMemoryJson(value)).digest("hex");
8373
+ const clone$1 = (value) => JSON.parse(JSON.stringify(value));
8374
+ function contributionRoles(snapshot, entryId) {
8375
+ const roles = /* @__PURE__ */ new Set();
8376
+ if (snapshot.sources.some((value) => value.provenance.entryId === entryId)) roles.add("source");
8377
+ if (snapshot.strategies.some((value) => value.provenance.entryId === entryId)) roles.add("strategy");
8378
+ if ((snapshot.strategyExtensions ?? []).some((value) => value.provenance.entryId === entryId)) roles.add("strategy-extension");
8379
+ return [...roles];
8382
8380
  }
8383
- function liveConfig() {
8384
- const schema = new profile_schema_default(Config.toJSON());
8385
- schema.dict = Object.fromEntries(Object.entries(schema.dict ?? {}).map(([key, field]) => [key, key === "remoteAccess" ? field : field.volatile()]));
8386
- const standard = schema["~standard"];
8387
- Object.defineProperty(schema, "~standard", { value: {
8388
- ...standard,
8389
- validate(value) {
8390
- const parsed = standard.validate(value);
8391
- if ("then" in parsed) throw new TypeError("Mnemon configuration validation must be synchronous");
8392
- if (parsed.issues) return parsed;
8393
- try {
8394
- resolveConfig(plainHostConfig(parsed.value));
8395
- return parsed;
8396
- } catch (error) {
8397
- return { issues: [{ message: error instanceof Error ? error.message : String(error) }] };
8398
- }
8399
- }
8400
- } });
8401
- return schema;
8381
+ function fallbackDescriptor(entry, snapshot, editor) {
8382
+ const sources = snapshot.sources.filter((value) => value.provenance.entryId === entry.id);
8383
+ const strategies = snapshot.strategies.filter((value) => value.provenance.entryId === entry.id);
8384
+ const extensions = (snapshot.strategyExtensions ?? []).filter((value) => value.provenance.entryId === entry.id);
8385
+ const roles = contributionRoles(snapshot, entry.id);
8386
+ if (roles.length === 0) roles.push(editor?.kind ?? (PACKAGE$1.exec(entry.options.name)?.[1] === "source" ? "source" : "strategy"));
8387
+ const typeId = editor?.typeId ?? strategies[0]?.definition.manifest.typeId ?? extensions[0]?.definition.manifest.typeId ?? sources[0]?.definition.manifest.typeId;
8388
+ const sourceLabel = sources[0]?.definition.manifest.management?.label;
8389
+ const label = editor?.label ?? {
8390
+ en: sourceLabel ?? typeId ?? entry.options.name,
8391
+ "zh-CN": sourceLabel ?? typeId ?? entry.options.name
8392
+ };
8393
+ const description = editor?.description ?? {
8394
+ en: "",
8395
+ "zh-CN": ""
8396
+ };
8397
+ const provides = roles.includes("source") ? [{ id: "source" }, ...sources.map((source) => ({ id: `source.${source.definition.manifest.role}` }))] : [{ id: roles.includes("strategy") ? "strategy" : `strategy.${typeId ?? hash(entry.options.name).slice(0, 12)}` }];
8398
+ return defineMemoryPlugin({
8399
+ packageName: entry.options.name,
8400
+ label,
8401
+ description,
8402
+ roles,
8403
+ provides
8404
+ });
8402
8405
  }
8403
- /** Profile Config for live forms; transport authority retains normal remounts. */
8404
- const LiveConfig = supportsLiveConfig ? liveConfig() : Config;
8405
- //#endregion
8406
- //#region src/host/legacy-settings-import.ts
8407
- const UNSAFE_KEYS = /* @__PURE__ */ new Set([
8408
- "__proto__",
8409
- "prototype",
8410
- "constructor"
8411
- ]);
8412
- const ENTRY_ID = /^[a-zA-Z0-9][a-zA-Z0-9._:/-]{0,299}$/u;
8413
- const STRATEGY_ID = /^[a-z][a-z0-9-]{0,127}$/u;
8414
- const VIEW_NAMESPACE = /^mnemon-view(?:-[a-f0-9]{16})?$/u;
8415
- const LEGACY_NAMESPACE = /^mnemon-plugins(?:-[a-f0-9]{16})?$/u;
8416
- function object$2(value, label) {
8417
- if (value === null || typeof value !== "object" || Array.isArray(value) || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) throw new Error(`${label} must be a plain object`);
8418
- for (const key of Reflect.ownKeys(value)) {
8419
- if (typeof key !== "string" || UNSAFE_KEYS.has(key)) throw new Error(`${label} contains an unsafe key`);
8420
- const descriptor = Object.getOwnPropertyDescriptor(value, key);
8421
- if (!descriptor.enumerable || !Object.hasOwn(descriptor, "value")) throw new Error(`${label} must contain only data properties`);
8422
- }
8423
- return value;
8406
+ function prepared(item, config) {
8407
+ if (item.editor === void 0) throw new Error(`Plugin does not expose a pure configuration factory: ${item.entry.id}`);
8408
+ const contribution = item.editor.create(config);
8409
+ if ([...contribution.strategies ?? [], ...contribution.strategyExtensions ?? []][0].manifest.packageName !== item.entry.options.name) throw new Error("Plugin factory does not match its declared Entry");
8410
+ if (contribution.plugin !== void 0 && hash(contribution.plugin) !== hash(item.descriptor)) throw new Error("Plugin factory descriptor does not match its module export");
8411
+ return prepareMemoryContributions({
8412
+ ...contribution,
8413
+ plugin: contribution.plugin ?? item.descriptor
8414
+ }, { instanceId: item.entry.id });
8424
8415
  }
8425
- /** Copy JSON data without invoking getters, serializers or prototype methods. */
8426
- function copy(value, label, ancestors = /* @__PURE__ */ new Set(), depth = 0) {
8427
- if (value === null || typeof value === "string" || typeof value === "boolean") return value;
8428
- if (typeof value === "number" && Number.isFinite(value)) return value;
8429
- if (typeof value !== "object" || value === null) throw new Error(`${label} must contain only JSON data`);
8430
- if (depth > 64 || ancestors.has(value)) throw new Error(`${label} contains cyclic or excessively nested data`);
8431
- ancestors.add(value);
8432
- try {
8433
- if (Array.isArray(value)) {
8434
- if (Object.getPrototypeOf(value) !== Array.prototype) throw new Error(`${label} must contain plain arrays`);
8435
- if (Reflect.ownKeys(value).length !== value.length + 1) throw new Error(`${label} contains a sparse or extended array`);
8436
- return Array.from({ length: value.length }, (_, index) => {
8437
- const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
8438
- if (!descriptor || !descriptor.enumerable || !Object.hasOwn(descriptor, "value")) throw new Error(`${label} must contain only data properties`);
8439
- return copy(descriptor.value, label, ancestors, depth + 1);
8440
- });
8441
- }
8442
- return Object.fromEntries(Object.entries(object$2(value, label)).map(([key, item]) => [key, copy(item, label, ancestors, depth + 1)]));
8443
- } finally {
8444
- ancestors.delete(value);
8445
- }
8446
- }
8447
- function copiedObject(value, label) {
8448
- object$2(value, label);
8449
- return copy(value, label);
8450
- }
8451
- /** Inspect validated copies only; never evaluate expressions or invoke getters. */
8452
- function containsExpression(value) {
8453
- return value !== null && typeof value === "object" && (Object.hasOwn(value, "__jsExpr") || Object.values(value).some(containsExpression));
8454
- }
8455
- function fields(value, allowed, label) {
8456
- if (Object.keys(value).some((key) => !allowed.includes(key))) throw new Error(`${label} contains unsupported fields`);
8457
- }
8458
- function interaction(value, label, allowed) {
8459
- const result = copiedObject(value, label);
8460
- fields(result, allowed, label);
8461
- if (Object.values(result).some((item) => typeof item !== "boolean")) throw new Error(`${label} preferences must be boolean`);
8462
- return result;
8463
- }
8464
- function entryId(value, label) {
8465
- if (typeof value !== "string" || !ENTRY_ID.test(value) || UNSAFE_KEYS.has(value)) throw new Error(`${label} contains an invalid Entry id`);
8466
- }
8467
- function view(value, label) {
8468
- const result = copiedObject(value, label);
8469
- fields(result, ["strategyTypeId", "entries"], label);
8470
- if (Object.hasOwn(result, "strategyTypeId") && (typeof result.strategyTypeId !== "string" || !STRATEGY_ID.test(result.strategyTypeId))) throw new Error(`${label} contains an invalid Strategy type id`);
8471
- const entries = {};
8472
- if (Object.hasOwn(result, "entries")) {
8473
- const values = object$2(result.entries, `${label}.entries`);
8474
- if (Object.keys(values).length > 64) throw new Error(`${label} contains too many Entries`);
8475
- for (const [id, raw] of Object.entries(values)) {
8476
- entryId(id, label);
8477
- const item = copiedObject(raw, `${label}.entries`);
8478
- fields(item, ["enabled", "config"], `${label}.entries`);
8479
- if (typeof item.enabled !== "boolean") throw new Error(`${label} Entry enabled must be boolean`);
8480
- const config = Object.hasOwn(item, "config") ? copiedObject(item.config, `${label} Entry config`) : {};
8481
- if (JSON.stringify(config).length > 65536) throw new Error(`${label} Entry config exceeds 64 KiB`);
8482
- entries[id] = {
8483
- enabled: item.enabled,
8484
- config
8485
- };
8486
- }
8487
- }
8416
+ function installedPlugin(descriptor, entryId) {
8488
8417
  return {
8489
- ...typeof result.strategyTypeId === "string" ? { strategyTypeId: result.strategyTypeId } : {},
8490
- ...Object.hasOwn(result, "entries") ? { entries } : {}
8418
+ kind: "plugin",
8419
+ instanceKey: `plugin:${entryId}`,
8420
+ provenance: {
8421
+ packageName: descriptor.packageName,
8422
+ entryId
8423
+ },
8424
+ descriptor
8491
8425
  };
8492
8426
  }
8493
- function sources(value, label, sourceEntryIds, diagnostics) {
8494
- const result = copiedObject(value, label);
8495
- fields(result, ["sources"], label);
8496
- const values = Object.hasOwn(result, "sources") ? object$2(result.sources, `${label}.sources`) : {};
8497
- if (Object.keys(values).length > 64) throw new Error(`${label} contains too many Source Entries`);
8498
- const entries = {};
8499
- for (const [id, raw] of Object.entries(values)) {
8500
- entryId(id, label);
8501
- const item = copiedObject(raw, `${label}.sources`);
8502
- fields(item, ["enabled"], `${label}.sources`);
8503
- if (typeof item.enabled !== "boolean") throw new Error(`${label} Source enabled must be boolean`);
8504
- if (!sourceEntryIds.has(id)) {
8505
- diagnostics.push(`Skipped legacy Source preference for ${id}: the current profile does not declare it as a Source.`);
8506
- continue;
8507
- }
8508
- entries[id] = {
8509
- enabled: item.enabled,
8510
- config: {}
8511
- };
8512
- }
8513
- return entries;
8514
- }
8515
- function applyEntryOverrides(entries, overrides, sourceEntryIds, diagnostics) {
8516
- if (!Array.isArray(overrides)) throw new Error("Explicit Entry overrides must be an array");
8517
- const matching = /* @__PURE__ */ new Map();
8518
- for (const raw of overrides) {
8519
- const row = object$2(raw, "Explicit Entry override");
8520
- entryId(row.id, "Explicit Entry override");
8521
- if (!Object.hasOwn(entries, row.id)) continue;
8522
- const previous = matching.get(row.id) ?? {};
8523
- matching.set(row.id, {
8524
- ...previous,
8525
- ...row.disabled === void 0 ? {} : { disabled: row.disabled },
8526
- ...row.config === void 0 ? {} : { config: row.config }
8527
- });
8427
+ function orderPlugins(values) {
8428
+ const byId = new Map(values.map((value) => [value.entryId, value]));
8429
+ const edges = new Map(values.map((value) => [value.entryId, /* @__PURE__ */ new Set()]));
8430
+ const indegree = new Map(values.map((value) => [value.entryId, 0]));
8431
+ for (const dependent of values) for (const requirement of dependent.requires) for (const provider of values) {
8432
+ if (provider.entryId === dependent.entryId || !provider.provides.some((capability) => capability.id === requirement)) continue;
8433
+ if (edges.get(provider.entryId).has(dependent.entryId)) continue;
8434
+ edges.get(provider.entryId).add(dependent.entryId);
8435
+ indegree.set(dependent.entryId, indegree.get(dependent.entryId) + 1);
8528
8436
  }
8529
- for (const [id, row] of matching) {
8530
- if (row.disabled !== void 0 && typeof row.disabled !== "boolean") {
8531
- delete entries[id];
8532
- diagnostics.push(`Skipped legacy View preference for ${id}: its explicit disabled value is not boolean.`);
8533
- continue;
8534
- }
8535
- if (row.config !== void 0 && !sourceEntryIds.has(id)) {
8536
- const config = copiedObject(row.config, "Explicit Entry config");
8537
- if (containsExpression(config)) {
8538
- delete entries[id];
8539
- diagnostics.push(`Skipped legacy View preference for ${id}: its explicit config contains an expression.`);
8540
- continue;
8437
+ const stable = (left, right) => byId.get(left).packageName.localeCompare(byId.get(right).packageName) || left.localeCompare(right);
8438
+ const ready = [...indegree].filter(([, count]) => count === 0).map(([entryId]) => entryId).sort(stable);
8439
+ const ordered = [];
8440
+ while (ready.length > 0) {
8441
+ const entryId = ready.shift();
8442
+ ordered.push(byId.get(entryId));
8443
+ for (const dependent of [...edges.get(entryId)].sort(stable)) {
8444
+ const next = indegree.get(dependent) - 1;
8445
+ indegree.set(dependent, next);
8446
+ if (next === 0) {
8447
+ ready.push(dependent);
8448
+ ready.sort(stable);
8541
8449
  }
8542
- entries[id].config = config;
8543
8450
  }
8544
- if (typeof row.disabled === "boolean") entries[id].enabled = !row.disabled;
8545
8451
  }
8452
+ if (ordered.length < values.length) ordered.push(...values.filter((value) => !ordered.includes(value)).sort((left, right) => stable(left.entryId, right.entryId)));
8453
+ return ordered;
8546
8454
  }
8547
8455
  /**
8548
- * Recover only the canonical root and the exact owning profile's old namespaces.
8549
- * This does not read files, edit profile rows, validate plugin domains or commit.
8550
- * Malformed relevant input rejects the complete plan without an import marker.
8456
+ * Profile-local plugin graph overlay. Every dsh-mnemon Source or Strategy Entry
8457
+ * is one peer node; activation, configuration, dependency checks and rollback
8458
+ * use one transaction. Contribution roles do not create separate managers.
8551
8459
  */
8552
- function planLegacySettingsImport(options) {
8553
- const diagnostics = [];
8554
- try {
8555
- const rawCurrent = object$2(options.currentOverride, "Current config override");
8556
- if (rawCurrent.legacySettingsImported === true) return {
8557
- patch: {},
8558
- diagnostics
8559
- };
8560
- if (options.entryId !== "mnemon") throw new Error("Legacy settings belong to the canonical mnemon Entry; another root Entry is ambiguous.");
8561
- if (!VIEW_NAMESPACE.test(options.viewNamespace) || !LEGACY_NAMESPACE.test(options.legacyNamespace) || options.legacyNamespace !== options.viewNamespace.replace(/^mnemon-view/u, "mnemon-plugins")) throw new Error("Legacy settings namespaces must identify the same exact profile.");
8562
- const sections = object$2(options.sections, "Legacy settings document");
8563
- for (const key of Object.keys(sections)) if (key.startsWith("mnemon-view-") && !VIEW_NAMESPACE.test(key) || key.startsWith("mnemon-plugins-") && !LEGACY_NAMESPACE.test(key)) throw new Error("Legacy settings document contains an invalid Mnemon namespace.");
8564
- const relevant = [
8565
- "mnemon",
8566
- "mnemon-ui",
8567
- options.viewNamespace,
8568
- options.legacyNamespace
8569
- ];
8570
- if (!relevant.some((key) => Object.hasOwn(sections, key))) return {
8571
- patch: {},
8572
- diagnostics
8573
- };
8574
- for (const key of relevant) if (Object.hasOwn(sections, key) && containsExpression(copiedObject(sections[key], key))) throw new Error(`${key} contains an expression; legacy settings must contain only literal data.`);
8575
- const current = copiedObject(rawCurrent, "Current config override");
8576
- if (Object.hasOwn(current, "legacySettingsImported") && typeof current.legacySettingsImported !== "boolean") throw new Error("Current import marker must be boolean");
8577
- const root = Object.hasOwn(sections, "mnemon") ? copiedObject(sections.mnemon, "mnemon") : {};
8578
- if (Object.hasOwn(root, "memoryView") || Object.hasOwn(root, "legacySettingsImported")) throw new Error("Legacy mnemon contains reserved migration fields");
8579
- const oldInteraction = Object.hasOwn(root, "conversationInteraction") ? interaction(root.conversationInteraction, "mnemon.conversationInteraction", [
8580
- "toolviews",
8581
- "turnBar",
8582
- "saveAction"
8583
- ]) : {};
8584
- const oldUi = Object.hasOwn(sections, "mnemon-ui") ? interaction(sections["mnemon-ui"], "mnemon-ui", ["turnBar", "saveAction"]) : {};
8585
- const currentInteraction = Object.hasOwn(current, "conversationInteraction") ? interaction(current.conversationInteraction, "Current conversationInteraction", [
8586
- "toolviews",
8587
- "turnBar",
8588
- "saveAction"
8589
- ]) : {};
8590
- const patch = Object.fromEntries(Object.entries(root).filter(([key]) => key !== "conversationInteraction" && !Object.hasOwn(current, key)));
8591
- const inheritedInteraction = {
8592
- ...oldInteraction,
8593
- ...oldUi
8594
- };
8595
- if (Object.keys(inheritedInteraction).some((key) => !Object.hasOwn(currentInteraction, key))) patch.conversationInteraction = {
8596
- ...inheritedInteraction,
8597
- ...currentInteraction
8598
- };
8599
- const currentView = Object.hasOwn(current, "memoryView") ? view(current.memoryView, "Current memoryView") : {};
8600
- if (Object.hasOwn(sections, options.viewNamespace) || Object.hasOwn(sections, options.legacyNamespace)) {
8601
- if (!Array.isArray(options.sourceEntryIds)) throw new Error("Source Entry ids must be an array");
8602
- for (const id of options.sourceEntryIds) entryId(id, "Source Entry ids");
8603
- const sourceEntryIds = new Set(options.sourceEntryIds);
8604
- const oldSources = Object.hasOwn(sections, options.legacyNamespace) ? sources(sections[options.legacyNamespace], options.legacyNamespace, sourceEntryIds, diagnostics) : {};
8605
- const oldView = Object.hasOwn(sections, options.viewNamespace) ? view(sections[options.viewNamespace], options.viewNamespace) : {};
8606
- const entries = {
8607
- ...oldSources,
8608
- ...oldView.entries
8609
- };
8610
- applyEntryOverrides(entries, options.entryOverrides, sourceEntryIds, diagnostics);
8611
- const migratedView = {
8612
- ...oldView,
8613
- entries,
8614
- ...currentView
8615
- };
8616
- if (!Object.hasOwn(currentView, "entries") || !Object.hasOwn(currentView, "strategyTypeId") && oldView.strategyTypeId !== void 0) patch.memoryView = migratedView;
8617
- }
8618
- patch.legacySettingsImported = true;
8619
- return {
8620
- patch,
8621
- diagnostics
8622
- };
8623
- } catch (error) {
8624
- diagnostics.push(error instanceof Error ? error.message : "Legacy settings import could not be planned");
8625
- return {
8626
- patch: {},
8627
- diagnostics
8628
- };
8629
- }
8630
- }
8631
- //#endregion
8632
- //#region src/host/settings-service.ts
8633
- const pendingNativeImports = /* @__PURE__ */ new WeakSet();
8634
- const clone$1 = (value) => structuredClone(value);
8635
- const object$1 = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
8636
- function profileYaml(source) {
8637
- const document = parseDocument(source, { customTags: [{
8638
- tag: "tag:yaml.org,2002:js",
8639
- resolve: (value) => ({ __jsExpr: value })
8640
- }] });
8641
- if (document.errors.length > 0 || document.warnings.length > 0) throw new Error("Invalid or unsupported legacy/profile YAML; retained backup was not changed");
8642
- return document.toJS({ maxAliasCount: 100 });
8643
- }
8644
- /** A local facade preserves Mnemon's wire namespaces; DSH owns all persistence. */
8645
- var ProfileMnemonSettings = class {
8460
+ var MemoryPluginManagement = class {
8646
8461
  ctx;
8647
- input;
8648
- registrations = /* @__PURE__ */ new Map();
8649
- listeners = /* @__PURE__ */ new Set();
8650
- revisions = /* @__PURE__ */ new Map();
8651
- owner;
8652
- forms;
8653
- editor;
8462
+ engine;
8463
+ settingsService;
8464
+ settingsNamespace;
8465
+ settings;
8466
+ legacySettings;
8467
+ queue = Promise.resolve();
8654
8468
  closed = false;
8655
- importing;
8656
- nativeImportPending;
8657
- constructor(ctx, input) {
8469
+ timer;
8470
+ restoreError;
8471
+ discoveryWarnings = [];
8472
+ changingEntries = false;
8473
+ restorePending = false;
8474
+ /** A failed move keeps saved choices in effect instead of retrying on every profile reload. */
8475
+ enablementMoveError;
8476
+ constructor(ctx, engine, settingsService) {
8658
8477
  this.ctx = ctx;
8659
- this.input = input;
8660
- this.forms = ctx.settings;
8661
- this.owner = ctx.fiber;
8662
- this.editor = ctx.get("configEditor");
8663
- const profile = ctx.get("profileContext");
8664
- const root = ctx.root ?? ctx;
8665
- if (profile?.home !== void 0 && existsSync(join(profile.home, "settings.yaml"))) pendingNativeImports.add(root);
8666
- this.nativeImportPending = pendingNativeImports.has(root);
8667
- const owner = this.owner;
8668
- const validate = (candidate) => {
8669
- const config = plainHostConfig(candidate);
8670
- for (const registration of this.registrations.values()) {
8671
- if (registration.legacy) continue;
8672
- const value = this.resolve(registration, config);
8673
- registration.validate?.(value);
8478
+ this.engine = engine;
8479
+ this.settingsService = settingsService;
8480
+ const loader = this.loader();
8481
+ const anchor = loader?.config?.baseUrl ?? loader?.ctx?.baseUrl ?? loader?.context?.baseUrl;
8482
+ const suffix = anchor ? `-${hash(anchor).slice(0, 16)}` : "";
8483
+ this.settingsNamespace = `${MNEMON_VIEW_SETTINGS_NAMESPACE}${suffix}`;
8484
+ this.settings = settingsService.register(this.settingsNamespace, schema, {
8485
+ base: { entries: {} },
8486
+ applies: "live",
8487
+ validate: (value) => {
8488
+ preferences(value);
8674
8489
  }
8675
- };
8676
- ctx.on("internal/config", function(_raw, next) {
8677
- const candidate = next();
8678
- if (this === owner) validate(candidate);
8679
- return candidate;
8680
- }, { prepend: true });
8681
- ctx.on("loader/volatile-update", ((paths) => {
8682
- if (this.closed) return;
8683
- for (const registration of this.registrations.values()) {
8684
- if (registration.legacy) continue;
8685
- if (!paths.some((path) => registration.field === void 0 ? path.length === 0 || !["memoryView", "legacySettingsImported"].includes(path[0]) : path.length === 0 || path[0] === registration.field)) continue;
8686
- const value = this.resolve(registration);
8687
- for (const listener of this.listeners) try {
8688
- listener(registration.namespace, value);
8689
- } catch (error) {
8690
- console.warn("dsh-mnemon: committed settings listener failed", error);
8691
- }
8490
+ });
8491
+ this.legacySettings = settingsService.register(`mnemon-plugins${suffix}`, legacySchema, {
8492
+ base: { sources: {} },
8493
+ applies: "live",
8494
+ validate: (value) => {
8495
+ legacyPreferences(value);
8692
8496
  }
8693
- }));
8694
- ctx.effect(() => {
8695
- const disposePresentation = owner === void 0 ? void 0 : this.forms.configure({ auto: false }, owner);
8696
- return () => {
8697
- this.closed = true;
8698
- this.listeners.clear();
8699
- this.revisions.clear();
8700
- disposePresentation?.();
8701
- };
8702
- }, "dsh-mnemon: profile settings");
8497
+ });
8703
8498
  }
8704
- get writable() {
8705
- const entry = this.owner?.entry;
8706
- return !this.closed && this.forms.writable && entry !== void 0 && this.editor?.entries().includes(entry) === true;
8499
+ resolveConfig(config, view = this.settings.get()) {
8500
+ const selected = view.strategyTypeId;
8501
+ return selected === void 0 ? config : {
8502
+ ...config,
8503
+ memoryTopology: {
8504
+ ...config.memoryTopology,
8505
+ strategyId: selected
8506
+ }
8507
+ };
8707
8508
  }
8708
- onUpdated(listener) {
8709
- this.listeners.add(listener);
8509
+ start() {
8510
+ const schedule = () => this.scheduleRestore();
8511
+ const stops = [
8512
+ this.ctx.on("loader/entry-init", schedule),
8513
+ this.ctx.on("loader/partial-dispose", schedule),
8514
+ this.ctx.on("app-boot/config-reload", () => this.scheduleRestore(true)),
8515
+ this.settingsService.onUpdated((namespace) => {
8516
+ if (namespace === this.settingsNamespace || namespace === `mnemon-plugins${this.settingsNamespace.slice(11)}`) schedule();
8517
+ })
8518
+ ];
8519
+ this.ctx.inject(["pluginManager"], () => {
8520
+ schedule();
8521
+ });
8522
+ schedule();
8710
8523
  return () => {
8711
- this.listeners.delete(listener);
8524
+ this.closed = true;
8525
+ this.restorePending = false;
8526
+ clearTimeout(this.timer);
8527
+ for (const stop of stops.reverse()) stop();
8712
8528
  };
8713
8529
  }
8714
- resolve(registration, config = plainHostConfig(this.owner?.config ?? this.input)) {
8715
- const value = registration.legacy ? registration.base : registration.field === void 0 ? config : config[registration.field];
8716
- return registration.schema(object$1(value));
8530
+ scheduleRestore(profileReload = false) {
8531
+ const hasSavedState = Object.keys(this.settings.get().entries ?? {}).length > 0 || Object.keys(this.legacySettings.get().sources ?? {}).length > 0;
8532
+ if (this.closed || !profileReload && this.changingEntries || !hasSavedState) return;
8533
+ this.restorePending = true;
8534
+ if (this.changingEntries || this.timer) return;
8535
+ this.timer = setTimeout(() => {
8536
+ this.timer = void 0;
8537
+ this.enqueueRestore();
8538
+ }, 0);
8717
8539
  }
8718
- register(namespace, schema, options) {
8719
- if (this.registrations.has(namespace)) throw new Error(`Mnemon settings already registered: ${namespace}`);
8720
- if (typeof schema !== "function") throw new Error("Mnemon settings require a schema");
8721
- const field = namespace === "mnemon-ui" ? "conversationInteraction" : /^mnemon-view(?:-[a-f0-9]{16})?$/u.test(namespace) ? "memoryView" : void 0;
8722
- const legacy = /^mnemon-plugins(?:-[a-f0-9]{16})?$/u.test(namespace);
8723
- if (namespace !== "mnemon" && field === void 0 && !legacy) throw new Error("Unsupported Mnemon settings namespace");
8724
- const registration = {
8725
- namespace,
8726
- ...field === void 0 ? {} : { field },
8727
- legacy,
8728
- schema,
8729
- base: clone$1(options.base ?? {}),
8730
- ...options.validate === void 0 ? {} : { validate: options.validate }
8731
- };
8732
- const value = this.resolve(registration);
8733
- options.validate?.(value);
8734
- this.registrations.set(namespace, registration);
8735
- return { get: () => this.resolve(registration) };
8540
+ enqueueRestore() {
8541
+ if (this.closed || this.changingEntries || !this.restorePending) return;
8542
+ clearTimeout(this.timer);
8543
+ this.timer = void 0;
8544
+ this.restorePending = false;
8545
+ this.exclusive(async () => {
8546
+ try {
8547
+ await this.restore();
8548
+ } catch (error) {
8549
+ this.restoreError = error instanceof Error ? error.message : String(error);
8550
+ }
8551
+ }).catch(() => {});
8736
8552
  }
8737
- nativeDescriptor() {
8738
- if (this.closed || this.owner?.entry === void 0 || !this.editor?.entries().includes(this.owner.entry)) return void 0;
8739
- const descriptor = this.forms.describe().find((item) => item.ns === this.owner?.entry?.options.id);
8740
- if (descriptor !== void 0) {
8741
- this.revisions.set(descriptor.revision, clone$1({
8742
- ns: descriptor.ns,
8743
- revision: descriptor.revision,
8744
- applies: descriptor.applies,
8745
- value: descriptor.value,
8746
- base: descriptor.base,
8747
- user: descriptor.user
8748
- }));
8749
- if (this.revisions.size > 64) this.revisions.delete(this.revisions.keys().next().value);
8750
- }
8751
- return descriptor;
8553
+ /** A response must not race the deferred restore after profile reconciliation. */
8554
+ async settled() {
8555
+ do {
8556
+ this.enqueueRestore();
8557
+ const pending = this.queue;
8558
+ await pending;
8559
+ if (pending === this.queue && !this.restorePending) return;
8560
+ } while (!this.closed);
8752
8561
  }
8753
- unchanged(previous, current, registration, operations) {
8754
- const project = (value) => {
8755
- if (registration.field !== void 0) return object$1(value)[registration.field];
8756
- const { memoryView: _view, legacySettingsImported: _imported, conversationInteraction, ...core } = object$1(value);
8757
- return operations.some((operation) => operation.path[0] === "conversationInteraction") ? {
8758
- ...core,
8759
- conversationInteraction
8760
- } : core;
8761
- };
8762
- const signature = (descriptor) => canonicalMemoryJson(JSON.parse(JSON.stringify([
8763
- project(descriptor.value) ?? null,
8764
- project(descriptor.base) ?? null,
8765
- project(descriptor.user) ?? null
8766
- ])));
8767
- return signature(previous) === signature(current);
8562
+ loader() {
8563
+ const candidate = this.ctx.get("loader");
8564
+ return candidate && typeof candidate.entries === "function" ? candidate : void 0;
8768
8565
  }
8769
- describe(options) {
8770
- const raw = this.nativeDescriptor();
8771
- if (raw === void 0) return [];
8772
- const descriptor = options?.redactSecrets ? this.forms.describe(options).find((item) => item.ns === this.owner?.entry?.options.id) : raw;
8773
- if (descriptor === void 0) return [];
8774
- return [...this.registrations.values()].map((registration) => {
8775
- const project = (value) => registration.legacy ? {} : registration.field === void 0 ? value : object$1(value)[registration.field];
8776
- return {
8777
- ns: registration.namespace,
8778
- value: project(descriptor.value) ?? {},
8779
- base: project(descriptor.base) ?? {},
8780
- user: project(descriptor.user) ?? {},
8781
- revision: descriptor.revision,
8782
- applies: "live"
8783
- };
8784
- });
8566
+ /**
8567
+ * With the DSH plugin manager, the profile patch owns enablement, so its
8568
+ * Plugins page and Mnemon agree. Profiles without it keep Mnemon's overlay.
8569
+ */
8570
+ pluginManager() {
8571
+ const candidate = this.ctx.get("pluginManager");
8572
+ return typeof candidate?.setPluginEnabled === "function" ? candidate : void 0;
8785
8573
  }
8786
- async mutate(namespace, operations, expectedRevision) {
8787
- const registration = this.registrations.get(namespace);
8788
- if (registration === void 0 || registration.legacy) throw new Error("Unsupported Mnemon settings namespace");
8789
- if (!this.writable) throw new Error("DSH settings are read-only");
8790
- const translated = operations.map((operation) => ({
8791
- ...operation,
8792
- path: [...registration.field === void 0 ? [] : [registration.field], ...operation.path]
8793
- }));
8794
- let revision = expectedRevision;
8795
- const previous = revision === void 0 ? void 0 : this.revisions.get(revision);
8796
- for (let attempt = 0;; attempt += 1) {
8797
- if (!this.writable) throw new Error("DSH settings are read-only");
8798
- try {
8799
- await this.forms.mutate(this.owner.entry.options.id, translated, revision);
8800
- return;
8801
- } catch (error) {
8802
- if (attempt >= 2 || previous === void 0 || typeof error !== "object" || error === null || !("code" in error) || error.code !== "SETTINGS_CONFLICT") throw error;
8803
- if (!this.writable) throw new Error("DSH settings are read-only");
8804
- const current = this.nativeDescriptor();
8805
- if (current === void 0 || !this.unchanged(previous, current, registration, operations)) throw error;
8806
- revision = current.revision;
8807
- }
8808
- }
8574
+ async setEnabled(manager, entryId, enabled) {
8575
+ const result = await manager.setPluginEnabled(entryId, enabled);
8576
+ if (result.application !== "applied") throw new Error(`DSH could not ${enabled ? "enable" : "disable"} ${entryId}: ${result.error?.message ?? result.application}`);
8809
8577
  }
8810
- /** Read only the retained backup; ConfigEditor preserves all other patch rows. */
8811
- async importLegacy(sourceEntryIds) {
8812
- if (this.importing !== void 0) return this.importing;
8813
- this.importing = this.performImport(sourceEntryIds).finally(() => {
8814
- this.importing = void 0;
8815
- });
8816
- return this.importing;
8578
+ settingsRevision(namespace = this.settingsNamespace) {
8579
+ return this.settingsService.describe({ redactSecrets: true }).find((value) => value.ns === namespace)?.revision ?? 0;
8817
8580
  }
8818
- async performImport(sourceEntryIds) {
8819
- const profile = this.ctx.get("profileContext");
8820
- const entry = this.owner?.entry;
8821
- if (!this.writable || profile?.home === void 0 || entry === void 0) return;
8822
- if (this.nativeImportPending) {
8823
- console.warn("dsh-mnemon: DSH is importing settings.yaml; restart once to recover retained Mnemon UI and plugin preferences");
8824
- return;
8825
- }
8826
- if (plainHostConfig(this.owner?.config ?? this.input).legacySettingsImported) return;
8827
- const backup = join(profile.home, "settings.yaml.imported");
8828
- let text;
8829
- try {
8830
- if ((await stat(backup)).size > 1048576) throw new Error("Legacy settings backup exceeds the import limit");
8831
- text = await readFile(backup, "utf8");
8832
- } catch (error) {
8833
- if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") return;
8834
- throw error;
8835
- }
8836
- const sections = profileYaml(text);
8837
- const viewNamespace = [...this.registrations.keys()].find((namespace) => namespace.startsWith("mnemon-view"));
8838
- const legacyNamespace = [...this.registrations.keys()].find((namespace) => namespace.startsWith("mnemon-plugins"));
8839
- const plan = () => {
8840
- const configuration = this.editor.configuration().find((item) => item.entry === entry);
8841
- if (configuration === void 0) throw new Error("Mnemon profile entry is no longer editable");
8842
- const document = profileYaml(readFileSync(this.editor.documentPath, "utf8"));
8843
- const entryOverrides = Array.isArray(document) ? document.flatMap((value) => {
8844
- const row = object$1(value);
8845
- if (typeof row.id !== "string") return [];
8846
- const entries = this.editor.entries().filter((candidate) => candidate.options.id === row.id);
8847
- if (entries.length > 1) throw new Error("Legacy settings import requires unambiguous profile Entry ids");
8848
- return [{
8849
- id: entries[0]?.id ?? row.id,
8850
- ..."disabled" in row ? { disabled: row.disabled } : {},
8851
- ..."config" in row ? { config: row.config } : {}
8852
- }];
8853
- }) : [];
8854
- return planLegacySettingsImport({
8855
- entryId: entry.options.id,
8856
- viewNamespace,
8857
- legacyNamespace,
8858
- sections,
8859
- currentOverride: configuration.override,
8860
- entryOverrides,
8861
- sourceEntryIds
8862
- });
8863
- };
8864
- const preview = plan();
8865
- if (Object.keys(preview.patch).length === 0) {
8866
- if (preview.diagnostics.length > 0) console.warn("dsh-mnemon: legacy settings were not imported; retained backup requires review");
8867
- return;
8868
- }
8869
- if (this.closed) return;
8870
- await this.editor.edit(entry, (current) => {
8871
- if (this.closed) throw new Error("Mnemon settings are disposed");
8872
- const next = plan();
8873
- return {
8874
- ...current,
8875
- ...next.patch
8876
- };
8877
- });
8878
- }
8879
- };
8880
- function createHostSettings(ctx, config) {
8881
- return typeof ctx.settings.register === "function" ? ctx.settings : new ProfileMnemonSettings(ctx, config);
8882
- }
8883
- function subscribeSettings(ctx, settings, listener) {
8884
- return settings instanceof ProfileMnemonSettings ? settings.onUpdated(listener) : ctx.on("settings/updated", listener);
8885
- }
8886
- //#endregion
8887
- //#region src/host/plugin-management.ts
8888
- const PACKAGE$1 = /^(?:@[a-z0-9._-]+\/)?dsh-mnemon-(source|strategy)-[a-z0-9._-]+$/u;
8889
- const hash = (value) => createHash("sha256").update(canonicalMemoryJson(value)).digest("hex");
8890
- const clone = (value) => JSON.parse(JSON.stringify(value));
8891
- function contributionRoles(snapshot, entryId) {
8892
- const roles = /* @__PURE__ */ new Set();
8893
- if (snapshot.sources.some((value) => value.provenance.entryId === entryId)) roles.add("source");
8894
- if (snapshot.strategies.some((value) => value.provenance.entryId === entryId)) roles.add("strategy");
8895
- if ((snapshot.strategyExtensions ?? []).some((value) => value.provenance.entryId === entryId)) roles.add("strategy-extension");
8896
- return [...roles];
8897
- }
8898
- function fallbackDescriptor(entry, snapshot, editor) {
8899
- const sources = snapshot.sources.filter((value) => value.provenance.entryId === entry.id);
8900
- const strategies = snapshot.strategies.filter((value) => value.provenance.entryId === entry.id);
8901
- const extensions = (snapshot.strategyExtensions ?? []).filter((value) => value.provenance.entryId === entry.id);
8902
- const roles = contributionRoles(snapshot, entry.id);
8903
- if (roles.length === 0) roles.push(editor?.kind ?? (PACKAGE$1.exec(entry.options.name)?.[1] === "source" ? "source" : "strategy"));
8904
- const typeId = editor?.typeId ?? strategies[0]?.definition.manifest.typeId ?? extensions[0]?.definition.manifest.typeId ?? sources[0]?.definition.manifest.typeId;
8905
- const sourceLabel = sources[0]?.definition.manifest.management?.label;
8906
- const label = editor?.label ?? {
8907
- en: sourceLabel ?? typeId ?? entry.options.name,
8908
- "zh-CN": sourceLabel ?? typeId ?? entry.options.name
8909
- };
8910
- const description = editor?.description ?? {
8911
- en: "",
8912
- "zh-CN": ""
8913
- };
8914
- const provides = roles.includes("source") ? [{ id: "source" }, ...sources.map((source) => ({ id: `source.${source.definition.manifest.role}` }))] : [{ id: roles.includes("strategy") ? "strategy" : `strategy.${typeId ?? hash(entry.options.name).slice(0, 12)}` }];
8915
- return defineMemoryPlugin({
8916
- packageName: entry.options.name,
8917
- label,
8918
- description,
8919
- roles,
8920
- provides
8921
- });
8922
- }
8923
- function prepared(item, config) {
8924
- if (item.editor === void 0) throw new Error(`Plugin does not expose a pure configuration factory: ${item.entry.id}`);
8925
- const contribution = item.editor.create(config);
8926
- if ([...contribution.strategies ?? [], ...contribution.strategyExtensions ?? []][0].manifest.packageName !== item.entry.options.name) throw new Error("Plugin factory does not match its declared Entry");
8927
- if (contribution.plugin !== void 0 && hash(contribution.plugin) !== hash(item.descriptor)) throw new Error("Plugin factory descriptor does not match its module export");
8928
- return prepareMemoryContributions({
8929
- ...contribution,
8930
- plugin: contribution.plugin ?? item.descriptor
8931
- }, { instanceId: item.entry.id });
8932
- }
8933
- function installedPlugin(descriptor, entryId) {
8934
- return {
8935
- kind: "plugin",
8936
- instanceKey: `plugin:${entryId}`,
8937
- provenance: {
8938
- packageName: descriptor.packageName,
8939
- entryId
8940
- },
8941
- descriptor
8942
- };
8943
- }
8944
- function orderPlugins(values) {
8945
- const byId = new Map(values.map((value) => [value.entryId, value]));
8946
- const edges = new Map(values.map((value) => [value.entryId, /* @__PURE__ */ new Set()]));
8947
- const indegree = new Map(values.map((value) => [value.entryId, 0]));
8948
- for (const dependent of values) for (const requirement of dependent.requires) for (const provider of values) {
8949
- if (provider.entryId === dependent.entryId || !provider.provides.some((capability) => capability.id === requirement)) continue;
8950
- if (edges.get(provider.entryId).has(dependent.entryId)) continue;
8951
- edges.get(provider.entryId).add(dependent.entryId);
8952
- indegree.set(dependent.entryId, indegree.get(dependent.entryId) + 1);
8953
- }
8954
- const stable = (left, right) => byId.get(left).packageName.localeCompare(byId.get(right).packageName) || left.localeCompare(right);
8955
- const ready = [...indegree].filter(([, count]) => count === 0).map(([entryId]) => entryId).sort(stable);
8956
- const ordered = [];
8957
- while (ready.length > 0) {
8958
- const entryId = ready.shift();
8959
- ordered.push(byId.get(entryId));
8960
- for (const dependent of [...edges.get(entryId)].sort(stable)) {
8961
- const next = indegree.get(dependent) - 1;
8962
- indegree.set(dependent, next);
8963
- if (next === 0) {
8964
- ready.push(dependent);
8965
- ready.sort(stable);
8966
- }
8967
- }
8968
- }
8969
- if (ordered.length < values.length) ordered.push(...values.filter((value) => !ordered.includes(value)).sort((left, right) => stable(left.entryId, right.entryId)));
8970
- return ordered;
8971
- }
8972
- /**
8973
- * Profile-local plugin graph overlay. Every dsh-mnemon Source or Strategy Entry
8974
- * is one peer node; activation, configuration, dependency checks and rollback
8975
- * use one transaction. Contribution roles do not create separate managers.
8976
- */
8977
- var MemoryPluginManagement = class {
8978
- ctx;
8979
- engine;
8980
- settingsService;
8981
- settingsNamespace;
8982
- settings;
8983
- legacySettings;
8984
- queue = Promise.resolve();
8985
- closed = false;
8986
- timer;
8987
- restoreError;
8988
- discoveryWarnings = [];
8989
- changingEntries = false;
8990
- restorePending = false;
8991
- constructor(ctx, engine, settingsService = ctx.settings) {
8992
- this.ctx = ctx;
8993
- this.engine = engine;
8994
- this.settingsService = settingsService;
8995
- const loader = this.loader();
8996
- const anchor = loader?.config?.baseUrl ?? loader?.ctx?.baseUrl ?? loader?.context?.baseUrl;
8997
- const suffix = anchor ? `-${hash(anchor).slice(0, 16)}` : "";
8998
- this.settingsNamespace = `${MNEMON_VIEW_SETTINGS_NAMESPACE}${suffix}`;
8999
- this.settings = settingsService.register(this.settingsNamespace, schema, {
9000
- base: { entries: {} },
9001
- applies: "live",
9002
- validate: (value) => {
9003
- preferences(value);
9004
- }
9005
- });
9006
- this.legacySettings = settingsService.register(`mnemon-plugins${suffix}`, legacySchema, {
9007
- base: { sources: {} },
9008
- applies: "live",
9009
- validate: (value) => {
9010
- legacyPreferences(value);
9011
- }
9012
- });
9013
- }
9014
- resolveConfig(config, view = this.settings.get()) {
9015
- const selected = view.strategyTypeId;
9016
- return selected === void 0 ? config : {
9017
- ...config,
9018
- memoryTopology: {
9019
- ...config.memoryTopology,
9020
- strategyId: selected
9021
- }
9022
- };
9023
- }
9024
- start() {
9025
- const schedule = () => this.scheduleRestore();
9026
- const stops = [
9027
- this.ctx.on("loader/entry-init", schedule),
9028
- this.ctx.on("loader/partial-dispose", schedule),
9029
- this.ctx.on("app-boot/config-reload", () => this.scheduleRestore(true)),
9030
- subscribeSettings(this.ctx, this.settingsService, (namespace) => {
9031
- if (namespace === this.settingsNamespace || namespace === `mnemon-plugins${this.settingsNamespace.slice(11)}`) schedule();
9032
- })
9033
- ];
9034
- schedule();
9035
- return () => {
9036
- this.closed = true;
9037
- this.restorePending = false;
9038
- clearTimeout(this.timer);
9039
- for (const stop of stops.reverse()) stop();
9040
- };
9041
- }
9042
- scheduleRestore(profileReload = false) {
9043
- const hasSavedState = Object.keys(this.settings.get().entries ?? {}).length > 0 || Object.keys(this.legacySettings.get().sources ?? {}).length > 0;
9044
- if (this.closed || !profileReload && this.changingEntries || !hasSavedState) return;
9045
- this.restorePending = true;
9046
- if (this.changingEntries || this.timer) return;
9047
- this.timer = setTimeout(() => {
9048
- this.timer = void 0;
9049
- this.enqueueRestore();
9050
- }, 0);
9051
- }
9052
- enqueueRestore() {
9053
- if (this.closed || this.changingEntries || !this.restorePending) return;
9054
- clearTimeout(this.timer);
9055
- this.timer = void 0;
9056
- this.restorePending = false;
9057
- this.exclusive(async () => {
9058
- try {
9059
- await this.restore();
9060
- } catch (error) {
9061
- this.restoreError = error instanceof Error ? error.message : String(error);
9062
- }
9063
- }).catch(() => {});
9064
- }
9065
- /** A response must not race the deferred restore after profile reconciliation. */
9066
- async settled() {
9067
- do {
9068
- this.enqueueRestore();
9069
- const pending = this.queue;
9070
- await pending;
9071
- if (pending === this.queue && !this.restorePending) return;
9072
- } while (!this.closed);
9073
- }
9074
- loader() {
9075
- const candidate = this.ctx.get("loader");
9076
- return candidate && typeof candidate.entries === "function" ? candidate : void 0;
9077
- }
9078
- settingsRevision() {
9079
- return this.settingsService.describe({ redactSecrets: true }).find((value) => value.ns === this.settingsNamespace)?.revision ?? 0;
8581
+ /** The plugin manager owns enablement unless moving the saved choices failed. */
8582
+ enablementOwner() {
8583
+ return this.enablementMoveError === void 0 ? this.pluginManager() : void 0;
9080
8584
  }
9081
8585
  async managed() {
9082
8586
  const loader = this.loader();
@@ -9168,7 +8672,7 @@ var MemoryPluginManagement = class {
9168
8672
  const pending = this.queue;
9169
8673
  const items = await this.managed();
9170
8674
  if (pending !== this.queue || this.restorePending) return this.catalog();
9171
- const values = items.map((item) => clone(item.value));
8675
+ const values = items.map((item) => clone$1(item.value));
9172
8676
  const snapshot = this.engine.contributionSnapshot();
9173
8677
  const managedIds = new Set(items.map((item) => item.entry.id));
9174
8678
  const entryIds = new Set([
@@ -9228,7 +8732,7 @@ var MemoryPluginManagement = class {
9228
8732
  revision: this.revision(items),
9229
8733
  writable: this.settingsService.writable && this.loader() !== void 0,
9230
8734
  entries: orderPlugins(values),
9231
- diagnostics: [...this.discoveryWarnings, ...this.restoreError === void 0 ? [] : [this.restoreError]]
8735
+ diagnostics: [...this.discoveryWarnings, ...[this.restoreError, this.enablementMoveError].filter((value) => value !== void 0)]
9232
8736
  };
9233
8737
  }
9234
8738
  choices(items, entries) {
@@ -9259,7 +8763,10 @@ var MemoryPluginManagement = class {
9259
8763
  entries: request.entries
9260
8764
  });
9261
8765
  const managedIds = new Set(items.map((item) => item.entry.id));
9262
- for (const entryId of Object.keys(incoming.entries)) if (!managedIds.has(entryId)) throw new Error("Memory plugin Entry is not managed by this Host: " + entryId);
8766
+ for (const [entryId, value] of Object.entries(incoming.entries)) {
8767
+ if (!managedIds.has(entryId)) throw new Error("Memory plugin Entry is not managed by this Host: " + entryId);
8768
+ if (typeof value.enabled !== "boolean") throw new Error("Plugin enabled must be boolean");
8769
+ }
9263
8770
  const choices = this.choices(items, incoming.entries);
9264
8771
  for (const item of items) {
9265
8772
  const chosen = choices.get(item.entry.id);
@@ -9316,8 +8823,9 @@ var MemoryPluginManagement = class {
9316
8823
  }
9317
8824
  async evaluateSnapshot(snapshot, config, scope, strategyTypeId, signal) {
9318
8825
  signal?.throwIfAborted();
8826
+ const { strategyFallback: _fallback, ...options } = memoryGenerationOptions(config, scope.workspaceId);
9319
8827
  const generation = new MemoryCompositionGeneration(snapshot, {
9320
- ...memoryGenerationOptions(config, scope.workspaceId),
8828
+ ...options,
9321
8829
  strategyTypeId
9322
8830
  });
9323
8831
  try {
@@ -9339,14 +8847,24 @@ var MemoryPluginManagement = class {
9339
8847
  const items = await this.managed();
9340
8848
  const choices = this.validateRequest(items, request);
9341
8849
  const expectedSettingsRevision = this.settingsRevision();
8850
+ const manager = this.enablementOwner();
9342
8851
  const previous = preferences(this.settings.get());
8852
+ const merged = {
8853
+ ...previous.entries,
8854
+ ...request.entries
8855
+ };
9343
8856
  const next = preferences({
9344
8857
  strategyTypeId: request.strategyTypeId,
9345
- entries: {
9346
- ...previous.entries,
9347
- ...request.entries
9348
- }
8858
+ entries: manager === void 0 ? merged : configurationOnly(merged)
9349
8859
  });
8860
+ const toggles = manager === void 0 ? [] : items.flatMap((item) => {
8861
+ const enabled = choices.get(item.entry.id).enabled;
8862
+ return enabled === !item.entry.disabled ? [] : [{
8863
+ entryId: item.entry.id,
8864
+ enabled
8865
+ }];
8866
+ }).sort((left, right) => Number(right.enabled) - Number(left.enabled));
8867
+ const written = [];
9350
8868
  let committedRevision;
9351
8869
  await this.updateEntries(items, choices, {
9352
8870
  validate: () => this.evaluateSnapshot(this.engine.contributionSnapshot(), config, scope, request.strategyTypeId, signal),
@@ -9361,8 +8879,13 @@ var MemoryPluginManagement = class {
9361
8879
  value: next.entries
9362
8880
  }], expectedSettingsRevision);
9363
8881
  committedRevision = this.settingsRevision();
8882
+ for (const toggle of toggles) {
8883
+ await this.setEnabled(manager, toggle.entryId, toggle.enabled);
8884
+ written.push(toggle);
8885
+ }
9364
8886
  },
9365
8887
  compensate: async () => {
8888
+ for (const toggle of [...written].reverse()) await this.setEnabled(manager, toggle.entryId, !toggle.enabled);
9366
8889
  if (hash(preferences(this.settings.get())) !== hash(next)) throw new Error("Memory plugin settings changed before rollback; refresh and inspect the saved configuration.");
9367
8890
  await this.settingsService.mutate(this.settingsNamespace, [previous.strategyTypeId === void 0 ? {
9368
8891
  op: "unset",
@@ -9381,15 +8904,56 @@ var MemoryPluginManagement = class {
9381
8904
  this.restoreError = void 0;
9382
8905
  });
9383
8906
  }
8907
+ /** Move saved enablement into the profile patch once, keeping only configuration overlays. */
8908
+ async moveEnablement(manager, items) {
8909
+ const saved = this.settings.get().entries ?? {};
8910
+ const legacy = this.legacySettings.get().sources ?? {};
8911
+ const moves = items.flatMap((item) => {
8912
+ const enabled = saved[item.entry.id] !== void 0 ? saved[item.entry.id].enabled : item.descriptor.roles.includes("source") ? legacy[item.entry.id]?.enabled : void 0;
8913
+ return enabled === void 0 ? [] : [{
8914
+ entryId: item.entry.id,
8915
+ enabled
8916
+ }];
8917
+ }).sort((left, right) => Number(right.enabled) - Number(left.enabled));
8918
+ if (moves.length === 0 && Object.keys(legacy).length === 0) return false;
8919
+ const legacyNamespace = `mnemon-plugins${this.settingsNamespace.slice(11)}`;
8920
+ const revisions = {
8921
+ view: this.settingsRevision(),
8922
+ legacy: this.settingsRevision(legacyNamespace)
8923
+ };
8924
+ for (const move of moves) await this.setEnabled(manager, move.entryId, move.enabled);
8925
+ await this.settingsService.mutate(this.settingsNamespace, [{
8926
+ op: "set",
8927
+ path: ["entries"],
8928
+ value: configurationOnly(saved)
8929
+ }], revisions.view);
8930
+ if (Object.keys(legacy).length > 0) await this.settingsService.mutate(legacyNamespace, [{
8931
+ op: "set",
8932
+ path: ["sources"],
8933
+ value: {}
8934
+ }], revisions.legacy);
8935
+ return true;
8936
+ }
9384
8937
  async restore() {
9385
8938
  if (this.closed) return;
9386
- const items = await this.managed();
8939
+ let items = await this.managed();
8940
+ const available = this.enablementOwner();
8941
+ if (available !== void 0 && this.settingsService.writable) try {
8942
+ if (await this.moveEnablement(available, items)) items = await this.managed();
8943
+ } catch (error) {
8944
+ this.enablementMoveError = `Saved plugin choices could not move into the DSH profile and stay managed by Mnemon: ${error instanceof Error ? error.message : String(error)}`;
8945
+ }
8946
+ const manager = this.enablementOwner();
9387
8947
  const saved = this.settings.get().entries ?? {};
9388
8948
  const legacy = this.legacySettings.get().sources ?? {};
9389
8949
  const desired = Object.fromEntries(items.flatMap((item) => {
9390
8950
  const unified = saved[item.entry.id];
9391
- if (unified !== void 0) return [[item.entry.id, unified]];
9392
- const oldSource = item.descriptor.roles.includes("source") ? legacy[item.entry.id] : void 0;
8951
+ const current = !item.entry.disabled;
8952
+ if (unified !== void 0) return [[item.entry.id, {
8953
+ enabled: manager === void 0 ? unified.enabled ?? current : current,
8954
+ config: unified.config
8955
+ }]];
8956
+ const oldSource = manager === void 0 && item.descriptor.roles.includes("source") ? legacy[item.entry.id] : void 0;
9393
8957
  return oldSource === void 0 ? [] : [[item.entry.id, {
9394
8958
  enabled: oldSource.enabled,
9395
8959
  config: item.value.config
@@ -9408,7 +8972,7 @@ var MemoryPluginManagement = class {
9408
8972
  const previous = items.map((item) => ({
9409
8973
  item,
9410
8974
  disabled: item.entry.options.disabled ?? null,
9411
- config: clone(item.entry.options.config ?? {}),
8975
+ config: clone$1(item.entry.options.config ?? {}),
9412
8976
  enabled: item.value.enabled,
9413
8977
  active: item.value.active
9414
8978
  }));
@@ -9429,7 +8993,7 @@ var MemoryPluginManagement = class {
9429
8993
  touched.add(item.entry.id);
9430
8994
  await item.entry.update(item.editor === void 0 ? { disabled: !desired.enabled } : {
9431
8995
  disabled: !desired.enabled,
9432
- config: clone(desired.config)
8996
+ config: clone$1(desired.config)
9433
8997
  });
9434
8998
  await item.entry.fiber?.await?.();
9435
8999
  const registered = contributionRoles(this.engine.contributionSnapshot(), item.entry.id).length > 0;
@@ -9511,7 +9075,7 @@ var MemoryPluginManagement = class {
9511
9075
  };
9512
9076
  //#endregion
9513
9077
  //#region src/host/view-rpc.ts
9514
- function object(value) {
9078
+ function object$2(value) {
9515
9079
  if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error("View request must be an object");
9516
9080
  return value;
9517
9081
  }
@@ -9528,7 +9092,7 @@ function createViewHandler(runtime, engine, management, access, lifecycle, insta
9528
9092
  "preview",
9529
9093
  "inspect-plugin"
9530
9094
  ].includes(endpoint) : !["apply", "install-plugin"].includes(endpoint)) throw new Error("View operation is not available on this channel");
9531
- const payload = object(input);
9095
+ const payload = object$2(input);
9532
9096
  if (endpoint === "inspect-plugin") {
9533
9097
  if (installation === void 0 || typeof payload.packageName !== "string") throw new Error("Plugin discovery is unavailable");
9534
9098
  return {
@@ -9590,9 +9154,9 @@ function createViewHandler(runtime, engine, management, access, lifecycle, insta
9590
9154
  };
9591
9155
  }
9592
9156
  if (endpoint !== "preview" && endpoint !== "apply") throw new Error("Unknown View operation");
9593
- const raw = object(payload.configuration);
9157
+ const raw = object$2(payload.configuration);
9594
9158
  if (typeof raw.expectedRevision !== "string" || typeof raw.strategyTypeId !== "string") throw new Error("View configuration requires a revision and Strategy id");
9595
- object(raw.entries);
9159
+ object$2(raw.entries);
9596
9160
  const request = raw;
9597
9161
  if (endpoint === "preview") return {
9598
9162
  ok: true,
@@ -9616,11 +9180,11 @@ function createViewHandler(runtime, engine, management, access, lifecycle, insta
9616
9180
  }
9617
9181
  };
9618
9182
  }
9619
- function registerViewRpc(connection, runtime, engine, management, lifecycle, authority, installation) {
9183
+ function registerViewRpc(connection, runtime, engine, management, lifecycle, installation) {
9620
9184
  const readHandler = createViewHandler(runtime, engine, management, "read", lifecycle, installation);
9621
9185
  const writeHandler = createViewHandler(runtime, engine, management, "write", lifecycle, installation);
9622
- connection.rpc.handle(MNEMON_VIEW_CHANNEL, readHandler, { authority: "trusted-host" });
9623
- connection.rpc.handle(MNEMON_VIEW_WRITE_CHANNEL, writeHandler, { authority });
9186
+ connection.rpc.handle(MNEMON_VIEW_CHANNEL, readHandler);
9187
+ connection.rpc.handle(MNEMON_VIEW_WRITE_CHANNEL, writeHandler);
9624
9188
  return {
9625
9189
  read: readHandler,
9626
9190
  write: writeHandler
@@ -9879,179 +9443,690 @@ var __esDecorate = function(ctor, descriptorIn, decorators, contextIn, initializ
9879
9443
  else descriptor[key] = _;
9880
9444
  }
9881
9445
  }
9882
- if (target) Object.defineProperty(target, contextIn.name, descriptor);
9883
- done = true;
9884
- };
9885
- function denied() {
9886
- return {
9887
- ok: false,
9888
- error: {
9889
- code: "bad-request",
9890
- message: "remote Mnemon management requires remoteAccess: trusted-host",
9891
- details: { issues: [] }
9446
+ if (target) Object.defineProperty(target, contextIn.name, descriptor);
9447
+ done = true;
9448
+ };
9449
+ function denied() {
9450
+ return {
9451
+ ok: false,
9452
+ error: {
9453
+ code: "bad-request",
9454
+ message: "remote Mnemon management requires remoteAccess: trusted-host",
9455
+ details: { issues: [] }
9456
+ }
9457
+ };
9458
+ }
9459
+ function unknownSettingsEndpoint(endpoint) {
9460
+ return {
9461
+ ok: false,
9462
+ error: {
9463
+ code: "bad-request",
9464
+ message: `unknown remote Mnemon settings endpoint: ${endpoint}`,
9465
+ details: { issues: [] }
9466
+ }
9467
+ };
9468
+ }
9469
+ /**
9470
+ * Project Mnemon's existing handlers through DSH API Gateway. The Gateway owns
9471
+ * `/api`, Host/Origin validation, browser pairing, and response envelopes;
9472
+ * this Service retains Mnemon's narrower management grant.
9473
+ */
9474
+ let MnemonRemoteService = (() => {
9475
+ let _classSuper = TypertRemoteService;
9476
+ let _instanceExtraInitializers = [];
9477
+ let _read_decorators;
9478
+ let _activation_decorators;
9479
+ let _write_decorators;
9480
+ let _pack_decorators;
9481
+ let _settings_decorators;
9482
+ let _view_decorators;
9483
+ let _viewWrite_decorators;
9484
+ return class MnemonRemoteService extends _classSuper {
9485
+ static {
9486
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
9487
+ _read_decorators = [Remote("read")];
9488
+ _activation_decorators = [Remote("activation")];
9489
+ _write_decorators = [Remote("write")];
9490
+ _pack_decorators = [Remote("pack")];
9491
+ _settings_decorators = [Remote("settings")];
9492
+ _view_decorators = [Remote("view")];
9493
+ _viewWrite_decorators = [Remote("viewWrite")];
9494
+ __esDecorate(this, null, _read_decorators, {
9495
+ kind: "method",
9496
+ name: "read",
9497
+ static: false,
9498
+ private: false,
9499
+ access: {
9500
+ has: (obj) => "read" in obj,
9501
+ get: (obj) => obj.read
9502
+ },
9503
+ metadata: _metadata
9504
+ }, null, _instanceExtraInitializers);
9505
+ __esDecorate(this, null, _activation_decorators, {
9506
+ kind: "method",
9507
+ name: "activation",
9508
+ static: false,
9509
+ private: false,
9510
+ access: {
9511
+ has: (obj) => "activation" in obj,
9512
+ get: (obj) => obj.activation
9513
+ },
9514
+ metadata: _metadata
9515
+ }, null, _instanceExtraInitializers);
9516
+ __esDecorate(this, null, _write_decorators, {
9517
+ kind: "method",
9518
+ name: "write",
9519
+ static: false,
9520
+ private: false,
9521
+ access: {
9522
+ has: (obj) => "write" in obj,
9523
+ get: (obj) => obj.write
9524
+ },
9525
+ metadata: _metadata
9526
+ }, null, _instanceExtraInitializers);
9527
+ __esDecorate(this, null, _pack_decorators, {
9528
+ kind: "method",
9529
+ name: "pack",
9530
+ static: false,
9531
+ private: false,
9532
+ access: {
9533
+ has: (obj) => "pack" in obj,
9534
+ get: (obj) => obj.pack
9535
+ },
9536
+ metadata: _metadata
9537
+ }, null, _instanceExtraInitializers);
9538
+ __esDecorate(this, null, _settings_decorators, {
9539
+ kind: "method",
9540
+ name: "settings",
9541
+ static: false,
9542
+ private: false,
9543
+ access: {
9544
+ has: (obj) => "settings" in obj,
9545
+ get: (obj) => obj.settings
9546
+ },
9547
+ metadata: _metadata
9548
+ }, null, _instanceExtraInitializers);
9549
+ __esDecorate(this, null, _view_decorators, {
9550
+ kind: "method",
9551
+ name: "view",
9552
+ static: false,
9553
+ private: false,
9554
+ access: {
9555
+ has: (obj) => "view" in obj,
9556
+ get: (obj) => obj.view
9557
+ },
9558
+ metadata: _metadata
9559
+ }, null, _instanceExtraInitializers);
9560
+ __esDecorate(this, null, _viewWrite_decorators, {
9561
+ kind: "method",
9562
+ name: "viewWrite",
9563
+ static: false,
9564
+ private: false,
9565
+ access: {
9566
+ has: (obj) => "viewWrite" in obj,
9567
+ get: (obj) => obj.viewWrite
9568
+ },
9569
+ metadata: _metadata
9570
+ }, null, _instanceExtraInitializers);
9571
+ if (_metadata) Object.defineProperty(this, Symbol.metadata, {
9572
+ enumerable: true,
9573
+ configurable: true,
9574
+ writable: true,
9575
+ value: _metadata
9576
+ });
9577
+ }
9578
+ handlers = __runInitializers(this, _instanceExtraInitializers);
9579
+ constructor(ctx, handlers) {
9580
+ super(ctx, "mnemonRemote", { namespace: MNEMON_REMOTE_NAMESPACE });
9581
+ this.handlers = handlers;
9582
+ }
9583
+ read(endpoint, payload, signal) {
9584
+ return this.handlers.read(endpoint, payload, signal);
9585
+ }
9586
+ activation(endpoint, payload, signal) {
9587
+ return this.handlers.activation(endpoint, payload, signal);
9588
+ }
9589
+ write(endpoint, payload, signal) {
9590
+ if (!this.handlers.management) return Promise.resolve(denied());
9591
+ return this.handlers.write(endpoint, payload, signal);
9592
+ }
9593
+ pack(endpoint, payload, signal) {
9594
+ if (!this.handlers.management) return Promise.resolve(denied());
9595
+ return this.handlers.pack(endpoint, payload, signal);
9596
+ }
9597
+ async settings(endpoint, payload, signal) {
9598
+ if (endpoint === "mutate" && !this.handlers.management) return denied();
9599
+ if (endpoint !== "get" && endpoint !== "mutate") return unknownSettingsEndpoint(endpoint);
9600
+ const response = await this.handlers.settings(endpoint, payload, signal);
9601
+ if (endpoint !== "get" || this.handlers.management || !response.ok || typeof response.value !== "object" || response.value === null || Array.isArray(response.value)) return response;
9602
+ return {
9603
+ ...response,
9604
+ value: {
9605
+ ...response.value,
9606
+ writable: false
9607
+ }
9608
+ };
9609
+ }
9610
+ view(endpoint, payload, signal) {
9611
+ return this.handlers.view(endpoint, payload, signal);
9612
+ }
9613
+ viewWrite(endpoint, payload, signal) {
9614
+ if (!this.handlers.management) return Promise.resolve(denied());
9615
+ return this.handlers.viewWrite(endpoint, payload, signal);
9616
+ }
9617
+ };
9618
+ })();
9619
+ //#endregion
9620
+ //#region src/host/live-config.ts
9621
+ function plain(value) {
9622
+ if (isVolatile(value)) return plain(value.get());
9623
+ if (Array.isArray(value)) return value.map(plain);
9624
+ if (value === null || typeof value !== "object") return value;
9625
+ const prototype = Object.getPrototypeOf(value);
9626
+ if (prototype !== Object.prototype && prototype !== null) return value;
9627
+ return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, plain(child)]));
9628
+ }
9629
+ /** Read a detached snapshot of the live profile configuration. */
9630
+ function plainHostConfig(value = {}) {
9631
+ return plain(value);
9632
+ }
9633
+ function liveConfig() {
9634
+ const schema = new z(Config.toJSON());
9635
+ schema.dict = Object.fromEntries(Object.entries(schema.dict ?? {}).map(([key, field]) => [key, key === "remoteAccess" ? field : field.volatile()]));
9636
+ const standard = schema["~standard"];
9637
+ Object.defineProperty(schema, "~standard", { value: {
9638
+ ...standard,
9639
+ validate(value) {
9640
+ const parsed = standard.validate(value);
9641
+ if ("then" in parsed) throw new TypeError("Mnemon configuration validation must be synchronous");
9642
+ if (parsed.issues) return parsed;
9643
+ try {
9644
+ resolveConfig(plainHostConfig(parsed.value));
9645
+ return parsed;
9646
+ } catch (error) {
9647
+ return { issues: [{ message: error instanceof Error ? error.message : String(error) }] };
9648
+ }
9649
+ }
9650
+ } });
9651
+ return schema;
9652
+ }
9653
+ /** Profile Config for live forms; transport authority retains normal remounts. */
9654
+ const LiveConfig = liveConfig();
9655
+ //#endregion
9656
+ //#region src/host/legacy-settings-import.ts
9657
+ const UNSAFE_KEYS = /* @__PURE__ */ new Set([
9658
+ "__proto__",
9659
+ "prototype",
9660
+ "constructor"
9661
+ ]);
9662
+ const ENTRY_ID = /^[a-zA-Z0-9][a-zA-Z0-9._:/-]{0,299}$/u;
9663
+ const STRATEGY_ID = /^[a-z][a-z0-9-]{0,127}$/u;
9664
+ const VIEW_NAMESPACE = /^mnemon-view(?:-[a-f0-9]{16})?$/u;
9665
+ const LEGACY_NAMESPACE = /^mnemon-plugins(?:-[a-f0-9]{16})?$/u;
9666
+ function object$1(value, label) {
9667
+ if (value === null || typeof value !== "object" || Array.isArray(value) || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) throw new Error(`${label} must be a plain object`);
9668
+ for (const key of Reflect.ownKeys(value)) {
9669
+ if (typeof key !== "string" || UNSAFE_KEYS.has(key)) throw new Error(`${label} contains an unsafe key`);
9670
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
9671
+ if (!descriptor.enumerable || !Object.hasOwn(descriptor, "value")) throw new Error(`${label} must contain only data properties`);
9672
+ }
9673
+ return value;
9674
+ }
9675
+ /** Copy JSON data without invoking getters, serializers or prototype methods. */
9676
+ function copy(value, label, ancestors = /* @__PURE__ */ new Set(), depth = 0) {
9677
+ if (value === null || typeof value === "string" || typeof value === "boolean") return value;
9678
+ if (typeof value === "number" && Number.isFinite(value)) return value;
9679
+ if (typeof value !== "object" || value === null) throw new Error(`${label} must contain only JSON data`);
9680
+ if (depth > 64 || ancestors.has(value)) throw new Error(`${label} contains cyclic or excessively nested data`);
9681
+ ancestors.add(value);
9682
+ try {
9683
+ if (Array.isArray(value)) {
9684
+ if (Object.getPrototypeOf(value) !== Array.prototype) throw new Error(`${label} must contain plain arrays`);
9685
+ if (Reflect.ownKeys(value).length !== value.length + 1) throw new Error(`${label} contains a sparse or extended array`);
9686
+ return Array.from({ length: value.length }, (_, index) => {
9687
+ const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
9688
+ if (!descriptor || !descriptor.enumerable || !Object.hasOwn(descriptor, "value")) throw new Error(`${label} must contain only data properties`);
9689
+ return copy(descriptor.value, label, ancestors, depth + 1);
9690
+ });
9691
+ }
9692
+ return Object.fromEntries(Object.entries(object$1(value, label)).map(([key, item]) => [key, copy(item, label, ancestors, depth + 1)]));
9693
+ } finally {
9694
+ ancestors.delete(value);
9695
+ }
9696
+ }
9697
+ function copiedObject(value, label) {
9698
+ object$1(value, label);
9699
+ return copy(value, label);
9700
+ }
9701
+ /** Inspect validated copies only; never evaluate expressions or invoke getters. */
9702
+ function containsExpression(value) {
9703
+ return value !== null && typeof value === "object" && (Object.hasOwn(value, "__jsExpr") || Object.values(value).some(containsExpression));
9704
+ }
9705
+ function fields(value, allowed, label) {
9706
+ if (Object.keys(value).some((key) => !allowed.includes(key))) throw new Error(`${label} contains unsupported fields`);
9707
+ }
9708
+ function interaction(value, label, allowed) {
9709
+ const result = copiedObject(value, label);
9710
+ fields(result, allowed, label);
9711
+ if (Object.values(result).some((item) => typeof item !== "boolean")) throw new Error(`${label} preferences must be boolean`);
9712
+ return result;
9713
+ }
9714
+ function entryId(value, label) {
9715
+ if (typeof value !== "string" || !ENTRY_ID.test(value) || UNSAFE_KEYS.has(value)) throw new Error(`${label} contains an invalid Entry id`);
9716
+ }
9717
+ function view(value, label) {
9718
+ const result = copiedObject(value, label);
9719
+ fields(result, ["strategyTypeId", "entries"], label);
9720
+ if (Object.hasOwn(result, "strategyTypeId") && (typeof result.strategyTypeId !== "string" || !STRATEGY_ID.test(result.strategyTypeId))) throw new Error(`${label} contains an invalid Strategy type id`);
9721
+ const entries = {};
9722
+ if (Object.hasOwn(result, "entries")) {
9723
+ const values = object$1(result.entries, `${label}.entries`);
9724
+ if (Object.keys(values).length > 64) throw new Error(`${label} contains too many Entries`);
9725
+ for (const [id, raw] of Object.entries(values)) {
9726
+ entryId(id, label);
9727
+ const item = copiedObject(raw, `${label}.entries`);
9728
+ fields(item, ["enabled", "config"], `${label}.entries`);
9729
+ if (Object.hasOwn(item, "enabled") && typeof item.enabled !== "boolean") throw new Error(`${label} Entry enabled must be boolean`);
9730
+ const config = Object.hasOwn(item, "config") ? copiedObject(item.config, `${label} Entry config`) : {};
9731
+ if (JSON.stringify(config).length > 65536) throw new Error(`${label} Entry config exceeds 64 KiB`);
9732
+ entries[id] = {
9733
+ ...typeof item.enabled === "boolean" ? { enabled: item.enabled } : {},
9734
+ config
9735
+ };
9736
+ }
9737
+ }
9738
+ return {
9739
+ ...typeof result.strategyTypeId === "string" ? { strategyTypeId: result.strategyTypeId } : {},
9740
+ ...Object.hasOwn(result, "entries") ? { entries } : {}
9741
+ };
9742
+ }
9743
+ function sources(value, label, sourceEntryIds, diagnostics) {
9744
+ const result = copiedObject(value, label);
9745
+ fields(result, ["sources"], label);
9746
+ const values = Object.hasOwn(result, "sources") ? object$1(result.sources, `${label}.sources`) : {};
9747
+ if (Object.keys(values).length > 64) throw new Error(`${label} contains too many Source Entries`);
9748
+ const entries = {};
9749
+ for (const [id, raw] of Object.entries(values)) {
9750
+ entryId(id, label);
9751
+ const item = copiedObject(raw, `${label}.sources`);
9752
+ fields(item, ["enabled"], `${label}.sources`);
9753
+ if (typeof item.enabled !== "boolean") throw new Error(`${label} Source enabled must be boolean`);
9754
+ if (!sourceEntryIds.has(id)) {
9755
+ diagnostics.push(`Skipped legacy Source preference for ${id}: the current profile does not declare it as a Source.`);
9756
+ continue;
9757
+ }
9758
+ entries[id] = {
9759
+ enabled: item.enabled,
9760
+ config: {}
9761
+ };
9762
+ }
9763
+ return entries;
9764
+ }
9765
+ function applyEntryOverrides(entries, overrides, sourceEntryIds, diagnostics) {
9766
+ if (!Array.isArray(overrides)) throw new Error("Explicit Entry overrides must be an array");
9767
+ const matching = /* @__PURE__ */ new Map();
9768
+ for (const raw of overrides) {
9769
+ const row = object$1(raw, "Explicit Entry override");
9770
+ entryId(row.id, "Explicit Entry override");
9771
+ if (!Object.hasOwn(entries, row.id)) continue;
9772
+ const previous = matching.get(row.id) ?? {};
9773
+ matching.set(row.id, {
9774
+ ...previous,
9775
+ ...row.disabled === void 0 ? {} : { disabled: row.disabled },
9776
+ ...row.config === void 0 ? {} : { config: row.config }
9777
+ });
9778
+ }
9779
+ for (const [id, row] of matching) {
9780
+ if (row.disabled !== void 0 && typeof row.disabled !== "boolean") {
9781
+ delete entries[id];
9782
+ diagnostics.push(`Skipped legacy View preference for ${id}: its explicit disabled value is not boolean.`);
9783
+ continue;
9784
+ }
9785
+ if (row.config !== void 0 && !sourceEntryIds.has(id)) {
9786
+ const config = copiedObject(row.config, "Explicit Entry config");
9787
+ if (containsExpression(config)) {
9788
+ delete entries[id];
9789
+ diagnostics.push(`Skipped legacy View preference for ${id}: its explicit config contains an expression.`);
9790
+ continue;
9791
+ }
9792
+ entries[id].config = config;
9793
+ }
9794
+ if (typeof row.disabled === "boolean") entries[id].enabled = !row.disabled;
9795
+ }
9796
+ }
9797
+ /**
9798
+ * Recover only the canonical root and the exact owning profile's old namespaces.
9799
+ * This does not read files, edit profile rows, validate plugin domains or commit.
9800
+ * Malformed relevant input rejects the complete plan without an import marker.
9801
+ */
9802
+ function planLegacySettingsImport(options) {
9803
+ const diagnostics = [];
9804
+ try {
9805
+ const rawCurrent = object$1(options.currentOverride, "Current config override");
9806
+ if (rawCurrent.legacySettingsImported === true) return {
9807
+ patch: {},
9808
+ diagnostics
9809
+ };
9810
+ if (options.entryId !== "mnemon") throw new Error("Legacy settings belong to the canonical mnemon Entry; another root Entry is ambiguous.");
9811
+ if (!VIEW_NAMESPACE.test(options.viewNamespace) || !LEGACY_NAMESPACE.test(options.legacyNamespace) || options.legacyNamespace !== options.viewNamespace.replace(/^mnemon-view/u, "mnemon-plugins")) throw new Error("Legacy settings namespaces must identify the same exact profile.");
9812
+ const sections = object$1(options.sections, "Legacy settings document");
9813
+ for (const key of Object.keys(sections)) if (key.startsWith("mnemon-view-") && !VIEW_NAMESPACE.test(key) || key.startsWith("mnemon-plugins-") && !LEGACY_NAMESPACE.test(key)) throw new Error("Legacy settings document contains an invalid Mnemon namespace.");
9814
+ const relevant = [
9815
+ "mnemon",
9816
+ "mnemon-ui",
9817
+ options.viewNamespace,
9818
+ options.legacyNamespace
9819
+ ];
9820
+ if (!relevant.some((key) => Object.hasOwn(sections, key))) return {
9821
+ patch: {},
9822
+ diagnostics
9823
+ };
9824
+ for (const key of relevant) if (Object.hasOwn(sections, key) && containsExpression(copiedObject(sections[key], key))) throw new Error(`${key} contains an expression; legacy settings must contain only literal data.`);
9825
+ const current = copiedObject(rawCurrent, "Current config override");
9826
+ if (Object.hasOwn(current, "legacySettingsImported") && typeof current.legacySettingsImported !== "boolean") throw new Error("Current import marker must be boolean");
9827
+ const root = Object.hasOwn(sections, "mnemon") ? copiedObject(sections.mnemon, "mnemon") : {};
9828
+ if (Object.hasOwn(root, "memoryView") || Object.hasOwn(root, "legacySettingsImported")) throw new Error("Legacy mnemon contains reserved migration fields");
9829
+ const oldInteraction = Object.hasOwn(root, "conversationInteraction") ? interaction(root.conversationInteraction, "mnemon.conversationInteraction", [
9830
+ "toolviews",
9831
+ "turnBar",
9832
+ "saveAction"
9833
+ ]) : {};
9834
+ const oldUi = Object.hasOwn(sections, "mnemon-ui") ? interaction(sections["mnemon-ui"], "mnemon-ui", ["turnBar", "saveAction"]) : {};
9835
+ const currentInteraction = Object.hasOwn(current, "conversationInteraction") ? interaction(current.conversationInteraction, "Current conversationInteraction", [
9836
+ "toolviews",
9837
+ "turnBar",
9838
+ "saveAction"
9839
+ ]) : {};
9840
+ const patch = Object.fromEntries(Object.entries(root).filter(([key]) => key !== "conversationInteraction" && !Object.hasOwn(current, key)));
9841
+ const inheritedInteraction = {
9842
+ ...oldInteraction,
9843
+ ...oldUi
9844
+ };
9845
+ if (Object.keys(inheritedInteraction).some((key) => !Object.hasOwn(currentInteraction, key))) patch.conversationInteraction = {
9846
+ ...inheritedInteraction,
9847
+ ...currentInteraction
9848
+ };
9849
+ const currentView = Object.hasOwn(current, "memoryView") ? view(current.memoryView, "Current memoryView") : {};
9850
+ if (Object.hasOwn(sections, options.viewNamespace) || Object.hasOwn(sections, options.legacyNamespace)) {
9851
+ if (!Array.isArray(options.sourceEntryIds)) throw new Error("Source Entry ids must be an array");
9852
+ for (const id of options.sourceEntryIds) entryId(id, "Source Entry ids");
9853
+ const sourceEntryIds = new Set(options.sourceEntryIds);
9854
+ const oldSources = Object.hasOwn(sections, options.legacyNamespace) ? sources(sections[options.legacyNamespace], options.legacyNamespace, sourceEntryIds, diagnostics) : {};
9855
+ const oldView = Object.hasOwn(sections, options.viewNamespace) ? view(sections[options.viewNamespace], options.viewNamespace) : {};
9856
+ const entries = {
9857
+ ...oldSources,
9858
+ ...oldView.entries
9859
+ };
9860
+ applyEntryOverrides(entries, options.entryOverrides, sourceEntryIds, diagnostics);
9861
+ const migratedView = {
9862
+ ...oldView,
9863
+ entries,
9864
+ ...currentView
9865
+ };
9866
+ if (!Object.hasOwn(currentView, "entries") || !Object.hasOwn(currentView, "strategyTypeId") && oldView.strategyTypeId !== void 0) patch.memoryView = migratedView;
9867
+ }
9868
+ patch.legacySettingsImported = true;
9869
+ return {
9870
+ patch,
9871
+ diagnostics
9872
+ };
9873
+ } catch (error) {
9874
+ diagnostics.push(error instanceof Error ? error.message : "Legacy settings import could not be planned");
9875
+ return {
9876
+ patch: {},
9877
+ diagnostics
9878
+ };
9879
+ }
9880
+ }
9881
+ //#endregion
9882
+ //#region src/host/settings-service.ts
9883
+ const pendingNativeImports = /* @__PURE__ */ new WeakSet();
9884
+ const clone = (value) => structuredClone(value);
9885
+ const object = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
9886
+ function profileYaml(source) {
9887
+ const document = parseDocument(source, { customTags: [{
9888
+ tag: "tag:yaml.org,2002:js",
9889
+ resolve: (value) => ({ __jsExpr: value })
9890
+ }] });
9891
+ if (document.errors.length > 0 || document.warnings.length > 0) throw new Error("Invalid or unsupported legacy/profile YAML; retained backup was not changed");
9892
+ return document.toJS({ maxAliasCount: 100 });
9893
+ }
9894
+ /** A local facade preserves Mnemon's wire namespaces; DSH owns all persistence. */
9895
+ var ProfileMnemonSettings = class {
9896
+ ctx;
9897
+ input;
9898
+ registrations = /* @__PURE__ */ new Map();
9899
+ listeners = /* @__PURE__ */ new Set();
9900
+ revisions = /* @__PURE__ */ new Map();
9901
+ owner;
9902
+ forms;
9903
+ editor;
9904
+ closed = false;
9905
+ importing;
9906
+ nativeImportPending;
9907
+ constructor(ctx, input) {
9908
+ this.ctx = ctx;
9909
+ this.input = input;
9910
+ this.forms = ctx.settings;
9911
+ this.owner = ctx.fiber;
9912
+ this.editor = ctx.get("configEditor");
9913
+ const profile = ctx.get("profileContext");
9914
+ const root = ctx.root ?? ctx;
9915
+ if (profile?.home !== void 0 && existsSync(join(profile.home, "settings.yaml"))) pendingNativeImports.add(root);
9916
+ this.nativeImportPending = pendingNativeImports.has(root);
9917
+ const owner = this.owner;
9918
+ const validate = (candidate) => {
9919
+ const config = plainHostConfig(candidate);
9920
+ for (const registration of this.registrations.values()) {
9921
+ if (registration.legacy) continue;
9922
+ const value = this.resolve(registration, config);
9923
+ registration.validate?.(value);
9924
+ }
9925
+ };
9926
+ ctx.on("internal/config", function(_raw, next) {
9927
+ const candidate = next();
9928
+ if (this === owner) validate(candidate);
9929
+ return candidate;
9930
+ }, { prepend: true });
9931
+ ctx.on("loader/volatile-update", ((paths) => {
9932
+ if (this.closed) return;
9933
+ for (const registration of this.registrations.values()) {
9934
+ if (registration.legacy) continue;
9935
+ if (!paths.some((path) => registration.field === void 0 ? path.length === 0 || !["memoryView", "legacySettingsImported"].includes(path[0]) : path.length === 0 || path[0] === registration.field)) continue;
9936
+ const value = this.resolve(registration);
9937
+ for (const listener of this.listeners) try {
9938
+ listener(registration.namespace, value);
9939
+ } catch (error) {
9940
+ console.warn("dsh-mnemon: committed settings listener failed", error);
9941
+ }
9942
+ }
9943
+ }));
9944
+ ctx.effect(() => {
9945
+ const disposePresentation = owner === void 0 ? void 0 : this.forms.configure({ auto: false }, owner);
9946
+ return () => {
9947
+ this.closed = true;
9948
+ this.listeners.clear();
9949
+ this.revisions.clear();
9950
+ disposePresentation?.();
9951
+ };
9952
+ }, "dsh-mnemon: profile settings");
9953
+ }
9954
+ get writable() {
9955
+ const entry = this.owner?.entry;
9956
+ return !this.closed && this.forms.writable && entry !== void 0 && this.editor?.entries().includes(entry) === true;
9957
+ }
9958
+ onUpdated(listener) {
9959
+ this.listeners.add(listener);
9960
+ return () => {
9961
+ this.listeners.delete(listener);
9962
+ };
9963
+ }
9964
+ resolve(registration, config = plainHostConfig(this.owner?.config ?? this.input)) {
9965
+ const value = registration.legacy ? registration.base : registration.field === void 0 ? config : config[registration.field];
9966
+ return registration.schema(object(value));
9967
+ }
9968
+ register(namespace, schema, options) {
9969
+ if (this.registrations.has(namespace)) throw new Error(`Mnemon settings already registered: ${namespace}`);
9970
+ if (typeof schema !== "function") throw new Error("Mnemon settings require a schema");
9971
+ const field = namespace === "mnemon-ui" ? "conversationInteraction" : /^mnemon-view(?:-[a-f0-9]{16})?$/u.test(namespace) ? "memoryView" : void 0;
9972
+ const legacy = /^mnemon-plugins(?:-[a-f0-9]{16})?$/u.test(namespace);
9973
+ if (namespace !== "mnemon" && field === void 0 && !legacy) throw new Error("Unsupported Mnemon settings namespace");
9974
+ const registration = {
9975
+ namespace,
9976
+ ...field === void 0 ? {} : { field },
9977
+ legacy,
9978
+ schema,
9979
+ base: clone(options.base ?? {}),
9980
+ ...options.validate === void 0 ? {} : { validate: options.validate }
9981
+ };
9982
+ const value = this.resolve(registration);
9983
+ options.validate?.(value);
9984
+ this.registrations.set(namespace, registration);
9985
+ return { get: () => this.resolve(registration) };
9986
+ }
9987
+ nativeDescriptor() {
9988
+ if (this.closed || this.owner?.entry === void 0 || !this.editor?.entries().includes(this.owner.entry)) return void 0;
9989
+ const descriptor = this.forms.describe().find((item) => item.ns === this.owner?.entry?.options.id);
9990
+ if (descriptor !== void 0) {
9991
+ this.revisions.set(descriptor.revision, clone({
9992
+ ns: descriptor.ns,
9993
+ revision: descriptor.revision,
9994
+ applies: descriptor.applies,
9995
+ value: descriptor.value,
9996
+ base: descriptor.base,
9997
+ user: descriptor.user
9998
+ }));
9999
+ if (this.revisions.size > 64) this.revisions.delete(this.revisions.keys().next().value);
10000
+ }
10001
+ return descriptor;
10002
+ }
10003
+ unchanged(previous, current, registration, operations) {
10004
+ const project = (value) => {
10005
+ if (registration.field !== void 0) return object(value)[registration.field];
10006
+ const { memoryView: _view, legacySettingsImported: _imported, conversationInteraction, ...core } = object(value);
10007
+ return operations.some((operation) => operation.path[0] === "conversationInteraction") ? {
10008
+ ...core,
10009
+ conversationInteraction
10010
+ } : core;
10011
+ };
10012
+ const signature = (descriptor) => canonicalMemoryJson(JSON.parse(JSON.stringify([
10013
+ project(descriptor.value) ?? null,
10014
+ project(descriptor.base) ?? null,
10015
+ project(descriptor.user) ?? null
10016
+ ])));
10017
+ return signature(previous) === signature(current);
10018
+ }
10019
+ describe(options) {
10020
+ const raw = this.nativeDescriptor();
10021
+ if (raw === void 0) return [];
10022
+ const descriptor = options?.redactSecrets ? this.forms.describe(options).find((item) => item.ns === this.owner?.entry?.options.id) : raw;
10023
+ if (descriptor === void 0) return [];
10024
+ return [...this.registrations.values()].map((registration) => {
10025
+ const project = (value) => registration.legacy ? {} : registration.field === void 0 ? value : object(value)[registration.field];
10026
+ return {
10027
+ ns: registration.namespace,
10028
+ value: project(descriptor.value) ?? {},
10029
+ base: project(descriptor.base) ?? {},
10030
+ user: project(descriptor.user) ?? {},
10031
+ revision: descriptor.revision,
10032
+ applies: "live"
10033
+ };
10034
+ });
10035
+ }
10036
+ async mutate(namespace, operations, expectedRevision) {
10037
+ const registration = this.registrations.get(namespace);
10038
+ if (registration === void 0 || registration.legacy) throw new Error("Unsupported Mnemon settings namespace");
10039
+ if (!this.writable) throw new Error("DSH settings are read-only");
10040
+ const translated = operations.map((operation) => ({
10041
+ ...operation,
10042
+ path: [...registration.field === void 0 ? [] : [registration.field], ...operation.path]
10043
+ }));
10044
+ let revision = expectedRevision;
10045
+ const previous = revision === void 0 ? void 0 : this.revisions.get(revision);
10046
+ for (let attempt = 0;; attempt += 1) {
10047
+ if (!this.writable) throw new Error("DSH settings are read-only");
10048
+ try {
10049
+ await this.forms.mutate(this.owner.entry.options.id, translated, revision);
10050
+ return;
10051
+ } catch (error) {
10052
+ if (attempt >= 2 || previous === void 0 || typeof error !== "object" || error === null || !("code" in error) || error.code !== "SETTINGS_CONFLICT") throw error;
10053
+ if (!this.writable) throw new Error("DSH settings are read-only");
10054
+ const current = this.nativeDescriptor();
10055
+ if (current === void 0 || !this.unchanged(previous, current, registration, operations)) throw error;
10056
+ revision = current.revision;
10057
+ }
10058
+ }
10059
+ }
10060
+ /** Read only the retained backup; ConfigEditor preserves all other patch rows. */
10061
+ async importLegacy(sourceEntryIds) {
10062
+ if (this.importing !== void 0) return this.importing;
10063
+ this.importing = this.performImport(sourceEntryIds).finally(() => {
10064
+ this.importing = void 0;
10065
+ });
10066
+ return this.importing;
10067
+ }
10068
+ async performImport(sourceEntryIds) {
10069
+ const profile = this.ctx.get("profileContext");
10070
+ const entry = this.owner?.entry;
10071
+ if (!this.writable || profile?.home === void 0 || entry === void 0) return;
10072
+ if (this.nativeImportPending) {
10073
+ console.warn("dsh-mnemon: DSH is importing settings.yaml; restart once to recover retained Mnemon UI and plugin preferences");
10074
+ return;
9892
10075
  }
9893
- };
9894
- }
9895
- function unknownSettingsEndpoint(endpoint) {
9896
- return {
9897
- ok: false,
9898
- error: {
9899
- code: "bad-request",
9900
- message: `unknown remote Mnemon settings endpoint: ${endpoint}`,
9901
- details: { issues: [] }
10076
+ if (plainHostConfig(this.owner?.config ?? this.input).legacySettingsImported) return;
10077
+ const backup = join(profile.home, "settings.yaml.imported");
10078
+ let text;
10079
+ try {
10080
+ if ((await stat(backup)).size > 1048576) throw new Error("Legacy settings backup exceeds the import limit");
10081
+ text = await readFile(backup, "utf8");
10082
+ } catch (error) {
10083
+ if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") return;
10084
+ throw error;
9902
10085
  }
9903
- };
9904
- }
9905
- /**
9906
- * Project Mnemon's existing handlers through DSH API Gateway. The Gateway owns
9907
- * `/api`, Host/Origin validation, browser pairing, and response envelopes;
9908
- * this Service retains Mnemon's narrower management grant.
9909
- */
9910
- let MnemonRemoteService = (() => {
9911
- let _classSuper = TypertRemoteService;
9912
- let _instanceExtraInitializers = [];
9913
- let _read_decorators;
9914
- let _activation_decorators;
9915
- let _write_decorators;
9916
- let _pack_decorators;
9917
- let _settings_decorators;
9918
- let _view_decorators;
9919
- let _viewWrite_decorators;
9920
- return class MnemonRemoteService extends _classSuper {
9921
- static {
9922
- const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
9923
- _read_decorators = [Remote("read")];
9924
- _activation_decorators = [Remote("activation")];
9925
- _write_decorators = [Remote("write")];
9926
- _pack_decorators = [Remote("pack")];
9927
- _settings_decorators = [Remote("settings")];
9928
- _view_decorators = [Remote("view")];
9929
- _viewWrite_decorators = [Remote("viewWrite")];
9930
- __esDecorate(this, null, _read_decorators, {
9931
- kind: "method",
9932
- name: "read",
9933
- static: false,
9934
- private: false,
9935
- access: {
9936
- has: (obj) => "read" in obj,
9937
- get: (obj) => obj.read
9938
- },
9939
- metadata: _metadata
9940
- }, null, _instanceExtraInitializers);
9941
- __esDecorate(this, null, _activation_decorators, {
9942
- kind: "method",
9943
- name: "activation",
9944
- static: false,
9945
- private: false,
9946
- access: {
9947
- has: (obj) => "activation" in obj,
9948
- get: (obj) => obj.activation
9949
- },
9950
- metadata: _metadata
9951
- }, null, _instanceExtraInitializers);
9952
- __esDecorate(this, null, _write_decorators, {
9953
- kind: "method",
9954
- name: "write",
9955
- static: false,
9956
- private: false,
9957
- access: {
9958
- has: (obj) => "write" in obj,
9959
- get: (obj) => obj.write
9960
- },
9961
- metadata: _metadata
9962
- }, null, _instanceExtraInitializers);
9963
- __esDecorate(this, null, _pack_decorators, {
9964
- kind: "method",
9965
- name: "pack",
9966
- static: false,
9967
- private: false,
9968
- access: {
9969
- has: (obj) => "pack" in obj,
9970
- get: (obj) => obj.pack
9971
- },
9972
- metadata: _metadata
9973
- }, null, _instanceExtraInitializers);
9974
- __esDecorate(this, null, _settings_decorators, {
9975
- kind: "method",
9976
- name: "settings",
9977
- static: false,
9978
- private: false,
9979
- access: {
9980
- has: (obj) => "settings" in obj,
9981
- get: (obj) => obj.settings
9982
- },
9983
- metadata: _metadata
9984
- }, null, _instanceExtraInitializers);
9985
- __esDecorate(this, null, _view_decorators, {
9986
- kind: "method",
9987
- name: "view",
9988
- static: false,
9989
- private: false,
9990
- access: {
9991
- has: (obj) => "view" in obj,
9992
- get: (obj) => obj.view
9993
- },
9994
- metadata: _metadata
9995
- }, null, _instanceExtraInitializers);
9996
- __esDecorate(this, null, _viewWrite_decorators, {
9997
- kind: "method",
9998
- name: "viewWrite",
9999
- static: false,
10000
- private: false,
10001
- access: {
10002
- has: (obj) => "viewWrite" in obj,
10003
- get: (obj) => obj.viewWrite
10004
- },
10005
- metadata: _metadata
10006
- }, null, _instanceExtraInitializers);
10007
- if (_metadata) Object.defineProperty(this, Symbol.metadata, {
10008
- enumerable: true,
10009
- configurable: true,
10010
- writable: true,
10011
- value: _metadata
10086
+ const sections = profileYaml(text);
10087
+ const viewNamespace = [...this.registrations.keys()].find((namespace) => namespace.startsWith("mnemon-view"));
10088
+ const legacyNamespace = [...this.registrations.keys()].find((namespace) => namespace.startsWith("mnemon-plugins"));
10089
+ const plan = () => {
10090
+ const configuration = this.editor.configuration().find((item) => item.entry === entry);
10091
+ if (configuration === void 0) throw new Error("Mnemon profile entry is no longer editable");
10092
+ const document = profileYaml(readFileSync(this.editor.documentPath, "utf8"));
10093
+ const entryOverrides = Array.isArray(document) ? document.flatMap((value) => {
10094
+ const row = object(value);
10095
+ if (typeof row.id !== "string") return [];
10096
+ const entries = this.editor.entries().filter((candidate) => candidate.options.id === row.id);
10097
+ if (entries.length > 1) throw new Error("Legacy settings import requires unambiguous profile Entry ids");
10098
+ return [{
10099
+ id: entries[0]?.id ?? row.id,
10100
+ ..."disabled" in row ? { disabled: row.disabled } : {},
10101
+ ..."config" in row ? { config: row.config } : {}
10102
+ }];
10103
+ }) : [];
10104
+ return planLegacySettingsImport({
10105
+ entryId: entry.options.id,
10106
+ viewNamespace,
10107
+ legacyNamespace,
10108
+ sections,
10109
+ currentOverride: configuration.override,
10110
+ entryOverrides,
10111
+ sourceEntryIds
10012
10112
  });
10113
+ };
10114
+ const preview = plan();
10115
+ if (Object.keys(preview.patch).length === 0) {
10116
+ if (preview.diagnostics.length > 0) console.warn("dsh-mnemon: legacy settings were not imported; retained backup requires review");
10117
+ return;
10013
10118
  }
10014
- handlers = __runInitializers(this, _instanceExtraInitializers);
10015
- constructor(ctx, handlers) {
10016
- super(ctx, "mnemonRemote", { namespace: MNEMON_REMOTE_NAMESPACE });
10017
- this.handlers = handlers;
10018
- }
10019
- read(endpoint, payload, signal) {
10020
- return this.handlers.read(endpoint, payload, signal);
10021
- }
10022
- activation(endpoint, payload, signal) {
10023
- return this.handlers.activation(endpoint, payload, signal);
10024
- }
10025
- write(endpoint, payload, signal) {
10026
- if (!this.handlers.management) return Promise.resolve(denied());
10027
- return this.handlers.write(endpoint, payload, signal);
10028
- }
10029
- pack(endpoint, payload, signal) {
10030
- if (!this.handlers.management) return Promise.resolve(denied());
10031
- return this.handlers.pack(endpoint, payload, signal);
10032
- }
10033
- async settings(endpoint, payload, signal) {
10034
- if (endpoint === "mutate" && !this.handlers.management) return denied();
10035
- if (endpoint !== "get" && endpoint !== "mutate") return unknownSettingsEndpoint(endpoint);
10036
- const response = await this.handlers.settings(endpoint, payload, signal);
10037
- if (endpoint !== "get" || this.handlers.management || !response.ok || typeof response.value !== "object" || response.value === null || Array.isArray(response.value)) return response;
10119
+ if (this.closed) return;
10120
+ await this.editor.edit(entry, (current) => {
10121
+ if (this.closed) throw new Error("Mnemon settings are disposed");
10122
+ const next = plan();
10038
10123
  return {
10039
- ...response,
10040
- value: {
10041
- ...response.value,
10042
- writable: false
10043
- }
10124
+ ...current,
10125
+ ...next.patch
10044
10126
  };
10045
- }
10046
- view(endpoint, payload, signal) {
10047
- return this.handlers.view(endpoint, payload, signal);
10048
- }
10049
- viewWrite(endpoint, payload, signal) {
10050
- if (!this.handlers.management) return Promise.resolve(denied());
10051
- return this.handlers.viewWrite(endpoint, payload, signal);
10052
- }
10053
- };
10054
- })();
10127
+ });
10128
+ }
10129
+ };
10055
10130
  //#endregion
10056
10131
  //#region src/host/plugin.ts
10057
10132
  const name = "dsh-mnemon";
@@ -10075,54 +10150,26 @@ function optionalWorkspaceRegistry(ctx) {
10075
10150
  function apply(rawContext, rawConfig = {}) {
10076
10151
  const ctx = rawContext;
10077
10152
  const config = plainHostConfig(rawConfig);
10078
- const hostSettings = createHostSettings(ctx, rawConfig);
10153
+ const hostSettings = new ProfileMnemonSettings(ctx, rawConfig);
10079
10154
  registerMnemonSubagentTokenUsageProjection(ctx);
10080
10155
  const extensions = provideMemoryRuntime(ctx);
10081
10156
  const memoryPlugins = new MemoryPluginManagement(ctx, extensions, hostSettings);
10082
10157
  const pluginInstallation = new MemoryPluginInstallation(ctx);
10083
- const effectiveConfig = (value) => memoryPlugins.resolveConfig(resolveConfig(value), hostSettings instanceof ProfileMnemonSettings ? value.memoryView ?? { entries: {} } : void 0);
10084
- const prepared = /* @__PURE__ */ new Map();
10085
- const disposePrepared = () => {
10086
- for (const candidate of prepared.values()) candidate.graph.dispose();
10087
- prepared.clear();
10088
- };
10158
+ const effectiveConfig = (value) => memoryPlugins.resolveConfig(resolveConfig(value), value.memoryView ?? { entries: {} });
10089
10159
  const settings = hostSettings.register("mnemon", Config, {
10090
10160
  base: config,
10091
10161
  applies: "live",
10092
- validate: (value) => {
10093
- disposePrepared();
10094
- const candidate = {
10095
- graph: createRuntimeGraph(effectiveConfig(value), void 0, extensions),
10096
- token: Symbol("prepared-runtime")
10097
- };
10098
- if (hostSettings instanceof ProfileMnemonSettings) {
10099
- candidate.graph.dispose();
10100
- return;
10101
- }
10102
- prepared.set(value, candidate);
10103
- queueMicrotask(() => {
10104
- if (prepared.get(value)?.token !== candidate.token) return;
10105
- prepared.delete(value);
10106
- candidate.graph.dispose();
10107
- });
10108
- }
10162
+ validate: (value) => createRuntimeGraph(effectiveConfig(value), void 0, extensions).dispose()
10109
10163
  });
10110
- const initialSettings = settings.get();
10111
- const initialCandidate = prepared.get(initialSettings);
10112
- if (initialCandidate !== void 0) prepared.delete(initialSettings);
10113
- const runtime = new LiveMnemonRuntime(initialCandidate?.graph ?? createRuntimeGraph(effectiveConfig(initialSettings), void 0, extensions), optionalWorkspaceRegistry(ctx), ctx.agents, extensions);
10164
+ const runtime = new LiveMnemonRuntime(createRuntimeGraph(effectiveConfig(settings.get()), void 0, extensions), optionalWorkspaceRegistry(ctx), ctx.agents, extensions);
10114
10165
  const resolved = runtime.config;
10115
- ctx.effect(() => subscribeSettings(ctx, hostSettings, (namespace, value) => {
10116
- const next = value;
10166
+ ctx.effect(() => hostSettings.onUpdated((namespace, value) => {
10117
10167
  if (namespace === memoryPlugins.settingsNamespace) {
10118
10168
  runtime.swap(createRuntimeGraph(effectiveConfig(settings.get()), void 0, extensions));
10119
10169
  return;
10120
10170
  }
10121
10171
  if (namespace !== "mnemon") return;
10122
- const candidate = prepared.get(next);
10123
- if (candidate !== void 0) prepared.delete(next);
10124
- disposePrepared();
10125
- runtime.swap(candidate?.graph ?? createRuntimeGraph(effectiveConfig(next), void 0, extensions));
10172
+ runtime.swap(createRuntimeGraph(effectiveConfig(value), void 0, extensions));
10126
10173
  }), "dsh-mnemon: live runtime settings");
10127
10174
  ctx.effect(() => memoryPlugins.start(), "dsh-mnemon: plugin graph settings");
10128
10175
  hostSettings.register("mnemon-ui", InteractionConfig, {
@@ -10146,7 +10193,7 @@ function apply(rawContext, rawConfig = {}) {
10146
10193
  unsubscribe();
10147
10194
  };
10148
10195
  }, "dsh-mnemon: canonical displayMode migration");
10149
- if (hostSettings instanceof ProfileMnemonSettings) ctx.effect(() => {
10196
+ ctx.effect(() => {
10150
10197
  let disposed = false;
10151
10198
  const loader = ctx.get("loader");
10152
10199
  Promise.resolve(loader?.await?.()).then(async () => {
@@ -10178,7 +10225,6 @@ function apply(rawContext, rawConfig = {}) {
10178
10225
  return async () => {
10179
10226
  stop();
10180
10227
  await coordinator.dispose();
10181
- disposePrepared();
10182
10228
  runtime.dispose();
10183
10229
  };
10184
10230
  }, "dsh-mnemon.lifecycle-root()");
@@ -10187,16 +10233,15 @@ function apply(rawContext, rawConfig = {}) {
10187
10233
  registerGuidance(ctx, resolved);
10188
10234
  ctx.inject(["connection"], (webContext) => {
10189
10235
  if (webContext.connection === void 0) return;
10190
- const managementAuthority = resolved.remoteAccess === "trusted-host" ? "trusted-host" : "loopback";
10191
- const rpc = registerRpc(webContext.connection, runtime, lifecycle, void 0, managementAuthority);
10192
- const settings = registerSettingsRpc(webContext.connection, hostSettings, managementAuthority);
10193
- const view = registerViewRpc(webContext.connection, runtime, extensions, memoryPlugins, lifecycle, managementAuthority, pluginInstallation);
10236
+ const rpc = registerRpc(webContext.connection, runtime, lifecycle);
10237
+ const settings = registerSettingsRpc(webContext.connection, hostSettings);
10238
+ const view = registerViewRpc(webContext.connection, runtime, extensions, memoryPlugins, lifecycle, pluginInstallation);
10194
10239
  if (Context.is(webContext)) new MnemonRemoteService(webContext, {
10195
10240
  ...rpc,
10196
10241
  settings,
10197
10242
  view: view.read,
10198
10243
  viewWrite: view.write,
10199
- management: managementAuthority === "trusted-host"
10244
+ management: resolved.remoteAccess === "trusted-host"
10200
10245
  });
10201
10246
  });
10202
10247
  }