前言¶
DeepSeek Harness (DSH) web 客户端支持通过插件扩展功能。在开发或调试过程中,常常需要修改历史对话中的用户输入。常见的做法是给消息编辑写一套自定义事件(版本树 + undo/redo),但自定义事件写入 session log 后,历史会话可能因事件白名单拒读而无法冷加载。
dsh-fork-edit 采用了不同的策略:不写入任何自定义事件。它通过在目标回合之前的闭合边界处 fork 出新会话,将编辑后的文本作为新会话里的普通用户回合执行。谱系关系只靠 parentSession header meta 记录。这种方法保证了历史会话永远可读,且编辑语义天然简单。
核心功能¶
编辑¶
在任意一条已落定的历史用户消息上悬停,官方操作按钮旁会出现 ✎ 图标(对话进行中按钮也会在数秒内自动出现,无需刷新或切换会话)。
点击图标后弹窗预填原输入。输入框内可直接粘贴图片(持久化后作为 image block 并入新消息),支持纯图片编辑。按 Enter 直接提交,Shift+Enter 换行。弹窗内带会话 timeline,纵排列出所有已闭合的用户回合(序号 + 单行预览),点击即可跳转切换编辑目标。新分支自动以首条用户输入命名,各分支一眼可辨。
侧边栏分支分组¶
官方侧边栏是平铺展示的,fork 出的后代挤在顶部难以辨认。本插件以 sessions.list 谱系为权威,把根会话的全部后代分组渲染在根行下方。
视觉上采用单层级,配合视觉缩进和树状连接线(└ 形)。行识别基于 React fiber 的会话 id,标题只作降级兜底。根行(或根不可见时的组头行)带折叠开关,折叠状态存 localStorage,随会话切换智能展开。官方只渲染最近 N 个会话时,后代自动锚定到最近可见的祖先。
级联归档¶
官方的「归档会话」只归档单个会话。本插件在 client 侧对服务方法做级联包装:归档任一会话时,自动收集并归档其整棵后代子树(部分失败不静默)。
注意:归档目前没有官方 unarchive 入口,不可逆。
技术实现¶
编辑 = fork 出一个新会话,该会话将编辑后的文本作为普通用户回合执行。核心事务在源 Agent 的 runMaintenance() 缝隙内执行,失败时逐项回滚,不产生半成品会话。
代码逻辑如下:
inverses = []
child = agents.create({ seed: 目标回合之前的闭合边界前缀, meta: { parentSession, seedLength, cwd, agentPreset } })
inverses.push(() => child.dispose())
workspace? → attachSession(childId)
await sessions.flush(child.session) # 耐久性屏障
child.agent.followup(编辑后文本 || 原回合用户输入) # 新会话自动执行
失败 → recoverOperation(inverses.reverse()) # 无半成品残留
安装与启用¶
插件需安装在 ~/.dsh/profiles/web/package.json 中:
{
"dependencies": {
"dsh-fork-edit": "link:/path/to/dsh-fork-edit"
}
// 并在 bundles 数组加入 "dsh-fork-edit"
}
若同时装有同类消息编辑插件(如 dsh-message-edit),请移除 —— 相同注入点会出双按钮。
HTTP API¶
Host 侧通过 ctx.webServer.register 暴露路由,Client 以同源 fetch 调用。
| 请求 | 作用 |
|---|---|
GET /fork-edit?sessionId=… |
已落定回合列表 |
POST /fork-edit { kind: "prefill", sessionId, turn? } |
目标回合原文本 |
POST /fork-edit { sessionId, turn?, mode, text? } |
执行分叉 |
mode 取值:edit-user(编辑用户输入)/ retry-turn(重试回合;0.9.0 起 UI 不再暴露,保留给纯图片回合等不便走编辑弹窗的调用方)。
版本 0.9.0 变更¶
0.9.0 起独立的 ↻ 重试入口已裁掉:官方 turn 尾部的 branch 按钮覆盖「另开分支」场景,「重跑本回合」用编辑弹窗原样保存即可(纯图片回合除外,见 HTTP API 的 retry-turn)。
短结尾¶
dsh-fork-edit 通过简单的 fork 语义解决了消息编辑的历史兼容性问题,适合需要在对话流中修正错误或探索不同路径的场景。更多细节可参考 GitHub。