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 +12 -0
- package/lib/client.js +69 -14
- package/lib/core/external-journal.js +12 -0
- package/lib/core/host-api.js +10 -1
- package/package.json +1 -1
- package/registry.json +7 -7
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)
|
|
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
|
|
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
|
-
|
|
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 保留
|
|
4968
|
-
//
|
|
4969
|
-
canRemoveRow2(r) && onRemove ? h("button", { className: "dshm-xbtn", title: lookup("op.remove"), onClick: () => onRemove(r
|
|
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: (
|
|
5557
|
-
|
|
5558
|
-
|
|
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
|
|
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
|
-
() =>
|
|
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'(审计上漏报更糟,未知调用方落兜底而非丢弃);
|
package/lib/core/host-api.js
CHANGED
|
@@ -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
package/registry.json
CHANGED
|
@@ -205,15 +205,15 @@
|
|
|
205
205
|
"homepage": "https://github.com/meyaomiao/dsh-server-deck"
|
|
206
206
|
},
|
|
207
207
|
{
|
|
208
|
-
"id": "
|
|
209
|
-
"name": "
|
|
210
|
-
"description": "
|
|
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": "
|
|
215
|
-
"github": "
|
|
216
|
-
"homepage": "https://github.com/
|
|
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",
|