dsh-tauri-turnrewind
Turn-level workspace undo for Git worktrees: every agent turn gets a private snapshot, /undo previews a red/green diff card that is confirmed in place, files changed after the turn are refused with a conflict diff unless --force or --skip-conflicts is passed, and HEAD, index, stash and commit histor
Install
dsh plugin --profile web add dsh-tauri-turnrewind
Sandbox test
This plugin has not been tested yet. Submit a test →
README
dsh-tauri-turnrewind
本地开发中的 DSH turn 回滚插件(TypeScript 重写版,位于 packages/,遵循 workspace 插件规范;旧 JS 版已移除)。
分发(2026-09-06 起):插件已随桌面构建打包——列入dsh-tauri-bundle依赖(构建期部署进src-tauri/resources/node_modules/)与src-tauri/resources/internal-plugins.json预装清单,下载桌面版即装即用,无需任何手动配置。定位仍是实验功能:恢复路径直接使用 Node/Git,尚未接入受控的宿主 sandbox/Tauri bridge,请在可丢弃的工作区先行试用。
Git 目录模式(当前形态):工作区必须位于 Git worktree(OpenCode 风格)。非 Git 目录显式禁用——turn 记为skipped(TURNREWIND_GIT_REQUIRED),/undo说明原因,不再为普通目录建快照。ignore 规则(.gitignore/.git/info/exclude/ global excludes /.gitattributes)委托给源仓库;私有 snapshot repo 通过 alternates 借用源对象。设计与进度见docs/TURN_REWIND_GIT_DIR_PROGRESS.md。
当前状态
这是 Host MVP 原型,当前实现:
- 为已领取的 Agent turn 建立私有 Git 快照;
- 将快照映射记录到
$DSH_HOME/ledger.sqlite; - git 子进程模型:快照、diff、恢复走异步 spawn,不阻塞 Host 事件循环;同一会话的捕获/结算按 FIFO 串行;收到 turn 输入后,before snapshot 通过
agent/pre-stepbarrier 完成后才允许模型和工具执行;同一 workspace 被其他 session 占用时,新 turn 会正常运行但记为skipped,避免共享 snapshot 链互相污染。同步开销已收敛:工作区解析合并为单次 rev-parse 子进程并带 60s 缓存(过期后先回缓存值、后台异步刷新),冲突检测的文件读取(上限 64MB)已改异步; - git 可用性探测:系统没有 git 时,turn 显式记为
skipped(原因TURNREWIND_GIT_UNAVAILABLE),而不是静默失败; - 快照链自愈:私有快照仓库被删/损坏后,下一次捕获自动降级重建基线(日志有一条 warning),后续 turn 照常可撤销;被清空前留下来的旧 turn 会在
/undo选目标时自动识别为死快照并标记跳过(snapshot ref missing),不会甩出 git 原始报错; - alternates 失效自愈:私有 repo 通过 alternates 借用源仓库对象,而源仓库
git gc --prune=now(amend/rebase 的日常残留)可能删掉被借用且不可达的对象;每次 capture 后做连通性检查(git rev-list --objects --missing=print),发现缺对象即降级为自包含存储(不再借用、不再复制源 index)并重建基线,旧 turn 走死快照跳过路径; - 工作区资格守卫(Git 目录模式):会话 cwd 必须位于 Git worktree(子目录自动归并到 worktree 根,共享同一快照域);家目录、家目录祖先、盘根等系统目录直接拒绝;非 Git 目录记为
TURNREWIND_GIT_REQUIRED,不再做全目录预算扫描(见「工作区资格」); - 不可用弹窗:客户端半(
src/client/)通过turnrewind会话投影检测到不可用提示时,在 Web UI 内弹出模态对话框(中英双语、跟随应用主题)。单会话只报一次:提示以会话内消息形式永久留档可查,弹窗只对页面存活期间新到达的提示触发,历史提示(重启后重新进入会话)不会重复弹窗; - 两阶段
/undo:先出预览卡(红绿 diff ++x -y徽标 + 文件清单),卡内 ✓/✗ 按钮确认执行或取消——不看预览就不会误执行;计划 5 分钟过期。过期与取消都只锁执行、不抹账本记录:过期的 plan 卡片保留文件清单与 diff 供随时回看(归档视图);取消的 plan 是用户主动放弃,卡片塌缩为一行「已取消」留痕。确认时二次校验磁盘与预览绑定; - 恢复前比较当前文件与 turn 完成时的快照,发现变化则拒绝覆盖,并给出「turn 产物 → 当前磁盘」的冲突 diff;
- 冲突可用
--skip-conflicts(只恢复无冲突文件)或--force(强制覆盖)直接执行; /undo --redo(重做最近一次已应用的 undo)已禁用:入口在解析层直接拒绝,底层恢复路径加固代码保留但未开放(见「已禁用:redo」);- 注册人类命令
/undo; /undo默认处理当前会话最新的单个可恢复 turn,也可指定完整 turn ID;- 同一 workspace 的活动 turn 或 undo 操作互斥;
- 插件重启时将未完成 turn 标记为 abandoned;未完成的 Undo 与 Redo operation 会标记为
needs-recovery,对应 workspace 在清理前拒绝新的 rewind 操作,避免未知磁盘状态被继续覆盖。恢复面板:不可用弹窗在 reason 命中恢复围栏时提供「打开恢复面板」入口,可查看被围 workspace 的中断操作明细,并选择「已检查,保留历史并解锁」(operation 转 recovery-acknowledged 终态)或「清除 rewind 数据并解锁」(等价 purge); /undo --doctor:只读诊断——git 可用性、工作区资格、账本规模与围栏、快照仓库健康(refs / alternates)、最近 turn 状态、备份新旧;- 账本打开时执行
PRAGMA quick_check(损坏显式拒绝加载,TURNREWIND_LEDGER_CORRUPT),并每日滚动备份到ledger.sqlite.bak(VACUUM INTO一致性快照),损坏时可按「还原 .bak → 重启」恢复; - 每次 Undo 的回退提示独立持久化;下一次模型 step 一次性注入全部 pending notice;
- 不修改用户项目的 HEAD、分支、index、stash 或提交历史。
使用方法
在已加载本插件的 DSH 会话中:
/undo
默认进入两阶段流程:先输出预览卡(每个文件的红绿 diff 与 +x -y 徽标),
卡内附两个按钮——✓ 执行撤销 与 ✕ 取消。点 ✓ 才会真正恢复文件;
不点击的话计划 5 分钟后自动过期,不会误执行。确认时还会二次校验磁盘,
预览之后文件又被改动过(冲突)会拒绝执行并提示重新预览。
也可以指定完整 turn ID、或只看计划不动手:
/undo <session-id>:<turn-number> /undo --dry-run # 仅输出文件清单与分类,不含 diff /undo --preview # 输出内容与 /undo 的预览卡一致
磁盘在预览后又被改动(冲突)时,可以选择处理策略:
/undo --skip-conflicts # 只恢复无冲突文件,跳过的文件会列出 /undo --force # 强制覆盖冲突文件(执行前仍有回滚点保护)
其他:
/undo --cancel <plan-id> # 取消一个待确认的预览计划 /undo --doctor # 只读诊断报告(不能与其他选项组合)
当前不支持 / 已禁用:
/undo --redo # 已禁用(功能冻结),入口直接拒绝 /undo --subtree # 未实现
父对话递归撤销、消息旁 Undo 按钮和设置页模式切换尚未实现。
工作区资格(Git-only)
曾有用户在 QQ 机器人会话(默认工作目录是家目录、约 250 GB 内容)上触发 git add --all 级别的灾难,早期版本因此引入了全目录预算扫描。当前实现以源仓库的 Git worktree 为快照边界:非 Git 目录禁用(不再做预算扫描,也不再为普通目录建快照)。turn 开始前的资格检查:
1. 系统目录直接拒绝:家目录本身、家目录的祖先目录(如 C:\Users、C:\)、以及任何盘符根目录。这类目录无论多小都不做快照。
2. 必须是 Git worktree:会话 cwd 不在 Git worktree 内的 turn 记为 skipped(原因 TURNREWIND_GIT_REQUIRED)。子目录会话自动归并到 worktree 根(git rev-parse --show-toplevel),共享同一快照域;同一仓库的 linked worktree 各自成域。
3. 快照范围委托给源仓库 ignore 规则:.gitignore、.git/info/exclude(每次 capture 重同步)、global excludes 与 .gitattributes 语义与源仓库一致;插件自身只额外排除 .git 元数据与 turnrewind 临时文件。不再有自定义敏感文件名单——未写进 ignore 的文件(含 .env、密钥类文件)会被快照;这是有意的取舍:token.ts、credentials.module.ts 等合法源码曾被旧规则静默排除,导致 undo 永远无法恢复它们。若不希望某些文件进入快照,请把它们加进源仓库的 ignore 规则。
被拒绝的 turn 仍正常执行,只是不提供 undo。同时该会话会收到一条一次性提示([Turn rewind unavailable]),说明工作区被拒绝的原因;每个会话只提示一次,后续 turn 不再重复打扰。提示会以两种形态呈现:
1. 会话内消息:插件来源的上下文注入消息,模型和用户都可见、可审计;
2. Web UI 弹窗:宿主端 turnrewind 会话投影(src/host/service/dialog-projection.ts)把提示折叠进会话列表快照,客户端半(src/client/register/dialog.ts)从 sessions.list 的 projectionValues.turnrewind 读到后弹出模态对话框。去重不在浏览器存储里做(localStorage 会因换端口/清存储丢「已读」):按「单会话一次」种子逻辑,进入会话时已存在的提示视为历史留档(会话内消息永久可见)不弹,只有页面存活期间新到达的提示弹一次。
- 敏感文件提醒:会话首次追踪一个工作区时做一次浅层启发式扫描(根 + 两层、上限 500 文件、跳过 node_modules),命中
.env/密钥类且未被 ignore 的文件时发一条一次性[Turn rewind privacy notice](每会话+工作区一条),列出会被快照的具体文件与退出方式(加 ignore)。扫描失败静默跳过——这是提醒,不是门禁。
大工作区的取舍:不再做预算预扫描,快照耗时与磁盘占用随仓库规模增长(Git ignore 能排除 node_modules 等,但容量/性能风险仍由使用者自行承担);单文件快照/恢复上限为 64 MB——超限文件仍会被捕获进快照,/undo 预览会以 [too large] 标注并在执行后单文件报告为「未恢复」,不会导致整次 undo 失败,其余文件照常恢复。若项目里有此类大文件且不希望被追踪,请把它们加进源仓库的 ignore 规则。
清理已膨胀的快照数据
如果某个工作区在旧版本下已经生成过巨大快照,先停止 DSH Host 进程,再执行:
node packages\dsh-tauri-turnrewind-ts\purge-workspace.mjs "C:\Users\<user>\Desktop\test" # release 数据目录 ~/.dsh node packages\dsh-tauri-turnrewind-ts\purge-workspace.mjs "C:\Users\<user>\Desktop\test" --home "$env:USERPROFILE\.dsh.dev" # debug
(需先 pnpm --filter dsh-tauri-turnrewind build 产出 dist/——CLI 从构建产物导入引擎。)
该命令删除该工作区对应的私有快照仓库($DSH_HOME/snapshots/<hash>.git)及其全部账本记录(turns / operations / notices / plans / workspaces),其他工作区的数据不受影响。workspace 被运行中的 Host 占用时命令会拒绝执行(workspace lock),请先停止对应 Host 进程。
注意:这个 CLI 只在两种场景需要——①账本损坏前的彻
…(truncated)
Rendered from the README published on npm. Formatted only, not edited.
Versions
Published on npm::2,最后更新 2026-09-08