把 AI 说过的话改成你想让它说的,改完立即生效,不用重启。
34.8/100
综合分
7
GitHub 星标
686
npm 周下载
—/5
用户评分
数据截至 2026/10/27 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add dsh-reply-forge-v3npm 源 · dsh-reply-forge-v3 的发布者账号已校验与本仓库一致
状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态信任档位:已验证
是什么
dsh 原生插件 · chat
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 7 天前
档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查✓ 自动检查通过展开查看检查明细 ↓
以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。
✓npm 包dsh-reply-forge-v3 @ 3.4.2
✓Node 引擎要求 >=18 · 基线 Node 22.19 满足
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明
验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/10/28 04:54:46
安全扫描自动 · 静态分析源码v2.7.0🔵 扫描:含敏感能力尚无人工评估展开查看扫描说明与证据 ↓
安全扫描自动 · 静态分析源码
v2.7.0静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。
读写/删除本地文件发起外部网络请求
查看扫描证据(共 28 处,展示前 28 条)
- 发起网络请求.dsh-plugin/client.js:1001
const res = await fetch(ROUTE + '?sessionId=' + encodeURIComponent(sessionId)) - 发起网络请求.dsh-plugin/client.js:1185
const res = await fetch('/api/reply-forge-v3/session/forge', { - 发起网络请求.dsh-plugin/client.js:1271
const res = await fetch(url) - 读取本地文件src/session-log.mjs:80
entries = readdirSync(dir) - 读取本地文件src/session-log.mjs:120
projects = readdirSync(sessionsRoot, { withFileTypes: true }) - 读取本地文件src/session-log.mjs:145
compressed = readFileSync(filePath) - 读取本地文件src/session-write.mjs:68
const buf = readFileSync(file) - 读取本地文件src/session-write.mjs:235
if (existsSync(file) === false) throw new Error('会话文件不存在:' + file) - 写入本地文件src/session-write.mjs:311
copyFileSync(file, backup) - 读取本地文件src/session-write.mjs:312
const bs = statSync(backup) - 写入本地文件src/session-write.mjs:316
writeFileSync(tmp, rebuild(file, loaded, loaded.lines, strategy)) - 写入本地文件src/session-write.mjs:319
renameSync(tmp, file) - 写入本地文件src/session-write.mjs:329
copyFileSync(backup, file) - 写入本地文件src/session-write.mjs:330
try { if (existsSync(tmp)) renameSync(tmp, tmp + '.failed-' + stamp) } catch { } - 读取本地文件src/session-write.mjs:330
try { if (existsSync(tmp)) renameSync(tmp, tmp + '.failed-' + stamp) } catch { } - 读取本地文件src/checkout.mjs:22
if (existsSync(p)) return createRequire(p) - 读取本地文件src/session-repair.mjs:33
const buf = readFileSync(file) - 写入本地文件src/session-repair.mjs:118
copyFileSync(file, backup) - 读取本地文件src/session-repair.mjs:119
if (existsSync(backup) === false || statSync(backup).size === 0) { - 读取本地文件src/session-repair.mjs:119
if (existsSync(backup) === false || statSync(backup).size === 0) { - 读取本地文件src/session-repair.mjs:122
if (createHash('sha256').update(readFileSync(backup)).digest('hex') - 写入本地文件src/session-repair.mjs:160
writeFileSync(tmp, Buffer.concat(chunks)) - 写入本地文件src/session-repair.mjs:162
renameSync(tmp, file) - 写入本地文件src/session-repair.mjs:164
try { if (existsSync(tmp)) renameSync(tmp, tmp + '.failed-' + stamp) } catch { } - 读取本地文件src/session-repair.mjs:164
try { if (existsSync(tmp)) renameSync(tmp, tmp + '.failed-' + stamp) } catch { } - 写入本地文件src/session-repair.mjs:213
copyFileSync(backup, file) - 写入本地文件src/session-repair.mjs:214
try { if (existsSync(tmp)) renameSync(tmp, tmp + '.failed-' + stamp) } catch { } - 读取本地文件src/session-repair.mjs:214
try { if (existsSync(tmp)) renameSync(tmp, tmp + '.failed-' + stamp) } catch { }
扫描于 2026/11/3(0 天前) · 范围 入口闭包 + 全包扫描 · 来源 npm 发布包
静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
DSH 桌面端(DeepSeek Harness)插件:编辑会话里的助手回复与思维链,让模型采信改后的内容——在线写入立即生效,也支持重启生效的落盘改写和不对会话造成影响刷新即恢复的仅渲染。
最近更新
在 GitHub 查看全部 →v3.4.22026-10-26
DSH 桌面端「回复锻造」插件 v3.4.2:刷新不再把伪造变回原文,新消息也不会套上一条的伪造了。
安装(选一条)
· npm:DSH「插件管理器」里输入 dsh-reply-forge-v3 点安装;或关掉 DSH 后 dsh plugin --profile desktop add dsh-reply-forge-v3
· zip:下载 dsh-reply-forge-3.4.2.zip 解压即用(或 .tgz 走 tarball 安装);升级后若启动异常,运行包内「关闭插件.bat」一键回滚
已安装的用户直接升级即可,不用重装。本版修的都是显示层,模型侧采信一直是好的——你改过的东西,模型早就读到了。
---
· 先说清楚:伪造从来没失效过
有人看到「刷新就变回原文」,第一反应是白改了。核查下来不是这样,三条实证:
· 401 个会话文件里 reply-forge/patch 共 91 条,全部落盘完好、类型没被迁移改名;
· 把真实会话喂给宿主自己的恢复逻辑再组装请求,5/5 个目标块读到的都是伪造内容;
· 同一块连续改 3 次全部通过陈旧校验——投影要是没生效,第二次就会被拒。
失效的只是界面:刷新后页面退回原文。下面两个 bug 都是这一层。
· 一、刷新就恢复原文
根因两条,都得断:
1. 宿主刷新走的是另一条路。页面内是活会话,插件注册的投影生效;刷新/重开是「冷读」,宿主折叠历史时用的是一份写死的投影列表,只含它自己的图片卸载投影,插件投影进不去。这是宿主设计,插件改不了。
2. 兜底表的键锚错了对象。刷新后「仍显示伪造」本来靠本机覆盖表撑着,但键取的是「接管那一刻页面上的文本」——而页面上的文本就是伪造本身。刷新后拿原文算的键在表里根本不存在,兜底自然落空。
现在读路由会额外回传未投影的磁盘原文,覆盖键改锚它(内容寻址,跨刷新稳定);旧表里锚在页面文本上的键继续兜底命中,历史覆盖不丢;记录异步后到时自动纠键;点「还原」时连旧键一起清掉。
· 二、新消息套上了上一条的伪造内容
真机症状很怪:改掉倒数第二条回复后,AI 新生成的那条也显示着上次的伪造内容,还挂着「还原」按钮。
成因是一条链:页面上的被伪造块显示的是原文(宿主不渲染投影),而定位只拿投影后的文本建索引 → 它永远精确匹配不上 → 落到「末尾对齐」的推断;新消息一进来,页面块数与记录块数对不上,新消息的块就被兜底配到那条旧块上 → 拿它的原文算出覆盖键 → 命中上一条的覆盖。
三处一起改:matchBlocks 把原文也入索引(被伪造块凭原文对回自己,兜底不再被误用);覆盖键只认精确匹配(推断出来的原文属于另一条消息,不许当键);纠键时补渲染一次(否则慢记录场景下「改了不生效」)。
已经中招的不用清数据:污染只在渲染层,patch 事件与消息正文都完好,刷新一次就恢复。
· 顺带加固:换 provider 才会踩的雷
伪造思维链时,除了块上的签名,现在连 source.replayState.blocks[i].signature 一起剥。provider 序列化历史时会把后者原样回填到 thinking 块上,只剥前者的话,伪造的思维链会带着「对原文的签名」发出去——签名校验型 provider(Anthropic 系)会拒掉整条请求,症状是「伪造写进去了,下位模型却不采信」。
· 升级护栏
npm run verify-host 新增一项检查:静态核验宿主的活会话折叠仍用注册表投影。冷读用硬编码列表只影响界面(宿主设计,无解);但活会话一旦也被换掉,模型侧就再也不采信伪造、且不报错——这条检查会在宿主升级时直接 FAIL 报警。
---
校验:发行包 = 仓库 tag v3.4.2 快照(scripts/build-release.mjs 用 git archive 导出,只含受版本控制的文件)。门禁:client.js 与源码一致 / 423 项 17 套全绿 / verify-host PASS 27 · WARN 0 · FAIL 0。
两条如实说明:① 这次修复只对新写入生效——之前已经伪造过的块,磁盘里没有可用的原文锚点,重新写一次就落到新锚点上,不是没修好;② 换设备、清缓存、无痕窗口仍会看到原文,那一层要宿主把插件投影纳入冷读路径,目前没有公开入口——但模型照样采信,因为它读的是会话文件,跟你用哪台机器、哪个窗口无关。
v3.4.12026-10-24
DSH 桌面端「回复锻造」插件 v3.4.1:长回复终于能完整改了。
安装(选一条)
· npm:DSH「插件管理器」里输入 dsh-reply-forge-v3 点安装;或关掉 DSH 后 dsh plugin --profile desktop add dsh-reply-forge-v3
· zip:下载 dsh-reply-forge-3.4.1.zip 解压即用(或 .tgz 走 tarball 安装);升级后若启动异常,运行包内「关闭插件.bat」一键回滚
已安装的用户直接升级即可,不用重装。本版包含 v3.4.0 的全部内容(v3.4.0 未单独发行,版本号预提后并入本版)。
---
· 长回复「只剩几个词」修好了
一条一千多字的回复点「伪造回复」,编辑器里只剩「不会」「线索」这类句中词——
加粗片段和标题还在,正文全没了。根因是取正文时只遍历元素的元素子节点,
而 markdown 渲染出来的段落是混合内容:正文加粗正文
,
两侧的文字是直属文本节点,被整段跳过。
现在改为按文档顺序遍历,直属文本照收;只在段落/标题/列表项这些块级边界补空格,
行内元素两侧不补,避免「这是 重点 内容」这种人为断开。
· 「改了不生效」也一并修了
在编辑器上方的目标下拉里切回这条消息,内容完整了,但写入后页面一个字都不变。
原因是记录里存的是 markdown 源码(不会、## 标题),页面里是渲染结果,
直接比字符串只要回复带任何格式就永远不相等——精确定位形同虚设,全部退化成
「按顺序推断」;页面与记录条数一错位,写入就打到别的块上。
现在定位改成同口径比较:把记录里的 markdown 用插件自己的渲染器渲染一遍再和页面比。
源码形态同时保留入索引,思维链这类纯文本块不会被规范化误伤。另外两点:
· 编辑器初值在精确对上记录时直接取记录原文。拿页面渲染结果当初值,一保存就把
加粗/标题/代码块全拍平成纯文本;
· 从目标下拉里选回自己这条不再被当成「改别的消息」,本机渲染照常跟着变。
---
· 本版同时包含 v3.4.0 的内容
· 历史版本:每条改过的回复旁边多了「历史版本」入口,能看到原稿→第一次改→第二次改
的完整版本链,标「当前」的那版就是模型此刻读到的内容;选中旧版点「还原到此版」,
内容填进编辑器,再点「写入伪造」即可让模型重新读到那一版。思维链同样有链。
· 升级过 DSH 不再静默丢伪造:宿主 0.2.1 会把旧会话里「未知类型」的事件改名成
plugin:,导致投影与版本血统静默失配(伪造退回原文、不报错)。现在两种类型
都注册、读侧都接受,新事件仍写旧类型。
· 升级自检:npm run verify-host 四层探针(版本基线 / 契约面 / boot 冒烟 / 活体 CDP),
DSH 升级后跑一次就知道插件还兼不兼容。
两个如实说明:「仅落盘」写入的块没有版本链(那条路直接改写会话文件,原稿在
.bak-forge-* 备份里);版本链读自会话记录文件,刚完成的在线写入落盘有数百毫秒滞后。
---
校验:发行包 = 仓库 tag v3.4.1 快照(scripts/build-release.mjs 用 git archive 导出,
可复现、不含任何本机私有文件);测试门禁 404 项 / 17 套全绿(其中 29 项需本机装有宿主包,
找不到自动 SKIP 不计失败)。
v3.3.02026-10-22
DSH 桌面端「回复锻造」插件 v3 第三个正式版:上 npm 了,现在一条命令就能装。
安装(选一条)
· npm:DSH「插件管理器」里输入 dsh-reply-forge-v3 点安装;或关掉 DSH 后 dsh plugin --profile desktop add dsh-reply-forge-v3
· zip:下载 dsh-reply-forge-3.3.0.zip 解压即用(或 .tgz 走 tarball 安装);升级后若启动异常,运行包内「关闭插件.bat」一键回滚
包名从 @local/dsh-reply-forge-v3 改成 dsh-reply-forge-v3(上 npm 必须去占位 scope)。已本地安装的重跑一次 安装插件.bat 会自动迁移,旧链接与配置条目一并清掉,不会出现两条操作条。
---
· 现在可以一行命令装了
以前装这个插件得:下载 zip → 解压 → 双击 安装插件.bat → 重启 DSH。
现在多了一条更省事的路:
· 在 DSH 的「插件管理器」里输入 dsh-reply-forge-v3,点安装;
· 或者关掉 DSH,在终端里执行 dsh plugin --profile desktop add dsh-reply-forge-v3。
zip 那条路仍然有效,两种装法内容完全一样。
· 包名变了,但不用你操心
为了能上 npm,插件包名从 @local/dsh-reply-forge-v3 改成了
dsh-reply-forge-v3。已经是本地安装的话,重新运行一次 安装插件.bat
就会自动迁移——旧的链接、依赖条目、lockfile 记录一并清干净,不会出现
两条消息操作条。
---
校验:发行包 = 仓库 tag v3.3.0 快照(scripts/build-release.mjs 用 git archive 导出,可复现、不含任何本机私有文件);npm 包走 files 白名单,16 个文件 / 86.3 KB,只含运行时必需(.dsh-plugin/ + src/ + cordis.patch.yml)。测试门禁 321 项 / 14 套全绿(其中 21 项需本机装有宿主包 @deepseek-ai/dsh-session,找不到自动 SKIP 不计失败)。
数据来自仓库的 GitHub Release,可能与插件实际版本不一致。
README
由 DeepSeek 最新模型翻译生成dsh-reply-forge v3
English · 简体中文
把 AI 说过的话改成你想让它说的,改完立即生效,不用重启。
思维链也能一起改。
DSH 桌面端(DeepSeek Harness)插件。会话里每条 AI 回复旁边多一个按钮,点开就能改正文或思维链;改完写进去,这个会话之后的每一轮,模型都只认你改过的版本。
已收录 awesome-dsh-plugin(DSH 生态精选列表,PR #7845 已合并)。
和 v1 / v2 的区别
| | v1 | v2 | v3 |
| 改的是什么 | 页面显示 | 会话落盘文件 | 活会话对象 |
| 模型看得到吗 | 看不到 | 看得到 | 看得到 |
| 生效时机 | 立即(只对本机) | 要重启 DSH | 立即 |
| 能改思维链吗 | 不能 | 不能 | 能 |
| 实现 | localStorage 覆盖 | 改写 .jsonl.zstd | message projection |
v3 把 v2 的落盘改写包成了兜底分支:会话在内存里就直接写活会话(立即生效),不在内存里才落盘。
展开完整文档(README 剩余 1 节)
dsh-reply-forge v3
English · 简体中文
把 AI 说过的话改成你想让它说的,改完立即生效,不用重启。
思维链也能一起改。
DSH 桌面端(DeepSeek Harness)插件。会话里每条 AI 回复旁边多一个按钮,点开就能改正文或思维链;改完写进去,这个会话之后的每一轮,模型都只认你改过的版本。
已收录 awesome-dsh-plugin(DSH 生态精选列表,PR #7845 已合并)。
和 v1 / v2 的区别
| | v1 | v2 | v3 |
| 改的是什么 | 页面显示 | 会话落盘文件 | 活会话对象 |
| 模型看得到吗 | 看不到 | 看得到 | 看得到 |
| 生效时机 | 立即(只对本机) | 要重启 DSH | 立即 |
| 能改思维链吗 | 不能 | 不能 | 能 |
| 实现 | localStorage 覆盖 | 改写 .jsonl.zstd | message projection |
v3 把 v2 的落盘改写包成了兜底分支:会话在内存里就直接写活会话(立即生效),不在内存里才落盘。
前两代是独立仓库,与 v3 完全独立(独立插件 id / 前缀,可分别启用):
- v2(dsh-reply-forge-v2,纯落盘改写):https://github.com/kinggod899/dsh-reply-forge-v2
- v1(dsh-reply-forge,localStorage 覆盖):更早的第一代,仓库沿革见 v2 说明
三代可以分别启用,但不要同时启用——它们共用同一条消息操作条,同时开会挂出多条(安装脚本默认会把更早的两代从 bundles 摘掉,见下)。
怎么用(快速版)
1. 鼠标移到任意一条 AI 回复上,点「伪造」按钮;
2. 在编辑器里选目标(可选本会话任意可编辑块——包括页面上看不见、折叠着的思维链)、选写入方式(默认「自动」);
3. 改文本,点写入伪造。写入前会弹一次确认(复述这次会怎么生效),写完成功立刻可见。
改过的块还能后悔:点编辑器里的「历史版本」,能看到这个块的完整版本链(原稿 → 每一次改写),选中旧版「还原到此版」→ 内容填入编辑器 → 再点一次「写入伪造」即可。思维链同样有这条链。
两个出口:仅本机渲染(Ctrl+Enter,只改这一页的样子、模型看不见、可逆)与 写入伪造(改「模型看到的对话」,在线写活会话立即生效 / 落盘重启后生效,不可逆)。
完整的界面说明、写入方式对照表、常见问题见 使用说明.md。
看效果
| 写入成功,下一轮立即生效 | 思维链 | 三种写入方式 |
| 写入活会话 | 编辑思维链 | 写入方式 |
三张连起来是一个真实片段:把一句 AI 回复改成「因为我是吃白饭的大肥鱼。」写进去,块上立刻亮绿色回执——「下一轮对话模型就看到改后的内容(不用重启),焦点已交还输入框」。
下一轮随口问一句「你去干嘛」,模型接的是「虽然是个吃白饭的」——它真的信了。
中间那张是思维链的编辑面板:目标下拉里的 seq=38 块0 就是页面上折叠着、看不见的那串内心戏,和正文一样能改。右边那张是写入方式三选:自动(会话在内存里就直接写活会话、不在才落盘,推荐)、仅在线(立即生效)、仅落盘(重载后生效)。
关掉插件会不会把会话弄坏
标记齐全就不会——这个「不会」有前提。
reply-forge/patch 是宿主不认识的事件类型,宿主持久化层的准入闸门 validateStoredEvents 规定:未知类型且信封没带 ignorable: true → 抛错、 整条日志拒读(会话打不开);投影层(插件在场时)对不认识的事件则直接跳过、不报错,伪造退回原文。宿主自己的注释写明了这条路(known-event-types.js):
The persisted SessionEvent.ignorable marker is the compatibility mechanism.
于是分三种情况:标记正确 + 冷启动(插件不在)→ 会话照常打开、伪造静默退回原文; 缺标记(早期写入或写盘损坏)→ 被闸门拒读,用修复工具补标记;插件在 DSH 运行时被热卸载 → 宿主抛 session message projection "reply-forge/patch" was removed(所以正常用法都要求重启 DSH,重启后干净重放,碰不到这条)。
v3 的 P0 工作就是把「标记正确」钉死:在线/落盘两条写路径进入宿主持久化前统一补 ignorable: true;每次真写前核验写句柄(WRITER_NOT_ROUTED 可安全回落盘、 APPEND_UNSAFE 一律拒绝,失败码表见 PLAN.md);真宿主持久化闭环测试 create → append → flush → 重读 → 闸门放行全过。
致谢与借鉴
dsh-chat-thinking-editor
是本插件重要的学习对象——它是 DSH 生态里最早做「思维链编辑」的探索者,「改完立即生效」这个目标与它对用户的坦诚(PR 说明里明确写「编辑过的会话包含插件自有事件,卸载插件后该会话可能无法读取」)都给了我们直接的启发。两条路线的差别在技术选择:它早期版本把编辑挂在 assistant/message 上并带 surfaceOp,那类事件在插件缺席时仍会被宿主做 surface 校验,校验不过就抛; v3 因此走了另一条路——自有事件 + 同类型 projection + ignorable 标记(见上一节)。没有高下之分,只是约束下的不同取舍;站在它的肩膀上,我们把「卸得干净」这件事做得更彻底了一点。
万一会话已经被弄坏:退出 DSH,双击 修复会话.bat 先看诊断(只读,不动文件),确认后再 修复会话.bat apply 补上 ignorable: true——工具先备份、只改有毒行、写完自读复验、修不好自动还原、修不了的旧伤明确报出而不是硬改。关插件的完整说明与安全排障见 使用说明.md。
安装
前提:已装 DSH 桌面端。两条路线选一条,装完都要完全退出并重启 DSH (静态配置只在启动时加载)。
路线一:npm(最省事,不需要额外装 Node.js)
在 DSH 的「插件管理器」里输入包名 dsh-reply-forge-v3 点安装;或者关掉 DSH 后在终端执行:
dsh plugin --profile desktop add dsh-reply-forge-v3
插件管理器还支持切换安装源(npm 官方源 / 中国大陆镜像源 / 自定义地址),内网或私有源环境也能装。
路线二:zip 解压(需要 Node.js)
双击 安装插件.bat。
安装/启用/关闭/修复四个 bat 靠 Node.js 改静态配置——bat 会自动探测 PATH、 C:\Program Files\nodejs、%LOCALAPPDATA%\Programs\nodejs 与 DSH 自带的 node (%USERPROFILE%\.dsh\dsh-runtimes\...\node\bin\node.exe,装了 DSH 就有)共四处,一般装过 Node 或 DSH 就能直接用。没有 Node 也不挡路:bat 会印出完整的手动四步(改 package.json / cordis.patch.yml / 建 junction),照做即可。
插件本身跑在 DSH 内部,不要求你额外装运行时。
脚本只做三件事:建 junction、写 package.json 的 bundles 与 dependencies、手工登记 lockfile 条目。它不执行 pnpm install —— 在 DSH 运行时跑 pnpm 会清空 node_modules。
安装时会默认把更早的两代(dsh-reply-forge-v2、dsh-reply-forge)从 bundles 里摘掉:三代共用同一条消息操作条,同时开会挂出多条。它们的依赖和链接都保留,随时可以用各自的「启用插件.bat」开回;想并存做对比就用 node scripts/install.mjs --keep-v2 --keep-v1。
其他脚本:
| 脚本 | 用途 |
| 安装插件.bat | 首次安装(含链接、依赖、lockfile、bundles) |
| 启用插件.bat | 已装过但被关掉时,把插件加回 bundles |
| 关闭插件.bat | 从 bundles 摘掉。启动异常时用它一键回滚 |
三个脚本都只改静态配置,都不碰 pnpm,都不会删你的覆盖数据; 改完都要重启 DSH(原因见上一节)。
工作原理
宿主不允许对 assistant/message 做 surface replacement —— assertSourceEventReferences 要求 replace 事件用 sourceEventSeqs 覆盖全部被遮蔽节点,同一个函数又硬性禁止 assistant/message 携带 sourceEventSeqs。两条约束互斥,必然抛错。
宿主为此留的正门是 message projection:按事件类型注册的纯函数,折叠时决定「某个 seq 的消息模型看到的是什么」。所以 v3 追加一条自有的 reply-forge/patch 事件(纯日志记录,不进 surface),再由同类型的 projection 把目标消息改写掉。
- 事件不带 surfaceOp / sourceEventSeqs,所以不触发任何 surface 校验;
- 插件被禁用时,事件被静默跳过,伪造退回原文;只要标记齐全,会话照常打得开;
- 重启后伪造仍在:会话恢复用的是已注册的 projection 列表,而本插件在
apply() 里同步注册;
- 在线写入的事件最终同样落进 session.v4.jsonl.zstd,所以「在线写的」和「落盘写的」一样耐久。但宿主持久化层会校验事件类型:未知类型必须带 ignorable: true 否则拒读整条日志,而 Session.append() 不接这个字段——所以 v3 在写路径上包了 shim,进入宿主持久化前统一补标记(详见上面「关掉插件会不会把会话弄坏」)。
关于宿主升级(v3.4.1 起):宿主 0.2.1 在读 v3 世代的旧会话文件时会做格式迁移,把「未知类型 + ignorable」的事件改名成 plugin:<原类型> 写盘——我们的 reply-forge/patch 会变成 plugin:reply-forge/patch。投影是按类型精确匹配的,所以本插件两种类型都注册、读侧都接受(projectedTextsOf 投影基准与版本血统推导同样双类型);新事件仍写旧类型,不触发改名。升级过 DSH 的老用户,旧会话里的伪造与版本链都还在。
完整论证、宿主源码证据、失败码表见 PLAN.md。
三条写入出口,按代价从小到大
| 出口 | 改的对象 | 副作用 |
| 改待发送队列 | 还没发给模型的消息(inbox) | 无:消息未进日志,无新事件、不动已提交字节、sessionId 不变、provider 缓存不受影响 |
| 在线 patch | 会话日志里的历史消息 | 追加自有事件;改动点之后的 KV 缓存作废 |
| 落盘改写 | 会话记录文件 | 需备份还原;运行中的宿主不读盘,改了要下次载入才生效 |
| 仅本机渲染 | 只有页面看得见 | 模型看不见,纯障眼法 |
第一条是 v3.1 之后补的,走官方 session.updateQueue(itemId, { kind: 'edit',
content: [...] })。入口挂在带 data-pending-steering 的气泡上(回合进行中
以 steering 形式插入的消息;next-turn 排队消息不渲染气泡,用户看不见,所以不提供入口)。
定位方式是文本回匹,这是宿主的限制不是偷懒:气泡 DOM 上没有任何 id 类属性(只有 data-pending-steering / data-submission-echo 两个布尔), itemId 只能拿气泡文本去比队列行文本。因此:
- 匹配到多条一字不差的相同文本 → 拒绝并提示,不赌第一条;
- 行内含非 text 块(附件)→ 拒绝,因为服务端只接受纯文本,静默改写会丢附件;
- 定位失败 → 不给输入框,只说明原因,避免「保存了却不知道改的是哪条」。
错误码表逐个核对过宿主 dsh-api-session-controller/lib/index.js 的 updateQueue handler(822-845 行),不是照抄别家插件。
版本血统:为什么不给事件加「旧文本」字段
「历史版本 / 还原到此版」(v3.4)刻意不给 patch 事件加任何字段——原稿本来就没被破坏(patch 是独立事件,原始 assistant/message 一字未动),每条 patch 又记着「改成什么」,投影折叠是「后者覆盖前者」;三者都在日志里,按事件顺序排起来就是完整版本链。所以血统是纯推导(src/lineage.mjs),只在用户点开面板时算一次——扫描、投影、写入路径上一个字节都没多付。「还原到此版」也不引入第二条写路径:它只把旧版文本填回编辑器,真正写入仍走「写入伪造」(两步确认、陈旧校验、自动备份全部复用)。
代价说清:「仅落盘」写入的块没有版本链——那条路(v2 原路)原地改写记录文件,原稿只在 .bak-forge- 备份里;在线写入落盘有数百毫秒滞后,此刻读链可能差最新一版。
测试
node tests/projection.test.mjs # 在真宿主 dsh-session 上验证机制(找不到宿主会 SKIP)
node tests/persistence.test.mjs # P0 真宿主持久化闭环:create→append→flush→重读→闸门
node tests/ignorable.test.mjs # P0:ignorable shim + 每会话写句柄核验
node tests/live.test.mjs # 在线层校验与失败码
node tests/patch.test.mjs # patch 纯数据层
node tests/lineage.test.mjs # 版本血统推导(v3.4)
node tests/migration-rename.test.mjs # v3→v4 迁移改名回归(真宿主 codec 回环)
node tests/routes.test.mjs # 路由层
node tests/client.test.mjs # 客户端产物
node tests/record.test.mjs # 记录过期判据(新会话卡死的根因)
node tests/session-write.test.mjs
node tests/session-log.test.mjs
node tests/session-repair.test.mjs # 受损会话修复工具
node tests/build.test.mjs
node tests/session-anatomy.test.mjs
node tests/verify-host.test.mjs # 升级自检脚本自身(asar 配方 / 基线比对 / 输出解析)
共 423 项 / 17 套(含 P0 的 ignorable 29 项、persistence 真宿主闭环 5 项、 session-repair 15 项、v3.4 的 lineage 22 项、v3.4.1 的 migration-rename 5 项、升级自检自身 verify-host 21 项)。
其中 31 项(projection 18 + migration-rename 5 + persistence 5 + ignorable 3)需要本机装有宿主包 @deepseek-ai/dsh-session 及 codec 链——找不到就 SKIP、不计失败(此态执行 392 项),所以发行包在没装 DSH 的机器上也能跑绿;装了宿主的机器上才是完整 423 项全跑。
跑法:逐个 node tests/<文件>,或 node --test "tests/.test.mjs" 看总门禁。改过 .dsh-plugin/client/.mjs 后必须重跑 node scripts/build-client.mjs(自研内联器,零依赖 / 无 JSX / 无 export default),build.test.mjs 会校验产物与源码一致。
DSH 升级后自检
宿主升级可能静默改变插件依赖的接口(v3.4 修的「迁移改名」就是这么爆发的:不报错、无提示,伪造退回原文)。升级 DSH 后,在插件源码目录跑:
npm run verify-host # T0 版本基线 + T1 契约面(含强制跑宿主门控测试,缺宿主=FAIL)
node scripts/verify-host.mjs --boot # + T2 启动冒烟
node scripts/verify-host.mjs --live # + T3 活体 CDP
版本与发布时钉住的 host-baseline.json 不一致 → WARN(通常无害,登记新基线即可);任一层不过 → FAIL,退出码 1。完整说明见 使用说明.md 第七节。
构建守卫与备选后端
| 命令 | 作用 |
| node scripts/build-client.mjs --check | 产物与源码字节一致(改了源码没重建就失败) |
| node scripts/lint-flat.mjs | 拍平守卫:客户端模块的顶层标识符是否跨模块重名(拍平成单文件后会静默互相覆盖,语法检查抓不到),以及是否用了内联器不支持的语法 |
| node scripts/build-client-esbuild.mjs | esbuild 后端(可选):默认只写预览 client.esbuild.js,加 --write 才覆盖主产物 |
build-release.mjs 出包前会强制跑前两道守卫,失败即中止、dist/ 不动。
esbuild 产物已实测与自研内联器行为等价(tests/client.test.mjs 57/57 全过,导出 { name, inject, apply } 一致),但它需要 npx 联网取包,所以默认仍是零依赖的内联器;切换请当成一次显式决定(--write),不是手滑。
命令行(离线改写)
不想开界面时可以直接改文件(需要先退出 DSH):
node scripts/forge-session.mjs --session <会话id> --list
node scripts/forge-session.mjs --session <会话id> --list --kind reasoning # 列出思维链
node scripts/forge-session.mjs --session <会话id> --seq 42 --block-index 1 \
--kind text --text "改后的正文" --dry-run
--kind 取 text / reasoning / both(默认 text);--list 按它过滤,改思维链前先用 --list --kind reasoning 拿到 seq/blockIndex。完整说明见使用说明.md。
真机诊断脚本
scripts/diag-focus.mjs —— 无人值守复现「保存后输入框没光标」:在 Node 里照抄 App 启动配方 boot() 起真宿主(合并 app 运行时树 + profile 插件依赖的 node_modules,配置锚点放合并根——typert-loader 的 createRequire 要靠它解析贡献者清单,放 profile 目录会 14 个全灭、页面 "Failed to load plugins"),抓带 token 的 web URL,无头 Edge + CDP 打开页面,点「伪造回复 → 仅本机渲染」全程采样 activeElement 与 focusin/focusout 轨迹。跑法 CODEBUDDY_SAFE_DELETE_ENABLED=0 node scripts/diag-focus.mjs (safe-delete 垫片会拦宿主删锁文件,见 memory 铁律)。
版本记录
- 3.3.0 —— 上了 npm,装起来更省事:
- 新安装方式:插件管理器里输入 dsh-reply-forge-v3 直接装,或关掉 DSH 后 dsh plugin --profile desktop add dsh-reply-forge-v3;zip 解压路线照旧,两者内容一致;
- 包名变更:@local/dsh-reply-forge-v3 → dsh-reply-forge-v3(上 npm 必须去掉 @local 占位 scope)。已本地安装的重跑一次 安装插件.bat 即自动迁移,旧链接、依赖条目、lockfile 记录一并清掉,不会出现两条操作条;
- 3.2.0 —— inbox 队列编辑上线 + 三处「按钮不见了」修复:
- 新功能:AI 生成中途发的消息会排队(气泡形式挂在会话里),现在可以把鼠标移到气泡上点「改待发送」直接改——不用撤、不用暂停 AI、不用重新打字;
- 真机 bug 四修:「改待发送」点了报「读不到待发送队列」——真凶是读取代码把宿主的方法从对象上摘下来再调用(测试环境能跑、真机上直接抛异常被吞掉),已改回正确调用形式并补按宿主真实形状写的回归测试;
- 真机 bug 五修:技术讨论里提到「工具调用」的长文本回复没有「伪造」按钮——宿主根本不在正文里渲染工具调用(都是独立卡片),这类词汇匹配纯属误伤,分类器已收缩到只认宿主费用页脚;
- 工程:persistence 真宿主闭环首次在真实桌面端宿主(app.asar 提取)上验证通过——此前对齐的是旧副本;测试 321 项 / 14 套(其中 21 项需本机宿主包)。
- 3.1.0 —— 三条线:
- 易用性:修新会话真机 bug(新会话全程提示「没有可定位的助手正文」——在线空快照被当成终态缓存,永不重读);写入方式改为可显式选择(自动 / 仅在线 / 仅落盘)。
- P0 安全封口:ignorable: true 补齐覆盖在线 / 落盘两条写路径(shim 在进入宿主持久化前打标记),并经真宿主持久化闭环验证(create → append → flush → 重读 → 闸门放行);新增每会话写句柄核验(WRITER_NOT_ROUTED 安全回落盘 / APPEND_UNSAFE 一律拒绝);新增 修复会话.bat(诊断优先、备份、原子写、自读复验、修不好自动还原)。
- 焦点修复(真机 bug)*:写完伪造 / 仅本机渲染保存成功后,下方输入框打不了字、没有光标,点一下「+ 添加文件」才能继续输入。根因是保存时 applyOverride 连编辑器带消息块一起抹掉,焦点掉到 <body>。修法:先关编辑器把焦点交还宿主输入框(data-composer-input),再抹块。第二轮查明宿主输入框在 inert(hero 态 / 被锁 / 机审中)时 contenteditable=false、根本不可聚焦,
focus() 是无操作——所以交还结果如实记录:保存后块上提示行报告
「焦点已交还输入框」或「⚠ 焦点未交还:输入框暂不可编辑」,另有焦点守候(~700ms 内焦点掉回 <body> 就重交还,用户一操作立即收工)与调试命名空间
__dshReplyForgeV3.focus()。第三轮按用户实测锁定仅发生在「写入伪造」的异步路径:
点击后按钮被禁用,Chromium 会先把焦点丢到 <body>,所以现于禁用前快照焦点、写入成功后恢复;等待期间若用户主动把焦点移到其它控件则不抢。第四轮查明 Electron 原生 window.confirm 是 OS 级模态框,会把 app 窗口键盘焦点弄丢(DOM 焦点全绿却打不了字),改为页面内两步确认(写入路径再无 OS 级模态)。
第五轮修「重启后写入伪造被 STALE_TARGET 拒绝」:落盘读未过投影,expectText 拿投影前原文当基准——读路由改过投影、投影优先拒绝落盘改写、客户端保留陈旧自动重试一次。v3 client 57 项、全量测试 233 项 / 13 套全绿。
- 3.0.0 —— 首发:message projection 在线写入 + 落盘兜底、思维链可改、目标选择器。
友链
- dsh-chat-thinking-editor —— DSH 生态最早的思维链编辑插件,本项目的学习对象(见上面「致谢与借鉴」)
- dsh-reply-forge-v2 ——本项目的前一代:纯落盘改写(要重启才生效)
许可证
MIT预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
kinggod899/dsh-reply-forge-v3
把 AI 说过的话改成你想让它说的,改完立即生效,不用重启。
相关插件 · 对话 / 记忆
上下文中枢oceanbase/powercontext
上下文跟着工作走 —— 跨会话、跨智能体、跨人。
★ 1,487已汉化DSH
73
智能体记忆库plastic-labs/honcho
Honcho 是记忆基础设施,用来构建有状态的智能体。这些智能体能理解不断变化的人、智能体、群组、项目和想法。
★ 7,894已汉化DSH
71.7
长程智能体控制面loopx-project/loopx
给 Agent 一个目标,让工作持续向前。
★ 6,358已汉化DSH
71.3
智能体上下文数据库volcengine/OpenViking
给 AI 智能体用的上下文数据库
★ 41,379已汉化DSH
71.3
长效机器人团队Fei2411/DS-Bot
DS Bot 是一个基于 DeepSeek Harness简称 dsh的 bot 插件,插件将 dsh 改造为 bot…
★ 5,084已汉化DSH
71.1
可携带记忆层EverMind-AI/EverOS
为什么选择 EverOS
★ 13,778已汉化DSH
71.1