dsh-recall-plugin 2.4.11 → 2.4.12
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 +18 -0
- package/README.en.md +2 -2
- package/README.md +2 -2
- package/lib/client.js +78 -43
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
本文件格式遵循 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),版本号遵循语义化版本。
|
|
4
4
|
|
|
5
|
+
## [2.4.12] - 2026-10-16
|
|
6
|
+
|
|
7
|
+
### 修复
|
|
8
|
+
|
|
9
|
+
- **用户消息正文在 dsh 0.2.0-rc.x 上静默空白(issue #23)——判据重写为「旧面闭包在场即旧面」**:旧实现以 `renderFactorySlot` 是否存在判新面,而 0.2.0-rc.2 已下发该函数、`conversation.user.*` 槽位却尚未引入(0.2.1-alpha.3 才引入)——正文渲染成空、零报错(气泡只剩内边距,插件动作行照常)。`renderMessageImages` 只存在于 ≤0.2.1-alpha.2,与新槽位引入版本互斥,以其判定两面才可靠;0.2.0-rc.2 归旧面自绘、正文恢复。判据与投影单源在新增的 `src/client/user-content.ts`。
|
|
10
|
+
- **新面内容渲染从「分片投影」收敛为「整条内容单入口」**:文本/图片/文件卡片/未知块/引用摘要一次交给官方 `conversation.chat.user.content` Factory(投影=官方 `contentParts` 三分派的逐字复刻;该函数官方未导出),与官方 `UserMessageNodeView` 同款调用(含 `interactive: true` 对齐)——插件只保留动作行(时间/复制/撤回),气泡外壳/文件卡片/JSON 块/文本四类自绘在新面退场,官方规则链(密钥脱敏等)自动跟随。
|
|
11
|
+
- **兜底不静默**:官方 Factory 返回空或抛错(槽位未注册的中间版本)时回落自绘(文本/文件卡片/JSON;新面兜底无图片入口,跳过)并打 warn——「探测通过但渲染为空」不再可能产出空白正文。旧面(≤0.2.1-alpha.2)行为不变。
|
|
12
|
+
|
|
13
|
+
### 兼容
|
|
14
|
+
|
|
15
|
+
- **dsh 0.2.1-alpha.4 兼容(零破坏核验,无代码改动)**(升级核查见 [docs/upgrade-assessments/dsh-0.2.1-alpha.4.md](docs/upgrade-assessments/dsh-0.2.1-alpha.4.md) 与 [docs/compat-audit.md](docs/compat-audit.md) 头部 0.2.1-alpha.4 段):双 lockfile 包清单对比(第三方依赖零变化)+ 32 个关键包 `lib/` 逐字节 diff 确认插件消费/注册契约零漂移——`sessions.fork`(`dsh-session` 整包)、`dsh-shell`、`dsh-settings`、`dsh-session-query`、`dsh-session-persistence`、`dsh-cordis-host-runner`(host 插件加载器)、`dsh-client-ui-chat`(slot 定义方)等 22 包零变更。
|
|
16
|
+
- **本版唯一机制变更(`engines.dsh` 优先判定)不作用于本插件**:`dsh-app-boot` 兼容性门禁改为「manifest 声明 `engines.dsh` 则只查它并**短路** peerDependencies;未声明沿用 peer 判定」——本插件未声明 `engines.dsh`(`engines` 仅 `{node:">=20"}`)→ 走 peer 路径,六条 `@deepseek-ai/dsh-*` peer 对 0.2.1-alpha.4 全满足;**维持不声明**(多段逐线窗口无法用单段等价表达,声明即短路 peer 判定且放宽未验证版本,见 I43)。输入链「命令芯片」重构(`ui-conversation` 等 3 包,编辑器内部)不改变 `setDraft` / `createDrafts` / `releaseDraftAttachments` / `addAttachments` 签名与行为;三个 client 基础包(store / primitives / slots)内联化退出运行时树,对插件透明(bundle 仅 require react)。门禁:`test:probe` **53/53**、单测 **499/499**、client **93/93**、`verify:host` 装配断言全过、`typecheck`、`check:dsh` 全绿;`dsh.compatibility.dshReleases` 补 `0.2.1-alpha.4: compatible`。
|
|
17
|
+
|
|
18
|
+
### 文档
|
|
19
|
+
|
|
20
|
+
- `docs/reference/` 镜像按 tag `dsh-v0.2.1-alpha.4` 重拉:13 源中 12 份逐字节相同、仅 `05-publish.md` 差异(新增 `engines.dsh` 版本声明段,+365 B)。
|
|
21
|
+
- `docs/dsh-contract.md`(版本段与沿革 ⑨)/ `docs/compat-audit.md`(新增 I43)/ `AGENTS.md`(I43 索引行)/ README 双语兼容范围与徽章同步至 0.2.1-alpha.4。
|
|
22
|
+
|
|
5
23
|
## [2.4.11] - 2026-10-15
|
|
6
24
|
|
|
7
25
|
### 兼容
|
package/README.en.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|

|
|
8
8
|

|
|
9
9
|
|
|
10
|
-
[](https://github.com/deepseek-ai/deepseek-harness/releases/tag/dsh-v0.2.1-alpha.4)
|
|
11
11
|

|
|
12
12
|
|
|
13
13
|
Under any message you've sent, click "↶ Recall" — **your workspace files and the conversation history roll back to just before that message was sent**.
|
|
@@ -73,7 +73,7 @@ Prerequisites:
|
|
|
73
73
|
|
|
74
74
|
- git CLI: without it the recall button won't appear (a notice shows at the top of the page); DSH itself keeps running.
|
|
75
75
|
- Shell: PowerShell 5.1 / 7 on Windows; bash on Linux/macOS.
|
|
76
|
-
- DSH version: `0.1.2-alpha.1` through `0.2.1-alpha.
|
|
76
|
+
- DSH version: `0.1.2-alpha.1` through `0.2.1-alpha.4`; later versions within a verified minor line are admitted automatically, while unverified new minor lines are blocked by the startup compatibility gate (window mechanics in the fold below).
|
|
77
77
|
- **0.1.7-alpha.1 is a breaking release** (the shell execution and settings interfaces changed); the plugin ships both seams side by side — the same release works on every 0.1.2–0.1.6 line and on 0.1.7+, with unchanged behaviour on older DSH versions.
|
|
78
78
|
- `0.1.1-rc.2` and earlier are not supported: their client runtime lacks the `sessions`/`workspaces`/`uiWorkspace` services, so the plugin UI silently fails to render.
|
|
79
79
|
|
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](https://www.npmjs.com/package/dsh-recall-plugin)
|
|
6
6
|
[](https://www.npmjs.com/package/dsh-recall-plugin)
|
|
7
7
|

|
|
8
|
-
[](https://github.com/deepseek-ai/deepseek-harness/releases/tag/dsh-v0.2.1-alpha.4)
|
|
9
9
|
|
|
10
10
|
在任意一条你发过的消息下方点「↶ 撤回」——**工作区文件和对话历史一起回到那条消息发出之前的状态**。
|
|
11
11
|
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
|
|
71
71
|
- git CLI:未安装时撤回按钮不出现(页面顶部会提示安装 git),不影响 DSH 运行。
|
|
72
72
|
- shell:Windows 上 PowerShell 5.1 / 7 均可;Linux/macOS 需 bash。
|
|
73
|
-
- DSH 版本:`0.1.2-alpha.1` 至 `0.2.1-alpha.
|
|
73
|
+
- DSH 版本:`0.1.2-alpha.1` 至 `0.2.1-alpha.4`;已核验 minor 线内的后续版本自动放行,未核验的新 minor 线会被启动期兼容门禁拦截(开窗机制见下方折叠块)。
|
|
74
74
|
- **0.1.7-alpha.1 是破坏性版本**(shell 执行与 settings 面接口更换),插件内置双分支共存适配:同一发布同时兼容 0.1.2–0.1.6 各线与 0.1.7+,老版本 DSH 上行为不变。
|
|
75
75
|
- `0.1.1-rc.2` 及更早不支持:那条线的客户端运行时没有 `sessions`/`workspaces`/`uiWorkspace` 服务,插件 UI 会静默不渲染。
|
|
76
76
|
|
package/lib/client.js
CHANGED
|
@@ -1023,6 +1023,56 @@
|
|
|
1023
1023
|
var appLog = createLogger("app");
|
|
1024
1024
|
var recallNodeLog = createLogger("recall-node");
|
|
1025
1025
|
|
|
1026
|
+
// src/client/user-content.ts
|
|
1027
|
+
var USER_CONTENT_FACTORY = "conversation.chat.user.content";
|
|
1028
|
+
function projectUserContent(blocks) {
|
|
1029
|
+
const texts = [];
|
|
1030
|
+
const attachments = [];
|
|
1031
|
+
const extraBlocks = [];
|
|
1032
|
+
for (const block of blocks) {
|
|
1033
|
+
const b = block;
|
|
1034
|
+
if (b && b.type === "text" && typeof b.text === "string") texts.push(b.text);
|
|
1035
|
+
else if (b && b.type === "image" && b.attachment !== void 0) attachments.push({ type: "image", image: { attachment: b.attachment } });
|
|
1036
|
+
else if (b && b.type === "file" && b.attachment !== void 0) attachments.push({ type: "file", file: b.attachment });
|
|
1037
|
+
else extraBlocks.push(block);
|
|
1038
|
+
}
|
|
1039
|
+
return { text: texts.join(""), attachments, extraBlocks };
|
|
1040
|
+
}
|
|
1041
|
+
function userContentFacts(referenceLabels, skillNames) {
|
|
1042
|
+
const facts = {};
|
|
1043
|
+
if (referenceLabels !== void 0) facts.sessionLabels = referenceLabels;
|
|
1044
|
+
if (skillNames !== void 0) facts.skillNames = skillNames;
|
|
1045
|
+
return facts;
|
|
1046
|
+
}
|
|
1047
|
+
function resolveUserContentFace(props) {
|
|
1048
|
+
if (typeof props.renderMessageImages === "function") return "legacy";
|
|
1049
|
+
if (typeof props.renderFactorySlot === "function" && typeof props.loadImage === "function") return "factory";
|
|
1050
|
+
return "legacy";
|
|
1051
|
+
}
|
|
1052
|
+
function renderOfficialUserContent(renderFactorySlot, blocks, data, loadImage, interactive) {
|
|
1053
|
+
if (typeof renderFactorySlot !== "function") return { ok: false };
|
|
1054
|
+
const projected = projectUserContent(blocks);
|
|
1055
|
+
const content = {
|
|
1056
|
+
text: projected.text,
|
|
1057
|
+
attachments: projected.attachments,
|
|
1058
|
+
extraBlocks: projected.extraBlocks
|
|
1059
|
+
};
|
|
1060
|
+
if (data.referenceLabels !== void 0) content.referenceLabels = data.referenceLabels;
|
|
1061
|
+
try {
|
|
1062
|
+
const render = renderFactorySlot;
|
|
1063
|
+
const node = render(USER_CONTENT_FACTORY, {
|
|
1064
|
+
content,
|
|
1065
|
+
loadImage,
|
|
1066
|
+
facts: userContentFacts(data.referenceLabels, data.skillNames),
|
|
1067
|
+
interactive
|
|
1068
|
+
});
|
|
1069
|
+
if (node === null || node === void 0 || node === false) return { ok: false };
|
|
1070
|
+
return { ok: true, node };
|
|
1071
|
+
} catch (error) {
|
|
1072
|
+
return { ok: false, error };
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1026
1076
|
// src/client/recall-node.ts
|
|
1027
1077
|
function attachmentRefsFromBlocks(blocks) {
|
|
1028
1078
|
const refs = [];
|
|
@@ -1559,61 +1609,46 @@
|
|
|
1559
1609
|
setRecall({ stage: "idle" });
|
|
1560
1610
|
};
|
|
1561
1611
|
const bubbleChildren = [];
|
|
1562
|
-
|
|
1563
|
-
|
|
1612
|
+
let officialFace = resolveUserContentFace({ renderMessageImages, renderFactorySlot, loadImage }) === "factory";
|
|
1613
|
+
if (officialFace) {
|
|
1614
|
+
const official = renderOfficialUserContent(renderFactorySlot, blocks, data, loadImage, true);
|
|
1615
|
+
if (official.ok) {
|
|
1616
|
+
bubbleChildren.push(React.createElement(React.Fragment, { key: "official" }, official.node));
|
|
1617
|
+
} else {
|
|
1618
|
+
officialFace = false;
|
|
1619
|
+
recallNodeLog.warn("user.content factory unavailable; falling back to own rendering:", official.error);
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
if (!officialFace) {
|
|
1623
|
+
if (imageBlocks.length && typeof renderMessageImages === "function") {
|
|
1564
1624
|
const render = renderMessageImages;
|
|
1565
1625
|
bubbleChildren.push(React.createElement(
|
|
1566
1626
|
React.Fragment,
|
|
1567
1627
|
{ key: "images" },
|
|
1568
1628
|
render({ images: imageBlocks, align: "end" })
|
|
1569
1629
|
));
|
|
1570
|
-
} else if (typeof renderFactorySlot === "function" && typeof loadImage === "function") {
|
|
1571
|
-
const render = renderFactorySlot;
|
|
1572
|
-
bubbleChildren.push(React.createElement(
|
|
1573
|
-
React.Fragment,
|
|
1574
|
-
{ key: "images" },
|
|
1575
|
-
render("conversation.chat.user.content", {
|
|
1576
|
-
content: {
|
|
1577
|
-
text: "",
|
|
1578
|
-
attachments: imageBlocks.map((b) => ({ type: "image", image: { attachment: b.attachment } })),
|
|
1579
|
-
extraBlocks: []
|
|
1580
|
-
},
|
|
1581
|
-
loadImage,
|
|
1582
|
-
interactive: false,
|
|
1583
|
-
facts: {}
|
|
1584
|
-
})
|
|
1585
|
-
));
|
|
1586
1630
|
}
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
if (!info) continue;
|
|
1591
|
-
bubbleChildren.push(React.createElement(
|
|
1592
|
-
"div",
|
|
1593
|
-
{ className: "dsh-recall-filecard", key: "file-" + i },
|
|
1594
|
-
React.createElement("span", { className: "dsh-recall-filecard-icon" }, info.ext),
|
|
1595
|
-
React.createElement(
|
|
1596
|
-
"span",
|
|
1597
|
-
{ className: "dsh-recall-filecard-body" },
|
|
1598
|
-
React.createElement("span", { className: "dsh-recall-filecard-name", title: info.name }, info.name),
|
|
1599
|
-
React.createElement("span", { className: "dsh-recall-filecard-meta" }, info.size ? info.ext + " " + info.size : info.ext)
|
|
1600
|
-
)
|
|
1601
|
-
));
|
|
1602
|
-
}
|
|
1603
|
-
if (text !== "") {
|
|
1604
|
-
if (typeof renderFactorySlot === "function") {
|
|
1605
|
-
const render = renderFactorySlot;
|
|
1631
|
+
for (let i = 0; i < fileBlocks.length; i++) {
|
|
1632
|
+
const info = fileCardInfo(fileBlocks[i], t);
|
|
1633
|
+
if (!info) continue;
|
|
1606
1634
|
bubbleChildren.push(React.createElement(
|
|
1607
1635
|
"div",
|
|
1608
|
-
{ className: "dsh-recall-
|
|
1609
|
-
|
|
1636
|
+
{ className: "dsh-recall-filecard", key: "file-" + i },
|
|
1637
|
+
React.createElement("span", { className: "dsh-recall-filecard-icon" }, info.ext),
|
|
1638
|
+
React.createElement(
|
|
1639
|
+
"span",
|
|
1640
|
+
{ className: "dsh-recall-filecard-body" },
|
|
1641
|
+
React.createElement("span", { className: "dsh-recall-filecard-name", title: info.name }, info.name),
|
|
1642
|
+
React.createElement("span", { className: "dsh-recall-filecard-meta" }, info.size ? info.ext + " " + info.size : info.ext)
|
|
1643
|
+
)
|
|
1610
1644
|
));
|
|
1611
|
-
}
|
|
1645
|
+
}
|
|
1646
|
+
if (text !== "") {
|
|
1612
1647
|
bubbleChildren.push(React.createElement("div", { className: "dsh-recall-bubble", key: "text" }, text));
|
|
1613
1648
|
}
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1649
|
+
for (let i = 0; i < rest.length; i++) {
|
|
1650
|
+
bubbleChildren.push(React.createElement("pre", { className: "dsh-recall-json", key: "rest-" + i }, JSON.stringify(rest[i], null, 2)));
|
|
1651
|
+
}
|
|
1617
1652
|
}
|
|
1618
1653
|
const actions = [];
|
|
1619
1654
|
actions.push(React.createElement("span", { className: "dsh-recall-time", key: "time" }, clockText2(data.time)));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-recall-plugin",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.12",
|
|
4
4
|
"description": "在 DSH 中撤回自己发过的消息:工作区文件与对话历史一起回到那条消息发出之前,消息的文本与附件自动放回输入框,改完即可重发。文件走独立影子 git 快照(不碰项目自身的 git),对话走官方 sessions.fork,原会话归档可找回。",
|
|
5
5
|
"icon": "assets/icon.svg",
|
|
6
6
|
"type": "module",
|
|
@@ -70,7 +70,8 @@
|
|
|
70
70
|
"0.2.0-rc.2": "compatible",
|
|
71
71
|
"0.2.1-alpha.1": "compatible",
|
|
72
72
|
"0.2.1-alpha.2": "compatible",
|
|
73
|
-
"0.2.1-alpha.3": "compatible"
|
|
73
|
+
"0.2.1-alpha.3": "compatible",
|
|
74
|
+
"0.2.1-alpha.4": "compatible"
|
|
74
75
|
}
|
|
75
76
|
}
|
|
76
77
|
},
|