前言¶
用 DSH 跑 coding agent 时,常见事故不是改错一行,而是 agent 把原本正确的内容覆盖掉之后,你才从 diff 或运行结果里发现问题。Git 要嘛还没 commit,要嘛改动根本不在仓库里;编辑器 Undo 管不到 agent 在别的路径写的文件;回收站也救不了「覆盖」这种没有独立副本的情况。BeforeWrite 是 maintainer caishengshuo 发布的 DSH 宿主插件,挂在写盘前的 fs/write-intent 钩子上,在内容被替换的瞬间把旧内容存下来,并通过工具 time_machine 和 Web 界面里的会话时间轴做列表、对比和回滚。项目在 GitHub 上标为 1 star,目录分类为「记忆」;许可证 MIT,当前 package 版本 5.1.0。
这是什么¶
BeforeWrite(插件包名 dsh-beforewrite)的定位可以概括成 README 里那句话:Every file, one version before it breaks——粒度是一次写入,不是按小时备份。它注册给 agent 的工具名固定为 time_machine,和项目名 BeforeWrite 不是同一个字符串。
与多数挂在 Client UI 槽位、多一块面板的 DSH 插件不同,BeforeWrite 挂在 Host Event 的 waterfall 上。写盘动作发生前,插件能拿到目标路径和即将被覆盖的旧内容;平时几乎无感,需要时才通过工具或界面找回某一版。
核心功能¶
自动快照¶
凡经过 DSH 文件服务的写入或编辑,在内容被替换前会自动留一份快照。索引追加写入 ~/.dsh/time-machine/index.jsonl,正文按 sha256 内容寻址放在 blobs/ 下,相同内容只存一次。
六个工具动作¶
全部通过 time_machine 调用:
| 动作 | 参数 | 作用 |
|---|---|---|
list |
limit, path |
最近快照,新 → 旧 |
history |
path |
某文件的全部版本 |
diff |
id |
快照与当前文件的行级对比 |
restore |
id |
回滚到该快照;回滚前的当前内容也会再存一份 |
stats |
— | 体量与去重后占用 |
snapshot |
path, reason |
手动打点 |
restore 不是简单撤销:替换前会把「当前磁盘内容」再快照一次,反悔可以再 restore 到新编号,历史只增不减。
图形界面时间轴¶
安装后 Web 界面顶栏下方会出现「时光机」入口。左栏按 DSH 会话切换;右栏合并三类记录——用户真正发出的「说过」、工具调用的「动作」、文件改动的「快照」。快照行可先看 diff,再一键回档。数据旁路监听 session/event,界面通过插件 HTTP 接口读取;回档接口仅接受 POST 并做同源校验,避免跨站页面静默触发回滚。
默认策略与边界¶
单文件快照上限 2 MiB;索引条数上限 20000,超出按旧到新淘汰。默认忽略 node_modules、.git、time-machine 路径。二进制内容会存并标记,但 diff / restore 会拒绝,避免把字节当文本写回;需从 blobs/ 人工取回。Shell 里 rm、sed -i、重定向、mv 等不经过文件服务,不会触发 write-intent,跑破坏性命令前应手动 snapshot。
安装与启用¶
前提:DSH 宿主(dsh web)在运行,且 plugin_manager 可用。
从本地目录安装(路径换成你机器上的插件目录):
plugin_manager(action="install_bundle", target="G:\\path\\to\\beforewrite")
README 也写了先取得仓库目录再对同一目录执行 install_bundle 的流程;具体以 GitHub 仓库 说明为准。
装完检查:
- 工具列表里出现
time_machine; - 默认存储目录
~/.dsh/time-machine/已创建,含blobs/与index.jsonl。
若改插件代码后重装无效果,README 指出常见原因是 DSH 按包名缓存插件模块:需同步改 package.json 与 cordis.patch.yml 里的 name 再装一次,细节见仓库内 docs/waterfall-notes.md。
典型用法¶
列出最近快照并做 diff、回滚的示例(来自 README):
$ time_machine list
最近 6 条快照(共 19 条,新 → 旧):
#19 2026-10-25 00:08:31 write (当时还不存在) G:\sklii\tm-final-check.txt
#18 2026-10-25 00:08:13 restore 147 B G:\sklii\tm-after-restart.txt
#17 2026-10-25 00:08:09 edit 110 B G:\sklii\tm-after-restart.txt
$ time_machine diff id=17
#17 2026-10-25 00:08:09 G:\sklii\tm-after-restart.txt
(旧 = 快照 / 新 = 现在)
─────────────────────────────
+2 行 / -1 行
@@ …
$ time_machine restore id=17
已回滚 G:\sklii\tm-after-restart.txt
恢复到 #17 的状态,110 B,操作类型 update。
回滚前的版本也存了一份,需要反悔就再 restore 那个新编号。
在 agent 即将用 shell 直接改文件前,可手动打点:
time_machine(action="snapshot", path="G:\\proj\\config.yaml", reason="before-bash-delete")
改 index.js 后可在插件目录离线跑冒烟测试(不依赖宿主):
node smoke.mjs
适用场景与注意¶
适合长期用 DSH 让 agent 改代码、配置或文档、又希望在「未 commit 的覆盖」这一档多一层兜底的人。不适合指望它替代 Git 版本管理、远程备份,或自动拦截所有 shell 写盘。
插件以当前 DSH 进程权限读写本地文件与 ~/.dsh/time-machine/;安装前应阅读源码与 MIT 许可证,确认存储路径与回档行为可接受。社区目录 SkillHub 插件页 与 GitHub 无 DeepSeek / 幻方官方从属关系,DSH 生态本身是「一切皆插件」的独立社区形态。
README 还列了未实现方向:会话级整轮回滚、更细保留策略、大索引下的读取缓存、以及依赖 fs 服务字节写回的二进制回档等。当前局限包括无远程同步、索引只增不减导致磁盘缓慢增长、工具名固定为 time_machine 等,使用前心里有数即可。
结尾¶
BeforeWrite 补的是「每一次写入前留一版」这一格:对 agent 经文件服务的改动几乎透明,需要时用 time_machine 或 Web 时间轴找回状态。目录与源码:
- 目录页:https://www.skillhub.cn/plugins/caishengshuo/beforewrite
- GitHub:https://github.com/caishengshuo/beforewrite