dsh-mnemon 0.5.1 → 0.5.3

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,10 @@
1
- import { t as withMemoryStorageLock } from "./storage-lock-Bqrhkyag.js";
1
+ import { i as withMemoryStorageLock, n as memoryStrategyConfigurationValues, r as readMemoryStrategyConfiguration } from "./strategy-configuration-B_DsZRQG.js";
2
2
  import { a as prepareMemoryContributions, i as validateMemoryPluginGraph, n as MemoryCompositionGeneration, r as captureMemoryContributionSnapshot, t as provideMemoryRuntime } from "./runtime-AtIOTeP1.js";
3
3
  import { DEFAULT_MEMORY_VIEW_BUDGET, MEMORY_CAPABILITIES } from "./contracts.js";
4
4
  import { r as defineMemoryPlugin, t as canonicalMemoryJson } from "./definitions-j0FLgE0-.js";
5
5
  import z from "schemastery";
6
6
  import { basename, delimiter, dirname, isAbsolute, join, normalize, resolve } from "node:path";
7
+ import { resolveEmbedding, resolvePersistenceStrategy, resolveRecallQuality } from "dsh-mnemon-source-memory-spaces";
7
8
  import { createHash, randomUUID } from "node:crypto";
8
9
  import { accessSync, closeSync, constants, existsSync, fstatSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
9
10
  import { homedir } from "node:os";
@@ -11,6 +12,7 @@ import { strToU8, unzipSync, zipSync } from "fflate";
11
12
  import { DOCUMENTS_ACTIVE_LIMIT_BYTES, DOCUMENTS_VERSION } from "dsh-mnemon-source-documents/contracts";
12
13
  import { RUNTIME_ENTRY_DELIMITER, RUNTIME_MEMORY_LIMITS, RUNTIME_MEMORY_VERSION } from "dsh-mnemon-source-runtime/contracts";
13
14
  import { fileURLToPath } from "node:url";
15
+ import { findMnemonCommand, mnemonNpmLauncher } from "dsh-mnemon-source-memory-spaces/native-cli";
14
16
  import { spawn } from "node:child_process";
15
17
  import { z as z$1 } from "zod";
16
18
  //#region src/host/display-mode.ts
@@ -73,9 +75,13 @@ const MAX_RUNTIME_MEMORY_LIMIT_BYTES = 1048576;
73
75
  const DEFAULT_RUNTIME_MAINTENANCE_MAX_TOKENS = 8192;
74
76
  const MAX_RUNTIME_MAINTENANCE_MAX_TOKENS = 1e6;
75
77
  const DEFAULT_TIMEOUT_MS = 1e4;
76
- const DEFAULT_RECALL_QUALITY_POLICY = "strict-v1";
77
- const DEFAULT_RECALL_LOW_SCORE_THRESHOLD = .25;
78
- const DEFAULT_RECALL_HIGH_SCORE_THRESHOLD = .6;
78
+ const recallDefaults = resolveRecallQuality(void 0);
79
+ const DEFAULT_RECALL_QUALITY_POLICY = recallDefaults.policy;
80
+ const DEFAULT_RECALL_LOW_SCORE_THRESHOLD = recallDefaults.lowScoreThreshold;
81
+ const DEFAULT_RECALL_HIGH_SCORE_THRESHOLD = recallDefaults.highScoreThreshold;
82
+ const DEFAULT_RECALL_CANDIDATE_MULTIPLIER = recallDefaults.candidateMultiplier;
83
+ const DEFAULT_RECALL_MAX_MEDIUM_RESULTS = recallDefaults.maxMediumResults;
84
+ const DEFAULT_RECALL_MAX_UNKNOWN_RESULTS = recallDefaults.maxUnknownResults;
79
85
  //#endregion
80
86
  //#region src/host/config.ts
81
87
  const InteractionConfig = z.object({
@@ -127,9 +133,9 @@ const RecallQualitySchema = z.object({
127
133
  policy: z.string().default(DEFAULT_RECALL_QUALITY_POLICY),
128
134
  lowScoreThreshold: z.number().min(0).max(1).default(DEFAULT_RECALL_LOW_SCORE_THRESHOLD),
129
135
  highScoreThreshold: z.number().min(0).max(1).default(DEFAULT_RECALL_HIGH_SCORE_THRESHOLD),
130
- candidateMultiplier: z.number().step(1).min(1).max(5).default(3),
131
- maxMediumResults: z.number().step(1).min(0).max(50).default(4),
132
- maxUnknownResults: z.number().step(1).min(0).max(50).default(2)
136
+ candidateMultiplier: z.number().step(1).min(1).max(5).default(DEFAULT_RECALL_CANDIDATE_MULTIPLIER),
137
+ maxMediumResults: z.number().step(1).min(0).max(50).default(DEFAULT_RECALL_MAX_MEDIUM_RESULTS),
138
+ maxUnknownResults: z.number().step(1).min(0).max(50).default(DEFAULT_RECALL_MAX_UNKNOWN_RESULTS)
133
139
  });
134
140
  const RuntimeMemorySchema = z.object({
135
141
  memoryLimitBytes: z.number().step(1).min(1).max(MAX_RUNTIME_MEMORY_LIMIT_BYTES).default(DEFAULT_RUNTIME_MEMORY_LIMIT_BYTES),
@@ -191,9 +197,9 @@ const Config = z.object({
191
197
  policy: DEFAULT_RECALL_QUALITY_POLICY,
192
198
  lowScoreThreshold: DEFAULT_RECALL_LOW_SCORE_THRESHOLD,
193
199
  highScoreThreshold: DEFAULT_RECALL_HIGH_SCORE_THRESHOLD,
194
- candidateMultiplier: 3,
195
- maxMediumResults: 4,
196
- maxUnknownResults: 2
200
+ candidateMultiplier: DEFAULT_RECALL_CANDIDATE_MULTIPLIER,
201
+ maxMediumResults: DEFAULT_RECALL_MAX_MEDIUM_RESULTS,
202
+ maxUnknownResults: DEFAULT_RECALL_MAX_UNKNOWN_RESULTS
197
203
  }),
198
204
  routingGuidance: z.boolean().default(true),
199
205
  displayMode: z.union([
@@ -335,106 +341,6 @@ function resolveMemoryTopology(value) {
335
341
  layers
336
342
  };
337
343
  }
338
- const MEMORY_PROVIDER_ID = /^[a-z][a-z0-9-]{0,127}$/u;
339
- const MEMORY_PLACEMENT_CAPABILITY_SET = /* @__PURE__ */ new Set([
340
- "graph",
341
- "entities",
342
- "related",
343
- "exact-write",
344
- "link",
345
- "forget"
346
- ]);
347
- const MEMORY_PLACEMENT_PREFERENCE_SET = /* @__PURE__ */ new Set([
348
- "balanced",
349
- "local-first",
350
- "shared-first"
351
- ]);
352
- function resolvePersistenceStrategy(value) {
353
- const mode = value?.mode ?? "manual";
354
- if (mode !== "manual" && mode !== "automatic") throw new Error(`dsh-mnemon: unsupported persistence strategy mode: ${String(mode)}`);
355
- const providerId = value?.providerId ?? "mnemon-native";
356
- if (!MEMORY_PROVIDER_ID.test(providerId)) throw new Error(`dsh-mnemon: invalid persistence strategy provider: ${String(providerId)}`);
357
- const prompt = value?.prompt?.trim() ?? "";
358
- if (prompt.length > 4e3) throw new Error("dsh-mnemon: persistence strategy prompt is too long (max 4000 characters)");
359
- const configuredProviderIds = value?.rules?.allowedProviderIds;
360
- const allowedProviderIds = [...new Set(configuredProviderIds === void 0 || configuredProviderIds.length === 0 && mode === "manual" ? ["mnemon-native"] : configuredProviderIds)];
361
- if (allowedProviderIds.length === 0) throw new Error("dsh-mnemon: persistence strategy requires at least one allowed provider");
362
- for (const id of allowedProviderIds) if (!MEMORY_PROVIDER_ID.test(id)) throw new Error(`dsh-mnemon: invalid persistence strategy provider: ${String(id)}`);
363
- const dataBoundary = value?.rules?.dataBoundary ?? "allow-remote";
364
- if (dataBoundary !== "allow-remote" && dataBoundary !== "local-only") throw new Error(`dsh-mnemon: unsupported persistence data boundary: ${String(dataBoundary)}`);
365
- const requiredCapabilities = [...new Set(value?.rules?.requiredCapabilities ?? [])];
366
- for (const capability of requiredCapabilities) if (!MEMORY_PLACEMENT_CAPABILITY_SET.has(capability)) throw new Error(`dsh-mnemon: unsupported persistence capability: ${String(capability)}`);
367
- const preference = value?.rules?.preference ?? "balanced";
368
- if (!MEMORY_PLACEMENT_PREFERENCE_SET.has(preference)) throw new Error(`dsh-mnemon: unsupported persistence preference: ${String(preference)}`);
369
- const providerConnections = Object.fromEntries(Object.entries(value?.providerConnections ?? {}).flatMap(([id, connection]) => {
370
- if (!MEMORY_PROVIDER_ID.test(id) || connection === void 0) return [];
371
- return [[id, Object.fromEntries(Object.entries(connection).filter((entry) => [
372
- "string",
373
- "number",
374
- "boolean"
375
- ].includes(typeof entry[1])))]];
376
- }));
377
- return {
378
- mode,
379
- providerId,
380
- prompt,
381
- rules: {
382
- allowedProviderIds,
383
- dataBoundary,
384
- requiredCapabilities,
385
- preference
386
- },
387
- providerConnections
388
- };
389
- }
390
- function resolveEmbedding(value) {
391
- const endpoint = optionalText(value?.endpoint) ?? "http://localhost:11434";
392
- if (endpoint.length > 2048) throw new Error("dsh-mnemon: embedding endpoint is too long");
393
- let parsed;
394
- try {
395
- parsed = new URL(endpoint);
396
- } catch {
397
- throw new Error("dsh-mnemon: embedding endpoint must be an absolute HTTP or HTTPS URL");
398
- }
399
- if (!["http:", "https:"].includes(parsed.protocol) || parsed.username !== "" || parsed.password !== "" || endpoint.includes("?") || endpoint.includes("#")) throw new Error("dsh-mnemon: embedding endpoint must be an absolute HTTP or HTTPS URL without credentials, query, or fragment");
400
- const normalizedEndpoint = endpoint.replace(/\/+$/u, "");
401
- const model = optionalText(value?.model) ?? "nomic-embed-text";
402
- if (model.length > 200 || /[\u0000-\u001f\u007f]/u.test(model)) throw new Error("dsh-mnemon: embedding model must contain 1..200 characters without control characters");
403
- const apiKey = optionalText(value?.apiKey) ?? "";
404
- if (apiKey.length > 2048 || /[\u0000-\u001f\u007f]/u.test(apiKey)) throw new Error("dsh-mnemon: embedding API key must contain 0..2048 characters without control characters");
405
- const protocol = value?.protocol ?? "auto";
406
- if (!MNEMON_EMBEDDING_PROTOCOLS.includes(protocol)) throw new Error(`dsh-mnemon: unsupported embedding protocol: ${String(protocol)}`);
407
- return {
408
- enabled: value?.enabled === true,
409
- endpoint: normalizedEndpoint,
410
- model,
411
- apiKey,
412
- protocol
413
- };
414
- }
415
- function resolveRecallQuality(value) {
416
- const policy = optionalText(value?.policy) ?? "strict-v1";
417
- if (!/^[a-z][a-z0-9-]{0,63}$/u.test(policy)) throw new Error("dsh-mnemon: recall quality policy id must match [a-z][a-z0-9-]{0,63}");
418
- const lowScoreThreshold = value?.lowScoreThreshold ?? .25;
419
- const highScoreThreshold = value?.highScoreThreshold ?? .6;
420
- const candidateMultiplier = value?.candidateMultiplier ?? 3;
421
- const maxMediumResults = value?.maxMediumResults ?? 4;
422
- const maxUnknownResults = value?.maxUnknownResults ?? 2;
423
- if (!Number.isFinite(lowScoreThreshold) || lowScoreThreshold < 0 || lowScoreThreshold > 1) throw new Error("dsh-mnemon: recall low score threshold must be within 0..1");
424
- if (!Number.isFinite(highScoreThreshold) || highScoreThreshold < 0 || highScoreThreshold > 1) throw new Error("dsh-mnemon: recall high score threshold must be within 0..1");
425
- if (lowScoreThreshold >= highScoreThreshold) throw new Error("dsh-mnemon: recall low score threshold must be less than the high score threshold");
426
- if (!Number.isInteger(candidateMultiplier) || candidateMultiplier < 1 || candidateMultiplier > 5) throw new Error("dsh-mnemon: recall candidate multiplier must be an integer within 1..5");
427
- if (!Number.isInteger(maxMediumResults) || maxMediumResults < 0 || maxMediumResults > 50) throw new Error("dsh-mnemon: recall max medium results must be an integer within 0..50");
428
- if (!Number.isInteger(maxUnknownResults) || maxUnknownResults < 0 || maxUnknownResults > 50) throw new Error("dsh-mnemon: recall max unknown results must be an integer within 0..50");
429
- return {
430
- policy,
431
- lowScoreThreshold,
432
- highScoreThreshold,
433
- candidateMultiplier,
434
- maxMediumResults,
435
- maxUnknownResults
436
- };
437
- }
438
344
  function resolveConfig(config = {}) {
439
345
  const cliPath = optionalText(config.cliPath);
440
346
  const legacyDataDir = optionalText(config.dataDir);
@@ -1902,6 +1808,200 @@ function assertParticipation(config, sourceTypeId, capability, trigger) {
1902
1808
  if (!allowsParticipation(config, sourceTypeId, capability, trigger)) throw new Error(`Memory Source ${sourceTypeId} does not allow ${trigger} ${participationChannel(capability)} in the current configuration`);
1903
1809
  }
1904
1810
  //#endregion
1811
+ //#region src/host/memory-executions.ts
1812
+ /** One Host owner pairs each Core turn with its runtime binding. */
1813
+ var MemoryExecutions = class {
1814
+ runtime;
1815
+ slots = /* @__PURE__ */ new Map();
1816
+ pending = /* @__PURE__ */ new Set();
1817
+ closed = false;
1818
+ constructor(runtime) {
1819
+ this.runtime = runtime;
1820
+ }
1821
+ async turn(agent, turn, signal, delegation) {
1822
+ signal.throwIfAborted();
1823
+ const slot = this.open(agent, "turn", `${agent.id}:${turn}`, "agent.root-turn", delegation);
1824
+ const abort = () => slot.controller.abort(signal.reason);
1825
+ signal.addEventListener("abort", abort, { once: true });
1826
+ try {
1827
+ const execution = await slot.ready;
1828
+ signal.throwIfAborted();
1829
+ return {
1830
+ ...execution,
1831
+ release: () => {
1832
+ signal.removeEventListener("abort", abort);
1833
+ slot.close();
1834
+ }
1835
+ };
1836
+ } catch (error) {
1837
+ signal.removeEventListener("abort", abort);
1838
+ slot.close();
1839
+ throw error;
1840
+ }
1841
+ }
1842
+ async workflow(agent, operation, signal) {
1843
+ this.assertOpen();
1844
+ signal.throwIfAborted();
1845
+ let slot = this.slots.get(agent.id);
1846
+ if (slot?.closing) slot = void 0;
1847
+ if (slot === void 0) {
1848
+ const graph = this.runtime.forAgent(agent);
1849
+ const context = graph.composableTurns.activeTurn(agent.id);
1850
+ if (!this.slots.has(agent.id) && context !== void 0) return {
1851
+ graph,
1852
+ context,
1853
+ signal,
1854
+ release() {}
1855
+ };
1856
+ slot = this.open(agent, "workflow", "workflow:" + randomUUID(), "agent." + operation);
1857
+ }
1858
+ const current = slot;
1859
+ if (current.kind === "turn") return {
1860
+ ...await untilAborted(current.ready, signal),
1861
+ signal,
1862
+ release() {}
1863
+ };
1864
+ current.users += 1;
1865
+ let released = false;
1866
+ const release = () => {
1867
+ if (released) return;
1868
+ released = true;
1869
+ if (--current.users === 0) current.close();
1870
+ };
1871
+ try {
1872
+ return {
1873
+ ...await untilAborted(current.ready, signal),
1874
+ signal: AbortSignal.any([signal, current.controller.signal]),
1875
+ release
1876
+ };
1877
+ } catch (error) {
1878
+ release();
1879
+ throw error;
1880
+ }
1881
+ }
1882
+ retain(agent, delegation) {
1883
+ this.assertOpen();
1884
+ const releaseView = delegation.viewId === void 0 ? void 0 : delegation.graph.composableTurns.retainView(delegation.viewId);
1885
+ try {
1886
+ const releaseRuntime = this.runtime.bindAgentRuntime(agent.id, delegation.graph);
1887
+ return () => {
1888
+ try {
1889
+ releaseView?.();
1890
+ } finally {
1891
+ releaseRuntime();
1892
+ }
1893
+ };
1894
+ } catch (error) {
1895
+ releaseView?.();
1896
+ throw error;
1897
+ }
1898
+ }
1899
+ dispose() {
1900
+ this.closed = true;
1901
+ for (const slot of this.pending) {
1902
+ slot.controller.abort(/* @__PURE__ */ new Error("Mnemon runtime is disposed"));
1903
+ if (slot.kind === "turn") slot.close();
1904
+ }
1905
+ }
1906
+ open(agent, kind, turnId, purpose, delegation) {
1907
+ this.assertOpen();
1908
+ const previous = this.slots.get(agent.id);
1909
+ const done = deferred();
1910
+ const ready = deferred();
1911
+ let execution;
1912
+ const finish = () => {
1913
+ if (this.slots.get(agent.id) === slot) this.slots.delete(agent.id);
1914
+ this.pending.delete(slot);
1915
+ done.resolve();
1916
+ };
1917
+ const slot = {
1918
+ kind,
1919
+ users: 0,
1920
+ closing: false,
1921
+ controller: new AbortController(),
1922
+ done: done.promise,
1923
+ ready: ready.promise,
1924
+ close: () => {
1925
+ if (slot.closing) return;
1926
+ slot.closing = true;
1927
+ slot.controller.abort(/* @__PURE__ */ new Error("memory execution ended"));
1928
+ if (execution !== void 0) try {
1929
+ execution.release();
1930
+ } finally {
1931
+ finish();
1932
+ }
1933
+ }
1934
+ };
1935
+ this.slots.set(agent.id, slot);
1936
+ this.pending.add(slot);
1937
+ (async () => {
1938
+ await previous?.done;
1939
+ slot.controller.signal.throwIfAborted();
1940
+ this.assertOpen();
1941
+ const graph = delegation?.graph ?? this.runtime.forAgent(agent);
1942
+ const manager = graph.composableTurns;
1943
+ const scope = delegation === void 0 ? agentScope(agent, graph.config) : {
1944
+ ...delegation.scope,
1945
+ sessionId: agent.id,
1946
+ agentId: agent.id
1947
+ };
1948
+ const releaseRuntime = delegation === void 0 ? this.runtime.bindAgentRuntime(agent.id, graph) : void 0;
1949
+ let context;
1950
+ const release = () => {
1951
+ try {
1952
+ if (context !== void 0 && manager.turn(context.turnId) === context) manager.endTurn(context.turnId);
1953
+ } finally {
1954
+ releaseRuntime?.();
1955
+ }
1956
+ };
1957
+ try {
1958
+ context = delegation?.viewId === void 0 ? await manager.beginTurn(turnId, scope, purpose, slot.controller.signal) : manager.pinTurn(turnId, scope, delegation.viewId);
1959
+ slot.controller.signal.throwIfAborted();
1960
+ execution = {
1961
+ graph,
1962
+ context,
1963
+ release
1964
+ };
1965
+ return execution;
1966
+ } catch (error) {
1967
+ release();
1968
+ throw error;
1969
+ }
1970
+ })().then(ready.resolve, (error) => {
1971
+ finish();
1972
+ ready.reject(error);
1973
+ });
1974
+ slot.ready.catch(() => {});
1975
+ return slot;
1976
+ }
1977
+ assertOpen() {
1978
+ if (this.closed) throw new Error("Mnemon runtime is disposed");
1979
+ }
1980
+ };
1981
+ async function untilAborted(promise, signal) {
1982
+ signal.throwIfAborted();
1983
+ const aborted = deferred();
1984
+ const abort = () => aborted.reject(signal.reason);
1985
+ signal.addEventListener("abort", abort, { once: true });
1986
+ try {
1987
+ return await Promise.race([promise, aborted.promise]);
1988
+ } finally {
1989
+ signal.removeEventListener("abort", abort);
1990
+ }
1991
+ }
1992
+ function deferred() {
1993
+ let resolve;
1994
+ let reject;
1995
+ return {
1996
+ promise: new Promise((yes, no) => {
1997
+ resolve = yes;
1998
+ reject = no;
1999
+ }),
2000
+ resolve,
2001
+ reject
2002
+ };
2003
+ }
2004
+ //#endregion
1905
2005
  //#region src/host/runtime.ts
1906
2006
  function agentScope(agent, config) {
1907
2007
  const workspaceId = agent.session.header?.cwd?.trim();
@@ -2017,6 +2117,7 @@ var LiveMnemonRuntime = class {
2017
2117
  workspaceRegistry;
2018
2118
  agents;
2019
2119
  extensions;
2120
+ executions = new MemoryExecutions(this);
2020
2121
  current;
2021
2122
  workspaceGraphs = /* @__PURE__ */ new Map();
2022
2123
  agentGraphs = /* @__PURE__ */ new Map();
@@ -2067,6 +2168,7 @@ var LiveMnemonRuntime = class {
2067
2168
  }
2068
2169
  dispose() {
2069
2170
  if (this.closed) return;
2171
+ this.executions.dispose();
2070
2172
  this.closed = true;
2071
2173
  const graphs = /* @__PURE__ */ new Set([
2072
2174
  this.current,
@@ -3174,7 +3276,6 @@ var MnemonSubagentCoordinator = class {
3174
3276
  runtimeQueue = Promise.resolve();
3175
3277
  documentQueue = Promise.resolve();
3176
3278
  observedReads = /* @__PURE__ */ new WeakMap();
3177
- workflows = /* @__PURE__ */ new Map();
3178
3279
  constructor(subagents, runtimeSource, resultRuntime, taskAgentModelResolver, runtimeMaintenanceMaxTokensResolver) {
3179
3280
  this.subagents = subagents;
3180
3281
  this.runtimeSource = runtimeSource;
@@ -3216,10 +3317,10 @@ var MnemonSubagentCoordinator = class {
3216
3317
  signal.throwIfAborted();
3217
3318
  const graph = this.runtimeSource.forAgent(parent);
3218
3319
  if (required && graph.composableTurns.activeTurn(parent.id) === void 0) throw new Error("Recall requires the View pinned to the current turn");
3219
- const release = await this.acquireWorkflow(parent, graph, routeId);
3320
+ const execution = await this.runtimeSource.executions.workflow(parent, routeId, signal);
3220
3321
  try {
3221
- const turn = this.turnAuthority(parent, true).context;
3222
- const evidence = await graph.source(typeId, turn.scope).forTurn(turn).route(routeId, input, signal);
3322
+ const turn = execution.context;
3323
+ const evidence = await execution.graph.source(typeId, turn.scope).forTurn(turn).route(routeId, input, execution.signal);
3223
3324
  if (typeId === "memory-spaces" && ["recall", "related"].includes(routeId)) {
3224
3325
  const observed = this.observedReads.get(turn) ?? /* @__PURE__ */ new Set();
3225
3326
  if (!observed.has(evidence.id)) {
@@ -3233,7 +3334,7 @@ var MnemonSubagentCoordinator = class {
3233
3334
  this.counters.failures += 1;
3234
3335
  throw error;
3235
3336
  } finally {
3236
- release();
3337
+ execution.release();
3237
3338
  }
3238
3339
  }
3239
3340
  /** Bind a model read to the Source state pinned by its own executing turn. */
@@ -3697,10 +3798,13 @@ ${runtimeSnapshotContext("user", plan.entries)}`;
3697
3798
  let failure;
3698
3799
  let disposeResultTool;
3699
3800
  let disposeResultObserver;
3700
- const graph = this.runtimeSource.forAgent(parent);
3701
3801
  let releaseWorkflow;
3702
3802
  try {
3703
- if (tools.length > 0) releaseWorkflow = await this.acquireWorkflow(parent, graph, operation);
3803
+ if (tools.length > 0) {
3804
+ const execution = await this.runtimeSource.executions.workflow(parent, operation, signal);
3805
+ releaseWorkflow = execution.release;
3806
+ signal = execution.signal;
3807
+ }
3704
3808
  const observer = this.resultRuntime.on("tools/result", ((execution, result) => {
3705
3809
  if (execution.token !== void 0) {
3706
3810
  const entries = stagedReceipts.get(execution.token);
@@ -3846,43 +3950,6 @@ Completion protocol: call \`${resultToolName}\` exactly once with the final resu
3846
3950
  if (cleanupFailure !== void 0) throw cleanupFailure;
3847
3951
  }
3848
3952
  }
3849
- /** Concurrent maintenance children share one owned View until the last exits. */
3850
- async acquireWorkflow(parent, graph, operation) {
3851
- const scope = agentScope(parent, graph.config);
3852
- const key = scope.agentId;
3853
- let owned = this.workflows.get(key);
3854
- if (owned === void 0) {
3855
- if (graph.composableTurns.activeTurn(key) !== void 0) return () => {};
3856
- const turnId = "workflow:" + randomUUID();
3857
- const releaseRuntime = this.runtimeSource.bindAgentRuntime(parent.id, graph);
3858
- owned = {
3859
- users: 0,
3860
- ready: graph.composableTurns.beginTurn(turnId, scope, "agent." + operation),
3861
- release: () => {
3862
- graph.composableTurns.endTurn(turnId);
3863
- releaseRuntime();
3864
- }
3865
- };
3866
- this.workflows.set(key, owned);
3867
- }
3868
- owned.users += 1;
3869
- const current = owned;
3870
- let released = false;
3871
- const release = () => {
3872
- if (released) return;
3873
- released = true;
3874
- if (--current.users > 0) return;
3875
- if (this.workflows.get(key) === current) this.workflows.delete(key);
3876
- current.release();
3877
- };
3878
- try {
3879
- await current.ready;
3880
- } catch (error) {
3881
- release();
3882
- throw error;
3883
- }
3884
- return release;
3885
- }
3886
3953
  provider(preferred) {
3887
3954
  const names = this.subagents.list();
3888
3955
  const compatible = (name) => {
@@ -4322,15 +4389,6 @@ function inspectMemoryView(generation, view, state, turn, memoryText) {
4322
4389
  }
4323
4390
  //#endregion
4324
4391
  //#region src/host/agent-memory-turn.ts
4325
- function agentMemoryScope(agent, graph) {
4326
- const cwd = agent.session.header?.cwd?.trim();
4327
- return {
4328
- storage: graph.config.storageScope,
4329
- ...cwd === void 0 || cwd === "" ? {} : { workspaceId: resolve(cwd) },
4330
- sessionId: agent.id,
4331
- agentId: agent.id
4332
- };
4333
- }
4334
4392
  /** The durable log, not a parent session id, identifies the executing turn. */
4335
4393
  function openAgentTurn(agent) {
4336
4394
  let open;
@@ -4361,20 +4419,7 @@ var AgentMemoryTurn = class {
4361
4419
  this.runtime = runtime;
4362
4420
  this.delegation = delegation;
4363
4421
  if (delegation === void 0) return;
4364
- const releaseView = delegation.viewId === void 0 ? void 0 : delegation.graph.composableTurns.retainView(delegation.viewId);
4365
- try {
4366
- const releaseRuntime = runtime.bindAgentRuntime(agent.id, delegation.graph);
4367
- this.releaseDelegation = () => {
4368
- try {
4369
- releaseView?.();
4370
- } finally {
4371
- releaseRuntime();
4372
- }
4373
- };
4374
- } catch (error) {
4375
- releaseView?.();
4376
- throw error;
4377
- }
4422
+ this.releaseDelegation = runtime.executions.retain(agent, delegation);
4378
4423
  }
4379
4424
  get current() {
4380
4425
  return this.pinned;
@@ -4403,7 +4448,7 @@ var AgentMemoryTurn = class {
4403
4448
  const graph = this.runtime.forAgent(this.agent);
4404
4449
  return {
4405
4450
  graph,
4406
- scope: agentMemoryScope(this.agent, graph)
4451
+ scope: agentScope(this.agent, graph.config)
4407
4452
  };
4408
4453
  }
4409
4454
  async begin(turn, signal) {
@@ -4437,12 +4482,7 @@ var AgentMemoryTurn = class {
4437
4482
  this.pending = void 0;
4438
4483
  const pinned = this.pinned;
4439
4484
  this.pinned = void 0;
4440
- if (pinned === void 0) return;
4441
- try {
4442
- if (pinned.manager.turn(pinned.context.turnId) === pinned.context) pinned.manager.endTurn(pinned.context.turnId);
4443
- } finally {
4444
- pinned.releaseRuntime?.();
4445
- }
4485
+ pinned?.release();
4446
4486
  }
4447
4487
  dispose() {
4448
4488
  if (this.closed) return;
@@ -4455,33 +4495,22 @@ var AgentMemoryTurn = class {
4455
4495
  }
4456
4496
  }
4457
4497
  async pin(turn, generation, signal) {
4458
- const graph = this.delegation?.graph ?? this.runtime.forAgent(this.agent);
4459
- const manager = graph.composableTurns;
4460
- const scope = this.delegation === void 0 ? agentMemoryScope(this.agent, graph) : {
4461
- ...this.delegation.scope,
4462
- sessionId: this.agent.id,
4463
- agentId: this.agent.id
4464
- };
4465
- const turnId = `${this.agent.id}:${turn}`;
4466
- const context = this.delegation?.viewId === void 0 ? await manager.beginTurn(turnId, scope, "agent.root-turn", signal) : manager.pinTurn(turnId, scope, this.delegation.viewId);
4467
- let releaseRuntime;
4498
+ const execution = await this.runtime.executions.turn(this.agent, turn, signal, this.delegation);
4499
+ const { graph, context } = execution;
4468
4500
  try {
4469
4501
  if (this.closed || this.generation !== generation) throw new Error("memory turn ended during View preparation");
4470
4502
  signal?.throwIfAborted();
4471
- if (this.delegation === void 0) releaseRuntime = this.runtime.bindAgentRuntime(this.agent.id, graph);
4472
4503
  const inspection = inspectMemoryView(graph.memoryComposition.generation(context.view.runtimeGeneration), context.view, "active", turn, modelMemoryWake(graph, context).text);
4473
4504
  this.pinned = {
4474
4505
  turn,
4475
4506
  graph,
4476
- manager,
4477
4507
  context,
4478
- ...releaseRuntime === void 0 ? {} : { releaseRuntime }
4508
+ release: execution.release
4479
4509
  };
4480
4510
  this.lastInspection = inspection;
4481
4511
  this.lastWorkspace = context.scope.workspaceId === void 0 ? void 0 : resolve(context.scope.workspaceId);
4482
4512
  } catch (error) {
4483
- if (manager.turn(context.turnId) === context) manager.endTurn(context.turnId);
4484
- releaseRuntime?.();
4513
+ execution.release();
4485
4514
  throw error;
4486
4515
  }
4487
4516
  }
@@ -4619,7 +4648,6 @@ var MnemonAgentLifecycle = class {
4619
4648
  injectedMemoryText;
4620
4649
  idleReviewTimer;
4621
4650
  reviewController;
4622
- idleReviewRun;
4623
4651
  reviewRunning = false;
4624
4652
  lastReviewAt;
4625
4653
  lastReviewAction;
@@ -4779,7 +4807,7 @@ var MnemonAgentLifecycle = class {
4779
4807
  if (context.agent !== void 0 && context.agent.id !== this.agent.id) return next();
4780
4808
  const turn = openAgentTurn(this.agent);
4781
4809
  if (turn === void 0) return next();
4782
- await this.settleIdleReview();
4810
+ this.cancelIdleReview(true);
4783
4811
  if (context.signal?.aborted === true || openAgentTurn(this.agent) !== turn) return next();
4784
4812
  await this.memoryTurn?.begin(turn, context.signal);
4785
4813
  return applyMemoryViewGuidance(await next(), this.memoryTurn?.current?.context.view, this.config.routingGuidance);
@@ -4811,17 +4839,9 @@ var MnemonAgentLifecycle = class {
4811
4839
  if (this.config.memoryTopology.strategyId !== "default-three-tier") return;
4812
4840
  if (this.agent.status !== "idle") return;
4813
4841
  if (!hostSessionEvents(this.agent.session).some((event) => event.type === "turn/end" && eventTurn(event) === turn) || !this.reviewActivity().eligible || !this.reviewAdmitted(turn)) return;
4814
- this.startIdleReview();
4842
+ this.runIdleReview();
4815
4843
  }, this.config.idleReviewMs);
4816
4844
  }
4817
- startIdleReview() {
4818
- const run = this.runIdleReview();
4819
- this.idleReviewRun = run;
4820
- const clear = () => {
4821
- if (this.idleReviewRun === run) this.idleReviewRun = void 0;
4822
- };
4823
- run.then(clear, clear);
4824
- }
4825
4845
  async runIdleReview() {
4826
4846
  const controller = new AbortController();
4827
4847
  const triggeredScore = this.reviewActivity().score;
@@ -4851,10 +4871,6 @@ var MnemonAgentLifecycle = class {
4851
4871
  this.idleReviewTimer = void 0;
4852
4872
  if (abortRunning) this.reviewController?.abort();
4853
4873
  }
4854
- async settleIdleReview() {
4855
- this.cancelIdleReview(true);
4856
- await this.idleReviewRun;
4857
- }
4858
4874
  ensureTurnActivity(turn) {
4859
4875
  let activity = this.turnActivity.get(turn);
4860
4876
  if (activity === void 0) {
@@ -5352,7 +5368,7 @@ var MnemonLifecycle = class {
5352
5368
  const parentPin = graph.composableTurns.activeTurn(parent.id);
5353
5369
  delegation = {
5354
5370
  graph,
5355
- scope: parentPin?.scope ?? agentMemoryScope(parent, graph),
5371
+ scope: parentPin?.scope ?? agentScope(parent, graph.config),
5356
5372
  ...parentPin === void 0 ? {} : { viewId: parentPin.view.id }
5357
5373
  };
5358
5374
  }
@@ -5477,7 +5493,11 @@ const runProcess = (command, args, options) => new Promise((resolve, reject) =>
5477
5493
  //#endregion
5478
5494
  //#region src/host/version-updates.ts
5479
5495
  const DSH_MNEMON_PACKAGE = "dsh-mnemon";
5480
- const findMnemonCommand = (config) => config.cliPath ?? process.env.MNEMON_CLI_PATH ?? resolveExecutable("mnemon") ?? resolveExecutable("/opt/homebrew/bin/mnemon") ?? resolveExecutable(join(homedir(), ".local/bin/mnemon"));
5496
+ const MNEMON_NPM_PACKAGE = "@mnemon-dev/mnemon";
5497
+ const SUBPACKAGE = /^dsh-mnemon-(source|strategy|provider)-[a-z0-9][a-z0-9._-]*$/;
5498
+ function isVersionComponentId(value) {
5499
+ return typeof value === "string" && (value === "mnemon" || value === DSH_MNEMON_PACKAGE || SUBPACKAGE.test(value));
5500
+ }
5481
5501
  const MNEMON_MODULE = "github.com/mnemon-dev/mnemon";
5482
5502
  const PACKAGE_MANIFEST_PATH = [new URL("../package.json", import.meta.url), new URL("../../package.json", import.meta.url)].map((url) => fileURLToPath(url)).find((path) => manifest$1(path)?.name === DSH_MNEMON_PACKAGE) ?? fileURLToPath(new URL("../package.json", import.meta.url));
5483
5503
  const CHECK_TIMEOUT_MS = 1e4;
@@ -5597,14 +5617,8 @@ async function fetchNpmLatest(name, tag = "latest") {
5597
5617
  const version = body.version;
5598
5618
  return typeof version === "string" ? version : void 0;
5599
5619
  }
5600
- async function fetchMnemonLatest() {
5601
- const body = await fetchJson("https://api.github.com/repos/mnemon-dev/mnemon/releases/latest");
5602
- if (typeof body !== "object" || body === null) return void 0;
5603
- const tag = body.tag_name;
5604
- return typeof tag === "string" ? tag.replace(/^v/, "") : void 0;
5605
- }
5606
- function dependencySpec(profile) {
5607
- return profile?.dependencies?.[DSH_MNEMON_PACKAGE] ?? profile?.devDependencies?.[DSH_MNEMON_PACKAGE];
5620
+ function dependencySpec(profile, name = DSH_MNEMON_PACKAGE) {
5621
+ return profile?.dependencies?.[name] ?? profile?.devDependencies?.[name];
5608
5622
  }
5609
5623
  function isLinkSpec(spec) {
5610
5624
  return spec !== void 0 && /^(?:link|file|workspace):|^\.{1,2}(?:[/\\]|$)/.test(spec);
@@ -5620,6 +5634,7 @@ function profileFromAncestor(packageManifestPath) {
5620
5634
  const profile = manifest$1(join(directory, "package.json"));
5621
5635
  if (profile?.name?.startsWith("dsh-profile-") === true) {
5622
5636
  const spec = dependencySpec(profile);
5637
+ if (spec === void 0) return void 0;
5623
5638
  const linked = isLinkSpec(spec);
5624
5639
  return {
5625
5640
  mode: linked ? "link" : "npm",
@@ -5683,6 +5698,23 @@ function updateOutput(result) {
5683
5698
  const output = [result.stdout.trim(), result.stderr.trim()].filter(Boolean).join("\n").trim();
5684
5699
  return output === "" ? void 0 : output.slice(-4e3);
5685
5700
  }
5701
+ /** Follow public package links afresh after an update, without Node's resolution cache. */
5702
+ function packageAt(directory, name) {
5703
+ for (let depth = 0; depth < 16; depth++) {
5704
+ const path = join(directory, "node_modules", name, "package.json");
5705
+ if (manifest$1(path)?.name === name) return path;
5706
+ const parent = dirname(directory);
5707
+ if (parent === directory) break;
5708
+ directory = parent;
5709
+ }
5710
+ }
5711
+ function samePath(left, right) {
5712
+ try {
5713
+ return realpathSync(left) === realpathSync(right);
5714
+ } catch {
5715
+ return false;
5716
+ }
5717
+ }
5686
5718
  var VersionUpdateManager = class {
5687
5719
  dshMnemonVersion;
5688
5720
  packageManifestPath;
@@ -5692,6 +5724,8 @@ var VersionUpdateManager = class {
5692
5724
  executable;
5693
5725
  fetchNpmLatest;
5694
5726
  fetchMnemonLatest;
5727
+ pendingRestart = /* @__PURE__ */ new Set();
5728
+ updating = false;
5695
5729
  constructor(dependencies = {}) {
5696
5730
  this.packageManifestPath = dependencies.packageManifestPath ?? PACKAGE_MANIFEST_PATH;
5697
5731
  this.dshMnemonVersion = manifest$1(this.packageManifestPath)?.version ?? "0.0.0";
@@ -5700,39 +5734,87 @@ var VersionUpdateManager = class {
5700
5734
  this.processRunner = dependencies.processRunner ?? runProcess;
5701
5735
  this.executable = dependencies.resolveExecutable ?? resolveExecutable;
5702
5736
  this.fetchNpmLatest = dependencies.fetchNpmLatest ?? fetchNpmLatest;
5703
- this.fetchMnemonLatest = dependencies.fetchMnemonLatest ?? fetchMnemonLatest;
5737
+ this.fetchMnemonLatest = dependencies.fetchMnemonLatest ?? (() => this.fetchNpmLatest(MNEMON_NPM_PACKAGE));
5704
5738
  }
5705
5739
  get currentDshMnemonVersion() {
5706
5740
  return this.dshMnemonVersion;
5707
5741
  }
5708
- async latestDshMnemonVersion() {
5709
- const channel = parseSemver(this.currentDshMnemonVersion)?.prerelease[0];
5742
+ async latestPackageVersion(name, current) {
5743
+ const channel = current === void 0 ? void 0 : parseSemver(current)?.prerelease[0];
5710
5744
  const tags = channel !== void 0 && [
5711
5745
  "alpha",
5712
5746
  "beta",
5713
5747
  "rc"
5714
5748
  ].includes(channel) ? ["latest", channel] : ["latest"];
5715
5749
  return (await Promise.all(tags.map(async (tag) => {
5716
- const version = await settledWithin(this.fetchNpmLatest(DSH_MNEMON_PACKAGE, tag), void 0);
5750
+ const version = await settledWithin(this.fetchNpmLatest(name, tag), void 0);
5717
5751
  const parsed = version === void 0 ? void 0 : parseSemver(version);
5718
5752
  if (parsed === void 0) return void 0;
5719
5753
  if (parsed.prerelease.length > 0 && (tag === "latest" || parsed.prerelease[0] !== tag)) return void 0;
5720
5754
  return version;
5721
5755
  }))).filter((version) => version !== void 0).sort(compareVersions).at(-1);
5722
5756
  }
5757
+ latestDshMnemonVersion() {
5758
+ return this.latestPackageVersion(DSH_MNEMON_PACKAGE, this.currentDshMnemonVersion);
5759
+ }
5760
+ async latestMnemonVersion() {
5761
+ const version = await settledWithin(this.fetchMnemonLatest(), void 0);
5762
+ const parsed = version === void 0 ? void 0 : parseSemver(version);
5763
+ return parsed !== void 0 && parsed.prerelease.length === 0 ? version : void 0;
5764
+ }
5765
+ npmInvocation() {
5766
+ const npm = this.executable("npm");
5767
+ if (npm === void 0) return void 0;
5768
+ if (!/\.cmd$/i.test(npm)) return {
5769
+ command: npm,
5770
+ args: []
5771
+ };
5772
+ const cli = join(dirname(npm), "node_modules/npm/bin/npm-cli.js");
5773
+ return existsSync(cli) ? {
5774
+ command: process.execPath,
5775
+ args: [cli]
5776
+ } : void 0;
5777
+ }
5723
5778
  async inspectMnemon() {
5724
5779
  const configured = this.mnemonCliPath() ?? findMnemonCommand({});
5725
5780
  const command = configured === void 0 ? void 0 : this.executable(configured);
5726
5781
  if (command === void 0) return { install: { mode: "missing" } };
5782
+ const launcher = mnemonNpmLauncher(command);
5727
5783
  let current;
5728
5784
  try {
5729
- current = versionFrom((await resultOrThrow(this.processRunner, command, ["--version"], CHECK_TIMEOUT_MS)).stdout);
5785
+ current = versionFrom((await resultOrThrow(this.processRunner, launcher === void 0 ? command : process.execPath, launcher === void 0 ? ["--version"] : [launcher, "--version"], CHECK_TIMEOUT_MS)).stdout);
5730
5786
  } catch {
5731
5787
  return { install: {
5732
- mode: "manual",
5733
- command
5788
+ mode: launcher === void 0 ? "manual" : "npm",
5789
+ command,
5790
+ hint: "cli-unreadable"
5734
5791
  } };
5735
5792
  }
5793
+ if (launcher !== void 0) {
5794
+ const install = {
5795
+ mode: "npm",
5796
+ command,
5797
+ hint: "npm-unmanaged"
5798
+ };
5799
+ const npm = this.npmInvocation();
5800
+ if (npm === void 0) install.hint = "npm-missing";
5801
+ else try {
5802
+ const globalRoot = (await resultOrThrow(this.processRunner, npm.command, [
5803
+ ...npm.args,
5804
+ "root",
5805
+ "--global"
5806
+ ], CHECK_TIMEOUT_MS)).stdout.trim();
5807
+ if (isAbsolute(globalRoot) && samePath(dirname(dirname(launcher)), join(globalRoot, MNEMON_NPM_PACKAGE))) {
5808
+ install.hint = "npm";
5809
+ install.updateCommand = process.execPath;
5810
+ install.updateArgs = [launcher, "update"];
5811
+ }
5812
+ } catch {}
5813
+ return {
5814
+ install,
5815
+ ...current === void 0 ? {} : { current }
5816
+ };
5817
+ }
5736
5818
  let realCommand = command;
5737
5819
  try {
5738
5820
  realCommand = realpathSync(command);
@@ -5813,13 +5895,60 @@ var VersionUpdateManager = class {
5813
5895
  }
5814
5896
  };
5815
5897
  }
5898
+ subpackages(install) {
5899
+ const starterPath = install.mode === "npm" && install.profileDir !== void 0 ? packageAt(install.profileDir, DSH_MNEMON_PACKAGE) ?? this.packageManifestPath : this.packageManifestPath;
5900
+ const starter = manifest$1(starterPath);
5901
+ const profile = install.profileDir === void 0 ? void 0 : manifest$1(join(install.profileDir, "package.json"));
5902
+ return [.../* @__PURE__ */ new Set([
5903
+ ...Object.keys(starter?.dependencies ?? {}),
5904
+ ...Object.keys(profile?.dependencies ?? {}),
5905
+ ...Object.keys(profile?.devDependencies ?? {})
5906
+ ])].filter((name) => SUBPACKAGE.test(name)).sort().map((name) => {
5907
+ const directSpec = dependencySpec(profile, name);
5908
+ const managedBy = directSpec === void 0 ? "starter" : "profile";
5909
+ const path = packageAt(directSpec === void 0 ? dirname(starterPath) : install.profileDir, name);
5910
+ const value = path === void 0 ? void 0 : manifest$1(path);
5911
+ const linked = isLinkSpec(directSpec ?? starter?.dependencies?.[name]) || install.mode === "link" || path !== void 0 && !realpathSync(dirname(path)).replaceAll("\\", "/").includes("/node_modules/");
5912
+ const mode = linked ? "link" : path === void 0 ? "missing" : install.mode === "npm" ? "npm" : "manual";
5913
+ const supported = managedBy === "profile" && mode === "npm" && this.executable("pnpm") !== void 0;
5914
+ return {
5915
+ ...path === void 0 ? {} : { manifestPath: path },
5916
+ status: {
5917
+ id: name,
5918
+ name,
5919
+ kind: SUBPACKAGE.exec(name)[1],
5920
+ managedBy,
5921
+ ...starter?.dependencies?.[name] === void 0 ? {} : { expectedVersion: starter.dependencies[name] },
5922
+ ...value?.version === void 0 ? {} : { current: value.version },
5923
+ ...path === void 0 ? {} : { installPath: realpathSync(dirname(path)) },
5924
+ ...install.profileName === void 0 ? {} : { installProfile: install.profileName },
5925
+ installMode: mode,
5926
+ outdated: false,
5927
+ updateSupported: supported,
5928
+ updateHint: linked ? "link" : managedBy === "starter" ? "starter" : supported ? "pnpm" : mode === "npm" ? "pnpm-missing" : "manual",
5929
+ restartRequired: this.pendingRestart.has(name) || this.pendingRestart.has(DSH_MNEMON_PACKAGE)
5930
+ }
5931
+ };
5932
+ });
5933
+ }
5816
5934
  async check() {
5817
- const [mnemonLocal, mnemonLatest, dshLatest] = await Promise.all([
5818
- settledWithin(this.inspectMnemon(), { install: { mode: "missing" } }),
5819
- settledWithin(this.fetchMnemonLatest(), void 0),
5820
- settledWithin(this.latestDshMnemonVersion(), void 0)
5821
- ]);
5822
5935
  const dshInstall = inspectDshInstall(this.packageManifestPath, this.dshHome);
5936
+ const [mnemonLocal, mnemonLatest, dshLatest, packages] = await Promise.all([
5937
+ settledWithin(this.inspectMnemon(), { install: {
5938
+ mode: "manual",
5939
+ hint: "cli-unreadable"
5940
+ } }),
5941
+ this.latestMnemonVersion(),
5942
+ settledWithin(this.latestDshMnemonVersion(), void 0),
5943
+ Promise.all(this.subpackages(dshInstall).map(async ({ status }) => {
5944
+ const latest = await this.latestPackageVersion(status.id, status.current);
5945
+ return {
5946
+ ...status,
5947
+ ...latest === void 0 ? { checkError: "latest-unavailable" } : { latest },
5948
+ outdated: status.current !== void 0 && latest !== void 0 && compareVersions(status.current, latest) < 0
5949
+ };
5950
+ }))
5951
+ ]);
5823
5952
  const pnpm = this.executable("pnpm");
5824
5953
  const mnemonOutdated = mnemonLocal.current !== void 0 && mnemonLatest !== void 0 && compareVersions(mnemonLocal.current, mnemonLatest) < 0;
5825
5954
  const dshOutdated = dshLatest !== void 0 && compareVersions(this.currentDshMnemonVersion, dshLatest) < 0;
@@ -5836,7 +5965,7 @@ var VersionUpdateManager = class {
5836
5965
  outdated: mnemonOutdated,
5837
5966
  installMode: mnemonLocal.install.mode,
5838
5967
  updateSupported: mnemonSupported,
5839
- updateHint: mnemonLocal.install.mode === "homebrew" ? mnemonSupported ? "brew" : "brew-missing" : mnemonLocal.install.mode === "go" ? "go" : mnemonLocal.install.mode === "missing" ? "install" : "manual",
5968
+ updateHint: mnemonLocal.install.hint ?? (mnemonLocal.install.mode === "homebrew" ? mnemonSupported ? "brew" : "brew-missing" : mnemonLocal.install.mode === "go" ? "go" : mnemonLocal.install.mode === "missing" ? "install" : "manual"),
5840
5969
  ...mnemonLatest === void 0 ? { checkError: "latest-unavailable" } : {}
5841
5970
  }, {
5842
5971
  id: "dsh-mnemon",
@@ -5848,15 +5977,26 @@ var VersionUpdateManager = class {
5848
5977
  outdated: dshOutdated,
5849
5978
  installMode: dshInstall.mode,
5850
5979
  updateSupported: dshSupported,
5980
+ packages,
5981
+ restartRequired: this.pendingRestart.size > 0,
5851
5982
  updateHint: dshInstall.mode === "npm" ? dshSupported ? "pnpm" : "pnpm-missing" : dshInstall.mode === "link" ? "link" : "manual",
5852
5983
  ...dshLatest === void 0 ? { checkError: "latest-unavailable" } : {}
5853
5984
  }]
5854
5985
  };
5855
5986
  }
5856
5987
  async update(component) {
5988
+ if (this.updating) throw new Error("A version update is already in progress");
5989
+ this.updating = true;
5990
+ try {
5991
+ return await this.performUpdate(component);
5992
+ } finally {
5993
+ this.updating = false;
5994
+ }
5995
+ }
5996
+ async performUpdate(component) {
5857
5997
  if (component === "mnemon") {
5858
5998
  const before = await this.inspectMnemon();
5859
- const latest = await this.fetchMnemonLatest();
5999
+ const latest = await this.latestMnemonVersion();
5860
6000
  if (before.current === void 0) throw new Error("Mnemon CLI is unavailable");
5861
6001
  if (latest === void 0) throw new Error("Unable to verify the latest Mnemon release");
5862
6002
  if (compareVersions(before.current, latest) >= 0) return {
@@ -5880,29 +6020,32 @@ var VersionUpdateManager = class {
5880
6020
  ...outputText === void 0 ? {} : { output: outputText }
5881
6021
  };
5882
6022
  }
5883
- if (component !== "dsh-mnemon") throw new Error(`Unknown version component: ${String(component)}`);
5884
- const latest = await this.latestDshMnemonVersion();
6023
+ const install = inspectDshInstall(this.packageManifestPath, this.dshHome);
6024
+ const child = component === DSH_MNEMON_PACKAGE ? void 0 : this.subpackages(install).find((item) => item.status.id === component)?.status;
6025
+ if (component !== DSH_MNEMON_PACKAGE && child === void 0) throw new Error(`Unknown version component: ${String(component)}`);
6026
+ if (child !== void 0 && !child.updateSupported) throw new Error("This subpackage must be updated through its Starter or original installation method");
6027
+ const previousVersion = child?.current ?? this.currentDshMnemonVersion;
6028
+ const latest = await this.latestPackageVersion(component, previousVersion);
5885
6029
  if (latest === void 0) throw new Error("Unable to verify the latest dsh-mnemon release");
5886
- if (compareVersions(this.currentDshMnemonVersion, latest) >= 0) return {
6030
+ if (compareVersions(previousVersion, latest) >= 0) return {
5887
6031
  component,
5888
- previousVersion: this.currentDshMnemonVersion,
5889
- currentVersion: this.currentDshMnemonVersion,
6032
+ previousVersion,
6033
+ currentVersion: previousVersion,
5890
6034
  updated: false,
5891
- restartRequired: false
6035
+ restartRequired: this.pendingRestart.has(component)
5892
6036
  };
5893
- const install = inspectDshInstall(this.packageManifestPath, this.dshHome);
5894
6037
  const pnpm = this.executable("pnpm");
5895
6038
  if (install.mode !== "npm" || install.profileDir === void 0 || pnpm === void 0) throw new Error("This dsh-mnemon installation cannot be updated automatically");
5896
- const previousVersion = this.currentDshMnemonVersion;
5897
6039
  const output = await resultOrThrow(this.processRunner, pnpm, [
5898
6040
  "add",
5899
- `${DSH_MNEMON_PACKAGE}@${latest}`,
6041
+ `${component}@${latest}`,
5900
6042
  "--save-exact"
5901
6043
  ], UPDATE_TIMEOUT_MS, { cwd: install.profileDir });
5902
- const installedVersion = manifest$1(join(install.profileDir, "node_modules", DSH_MNEMON_PACKAGE, "package.json"))?.version;
5903
- if (installedVersion !== latest) throw new Error(`dsh-mnemon update did not install the requested version ${latest}; found ${installedVersion ?? "no package"}`);
6044
+ const installedVersion = manifest$1(join(install.profileDir, "node_modules", component, "package.json"))?.version;
6045
+ if (installedVersion !== latest) throw new Error(`${component} update did not install the requested version ${latest}; found ${installedVersion ?? "no package"}`);
5904
6046
  const outputText = updateOutput(output);
5905
- this.dshMnemonVersion = installedVersion;
6047
+ if (component === DSH_MNEMON_PACKAGE) this.dshMnemonVersion = installedVersion;
6048
+ this.pendingRestart.add(component);
5906
6049
  return {
5907
6050
  component,
5908
6051
  previousVersion,
@@ -6274,7 +6417,8 @@ function createWriteHandler(input, lifecycle, versions) {
6274
6417
  const payload = object$2(rawPayload);
6275
6418
  if (endpoint === "version-update") {
6276
6419
  if (versions === void 0) throw new Error("version updates are unavailable");
6277
- if (payload.component !== "mnemon" && payload.component !== "dsh-mnemon") return badRequest$1("unknown version component");
6420
+ if (!input.config.writeEnabled) throw new Error("dsh-mnemon is configured read-only (writeEnabled: false)");
6421
+ if (!isVersionComponentId(payload.component)) return badRequest$1("unknown version component");
6278
6422
  return success$1(await versions.update(payload.component));
6279
6423
  }
6280
6424
  const runtime = scoped(input, payload, lifecycle);
@@ -7464,39 +7608,6 @@ function legacyPreferences(value) {
7464
7608
  for (const [entryId, item] of Object.entries(sources)) if (!ENTRY_ID.test(entryId) || typeof item?.enabled !== "boolean") throw new Error("Invalid legacy Source Entry preference");
7465
7609
  return clone({ sources });
7466
7610
  }
7467
- function configuration(definition, input) {
7468
- const config = record(input ?? {});
7469
- const fields = new Map(definition.fields.map((field) => [field.key, field]));
7470
- for (const [key, value] of Object.entries(config)) {
7471
- const field = fields.get(key);
7472
- if (!field) throw new Error(`Plugin field is not declared for management: ${key}`);
7473
- if (field.input === "number") {
7474
- if (typeof value !== "number" || !Number.isFinite(value) || !Number.isInteger(value) || field.minimum !== void 0 && value < field.minimum || field.maximum !== void 0 && value > field.maximum) throw new Error(`Invalid numeric plugin field: ${key}`);
7475
- } else if (field.input === "source-list" || field.input === "string-list") {
7476
- if (!Array.isArray(value) || value.length > 32 || value.some((item) => typeof item !== "string" || !item.trim() || item.length > 500) || new Set(value).size !== value.length) throw new Error(`Invalid plugin list: ${key}`);
7477
- } else if (typeof value !== "string" || value.length > (field.maximum ?? 4e3)) throw new Error(`Invalid plugin text: ${key}`);
7478
- }
7479
- return config;
7480
- }
7481
- function validateEditor(definition) {
7482
- const text = (value) => typeof value === "object" && value !== null && ["en", "zh-CN"].every((key) => {
7483
- const item = value[key];
7484
- return typeof item === "string" && item.trim() !== "" && item.length <= 4e3;
7485
- });
7486
- if (definition.apiVersion !== "dsh-mnemon/strategy-configuration/v1" || typeof definition.create !== "function" || !["strategy", "strategy-extension"].includes(definition.kind) || !/^[a-z][a-z0-9-]{0,127}$/u.test(definition.typeId) || !Array.isArray(definition.fields) || definition.fields.length > 16 || new Set(definition.fields.map((field) => field.key)).size !== definition.fields.length || definition.fields.some((field) => !/^[a-zA-Z][a-zA-Z0-9]{0,99}$/u.test(field.key) || ![
7487
- "number",
7488
- "text",
7489
- "textarea",
7490
- "string-list",
7491
- "source-list"
7492
- ].includes(field.input) || !text(field.label) || field.description !== void 0 && !text(field.description) || [field.minimum, field.maximum].some((bound) => bound !== void 0 && (typeof bound !== "number" || !Number.isFinite(bound))) || field.sourceRoles !== void 0 && (!Array.isArray(field.sourceRoles) || field.sourceRoles.length > 32 || field.sourceRoles.some((role) => typeof role !== "string" || !role || role.length > 128))) || !text(definition.label) || !text(definition.description)) throw new Error("Invalid plugin configuration descriptor");
7493
- if (canonicalMemoryJson({
7494
- label: definition.label,
7495
- description: definition.description,
7496
- fields: definition.fields
7497
- }, "Plugin editor metadata").length > 65536) throw new Error("Plugin editor metadata exceeds 64 KiB");
7498
- for (const field of definition.fields) if (field.defaultValue !== void 0) configuration(definition, { [field.key]: field.defaultValue });
7499
- }
7500
7611
  function contributionRoles(snapshot, entryId) {
7501
7612
  const roles = /* @__PURE__ */ new Set();
7502
7613
  if (snapshot.sources.some((value) => value.provenance.entryId === entryId)) roles.add("source");
@@ -7531,9 +7642,8 @@ function fallbackDescriptor(entry, snapshot, editor) {
7531
7642
  }
7532
7643
  function prepared(item, config) {
7533
7644
  if (item.editor === void 0) throw new Error(`Plugin does not expose a pure configuration factory: ${item.entry.id}`);
7534
- const contribution = item.editor.create(configuration(item.editor, config));
7535
- const values = [...contribution.strategies ?? [], ...contribution.strategyExtensions ?? []];
7536
- if (values.length !== 1 || values[0].manifest.typeId !== item.editor.typeId || values[0].manifest.kind !== item.editor.kind || values[0].manifest.packageName !== item.entry.options.name) throw new Error("Plugin factory does not match its declared Entry");
7645
+ const contribution = item.editor.create(config);
7646
+ if ([...contribution.strategies ?? [], ...contribution.strategyExtensions ?? []][0].manifest.packageName !== item.entry.options.name) throw new Error("Plugin factory does not match its declared Entry");
7537
7647
  if (contribution.plugin !== void 0 && hash(contribution.plugin) !== hash(item.descriptor)) throw new Error("Plugin factory descriptor does not match its module export");
7538
7648
  return prepareMemoryContributions({
7539
7649
  ...contribution,
@@ -7671,8 +7781,7 @@ var MemoryPluginManagement = class {
7671
7781
  try {
7672
7782
  let module = entry.fiber?.runtime?.callback;
7673
7783
  if (!module?.memoryPlugin && !module?.memoryStrategyConfiguration) module = await entry.parent.tree.import(entry.options.name);
7674
- const editor = module?.memoryStrategyConfiguration;
7675
- if (editor !== void 0) validateEditor(editor);
7784
+ const editor = module?.memoryStrategyConfiguration === void 0 ? void 0 : readMemoryStrategyConfiguration(module.memoryStrategyConfiguration);
7676
7785
  const registeredDescriptor = snapshot.plugins?.find((plugin) => plugin.provenance.entryId === entry.id)?.descriptor;
7677
7786
  const descriptor = defineMemoryPlugin(module?.memoryPlugin ?? registeredDescriptor ?? fallbackDescriptor(entry, snapshot, editor));
7678
7787
  if (descriptor.packageName !== entry.options.name) throw new Error("Plugin descriptor package does not match its Loader Entry");
@@ -7701,7 +7810,7 @@ var MemoryPluginManagement = class {
7701
7810
  requires: [...descriptor.requires ?? []],
7702
7811
  requiredBy: [],
7703
7812
  fields: editor?.fields ?? [],
7704
- config: editor === void 0 ? {} : configuration(editor, entry.options.config),
7813
+ config: editor === void 0 ? {} : memoryStrategyConfigurationValues(editor, entry.options.config),
7705
7814
  enabled: !entry.disabled,
7706
7815
  active,
7707
7816
  writable: this.ctx.settings.writable && !ancestorDisabled && (entry.options.disabled === void 0 || typeof entry.options.disabled === "boolean"),