dsh-m 0.9.5 → 0.9.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.en.md CHANGED
@@ -48,6 +48,17 @@ The default registry includes DSH Skins, ModSearch, the Lark / QQ / Weixin / WeC
48
48
 
49
49
  The restart chain below applies to **Web only** (Desktop takes effect via the official app lifecycle: after installing, quit and reopen the Desktop app — see the 0.9.0 section). After any mutation, the already-open Market and Installed views refresh the profile state together, keeping badges and cards in sync without closing and reopening the marketplace; a "⚡ Restart" banner appears — under systemd, the DSH launcher's `appExit` hook hands the restart back to a unit configured with `Restart=on-failure` or `Restart=always`, avoiding a `systemctl` helper inside the unit cgroup that is about to stop; if `appExit` is unavailable, the fallback uses a manager-owned transient `systemd-run` service and only then a detached helper. The client confirms the replacement by boot id and dismisses the banner, leaving DSH Web's own background connection recovery in control; it does not force a full-page reload during the auth/route handoff. The restart chain has been live-verified on the current DSH Web `0.2.0-rc.2` (2026-10-01): dsh-m probes the unit's `Restart=` policy, hands the stop back via `appExit`, the `status=75/TEMPFAIL` exit is caught by the Restart policy and auto-started, and after recovery the page reconnects in the background with the panel fully functional. Historical scope: the `0.1.5-rc.1` era covered live `/dshm` ping and authenticated `303 → 200` checks; `0.1.2-rc.1` is covered by contract checks plus runtime shape-detection fallback (see the verified array) — with the deployment moved on, no live E2E is chased on retired generations; the transient `systemd-run` fallback only applies to hosts without `appExit` (all supported generations provide it) and stays a documented fallback rather than a release gate; repeated install/uninstall is covered by the profile-transaction test surface (compensation, post-install guard, 813 cases) plus the 0.4.0→0.4.2 consecutive-release evidence. Installs stream live pnpm progress (resolve → download → link → build).
50
50
 
51
+ ### Fixed in 0.9.7 — Upgrade chip on Desktop showed a red "upgrade failed" banner (capability refusal is guidance, not an error)
52
+
53
+ - **Root cause**: the 0.9.1 upgrade chip always calls `self-upgrade`; the Desktop capability table refuses by design with a structured 409, but the client rendered that 409 like any other failure — a red "upgrade failed" banner. Per the capability table this is not a failure, it's a pointer to the official entry.
54
+ - **Fix**: `api()` now forwards the structured refusal fields (code/action/profile/guidance); on a 409 the chip click shows a **neutral info banner** with the official-entry guidance (the guidance text's single source stays server-side in `active-profile.ts`, zero duplication in the client); info banners stay for 12s. Real failures on Web still use the red error banner — unchanged.
55
+
56
+ ### Fixed in 0.9.6 — "Clear finished" silently no-opped on failed records
57
+
58
+ - **Root cause**: the 0.7.0 review consensus excluded failed/superseded records from clearing (kept for review), so clicking the button against a failed record did nothing with zero feedback — reported as a bug from the real Windows host (2026-10-01). An explicit clear click is not a "silent wipe"; the old consensus is overruled.
59
+ - **Fix**: "Clear ended" (formerly "Clear finished") now clears **all terminal states** (done/warned/failed/superseded); in-flight records (queued/running/input) are untouched. When there is nothing terminal to clear, the button is disabled with an explanatory tooltip instead of silently no-oping. The per-row ✕ still removes single records.
60
+ - English copy updated accordingly.
61
+
51
62
  ### Fixed in 0.9.5 — GUI read paths missed the active profile on Desktop (Installed tab always showed web)
52
63
 
53
64
  - **Root cause**: the 0.9.0 dual-profile wiring missed the `installed` / `market` read paths in the Host API — `listInstalledWithMeta` / `listMarket` fell back to `webProfileDir()`: on Desktop the Installed tab always said "no plugins installed in this web profile" and the market "installed" badges stayed empty (the agent-tool surface `dshm_list` was wired correctly, so only the GUI was wrong).
package/README.md CHANGED
@@ -48,6 +48,17 @@
48
48
 
49
49
  以下重启链路仅适用于 **Web**(Desktop 的生效走官方应用生命周期:安装完成后退出并重新打开 Desktop 应用即可,见 0.9.0 节)。安装 / 卸载 / 升级完成后,当前已打开的市场页与已装页会一起重新读取 profile 状态并同步徽标/卡片,不需要关闭后重新打开插件市场;随后出现「⚡ 一键重启」横幅——受 systemd 管理时通过 DSH launcher 的 `appExit` 交给服务的 `Restart` 策略,避免在待停止 unit 的 cgroup 内启动 `systemctl` helper;无 `appExit` 的 systemd 兜底改用 manager-owned transient `systemd-run`,最后才退回 detached-helper。客户端按 boot id 确认新进程已恢复后关闭横幅,交由 DSH Web 自身的后台连接重试恢复页面,不强制整页刷新,避免认证/路由切换期间白屏。重启链路已在当前 DSH Web `0.2.0-rc.2` 实机核验(2026-10-01):dsh-m 探测 unit `Restart=` 策略后经 `appExit` 交还 systemd,`status=75/TEMPFAIL` 退出由 Restart 策略接住自动拉起,服务恢复后页面后台重连、面板全功能可用。历史口径:`0.1.5-rc.1` 时代已核验 `/dshm` ping 与带认证 `303 → 200`;`0.1.2-rc.1` 按契约核对 + 形态探测兜底收录(verified 数组),部署面演进后不再追旧代 live E2E;transient `systemd-run` 兜底仅适用于无 `appExit` 的宿主(受支持代际均提供 `appExit`),保持设计兜底而非发布门槛;连续安装/卸载由 profile 事务测试面(补偿事务、装后守卫等 813 用例)与 0.4.0→0.4.2 连续发布实证覆盖。安装过程实时显示 pnpm 进度(解析 → 下载 → 链接 → 构建)。
50
50
 
51
+ ### 0.9.7 修复:Desktop 下点升级角标弹红色「升级失败」(能力表拒绝应为指导而非报错)
52
+
53
+ - **根因**:0.9.1 的升级角标点击后一律调 `self-upgrade`;Desktop 能力表按设计 409 结构化拒绝,但客户端把 409 当普通失败渲染成红色「升级失败」横幅——按能力表这根本不是失败,是「该走官方入口」的指引。
54
+ - **修复**:`api()` 透传能力表拒绝的结构化字段(code/action/profile/guidance),角标点击收到 409 时改出**中性 info 横幅**展示官方入口指引(guidance 单一事实源仍在服务端 `active-profile.ts`,客户端零复制);info 横幅停留 12s。Web 端真实失败仍走红色 err 横幅,行为不变。
55
+
56
+ ### 0.9.6 修复:「清除已完成」对失败记录无声 no-op
57
+
58
+ - **根因**:0.7.0 评审共识把「失败/已跳过」排除在清除范围外(保留供回看),于是按钮对着一条失败记录点击毫无反应、也无任何反馈——Windows 实机被当成 bug 上报(2026-10-01)。显式点击清除不是「静默抹掉」,旧共识被推翻。
59
+ - **修复**:「清除已结束」(原「清除已完成」)现在清除**全部终态**(done/warned/failed/superseded),在途态(queued/running/input)不受影响;没有可清终态时按钮置灰并带说明 tooltip,不再无声 no-op。单条 ✕ 照旧逐条删除。
60
+ - **英文文案**同步改为 "Clear ended"。
61
+
51
62
  ### 0.9.5 修复:Desktop 下 GUI 读路径漏接 active profile(已装页恒显 web)
52
63
 
53
64
  - **根因**:0.9.0 双 profile 接线时,Host API 的 `installed` / `market` 两个读路径漏传 active profile——`listInstalledWithMeta` / `listMarket` 内部落回 `webProfileDir()`:Desktop 下已装页恒显「web profile 尚未安装任何插件」、市场「已安装」徽标恒空(agent 工具面 `dshm_list` 同链路已接线,故只有 GUI 错)。
package/lib/client.js CHANGED
@@ -608,6 +608,7 @@ var init_view_refresh = __esm({
608
608
  var operations_exports = {};
609
609
  __export(operations_exports, {
610
610
  OP_STORAGE_KEY: () => OP_STORAGE_KEY,
611
+ TERMINAL_CLEARABLE: () => TERMINAL_CLEARABLE,
611
612
  createOperationsStore: () => createOperationsStore,
612
613
  createOpsPump: () => createOpsPump,
613
614
  drainRestored: () => drainRestored,
@@ -683,7 +684,7 @@ function createOperationsStore(storage) {
683
684
  persist();
684
685
  return this.list();
685
686
  },
686
- /** 只清 done/warned;failed/superseded 保留供回看(评审共识:不把失败静默抹掉)。 */
687
+ /** 清除全部终态(done/warned/failed/superseded);queued/running/input 等在途态不受影响。 */
687
688
  clearFinished() {
688
689
  records = records.filter((r) => !TERMINAL_CLEARABLE.has(r.status));
689
690
  persist();
@@ -862,7 +863,7 @@ var init_operations = __esm({
862
863
  "src/client/operations.js"() {
863
864
  "use strict";
864
865
  OP_STORAGE_KEY = "dshm-operations";
865
- TERMINAL_CLEARABLE = /* @__PURE__ */ new Set(["done", "warned"]);
866
+ TERMINAL_CLEARABLE = /* @__PURE__ */ new Set(["done", "warned", "failed", "superseded"]);
866
867
  }
867
868
  });
868
869
 
@@ -1048,7 +1049,7 @@ var { toggleViewModel: toggleViewModel2, toggleNoticeKeys: toggleNoticeKeys2 } =
1048
1049
  var { pickPayload: pickPayload2, parseToolArgs: parseToolArgs2 } = (init_tool_view(), __toCommonJS(tool_view_exports));
1049
1050
  var { RESTART_POLL_MS: RESTART_POLL_MS2, RESTART_DEADLINE_MS: RESTART_DEADLINE_MS2, nextRestartWait: nextRestartWait2, isAmbiguousRestartRequestError: isAmbiguousRestartRequestError2 } = (init_restart_wait(), __toCommonJS(restart_wait_exports));
1050
1051
  var { refreshAfterMutation: refreshAfterMutation2 } = (init_view_refresh(), __toCommonJS(view_refresh_exports));
1051
- var { createOperationsStore: createOperationsStore2, restoreRecords: restoreRecords2, createOpsPump: createOpsPump2, opAppliesTo: opAppliesTo2 } = (init_operations(), __toCommonJS(operations_exports));
1052
+ var { createOperationsStore: createOperationsStore2, restoreRecords: restoreRecords2, createOpsPump: createOpsPump2, opAppliesTo: opAppliesTo2, TERMINAL_CLEARABLE: TERMINAL_CLEARABLE2 } = (init_operations(), __toCommonJS(operations_exports));
1052
1053
  var { createFavoritesStore: createFavoritesStore2, partitionStale: partitionStale2 } = (init_favorites(), __toCommonJS(favorites_exports));
1053
1054
  var { readSelfCheckCache: readSelfCheckCache2, writeSelfCheckCache: writeSelfCheckCache2, clearSelfCheckCache: clearSelfCheckCache2, deriveChipState: deriveChipState2 } = (init_self_check(), __toCommonJS(self_check_exports));
1054
1055
  var ZH = {
@@ -1079,7 +1080,8 @@ var ZH = {
1079
1080
  "modal.installcmd": "\u5B89\u88C5\u547D\u4EE4",
1080
1081
  "modal.copy": "\u590D\u5236",
1081
1082
  "modal.copied": "\u5DF2\u590D\u5236",
1082
- "op.clear": "\u6E05\u9664\u5DF2\u5B8C\u6210",
1083
+ "op.clear": "\u6E05\u9664\u5DF2\u7ED3\u675F",
1084
+ "op.clear.none": "\u6CA1\u6709\u53EF\u6E05\u9664\u7684\u5DF2\u7ED3\u675F\u8BB0\u5F55",
1083
1085
  "op.superseded.note": "{target} \u5DF2\u8DF3\u8FC7\uFF08\u524D\u63D0\u5DF2\u4E0D\u6210\u7ACB\u6216\u5DF2\u624B\u52A8\u5904\u7406\uFF09",
1084
1086
  "op.cancelled": "\u7528\u6237\u653E\u5F03\u786E\u8BA4",
1085
1087
  "op.remove": "\u79FB\u9664\u8BB0\u5F55",
@@ -1339,7 +1341,8 @@ var EN = {
1339
1341
  "modal.installcmd": "Install command",
1340
1342
  "modal.copy": "Copy",
1341
1343
  "modal.copied": "Copied",
1342
- "op.clear": "Clear finished",
1344
+ "op.clear": "Clear ended",
1345
+ "op.clear.none": "Nothing finished to clear",
1343
1346
  "op.superseded.note": "{target} skipped (precondition gone or already handled)",
1344
1347
  "op.cancelled": "user cancelled",
1345
1348
  "op.remove": "Dismiss",
@@ -1842,6 +1845,9 @@ async function api(method, params, signal) {
1842
1845
  if (!res.ok || data.ok === false) {
1843
1846
  const err = new Error(data.error || `API ${res.status}`);
1844
1847
  if (data && typeof data === "object" && data.issue) err.issue = data.issue;
1848
+ if (data && typeof data === "object" && data.code === "unsupported-on-profile") {
1849
+ err.unsupported = { action: data.action, profile: data.profile, guidance: typeof data.guidance === "string" ? data.guidance : "" };
1850
+ }
1845
1851
  if (data && typeof data === "object" && data.kind && Array.isArray(data.violations)) {
1846
1852
  err.guard = { kind: data.kind, violations: data.violations, compensation: data.compensation, needsRestart: data.needsRestart === true, restartSafe: data.restartSafe === true, repairBasis: data.repairBasis };
1847
1853
  }
@@ -3326,6 +3332,7 @@ function OperationsPanel({ records, onClearFinished, onRemove }) {
3326
3332
  const active = records.filter((r) => r.status === "queued" || r.status === "running" || r.status === "input");
3327
3333
  const finished = records.filter((r) => active.indexOf(r) < 0).slice(-6);
3328
3334
  if (!active.length && !finished.length) return null;
3335
+ const clearable = records.some((r) => TERMINAL_CLEARABLE2.has(r.status));
3329
3336
  const row = (r) => h(
3330
3337
  "div",
3331
3338
  { key: r.id, className: `dsvm-oprow ${OP_STATUS_CLS[r.status] || ""}` },
@@ -3346,7 +3353,7 @@ function OperationsPanel({ records, onClearFinished, onRemove }) {
3346
3353
  "div",
3347
3354
  { className: "dsvm-opgroup done" },
3348
3355
  ...finished.map(row),
3349
- h("button", { className: "dshm-btn sm", onClick: onClearFinished }, lookup("op.clear"))
3356
+ h("button", { className: "dshm-btn sm", disabled: !clearable, title: clearable ? void 0 : lookup("op.clear.none"), onClick: onClearFinished }, lookup("op.clear"))
3350
3357
  ) : null
3351
3358
  );
3352
3359
  }
@@ -3388,7 +3395,11 @@ function DshmVersionChip({ version, notify, initialCheck }) {
3388
3395
  text: lookup("notify.selfupgraded", { from: version, to: res.version || "latest" }) + (res.buildApprovals && res.buildApprovals.length ? lookup("notify.builds", { names: res.buildApprovals.join(", ") }) : res.fallbackAllBuilds ? lookup("notify.builds.fallback") : "")
3389
3396
  });
3390
3397
  } catch (e) {
3391
- notify({ kind: "err", text: lookup("notify.selfupgraded.failed", { err: e && e.message || e }) });
3398
+ if (e && e.unsupported) {
3399
+ notify({ kind: "info", text: e.unsupported.guidance || e && e.message || String(e) });
3400
+ } else {
3401
+ notify({ kind: "err", text: lookup("notify.selfupgraded.failed", { err: e && e.message || e }) });
3402
+ }
3392
3403
  } finally {
3393
3404
  setBusy(false);
3394
3405
  }
@@ -3582,7 +3593,7 @@ function MarketPanel({ onClose }) {
3582
3593
  }, [onClose]);
3583
3594
  useEffect(() => {
3584
3595
  if (!toast) return;
3585
- const t = setTimeout(() => setToast(null), toast.kind === "err" ? 15e3 : 6e3);
3596
+ const t = setTimeout(() => setToast(null), toast.kind === "err" ? 15e3 : toast.kind === "info" ? 12e3 : 6e3);
3586
3597
  return () => clearTimeout(t);
3587
3598
  }, [toast]);
3588
3599
  const notify = useCallback(({ kind, text, needsRestart }) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-m",
3
- "version": "0.9.5",
3
+ "version": "0.9.7",
4
4
  "description": "可自定义收录清单的 DSH 插件市场:收录、安装、卸载、升级一站完成,附 agent 工具与 dshm CLI。",
5
5
  "license": "MIT",
6
6
  "type": "module",