dsh-m 0.9.80 → 0.9.82

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/CHANGELOG.md CHANGED
@@ -8,6 +8,12 @@ The full release history of dsh-m, maintained bilingually: **Chinese first, Engl
8
8
 
9
9
  ## 中文
10
10
 
11
+ ### 0.9.82 功能:外部操作记录 failed 行单条移除——红点死锁解除(ADR-0018 修订)
12
+
13
+ - **问题(红点死锁 + 操作层与展示层不对齐)**:外部记录(CLI / Agent 工具通道)失败后点亮设置 tab 红点,但外部行没有单条 ✕——0.9.79 的原裁决因服务端只有 list/clear 而不给出 ✕ 出口;唯一消除手段「清除已结束」连带清空全部终态记录(含成功历史),行为上等同永久亮灯,警报疲劳会让红点彻底失效。展示层(渠道徽标、按日分组、计数)0.9.79 起已与本地记录同构,唯独操作层掉队。
14
+ - **方案(服务端 per-id 删除链路 + 客户端分流接线)**:core 新增 `removeExternalOperation(id)`(整文件读-改-写原子替换,幂等——id 不存在静默 no-op,保留记录原样透写不改写历史字段);host-api 新增 `external-operations-remove` method(body.id string 校验 400,鉴权同 list/clear 零新增特例);store 新增 `removeExternal(id)`(overlay 剔除 + **墓碑** `removedIds`——拦截在途 fetch 迟到响应把已删行并回,`clearExternal` 重置);`canRemoveRow` 对外部记录放行 `failed`(done 仍不给 ✕,与面板「问题态才有 ✕」语义精确对齐);`onOpsRemove` 按渠道分流——本地路径逐字节不变(R0 红线),外部路径**先 api 成功再动 overlay**(事务顺序强于「清除已结束」的 fire-and-forget),失败行保留 + err toast(`op.remove.failed` 新键,{err} 插值带出服务端错误线索,zh/en 同步)。单条 ✕ 无二次确认(与面板一致)。ADR-0018 文末追加修订注记,原裁决原文保留。
15
+ - **验证**:1435 项测试全绿(0.9.80 基线 1425 + 净增 10:journal 存储 3、host-api method 4、store removeExternal+墓碑 2、canRemoveRow 改写与接线锚 2,含一处既有用例标题改写)、typecheck 零错误;实施计划经独立评审 Agent 三轮评审收敛(探针路径/链接期报错形态/墓碑兜底/断言保全/401 惯例/插值链路/回退口径 11 条意见全部闭环)。
16
+
11
17
  ### 0.9.80 修复:已装页开关抖动根治 + 社区开关定位恒定 + 「已实测」徽章退役(三项 UX 修复,client-only)
12
18
 
13
19
  - **① 已装页开关抖动根治(主人实拍:点开关整页抖、开关原地失踪、鼠标不再命中)**:CDP 实测每次点击开关发生 47px×2 卡片栅格位移 + 0.6–1.3s 命中丢失窗口(原点落到上方卡片 `.dshm-links` 外链行,误触即开浏览器标签)。根因三件:`ProgressLine`(install 专用 pnpm 进度条)对 toggle 也挂载(35px 行 + body 12px gap,插入/拔出即位移);toast/重启横幅是面板 column flex 的流内子元素(出现/消失压缩 body 39px×2);开关视觉翻转要等 5 个刷新请求落地(1.5–2.3s「点了没反应」真空期,且期间开关 `locked` 灰态)。修复:新增 `progressTarget`(仅 install/upgrade/self-upgrade 挂进度条;`activeMutateTarget` 语义不动,仍管逐行禁用);toast/横幅包进 `.dshm-banner-stack`(absolute 底部浮层,pointer-events 穿透);`pendingToggles` 乐观翻转(点击 42ms 即变,`finally` 摘除=成功收敛/失败回滚);toggle 只重取已装视图(installed+market×2+updates+external-operations → installed×1,`view-refresh.js` 未动、单测原样);`.dshm-switch::before` 热区 34×19 → 46×31(视觉不变)。CDP 回测四断言全过:位移 0、命中零丢失、翻转 42ms、body 底缘 0 位移。
@@ -579,6 +585,12 @@ The full release history of dsh-m, maintained bilingually: **Chinese first, Engl
579
585
 
580
586
  ## English
581
587
 
588
+ ### 0.9.82 Feature — per-row dismiss for failed external operation records: the red-dot deadlock is broken (ADR-0018 revision)
589
+
590
+ - **Problem (red-dot deadlock + ops layer lagging behind the display layer)**: a failed external record (CLI / agent-tool channel) lights the settings-tab red dot but had no per-row dismiss — the 0.9.79 ruling withheld the ✕ because the server only exposed list/clear. The only way to clear the dot, "Clear ended", also wipes every finished record (successful history included) — behaviorally a permanent alert, and alert fatigue defeats the dot entirely. The display layer (channel badge, day grouping, counts) has been isomorphic with local records since 0.9.79; only the operations layer lagged.
591
+ - **Fix (server per-id delete chain + client channel branching)**: core gains `removeExternalOperation(id)` (whole-file read-modify-write atomic replace, idempotent — silent no-op for missing ids, surviving records written back verbatim); host-api gains the `external-operations-remove` method (body.id string-validated with 400, auth identical to list/clear — zero new special cases); the store gains `removeExternal(id)` (overlay removal + a **tombstone** set `removedIds` that intercepts in-flight fetch responses from re-adding just-deleted rows, reset by `clearExternal`); `canRemoveRow` lets external `failed` rows through (done still has no ✕, precisely matching the panel's problem-status-only rule); `onOpsRemove` branches by channel — the local path is byte-for-byte unchanged (R0 red line), the external path **awaits the server delete before touching the overlay**, and on failure keeps the row and toasts with the new `op.remove.failed` key ({err} interpolation carries the server error, zh/en). No confirmation dialog (same as the panel ✕). ADR-0018 gains a revision note; the original ruling text is preserved.
592
+ - **Verification**: 1435 tests green (0.9.80 baseline 1425 + 10 net new: journal storage 3, host-api method 4, store removeExternal+tombstone 2, canRemoveRow rewrite and wiring anchors 2, incl. one retitled existing case), typecheck clean; the implementation plan converged through three independent review-agent rounds (probe path / link-time error shape / tombstone backstop / assertion preservation / 401 convention / interpolation chain / rollback stance — 11 findings all closed).
593
+
582
594
  ### Fixes in 0.9.80 — installed-tab toggle jitter eliminated + community switch pinned to its title row + 已实测 badge retired (three UX fixes, client-only)
583
595
 
584
596
  - **① Installed-tab toggle jitter eliminated (user report: clicking the switch shook the whole page, the switch vanished in place, and the cursor no longer hit it)**: CDP measurement showed a 47px×2 card-grid shift per click plus a 0.6–1.3s hit-test loss window (the cursor's fixed point landed on the card above's `.dshm-links` external-links row — a mis-click opens a browser tab). Three root causes: `ProgressLine` (the install-only pnpm progress bar) mounted for toggles too (35px row + 12px body gap — inserting/unmounting it is the shift itself); the toast/restart banners were in-flow flex children of the panel column (mount/dismiss compressed the body by 39px×2); the switch visually flipped only after five refresh requests landed (a 1.5–2.3s "nothing happened" dead window, with the switch greyed to `locked`). Fixes: a new `progressTarget` gates ProgressLine to install/upgrade/self-upgrade (`activeMutateTarget` semantics untouched — it still drives per-row disabling); toasts/banners are wrapped in `.dshm-banner-stack` (absolute bottom overlay, pointer-events pass-through); optimistic `pendingToggles` flips the switch in 42ms (cleared in `finally` — converges on success, reverts to server state on failure); toggle refreshes the installed view only (installed+market×2+updates+external-operations → installed×1; `view-refresh.js` untouched, its unit tests unchanged); `.dshm-switch::before` grows the hit area 34×19 → 46×31 (visuals unchanged). CDP re-probe passes all four assertions: zero drift, zero hit-loss, 42ms flip, stable body bottom edge.
package/lib/client.js CHANGED
@@ -1404,6 +1404,7 @@ function createOperationsStore(storage) {
1404
1404
  let records = safeParse(s.read());
1405
1405
  let persistDegraded = false;
1406
1406
  let externalOverlay = [];
1407
+ const removedIds = /* @__PURE__ */ new Set();
1407
1408
  const persist = () => {
1408
1409
  if (persistDegraded) return;
1409
1410
  try {
@@ -1421,13 +1422,24 @@ function createOperationsStore(storage) {
1421
1422
  setExternal(next) {
1422
1423
  const incoming = (Array.isArray(next) ? next : []).filter((r) => r && typeof r === "object" && typeof r.id === "string");
1423
1424
  const byId = new Map(externalOverlay.map((r) => [r.id, r]));
1424
- for (const r of incoming) byId.set(r.id, { ...byId.get(r.id) || {}, ...r });
1425
+ for (const r of incoming) {
1426
+ if (removedIds.has(r.id)) continue;
1427
+ byId.set(r.id, { ...byId.get(r.id) || {}, ...r });
1428
+ }
1425
1429
  externalOverlay = [...byId.values()];
1426
1430
  return this.listMerged();
1427
1431
  },
1428
1432
  /** 清空外部 overlay(「清除已结束」联动 external-operations-clear 成功后调用)。 */
1429
1433
  clearExternal() {
1430
1434
  externalOverlay = [];
1435
+ removedIds.clear();
1436
+ return this.listMerged();
1437
+ },
1438
+ /** 按 id 剔除外部 overlay(0.9.82 修订 R2-3):仅内存视图——调用方必须已在服务端
1439
+ * 删除成功后调用(external-operations-remove);墓碑拦住在途 fetch 迟到响应的复活。 */
1440
+ removeExternal(id) {
1441
+ removedIds.add(id);
1442
+ externalOverlay = externalOverlay.filter((r) => r.id !== id);
1431
1443
  return this.listMerged();
1432
1444
  },
1433
1445
  /** @returns {OperationRecord[]} 外部 overlay 当前快照(原始形态,浅拷贝)。 */
@@ -1730,13 +1742,14 @@ function opRowVm(r) {
1730
1742
  dir: isToggle && typeof meta.on === "boolean" ? meta.on ? "on" : "off" : null,
1731
1743
  version,
1732
1744
  target: typeof rec.target === "string" ? rec.target : "",
1733
- // 外部记录渠道标记(0.9.79 ADR-0018):非空 = 渠道徽标渲染 + 无单条 ✕ 出口
1745
+ // 外部记录渠道标记(0.9.79 ADR-0018):非空 = 渠道徽标渲染 + 单条移除出口判定
1746
+ // (0.9.82 修订 R2-3:failed 放行,见 canRemoveRow)
1734
1747
  channel: typeof rec.channel === "string" && rec.channel !== "" ? rec.channel : null
1735
1748
  };
1736
1749
  }
1737
1750
  function canRemoveRow(r) {
1738
1751
  if (!r || typeof r !== "object") return false;
1739
- if (r.channel) return false;
1752
+ if (r.channel) return r.status === "failed";
1740
1753
  return r.status === "input" || r.status === "failed" || r.status === "superseded";
1741
1754
  }
1742
1755
  function channelLabelKey(channel) {
@@ -1997,6 +2010,7 @@ var ZH = {
1997
2010
  "op.superseded.note": "{target} \u5DF2\u8DF3\u8FC7\uFF08\u524D\u63D0\u5DF2\u4E0D\u6210\u7ACB\u6216\u5DF2\u624B\u52A8\u5904\u7406\uFF09",
1998
2011
  "op.cancelled": "\u7528\u6237\u653E\u5F03\u786E\u8BA4",
1999
2012
  "op.remove": "\u79FB\u9664\u8BB0\u5F55",
2013
+ "op.remove.failed": "\u8BB0\u5F55\u79FB\u9664\u5931\u8D25\uFF1A{err}",
2000
2014
  "op.panel.jump": "\u67E5\u770B\u64CD\u4F5C\u8BB0\u5F55",
2001
2015
  "op.kind.install": "\u5B89\u88C5",
2002
2016
  "op.kind.upgrade": "\u5347\u7EA7",
@@ -2296,6 +2310,7 @@ var EN = {
2296
2310
  "op.superseded.note": "{target} skipped (precondition gone or already handled)",
2297
2311
  "op.cancelled": "user cancelled",
2298
2312
  "op.remove": "Dismiss",
2313
+ "op.remove.failed": "Failed to dismiss record: {err}",
2299
2314
  "op.panel.jump": "Open operations card",
2300
2315
  "op.kind.install": "Install",
2301
2316
  "op.kind.upgrade": "Upgrade",
@@ -2907,8 +2922,12 @@ button.dshm-badge:hover{filter:brightness(.95)}
2907
2922
  .dshm-entry svg{flex:none;width:16px;height:16px}
2908
2923
  .dshm-entry span{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
2909
2924
  /* \u6536\u8D77\uFF08rail\uFF09\u6001\uFF1A\u4E0E\u5BBF\u4E3B\u8BBE\u7F6E\u5165\u53E3 / dsh-skins \u540C\u51E0\u4F55\u2014\u201436\xD736 \u5706\u5F62\u3001margin 8/10\u3001
2910
- \u56FE\u6807 18\u3001\u5185\u5BB9\u6C34\u5E73\u5C45\u4E2D\u3002\u7F3A\u8FD9\u5957\u4F1A\u5BFC\u81F4\u6536\u8D77\u65F6\u56FE\u6807\u504F\u79BB\u8F74\u7EBF\u4E14\u884C\u8DDD\u4E0D\u9F50\uFF08\u5B9E\u6D4B\u5DE6\u504F ~4px\uFF09\u3002 */
2911
- .dshm-entry.rail{width:36px;height:36px;margin:8px 0 10px;padding:0;justify-content:center;border-radius:50%;gap:0}
2925
+ \u56FE\u6807 18\u3001\u5185\u5BB9\u6C34\u5E73\u5C45\u4E2D\u3002\u7F3A\u8FD9\u5957\u4F1A\u5BFC\u81F4\u6536\u8D77\u65F6\u56FE\u6807\u504F\u79BB\u8F74\u7EBF\u4E14\u884C\u8DDD\u4E0D\u9F50\uFF08\u5B9E\u6D4B\u5DE6\u504F ~4px\uFF09\u3002
2926
+ 2026-10-14\uFF1A\u5DE6\u53F3 margin \u6539 auto\u2014\u2014\u69FD\u4F4D\u51FA\u53E3\u662F\u5217\u5411 flex\uFF0C36px \u5B9A\u5BBD\u6309\u94AE\u9ED8\u8BA4\u8D34\u4EA4\u53C9\u8F74
2927
+ \u8D77\u70B9\uFF08\u5DE6\u4FA7\uFF09\uFF1B\u684C\u9762 rail \u7A84\u53EA\u6709 ~4px \u53EF\u611F\u77E5\uFF0C\u624B\u673A\u5BBD rail \u5DE6\u504F ~22px\uFF08\u624B\u673A\u622A\u56FE\u50CF\u7D20
2928
+ \u5B9E\u6D4B\uFF1A\u9F7F\u8F6E/\u8C03\u8272\u76D8/\u9CB8\u9C7C cx\u224899-100\uFF0C\u5E02\u573A\u56DB\u5BAB\u683C cx=77.5\uFF0Crail \u4E2D\u7EBF\u2248100\uFF09\u3002auto
2929
+ margin \u4E0E dsh-skins rail \u5305\u88C5\u5C42 justify-content:center \u7B49\u6548\uFF08palette cx=100.5\uFF09\u3002 */
2930
+ .dshm-entry.rail{width:36px;height:36px;margin:8px auto 10px;padding:0;justify-content:center;border-radius:50%;gap:0}
2912
2931
  .dshm-entry.rail svg{width:18px;height:18px}
2913
2932
  [data-slot="sidebar.footer.action"]{display:flex!important;flex-direction:column;width:100%;min-width:0}
2914
2933
  [data-slot="sidebar.footer.action"]>*{flex:none;min-width:0}
@@ -4963,10 +4982,10 @@ function OperationsCard({ records, onClearFinished, onRemove }) {
4963
4982
  r.status === "running" ? Spin() : null,
4964
4983
  r.error ? h("span", { className: "dsvm-opnote" }, r.error) : null,
4965
4984
  r.warning ? h("span", { className: "dsvm-opnote" }, r.warning) : null,
4966
- // 单条移除(终审·新伤2 → 0.9.79 R3-1/R2-3):出口判定下沉 canRemoveRow——
4967
- // 本地 input/failed/superseded 保留 ✕;外部记录(channel 非空)不给单条 ✕(防按 id 复活),
4968
- // 移除出口仅「清除已结束」。
4969
- canRemoveRow2(r) && onRemove ? h("button", { className: "dshm-xbtn", title: lookup("op.remove"), onClick: () => onRemove(r.id) }, h(XIcon)) : null
4985
+ // 单条移除(终审·新伤2 → 0.9.79 R3-1/R2-3 → 0.9.82 修订 R2-3):出口判定下沉 canRemoveRow——
4986
+ // 本地 input/failed/superseded 保留 ✕;外部记录 failed 放行(服务端 per-id delete 已建),
4987
+ // done 仍不给 ✕。onRemove 收整行 record,onOpsRemove 按 channel 分流(本地 remove / 外部 api)。
4988
+ canRemoveRow2(r) && onRemove ? h("button", { className: "dshm-xbtn", title: lookup("op.remove"), onClick: () => onRemove(r) }, h(XIcon)) : null
4970
4989
  );
4971
4990
  };
4972
4991
  return h(
@@ -5553,9 +5572,18 @@ function MarketPanel({ onClose }) {
5553
5572
  void api("external-operations-clear").catch(() => {
5554
5573
  });
5555
5574
  },
5556
- onOpsRemove: (id) => {
5557
- opsStore.remove(id);
5558
- syncOps();
5575
+ onOpsRemove: (rec) => {
5576
+ if (rec && rec.channel) {
5577
+ api("external-operations-remove", { id: rec.id }).then(() => {
5578
+ opsStore.removeExternal(rec.id);
5579
+ syncOps();
5580
+ }).catch((err) => {
5581
+ notify({ kind: "err", text: lookup("op.remove.failed", { err: err && err.message || "unknown" }) });
5582
+ });
5583
+ } else {
5584
+ opsStore.remove(rec.id);
5585
+ syncOps();
5586
+ }
5559
5587
  }
5560
5588
  }) : null
5561
5589
  // 操作记录已整体搬迁至设置 tab 末位通宽卡(0.9.x 布局重构·第二批):底部不再常驻
@@ -5616,8 +5644,17 @@ function MarketIcon() {
5616
5644
  h("rect", { x: "14", y: "14", width: "7", height: "7", rx: "1", stroke: "currentColor", strokeWidth: "2" })
5617
5645
  );
5618
5646
  }
5647
+ var dshmSlotsRef = null;
5648
+ var shellTakeover = () => {
5649
+ try {
5650
+ return ((dshmSlotsRef && dshmSlotsRef.entries ? dshmSlotsRef.entries("sidebar.workspaces") : []) || []).some((entry) => (entry.options.priority ?? 0) < 0);
5651
+ } catch {
5652
+ return false;
5653
+ }
5654
+ };
5619
5655
  function MarketEntry(props) {
5620
5656
  useEffect(() => ensureCss(), []);
5657
+ if (shellTakeover()) return null;
5621
5658
  const rail = !(props && props.wide);
5622
5659
  return h(
5623
5660
  "button",
@@ -5636,11 +5673,22 @@ function MarketEntry(props) {
5636
5673
  props && props.wide ? h("span", null, lookup("market.title")) : null
5637
5674
  );
5638
5675
  }
5676
+ function safeRegister(slots, options, component) {
5677
+ const label = `${options.name}${options.key != null ? `:${options.key}` : options.id != null ? `#${options.id}` : ""}`;
5678
+ try {
5679
+ return slots.register(options, component);
5680
+ } catch (error) {
5681
+ if (!/already has a registration|already has an entry/.test(String(error && error.message || error))) throw error;
5682
+ console.warn(`[dsh-m] slot "${label}" is contested; dsh-m yields it and keeps running`, error);
5683
+ return () => {
5684
+ };
5685
+ }
5686
+ }
5639
5687
  function registerSlot(slots, options, component) {
5640
5688
  const next = { ...options };
5641
5689
  if (next.id == null && next.key != null) next.id = String(next.key);
5642
5690
  if (next.key == null && next.id != null) next.key = next.id;
5643
- return slots.register(next, component);
5691
+ return safeRegister(slots, next, component);
5644
5692
  }
5645
5693
  function ToolCardRow({ it, onInstalled }) {
5646
5694
  const [busy, setBusy] = useState(false);
@@ -5754,6 +5802,7 @@ var inject = ["slots"];
5754
5802
  function apply(ctx) {
5755
5803
  const slots = ctx.slots;
5756
5804
  if (!slots) return;
5805
+ dshmSlotsRef = slots;
5757
5806
  ctx.effect(() => ensureCss(), "dshm-style");
5758
5807
  ctx.inject(["locale"], (c) => {
5759
5808
  if (!c.locale || typeof c.locale.register !== "function") return;
@@ -5768,7 +5817,13 @@ function apply(ctx) {
5768
5817
  });
5769
5818
  slots.inject(
5770
5819
  "sidebar.footer.action",
5771
- () => slots.register(
5820
+ () => safeRegister(
5821
+ slots,
5822
+ // 页脚 order 维持正数工具位(9):bot 视图的让位由壳接管哨兵负责(该视图
5823
+ // 下 MarketEntry 整行不渲染),order 只影响官方 harness 页脚的排序——那里
5824
+ // DS Bot 入口(-200)应在本工具行之上。2026-10-13 曾试装深负 order
5825
+ // (-400/-500)让 ds-bot 集群贴底,哨兵上线后该做法只剩 harness 视图副作用
5826
+ // (DS Bot 入口沉底),已回撤(裁定:哨兵管让位,order 管排序,各司其职)。
5772
5827
  { name: "sidebar.footer.action", id: "dshm-market", key: "dshm-market", order: 9, locale: "dshm", label: () => lookup("market.title") },
5773
5828
  function DshmMarketEntry(actionProps) {
5774
5829
  return h(MarketEntry, actionProps);
@@ -105,6 +105,18 @@ export async function clearExternalOperations() {
105
105
  await mkdir(dirname(file), { recursive: true });
106
106
  await atomicWrite(file, '[]\n');
107
107
  }
108
+ /** 按 id 删除单条外部记录(0.9.82 修订 R2-3):幂等——id 不存在静默 no-op;
109
+ * 整文件读-改-写与 append 同款并发纪律(ADR-0018 Consequences 既有明示局限);
110
+ * 保留记录原样透写,不做 normalize 重写(删除不改写历史字段)。 */
111
+ export async function removeExternalOperation(id) {
112
+ const file = journalFile();
113
+ const records = await readAll(file);
114
+ const kept = records.filter((r) => r.id !== id);
115
+ if (kept.length === records.length)
116
+ return;
117
+ await mkdir(dirname(file), { recursive: true });
118
+ await atomicWrite(file, `${JSON.stringify(kept, null, 2)}\n`);
119
+ }
108
120
  /**
109
121
  * 九个公共变更入口共用的终态直落钩子(0.9.79,ADR-0018):
110
122
  * - channel 缺省 'external'(审计上漏报更糟,未知调用方落兜底而非丢弃);
@@ -12,7 +12,7 @@ import { resolveDshVersion, readLauncherPackageVersion } from './dsh-version.js'
12
12
  import { runDoctor } from './doctor.js';
13
13
  import { listInstalledWithMeta, listMarket, installFromRegistry, selfUpgrade, uninstallPlugin, upgradePlugin, InstallGuardError, } from './market.js';
14
14
  import { runProfileTransaction, TransactionError } from './profile-transaction.js';
15
- import { listExternalOperations, clearExternalOperations } from './external-journal.js';
15
+ import { listExternalOperations, clearExternalOperations, removeExternalOperation } from './external-journal.js';
16
16
  import { readInstalledPluginReadme } from './installed.js';
17
17
  import { loadPrimaryScreenshots } from './primary-screenshots.js';
18
18
  import { isNewerVersion, npmLatest, npmPackumentReadme } from './versions.js';
@@ -234,6 +234,7 @@ export function createApiDispatcher(ctx) {
234
234
  ensureService: ctx.deps?.ensureService,
235
235
  listExternalOperations: ctx.deps?.listExternalOperations ?? listExternalOperations,
236
236
  clearExternalOperations: ctx.deps?.clearExternalOperations ?? clearExternalOperations,
237
+ removeExternalOperation: ctx.deps?.removeExternalOperation ?? removeExternalOperation,
237
238
  };
238
239
  const cfg = () => ctx.controller.config;
239
240
  // 0.9.0:宿主信任检查委派(含 ping 与未知 method,fail-closed);
@@ -589,6 +590,14 @@ export function createApiDispatcher(ctx) {
589
590
  payload = {};
590
591
  break;
591
592
  }
593
+ case 'external-operations-remove': {
594
+ // 0.9.82 修订 R2-3:外部 failed 行单条 ✕ 的服务端出口;鉴权同 list/clear(零新增特例)
595
+ if (typeof body.id !== 'string' || body.id === '')
596
+ throw new ApiProtocolError(400, '缺少 id');
597
+ await d.removeExternalOperation(body.id);
598
+ payload = {};
599
+ break;
600
+ }
592
601
  case 'restart': {
593
602
  // 0.9.0:Desktop 不触发旧 helper(Electron 生命周期归官方;杀子进程=红线)
594
603
  assertWriteAllowed(profile, 'restart');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-m",
3
- "version": "0.9.80",
3
+ "version": "0.9.82",
4
4
  "description": "可自定义收录清单的 DSH 插件市场:收录、安装、卸载、升级一站完成,附 agent 工具与 dshm CLI。",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/registry.json CHANGED
@@ -205,15 +205,15 @@
205
205
  "homepage": "https://github.com/meyaomiao/dsh-server-deck"
206
206
  },
207
207
  {
208
- "id": "dsh-flowglass",
209
- "name": "DSH Flowglass",
210
- "description": "流镜:把当前会话画成三列泳道流程图,展示工具调用、并行分组与子代理分支;可选集成 better-sidebar。",
208
+ "id": "ds-bot",
209
+ "name": "DS Bot",
210
+ "description": "多 Bot 协作:把 DSH 变成常驻 Bot 团队,支持主 Bot、Bot 间消息与群聊。",
211
211
  "category": "watchlist",
212
- "tags": ["可视化", "流程图", "会话", "界面"],
212
+ "tags": ["Bot", "群聊", "多 Agent", "协作"],
213
213
  "source": "npm",
214
- "npm": "dsh-flowglass",
215
- "github": "Iwctwbh/dsh-flowglass",
216
- "homepage": "https://github.com/Iwctwbh/dsh-flowglass"
214
+ "npm": "ds-bot",
215
+ "github": "Fei2411/DeepSeek-Bot",
216
+ "homepage": "https://github.com/Fei2411/DeepSeek-Bot"
217
217
  },
218
218
  {
219
219
  "id": "dsh-chat-manager",