给 DSH Agent 装一个 Ctrl+Z:dsh-file-undo 操作级回退插件

让 AI agent 改文件不再"覆水难收"——自动记录每次写入前的状态,一条 /undo 回到操作前。
封面图

痛点:agent 改文件,快、狠、难反悔

用 DeepSeek Harness(DSH)这类 agent 跑活的人都有过这种时刻:你让它改一个配置文件,它一口气动了十几个文件;你盯着看改到一半,发现某个关键文件已经被覆盖了,而它没有留底

Git 能救,但前提是这个文件恰好被 commit 过、且你记得上一次提交点。现实里更多的情况是:文件在 git 之外,或者 agent 改得太快太碎,等你回过神来已经错过了干净的回滚点。

dsh-file-undo 解决的就是这件事:给 DSH 里的 agent 加一个文件级的"撤销键"。

它做什么

一句话:自动记录 agent 每次 write / edit 操作前的文件内容,用 /undo 还原。

四个命令,一张表讲清:

命令 作用
/undo 撤销最近一次文件写入/编辑
/undo list 查看所有已记录的操作(索引 + 时间 + 工具 + 文件路径)
/undo <n> 撤销指定索引的那一次操作
/undo prune [days] 清理 N 天前的快照,默认 7 天

一个典型场景:

/undo list
  [0] 14:20 edit config.yaml
  [1] 14:21 write src/utils.ts
  [2] 14:23 edit src/index.ts

/undo 1     # 只回退 src/utils.ts 那一次,其余不动

它怎么工作

原理不复杂,关键在设计上绕开了一个坑:

  • 拦截时机:插件挂在 DSH 的 tools/pre-execute 瀑布上——在 write / edit 工具真正执行之前,先把目标文件的当前内容读出来存成快照。
  • 为什么是"执行前"而不是"执行后":因为 DSH 的 write/edit 工具执行完只返回一句成功文案,把文件操作前的内容丢掉了。所以要留底,必须在工具动手之前自己读一遍。
  • 存储:快照 append-only 追加到本地 JSONL(~/.dsh/file-undo/snapshots.jsonl),一条一个操作,避免并发写时的读改写竞争。
  • 还原/undo 时把快照里的"操作前内容"通过官方 fs 服务写回文件,并携带当前会话的沙箱策略,确保写回权限和原操作一致。

边界:诚实说清楚它管不了什么

这个插件做得很窄,也正因为窄才可靠。以下场景不在覆盖范围内:

  1. 只覆盖 write / edit 两个工具——agent 通过 shell 直接改文件(Set-Content、重定向等)管不了。
  2. 删除回退不支持——官方 fs 至今没有 delete/unlink 接口,文件删除无法被追踪或还原;等官方补上这个能力才能做。
  3. 快照全局共享——不按会话隔离。

这些不是"没做完",而是被明确的设计边界挡住了(比如删除回退,硬做就意味着绕过官方 API,得不偿失)。

安装

本地构建(link)三步:

# 1. 构建插件
pnpm install && pnpm build

# 2. profile 的 package.json 加依赖
#   "dependencies": { "dsh-file-undo": "link:<本目录绝对路径>" }

# 3. profile 的 cordis.patch.yml 挂载
#   - insert:
#       - id: file-undo
#         name: 'dsh-file-undo'

# 4. 重装并重启
cd ~/.dsh/profiles/web && pnpm install && dsh web

(若发布到 npm,则一条 dsh plugin --profile web add dsh-file-undo 即可。)

开发中踩的几个坑(给后来者)

写这个插件的过程,Cordis 的几处"运行时才报"的机制比想象中更狠,列出来供避雷:

  1. inject 漏声明服务:代码里用了 ctx.commands 但没写进 injecttsc 类型检查照样过(类型合并掩盖了),要到运行时 Guard 才抛 cannot get property "commands" without inject类型过 ≠ 能跑
  2. 想当然的工具名:一开始拦截 str_replace_editor,结果这个环境里 agent 实际用的文件工具是 write / edit,拦截永远不命中。先查工具清单再写拦截
  3. 带参数命令要声明 input/undo list/undo <n> 这种带参数的形式,命令不声明 input 提示的话,会被前端降级成普通消息,根本不触发。
  4. 写回要带沙箱策略:插件在全局 context 里写文件,不传 session 的 sandboxPolicy 会直接 file access denied——还原和原操作必须在同一个 workspace 边界内。

这四条里,任何一条单看都是"哦,注意一下",但它们叠加起来,足够让一个插件"能加载、能注册、就是不干活"。

结语

dsh-file-undo 不是一个宏大的东西——它就是一个文件级的撤销器,范围只有 write/edit 两个工具,但在这个范围内是实测可用的:拦截 → 快照 → 列出 → 还原,全链路跑通。

如果你的 agent 经常改文件,又不想每次都靠 Git 兜底,值得一试。

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐