回滚智能体对文件的写入与编辑,支持审查和重做
26.6/100
综合分
1
GitHub 星标
—
npm 周下载
—/5
用户评分
数据截至 2026/10/17 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add QinLuza/dsh-file-undo该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)
状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态信任档位:需留意
是什么
dsh 原生插件 · dev
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
更新放缓:最近一次提交在 56 天前
档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
⚠︎ 实装验证未通过(dependency_conflict · 2026/10/17) ——可能是验证环境差异,装前建议到 GitHub 仓库确认最近更新与 issue。
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码v2.7.0🔵 扫描:含敏感能力尚无人工评估展开查看扫描说明与证据 ↓
安全扫描自动 · 静态分析源码
v2.7.0静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。
可执行外部命令读写/删除本地文件写入剪贴板(不计风险)发起外部网络请求
查看扫描证据(共 58 处,展示前 53 条)
- 执行外部命令来自配置/变量lib/index.js:498
const child = execFile(gitBin, args, { - 执行外部命令来自配置/变量lib/index.js:528
execFile(candidate, ["--version"], { timeout: 5e3 }, (error) => error ? reject(error) : resolve()); - 写入本地文件lib/index.js:540
await mkdir(archiveRepoPath(scope), { recursive: true }); - 发起网络请求lib/client.js:352
const response = await fetch(`/file-undo/api/${method}`, { - 写入剪贴板lib/client.js:1069
await navigator.clipboard.writeText(text); - 读取本地文件lib/store.js:188
workspaceKeys = await readdir(storeRoot(), { withFileTypes: true }).then((entries) => entries.filter((e) => e.isDirectory()).map((e) => e.name)); - 读取本地文件lib/store.js:196
chatKeys = await readdir(join(storeRoot(), workspaceKey), { withFileTypes: true }).then((entries) => entries.filter((e) => e.isDirectory()).map((e) => - 读取本地文件lib/store.js:203
const info = await stat(path); - 写入本地文件lib/store.js:236
await mkdir(scopeDir(scope), { recursive: true }); - 读取本地文件lib/store.js:278
text = await readFile(snapshotPath(scope), "utf8"); - 写入本地文件lib/store.js:297
await writeFile(stagingPath(scope), all.map((s) => `${JSON.stringify(s)}\n`).join(""), "utf8"); - 写入本地文件lib/store.js:298
await rename(stagingPath(scope), snapshotPath(scope)); - 写入本地文件lib/store.js:303
await appendFile(snapshotPath(scope), `${JSON.stringify(snapshot)}\n`, "utf8"); - 读取本地文件lib/api.js:588
hasRecords: existsSync(snapshotPath({ - 读取本地文件smoke-test.mjs:49
return readFile(target.targetKey, 'utf8') - 写入本地文件smoke-test.mjs:53
await writeFile(target.targetKey, content, 'utf8') - 写入本地文件smoke-test.mjs:132
await writeFile(fileA, 'hello', 'utf8') - 写入本地文件smoke-test.mjs:156
await writeFile(fileA, 'WORLD', 'utf8') - 读取本地文件smoke-test.mjs:158
check('write snapshotted before-state', (await readFile(fileA, 'utf8')) === 'WORLD') - 写入本地文件smoke-test.mjs:162
await writeFile(fileA, 'WORLD!', 'utf8') - 读取本地文件smoke-test.mjs:173
const text = await readFile(scopedSnapshots, 'utf8').catch(() => '') - 读取本地文件smoke-test.mjs:188
check('/undo restored edit', undone.kind === 'success' && (await readFile(fileA, 'utf8')) === 'WORLD', undone.text) - 读取本地文件smoke-test.mjs:193
check('/undo 0 restored write', undone0.kind === 'success' && (await readFile(fileA, 'utf8')) === 'hello', undone0.text) - 写入本地文件smoke-test.mjs:197
await writeFile(join(workspace, 'new.txt'), 'fresh', 'utf8') - 读取本地文件smoke-test.mjs:202
try { await readFile(join(workspace, 'new.txt'), 'utf8'); goneCheck = false } catch { goneCheck = true } - 执行外部命令固定命令smoke-test.mjs:218
execFileSync('git', ['--version'], { stdio: 'ignore' }) - 写入本地文件smoke-test.mjs:237
await writeFile(fileA, 'hi', 'utf8') - 删除本地文件递归smoke-test.mjs:254
await rm(workspace, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 }) - 删除本地文件递归smoke-test.mjs:255
await rm(fakeHome, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 }) - 读取本地文件src/api.ts:1000
hasRecords: existsSync(snapshotPath({ workspaceKey: workspaceKeyOf(entry.cwd), chatKey: entry.id })), - 发起网络请求src/client/api.ts:167
const response = await fetch(`/file-undo/api/${method}`, { - 执行外部命令来自配置/变量src/index.ts:613
const child = execFile(gitBin, args, { - 执行外部命令来自配置/变量src/index.ts:644
execFile(candidate, ['--version'], { timeout: 5_000 }, error => (error ? reject(error) : resolve())) - 写入本地文件src/index.ts:660
await mkdir(archiveRepoPath(scope), { recursive: true }) - 读取本地文件src/store.ts:281
workspaceKeys = await readdir(storeRoot(), { withFileTypes: true }) - 读取本地文件src/store.ts:290
chatKeys = await readdir(join(storeRoot(), workspaceKey), { withFileTypes: true }) - 读取本地文件src/store.ts:298
const info = await stat(path) - 写入本地文件src/store.ts:334
await mkdir(scopeDir(scope), { recursive: true }) - 读取本地文件src/store.ts:424
text = await readFile(snapshotPath(scope), 'utf8') - 写入本地文件src/store.ts:447
await writeFile(stagingPath(scope), all.map(s => `${JSON.stringify(s)}\n`).join(''), 'utf8') - 写入本地文件src/store.ts:448
await rename(stagingPath(scope), snapshotPath(scope)) - 写入本地文件src/store.ts:454
await appendFile(snapshotPath(scope), `${JSON.stringify(snapshot)}\n`, 'utf8') - 读取本地文件verify-protocol.mjs:32
const fixtureText = await readFile(FIXTURE, 'utf8') - 删除本地文件递归verify-protocol.mjs:300
await rm(tempHome, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 }) - 写入本地文件verify-prune.mjs:40
await mkdir(join(tempHome, '.dsh', 'file-undo', scope.workspaceKey, scope.chatKey), { recursive: true }) - 写入本地文件verify-prune.mjs:41
await writeFile(storePath, seeds.map(s => JSON.stringify(s)).join('\n') + '\n', 'utf8') - 读取本地文件verify-prune.mjs:78
const after5 = (await readFile(storePath, 'utf8')).trim().split('\n').map(JSON.parse) - 写入本地文件verify-prune.mjs:85
await writeFile(storePath, '', 'utf8') - 写入本地文件verify-prune.mjs:90
await writeFile(storePath, JSON.stringify({ filePath: 'x.txt', command: 'edit', before: 'z', time: now - 6 * D }) + '\n', 'utf8') - 删除本地文件递归verify-prune.mjs:98
await rm(tempHome, { recursive: true, force: true }) - 读取本地文件verify-rewind.mjs:90
const text = await readFile(path, 'utf8') - 写入本地文件verify-rewind.mjs:95
await writeFile(path, out.join('\n') + '\n') - 删除本地文件递归verify-visual.mjs:892
await rm(tempHome, { recursive: true, force: true })
扫描于 2026/11/1(0 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库
静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。
依赖的 DSH / Cordis 模块
@deepseek-ai/cordis@deepseek-ai/dsh-commands@deepseek-ai/dsh-fs@deepseek-ai/dsh-sandbox-policy@deepseek-ai/dsh-session@deepseek-ai/dsh-tools给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
DSH 插件:撤销文件写入/编辑操作(操作前快照,用 /undo 恢复)
README
由 DeepSeek 最新模型翻译生成dsh-file-undo
DSH 插件:撤销智能体对文件的 write / edit 操作。每次文件改动落盘前自动进时间线,侧栏面板红绿 diff 逐条审查,两阶段撤销 + 重做双向循环,多会话自动跟随。当前版本 0.2.0。
可视化面板
侧栏底部「撤销」图标(带未撤销计数徽章)→ 审查面板:
- 历史列表:时间、工具徽章(write / edit / create)、文件名、+恢复 / −移除 行数、轮次徽章(该操作发生在模型的第几轮);失败的调用同样留痕
- 撤销视角红绿 diff:绿行 = 撤销将恢复的内容,红行 = 撤销将移除的当前内容,hunk 头 + 双行号,超长截断(800 行后「显示全部」)
- 自动预检:打开条目即执行,能否安全撤销逐条给原因(见「安全模型」)
- 两阶段确认:第一次点击武装按钮(3 秒自动解除),再点才真正执行
- 会话切换器:列出同一项目树(当前目录及其父子目录)下的全部会话(平台标题 + 活跃/存档状态),选中即钉住;默认自动跟随当前查看的会话
安全模型(两阶段 + 事务 + 乐观并发)
| 阶段 | 行为 |
展开完整文档(README 剩余 1 节)
dsh-file-undo DSH 插件:撤销智能体对文件的 write / edit 操作。每次文件改动落盘前自动进时间线,侧栏面板红绿 diff 逐条审查,两阶段撤销 + 重做双向循环,多会话自动跟随。当前版本 0.2.0。 可视化面板 侧栏底部「撤销」图标(带未撤销计数徽章)→ 审查面板: - 历史列表:时间、工具徽章(write / edit / create)、文件名、+恢复 / −移除 行数、轮次徽章(该操作发生在模型的第几轮);失败的调用同样留痕 - 撤销视角红绿 diff:绿行 = 撤销将恢复的内容,红行 = 撤销将移除的当前内容,hunk 头 + 双行号,超长截断(800 行后「显示全部」) - 自动预检:打开条目即执行,能否安全撤销逐条给原因(见「安全模型」) - 两阶段确认:第一次点击武装按钮(3 秒自动解除),再点才真正执行 - 会话切换器:列出同一项目树(当前目录及其父子目录)下的全部会话(平台标题 + 活跃/存档状态),选中即钉住;默认自动跟随当前查看的会话 安全模型(两阶段 + 事务 + 乐观并发) | 阶段 | 行为 | | 预检 preview | 只读分类,逐条给原因:file_creation(创建类撤销 = 删除文件,内容/hash 双闸 + 确认)/ external_modified(快照后文件被手动改过,拒绝吞掉手工编辑)/ superseded_by_later_ops(其后还有 N 条自己的编辑,引导「回退到此状态」)/ already_reverted / file_missing / stale 等 | | 提交 apply | 携带预检时的 expectedCurrentHash 乐观锁——文件在预览后又变化则拒绝(stale);通过后整文件写回 before 全文,成功标记 reverted | | 回退 RewindTo | 选中任意历史条目单次原子写回,链条咬合校验 + 明示将丢弃的后续步数,其后条目批量标记 cascade | | 级联恢复 | 重新应用上游条目时整批复活被连带作废的行(确认框明示「连带恢复 N 条」),撤销/重做双向幂等 | 聊天里的 /undo 不做 external_modified 拦截(保留强制语义)。 会话跟随 面板自动跟随当前查看的会话:client 半订阅 betterSidebar 服务(主源)与 sessions.list(兜底),会话切换后面板所有请求携带该会话标识;host 侧逐请求过平台会话目录核验,cwd 一律从平台读取——伪造的会话 id 一律 404 unknown-session。两源全缺席(headless 等)时静默降级为旧有行为。 命令 | 命令 | 作用 | | /undo | 撤销最近一次文件写入/编辑 | | /undo list | 查看当前会话已记录的操作历史 | | /undo <n> | 撤销指定索引的操作 | | /undo sessions | 列出同项目树会话(与面板切换器同一条 API) | | /undo prune [days] | 清理 N 天前的快照(默认 7),只缩回退深度,不动当前文件 | | /undo git [n] | 从 git 归档恢复第 n 个快照(0 = 最新) | | /undo git-status | git 归档健康状态 | 原理 1. 全文快照,不存反向补丁:tools/pre-execute 在 write/edit 落盘前捕获 before 全文;tools/post-execute 回填 after 全文 + sha256(外部修改检测的期望哈希)。diff 渲染现场计算,撤销整文件写回,两者解耦。 2. diff 计算与渲染分离:host 侧行级 LCS(前缀/后缀裁剪 + 整数行 ID + Uint32 DP,超大中段降级为整块替换)产出 hunk;客户端按行首字符分类 + 纯 CSS 上色(与主题令牌混色)。 3. 引擎裁决、UI 只展示:能否动文件全部在 host 侧判定(预检分类 + 事务 + 乐观锁),客户端只渲染分类结果。 4. 状态机闭环:recorded → reverted → reapplied,重做后可再撤销。 5. 相对路径按会话解析:工具的 file_path 相对会话工作区,捕获时用会话 cwd 作为解析基准并存稳定标识复用。 6. HTTP API:POST /file-undo/api/*(context / history / detail / preview / apply / reapply / rewind 等),带 Host 头信任围栏(loopback / trustedHosts + sec-fetch-site + Origin 校验)。 快照存储 ~/.dsh/file-undo/ <workspace-key>/ # 由会话 cwd 规范化派生(消毒 + 8 位哈希后缀防撞) <chat-key>/ # 会话 id snapshots.jsonl # append-only,单飞写锁 + 原子改名 git-archive/ # 第二层归档:每条快照一个 commit,静默降级 - 同一目录无论 D:\proj / d:/proj/ 哪种写法,恒为同一个键(路径规范化后派生,存量目录零迁移) - 插件加载时按 7 天惰性清理;/undo prune 手动清理,time 缺失的条目保守保留 安装 方式一:本地构建(link) 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 生效。之后的纯 client 半改动只需浏览器硬刷新。 方式二:npm(发布后) dsh plugin --profile web add dsh-file-undo 限制 - 只覆盖 write / edit 两个工具;在 shell 里直接改文件(如 Set-Content、重定向)不会进入时间线 - 「回退到此状态」会丢弃其后的改动(预检会明示数量),不做旧基线重放 - 面板与自动跟随需要 web 界面;headless 下聊天命令仍可用(会话缝缺席时自动降级) 开发 pnpm typecheck pnpm build node verify-prune.mjs # 清理逻辑隔离验证(临时 HOME,不碰真实快照) node verify-rewind.mjs # 回退语义回归 node smoke-test.mjs # 冒烟测试 node verify-follow.mjs # 会话跟随数据源回归 License MIT
预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
QinLuza/dsh-file-undo
回滚智能体对文件的写入与编辑,支持审查和重做
相关插件 · 开发 / 工程
隐私简历生成器reactive-resume/reactive-resume
Reactive Resume 已迁至 GitHub 上的…
★ 45,125已汉化MCP
71.7
AI 架构文档生成器sopaco/deepwiki-rs
🚀 Litho 已进化为 Terrain —— 给你的 AI 智能体一张会呼吸的代码库地图。
★ 3,157已汉化DSH
70.8
长时任务执行闭环AMAP-ML/LongHorizon-Harness
面向 Computer-Use Agent 的 Loop Engineering
★ 1,764已汉化DSH
70.5
AI 编程纪律方法包GanyuanRan/Aegis
让 AI 编程 agent 变得可信:少返工、更安全、说“完成”前先给证据。
★ 1,384已汉化DSH
69.6
多代理编排运行时Enderfga/claw-orchestrator
面向编程智能体的运行时。把 Claude Code、Codex、Antigravity、Grok…
★ 586已汉化MCP
69.4
记忆进化插件csyangwen/dsh-memory-evolve
dsh-memory-evolve 使用场景指南
★ 461已汉化DSH
67.9