dsh-m 0.9.4 → 0.9.6

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,18 @@ 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.6 — "Clear finished" silently no-opped on failed records
52
+
53
+ - **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.
54
+ - **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.
55
+ - English copy updated accordingly.
56
+
57
+ ### Fixed in 0.9.5 — GUI read paths missed the active profile on Desktop (Installed tab always showed web)
58
+
59
+ - **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).
60
+ - **Fix**: both now pass `profileDir: profile.dir` + `profile: profile.name` (same as the tools surface; profileContext as the single source of truth); registry/community cache segments follow the `profile` parameter automatically.
61
+ - **Copy**: the hardcoded "web profile" in the installed empty/loading states and the profile hint is now profile-neutral (the actual path is still shown).
62
+
51
63
  ### Fixed in 0.9.4 — Fullscreen header eaten by the Windows Desktop titlebar (tabs / restore unreachable)
52
64
 
53
65
  - **Root cause**: on Windows Desktop the shell runs `titleBarStyle:hidden + titleBarOverlay` — the top 40px of the window is a full-width `-webkit-app-region:drag` band (drag hits are decided by layout and ignore z-index / paint order), and the OS-drawn — □ ✕ caption buttons float above everything. The maximized panel header landed inside that band: tab clicks were swallowed by window dragging and the restore/close buttons were covered by the system buttons — no way back.
package/README.md CHANGED
@@ -48,6 +48,18 @@
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.6 修复:「清除已完成」对失败记录无声 no-op
52
+
53
+ - **根因**:0.7.0 评审共识把「失败/已跳过」排除在清除范围外(保留供回看),于是按钮对着一条失败记录点击毫无反应、也无任何反馈——Windows 实机被当成 bug 上报(2026-10-01)。显式点击清除不是「静默抹掉」,旧共识被推翻。
54
+ - **修复**:「清除已结束」(原「清除已完成」)现在清除**全部终态**(done/warned/failed/superseded),在途态(queued/running/input)不受影响;没有可清终态时按钮置灰并带说明 tooltip,不再无声 no-op。单条 ✕ 照旧逐条删除。
55
+ - **英文文案**同步改为 "Clear ended"。
56
+
57
+ ### 0.9.5 修复:Desktop 下 GUI 读路径漏接 active profile(已装页恒显 web)
58
+
59
+ - **根因**:0.9.0 双 profile 接线时,Host API 的 `installed` / `market` 两个读路径漏传 active profile——`listInstalledWithMeta` / `listMarket` 内部落回 `webProfileDir()`:Desktop 下已装页恒显「web profile 尚未安装任何插件」、市场「已安装」徽标恒空(agent 工具面 `dshm_list` 同链路已接线,故只有 GUI 错)。
60
+ - **修复**:两处补齐 `profileDir: profile.dir` + `profile: profile.name`(与 tools 面同款,profileContext 单一事实源);registry/社区缓存随 `profile` 参数自动落到 desktop 段。
61
+ - **文案**:已装空态/加载态与 profile 提示里的「web profile」硬编码改为 profile 中性表述(实际路径照旧展示)。
62
+
51
63
  ### 0.9.4 修复:Windows Desktop 全屏后头部不可点(tab / 还原键被系统标题栏吞掉)
52
64
 
53
65
  - **根因**:Desktop(Windows)以 `titleBarStyle:hidden + titleBarOverlay` 运行——窗口顶部 40px 是壳的全宽 `-webkit-app-region:drag` 拖拽带(按布局参与拖拽判定、无视 z-index 与绘制顺序),右上角另有系统绘制的 — □ ✕ 悬浮于一切内容之上。面板全屏后头部(tab、还原/关闭键)正好落进这条带:点击被窗口拖拽吞掉、还原键被系统键遮挡,全屏无法退出。
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",
@@ -1111,8 +1113,8 @@ var ZH = {
1111
1113
  "common.none": "\u2014",
1112
1114
  "market.loading": "\u52A0\u8F7D\u6536\u5F55\u6E05\u5355\u4E2D\u2026 ",
1113
1115
  "market.empty": "\u65E0\u5339\u914D\u63D2\u4EF6\uFF0C\u8BD5\u8BD5\u5176\u4ED6\u5173\u952E\u8BCD\u6216\u5206\u7C7B",
1114
- "installed.loading": "\u8BFB\u53D6 web profile \u4E2D\u2026 ",
1115
- "installed.empty": "web profile \u5C1A\u672A\u5B89\u88C5\u4EFB\u4F55 dsh \u63D2\u4EF6",
1116
+ "installed.loading": "\u8BFB\u53D6\u5DF2\u88C5\u5217\u8868\u4E2D\u2026 ",
1117
+ "installed.empty": "\u5F53\u524D profile \u5C1A\u672A\u5B89\u88C5\u4EFB\u4F55 dsh \u63D2\u4EF6",
1116
1118
  "installed.none": "\u672A\u5B89\u88C5",
1117
1119
  "installed.others": "\u53E6\u6709 {n} \u4E2A\u975E dsh \u4F9D\u8D56\uFF08\u672A\u8BC6\u522B\u4E3A\u63D2\u4EF6\uFF09\uFF0C\u5DF2\u9ED8\u8BA4\u6298\u53E0\u3002",
1118
1120
  "installed.upgradeAll": "\u5168\u90E8\u66F4\u65B0 ({n})",
@@ -1307,7 +1309,7 @@ var ZH = {
1307
1309
  "readme.truncated": "\u2026\uFF08\u8D85\u8FC7 64KB \u5DF2\u622A\u65AD\uFF0C\u5B8C\u6574\u5185\u5BB9\u89C1\u63D2\u4EF6\u76EE\u5F55\uFF09",
1308
1310
  "warn.unlink": "\u5378\u8F7D\u53EA\u79FB\u9664 profile \u5BF9\u672C\u5730\u76EE\u5F55\u7684\u5F15\u7528\uFF08{path}\uFF09\uFF0C\u4E0D\u4F1A\u5220\u9664\u76EE\u5F55\u672C\u8EAB\u3002",
1309
1311
  "warn.core": "\u8FD9\u662F file: \u5B89\u88C5\u7684\u6838\u5FC3/\u5F52\u6863\u5305\uFF0C\u5378\u8F7D\u53EF\u80FD\u5F71\u54CD DSH \u529F\u80FD\uFF0C\u4E14\u9700\u8981\u624B\u52A8\u6062\u590D\u3002",
1310
- "profile.hint": "web profile\uFF1A{path}",
1312
+ "profile.hint": "profile\uFF1A{path}",
1311
1313
  "title.panel": "\u63D2\u4EF6\u5E02\u573A",
1312
1314
  "title.full": "DeepSeek Harness \u63D2\u4EF6\u5E02\u573A"
1313
1315
  };
@@ -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",
@@ -1371,8 +1374,8 @@ var EN = {
1371
1374
  "common.none": "\u2014",
1372
1375
  "market.loading": "Loading listings\u2026 ",
1373
1376
  "market.empty": "No matching plugins \u2014 try another keyword or category",
1374
- "installed.loading": "Reading web profile\u2026 ",
1375
- "installed.empty": "No DSH plugins installed in this web profile",
1377
+ "installed.loading": "Reading installed list\u2026 ",
1378
+ "installed.empty": "No DSH plugins installed in this profile",
1376
1379
  "installed.none": "Not installed",
1377
1380
  "installed.others": "{n} non-DSH dependencies (not recognized as plugins) are collapsed.",
1378
1381
  "installed.upgradeAll": "Update all ({n})",
@@ -1567,7 +1570,7 @@ var EN = {
1567
1570
  "readme.truncated": "\u2026(truncated at 64KB \u2014 see the plugin directory for full content)",
1568
1571
  "warn.unlink": "Uninstalling only removes the profile's reference to the local directory ({path}); the directory itself is kept.",
1569
1572
  "warn.core": "This is a core/archive package installed via file:. Uninstalling may affect DSH features and requires manual restore.",
1570
- "profile.hint": "web profile: {path}",
1573
+ "profile.hint": "profile: {path}",
1571
1574
  "title.panel": "Plugin Marketplace",
1572
1575
  "title.full": "DeepSeek Harness Plugin Marketplace"
1573
1576
  };
@@ -3326,6 +3329,7 @@ function OperationsPanel({ records, onClearFinished, onRemove }) {
3326
3329
  const active = records.filter((r) => r.status === "queued" || r.status === "running" || r.status === "input");
3327
3330
  const finished = records.filter((r) => active.indexOf(r) < 0).slice(-6);
3328
3331
  if (!active.length && !finished.length) return null;
3332
+ const clearable = records.some((r) => TERMINAL_CLEARABLE2.has(r.status));
3329
3333
  const row = (r) => h(
3330
3334
  "div",
3331
3335
  { key: r.id, className: `dsvm-oprow ${OP_STATUS_CLS[r.status] || ""}` },
@@ -3346,7 +3350,7 @@ function OperationsPanel({ records, onClearFinished, onRemove }) {
3346
3350
  "div",
3347
3351
  { className: "dsvm-opgroup done" },
3348
3352
  ...finished.map(row),
3349
- h("button", { className: "dshm-btn sm", onClick: onClearFinished }, lookup("op.clear"))
3353
+ h("button", { className: "dshm-btn sm", disabled: !clearable, title: clearable ? void 0 : lookup("op.clear.none"), onClick: onClearFinished }, lookup("op.clear"))
3350
3354
  ) : null
3351
3355
  );
3352
3356
  }
@@ -343,6 +343,10 @@ export function createApiDispatcher(ctx) {
343
343
  force: boolArg(body.force),
344
344
  withLatest: true,
345
345
  namespace: 'host',
346
+ // 0.9.5 双 profile 读路径接线(实机回归 2026-10-01):desktop 下市场「已安装」
347
+ // 徽标此前漏传 profile → listMarket 内部落回 webProfileDir,已装徽标恒空。
348
+ profileDir: profile.dir,
349
+ profile: profile.name,
346
350
  signal,
347
351
  });
348
352
  payload = { ...result };
@@ -350,7 +354,15 @@ export function createApiDispatcher(ctx) {
350
354
  }
351
355
  case 'installed': {
352
356
  await ctx.controller.ensureReady();
353
- const result = await d.listInstalledWithMeta(cfg(), { namespace: 'host', signal });
357
+ // 0.9.5 双 profile 读路径接线(实机回归 2026-10-01):desktop 下已装列表此前
358
+ // 漏传 profile → 落回 webProfileDir,已装页恒显「web profile 尚未安装任何插件」。
359
+ // 与 tools.ts dshm_list 同款接线(profile.dir 单一事实源)。
360
+ const result = await d.listInstalledWithMeta(cfg(), {
361
+ namespace: 'host',
362
+ profileDir: profile.dir,
363
+ profile: profile.name,
364
+ signal,
365
+ });
354
366
  payload = { ...result };
355
367
  break;
356
368
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-m",
3
- "version": "0.9.4",
3
+ "version": "0.9.6",
4
4
  "description": "可自定义收录清单的 DSH 插件市场:收录、安装、卸载、升级一站完成,附 agent 工具与 dshm CLI。",
5
5
  "license": "MIT",
6
6
  "type": "module",