概览
查看原图 ↗
查看原图 ↗
查看原图 ↗
查看原图 ↗
查看原图 ↗
English | 中文
为 DeepSeek Harness
同步回退对话与工作区文件,不碰 Git。每次回退都发生在一个新的 fork 中,因此改变主意时
还可以用 /redo 回去。由一个快得离谱的 Rust 🦀 引擎驱动。
> /rewind 2
Rewound to turn 2 (把限流改成按租户维度).
Files: 7 written, 1 deleted.
The conversation continues in session-9f3c1a04-….
Run /redo there to reverse this rewind.
快速开始#
环境要求#
- 带有
web或headlessprofile 的 DeepSeek Harness - Node.js
^22.19或>=24 - x64 或 arm64 架构的 Linux、macOS 或 Windows
原生 filesnap 二进制会随插件 一起安装,不需要另装 Rust、Git 或其他运行时。
[!IMPORTANT] dsh 目前没有为仓库外插件提供正式的事件类型注册接口,因此 dsh-filesnap 会在加载时 注册自己的 session 事件。如果卸载插件,它捕获过的会话将暂时无法打开,重新安装后 即可恢复访问。 会话数据仍完整保留在磁盘上。详见 架构限制。
1. 安装#
$ dsh plugin --profile web add dsh-filesnap
安装到此为止:本包声明了 dsh.bundle,启动器会自己把它挂进 profile。headless
profile 把 web 换成 headless 即可。如果你为 0.2.2 之前的版本在 profile 的
cordis.patch.yml 里手动加过 id: filesnap 那一行,它照常有效——profile 自己的
patch 在 bundle 之后应用,同 id 的行会覆盖而不是重复。
2. 验证并使用#
$ dsh --profile web --dump-config | grep -A 1 filesnap
- id: filesnap
name: dsh-filesnap
重启 profile,完成一轮 agent 任务,然后输入 /rewind。如果组合后的 profile 中没有
插件,或浏览器控件没有出现,请看排障指南。
为什么选择 dsh-filesnap#
| 意味着什么 | |
|---|---|
| 对话 + 文件 | 在选中的轮次 fork 对话,并把工作区恢复进同一个 fork。 |
| 不依赖 Git | 在 Git 仓库和普通目录中都能工作,不改变 commit、branch、stash 或 worktree 状态。 |
| 回退也能撤销 | 在还原写入前先创建救援点,/redo 可以撤销这次回退。 |
| 覆盖面更完整 | 支持二进制文件、被 Git 忽略的文件,以及经 ctx.fs 发生在项目目录外的编辑。 |
| 原生引擎 | 有界扫描、内容寻址和还原运行在独立的 🦀 Rust 小型二进制中,不占用会话的 Node 进程。 |
| 范围可检查 | /rewind status 会报告存储占用,以及哪些文件未被保护和对应原因。 |
这套设计不会把版本控制当作快照仓库。带版本日期和源码审计的完整对比见 插件对比。
命令#
| 命令 | 结果 |
|---|---|
/rewind |
列出每轮开始前捕获的工作区状态。 |
/rewind <turn> |
在该轮 fork 对话,并恢复对应文件。 |
/redo |
撤销落在当前会话中的回退。 |
/rewind status |
报告存储内容和当前未受保护的文件。 |
/rewind 接受列表中显示的轮次编号或 point id。它刻意不支持“往回三轮”这样的相对
寻址:还原会覆盖文件,因此目标必须明确。
这两个命令都不会触发模型轮次。回退是用户对对话执行的操作,不应该再经过被回退的对话。
哪些文件会被保护#
每次模型 step 之前,dsh-filesnap 会捕获三个有界集合的并集:
- 工作区已知的文件,包括 Git 报告的 tracked 文件名;
- 在
ctx.fs写入或编辑之前观察到的路径,即使路径位于项目目录之外; - 最近发生变化的工作区文件的有界扫描,用来覆盖 shell 命令造成的写入。
内容按哈希寻址,因此未变化的文件会被复用,不会每轮复制一份。.filesnapignore 是
对称的:被忽略的路径不会被存储、恢复,也不会被还原操作删除。只有目标快照明确记录某个
路径当时不存在,还原才有权删除它。
/rewind status 会重新扫描当前目录,并列出超出覆盖范围的项目,例如无法读取、过大或
不是普通文件的路径。覆盖细节与还原不变量见架构说明。
性能#
引擎运行在模型请求之前,而模型请求通常需要数秒。当前在 warm page cache 下得到的初步 测量是:
| 工作区 | 捕获文件数 | 首次捕获 | 重复捕获 |
|---|---|---|---|
| 本仓库 | 84 | 20 ms | 8 ms |
| DeepSeek Harness monorepo | 磁盘上 70,918 个文件中的 7,995 个 | 1.75 s | 268 ms |
这些数字只描述原始测量机器,不是对所有设备的承诺。跟踪集合是有界的,不会每轮遍历 70,918 个文件;重复捕获还会复用未变化的内容。测量方法、尚缺的机器元数据和复现命令见 基准测试。
浏览器体验#
可选的 ./client export 会增加:
- 每条已完成 assistant 消息旁边的回退操作;
- header 中的 redo 和存储状态操作。
转录本身已经是轮次列表,所以插件不会再增加一个重复的 checkpoint 面板。在浏览器中, 部署先创建正确组合的子会话,host 把文件还原进去,client 再打开该子会话。headless 场景 则由 host 插件自行 fork。
浏览器 bundle 会在发布流程中完成类型检查和构建,但目前还没有自动化浏览器内测试。
配置#
默认值适用于普通本地部署。
| 字段 | 默认值 | 用途 |
|---|---|---|
command |
自动解析 | 指向另一个引擎构建,或由远程 subprocess provider 解析的裸命令。 |
dataDir |
平台数据目录 | 存储位置,永远不在项目目录中。 |
timeoutMs |
120000 |
单次引擎调用的墙钟上限。 |
graceMs |
2000 |
取消或超时后从 SIGTERM 到 SIGKILL 的宽限时间。 |
maxOutputBytes |
1048576 |
每条输出流的内存上限。 |
declareEdits |
true |
在编辑之前立即记录文件 pre-image。 |
未知字段和不可用值会在插件加载时失败,不会等到某次回退点悄悄消失后才暴露。
当前限制#
- 卸载插件后,包含其事件类型的会话在重新安装前无法读取;不会删除任何会话数据。
- 在 web 输入框中键入
/rewind只会报告子会话 id,不会自动跳转;逐轮浏览器操作会完成跳转。 - host 自行执行的 headless fork 会继承模型 route 和 preset,但不会继承部署的 per-agent 模型选择或 workspace 挂载。
- shell 在工作区外、大小边界之外或近期变化预算之外创建的文件,需要同时被文件系统写入 事件观察到才能覆盖。
- 引擎已经支持
gc、doctor和删除 session,但插件还没有把它们暴露为/rewind子命令。
文档#
| 文档 | 回答什么问题 |
|---|---|
| 架构说明 | 何时捕获、fork 与还原的顺序,以及记录了什么。 |
| 插件对比 | 不同 dsh rewind 设计的区别,并标明审计版本。 |
| 基准测试 | 已发布的时间数据意味着什么,以及如何复现。 |
| 排障指南 | 安装、profile、client bundle 和存储诊断。 |
| 参与贡献 | 本地开发、构建和四层测试。 |
快照引擎也可以脱离 dsh 使用:通过 Rust(cargo add filesnap)或其带版本的 JSON Lines
CLI 集成。本仓库完整的 subprocess adapter 是 src/cli.ts。
参与贡献#
欢迎用中文或英文参与:
- Discussions:问题、想法和使用案例;
- Issues:bug 和明确的改进项;
- Pull requests:已经实现的修改。
修改 host/engine 边界前请阅读 CONTRIBUTING.zh.md。安全问题请通过 SECURITY.md 中的私有渠道报告,不要创建公开 issue。
