前言¶
在 DeepSeek Harness(DSH)里跑 agent,模型会通过 write / edit 等工具改工作区文件。改错了、改多了,或者你想先预览再决定是否保留,都需要一种可操作的回退方式。社区里已有「对话回退」类插件,会把对话上下文和文件一起切回去,适合「回到某条消息之前」;若你只想把文件退到某一轮对话开始前的状态,且希望模型能自己触发、headless 环境也能用、又不依赖 git,就需要专门做文件层快照与回滚的插件。下面介绍维护者 ruaibeite 的 dsh-undo(分类:admin-security),它在本轮每次写入落盘前快照,并支持工具、/undo 命令与 Web GUI 按钮三种入口。
这是什么¶
dsh-undo 是 DSH 的 host 侧插件:在每次文件写入或编辑落盘之前保存原内容快照,模型可调用 undo 工具,或你在输入框敲 /undo,或在助手消息动作行点 ↶ 回退本轮,把一整轮对话里改动过的所有文件一次退回(也可按快照 id 做单文件精细回退)。它不 fork shell、不读凭据、不发网络请求、不碰 git 仓库;快照与索引写在 $DSH_HOME/undo/。需要「对话 + 文件」一起回退时,应选用 dsh-rewind-plugin、dsh-recall-plugin 等对话回退类插件;本插件只做文件层,刻意保持轻量。
与对话回退类插件的差异¶
| dsh-undo | 对话回退类(如 dsh-rewind-plugin、dsh-recall-plugin) | |
|---|---|---|
| 回滚对象 | 文件(本轮改动过的全部文件) | 对话上下文 + 文件 |
| 谁来触发 | 模型调 undo 工具;人可敲 /undo;Web GUI 有按钮 |
多在 Web GUI 点按钮或敲命令 |
| 运行环境 | host 侧;headless、无 Web UI 的 profile 同样可用 | 主要在 Web GUI |
| 依赖 | 无(不依赖 git) | 多数依赖 git 或会话 fork |
| 沙箱 | 快照在进程内用 Node fs 写,workspace-write 下无需额外放宽权限 |
经 shell 写工作区外存储时可能被沙箱拒绝 |
核心功能¶
捕获与轮次¶
两条互补路径只做旁路记录,不改变写入语义:
- intent 钩子(主):挂在
fs/write-intent/fs/edit-intentwaterfall,能拿到callId、agent等工具执行信息;须prepend: true,否则可能被内置 observation policy 截断。 - fs 服务包装(兜底):包装
writeText/editText所属原型;若 intent 收不到事件,仍会有快照。
同一次写入可能两条路径都命中,插件用跨机制去重(同路径、同事件、250 ms 内、不同机制只记一条)。
轮次边界来自会话日志中最近一条 user/message 事件的 seq(默认扫描尾部 sessionScanEvents 条);读不到会话的写入归入当前进行中的轮次。
undo 工具¶
模型可调用 undo:
| 参数 | 说明 |
|---|---|
action |
list:按轮次列出快照;restore:回滚 |
id |
回滚单个快照;给出时优先于 turn |
turn |
回滚哪一轮:last(默认)、1/2/…(从新到旧),或 list 显示的 turn 键 |
limit |
list 时返回多少轮,默认 10 |
dryRun |
restore 时只报告每个文件会变成什么,不写盘 |
回滚语义要点:同一文件在一轮内多次改动时,恢复到该轮开始前最早快照对应的内容;本轮新建的文件回滚即删除;回滚动作本身也会被快照,因此可再次 undo(等效 redo);单文件失败不中止整轮,结果逐条报告。
人工入口:/undo 命令¶
| 命令 | 作用 |
|---|---|
/undo 或 /undo list |
列出最近轮次 |
/undo restore [轮次] |
回滚该轮;省略轮次 = 最近一轮 |
/undo dry [轮次] |
预演,不写盘 |
/undo id <快照id> |
单文件精细回退 |
命令与 undo 工具共用同一套执行逻辑。若 profile 无命令注册表则只少人工入口;/undo 已被占用时会自动注册为 /undo-turn。
Web GUI:助手消息动作行¶
已定稿助手消息的动作行(复制、分支旁)会出现 ↶ 回退本轮,点击即回退最近一轮;按钮执行宿主命令 /undo 1,与工具和命令行为一致。预览可用 /undo dry。
安装与启用¶
从 GitHub 安装(README 推荐;package.json 的 dsh.bundle 供 dsh 自动挂载):
dsh plugin --profile web add github:ruaibeite/dsh-undo
也可在克隆仓库后使用自带脚本(会自动改用桌面版 CLI):
./install.sh
PROFILE_NAME=headless ./install.sh
安装后重启 dsh 生效。插件以当前 dsh 进程权限运行;安装前请自行查看 GitHub 源码 与 MIT 许可证。要求 Node >=20(见 package.json engines)。
存储与配置¶
$DSH_HOME/undo/
├── index.json # 快照索引(id/time/path/existed/bytes/turn/sessionId)
└── files/<快照id> # 快照内容
$DSH_HOME 内部的写入会被排除。在 cordis.patch.yml 对应条目可配置:
| 键 | 默认 | 说明 |
|---|---|---|
maxSnapshots |
300 | 快照条数上限,超出淘汰最旧 |
maxFileBytes |
4194304 | 超过此大小的文件不快照 |
onlyToolWrites |
false | true 时只快照带工具 actor 的写入 |
sessionScanEvents |
400 | 定位轮次边界时会话日志尾部扫描条数 |
listFilesPerTurn |
10 | list 每轮最多列出文件数,其余省略汇总 |
debug |
false | true 时事件写入 $DSH_HOME/undo/debug.log |
典型用法¶
- 安装并重启 dsh 后,让 agent 在一轮对话里修改若干文件。
- 在输入框执行
/undo list,查看最近轮次与文件摘要。 - 确认要退哪一轮:执行
/undo dry 1预演(1表示最新一轮),或让模型调用undo,例如action: "restore", turn: "last", dryRun: true。 - 执行
/undo restore 1或模型action: "restore"完成回滚;若只需改一个文件,用/undo id <快照id>或工具的id参数。
Web GUI 下也可直接点助手消息旁的 ↶ 回退本轮;再次点击可撤销刚才的回滚(因回滚本身被快照)。
适用场景与注意¶
适合:需要在 DSH 里按「对话轮次」批量撤销文件改动;headless / workspace-write 等受限会话下仍要快照(进程内 fs,不依赖 shell 写工作区外);不想为回退单独维护 git 影子库;希望模型在改乱代码时自行调用 undo。
限制(README 原文):
- 只回滚文件,不回滚对话。
- 只记录经
ctx.fs的写入;shell 等绕过 fs 直接落盘的改动不会产生快照(之后若再被工具写入,会按当时已存在状态快照)。 - 单文件超过
maxFileBytes不快照。 - 轮次分组依赖会话日志中的
user/message;取不到时退化为按工具调用分组。 - 回滚既有文件走
ctx.fs.writeText,受文件策略约束(只读会话会被拒);删除本轮新建文件用fs.unlinkSync,是唯一不经策略的写操作,用于把「新建」还原为「不存在」。
DSH 生态是「一切皆插件」;社区目录 SkillHub 插件页 为独立站点,与 DeepSeek / 幻方无官方从属关系。
结尾¶
dsh-undo 把「按轮次的文件 undo」做成 host 侧轻量能力:双路径快照、undo 工具与 /undo、GUI 按钮共用一套逻辑,在受限沙箱下也能记录。若你的需求是对话层回退,请选用对口的 rewind/recall 类插件;若主要是 agent 改文件后的可逆操作,可按上文命令安装并重启 dsh。
- 目录页:https://www.skillhub.cn/plugins/ruaibeite/dsh-undo
- GitHub:https://github.com/ruaibeite/dsh-undo