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 +11 -0
- package/README.md +11 -0
- package/lib/client.js +19 -8
- package/package.json +1 -1
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
|
-
/**
|
|
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\
|
|
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
|
|
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
|
-
|
|
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 }) => {
|