dsh-file-undo v0.20:agent 改崩文件,一键倒回三步前

AI 把文件改崩时,Ctrl+Z 救不了你 ------ dsh-file-undo v0.20

一个为 DeepSeek Harness(DSH)打造的文件撤销插件:agent 每一次 write / edit 落盘前自动进时间线, 侧栏面板红绿 diff 逐条审查,两阶段撤销 + 重做双向循环,面板自动跟随你正在看的会话。纯 TypeScript 双半包,MIT 开源,公开版本 v0.20。

先看结论

用 AI 编码助手干活,文件不是你改的,但锅是你的。几个熟悉的场景:

  • 让 agent 重构 20 个文件,跑完测试才发现第 3 步就引入了 bug ------ 中间 17 步全是白做的
  • 让它"顺手把那个临时文件删了试试",删完它自己都不记得动过什么
  • 想回退,git status 一屏红字翻到眼花;更糟的是工作区根本没 git init

dsh-file-undo 做的事很简单:Agent 的每一次文件操作,落盘前自动进时间线;红绿 diff 逐条审查;看准了一键撤销,反悔了再一键重做。

装好插件重启,侧栏底部多一枚「撤销」图标,徽章实时显示可撤销操作数。点开是审查面板:左栏历史列表(时间、工具徽章、文件名、+恢复 / −移除 行数),右栏红绿 diff 和操作按钮。

创建类操作是真删文件,红键 + 内容/hash 双闸伺候;那条灰色的"失败"记录也是账本的一部分------Agent 试过什么、为什么没成,都在。

新增功能与优化体验

面板跟着你走

以前面板只认「最近做过文件操作的会话」------切到一个还没动过手的会话想看历史,它还停在旧的,看着就像那个会话"没有历史"。

现在面板自动跟随你当前正在查看的会话:切过去,列表、差异、撤销按钮一起跟上,哪怕这个会话还什么都没改过。侧栏徽章的未撤销计数也跟着改成统计当前会话。

想手动指定也可以:面板头部的会话切换器列出同一项目树(当前目录及其父子目录)下的全部会话,显示平台会话标题和活跃/存档状态,选中即钉住,优先级高于自动跟随。

同目录多会话不再靠 uuid 认人

同一个目录下开几个会话,以前列表里只能靠一串 UUID 区分,根本认不出谁是谁。现在直接显示平台会话标题

修掉一个"历史凭空消失"的坑

D:\projD:\proj\d:\projD:/proj 四种写法会派生出四个快照仓库,同一项目的会话历史被劈开,面板只认得其中一个。现在工作区键由规范化后的路径派生------一个目录恒等于一个键,存量数据零迁移。

撤销重做是双向无限循环

反悔了?已撤销的条目上有「重新应用」,写回操作后的内容。回退到中间某一步时被连带作废的那些条目不用逐条救------重新应用上游时整批复活 ,确认框明示「连带恢复 N 条」。复活之后还能再撤销。

每条快照挂着模型轮次

host 从平台轨迹读工具调用事件的权威载荷,把快照映射回真实的模型轮次。悬停提示长这样:"第 4 轮 · 第 2 步 · 本轮共 6 次工具调用"。

60 秒走一遍

让 Agent 重构一个模块。面板里条目一条条长出来,每条挂着工具徽章和行数统计。

点开第 3 条,红绿 Diff 摆在眼前:绿行 = 撤销将恢复的内容,红行 = 撤销将移除的当前内容------这是"撤销视角"的 Diff,不用你脑内取反。确认改错了,点一次「撤销」按钮变成「确认撤销?」(3 秒内再点才执行,防手滑),文件回到操作前的状态。

后面 17 步怎么办?它们不会凭空消失,而是被标记成 cascade(连带作废)。想回到更早的某个时点,选中任意条目点「回退到此状态」,单次原子写回,会先告诉你这一下会丢弃其后多少步。

不想开面板,聊天里 /undo 一套命令全都在:/undo list 看历史、/undo <n> 撤销指定条目、/undo prune 7 清理 7 天前的快照(只缩回退深度,不动任何当前文件)。

撤销的安全闸,比撤销本身重要

能一键回写文件的工具,危险不在功能,在误触。这个插件的预检逻辑比撤销本身写得还长:

  • external_modified :快照之后你手动改过这个文件?直接拒绝执行,面板写明原因。AI 不能吞掉你的手工编辑------真要强制撤销,走 /undo 命令(保留原语义,不做此拦截)。
  • superseded_by_later_ops:直接撤销中间某条旧条目?拒绝,并告诉你它后面还压着自己记录的 N 条编辑------引导你去用「回退到此状态」,而不是制造一个账本对不上的文件。
  • 乐观锁 :预检通过后执行写回时携带 expectedCurrentHash(sha256)。文件在预览和确认之间又变了,写回直接报 stale 拒绝。
  • 两阶段确认:第一次点击只是武装按钮,3 秒不点自动解除。创建类操作的撤销是真删文件,走同样的确认 + 内容/hash 双闸。

引擎裁决、UI 只展示------「能不能动这个文件」全部在 host 侧判定,客户端只拿分类结果渲染界面。这条原则让它敢于把删除键交给用户。

底层原理

双半包:host 拿事实,client 只渲染

Host 半挂在工具链的 tools/pre-execute / tools/post-execute 钩子上:落盘前捕获 before 全文,工具执行完回填 after 全文和哈希。Client 半是侧栏面板,通过带 Host 头信任围栏的 /file-undo/api/* 拿结果渲染。

text 复制代码
agent 调用 write / edit
        │
        ▼
┌─ host 半 ─────────────────────────────────────┐
│ pre-execute:落盘前捕获 before 全文            │
│ post-execute:回填 after 全文 + sha256         │
│ 存储:~/.dsh/file-undo/<工作区>/<会话>/jsonl   │
└──────────────┬────────────────────────────────┘
               │  /file-undo/api/*
               ▼
┌─ client 半(侧栏面板)────────────────────────┐
│ 历史列表 → host 侧 LCS diff → 红绿渲染         │
│ 预检分类 → 两阶段确认 → 事务写回(乐观锁)      │
└───────────────────────────────────────────────┘

原子化回滚:一次写盘,一本账,不留中间态

回退不是"逐条逆序撤销"------那样中途崩一次就留下半回滚的烂摊子。它是一次原子写回 加一次单事务账本标记

  • 文件要么整体回到目标态、要么完全不动,没有"写了一半"的状态;
  • 目标行和它后面所有被连带作废的行,在同一个锁内批量落盘。

这不是洁癖。分两次写就会造出"已标记撤销却没有撤销来源"的僵尸行,而那正是这个插件存在的意义------消除账本滞后。重应用时整批连带复活同理。

全文快照 + 现场算 diff

快照存 before/after 全文加哈希,不是反向补丁------diff 是渲染时现场算的,撤销是整文件写回,两件事解耦,各自简单可靠。补丁链那种断一环全断的设计,不要。

diff 算法值得一句:host 侧行级 LCS 做了前缀/后缀裁剪和整数行 ID + Uint32 DP,超大中段降级为整块替换------800 行以内的 diff 都是逐行精细渲染,再往上给「显示全部」按钮。

存储:不依赖 Git,也不怕大

text 复制代码
~/.dsh/file-undo/
  <workspace-key>/          # 由会话 cwd 规范化派生(消毒 + 8 位哈希后缀防撞)
    <chat-key>/             # 会话 id
      snapshots.jsonl       # append-only,并发安全(单飞写锁 + 原子改名)
      git-archive/          # 第二层归档:每条快照一个 commit,静默降级

与你的 Git 仓库零接触,没 git init 过的工作区照样能用。

诚实的边界

  • 只覆盖 write / edit 两个工具;agent 在 shell 里用 Set-Content 或重定向改文件,不进时间线
  • 「回退到此状态」是丢弃后续改动,不是把后续改动重放到旧基线上------预检会把丢弃数量摆在明处
  • 深度集成是它的价值,也是它的边界:这是 DSH 专属插件,离了 DSH 没有意义
  • 失败的调用也会进面板("失败日志"而非隐藏)------你知道 agent 试过什么、为什么没成

上手

sh 复制代码
# 本地构建(当前版本的安装方式)
git clone https://github.com/QinLuza/dsh-file-undo.git
cd dsh-file-undo && pnpm install && pnpm build

# 在 profile 的 package.json 里加依赖
#   "dsh-file-undo": "link:<本目录绝对路径>"
# 然后在 profile 目录执行
pnpm install

重启 DSH(host 端生效);之后纯 client 端的改动只需浏览器硬刷新。npm 包发布后可以一条命令直装:dsh plugin --profile web add dsh-file-undo

仓库:github.com/QinLuza/dsh... ------ 内置一套 verify-*.mjs 回归脚本,快照引擎、diff 渲染、会话隔离每一条语义都有断言盯着,改坏东西会直接红。

收个尾

三句话总结:

  • Agent 的每一次文件操作都该有一条时间线------这不是 Git 的替代,是不依赖 Git 的那层"后悔药"
  • 安全闸和撤销同等重要:预检分类、乐观锁、两阶段确认,宁可拒绝不可错写
  • 多会话工作流里,面板必须跟人走------现在的自动跟随让它终于像个"当前会话的历史",而不是"某个会话的历史"

下一步:装上,让 agent 随便改点东西,打开侧栏那枚「撤销」图标看一眼。之后你大概会像我一样,在每次让 agent 动手之前先确认它装没装。 检分类、乐观锁、两阶段确认,宁可拒绝不可错写

  • 多会话工作流里,面板必须跟人走------现在的自动跟随让它终于像个"当前会话的历史",而不是"某个会话的历史"

下一步:装上,让 agent 随便改点东西,打开侧栏那枚「撤销」图标看一眼。之后你大概会像我一样,在每次让 agent 动手之前先确认它装没装。

相关推荐
夏文强3 小时前
DeepSeek Harness 可观测性:用 OpenTelemetry 把会话遥测出去
人工智能·开源·大模型·agent·deepseek
AC赳赳老秦3 小时前
电力能源公开数据采集实操:用 OpenClaw 合规抓取电网电价与发电量数据,生成区域能源供需分析报告
大数据·数据库·人工智能·python·php·deepseek·openclaw
夏文强3 小时前
DeepSeek Harness SDK 集成:把 Agent 嵌进你的应用
人工智能·开源·大模型·agent·deepseek
Sendingab18 小时前
深入理解 DeepSeek Harness(dsh):让模型真正“动手干活“的开源 Agent 框架
智能体·deepseek·harness
大模型真好玩18 小时前
DeepSeek Harness 入门很简单(二)——DeepSeek Harness通用设置及Agent预设详解
人工智能·agent·deepseek
仙魁XAN1 天前
【Codex + Deepseek】第 7 篇:如何把一个模糊想法变成可执行开发需求
人工智能·codex·deepseek·vibe coding
夏文强1 天前
DeepSeek Harness 底层探秘:Cordis 元框架与「一切皆插件」的实现
人工智能·开源·大模型·agent·deepseek
仙魁XAN1 天前
【Codex + Deepseek】第 2 篇:什么是 vibe coding:自然语言驱动开发的真实含义
人工智能·codex·deepseek·vibe coding
夏文强1 天前
DeepSeek Harness 可追溯性实战:会话日志的 resume、fork 与 replay
人工智能·开源·大模型·agent·deepseek