dsh-mnemon 0.5.16 → 0.5.18

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";
@@ -15,14 +15,14 @@ import { RUNTIME_ENTRY_DELIMITER, RUNTIME_MEMORY_LIMITS, RUNTIME_MEMORY_VERSION
15
15
  import { threeTierActionWorkflow } from "dsh-mnemon-strategy-default-three-tier/extension-sdk";
16
16
  import { AsyncLocalStorage } from "node:async_hooks";
17
17
  import { fileURLToPath } from "node:url";
18
- import { findMnemonCommand, mnemonNpmLauncher, nodeLauncherEnvironment } from "dsh-mnemon-source-memory-spaces/native-cli";
18
+ import { findMnemonCommand, mnemonNpmLauncher, nodeLauncherEnvironment, resolveMnemonInvocation } from "dsh-mnemon-source-memory-spaces/native-cli";
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);
@@ -6486,7 +6475,8 @@ var VersionUpdateManager = class {
6486
6475
  const launcher = mnemonNpmLauncher(command);
6487
6476
  let current;
6488
6477
  try {
6489
- current = versionFrom((await resultOrThrow(this.processRunner, launcher === void 0 ? command : process.execPath, launcher === void 0 ? ["--version"] : [launcher, "--version"], CHECK_TIMEOUT_MS, launcher === void 0 ? {} : { env: nodeLauncherEnvironment() })).stdout);
6478
+ const invocation = resolveMnemonInvocation(command, ["--version"]);
6479
+ current = versionFrom((await resultOrThrow(this.processRunner, invocation.command, invocation.args, CHECK_TIMEOUT_MS, { env: invocation.env })).stdout);
6490
6480
  } catch {
6491
6481
  return { install: {
6492
6482
  mode: launcher === void 0 ? "manual" : "npm",
@@ -6662,17 +6652,6 @@ var VersionUpdateManager = class {
6662
6652
  return {
6663
6653
  checkedAt: (/* @__PURE__ */ new Date()).toISOString(),
6664
6654
  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
6655
  id: "dsh-mnemon",
6677
6656
  name: "dsh-mnemon",
6678
6657
  ...dshInstall.profileName === void 0 ? {} : { installProfile: dshInstall.profileName },
@@ -6686,6 +6665,17 @@ var VersionUpdateManager = class {
6686
6665
  restartRequired: this.pendingRestart.size > 0,
6687
6666
  updateHint: dshInstall.mode === "npm" ? dshSupported ? "pnpm" : "pnpm-missing" : dshInstall.mode === "link" ? "link" : "manual",
6688
6667
  ...dshLatest === void 0 ? { checkError: "latest-unavailable" } : {}
6668
+ }, {
6669
+ id: "mnemon",
6670
+ name: "Mnemon CLI",
6671
+ ...mnemonLocal.install.command === void 0 ? {} : { executablePath: mnemonLocal.install.command },
6672
+ ...mnemonLocal.current === void 0 ? {} : { current: mnemonLocal.current },
6673
+ ...mnemonLatest === void 0 ? {} : { latest: mnemonLatest },
6674
+ outdated: mnemonOutdated,
6675
+ installMode: mnemonLocal.install.mode,
6676
+ updateSupported: mnemonSupported,
6677
+ updateHint: mnemonLocal.install.hint ?? (mnemonLocal.install.mode === "homebrew" ? mnemonSupported ? "brew" : "brew-missing" : mnemonLocal.install.mode === "go" ? "go" : mnemonLocal.install.mode === "missing" ? "install" : "manual"),
6678
+ ...mnemonLatest === void 0 ? { checkError: "latest-unavailable" } : {}
6689
6679
  }]
6690
6680
  };
6691
6681
  }
@@ -6799,6 +6789,13 @@ function requireWritable(runtime) {
6799
6789
  function requireCapability(runtime, typeId, capability) {
6800
6790
  assertParticipation(runtime.graph.config, typeId, capability, "manual");
6801
6791
  }
6792
+ /** A memory layer that is off takes no part in reads or writes, management and assistance included. */
6793
+ function requireLayerOn(runtime, sourceTypeId) {
6794
+ 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`);
6795
+ }
6796
+ function sourceTypeOf(generation, sourceInstanceKey) {
6797
+ return generation?.sourceInstances().find((source) => source.sourceInstanceKey === sourceInstanceKey)?.sourceTypeId;
6798
+ }
6802
6799
  function success$1(value) {
6803
6800
  return {
6804
6801
  ok: true,
@@ -6883,8 +6880,12 @@ async function catalog(runtime, lifecycle) {
6883
6880
  }
6884
6881
  }
6885
6882
  async function compositionStatus(runtime) {
6883
+ const composition = runtime.graph.memoryComposition.inspect();
6884
+ const strategyTypeId = runtime.graph.memoryComposition.current()?.strategy.definition.manifest.typeId;
6886
6885
  return {
6887
- evaluation: runtime.graph.memoryComposition.inspect().evaluation,
6886
+ serving: composition.servingGenerationId !== void 0,
6887
+ ...strategyTypeId === void 0 ? {} : { strategyTypeId },
6888
+ evaluation: composition.evaluation,
6888
6889
  sources: (await catalog(runtime)).sources,
6889
6890
  configuration: runtime.graph.config.memoryTopology
6890
6891
  };
@@ -7010,6 +7011,7 @@ function createReadHandler(input, lifecycle, versions) {
7010
7011
  case "source-management-read": {
7011
7012
  const lease = runtime.graph.memoryComposition.acquire();
7012
7013
  try {
7014
+ requireLayerOn(runtime, sourceTypeOf(lease.generation, String(payload.sourceInstanceKey ?? "")));
7013
7015
  return success$1(await lease.generation.executeManagement({
7014
7016
  scope: runtime.scope,
7015
7017
  sourceInstanceKey: String(payload.sourceInstanceKey ?? ""),
@@ -7104,6 +7106,7 @@ function createActivationHandler(input) {
7104
7106
  try {
7105
7107
  const source = (await lease.generation.managementCatalog(runtime.scope)).sources.find((item) => item.sourceInstanceKey === payload.sourceInstanceKey && item.sourceTypeId === "memory-spaces");
7106
7108
  if (source === void 0) throw new Error("Memory Spaces Source instance is unavailable");
7109
+ requireLayerOn(runtime, source.sourceTypeId);
7107
7110
  return success$1(await lease.generation.executeManagement({
7108
7111
  scope: runtime.scope,
7109
7112
  sourceInstanceKey: source.sourceInstanceKey,
@@ -7158,6 +7161,7 @@ function createWriteHandler(input, lifecycle, versions) {
7158
7161
  ...typeof payload.expectedRevision === "string" ? { expectedRevision: payload.expectedRevision } : {},
7159
7162
  ...signal === void 0 ? {} : { signal }
7160
7163
  };
7164
+ requireLayerOn(runtime, sourceTypeOf(runtime.graph.memoryComposition.current(), request.sourceInstanceKey));
7161
7165
  if (lifecycle !== void 0) return success$1(await lifecycle.manageSource(runtime.graph, request));
7162
7166
  const lease = runtime.graph.memoryComposition.acquire();
7163
7167
  try {
@@ -7171,6 +7175,7 @@ function createWriteHandler(input, lifecycle, versions) {
7171
7175
  const source = (await catalog(runtime, lifecycle)).sources.find((item) => item.sourceInstanceKey === payload.sourceInstanceKey);
7172
7176
  const operation = String(payload.operation ?? "");
7173
7177
  if (source === void 0 || !source.assistance.includes(operation)) throw new Error("Host assistance is not available for this Source instance");
7178
+ requireLayerOn(runtime, source.sourceTypeId);
7174
7179
  if (operation !== "agent-search" && (payload.confirmed !== true || payload.expectedRevision !== source.revision)) throw new Error("Host assistance requires confirmation of the current Source revision");
7175
7180
  const value = await assisted(runtime, lifecycle, source.sourceTypeId, operation, object$4(payload.input), signal, {
7176
7181
  sourceInstanceKey: source.sourceInstanceKey,
@@ -7239,16 +7244,16 @@ function createPackHandler(input) {
7239
7244
  }
7240
7245
  };
7241
7246
  }
7242
- function registerRpc(connection, input, lifecycle, versions, managementAuthority = "loopback") {
7247
+ function registerRpc(connection, input, lifecycle, versions) {
7243
7248
  const versionManager = versions ?? new VersionUpdateManager({ mnemonCliPath: () => input.config.cliPath });
7244
7249
  const readHandler = createReadHandler(input, lifecycle, versionManager);
7245
7250
  const activationHandler = createActivationHandler(input);
7246
7251
  const writeHandler = createWriteHandler(input, lifecycle, versionManager);
7247
7252
  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 });
7253
+ connection.rpc.handle(MNEMON_READ_CHANNEL, readHandler);
7254
+ connection.rpc.handle(MNEMON_ACTIVATION_CHANNEL, activationHandler);
7255
+ connection.rpc.handle(MNEMON_WRITE_CHANNEL, writeHandler);
7256
+ connection.rpc.handle(MNEMON_PACK_CHANNEL, packHandler);
7252
7257
  return {
7253
7258
  read: readHandler,
7254
7259
  activation: activationHandler,
@@ -7403,9 +7408,9 @@ function createSettingsHandler(settings) {
7403
7408
  }
7404
7409
  };
7405
7410
  }
7406
- function registerSettingsRpc(connection, settings, authority = "loopback") {
7411
+ function registerSettingsRpc(connection, settings) {
7407
7412
  const handler = createSettingsHandler(settings);
7408
- connection.rpc.handle(MNEMON_SETTINGS_CHANNEL, handler, { authority });
7413
+ connection.rpc.handle(MNEMON_SETTINGS_CHANNEL, handler);
7409
7414
  return handler;
7410
7415
  }
7411
7416
  //#endregion
@@ -8328,7 +8333,6 @@ const mnemonSubagentTokenUsageProjectionDefinition = {
8328
8333
  key: "mnemonSubagentTokenUsage",
8329
8334
  stateVersion: 1,
8330
8335
  stateSchema: tokenUsageStateSchema,
8331
- schema: tokenUsageWire.viewSchema,
8332
8336
  init: () => ({
8333
8337
  descriptorSeen: false,
8334
8338
  totals: emptyTokenUsage(),
@@ -8351,7 +8355,6 @@ const mnemonSubagentTokenUsageProjectionDefinition = {
8351
8355
  last: sample
8352
8356
  };
8353
8357
  },
8354
- view: tokenUsageWire.view,
8355
8358
  wire: tokenUsageWire
8356
8359
  };
8357
8360
  /** Register lazily when the optional DSH projection service is present. */
@@ -8361,722 +8364,224 @@ function registerMnemonSubagentTokenUsageProjection(ctx) {
8361
8364
  });
8362
8365
  }
8363
8366
  //#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)]));
8367
+ //#region src/host/plugin-management.ts
8368
+ /** Saved choices keep only configuration once the profile patch owns enablement. */
8369
+ function configurationOnly(entries) {
8370
+ return Object.fromEntries(Object.entries(entries).map(([entryId, value]) => [entryId, { config: value.config }]));
8378
8371
  }
8379
- /** Read a detached configuration snapshot from either DSH generation. */
8380
- function plainHostConfig(value = {}) {
8381
- return plain(value);
8372
+ const PACKAGE$1 = /^(?:@[a-z0-9._-]+\/)?dsh-mnemon-(source|strategy)-[a-z0-9._-]+$/u;
8373
+ const hash = (value) => createHash("sha256").update(canonicalMemoryJson(value)).digest("hex");
8374
+ const clone$1 = (value) => JSON.parse(JSON.stringify(value));
8375
+ function contributionRoles(snapshot, entryId) {
8376
+ const roles = /* @__PURE__ */ new Set();
8377
+ if (snapshot.sources.some((value) => value.provenance.entryId === entryId)) roles.add("source");
8378
+ if (snapshot.strategies.some((value) => value.provenance.entryId === entryId)) roles.add("strategy");
8379
+ if ((snapshot.strategyExtensions ?? []).some((value) => value.provenance.entryId === entryId)) roles.add("strategy-extension");
8380
+ return [...roles];
8382
8381
  }
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;
8382
+ function fallbackDescriptor(entry, snapshot, editor) {
8383
+ const sources = snapshot.sources.filter((value) => value.provenance.entryId === entry.id);
8384
+ const strategies = snapshot.strategies.filter((value) => value.provenance.entryId === entry.id);
8385
+ const extensions = (snapshot.strategyExtensions ?? []).filter((value) => value.provenance.entryId === entry.id);
8386
+ const roles = contributionRoles(snapshot, entry.id);
8387
+ if (roles.length === 0) roles.push(editor?.kind ?? (PACKAGE$1.exec(entry.options.name)?.[1] === "source" ? "source" : "strategy"));
8388
+ const typeId = editor?.typeId ?? strategies[0]?.definition.manifest.typeId ?? extensions[0]?.definition.manifest.typeId ?? sources[0]?.definition.manifest.typeId;
8389
+ const sourceLabel = sources[0]?.definition.manifest.management?.label;
8390
+ const label = editor?.label ?? {
8391
+ en: sourceLabel ?? typeId ?? entry.options.name,
8392
+ "zh-CN": sourceLabel ?? typeId ?? entry.options.name
8393
+ };
8394
+ const description = editor?.description ?? {
8395
+ en: "",
8396
+ "zh-CN": ""
8397
+ };
8398
+ 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)}` }];
8399
+ return defineMemoryPlugin({
8400
+ packageName: entry.options.name,
8401
+ label,
8402
+ description,
8403
+ roles,
8404
+ provides
8405
+ });
8402
8406
  }
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;
8407
+ function prepared(item, config) {
8408
+ if (item.editor === void 0) throw new Error(`Plugin does not expose a pure configuration factory: ${item.entry.id}`);
8409
+ const contribution = item.editor.create(config);
8410
+ if ([...contribution.strategies ?? [], ...contribution.strategyExtensions ?? []][0].manifest.packageName !== item.entry.options.name) throw new Error("Plugin factory does not match its declared Entry");
8411
+ if (contribution.plugin !== void 0 && hash(contribution.plugin) !== hash(item.descriptor)) throw new Error("Plugin factory descriptor does not match its module export");
8412
+ return prepareMemoryContributions({
8413
+ ...contribution,
8414
+ plugin: contribution.plugin ?? item.descriptor
8415
+ }, { instanceId: item.entry.id });
8424
8416
  }
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
- }
8417
+ function installedPlugin(descriptor, entryId) {
8488
8418
  return {
8489
- ...typeof result.strategyTypeId === "string" ? { strategyTypeId: result.strategyTypeId } : {},
8490
- ...Object.hasOwn(result, "entries") ? { entries } : {}
8419
+ kind: "plugin",
8420
+ instanceKey: `plugin:${entryId}`,
8421
+ provenance: {
8422
+ packageName: descriptor.packageName,
8423
+ entryId
8424
+ },
8425
+ descriptor
8491
8426
  };
8492
8427
  }
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
- });
8428
+ function orderPlugins(values) {
8429
+ const byId = new Map(values.map((value) => [value.entryId, value]));
8430
+ const edges = new Map(values.map((value) => [value.entryId, /* @__PURE__ */ new Set()]));
8431
+ const indegree = new Map(values.map((value) => [value.entryId, 0]));
8432
+ for (const dependent of values) for (const requirement of dependent.requires) for (const provider of values) {
8433
+ if (provider.entryId === dependent.entryId || !provider.provides.some((capability) => capability.id === requirement)) continue;
8434
+ if (edges.get(provider.entryId).has(dependent.entryId)) continue;
8435
+ edges.get(provider.entryId).add(dependent.entryId);
8436
+ indegree.set(dependent.entryId, indegree.get(dependent.entryId) + 1);
8528
8437
  }
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;
8438
+ const stable = (left, right) => byId.get(left).packageName.localeCompare(byId.get(right).packageName) || left.localeCompare(right);
8439
+ const ready = [...indegree].filter(([, count]) => count === 0).map(([entryId]) => entryId).sort(stable);
8440
+ const ordered = [];
8441
+ while (ready.length > 0) {
8442
+ const entryId = ready.shift();
8443
+ ordered.push(byId.get(entryId));
8444
+ for (const dependent of [...edges.get(entryId)].sort(stable)) {
8445
+ const next = indegree.get(dependent) - 1;
8446
+ indegree.set(dependent, next);
8447
+ if (next === 0) {
8448
+ ready.push(dependent);
8449
+ ready.sort(stable);
8541
8450
  }
8542
- entries[id].config = config;
8543
8451
  }
8544
- if (typeof row.disabled === "boolean") entries[id].enabled = !row.disabled;
8545
8452
  }
8453
+ if (ordered.length < values.length) ordered.push(...values.filter((value) => !ordered.includes(value)).sort((left, right) => stable(left.entryId, right.entryId)));
8454
+ return ordered;
8546
8455
  }
8547
8456
  /**
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.
8457
+ * Profile-local plugin graph overlay. Every dsh-mnemon Source or Strategy Entry
8458
+ * is one peer node; activation, configuration, dependency checks and rollback
8459
+ * use one transaction. Contribution roles do not create separate managers.
8551
8460
  */
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 {
8461
+ var MemoryPluginManagement = class {
8646
8462
  ctx;
8647
- input;
8648
- registrations = /* @__PURE__ */ new Map();
8649
- listeners = /* @__PURE__ */ new Set();
8650
- revisions = /* @__PURE__ */ new Map();
8651
- owner;
8652
- forms;
8653
- editor;
8463
+ engine;
8464
+ settingsService;
8465
+ settingsNamespace;
8466
+ settings;
8467
+ legacySettings;
8468
+ queue = Promise.resolve();
8654
8469
  closed = false;
8655
- importing;
8656
- nativeImportPending;
8657
- constructor(ctx, input) {
8470
+ timer;
8471
+ restoreError;
8472
+ discoveryWarnings = [];
8473
+ changingEntries = false;
8474
+ restorePending = false;
8475
+ /** A failed move keeps saved choices in effect instead of retrying on every profile reload. */
8476
+ enablementMoveError;
8477
+ constructor(ctx, engine, settingsService) {
8658
8478
  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);
8479
+ this.engine = engine;
8480
+ this.settingsService = settingsService;
8481
+ const loader = this.loader();
8482
+ const anchor = loader?.config?.baseUrl ?? loader?.ctx?.baseUrl ?? loader?.context?.baseUrl;
8483
+ const suffix = anchor ? `-${hash(anchor).slice(0, 16)}` : "";
8484
+ this.settingsNamespace = `${MNEMON_VIEW_SETTINGS_NAMESPACE}${suffix}`;
8485
+ this.settings = settingsService.register(this.settingsNamespace, schema, {
8486
+ base: { entries: {} },
8487
+ applies: "live",
8488
+ validate: (value) => {
8489
+ preferences(value);
8674
8490
  }
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
- }
8491
+ });
8492
+ this.legacySettings = settingsService.register(`mnemon-plugins${suffix}`, legacySchema, {
8493
+ base: { sources: {} },
8494
+ applies: "live",
8495
+ validate: (value) => {
8496
+ legacyPreferences(value);
8692
8497
  }
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");
8498
+ });
8703
8499
  }
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;
8500
+ resolveConfig(config, view = this.settings.get()) {
8501
+ const selected = view.strategyTypeId;
8502
+ return selected === void 0 ? config : {
8503
+ ...config,
8504
+ memoryTopology: {
8505
+ ...config.memoryTopology,
8506
+ strategyId: selected
8507
+ }
8508
+ };
8707
8509
  }
8708
- onUpdated(listener) {
8709
- this.listeners.add(listener);
8510
+ start() {
8511
+ const schedule = () => this.scheduleRestore();
8512
+ const stops = [
8513
+ this.ctx.on("loader/entry-init", schedule),
8514
+ this.ctx.on("loader/partial-dispose", schedule),
8515
+ this.ctx.on("app-boot/config-reload", () => this.scheduleRestore(true)),
8516
+ this.settingsService.onUpdated((namespace) => {
8517
+ if (namespace === this.settingsNamespace || namespace === `mnemon-plugins${this.settingsNamespace.slice(11)}`) schedule();
8518
+ })
8519
+ ];
8520
+ this.ctx.inject(["pluginManager"], () => {
8521
+ schedule();
8522
+ });
8523
+ schedule();
8710
8524
  return () => {
8711
- this.listeners.delete(listener);
8525
+ this.closed = true;
8526
+ this.restorePending = false;
8527
+ clearTimeout(this.timer);
8528
+ for (const stop of stops.reverse()) stop();
8712
8529
  };
8713
8530
  }
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));
8531
+ scheduleRestore(profileReload = false) {
8532
+ const hasSavedState = Object.keys(this.settings.get().entries ?? {}).length > 0 || Object.keys(this.legacySettings.get().sources ?? {}).length > 0;
8533
+ if (this.closed || !profileReload && this.changingEntries || !hasSavedState) return;
8534
+ this.restorePending = true;
8535
+ if (this.changingEntries || this.timer) return;
8536
+ this.timer = setTimeout(() => {
8537
+ this.timer = void 0;
8538
+ this.enqueueRestore();
8539
+ }, 0);
8717
8540
  }
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) };
8541
+ enqueueRestore() {
8542
+ if (this.closed || this.changingEntries || !this.restorePending) return;
8543
+ clearTimeout(this.timer);
8544
+ this.timer = void 0;
8545
+ this.restorePending = false;
8546
+ this.exclusive(async () => {
8547
+ try {
8548
+ await this.restore();
8549
+ } catch (error) {
8550
+ this.restoreError = error instanceof Error ? error.message : String(error);
8551
+ }
8552
+ }).catch(() => {});
8736
8553
  }
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;
8554
+ /** A response must not race the deferred restore after profile reconciliation. */
8555
+ async settled() {
8556
+ do {
8557
+ this.enqueueRestore();
8558
+ const pending = this.queue;
8559
+ await pending;
8560
+ if (pending === this.queue && !this.restorePending) return;
8561
+ } while (!this.closed);
8752
8562
  }
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);
8563
+ loader() {
8564
+ const candidate = this.ctx.get("loader");
8565
+ return candidate && typeof candidate.entries === "function" ? candidate : void 0;
8768
8566
  }
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
- });
8567
+ /**
8568
+ * With the DSH plugin manager, the profile patch owns enablement, so its
8569
+ * Plugins page and Mnemon agree. Profiles without it keep Mnemon's overlay.
8570
+ */
8571
+ pluginManager() {
8572
+ const candidate = this.ctx.get("pluginManager");
8573
+ return typeof candidate?.setPluginEnabled === "function" ? candidate : void 0;
8785
8574
  }
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
- }
8575
+ async setEnabled(manager, entryId, enabled) {
8576
+ const result = await manager.setPluginEnabled(entryId, enabled);
8577
+ if (result.application !== "applied") throw new Error(`DSH could not ${enabled ? "enable" : "disable"} ${entryId}: ${result.error?.message ?? result.application}`);
8809
8578
  }
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;
8579
+ settingsRevision(namespace = this.settingsNamespace) {
8580
+ return this.settingsService.describe({ redactSecrets: true }).find((value) => value.ns === namespace)?.revision ?? 0;
8817
8581
  }
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;
8582
+ /** The plugin manager owns enablement unless moving the saved choices failed. */
8583
+ enablementOwner() {
8584
+ return this.enablementMoveError === void 0 ? this.pluginManager() : void 0;
9080
8585
  }
9081
8586
  async managed() {
9082
8587
  const loader = this.loader();
@@ -9168,7 +8673,7 @@ var MemoryPluginManagement = class {
9168
8673
  const pending = this.queue;
9169
8674
  const items = await this.managed();
9170
8675
  if (pending !== this.queue || this.restorePending) return this.catalog();
9171
- const values = items.map((item) => clone(item.value));
8676
+ const values = items.map((item) => clone$1(item.value));
9172
8677
  const snapshot = this.engine.contributionSnapshot();
9173
8678
  const managedIds = new Set(items.map((item) => item.entry.id));
9174
8679
  const entryIds = new Set([
@@ -9228,7 +8733,7 @@ var MemoryPluginManagement = class {
9228
8733
  revision: this.revision(items),
9229
8734
  writable: this.settingsService.writable && this.loader() !== void 0,
9230
8735
  entries: orderPlugins(values),
9231
- diagnostics: [...this.discoveryWarnings, ...this.restoreError === void 0 ? [] : [this.restoreError]]
8736
+ diagnostics: [...this.discoveryWarnings, ...[this.restoreError, this.enablementMoveError].filter((value) => value !== void 0)]
9232
8737
  };
9233
8738
  }
9234
8739
  choices(items, entries) {
@@ -9259,7 +8764,10 @@ var MemoryPluginManagement = class {
9259
8764
  entries: request.entries
9260
8765
  });
9261
8766
  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);
8767
+ for (const [entryId, value] of Object.entries(incoming.entries)) {
8768
+ if (!managedIds.has(entryId)) throw new Error("Memory plugin Entry is not managed by this Host: " + entryId);
8769
+ if (typeof value.enabled !== "boolean") throw new Error("Plugin enabled must be boolean");
8770
+ }
9263
8771
  const choices = this.choices(items, incoming.entries);
9264
8772
  for (const item of items) {
9265
8773
  const chosen = choices.get(item.entry.id);
@@ -9316,8 +8824,9 @@ var MemoryPluginManagement = class {
9316
8824
  }
9317
8825
  async evaluateSnapshot(snapshot, config, scope, strategyTypeId, signal) {
9318
8826
  signal?.throwIfAborted();
8827
+ const { strategyFallback: _fallback, ...options } = memoryGenerationOptions(config, scope.workspaceId);
9319
8828
  const generation = new MemoryCompositionGeneration(snapshot, {
9320
- ...memoryGenerationOptions(config, scope.workspaceId),
8829
+ ...options,
9321
8830
  strategyTypeId
9322
8831
  });
9323
8832
  try {
@@ -9339,14 +8848,24 @@ var MemoryPluginManagement = class {
9339
8848
  const items = await this.managed();
9340
8849
  const choices = this.validateRequest(items, request);
9341
8850
  const expectedSettingsRevision = this.settingsRevision();
8851
+ const manager = this.enablementOwner();
9342
8852
  const previous = preferences(this.settings.get());
8853
+ const merged = {
8854
+ ...previous.entries,
8855
+ ...request.entries
8856
+ };
9343
8857
  const next = preferences({
9344
8858
  strategyTypeId: request.strategyTypeId,
9345
- entries: {
9346
- ...previous.entries,
9347
- ...request.entries
9348
- }
8859
+ entries: manager === void 0 ? merged : configurationOnly(merged)
9349
8860
  });
8861
+ const toggles = manager === void 0 ? [] : items.flatMap((item) => {
8862
+ const enabled = choices.get(item.entry.id).enabled;
8863
+ return enabled === !item.entry.disabled ? [] : [{
8864
+ entryId: item.entry.id,
8865
+ enabled
8866
+ }];
8867
+ }).sort((left, right) => Number(right.enabled) - Number(left.enabled));
8868
+ const written = [];
9350
8869
  let committedRevision;
9351
8870
  await this.updateEntries(items, choices, {
9352
8871
  validate: () => this.evaluateSnapshot(this.engine.contributionSnapshot(), config, scope, request.strategyTypeId, signal),
@@ -9361,8 +8880,13 @@ var MemoryPluginManagement = class {
9361
8880
  value: next.entries
9362
8881
  }], expectedSettingsRevision);
9363
8882
  committedRevision = this.settingsRevision();
8883
+ for (const toggle of toggles) {
8884
+ await this.setEnabled(manager, toggle.entryId, toggle.enabled);
8885
+ written.push(toggle);
8886
+ }
9364
8887
  },
9365
8888
  compensate: async () => {
8889
+ for (const toggle of [...written].reverse()) await this.setEnabled(manager, toggle.entryId, !toggle.enabled);
9366
8890
  if (hash(preferences(this.settings.get())) !== hash(next)) throw new Error("Memory plugin settings changed before rollback; refresh and inspect the saved configuration.");
9367
8891
  await this.settingsService.mutate(this.settingsNamespace, [previous.strategyTypeId === void 0 ? {
9368
8892
  op: "unset",
@@ -9381,15 +8905,56 @@ var MemoryPluginManagement = class {
9381
8905
  this.restoreError = void 0;
9382
8906
  });
9383
8907
  }
8908
+ /** Move saved enablement into the profile patch once, keeping only configuration overlays. */
8909
+ async moveEnablement(manager, items) {
8910
+ const saved = this.settings.get().entries ?? {};
8911
+ const legacy = this.legacySettings.get().sources ?? {};
8912
+ const moves = items.flatMap((item) => {
8913
+ 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;
8914
+ return enabled === void 0 ? [] : [{
8915
+ entryId: item.entry.id,
8916
+ enabled
8917
+ }];
8918
+ }).sort((left, right) => Number(right.enabled) - Number(left.enabled));
8919
+ if (moves.length === 0 && Object.keys(legacy).length === 0) return false;
8920
+ const legacyNamespace = `mnemon-plugins${this.settingsNamespace.slice(11)}`;
8921
+ const revisions = {
8922
+ view: this.settingsRevision(),
8923
+ legacy: this.settingsRevision(legacyNamespace)
8924
+ };
8925
+ for (const move of moves) await this.setEnabled(manager, move.entryId, move.enabled);
8926
+ await this.settingsService.mutate(this.settingsNamespace, [{
8927
+ op: "set",
8928
+ path: ["entries"],
8929
+ value: configurationOnly(saved)
8930
+ }], revisions.view);
8931
+ if (Object.keys(legacy).length > 0) await this.settingsService.mutate(legacyNamespace, [{
8932
+ op: "set",
8933
+ path: ["sources"],
8934
+ value: {}
8935
+ }], revisions.legacy);
8936
+ return true;
8937
+ }
9384
8938
  async restore() {
9385
8939
  if (this.closed) return;
9386
- const items = await this.managed();
8940
+ let items = await this.managed();
8941
+ const available = this.enablementOwner();
8942
+ if (available !== void 0 && this.settingsService.writable) try {
8943
+ if (await this.moveEnablement(available, items)) items = await this.managed();
8944
+ } catch (error) {
8945
+ this.enablementMoveError = `Saved plugin choices could not move into the DSH profile and stay managed by Mnemon: ${error instanceof Error ? error.message : String(error)}`;
8946
+ }
8947
+ const manager = this.enablementOwner();
9387
8948
  const saved = this.settings.get().entries ?? {};
9388
8949
  const legacy = this.legacySettings.get().sources ?? {};
9389
8950
  const desired = Object.fromEntries(items.flatMap((item) => {
9390
8951
  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;
8952
+ const current = !item.entry.disabled;
8953
+ if (unified !== void 0) return [[item.entry.id, {
8954
+ enabled: manager === void 0 ? unified.enabled ?? current : current,
8955
+ config: unified.config
8956
+ }]];
8957
+ const oldSource = manager === void 0 && item.descriptor.roles.includes("source") ? legacy[item.entry.id] : void 0;
9393
8958
  return oldSource === void 0 ? [] : [[item.entry.id, {
9394
8959
  enabled: oldSource.enabled,
9395
8960
  config: item.value.config
@@ -9408,7 +8973,7 @@ var MemoryPluginManagement = class {
9408
8973
  const previous = items.map((item) => ({
9409
8974
  item,
9410
8975
  disabled: item.entry.options.disabled ?? null,
9411
- config: clone(item.entry.options.config ?? {}),
8976
+ config: clone$1(item.entry.options.config ?? {}),
9412
8977
  enabled: item.value.enabled,
9413
8978
  active: item.value.active
9414
8979
  }));
@@ -9429,7 +8994,7 @@ var MemoryPluginManagement = class {
9429
8994
  touched.add(item.entry.id);
9430
8995
  await item.entry.update(item.editor === void 0 ? { disabled: !desired.enabled } : {
9431
8996
  disabled: !desired.enabled,
9432
- config: clone(desired.config)
8997
+ config: clone$1(desired.config)
9433
8998
  });
9434
8999
  await item.entry.fiber?.await?.();
9435
9000
  const registered = contributionRoles(this.engine.contributionSnapshot(), item.entry.id).length > 0;
@@ -9511,7 +9076,7 @@ var MemoryPluginManagement = class {
9511
9076
  };
9512
9077
  //#endregion
9513
9078
  //#region src/host/view-rpc.ts
9514
- function object(value) {
9079
+ function object$2(value) {
9515
9080
  if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error("View request must be an object");
9516
9081
  return value;
9517
9082
  }
@@ -9528,7 +9093,7 @@ function createViewHandler(runtime, engine, management, access, lifecycle, insta
9528
9093
  "preview",
9529
9094
  "inspect-plugin"
9530
9095
  ].includes(endpoint) : !["apply", "install-plugin"].includes(endpoint)) throw new Error("View operation is not available on this channel");
9531
- const payload = object(input);
9096
+ const payload = object$2(input);
9532
9097
  if (endpoint === "inspect-plugin") {
9533
9098
  if (installation === void 0 || typeof payload.packageName !== "string") throw new Error("Plugin discovery is unavailable");
9534
9099
  return {
@@ -9590,9 +9155,9 @@ function createViewHandler(runtime, engine, management, access, lifecycle, insta
9590
9155
  };
9591
9156
  }
9592
9157
  if (endpoint !== "preview" && endpoint !== "apply") throw new Error("Unknown View operation");
9593
- const raw = object(payload.configuration);
9158
+ const raw = object$2(payload.configuration);
9594
9159
  if (typeof raw.expectedRevision !== "string" || typeof raw.strategyTypeId !== "string") throw new Error("View configuration requires a revision and Strategy id");
9595
- object(raw.entries);
9160
+ object$2(raw.entries);
9596
9161
  const request = raw;
9597
9162
  if (endpoint === "preview") return {
9598
9163
  ok: true,
@@ -9616,11 +9181,11 @@ function createViewHandler(runtime, engine, management, access, lifecycle, insta
9616
9181
  }
9617
9182
  };
9618
9183
  }
9619
- function registerViewRpc(connection, runtime, engine, management, lifecycle, authority, installation) {
9184
+ function registerViewRpc(connection, runtime, engine, management, lifecycle, installation) {
9620
9185
  const readHandler = createViewHandler(runtime, engine, management, "read", lifecycle, installation);
9621
9186
  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 });
9187
+ connection.rpc.handle(MNEMON_VIEW_CHANNEL, readHandler);
9188
+ connection.rpc.handle(MNEMON_VIEW_WRITE_CHANNEL, writeHandler);
9624
9189
  return {
9625
9190
  read: readHandler,
9626
9191
  write: writeHandler
@@ -9879,179 +9444,690 @@ var __esDecorate = function(ctor, descriptorIn, decorators, contextIn, initializ
9879
9444
  else descriptor[key] = _;
9880
9445
  }
9881
9446
  }
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: [] }
9447
+ if (target) Object.defineProperty(target, contextIn.name, descriptor);
9448
+ done = true;
9449
+ };
9450
+ function denied() {
9451
+ return {
9452
+ ok: false,
9453
+ error: {
9454
+ code: "bad-request",
9455
+ message: "remote Mnemon management requires remoteAccess: trusted-host",
9456
+ details: { issues: [] }
9457
+ }
9458
+ };
9459
+ }
9460
+ function unknownSettingsEndpoint(endpoint) {
9461
+ return {
9462
+ ok: false,
9463
+ error: {
9464
+ code: "bad-request",
9465
+ message: `unknown remote Mnemon settings endpoint: ${endpoint}`,
9466
+ details: { issues: [] }
9467
+ }
9468
+ };
9469
+ }
9470
+ /**
9471
+ * Project Mnemon's existing handlers through DSH API Gateway. The Gateway owns
9472
+ * `/api`, Host/Origin validation, browser pairing, and response envelopes;
9473
+ * this Service retains Mnemon's narrower management grant.
9474
+ */
9475
+ let MnemonRemoteService = (() => {
9476
+ let _classSuper = TypertRemoteService;
9477
+ let _instanceExtraInitializers = [];
9478
+ let _read_decorators;
9479
+ let _activation_decorators;
9480
+ let _write_decorators;
9481
+ let _pack_decorators;
9482
+ let _settings_decorators;
9483
+ let _view_decorators;
9484
+ let _viewWrite_decorators;
9485
+ return class MnemonRemoteService extends _classSuper {
9486
+ static {
9487
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
9488
+ _read_decorators = [Remote("read")];
9489
+ _activation_decorators = [Remote("activation")];
9490
+ _write_decorators = [Remote("write")];
9491
+ _pack_decorators = [Remote("pack")];
9492
+ _settings_decorators = [Remote("settings")];
9493
+ _view_decorators = [Remote("view")];
9494
+ _viewWrite_decorators = [Remote("viewWrite")];
9495
+ __esDecorate(this, null, _read_decorators, {
9496
+ kind: "method",
9497
+ name: "read",
9498
+ static: false,
9499
+ private: false,
9500
+ access: {
9501
+ has: (obj) => "read" in obj,
9502
+ get: (obj) => obj.read
9503
+ },
9504
+ metadata: _metadata
9505
+ }, null, _instanceExtraInitializers);
9506
+ __esDecorate(this, null, _activation_decorators, {
9507
+ kind: "method",
9508
+ name: "activation",
9509
+ static: false,
9510
+ private: false,
9511
+ access: {
9512
+ has: (obj) => "activation" in obj,
9513
+ get: (obj) => obj.activation
9514
+ },
9515
+ metadata: _metadata
9516
+ }, null, _instanceExtraInitializers);
9517
+ __esDecorate(this, null, _write_decorators, {
9518
+ kind: "method",
9519
+ name: "write",
9520
+ static: false,
9521
+ private: false,
9522
+ access: {
9523
+ has: (obj) => "write" in obj,
9524
+ get: (obj) => obj.write
9525
+ },
9526
+ metadata: _metadata
9527
+ }, null, _instanceExtraInitializers);
9528
+ __esDecorate(this, null, _pack_decorators, {
9529
+ kind: "method",
9530
+ name: "pack",
9531
+ static: false,
9532
+ private: false,
9533
+ access: {
9534
+ has: (obj) => "pack" in obj,
9535
+ get: (obj) => obj.pack
9536
+ },
9537
+ metadata: _metadata
9538
+ }, null, _instanceExtraInitializers);
9539
+ __esDecorate(this, null, _settings_decorators, {
9540
+ kind: "method",
9541
+ name: "settings",
9542
+ static: false,
9543
+ private: false,
9544
+ access: {
9545
+ has: (obj) => "settings" in obj,
9546
+ get: (obj) => obj.settings
9547
+ },
9548
+ metadata: _metadata
9549
+ }, null, _instanceExtraInitializers);
9550
+ __esDecorate(this, null, _view_decorators, {
9551
+ kind: "method",
9552
+ name: "view",
9553
+ static: false,
9554
+ private: false,
9555
+ access: {
9556
+ has: (obj) => "view" in obj,
9557
+ get: (obj) => obj.view
9558
+ },
9559
+ metadata: _metadata
9560
+ }, null, _instanceExtraInitializers);
9561
+ __esDecorate(this, null, _viewWrite_decorators, {
9562
+ kind: "method",
9563
+ name: "viewWrite",
9564
+ static: false,
9565
+ private: false,
9566
+ access: {
9567
+ has: (obj) => "viewWrite" in obj,
9568
+ get: (obj) => obj.viewWrite
9569
+ },
9570
+ metadata: _metadata
9571
+ }, null, _instanceExtraInitializers);
9572
+ if (_metadata) Object.defineProperty(this, Symbol.metadata, {
9573
+ enumerable: true,
9574
+ configurable: true,
9575
+ writable: true,
9576
+ value: _metadata
9577
+ });
9578
+ }
9579
+ handlers = __runInitializers(this, _instanceExtraInitializers);
9580
+ constructor(ctx, handlers) {
9581
+ super(ctx, "mnemonRemote", { namespace: MNEMON_REMOTE_NAMESPACE });
9582
+ this.handlers = handlers;
9583
+ }
9584
+ read(endpoint, payload, signal) {
9585
+ return this.handlers.read(endpoint, payload, signal);
9586
+ }
9587
+ activation(endpoint, payload, signal) {
9588
+ return this.handlers.activation(endpoint, payload, signal);
9589
+ }
9590
+ write(endpoint, payload, signal) {
9591
+ if (!this.handlers.management) return Promise.resolve(denied());
9592
+ return this.handlers.write(endpoint, payload, signal);
9593
+ }
9594
+ pack(endpoint, payload, signal) {
9595
+ if (!this.handlers.management) return Promise.resolve(denied());
9596
+ return this.handlers.pack(endpoint, payload, signal);
9597
+ }
9598
+ async settings(endpoint, payload, signal) {
9599
+ if (endpoint === "mutate" && !this.handlers.management) return denied();
9600
+ if (endpoint !== "get" && endpoint !== "mutate") return unknownSettingsEndpoint(endpoint);
9601
+ const response = await this.handlers.settings(endpoint, payload, signal);
9602
+ if (endpoint !== "get" || this.handlers.management || !response.ok || typeof response.value !== "object" || response.value === null || Array.isArray(response.value)) return response;
9603
+ return {
9604
+ ...response,
9605
+ value: {
9606
+ ...response.value,
9607
+ writable: false
9608
+ }
9609
+ };
9610
+ }
9611
+ view(endpoint, payload, signal) {
9612
+ return this.handlers.view(endpoint, payload, signal);
9613
+ }
9614
+ viewWrite(endpoint, payload, signal) {
9615
+ if (!this.handlers.management) return Promise.resolve(denied());
9616
+ return this.handlers.viewWrite(endpoint, payload, signal);
9617
+ }
9618
+ };
9619
+ })();
9620
+ //#endregion
9621
+ //#region src/host/live-config.ts
9622
+ function plain(value) {
9623
+ if (isVolatile(value)) return plain(value.get());
9624
+ if (Array.isArray(value)) return value.map(plain);
9625
+ if (value === null || typeof value !== "object") return value;
9626
+ const prototype = Object.getPrototypeOf(value);
9627
+ if (prototype !== Object.prototype && prototype !== null) return value;
9628
+ return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, plain(child)]));
9629
+ }
9630
+ /** Read a detached snapshot of the live profile configuration. */
9631
+ function plainHostConfig(value = {}) {
9632
+ return plain(value);
9633
+ }
9634
+ function liveConfig() {
9635
+ const schema = new z(Config.toJSON());
9636
+ schema.dict = Object.fromEntries(Object.entries(schema.dict ?? {}).map(([key, field]) => [key, key === "remoteAccess" ? field : field.volatile()]));
9637
+ const standard = schema["~standard"];
9638
+ Object.defineProperty(schema, "~standard", { value: {
9639
+ ...standard,
9640
+ validate(value) {
9641
+ const parsed = standard.validate(value);
9642
+ if ("then" in parsed) throw new TypeError("Mnemon configuration validation must be synchronous");
9643
+ if (parsed.issues) return parsed;
9644
+ try {
9645
+ resolveConfig(plainHostConfig(parsed.value));
9646
+ return parsed;
9647
+ } catch (error) {
9648
+ return { issues: [{ message: error instanceof Error ? error.message : String(error) }] };
9649
+ }
9650
+ }
9651
+ } });
9652
+ return schema;
9653
+ }
9654
+ /** Profile Config for live forms; transport authority retains normal remounts. */
9655
+ const LiveConfig = liveConfig();
9656
+ //#endregion
9657
+ //#region src/host/legacy-settings-import.ts
9658
+ const UNSAFE_KEYS = /* @__PURE__ */ new Set([
9659
+ "__proto__",
9660
+ "prototype",
9661
+ "constructor"
9662
+ ]);
9663
+ const ENTRY_ID = /^[a-zA-Z0-9][a-zA-Z0-9._:/-]{0,299}$/u;
9664
+ const STRATEGY_ID = /^[a-z][a-z0-9-]{0,127}$/u;
9665
+ const VIEW_NAMESPACE = /^mnemon-view(?:-[a-f0-9]{16})?$/u;
9666
+ const LEGACY_NAMESPACE = /^mnemon-plugins(?:-[a-f0-9]{16})?$/u;
9667
+ function object$1(value, label) {
9668
+ 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`);
9669
+ for (const key of Reflect.ownKeys(value)) {
9670
+ if (typeof key !== "string" || UNSAFE_KEYS.has(key)) throw new Error(`${label} contains an unsafe key`);
9671
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
9672
+ if (!descriptor.enumerable || !Object.hasOwn(descriptor, "value")) throw new Error(`${label} must contain only data properties`);
9673
+ }
9674
+ return value;
9675
+ }
9676
+ /** Copy JSON data without invoking getters, serializers or prototype methods. */
9677
+ function copy(value, label, ancestors = /* @__PURE__ */ new Set(), depth = 0) {
9678
+ if (value === null || typeof value === "string" || typeof value === "boolean") return value;
9679
+ if (typeof value === "number" && Number.isFinite(value)) return value;
9680
+ if (typeof value !== "object" || value === null) throw new Error(`${label} must contain only JSON data`);
9681
+ if (depth > 64 || ancestors.has(value)) throw new Error(`${label} contains cyclic or excessively nested data`);
9682
+ ancestors.add(value);
9683
+ try {
9684
+ if (Array.isArray(value)) {
9685
+ if (Object.getPrototypeOf(value) !== Array.prototype) throw new Error(`${label} must contain plain arrays`);
9686
+ if (Reflect.ownKeys(value).length !== value.length + 1) throw new Error(`${label} contains a sparse or extended array`);
9687
+ return Array.from({ length: value.length }, (_, index) => {
9688
+ const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
9689
+ if (!descriptor || !descriptor.enumerable || !Object.hasOwn(descriptor, "value")) throw new Error(`${label} must contain only data properties`);
9690
+ return copy(descriptor.value, label, ancestors, depth + 1);
9691
+ });
9692
+ }
9693
+ return Object.fromEntries(Object.entries(object$1(value, label)).map(([key, item]) => [key, copy(item, label, ancestors, depth + 1)]));
9694
+ } finally {
9695
+ ancestors.delete(value);
9696
+ }
9697
+ }
9698
+ function copiedObject(value, label) {
9699
+ object$1(value, label);
9700
+ return copy(value, label);
9701
+ }
9702
+ /** Inspect validated copies only; never evaluate expressions or invoke getters. */
9703
+ function containsExpression(value) {
9704
+ return value !== null && typeof value === "object" && (Object.hasOwn(value, "__jsExpr") || Object.values(value).some(containsExpression));
9705
+ }
9706
+ function fields(value, allowed, label) {
9707
+ if (Object.keys(value).some((key) => !allowed.includes(key))) throw new Error(`${label} contains unsupported fields`);
9708
+ }
9709
+ function interaction(value, label, allowed) {
9710
+ const result = copiedObject(value, label);
9711
+ fields(result, allowed, label);
9712
+ if (Object.values(result).some((item) => typeof item !== "boolean")) throw new Error(`${label} preferences must be boolean`);
9713
+ return result;
9714
+ }
9715
+ function entryId(value, label) {
9716
+ if (typeof value !== "string" || !ENTRY_ID.test(value) || UNSAFE_KEYS.has(value)) throw new Error(`${label} contains an invalid Entry id`);
9717
+ }
9718
+ function view(value, label) {
9719
+ const result = copiedObject(value, label);
9720
+ fields(result, ["strategyTypeId", "entries"], label);
9721
+ if (Object.hasOwn(result, "strategyTypeId") && (typeof result.strategyTypeId !== "string" || !STRATEGY_ID.test(result.strategyTypeId))) throw new Error(`${label} contains an invalid Strategy type id`);
9722
+ const entries = {};
9723
+ if (Object.hasOwn(result, "entries")) {
9724
+ const values = object$1(result.entries, `${label}.entries`);
9725
+ if (Object.keys(values).length > 64) throw new Error(`${label} contains too many Entries`);
9726
+ for (const [id, raw] of Object.entries(values)) {
9727
+ entryId(id, label);
9728
+ const item = copiedObject(raw, `${label}.entries`);
9729
+ fields(item, ["enabled", "config"], `${label}.entries`);
9730
+ if (Object.hasOwn(item, "enabled") && typeof item.enabled !== "boolean") throw new Error(`${label} Entry enabled must be boolean`);
9731
+ const config = Object.hasOwn(item, "config") ? copiedObject(item.config, `${label} Entry config`) : {};
9732
+ if (JSON.stringify(config).length > 65536) throw new Error(`${label} Entry config exceeds 64 KiB`);
9733
+ entries[id] = {
9734
+ ...typeof item.enabled === "boolean" ? { enabled: item.enabled } : {},
9735
+ config
9736
+ };
9737
+ }
9738
+ }
9739
+ return {
9740
+ ...typeof result.strategyTypeId === "string" ? { strategyTypeId: result.strategyTypeId } : {},
9741
+ ...Object.hasOwn(result, "entries") ? { entries } : {}
9742
+ };
9743
+ }
9744
+ function sources(value, label, sourceEntryIds, diagnostics) {
9745
+ const result = copiedObject(value, label);
9746
+ fields(result, ["sources"], label);
9747
+ const values = Object.hasOwn(result, "sources") ? object$1(result.sources, `${label}.sources`) : {};
9748
+ if (Object.keys(values).length > 64) throw new Error(`${label} contains too many Source Entries`);
9749
+ const entries = {};
9750
+ for (const [id, raw] of Object.entries(values)) {
9751
+ entryId(id, label);
9752
+ const item = copiedObject(raw, `${label}.sources`);
9753
+ fields(item, ["enabled"], `${label}.sources`);
9754
+ if (typeof item.enabled !== "boolean") throw new Error(`${label} Source enabled must be boolean`);
9755
+ if (!sourceEntryIds.has(id)) {
9756
+ diagnostics.push(`Skipped legacy Source preference for ${id}: the current profile does not declare it as a Source.`);
9757
+ continue;
9758
+ }
9759
+ entries[id] = {
9760
+ enabled: item.enabled,
9761
+ config: {}
9762
+ };
9763
+ }
9764
+ return entries;
9765
+ }
9766
+ function applyEntryOverrides(entries, overrides, sourceEntryIds, diagnostics) {
9767
+ if (!Array.isArray(overrides)) throw new Error("Explicit Entry overrides must be an array");
9768
+ const matching = /* @__PURE__ */ new Map();
9769
+ for (const raw of overrides) {
9770
+ const row = object$1(raw, "Explicit Entry override");
9771
+ entryId(row.id, "Explicit Entry override");
9772
+ if (!Object.hasOwn(entries, row.id)) continue;
9773
+ const previous = matching.get(row.id) ?? {};
9774
+ matching.set(row.id, {
9775
+ ...previous,
9776
+ ...row.disabled === void 0 ? {} : { disabled: row.disabled },
9777
+ ...row.config === void 0 ? {} : { config: row.config }
9778
+ });
9779
+ }
9780
+ for (const [id, row] of matching) {
9781
+ if (row.disabled !== void 0 && typeof row.disabled !== "boolean") {
9782
+ delete entries[id];
9783
+ diagnostics.push(`Skipped legacy View preference for ${id}: its explicit disabled value is not boolean.`);
9784
+ continue;
9785
+ }
9786
+ if (row.config !== void 0 && !sourceEntryIds.has(id)) {
9787
+ const config = copiedObject(row.config, "Explicit Entry config");
9788
+ if (containsExpression(config)) {
9789
+ delete entries[id];
9790
+ diagnostics.push(`Skipped legacy View preference for ${id}: its explicit config contains an expression.`);
9791
+ continue;
9792
+ }
9793
+ entries[id].config = config;
9794
+ }
9795
+ if (typeof row.disabled === "boolean") entries[id].enabled = !row.disabled;
9796
+ }
9797
+ }
9798
+ /**
9799
+ * Recover only the canonical root and the exact owning profile's old namespaces.
9800
+ * This does not read files, edit profile rows, validate plugin domains or commit.
9801
+ * Malformed relevant input rejects the complete plan without an import marker.
9802
+ */
9803
+ function planLegacySettingsImport(options) {
9804
+ const diagnostics = [];
9805
+ try {
9806
+ const rawCurrent = object$1(options.currentOverride, "Current config override");
9807
+ if (rawCurrent.legacySettingsImported === true) return {
9808
+ patch: {},
9809
+ diagnostics
9810
+ };
9811
+ if (options.entryId !== "mnemon") throw new Error("Legacy settings belong to the canonical mnemon Entry; another root Entry is ambiguous.");
9812
+ 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.");
9813
+ const sections = object$1(options.sections, "Legacy settings document");
9814
+ 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.");
9815
+ const relevant = [
9816
+ "mnemon",
9817
+ "mnemon-ui",
9818
+ options.viewNamespace,
9819
+ options.legacyNamespace
9820
+ ];
9821
+ if (!relevant.some((key) => Object.hasOwn(sections, key))) return {
9822
+ patch: {},
9823
+ diagnostics
9824
+ };
9825
+ 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.`);
9826
+ const current = copiedObject(rawCurrent, "Current config override");
9827
+ if (Object.hasOwn(current, "legacySettingsImported") && typeof current.legacySettingsImported !== "boolean") throw new Error("Current import marker must be boolean");
9828
+ const root = Object.hasOwn(sections, "mnemon") ? copiedObject(sections.mnemon, "mnemon") : {};
9829
+ if (Object.hasOwn(root, "memoryView") || Object.hasOwn(root, "legacySettingsImported")) throw new Error("Legacy mnemon contains reserved migration fields");
9830
+ const oldInteraction = Object.hasOwn(root, "conversationInteraction") ? interaction(root.conversationInteraction, "mnemon.conversationInteraction", [
9831
+ "toolviews",
9832
+ "turnBar",
9833
+ "saveAction"
9834
+ ]) : {};
9835
+ const oldUi = Object.hasOwn(sections, "mnemon-ui") ? interaction(sections["mnemon-ui"], "mnemon-ui", ["turnBar", "saveAction"]) : {};
9836
+ const currentInteraction = Object.hasOwn(current, "conversationInteraction") ? interaction(current.conversationInteraction, "Current conversationInteraction", [
9837
+ "toolviews",
9838
+ "turnBar",
9839
+ "saveAction"
9840
+ ]) : {};
9841
+ const patch = Object.fromEntries(Object.entries(root).filter(([key]) => key !== "conversationInteraction" && !Object.hasOwn(current, key)));
9842
+ const inheritedInteraction = {
9843
+ ...oldInteraction,
9844
+ ...oldUi
9845
+ };
9846
+ if (Object.keys(inheritedInteraction).some((key) => !Object.hasOwn(currentInteraction, key))) patch.conversationInteraction = {
9847
+ ...inheritedInteraction,
9848
+ ...currentInteraction
9849
+ };
9850
+ const currentView = Object.hasOwn(current, "memoryView") ? view(current.memoryView, "Current memoryView") : {};
9851
+ if (Object.hasOwn(sections, options.viewNamespace) || Object.hasOwn(sections, options.legacyNamespace)) {
9852
+ if (!Array.isArray(options.sourceEntryIds)) throw new Error("Source Entry ids must be an array");
9853
+ for (const id of options.sourceEntryIds) entryId(id, "Source Entry ids");
9854
+ const sourceEntryIds = new Set(options.sourceEntryIds);
9855
+ const oldSources = Object.hasOwn(sections, options.legacyNamespace) ? sources(sections[options.legacyNamespace], options.legacyNamespace, sourceEntryIds, diagnostics) : {};
9856
+ const oldView = Object.hasOwn(sections, options.viewNamespace) ? view(sections[options.viewNamespace], options.viewNamespace) : {};
9857
+ const entries = {
9858
+ ...oldSources,
9859
+ ...oldView.entries
9860
+ };
9861
+ applyEntryOverrides(entries, options.entryOverrides, sourceEntryIds, diagnostics);
9862
+ const migratedView = {
9863
+ ...oldView,
9864
+ entries,
9865
+ ...currentView
9866
+ };
9867
+ if (!Object.hasOwn(currentView, "entries") || !Object.hasOwn(currentView, "strategyTypeId") && oldView.strategyTypeId !== void 0) patch.memoryView = migratedView;
9868
+ }
9869
+ patch.legacySettingsImported = true;
9870
+ return {
9871
+ patch,
9872
+ diagnostics
9873
+ };
9874
+ } catch (error) {
9875
+ diagnostics.push(error instanceof Error ? error.message : "Legacy settings import could not be planned");
9876
+ return {
9877
+ patch: {},
9878
+ diagnostics
9879
+ };
9880
+ }
9881
+ }
9882
+ //#endregion
9883
+ //#region src/host/settings-service.ts
9884
+ const pendingNativeImports = /* @__PURE__ */ new WeakSet();
9885
+ const clone = (value) => structuredClone(value);
9886
+ const object = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
9887
+ function profileYaml(source) {
9888
+ const document = parseDocument(source, { customTags: [{
9889
+ tag: "tag:yaml.org,2002:js",
9890
+ resolve: (value) => ({ __jsExpr: value })
9891
+ }] });
9892
+ if (document.errors.length > 0 || document.warnings.length > 0) throw new Error("Invalid or unsupported legacy/profile YAML; retained backup was not changed");
9893
+ return document.toJS({ maxAliasCount: 100 });
9894
+ }
9895
+ /** A local facade preserves Mnemon's wire namespaces; DSH owns all persistence. */
9896
+ var ProfileMnemonSettings = class {
9897
+ ctx;
9898
+ input;
9899
+ registrations = /* @__PURE__ */ new Map();
9900
+ listeners = /* @__PURE__ */ new Set();
9901
+ revisions = /* @__PURE__ */ new Map();
9902
+ owner;
9903
+ forms;
9904
+ editor;
9905
+ closed = false;
9906
+ importing;
9907
+ nativeImportPending;
9908
+ constructor(ctx, input) {
9909
+ this.ctx = ctx;
9910
+ this.input = input;
9911
+ this.forms = ctx.settings;
9912
+ this.owner = ctx.fiber;
9913
+ this.editor = ctx.get("configEditor");
9914
+ const profile = ctx.get("profileContext");
9915
+ const root = ctx.root ?? ctx;
9916
+ if (profile?.home !== void 0 && existsSync(join(profile.home, "settings.yaml"))) pendingNativeImports.add(root);
9917
+ this.nativeImportPending = pendingNativeImports.has(root);
9918
+ const owner = this.owner;
9919
+ const validate = (candidate) => {
9920
+ const config = plainHostConfig(candidate);
9921
+ for (const registration of this.registrations.values()) {
9922
+ if (registration.legacy) continue;
9923
+ const value = this.resolve(registration, config);
9924
+ registration.validate?.(value);
9925
+ }
9926
+ };
9927
+ ctx.on("internal/config", function(_raw, next) {
9928
+ const candidate = next();
9929
+ if (this === owner) validate(candidate);
9930
+ return candidate;
9931
+ }, { prepend: true });
9932
+ ctx.on("loader/volatile-update", ((paths) => {
9933
+ if (this.closed) return;
9934
+ for (const registration of this.registrations.values()) {
9935
+ if (registration.legacy) continue;
9936
+ if (!paths.some((path) => registration.field === void 0 ? path.length === 0 || !["memoryView", "legacySettingsImported"].includes(path[0]) : path.length === 0 || path[0] === registration.field)) continue;
9937
+ const value = this.resolve(registration);
9938
+ for (const listener of this.listeners) try {
9939
+ listener(registration.namespace, value);
9940
+ } catch (error) {
9941
+ console.warn("dsh-mnemon: committed settings listener failed", error);
9942
+ }
9943
+ }
9944
+ }));
9945
+ ctx.effect(() => {
9946
+ const disposePresentation = owner === void 0 ? void 0 : this.forms.configure({ auto: false }, owner);
9947
+ return () => {
9948
+ this.closed = true;
9949
+ this.listeners.clear();
9950
+ this.revisions.clear();
9951
+ disposePresentation?.();
9952
+ };
9953
+ }, "dsh-mnemon: profile settings");
9954
+ }
9955
+ get writable() {
9956
+ const entry = this.owner?.entry;
9957
+ return !this.closed && this.forms.writable && entry !== void 0 && this.editor?.entries().includes(entry) === true;
9958
+ }
9959
+ onUpdated(listener) {
9960
+ this.listeners.add(listener);
9961
+ return () => {
9962
+ this.listeners.delete(listener);
9963
+ };
9964
+ }
9965
+ resolve(registration, config = plainHostConfig(this.owner?.config ?? this.input)) {
9966
+ const value = registration.legacy ? registration.base : registration.field === void 0 ? config : config[registration.field];
9967
+ return registration.schema(object(value));
9968
+ }
9969
+ register(namespace, schema, options) {
9970
+ if (this.registrations.has(namespace)) throw new Error(`Mnemon settings already registered: ${namespace}`);
9971
+ if (typeof schema !== "function") throw new Error("Mnemon settings require a schema");
9972
+ const field = namespace === "mnemon-ui" ? "conversationInteraction" : /^mnemon-view(?:-[a-f0-9]{16})?$/u.test(namespace) ? "memoryView" : void 0;
9973
+ const legacy = /^mnemon-plugins(?:-[a-f0-9]{16})?$/u.test(namespace);
9974
+ if (namespace !== "mnemon" && field === void 0 && !legacy) throw new Error("Unsupported Mnemon settings namespace");
9975
+ const registration = {
9976
+ namespace,
9977
+ ...field === void 0 ? {} : { field },
9978
+ legacy,
9979
+ schema,
9980
+ base: clone(options.base ?? {}),
9981
+ ...options.validate === void 0 ? {} : { validate: options.validate }
9982
+ };
9983
+ const value = this.resolve(registration);
9984
+ options.validate?.(value);
9985
+ this.registrations.set(namespace, registration);
9986
+ return { get: () => this.resolve(registration) };
9987
+ }
9988
+ nativeDescriptor() {
9989
+ if (this.closed || this.owner?.entry === void 0 || !this.editor?.entries().includes(this.owner.entry)) return void 0;
9990
+ const descriptor = this.forms.describe().find((item) => item.ns === this.owner?.entry?.options.id);
9991
+ if (descriptor !== void 0) {
9992
+ this.revisions.set(descriptor.revision, clone({
9993
+ ns: descriptor.ns,
9994
+ revision: descriptor.revision,
9995
+ applies: descriptor.applies,
9996
+ value: descriptor.value,
9997
+ base: descriptor.base,
9998
+ user: descriptor.user
9999
+ }));
10000
+ if (this.revisions.size > 64) this.revisions.delete(this.revisions.keys().next().value);
10001
+ }
10002
+ return descriptor;
10003
+ }
10004
+ unchanged(previous, current, registration, operations) {
10005
+ const project = (value) => {
10006
+ if (registration.field !== void 0) return object(value)[registration.field];
10007
+ const { memoryView: _view, legacySettingsImported: _imported, conversationInteraction, ...core } = object(value);
10008
+ return operations.some((operation) => operation.path[0] === "conversationInteraction") ? {
10009
+ ...core,
10010
+ conversationInteraction
10011
+ } : core;
10012
+ };
10013
+ const signature = (descriptor) => canonicalMemoryJson(JSON.parse(JSON.stringify([
10014
+ project(descriptor.value) ?? null,
10015
+ project(descriptor.base) ?? null,
10016
+ project(descriptor.user) ?? null
10017
+ ])));
10018
+ return signature(previous) === signature(current);
10019
+ }
10020
+ describe(options) {
10021
+ const raw = this.nativeDescriptor();
10022
+ if (raw === void 0) return [];
10023
+ const descriptor = options?.redactSecrets ? this.forms.describe(options).find((item) => item.ns === this.owner?.entry?.options.id) : raw;
10024
+ if (descriptor === void 0) return [];
10025
+ return [...this.registrations.values()].map((registration) => {
10026
+ const project = (value) => registration.legacy ? {} : registration.field === void 0 ? value : object(value)[registration.field];
10027
+ return {
10028
+ ns: registration.namespace,
10029
+ value: project(descriptor.value) ?? {},
10030
+ base: project(descriptor.base) ?? {},
10031
+ user: project(descriptor.user) ?? {},
10032
+ revision: descriptor.revision,
10033
+ applies: "live"
10034
+ };
10035
+ });
10036
+ }
10037
+ async mutate(namespace, operations, expectedRevision) {
10038
+ const registration = this.registrations.get(namespace);
10039
+ if (registration === void 0 || registration.legacy) throw new Error("Unsupported Mnemon settings namespace");
10040
+ if (!this.writable) throw new Error("DSH settings are read-only");
10041
+ const translated = operations.map((operation) => ({
10042
+ ...operation,
10043
+ path: [...registration.field === void 0 ? [] : [registration.field], ...operation.path]
10044
+ }));
10045
+ let revision = expectedRevision;
10046
+ const previous = revision === void 0 ? void 0 : this.revisions.get(revision);
10047
+ for (let attempt = 0;; attempt += 1) {
10048
+ if (!this.writable) throw new Error("DSH settings are read-only");
10049
+ try {
10050
+ await this.forms.mutate(this.owner.entry.options.id, translated, revision);
10051
+ return;
10052
+ } catch (error) {
10053
+ if (attempt >= 2 || previous === void 0 || typeof error !== "object" || error === null || !("code" in error) || error.code !== "SETTINGS_CONFLICT") throw error;
10054
+ if (!this.writable) throw new Error("DSH settings are read-only");
10055
+ const current = this.nativeDescriptor();
10056
+ if (current === void 0 || !this.unchanged(previous, current, registration, operations)) throw error;
10057
+ revision = current.revision;
10058
+ }
10059
+ }
10060
+ }
10061
+ /** Read only the retained backup; ConfigEditor preserves all other patch rows. */
10062
+ async importLegacy(sourceEntryIds) {
10063
+ if (this.importing !== void 0) return this.importing;
10064
+ this.importing = this.performImport(sourceEntryIds).finally(() => {
10065
+ this.importing = void 0;
10066
+ });
10067
+ return this.importing;
10068
+ }
10069
+ async performImport(sourceEntryIds) {
10070
+ const profile = this.ctx.get("profileContext");
10071
+ const entry = this.owner?.entry;
10072
+ if (!this.writable || profile?.home === void 0 || entry === void 0) return;
10073
+ if (this.nativeImportPending) {
10074
+ console.warn("dsh-mnemon: DSH is importing settings.yaml; restart once to recover retained Mnemon UI and plugin preferences");
10075
+ return;
9892
10076
  }
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: [] }
10077
+ if (plainHostConfig(this.owner?.config ?? this.input).legacySettingsImported) return;
10078
+ const backup = join(profile.home, "settings.yaml.imported");
10079
+ let text;
10080
+ try {
10081
+ if ((await stat(backup)).size > 1048576) throw new Error("Legacy settings backup exceeds the import limit");
10082
+ text = await readFile(backup, "utf8");
10083
+ } catch (error) {
10084
+ if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") return;
10085
+ throw error;
9902
10086
  }
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
10087
+ const sections = profileYaml(text);
10088
+ const viewNamespace = [...this.registrations.keys()].find((namespace) => namespace.startsWith("mnemon-view"));
10089
+ const legacyNamespace = [...this.registrations.keys()].find((namespace) => namespace.startsWith("mnemon-plugins"));
10090
+ const plan = () => {
10091
+ const configuration = this.editor.configuration().find((item) => item.entry === entry);
10092
+ if (configuration === void 0) throw new Error("Mnemon profile entry is no longer editable");
10093
+ const document = profileYaml(readFileSync(this.editor.documentPath, "utf8"));
10094
+ const entryOverrides = Array.isArray(document) ? document.flatMap((value) => {
10095
+ const row = object(value);
10096
+ if (typeof row.id !== "string") return [];
10097
+ const entries = this.editor.entries().filter((candidate) => candidate.options.id === row.id);
10098
+ if (entries.length > 1) throw new Error("Legacy settings import requires unambiguous profile Entry ids");
10099
+ return [{
10100
+ id: entries[0]?.id ?? row.id,
10101
+ ..."disabled" in row ? { disabled: row.disabled } : {},
10102
+ ..."config" in row ? { config: row.config } : {}
10103
+ }];
10104
+ }) : [];
10105
+ return planLegacySettingsImport({
10106
+ entryId: entry.options.id,
10107
+ viewNamespace,
10108
+ legacyNamespace,
10109
+ sections,
10110
+ currentOverride: configuration.override,
10111
+ entryOverrides,
10112
+ sourceEntryIds
10012
10113
  });
10114
+ };
10115
+ const preview = plan();
10116
+ if (Object.keys(preview.patch).length === 0) {
10117
+ if (preview.diagnostics.length > 0) console.warn("dsh-mnemon: legacy settings were not imported; retained backup requires review");
10118
+ return;
10013
10119
  }
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;
10120
+ if (this.closed) return;
10121
+ await this.editor.edit(entry, (current) => {
10122
+ if (this.closed) throw new Error("Mnemon settings are disposed");
10123
+ const next = plan();
10038
10124
  return {
10039
- ...response,
10040
- value: {
10041
- ...response.value,
10042
- writable: false
10043
- }
10125
+ ...current,
10126
+ ...next.patch
10044
10127
  };
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
- })();
10128
+ });
10129
+ }
10130
+ };
10055
10131
  //#endregion
10056
10132
  //#region src/host/plugin.ts
10057
10133
  const name = "dsh-mnemon";
@@ -10075,54 +10151,26 @@ function optionalWorkspaceRegistry(ctx) {
10075
10151
  function apply(rawContext, rawConfig = {}) {
10076
10152
  const ctx = rawContext;
10077
10153
  const config = plainHostConfig(rawConfig);
10078
- const hostSettings = createHostSettings(ctx, rawConfig);
10154
+ const hostSettings = new ProfileMnemonSettings(ctx, rawConfig);
10079
10155
  registerMnemonSubagentTokenUsageProjection(ctx);
10080
10156
  const extensions = provideMemoryRuntime(ctx);
10081
10157
  const memoryPlugins = new MemoryPluginManagement(ctx, extensions, hostSettings);
10082
10158
  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
- };
10159
+ const effectiveConfig = (value) => memoryPlugins.resolveConfig(resolveConfig(value), value.memoryView ?? { entries: {} });
10089
10160
  const settings = hostSettings.register("mnemon", Config, {
10090
10161
  base: config,
10091
10162
  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
- }
10163
+ validate: (value) => createRuntimeGraph(effectiveConfig(value), void 0, extensions).dispose()
10109
10164
  });
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);
10165
+ const runtime = new LiveMnemonRuntime(createRuntimeGraph(effectiveConfig(settings.get()), void 0, extensions), optionalWorkspaceRegistry(ctx), ctx.agents, extensions);
10114
10166
  const resolved = runtime.config;
10115
- ctx.effect(() => subscribeSettings(ctx, hostSettings, (namespace, value) => {
10116
- const next = value;
10167
+ ctx.effect(() => hostSettings.onUpdated((namespace, value) => {
10117
10168
  if (namespace === memoryPlugins.settingsNamespace) {
10118
10169
  runtime.swap(createRuntimeGraph(effectiveConfig(settings.get()), void 0, extensions));
10119
10170
  return;
10120
10171
  }
10121
10172
  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));
10173
+ runtime.swap(createRuntimeGraph(effectiveConfig(value), void 0, extensions));
10126
10174
  }), "dsh-mnemon: live runtime settings");
10127
10175
  ctx.effect(() => memoryPlugins.start(), "dsh-mnemon: plugin graph settings");
10128
10176
  hostSettings.register("mnemon-ui", InteractionConfig, {
@@ -10146,7 +10194,7 @@ function apply(rawContext, rawConfig = {}) {
10146
10194
  unsubscribe();
10147
10195
  };
10148
10196
  }, "dsh-mnemon: canonical displayMode migration");
10149
- if (hostSettings instanceof ProfileMnemonSettings) ctx.effect(() => {
10197
+ ctx.effect(() => {
10150
10198
  let disposed = false;
10151
10199
  const loader = ctx.get("loader");
10152
10200
  Promise.resolve(loader?.await?.()).then(async () => {
@@ -10178,25 +10226,24 @@ function apply(rawContext, rawConfig = {}) {
10178
10226
  return async () => {
10179
10227
  stop();
10180
10228
  await coordinator.dispose();
10181
- disposePrepared();
10182
10229
  runtime.dispose();
10183
10230
  };
10184
10231
  }, "dsh-mnemon.lifecycle-root()");
10185
10232
  registerTools(ctx, runtime, coordinator);
10186
10233
  registerCommands(ctx.commands, runtime, coordinator);
10187
10234
  registerGuidance(ctx, resolved);
10188
- ctx.inject(["connection"], (webContext) => {
10189
- 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);
10235
+ ctx.inject(["connection", "webServer"], (webContext) => {
10236
+ const connection = Context.is(webContext) ? webContext.extend({ webServer: webContext.get("webServer") }).connection : webContext.connection;
10237
+ if (connection === void 0) return;
10238
+ const rpc = registerRpc(connection, runtime, lifecycle);
10239
+ const settings = registerSettingsRpc(connection, hostSettings);
10240
+ const view = registerViewRpc(connection, runtime, extensions, memoryPlugins, lifecycle, pluginInstallation);
10194
10241
  if (Context.is(webContext)) new MnemonRemoteService(webContext, {
10195
10242
  ...rpc,
10196
10243
  settings,
10197
10244
  view: view.read,
10198
10245
  viewWrite: view.write,
10199
- management: managementAuthority === "trusted-host"
10246
+ management: resolved.remoteAccess === "trusted-host"
10200
10247
  });
10201
10248
  });
10202
10249
  }