dsh-mnemon 0.5.21 → 0.5.23

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/bundle.js CHANGED
@@ -1,4 +1,4 @@
1
- import { t as prepareStarterResolution } from "./starter-resolution-BJPNaa9v.js";
1
+ import { t as prepareStarterResolution } from "./starter-resolution-B0Wj3X3R.js";
2
2
  //#region src/bundle.ts
3
3
  const groupKey = Symbol.for("cordis.group");
4
4
  const init = Symbol.for("cordis.init");
package/lib/client.js CHANGED
@@ -1854,7 +1854,7 @@ window.__ModuleLoader__.load({
1854
1854
  "versions.packagesOutdated": "{count} 个有新版本",
1855
1855
  "versions.packagesDetail": "展开查看维护方式",
1856
1856
  "versions.packagesHide": "收起子包详情",
1857
- "versions.packagesHint": "默认组合随主包更新;在当前 Profile 中独立安装的子包可单独更新。更新后重启 dsh web。",
1857
+ "versions.packagesHint": "默认组合随主包更新;在当前 Profile 中独立安装的子包可单独更新。更新后重启 DSH。",
1858
1858
  "versions.managedStarter": "随主包维护",
1859
1859
  "versions.managedProfile": "Profile 独立维护",
1860
1860
  "versions.starterVersion": "主包指定",
@@ -1880,7 +1880,11 @@ window.__ModuleLoader__.load({
1880
1880
  "versions.updating": "更新中…",
1881
1881
  "versions.updated": "{name} 已更新",
1882
1882
  "versions.alreadyCurrent": "当前已经是最新版本",
1883
- "versions.restartRequired": "请重启 dsh web,以加载新的 dsh-mnemon 插件代码。",
1883
+ "versions.restartRequired": "请重启 DSH 以加载新的 dsh-mnemon 插件代码;桌面版请完全退出后重新打开。",
1884
+ "versions.pendingInstalledTitle": "dsh-mnemon {version} 已安装",
1885
+ "versions.pendingInstalledDetail": "当前运行的仍是 {running};重启 DSH 后生效,桌面版请完全退出后重新打开。",
1886
+ "versions.pendingPackagesTitle": "{names} 已更新",
1887
+ "versions.pendingPackagesDetail": "重启 DSH 后生效,桌面版请完全退出后重新打开。",
1884
1888
  "versions.checkedAt": "检查于 {time}",
1885
1889
  "versions.latestUnavailable": "暂时无法读取远程最新版本;请检查网络后重试。",
1886
1890
  "versions.modeHomebrew": "Homebrew",
@@ -1892,8 +1896,9 @@ window.__ModuleLoader__.load({
1892
1896
  "versions.hintHomebrew": "由 Homebrew 管理;发现新版本时可在此安全更新。",
1893
1897
  "versions.hintBrewMissing": "检测到 Homebrew 安装,但当前找不到 brew 命令。",
1894
1898
  "versions.hintGo": "由 go install 管理;发现新版本时可在此安全更新。",
1895
- "versions.hintPnpm": "由当前 DSH Profile 管理;更新后需要重启 dsh web。",
1896
- "versions.hintPnpmMissing": "检测到 DSH Profile 安装,但当前找不到 pnpm 命令。",
1899
+ "versions.hintDsh": "由当前 DSH Profile 管理;用 DSH 自己的插件安装方式更新,完成后重启 DSH。",
1900
+ "versions.hintPnpm": "由当前 DSH Profile 管理;更新后需要重启 DSH。",
1901
+ "versions.hintPnpmMissing": "检测到 DSH Profile 安装,但宿主找不到 pnpm 命令;安装 pnpm 并重启 DSH 后即可在此更新。",
1897
1902
  "versions.hintLink": "当前为本地 Link 开发版本;请在源码目录拉取并构建,避免覆盖本地修改。",
1898
1903
  "versions.hintInstall": "未安装 Mnemon CLI。只有 Mnemon Native 需要它;需要时安装并确保 mnemon 位于 PATH,或设置 MNEMON_CLI_PATH / mnemon.cliPath。",
1899
1904
  "versions.hintManual": "无法安全识别安装来源;请沿用原安装方式手工更新。",
@@ -2445,7 +2450,7 @@ window.__ModuleLoader__.load({
2445
2450
  "versions.packagesOutdated": "{count} updates available",
2446
2451
  "versions.packagesDetail": "Expand for maintenance options",
2447
2452
  "versions.packagesHide": "Collapse package details",
2448
- "versions.packagesHint": "Default packages update with the Starter. Packages installed independently in this Profile can update individually. Restart dsh web afterward.",
2453
+ "versions.packagesHint": "Default packages update with the Starter. Packages installed independently in this Profile can update individually. Restart DSH afterward.",
2449
2454
  "versions.managedStarter": "Maintained with Starter",
2450
2455
  "versions.managedProfile": "Managed by Profile",
2451
2456
  "versions.starterVersion": "Starter pin",
@@ -2471,7 +2476,11 @@ window.__ModuleLoader__.load({
2471
2476
  "versions.updating": "Updating…",
2472
2477
  "versions.updated": "{name} updated",
2473
2478
  "versions.alreadyCurrent": "Already on the latest version",
2474
- "versions.restartRequired": "Restart dsh web to load the new dsh-mnemon plugin code.",
2479
+ "versions.restartRequired": "Restart DSH to load the new dsh-mnemon plugin code; in the desktop app, quit and reopen it.",
2480
+ "versions.pendingInstalledTitle": "dsh-mnemon {version} is installed",
2481
+ "versions.pendingInstalledDetail": "This Host still runs {running}. Restart DSH to load it; in the desktop app, quit and reopen it.",
2482
+ "versions.pendingPackagesTitle": "{names} updated",
2483
+ "versions.pendingPackagesDetail": "Restart DSH to load the update; in the desktop app, quit and reopen it.",
2475
2484
  "versions.checkedAt": "Checked at {time}",
2476
2485
  "versions.latestUnavailable": "The remote latest version is unavailable. Check the network and try again.",
2477
2486
  "versions.modeHomebrew": "Homebrew",
@@ -2483,8 +2492,9 @@ window.__ModuleLoader__.load({
2483
2492
  "versions.hintHomebrew": "Managed by Homebrew; an available release can be safely updated here.",
2484
2493
  "versions.hintBrewMissing": "A Homebrew installation was detected, but the brew command is unavailable.",
2485
2494
  "versions.hintGo": "Managed by go install; an available release can be safely updated here.",
2486
- "versions.hintPnpm": "Managed by the current DSH Profile; restart dsh web after updating.",
2487
- "versions.hintPnpmMissing": "A DSH Profile installation was detected, but the pnpm command is unavailable.",
2495
+ "versions.hintDsh": "Managed by the current DSH Profile. Updates use DSH's own plugin installer; restart DSH afterwards.",
2496
+ "versions.hintPnpm": "Managed by the current DSH Profile; restart DSH after updating.",
2497
+ "versions.hintPnpmMissing": "A DSH Profile installation was detected, but the Host cannot find the pnpm command. Install pnpm and restart DSH to update here.",
2488
2498
  "versions.hintLink": "This is a local linked development build. Pull and build in the source directory to preserve local changes.",
2489
2499
  "versions.hintInstall": "Mnemon CLI is not installed. Only Mnemon Native needs it; to use Native, install it and place mnemon on PATH, or set MNEMON_CLI_PATH / mnemon.cliPath.",
2490
2500
  "versions.hintManual": "The installation source cannot be identified safely. Update it using the original installation method.",
@@ -8938,6 +8948,74 @@ window.__ModuleLoader__.load({
8938
8948
  }
8939
8949
  };
8940
8950
  //#endregion
8951
+ //#region src/client/starter-update.ts
8952
+ const STORAGE_KEY = "dsh-mnemon.starter-update";
8953
+ /** An older record belongs to an earlier visit, not to an update this page just ran. */
8954
+ const FRESH_MS = 6e5;
8955
+ function sessionStore() {
8956
+ try {
8957
+ return typeof window === "undefined" ? void 0 : window.sessionStorage;
8958
+ } catch {
8959
+ return;
8960
+ }
8961
+ }
8962
+ function recordStarterUpdate(update, now = Date.now()) {
8963
+ const record = {
8964
+ at: now,
8965
+ ...update.from === void 0 ? {} : { from: update.from },
8966
+ ...update.to === void 0 ? {} : { to: update.to }
8967
+ };
8968
+ try {
8969
+ sessionStore()?.setItem(STORAGE_KEY, JSON.stringify(record));
8970
+ } catch {}
8971
+ }
8972
+ function clearStarterUpdate() {
8973
+ try {
8974
+ sessionStore()?.removeItem(STORAGE_KEY);
8975
+ } catch {}
8976
+ }
8977
+ function pendingStarterUpdate(now = Date.now()) {
8978
+ let raw;
8979
+ try {
8980
+ raw = sessionStore()?.getItem(STORAGE_KEY);
8981
+ } catch {
8982
+ return;
8983
+ }
8984
+ if (raw === null || raw === void 0) return void 0;
8985
+ let value;
8986
+ try {
8987
+ value = JSON.parse(raw);
8988
+ } catch {
8989
+ value = void 0;
8990
+ }
8991
+ if (!isRecord(value) || typeof value.at !== "number" || now - value.at > FRESH_MS || value.at > now + 6e4) {
8992
+ clearStarterUpdate();
8993
+ return;
8994
+ }
8995
+ return {
8996
+ at: value.at,
8997
+ ...typeof value.from === "string" ? { from: value.from } : {},
8998
+ ...typeof value.to === "string" ? { to: value.to } : {}
8999
+ };
9000
+ }
9001
+ /** Open the Memory System once it can open, when this page just started a Starter update. */
9002
+ function reopenAfterStarterUpdate(workspace) {
9003
+ if (pendingStarterUpdate() === void 0) return () => {};
9004
+ let unsubscribe;
9005
+ const attempt = () => {
9006
+ const open = workspace.getSnapshot();
9007
+ if (open === void 0) return;
9008
+ unsubscribe?.();
9009
+ unsubscribe = void 0;
9010
+ if (pendingStarterUpdate() !== void 0) open();
9011
+ };
9012
+ unsubscribe = workspace.subscribe(attempt);
9013
+ attempt();
9014
+ return () => {
9015
+ unsubscribe?.();
9016
+ };
9017
+ }
9018
+ //#endregion
8941
9019
  //#region src/client/change-signal.ts
8942
9020
  /**
8943
9021
  * A revision other surfaces bump when something they cannot observe directly
@@ -9233,6 +9311,7 @@ window.__ModuleLoader__.load({
9233
9311
  if (component.updateHint === "brew") return t("versions.hintHomebrew");
9234
9312
  if (component.updateHint === "brew-missing") return t("versions.hintBrewMissing");
9235
9313
  if (component.updateHint === "go") return t("versions.hintGo");
9314
+ if (component.updateHint === "dsh") return t("versions.hintDsh");
9236
9315
  if (component.updateHint === "pnpm") return t("versions.hintPnpm");
9237
9316
  if (component.updateHint === "pnpm-missing") return t("versions.hintPnpmMissing");
9238
9317
  if (component.updateHint === "link") return t("versions.hintLink");
@@ -9353,20 +9432,41 @@ window.__ModuleLoader__.load({
9353
9432
  setUpdating(component.id);
9354
9433
  setError(null);
9355
9434
  setResult(null);
9435
+ if (component.id === "dsh-mnemon") recordStarterUpdate({
9436
+ from: component.current,
9437
+ to: component.latest
9438
+ });
9356
9439
  try {
9357
9440
  const next = await props.client.updateVersion(component.id);
9358
9441
  setResult(next);
9359
9442
  await check();
9360
9443
  props.onRefreshStatus();
9361
9444
  } catch (reason) {
9445
+ if (component.id === "dsh-mnemon") clearStarterUpdate();
9362
9446
  setError(message(reason));
9363
9447
  } finally {
9364
9448
  setUpdating(null);
9365
9449
  updateInFlight.current = false;
9366
9450
  }
9367
9451
  };
9452
+ const close = () => {
9453
+ clearStarterUpdate();
9454
+ props.onClose();
9455
+ };
9368
9456
  const updatingBusy = updating !== null;
9369
9457
  const controlsBusy = checking || updatingBusy;
9458
+ const resumed = props.resumedUpdate;
9459
+ const starter = snapshot?.components.find((component) => component.id === "dsh-mnemon");
9460
+ const outcome = result === null && error === null && resumed !== void 0 && snapshot?.lastUpdate?.component === "dsh-mnemon" && Date.parse(snapshot.lastUpdate.at) >= resumed.at - 6e4 ? snapshot.lastUpdate : void 0;
9461
+ const resumedResult = outcome !== void 0 ? outcome.result ?? null : result === null && resumed !== void 0 && snapshot?.lastUpdate === void 0 && starter?.restartRequired === true && (resumed.to === void 0 || starter.current === resumed.to) ? {
9462
+ component: "dsh-mnemon",
9463
+ updated: true,
9464
+ restartRequired: true,
9465
+ ...resumed.from === void 0 ? {} : { previousVersion: resumed.from },
9466
+ ...starter.current === void 0 ? {} : { currentVersion: starter.current }
9467
+ } : null;
9468
+ const shownResult = result ?? resumedResult;
9469
+ const shownError = error ?? outcome?.error ?? null;
9370
9470
  const updateButton = (component) => props.writeEnabled && component.outdated && component.updateSupported && component.checkError === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
9371
9471
  type: "button",
9372
9472
  className: MnemonView_module_css_default.primaryButton,
@@ -9419,7 +9519,7 @@ window.__ModuleLoader__.load({
9419
9519
  description: t("versions.description"),
9420
9520
  busy: updatingBusy,
9421
9521
  contentReady: !checking,
9422
- onClose: props.onClose,
9522
+ onClose: close,
9423
9523
  footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
9424
9524
  className: MnemonView_module_css_default.modalFooterMeta,
9425
9525
  children: snapshot === null ? "" : t("versions.checkedAt", { time: new Date(snapshot.checkedAt).toLocaleTimeString() })
@@ -9437,7 +9537,7 @@ window.__ModuleLoader__.load({
9437
9537
  "data-dialog-close": true,
9438
9538
  className: MnemonView_module_css_default.secondaryButton,
9439
9539
  disabled: updatingBusy,
9440
- onClick: props.onClose,
9540
+ onClick: close,
9441
9541
  children: t("common.close")
9442
9542
  })]
9443
9543
  })] }),
@@ -9449,19 +9549,19 @@ window.__ModuleLoader__.load({
9449
9549
  role: "status",
9450
9550
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {}), t("versions.checking")]
9451
9551
  }),
9452
- error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9552
+ shownError !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9453
9553
  className: MnemonView_module_css_default.versionError,
9454
9554
  role: "alert",
9455
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("versions.failed") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: error })]
9555
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("versions.failed") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: shownError })]
9456
9556
  }),
9457
9557
  !props.writeEnabled && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9458
9558
  className: MnemonView_module_css_default.versionNotice,
9459
9559
  children: t("versions.readOnly")
9460
9560
  }),
9461
- result !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9561
+ shownResult !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9462
9562
  className: MnemonView_module_css_default.versionResult,
9463
9563
  role: "status",
9464
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: result.updated ? t("versions.updated", { name: result.component === "mnemon" ? "Mnemon CLI" : result.component }) : t("versions.alreadyCurrent") }), result.restartRequired && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("versions.restartRequired") })]
9564
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: shownResult.updated ? t("versions.updated", { name: shownResult.component === "mnemon" ? "Mnemon CLI" : shownResult.component }) : t("versions.alreadyCurrent") }), shownResult.restartRequired && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("versions.restartRequired") })]
9465
9565
  }),
9466
9566
  snapshot !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9467
9567
  className: MnemonView_module_css_default.versionList,
@@ -9987,8 +10087,19 @@ window.__ModuleLoader__.load({
9987
10087
  }
9988
10088
  function StatusPage(props) {
9989
10089
  const t = useT();
10090
+ const [resumedUpdate, setResumedUpdate] = (0, react.useState)(() => pendingStarterUpdate());
9990
10091
  const [versionsOpen, setVersionsOpen] = (0, react.useState)(false);
9991
10092
  const status = props.status;
10093
+ const answered = status !== null || !props.loading;
10094
+ (0, react.useEffect)(() => {
10095
+ if (resumedUpdate === void 0 || !answered) return;
10096
+ if (status !== null && status.restartPending === void 0 && resumedUpdate.to !== void 0 && status.dshMnemonVersion === resumedUpdate.to) {
10097
+ clearStarterUpdate();
10098
+ setResumedUpdate(void 0);
10099
+ return;
10100
+ }
10101
+ setVersionsOpen(true);
10102
+ }, [answered]);
9992
10103
  const reviewError = status?.lifecycle?.current?.lastError;
9993
10104
  const storage = status?.storage;
9994
10105
  const selectedScopeKind = storage?.activeKind ?? "global";
@@ -10056,12 +10167,32 @@ window.__ModuleLoader__.load({
10056
10167
  versionsOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(VersionDialog, {
10057
10168
  client: props.client,
10058
10169
  writeEnabled: props.writeEnabled,
10059
- onClose: () => setVersionsOpen(false),
10170
+ resumedUpdate,
10171
+ onClose: () => {
10172
+ setResumedUpdate(void 0);
10173
+ setVersionsOpen(false);
10174
+ },
10060
10175
  onRefreshStatus: props.onRefresh
10061
10176
  })
10062
10177
  ]
10063
10178
  });
10064
10179
  }
10180
+ /**
10181
+ * An installed update waits for DSH to restart: the Host keeps the code it loaded, while DSH has
10182
+ * already swapped in this page. Every Memory System page says so until the restart.
10183
+ */
10184
+ function RestartReminder({ pending }) {
10185
+ const t = useT();
10186
+ return pending.installed !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Callout, {
10187
+ tone: "warning",
10188
+ title: t("versions.pendingInstalledTitle", { version: pending.installed }),
10189
+ children: t("versions.pendingInstalledDetail", { running: pending.running })
10190
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Callout, {
10191
+ tone: "warning",
10192
+ title: t("versions.pendingPackagesTitle", { names: (pending.packages ?? []).join(t("config.listSeparator")) }),
10193
+ children: t("versions.pendingPackagesDetail")
10194
+ });
10195
+ }
10065
10196
  /** Mnemon Native is one Provider among peers: it has a health card once its CLI is installed or a space uses it. */
10066
10197
  function nativeInUse(status) {
10067
10198
  return status.commandFound || (status.memoryBodies ?? []).some((body) => body.provider.origin === "native");
@@ -10901,6 +11032,10 @@ window.__ModuleLoader__.load({
10901
11032
  children: notice.detail
10902
11033
  })
10903
11034
  }),
11035
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Reveal, {
11036
+ className: MnemonView_module_css_default.notice,
11037
+ children: status?.restartPending !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RestartReminder, { pending: status.restartPending })
11038
+ }),
10904
11039
  workspaceToast.element,
10905
11040
  remoteReadOnly && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10906
11041
  className: MnemonView_module_css_default.alert,
@@ -12055,6 +12190,7 @@ window.__ModuleLoader__.load({
12055
12190
  activeMemoryWorkspace = void 0;
12056
12191
  };
12057
12192
  }, "dsh-mnemon: memory workspace entry");
12193
+ ctx.effect(() => reopenAfterStarterUpdate(seats.workspace), "dsh-mnemon: reopen after a Starter update");
12058
12194
  const componentSettingsDirectory = createComponentSettingsDirectory(ctx);
12059
12195
  const configurationServices = () => ({
12060
12196
  componentSettingsDirectory,
package/lib/index.js CHANGED
@@ -2376,10 +2376,10 @@ var LiveMnemonRuntime = class {
2376
2376
  route(request) {
2377
2377
  this.assertOpen();
2378
2378
  const effectiveAgent = this.agent(request.sessionId);
2379
- const effectiveWorkspace = effectiveAgent === void 0 ? void 0 : this.workspaceForPath(effectiveAgent.session.header?.cwd);
2379
+ const effectiveWorkspace = effectiveAgent === void 0 ? this.workspaceForSession(request.sessionId) : this.workspaceForPath(effectiveAgent.session.header?.cwd);
2380
2380
  const selectedWorkspace = request.workspaceId === void 0 || request.workspaceId.trim() === "" ? effectiveWorkspace : this.requireWorkspace(request.workspaceId);
2381
- const graph = selectedWorkspace === void 0 ? effectiveAgent === void 0 ? this.current : this.forAgent(effectiveAgent) : isWorkspaceStorageScope(this.current.config.storageScope) ? this.forWorkspacePath(selectedWorkspace.path) : this.current;
2382
- const effectiveGraph = effectiveAgent === void 0 ? this.current : this.forAgent(effectiveAgent);
2381
+ const effectiveGraph = effectiveAgent !== void 0 ? this.forAgent(effectiveAgent) : effectiveWorkspace !== void 0 && isWorkspaceStorageScope(this.current.config.storageScope) ? this.forWorkspacePath(effectiveWorkspace.path) : this.current;
2382
+ const graph = selectedWorkspace === void 0 ? effectiveGraph : isWorkspaceStorageScope(this.current.config.storageScope) ? this.forWorkspacePath(selectedWorkspace.path) : this.current;
2383
2383
  const selectedRoot = resolve(graph.directory);
2384
2384
  const effectiveRoot = resolve(effectiveGraph.directory);
2385
2385
  return {
@@ -2388,7 +2388,8 @@ var LiveMnemonRuntime = class {
2388
2388
  ...effectiveWorkspace === void 0 ? {} : { effectiveWorkspace },
2389
2389
  selectedRoot,
2390
2390
  effectiveRoot,
2391
- aligned: selectedRoot === effectiveRoot
2391
+ aligned: selectedRoot === effectiveRoot,
2392
+ liveSession: effectiveAgent !== void 0
2392
2393
  };
2393
2394
  }
2394
2395
  forWorkspacePath(workspaceRoot) {
@@ -2424,6 +2425,11 @@ var LiveMnemonRuntime = class {
2424
2425
  if (workspace === void 0) throw new Error("selected DSH workspace is unavailable");
2425
2426
  return workspace;
2426
2427
  }
2428
+ workspaceForSession(sessionId) {
2429
+ const normalized = sessionId?.trim();
2430
+ if (normalized === void 0 || normalized === "") return void 0;
2431
+ return this.workspaceRegistry?.list().find((workspace) => workspace.sessionIds?.includes(normalized) === true);
2432
+ }
2427
2433
  workspaceForPath(path) {
2428
2434
  const normalized = path?.trim();
2429
2435
  if (normalized === void 0 || normalized === "") return void 0;
@@ -2836,6 +2842,112 @@ function reviewCheckpoint(session, maxChars) {
2836
2842
  return header + (selected.reverse().join("") || "(no eligible text; skip)");
2837
2843
  }
2838
2844
  //#endregion
2845
+ //#region src/host/plugin-message.ts
2846
+ const MNEMON_PLUGIN_SOURCE = "dsh-mnemon";
2847
+ /** A user-role message this plugin adds to a step, marked with its own source. */
2848
+ function createPluginMessage(text, form) {
2849
+ return structuredClone({
2850
+ id: randomUUID(),
2851
+ role: "user",
2852
+ content: [{
2853
+ type: "text",
2854
+ text
2855
+ }],
2856
+ source: {
2857
+ kind: MNEMON_PLUGIN_SOURCE,
2858
+ form
2859
+ }
2860
+ });
2861
+ }
2862
+ //#endregion
2863
+ //#region src/host/continuation-turn.ts
2864
+ /** The user turn that ends a refused delegated child's tool continuations. */
2865
+ const CONTINUATION_TEXT = "Continue from the tool results above.";
2866
+ /** How a server reports a chat template that found no user query (Ollama, vLLM and others). */
2867
+ const NO_USER_QUERY = /no user query found in messages/iu;
2868
+ const startingChild = new AsyncLocalStorage();
2869
+ function hasUserText(message) {
2870
+ return message.role === "user" && message.content.some((block) => block.type === "text" && typeof block.text === "string" && block.text.trim() !== "");
2871
+ }
2872
+ /**
2873
+ * Some chat templates refuse a request without a user query: Ollama 0.33
2874
+ * serving Qwen3.x answers 500 "no user query found in messages" (#327). A
2875
+ * delegated child's only user turn is its prompt. Once tool results fill the
2876
+ * model's context window, Ollama truncates from the front and drops that
2877
+ * prompt while keeping the tool messages after it.
2878
+ *
2879
+ * Nothing changes until a server refuses a request that way. The refused step
2880
+ * then ends with a short user turn and runs again at once (the server ran no
2881
+ * inference), and each later tool continuation of the same child ends the
2882
+ * same way, since the server always keeps the last message. A route that never
2883
+ * refuses keeps its requests as they are, so a template that shows reasoning
2884
+ * only after the last user query keeps the earlier steps' reasoning.
2885
+ */
2886
+ function continueAfterRefusal(agent) {
2887
+ if (typeof agent.ctx?.on !== "function") return [];
2888
+ let refused = false;
2889
+ const retried = /* @__PURE__ */ new Set();
2890
+ const turn = () => createPluginMessage(CONTINUATION_TEXT, "instructions");
2891
+ return [agent.ctx.on("agent/request-error", (async (payload, next) => {
2892
+ const step = `${payload.turn}:${payload.step}`;
2893
+ const session = agent.session;
2894
+ if (payload.signal.aborted || payload.step < 2 || retried.has(step) || typeof session.append !== "function" || !NO_USER_QUERY.test(payload.failure?.message ?? "")) return next();
2895
+ retried.add(step);
2896
+ refused = true;
2897
+ session.append("user/message", turn(), { surfaceOp: "append" });
2898
+ return { kind: "retry" };
2899
+ }), { prepend: true }), agent.ctx.on("agent/pre-step", (async (payload, next) => {
2900
+ const decision = await next();
2901
+ if (!refused || payload.step === 1 || payload.signal.aborted || decision.kind !== "enter") return decision;
2902
+ if (payload.messages.length > 0 && decision.messages.length === 0) return decision;
2903
+ if (decision.messages.some(hasUserText)) return decision;
2904
+ return {
2905
+ ...decision,
2906
+ messages: [...decision.messages, turn()]
2907
+ };
2908
+ }), { prepend: true })];
2909
+ }
2910
+ /**
2911
+ * Start one delegated child and attach the refusal recovery while DSH
2912
+ * publishes it, before its first step. Async context attributes concurrent
2913
+ * starts, and DSH's ownership check confirms the parent where it exists. A host
2914
+ * that does not report creation leaves the child as it was.
2915
+ */
2916
+ async function startWithContinuationTurns(host, parent, start) {
2917
+ const pending = Symbol("Mnemon delegated start");
2918
+ const stops = [];
2919
+ const listener = host.on("agent/created", (({ agent }) => {
2920
+ if (startingChild.getStore() !== pending) return;
2921
+ if (typeof host.agents?.isOwnedBy === "function" && !host.agents.isOwnedBy(agent.id, parent)) return;
2922
+ for (const stop of continueAfterRefusal(agent)) if (typeof stop === "function") stops.push(stop);
2923
+ }));
2924
+ const release = async () => {
2925
+ for (const stop of stops.splice(0)) await stop();
2926
+ };
2927
+ let run;
2928
+ try {
2929
+ run = await startingChild.run(pending, start);
2930
+ } catch (error) {
2931
+ await release();
2932
+ throw error;
2933
+ } finally {
2934
+ if (typeof listener === "function") await listener();
2935
+ }
2936
+ const active = run;
2937
+ return {
2938
+ id: active.id,
2939
+ ...active.localAgent === void 0 ? {} : { localAgent: active.localAgent },
2940
+ result: active.result,
2941
+ async dispose() {
2942
+ try {
2943
+ await active.dispose();
2944
+ } finally {
2945
+ await release();
2946
+ }
2947
+ }
2948
+ };
2949
+ }
2950
+ //#endregion
2839
2951
  //#region src/host/subagent.ts
2840
2952
  function evidenceInsights(evidence) {
2841
2953
  return evidence.items.map((item) => {
@@ -4572,7 +4684,8 @@ This is the only completion channel for this run. Do not finish with a plain-tex
4572
4684
  provider: fixed.provider,
4573
4685
  model: fixed.model
4574
4686
  };
4575
- const start = () => this.subagents.start(provider, {
4687
+ const host = this.resultRuntime;
4688
+ const start = () => startWithContinuationTurns(host, parent, () => this.subagents.start(provider, {
4576
4689
  label,
4577
4690
  prompt: [{
4578
4691
  type: "text",
@@ -4584,7 +4697,7 @@ This is the only completion channel for this run. Do not finish with a plain-tex
4584
4697
  maxDepth: 1,
4585
4698
  toolFilter: { allow: [...tools, resultToolName] },
4586
4699
  persona: completionPersona
4587
- });
4700
+ }));
4588
4701
  run = operation === "review" ? await startGuardedReview(this.resultRuntime, parent, [...tools, resultToolName], start, (child) => {
4589
4702
  reviewChildId = child.id;
4590
4703
  }, reviewLayerPolicy()) : await start();
@@ -5251,21 +5364,6 @@ function llmService(value) {
5251
5364
  if (typeof value !== "object" || value === null || !("listProviders" in value) || typeof value.listProviders !== "function" || !("listModels" in value) || typeof value.listModels !== "function") return void 0;
5252
5365
  return value;
5253
5366
  }
5254
- const MNEMON_PLUGIN_SOURCE = "dsh-mnemon";
5255
- function createPluginMessage(text, form) {
5256
- return structuredClone({
5257
- id: crypto.randomUUID(),
5258
- role: "user",
5259
- content: [{
5260
- type: "text",
5261
- text
5262
- }],
5263
- source: {
5264
- kind: MNEMON_PLUGIN_SOURCE,
5265
- form
5266
- }
5267
- });
5268
- }
5269
5367
  function isMnemonMessageSource(source) {
5270
5368
  if (typeof source !== "object" || source === null) return false;
5271
5369
  const { kind, plugin } = source;
@@ -5891,6 +5989,16 @@ var MnemonLifecycle = class {
5891
5989
  mutateDocument(sessionId, request, signal = new AbortController().signal) {
5892
5990
  return this.coordinator.document(this.liveAgent(sessionId), request, signal);
5893
5991
  }
5992
+ /**
5993
+ * The same Document write under a fresh task Agent, for a conversation whose
5994
+ * Agent is not loaded: it archives the least recently used Document to make
5995
+ * room, as the conversation's Agent would.
5996
+ */
5997
+ mutateDocumentTask(sessionId, request, workspaceRoot, signal = new AbortController().signal) {
5998
+ const root = workspaceRoot?.trim() || this.workspaceRoot(sessionId);
5999
+ if (root === void 0 || root.trim() === "") throw new Error("a selected DSH workspace is required to write a Mnemon Document");
6000
+ return this.runTaskAgent(sessionId, root, signal, (agent) => this.coordinator.document(agent, request, signal));
6001
+ }
5894
6002
  archiveDocument(sessionId, id, workspaceRoot, signal = new AbortController().signal) {
5895
6003
  const root = workspaceRoot?.trim() || this.workspaceRoot(sessionId);
5896
6004
  if (root === void 0 || root.trim() === "") throw new Error("a selected DSH workspace is required to archive a Mnemon Document");
@@ -5902,6 +6010,11 @@ var MnemonLifecycle = class {
5902
6010
  placeProvider(sessionId, body, prepared, signal = new AbortController().signal) {
5903
6011
  return this.coordinator.placeProvider(this.liveAgent(sessionId), body, prepared, signal);
5904
6012
  }
6013
+ /** Choose a new Memory Space's Provider under a fresh task Agent, as Ask Agent does without a loaded conversation Agent. */
6014
+ placeProviderTask(sessionId, body, prepared, workspaceRoot, signal = new AbortController().signal) {
6015
+ const root = workspaceRoot?.trim() || this.workspaceRoot(sessionId);
6016
+ return this.runTaskAgent(sessionId, root, signal, (agent) => this.coordinator.placeProvider(agent, body, prepared, signal));
6017
+ }
5905
6018
  maintainMetadata(sessionId, memoryBodyIds, workspaceRoot, signal = new AbortController().signal) {
5906
6019
  const root = workspaceRoot?.trim() || this.workspaceRoot(sessionId);
5907
6020
  return this.runTaskAgent(sessionId, root, signal, (agent) => this.coordinator.maintainMetadata(agent, memoryBodyIds, signal));
@@ -6233,8 +6346,15 @@ function isVersionComponentId(value) {
6233
6346
  }
6234
6347
  const MNEMON_MODULE = "github.com/mnemon-dev/mnemon";
6235
6348
  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));
6349
+ /**
6350
+ * The version this process loaded. Profiles install with a hoisted layout, so an update rewrites
6351
+ * these same files while the loaded modules stay in memory until DSH restarts.
6352
+ */
6353
+ const LOADED_VERSION = manifest$1(PACKAGE_MANIFEST_PATH)?.version;
6236
6354
  const CHECK_TIMEOUT_MS = 1e4;
6237
6355
  const UPDATE_TIMEOUT_MS = 6e5;
6356
+ /** A check waits this long for an update that is finishing, within the browser's 15 s check deadline. */
6357
+ const IN_FLIGHT_WAIT_MS = 3e3;
6238
6358
  const MAX_UPDATE_OUTPUT_BYTES = 16384;
6239
6359
  async function settledWithin(promise, fallback, timeoutMs = 11e3) {
6240
6360
  let timer;
@@ -6353,6 +6473,10 @@ async function fetchNpmLatest(name, tag = "latest") {
6353
6473
  function dependencySpec(profile, name = DSH_MNEMON_PACKAGE) {
6354
6474
  return profile?.dependencies?.[name] ?? profile?.devDependencies?.[name];
6355
6475
  }
6476
+ /** A dependency the Profile records at one exact version, as DSH's installer writes it. */
6477
+ function exactVersion(spec) {
6478
+ return spec !== void 0 && /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(spec) ? spec : void 0;
6479
+ }
6356
6480
  function isLinkSpec(spec) {
6357
6481
  return spec !== void 0 && /^(?:link|file|workspace):|^\.{1,2}(?:[/\\]|$)/.test(spec);
6358
6482
  }
@@ -6427,6 +6551,22 @@ async function resultOrThrow(runner, command, args, timeoutMs, options = {}) {
6427
6551
  }
6428
6552
  return result;
6429
6553
  }
6554
+ /** DSH's failure code with the DSH a release needs, or with the first line of its diagnostic. */
6555
+ function dshFailure(change) {
6556
+ const reason = change.error?.code ?? change.application;
6557
+ const incompatible = change.error?.incompatible ?? [];
6558
+ const peers = incompatible.flatMap((plugin) => Object.entries(plugin.peers ?? {}).map(([name, range]) => `${name} ${range}`));
6559
+ if (peers.length > 0) {
6560
+ const runtime = incompatible.find((plugin) => plugin.runtimeVersion !== void 0)?.runtimeVersion;
6561
+ return `${reason} (requires ${peers.join(", ")}${runtime === void 0 ? "" : `; this DSH is ${runtime}`})`;
6562
+ }
6563
+ const lines = (change.error?.diagnostic ?? change.packageResult?.output ?? "").split("\n").map((line) => line.trim()).filter((line) => line !== "");
6564
+ const line = lines.findLast((line) => /ERR_PNPM_[A-Z_]+/.test(line)) ?? lines.findLast((line) => /^(?:\[?error\]?\b|ERROR\b)|\brejected\b/i.test(line)) ?? lines.at(-1);
6565
+ const kind = change.packageResult?.kind;
6566
+ const detail = [kind === void 0 || kind === "unknown" || line?.includes(kind) === true ? void 0 : kind, line?.slice(0, 300)].filter(Boolean).join(": ");
6567
+ const log = change.packageResult?.logPath;
6568
+ return `${reason}${detail === "" ? "" : ` (${detail})`}${log === void 0 || log === "" ? "" : `; log: ${log}`}`;
6569
+ }
6430
6570
  function updateOutput(result) {
6431
6571
  const output = [result.stdout.trim(), result.stderr.trim()].filter(Boolean).join("\n").trim();
6432
6572
  return output === "" ? void 0 : output.slice(-4e3);
@@ -6449,6 +6589,9 @@ function samePath(left, right) {
6449
6589
  }
6450
6590
  }
6451
6591
  var VersionUpdateManager = class {
6592
+ /** The version this Host runs; an update changes what is installed, not what is loaded. */
6593
+ runningVersion;
6594
+ /** The Starter the last update installed, read only while the files on disk cannot be. */
6452
6595
  dshMnemonVersion;
6453
6596
  packageManifestPath;
6454
6597
  dshHome;
@@ -6457,20 +6600,73 @@ var VersionUpdateManager = class {
6457
6600
  executable;
6458
6601
  fetchNpmLatest;
6459
6602
  fetchMnemonLatest;
6460
- pendingRestart = /* @__PURE__ */ new Set();
6461
- updating = false;
6603
+ bundleInstallerOf;
6604
+ runningProfile;
6605
+ /** What Check versions updated while this Host runs, each with the version it replaced. */
6606
+ pendingRestart = /* @__PURE__ */ new Map();
6607
+ inFlight;
6608
+ lastUpdate;
6462
6609
  constructor(dependencies = {}) {
6463
6610
  this.packageManifestPath = dependencies.packageManifestPath ?? PACKAGE_MANIFEST_PATH;
6464
- this.dshMnemonVersion = manifest$1(this.packageManifestPath)?.version ?? "0.0.0";
6611
+ this.runningVersion = (dependencies.packageManifestPath === void 0 ? LOADED_VERSION : void 0) ?? manifest$1(this.packageManifestPath)?.version ?? "0.0.0";
6612
+ this.dshMnemonVersion = this.runningVersion;
6465
6613
  this.dshHome = dependencies.dshHome ?? (process.env.DSH_HOME?.trim() || join(homedir(), ".dsh"));
6466
6614
  this.mnemonCliPath = dependencies.mnemonCliPath ?? (() => findMnemonCommand({}));
6467
6615
  this.processRunner = dependencies.processRunner ?? runProcess;
6468
6616
  this.executable = dependencies.resolveExecutable ?? resolveExecutable;
6469
6617
  this.fetchNpmLatest = dependencies.fetchNpmLatest ?? fetchNpmLatest;
6470
6618
  this.fetchMnemonLatest = dependencies.fetchMnemonLatest ?? (() => this.fetchNpmLatest(MNEMON_NPM_PACKAGE));
6619
+ this.bundleInstallerOf = dependencies.bundleInstaller ?? (() => void 0);
6620
+ this.runningProfile = dependencies.runningProfile ?? (() => void 0);
6471
6621
  }
6622
+ /**
6623
+ * DSH's own installer, only for the profile this Host runs in, so it never writes another
6624
+ * profile, and only while DSH has a package manager to run.
6625
+ */
6626
+ bundleInstaller(install) {
6627
+ if (install.mode !== "npm" || install.profileDir === void 0) return void 0;
6628
+ const installer = this.bundleInstallerOf();
6629
+ if (typeof installer?.installBundle !== "function" || typeof installer.listBundles !== "function") return void 0;
6630
+ const running = this.runningProfile();
6631
+ if (running === void 0 || !running.packageManager && this.executable("pnpm") === void 0) return void 0;
6632
+ try {
6633
+ return realpathSync(running.dir) === realpathSync(install.profileDir) ? installer : void 0;
6634
+ } catch {
6635
+ return;
6636
+ }
6637
+ }
6638
+ /** The Starter installed now, which DSH loads at its next start, however it was installed. */
6472
6639
  get currentDshMnemonVersion() {
6473
- return this.dshMnemonVersion;
6640
+ return this.installedVersion(inspectDshInstall(this.packageManifestPath, this.dshHome));
6641
+ }
6642
+ installedVersion(install) {
6643
+ const recorded = install.mode === "npm" && install.profileDir !== void 0 ? exactVersion(dependencySpec(manifest$1(join(install.profileDir, "package.json")))) : void 0;
6644
+ if (recorded !== void 0) return recorded;
6645
+ return manifest$1(install.mode === "npm" && install.profileDir !== void 0 ? packageAt(install.profileDir, DSH_MNEMON_PACKAGE) ?? this.packageManifestPath : this.packageManifestPath)?.version ?? this.dshMnemonVersion;
6646
+ }
6647
+ /** A package Check versions updated, and still not the version it replaced. */
6648
+ replaced(id, current) {
6649
+ return this.pendingRestart.has(id) && current !== this.pendingRestart.get(id);
6650
+ }
6651
+ remember(id, previousVersion) {
6652
+ if (!this.pendingRestart.has(id)) this.pendingRestart.set(id, previousVersion);
6653
+ }
6654
+ /**
6655
+ * What waits for a DSH restart: a Starter on disk other than the one this Host runs, however it
6656
+ * was installed, and the packages Check versions updated on their own.
6657
+ */
6658
+ restartStatus() {
6659
+ const install = inspectDshInstall(this.packageManifestPath, this.dshHome);
6660
+ const installed = this.installedVersion(install);
6661
+ const pending = [...this.pendingRestart.keys()].filter((id) => id !== DSH_MNEMON_PACKAGE);
6662
+ const rows = pending.length === 0 ? [] : this.subpackages(install).map((row) => row.status);
6663
+ const packages = pending.filter((id) => this.replaced(id, rows.find((row) => row.id === id)?.current)).sort();
6664
+ if (installed === this.runningVersion && packages.length === 0) return void 0;
6665
+ return {
6666
+ running: this.runningVersion,
6667
+ ...installed === this.runningVersion ? {} : { installed },
6668
+ ...packages.length === 0 ? {} : { packages }
6669
+ };
6474
6670
  }
6475
6671
  async latestPackageVersion(name, current) {
6476
6672
  const channel = current === void 0 ? void 0 : parseSemver(current)?.prerelease[0];
@@ -6634,19 +6830,24 @@ var VersionUpdateManager = class {
6634
6830
  subpackages(install) {
6635
6831
  const starterPath = install.mode === "npm" && install.profileDir !== void 0 ? packageAt(install.profileDir, DSH_MNEMON_PACKAGE) ?? this.packageManifestPath : this.packageManifestPath;
6636
6832
  const starter = manifest$1(starterPath);
6833
+ const starterReplaced = this.installedVersion(install) !== this.runningVersion;
6637
6834
  const profile = install.profileDir === void 0 ? void 0 : manifest$1(join(install.profileDir, "package.json"));
6638
- return [.../* @__PURE__ */ new Set([
6835
+ const names = [.../* @__PURE__ */ new Set([
6639
6836
  ...Object.keys(starter?.dependencies ?? {}),
6640
6837
  ...Object.keys(profile?.dependencies ?? {}),
6641
6838
  ...Object.keys(profile?.devDependencies ?? {})
6642
- ])].filter((name) => SUBPACKAGE.test(name)).sort().map((name) => {
6839
+ ])].filter((name) => SUBPACKAGE.test(name)).sort();
6840
+ const installer = this.bundleInstaller(install);
6841
+ return names.map((name) => {
6643
6842
  const directSpec = dependencySpec(profile, name);
6644
6843
  const managedBy = directSpec === void 0 ? "starter" : "profile";
6645
6844
  const path = packageAt(directSpec === void 0 ? dirname(starterPath) : install.profileDir, name);
6646
6845
  const value = path === void 0 ? void 0 : manifest$1(path);
6846
+ const current = (managedBy === "profile" ? exactVersion(directSpec) : void 0) ?? value?.version;
6647
6847
  const linked = isLinkSpec(directSpec ?? starter?.dependencies?.[name]) || install.mode === "link" || path !== void 0 && !realpathSync(dirname(path)).replaceAll("\\", "/").includes("/node_modules/");
6648
6848
  const mode = linked ? "link" : path === void 0 ? "missing" : install.mode === "npm" ? "npm" : "manual";
6649
- const supported = managedBy === "profile" && mode === "npm" && this.executable("pnpm") !== void 0;
6849
+ const throughDsh = managedBy === "profile" && mode === "npm" && installer !== void 0 && value?.dsh?.bundle !== void 0;
6850
+ const supported = managedBy === "profile" && mode === "npm" && (throughDsh || this.executable("pnpm") !== void 0);
6650
6851
  return {
6651
6852
  ...path === void 0 ? {} : { manifestPath: path },
6652
6853
  status: {
@@ -6655,19 +6856,20 @@ var VersionUpdateManager = class {
6655
6856
  kind: SUBPACKAGE.exec(name)[1],
6656
6857
  managedBy,
6657
6858
  ...starter?.dependencies?.[name] === void 0 ? {} : { expectedVersion: starter.dependencies[name] },
6658
- ...value?.version === void 0 ? {} : { current: value.version },
6859
+ ...current === void 0 ? {} : { current },
6659
6860
  ...path === void 0 ? {} : { installPath: realpathSync(dirname(path)) },
6660
6861
  ...install.profileName === void 0 ? {} : { installProfile: install.profileName },
6661
6862
  installMode: mode,
6662
6863
  outdated: false,
6663
6864
  updateSupported: supported,
6664
- updateHint: linked ? "link" : managedBy === "starter" ? "starter" : supported ? "pnpm" : mode === "npm" ? "pnpm-missing" : "manual",
6665
- restartRequired: this.pendingRestart.has(name) || this.pendingRestart.has(DSH_MNEMON_PACKAGE)
6865
+ updateHint: linked ? "link" : managedBy === "starter" ? "starter" : throughDsh ? "dsh" : supported ? "pnpm" : mode === "npm" ? "pnpm-missing" : "manual",
6866
+ restartRequired: this.replaced(name, current) || starterReplaced
6666
6867
  }
6667
6868
  };
6668
6869
  });
6669
6870
  }
6670
6871
  async check() {
6872
+ if (this.inFlight !== void 0) await settledWithin(this.inFlight, void 0, IN_FLIGHT_WAIT_MS);
6671
6873
  const dshInstall = inspectDshInstall(this.packageManifestPath, this.dshHome);
6672
6874
  const [mnemonLocal, mnemonLatest, dshLatest, packages] = await Promise.all([
6673
6875
  settledWithin(this.inspectMnemon(), { install: {
@@ -6686,25 +6888,28 @@ var VersionUpdateManager = class {
6686
6888
  }))
6687
6889
  ]);
6688
6890
  const pnpm = this.executable("pnpm");
6891
+ const installer = this.bundleInstaller(dshInstall);
6689
6892
  const mnemonOutdated = mnemonLocal.current !== void 0 && mnemonLatest !== void 0 && compareVersions(mnemonLocal.current, mnemonLatest) < 0;
6690
- const dshOutdated = dshLatest !== void 0 && compareVersions(this.currentDshMnemonVersion, dshLatest) < 0;
6893
+ const installed = this.installedVersion(dshInstall);
6894
+ const dshOutdated = dshLatest !== void 0 && compareVersions(installed, dshLatest) < 0;
6691
6895
  const mnemonSupported = mnemonLocal.install.updateCommand !== void 0;
6692
- const dshSupported = dshInstall.mode === "npm" && dshInstall.profileDir !== void 0 && pnpm !== void 0;
6896
+ const dshSupported = dshInstall.mode === "npm" && dshInstall.profileDir !== void 0 && (installer !== void 0 || pnpm !== void 0);
6693
6897
  return {
6694
6898
  checkedAt: (/* @__PURE__ */ new Date()).toISOString(),
6899
+ ...this.lastUpdate === void 0 ? {} : { lastUpdate: this.lastUpdate },
6695
6900
  components: [{
6696
6901
  id: "dsh-mnemon",
6697
6902
  name: "dsh-mnemon",
6698
6903
  ...dshInstall.profileName === void 0 ? {} : { installProfile: dshInstall.profileName },
6699
6904
  installPath: dshInstall.locationDir,
6700
- current: this.currentDshMnemonVersion,
6905
+ current: installed,
6701
6906
  ...dshLatest === void 0 ? {} : { latest: dshLatest },
6702
6907
  outdated: dshOutdated,
6703
6908
  installMode: dshInstall.mode,
6704
6909
  updateSupported: dshSupported,
6705
6910
  packages,
6706
- restartRequired: this.pendingRestart.size > 0,
6707
- updateHint: dshInstall.mode === "npm" ? dshSupported ? "pnpm" : "pnpm-missing" : dshInstall.mode === "link" ? "link" : "manual",
6911
+ restartRequired: this.restartStatus() !== void 0,
6912
+ updateHint: dshInstall.mode === "npm" ? installer !== void 0 ? "dsh" : dshSupported ? "pnpm" : "pnpm-missing" : dshInstall.mode === "link" ? "link" : "manual",
6708
6913
  ...dshLatest === void 0 ? { checkError: "latest-unavailable" } : {}
6709
6914
  }, {
6710
6915
  id: "mnemon",
@@ -6721,12 +6926,26 @@ var VersionUpdateManager = class {
6721
6926
  };
6722
6927
  }
6723
6928
  async update(component) {
6724
- if (this.updating) throw new Error("A version update is already in progress");
6725
- this.updating = true;
6929
+ if (this.inFlight !== void 0) throw new Error("A version update is already in progress");
6930
+ const run = this.performUpdate(component);
6931
+ this.inFlight = run;
6726
6932
  try {
6727
- return await this.performUpdate(component);
6933
+ const result = await run;
6934
+ this.lastUpdate = {
6935
+ at: (/* @__PURE__ */ new Date()).toISOString(),
6936
+ component,
6937
+ result
6938
+ };
6939
+ return result;
6940
+ } catch (error) {
6941
+ this.lastUpdate = {
6942
+ at: (/* @__PURE__ */ new Date()).toISOString(),
6943
+ component,
6944
+ error: error instanceof Error ? error.message : String(error)
6945
+ };
6946
+ throw error;
6728
6947
  } finally {
6729
- this.updating = false;
6948
+ this.inFlight = void 0;
6730
6949
  }
6731
6950
  }
6732
6951
  async performUpdate(component) {
@@ -6768,8 +6987,10 @@ var VersionUpdateManager = class {
6768
6987
  previousVersion,
6769
6988
  currentVersion: previousVersion,
6770
6989
  updated: false,
6771
- restartRequired: this.pendingRestart.has(component)
6990
+ restartRequired: component === DSH_MNEMON_PACKAGE ? previousVersion !== this.runningVersion : this.replaced(component, previousVersion)
6772
6991
  };
6992
+ const installer = child === void 0 || child.updateHint === "dsh" ? this.bundleInstaller(install) : void 0;
6993
+ if (installer !== void 0) return this.updateThroughDsh(installer, component, previousVersion, latest);
6773
6994
  const pnpm = this.executable("pnpm");
6774
6995
  if (install.mode !== "npm" || install.profileDir === void 0 || pnpm === void 0) throw new Error("This dsh-mnemon installation cannot be updated automatically");
6775
6996
  const output = await resultOrThrow(this.processRunner, pnpm, [
@@ -6781,7 +7002,7 @@ var VersionUpdateManager = class {
6781
7002
  if (installedVersion !== latest) throw new Error(`${component} update did not install the requested version ${latest}; found ${installedVersion ?? "no package"}`);
6782
7003
  const outputText = updateOutput(output);
6783
7004
  if (component === DSH_MNEMON_PACKAGE) this.dshMnemonVersion = installedVersion;
6784
- this.pendingRestart.add(component);
7005
+ this.remember(component, previousVersion);
6785
7006
  return {
6786
7007
  component,
6787
7008
  previousVersion,
@@ -6791,6 +7012,33 @@ var VersionUpdateManager = class {
6791
7012
  ...outputText === void 0 ? {} : { output: outputText }
6792
7013
  };
6793
7014
  }
7015
+ /**
7016
+ * An exact version installs at once, outside pnpm's release-age window, and for the Starter pins
7017
+ * its tested components. The update replaces the package only; which bundles run stays as it was.
7018
+ */
7019
+ async updateThroughDsh(installer, component, previousVersion, latest) {
7020
+ const spec = `${component}@${latest}`;
7021
+ let change;
7022
+ try {
7023
+ change = await installer.installBundle(spec, { enabled: false });
7024
+ } catch (error) {
7025
+ throw new Error(`DSH could not install ${spec}: ${error instanceof Error ? error.message : String(error)}`);
7026
+ }
7027
+ const output = change.packageResult?.output?.trim();
7028
+ if (change.application === "failed" || change.application === "cancelled") throw new Error(`DSH could not install ${spec}: ${dshFailure(change)}`);
7029
+ const installedVersion = (await installer.listBundles()).find((bundle) => bundle.name === component)?.version;
7030
+ if (installedVersion !== latest) throw new Error(`${component} update did not install the requested version ${latest}; found ${installedVersion ?? "no package"}`);
7031
+ if (component === DSH_MNEMON_PACKAGE) this.dshMnemonVersion = installedVersion;
7032
+ this.remember(component, previousVersion);
7033
+ return {
7034
+ component,
7035
+ previousVersion,
7036
+ currentVersion: installedVersion,
7037
+ updated: true,
7038
+ restartRequired: true,
7039
+ ...output === void 0 || output === "" ? {} : { output: output.slice(-4e3) }
7040
+ };
7041
+ }
6794
7042
  };
6795
7043
  //#endregion
6796
7044
  //#region src/host/rpc.ts
@@ -6970,7 +7218,13 @@ async function assisted(runtime, lifecycle, typeId, operation, input, signal, ta
6970
7218
  if (operation === "mutate") {
6971
7219
  requireCapability(runtime, typeId, "write");
6972
7220
  const request = input;
6973
- return runtime.aligned && sessionId !== "" ? lifecycle.mutateDocument(sessionId, request, signal) : runtime.source(typeId).mutate("mutate", request, signal);
7221
+ if (runtime.aligned && runtime.liveSession && sessionId !== "") return lifecycle.mutateDocument(sessionId, request, signal);
7222
+ try {
7223
+ return await runtime.source(typeId).mutate("mutate", request, signal);
7224
+ } catch (error) {
7225
+ if (!runtime.aligned || sessionId === "" || !sourceFailure(error, "document-capacity")) throw error;
7226
+ return lifecycle.mutateDocumentTask(sessionId, request, workspaceRoot, signal);
7227
+ }
6974
7228
  }
6975
7229
  }
6976
7230
  if (typeId !== "memory-spaces") throw new Error("unsupported Source assistance operation");
@@ -6994,10 +7248,11 @@ async function assisted(runtime, lifecycle, typeId, operation, input, signal, ta
6994
7248
  if (request.placement === void 0) return source.mutate("body-create", request, signal);
6995
7249
  requireAligned(runtime);
6996
7250
  const prepared = await source.read("prepare-body-placement", request, signal);
6997
- const placementDecision = await lifecycle.placeProvider(sessionId, {
7251
+ const body = {
6998
7252
  name: request.name,
6999
7253
  description: request.description
7000
- }, prepared, signal);
7254
+ };
7255
+ const placementDecision = runtime.liveSession ? await lifecycle.placeProvider(sessionId, body, prepared, signal) : await lifecycle.placeProviderTask(sessionId, body, prepared, workspaceRoot, signal);
7001
7256
  return source.mutate("body-create", {
7002
7257
  request,
7003
7258
  placementDecision
@@ -7082,6 +7337,10 @@ function createReadHandler(input, lifecycle, versions) {
7082
7337
  documents = await runtime.source("documents").read("snapshot", null, signal);
7083
7338
  } catch {}
7084
7339
  const composition = await compositionStatus(runtime);
7340
+ let restartPending;
7341
+ try {
7342
+ restartPending = versions?.restartStatus();
7343
+ } catch {}
7085
7344
  return success$1({
7086
7345
  ...composition.sources.some((source) => source.sourceTypeId === "memory-spaces") ? await runtime.source("memory-spaces").read(endpoint, payload, signal) : {
7087
7346
  healthy: composition.evaluation.state === "ready",
@@ -7093,7 +7352,8 @@ function createReadHandler(input, lifecycle, versions) {
7093
7352
  writeEnabled: runtime.graph.config.writeEnabled,
7094
7353
  defaultRecallLimit: runtime.graph.config.defaultRecallLimit
7095
7354
  },
7096
- ...versions === void 0 ? {} : { dshMnemonVersion: versions.currentDshMnemonVersion },
7355
+ ...versions === void 0 ? {} : { dshMnemonVersion: versions.runningVersion },
7356
+ ...restartPending === void 0 ? {} : { restartPending },
7097
7357
  ...lifecycle === void 0 ? {} : { lifecycle: lifecycle.snapshot(runtime.scope.sessionId, isWorkspaceStorageScope(runtime.graph.config.storageScope) ? runtime.scope.workspaceId : void 0) },
7098
7358
  ...documents === void 0 ? {} : { documents },
7099
7359
  memorySystem: composition,
@@ -7248,7 +7508,7 @@ function createWriteHandler(input, lifecycle, versions) {
7248
7508
  }
7249
7509
  if (Object.hasOwn(SPACE_WRITE_CAPABILITIES, endpoint)) {
7250
7510
  requireCapability(runtime, "memory-spaces", SPACE_WRITE_CAPABILITIES[endpoint]);
7251
- if (endpoint === "remember" && lifecycle !== void 0 && runtime.aligned && runtime.scope.sessionId) return success$1(await lifecycle.remember(runtime.scope.sessionId, {
7511
+ if (endpoint === "remember" && lifecycle !== void 0 && runtime.aligned && runtime.liveSession && runtime.scope.sessionId) return success$1(await lifecycle.remember(runtime.scope.sessionId, {
7252
7512
  ...payload,
7253
7513
  source: "user"
7254
7514
  }, signal));
@@ -9156,8 +9416,8 @@ function createViewHandler(runtime, engine, management, access, lifecycle, insta
9156
9416
  ...sessionId === void 0 ? {} : { sessionId },
9157
9417
  ...selectedWorkspaceId === void 0 ? {} : { workspaceId: selectedWorkspaceId }
9158
9418
  });
9159
- const workspaceId = route.selectedWorkspace?.path ?? lifecycle?.workspaceRoot(sessionId);
9160
- const sessionWorkspace = lifecycle?.workspaceRoot(sessionId);
9419
+ const sessionWorkspace = lifecycle?.workspaceRoot(sessionId) ?? route.effectiveWorkspace?.path;
9420
+ const workspaceId = route.selectedWorkspace?.path ?? sessionWorkspace;
9161
9421
  const aligned = route.aligned && (route.selectedWorkspace === void 0 || sessionId === void 0 || sessionWorkspace !== void 0 && resolve(route.selectedWorkspace.path) === resolve(sessionWorkspace));
9162
9422
  const config = management.resolveConfig(runtime.config);
9163
9423
  const scope = {
@@ -10276,7 +10536,18 @@ function apply(rawContext, rawConfig = {}) {
10276
10536
  ctx.inject(["connection", "webServer"], (webContext) => {
10277
10537
  const connection = Context.is(webContext) ? webContext.extend({ webServer: webContext.get("webServer") }).connection : webContext.connection;
10278
10538
  if (connection === void 0) return;
10279
- const rpc = registerRpc(connection, runtime, lifecycle);
10539
+ const versions = new VersionUpdateManager({
10540
+ mnemonCliPath: () => runtime.config.cliPath,
10541
+ bundleInstaller: () => ctx.get("pluginManager"),
10542
+ runningProfile: () => {
10543
+ const profile = ctx.get("profileContext");
10544
+ return typeof profile?.dir === "string" ? {
10545
+ dir: profile.dir,
10546
+ packageManager: profile.packageManager !== void 0
10547
+ } : void 0;
10548
+ }
10549
+ });
10550
+ const rpc = registerRpc(connection, runtime, lifecycle, versions);
10280
10551
  const settings = registerSettingsRpc(connection, hostSettings);
10281
10552
  const view = registerViewRpc(connection, runtime, extensions, memoryPlugins, lifecycle, pluginInstallation);
10282
10553
  if (Context.is(webContext)) new MnemonRemoteService(webContext, {
@@ -7,16 +7,16 @@ var dependencies = {
7
7
  "@deepseek-ai/schemastery": "^3.18.4",
8
8
  "dsh-mnemon-provider-byterover": "0.5.5",
9
9
  "dsh-mnemon-provider-hindsight": "0.5.5",
10
- "dsh-mnemon-provider-holographic": "0.5.5",
10
+ "dsh-mnemon-provider-holographic": "0.5.6",
11
11
  "dsh-mnemon-provider-honcho": "0.5.5",
12
12
  "dsh-mnemon-provider-mem0": "0.5.5",
13
13
  "dsh-mnemon-provider-mnemon-native": "0.5.8",
14
14
  "dsh-mnemon-provider-openviking": "0.5.7",
15
15
  "dsh-mnemon-provider-retaindb": "0.5.5",
16
16
  "dsh-mnemon-provider-supermemory": "0.5.5",
17
- "dsh-mnemon-source-documents": "0.5.8",
18
- "dsh-mnemon-source-memory-spaces": "0.5.14",
19
- "dsh-mnemon-source-runtime": "0.5.11",
17
+ "dsh-mnemon-source-documents": "0.5.9",
18
+ "dsh-mnemon-source-memory-spaces": "0.5.15",
19
+ "dsh-mnemon-source-runtime": "0.5.12",
20
20
  "dsh-mnemon-strategy-auto-capture": "0.5.5",
21
21
  "dsh-mnemon-strategy-default-three-tier": "0.5.7",
22
22
  "dsh-mnemon-strategy-general": "0.5.1",
package/lib/starter.js CHANGED
@@ -1,4 +1,4 @@
1
- import { t as prepareStarterResolution } from "./starter-resolution-BJPNaa9v.js";
1
+ import { t as prepareStarterResolution } from "./starter-resolution-B0Wj3X3R.js";
2
2
  //#region src/starter.ts
3
3
  const name = "dsh-mnemon-starter";
4
4
  const provide = ["mnemonStarterReady"];
@@ -268,6 +268,10 @@ export declare const zh: {
268
268
  'versions.updated': string;
269
269
  'versions.alreadyCurrent': string;
270
270
  'versions.restartRequired': string;
271
+ 'versions.pendingInstalledTitle': string;
272
+ 'versions.pendingInstalledDetail': string;
273
+ 'versions.pendingPackagesTitle': string;
274
+ 'versions.pendingPackagesDetail': string;
271
275
  'versions.checkedAt': string;
272
276
  'versions.latestUnavailable': string;
273
277
  'versions.modeHomebrew': string;
@@ -279,6 +283,7 @@ export declare const zh: {
279
283
  'versions.hintHomebrew': string;
280
284
  'versions.hintBrewMissing': string;
281
285
  'versions.hintGo': string;
286
+ 'versions.hintDsh': string;
282
287
  'versions.hintPnpm': string;
283
288
  'versions.hintPnpmMissing': string;
284
289
  'versions.hintLink': string;
@@ -486,7 +486,10 @@ export interface StatusView {
486
486
  healthy: boolean;
487
487
  error?: string;
488
488
  version?: string;
489
+ /** The dsh-mnemon version this Host runs, which an installed update replaces only when DSH restarts. */
489
490
  dshMnemonVersion?: string;
491
+ /** Updates installed while this Host runs; they load when DSH restarts. */
492
+ restartPending?: VersionRestartStatus;
490
493
  cliPath: string;
491
494
  commandFound: boolean;
492
495
  dataDir: string;
@@ -606,6 +609,24 @@ export interface VersionPackageStatus extends VersionComponentStatus {
606
609
  export interface VersionStatus {
607
610
  checkedAt: string;
608
611
  components: VersionComponentStatus[];
612
+ /** How the last update this Host ran from Check versions ended, for a page DSH swapped in meanwhile. */
613
+ lastUpdate?: VersionUpdateOutcome;
614
+ }
615
+ export interface VersionUpdateOutcome {
616
+ at: string;
617
+ component: VersionComponentId;
618
+ /** The update's reply, when it ended without an error. */
619
+ result?: VersionUpdateResult;
620
+ /** Why it failed, when it did. */
621
+ error?: string;
622
+ }
623
+ export interface VersionRestartStatus {
624
+ /** The dsh-mnemon version this Host runs. */
625
+ running: string;
626
+ /** The dsh-mnemon version now installed, when it differs, however it was installed: Check versions or `dsh plugin`, for example. */
627
+ installed?: string;
628
+ /** Packages Check versions updated on their own. */
629
+ packages?: VersionPackageId[];
609
630
  }
610
631
  export interface VersionUpdateResult {
611
632
  component: VersionComponentId;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-mnemon",
3
- "version": "0.5.21",
3
+ "version": "0.5.23",
4
4
  "description": "Composable, view-based memory for DeepSeek Harness. Pluggable sources and strategies, with layered memory out of the box.",
5
5
  "keywords": [
6
6
  "deepseek-harness",
@@ -135,6 +135,7 @@
135
135
  "typecheck": "tsc -p tsconfig.json --noEmit",
136
136
  "test": "vitest run",
137
137
  "test:root": "vitest run --dir tests",
138
+ "bench:algorithms": "vitest run --config scripts/benchmark-algorithms.config.ts",
138
139
  "verify:build": "node scripts/verify-deterministic-build.mjs",
139
140
  "verify:headless": "node scripts/verify-headless-profile.mjs",
140
141
  "verify:package": "node scripts/verify-package-contents.mjs && node scripts/verify-public-entries.mjs && pnpm run lint:package",
@@ -153,16 +154,16 @@
153
154
  "@deepseek-ai/schemastery": "^3.18.4",
154
155
  "dsh-mnemon-provider-byterover": "0.5.5",
155
156
  "dsh-mnemon-provider-hindsight": "0.5.5",
156
- "dsh-mnemon-provider-holographic": "0.5.5",
157
+ "dsh-mnemon-provider-holographic": "0.5.6",
157
158
  "dsh-mnemon-provider-honcho": "0.5.5",
158
159
  "dsh-mnemon-provider-mem0": "0.5.5",
159
160
  "dsh-mnemon-provider-mnemon-native": "0.5.8",
160
161
  "dsh-mnemon-provider-openviking": "0.5.7",
161
162
  "dsh-mnemon-provider-retaindb": "0.5.5",
162
163
  "dsh-mnemon-provider-supermemory": "0.5.5",
163
- "dsh-mnemon-source-documents": "0.5.8",
164
- "dsh-mnemon-source-memory-spaces": "0.5.14",
165
- "dsh-mnemon-source-runtime": "0.5.11",
164
+ "dsh-mnemon-source-documents": "0.5.9",
165
+ "dsh-mnemon-source-memory-spaces": "0.5.15",
166
+ "dsh-mnemon-source-runtime": "0.5.12",
166
167
  "dsh-mnemon-strategy-auto-capture": "0.5.5",
167
168
  "dsh-mnemon-strategy-default-three-tier": "0.5.7",
168
169
  "dsh-mnemon-strategy-general": "0.5.1",