dsh-file-history
Manifest validDeepSeek Harness plugin: back up a file before an agent's write/edit overwrites it. Per-project .dsh-backup, file_history restore tool, top-bar status panel, per-project switch.
dsh-file-history
A DeepSeek Harness plugin that backs up a file before an agent's write/edit overwrites it.
Per-project .dsh-backup/ (filename + timestamp, last N generations), a file_history tool to list / diff /
restore single files or a whole turn, a top-bar panel that shows whether it is alive, and a per-project switch.
If a backup cannot be made, the write is refused (fail-closed). 中文说明见下。
DeepSeek Harness 的「改前自动备份」插件:让 agent 改文件这件事从"靠自觉备份"变成"harness 兜底"。
- 任何
write/edit覆盖已存在文件之前,自动把原文逐字节备份进项目内的.dsh-backup/; - 备份文件名 = 源文件名 + 时间戳 + 原扩展名,按源文件路径镜像存放;
- 每个源文件只保留最近 5 轮(
maxGenerations,可就地调整),另加天数与容量两层兜底; - 备份失败就拦下这次写入(fail-closed),不会出现"没备份却照样改坏";
- 模型侧有
file_history工具(list/show/restore/revert_turn),系统提示里也常驻几行说明, agent 知道备份系统的存在、位置与恢复方式; - 运行状况标志:
.dsh-backup/_dsh-file-history/status.json(时间戳每次备份/还原刷新)。
为什么需要它
harness 原生只有 dsh-atomic-write(写入原子)和 dsh-fs-observation-policy(先读后写)——
原子不等于可撤销。文件被覆盖或删掉之后,唯一的补救是让模型重读上下文再写一遍:
既烧 token,又可能二次改错。本插件把原文留在工具层,回滚变成一次工具调用(或一次文件复制)。
备份长什么样
<项目根>/.dsh-backup/
├── src/lib/nested.20260917-095500.ts # 子目录按源路径镜像
├── src/lib/nested.20260917-095500.ts.json # 该备份的元数据
├── demo.20260917-095500.txt
├── demo.20260917-095500.txt.json
└── _dsh-file-history/
├── status.json # ★ 运行状况标志
└── manifest.jsonl # 快照/还原/回退审计流水
~/.dsh/file-history/<工作区>/<文件签名>/<时间戳>/content # 仅“项目外文件”的兜底目录
恢复方式三选一:file_history(action="restore", path=...)、file_history(action="revert_turn")(整轮),
或直接把人能看懂的备份文件复制回原位。
目录
| 文件 | 作用 |
|---|---|
lib/index.js | 宿主半边:tools/pre-execute 备份 + file_history 工具 + 系统提示公告 + 保留策略 + 客户端 RPC 路由 |
lib/client.js | 客户端半边:右上角「备份」状态芯片 + 悬停面板(实时流水 / 统计 / 总开关) |
package.json | 插件清单(dsh.client 段声明浏览器半边,依赖 @deepseek-ai/cordis、@deepseek-ai/schemastery) |
install.ps1 | 安装/卸载到指定 dsh profile(写 node_modules + 登记 cordis.patch.yml,自动备份原文件) |
selftest.mjs | 离线自测:假 ctx 驱动已安装副本,60 项断言(宿主加载路径、schema 契约、客户端 RPC、项目级开关) |
host-contract-check.mjs | 真依赖契约检查:在 profile 内加载真实 schemastery/timer 语义,9 项断言 |
monitor.ps1 | 命令行观测面板:一次看清 / -Watch 实时盯梢 / -Scan 扫全机 |
after-restart-check.ps1 | 重启后体检:插件是否已加载、备份目录是否已创建 |
verify.ps1 | 端到端验证:起一个独立 headless dsh,让真实 agent 改坏再还原 |
e2e/patch.yml | 端到端验证用的临时 patch |
可视化观测:右上角状态芯片
装上客户端半边后,会话右上角(「时间戳开 / 记忆库开」那排旁边)会多一个备份芯片:
- 灯色:🟢 最近 2 分钟内有备份 · 🔵 已启用但暂时没动作 · 🔴 有错误(含最近一次失败原因)· ⚪ 读不到状态。
- 芯片文字:当前占用(现存备份的总大小,会随 5 代清理降下来),不是历史累计次数;悬停提示里另给「份数 / 累计次数」。
- 点击展开面板:最后活动时间 / 备份次数 / 保留代数 / 占用 · 统计 / 最近一次备份与还原的完整路径 / 实时流水(每 2 秒刷新,格式
时间 · 动作 · 文件名,鼠标悬停看备份文件路径)/ 一键开关。 - 面板关着时每 30 秒轮询一次(只更新灯色,开销可忽略);开着时每 2 秒。
- 数据来源与命令行面板完全一致:
status.json+manifest.jsonl,通过宿主路由POST /dsh-file-history/api读取。 - 同一个开关也出现在「设置 → 通用」里(与记忆库的入口一致)。
宿主接口契约(踩过坑,务必按这个写)
这两条是 2026-09-17 两次真实加载失败的根因,已写进 host-contract-check.mjs 做回归:
ctx.settings.register(ns, schema, options)的schema必须是 schemastery Schema,不能是工厂函数。 宿主dsh-settings的resolve()是schema(mergeLayers(base, section))—— 把合并后的配置对象 当参数调用 schema。写成(s) => s.object({...})时s就是那个普通配置对象, 于是s.boolean()不存在 →TypeError: s.boolean is not a function(插件一加载就炸)。 正确写法(与dsh-memory-db一致):import s from '@deepseek-ai/schemastery'+s.object({...})。- 用到
ctx.setTimeout/ctx.timeout就必须inject: ['tools','settings','timer']。 这些方法由cordis-plugin-timer通过ctx.mixin混入,没 inject 会报cannot get property "timer" without inject;另外ctx.setTimeout已 deprecated,统一用ctx.timeout。
安装
方式 A:克隆仓库 + 安装脚本(最直观)
git clone https://github.com/cuteG41cute/dsh-file-history.git
cd dsh-file-history
powershell -ExecutionPolicy Bypass -File install.ps1 -Profile web -Force
# 然后重启 dsh 服务 / 桌面版窗口
方式 B:pnpm 直接装(不落源码)
dsh plugin --profile web add github:cuteG41cute/dsh-file-history
# 然后在 profile 的 cordis.patch.yml 里加:
# - insert:
# - id: dsh-file-history
# name: 'dsh-file-history'
# inject: [tools, settings, timer, agents]
# 最后重启 dsh 服务
也可以 npm 方式:
npm publish之后dsh plugin --profile web add dsh-file-history。
重启是必须的:宿主侧 @deepseek-ai/cordis-plugin-hmr 在 dsh-base 里 disabled: true,
改 cordis.patch.yml 不会热加载新插件(客户端插件 HMR 是另一回事);客户端面板还多一步刷新页面。
卸载:powershell -ExecutionPolicy Bypass -File install.ps1 -Remove(已产生的备份不会被删除)。
自测与验证
# 1) 装依赖(只有跑测试需要)
npm install
# 2) 离线自测,秒级,60 项断言(假 ctx)
node selftest.mjs
# 3) 真依赖契约检查(仓库内或已安装目录都能跑;找不到 schemastery 会回退到 DSH profile 里那份)
node host-contract-check.mjs
# 4) 重启后的体检 + 端到端(起独立实例,不动正在运行的 3080 服务)
powershell -ExecutionPolicy Bypass -File after-restart-check.ps1
powershell -ExecutionPolicy Bypass -File verify.ps1
注意:本机 PowerShell 7(pwsh)不在 PATH 上,脚本一律用系统自带的
powershell.exe(Windows PowerShell 5.1)执行。
所有 .ps1 必须带 UTF-8 BOM:Windows PowerShell 5.1 对无 BOM 的脚本按 ANSI(GBK) 解码,
脚本里的中文注释/字符串会变成乱码并直接把语法搞坏(Unexpected token)。
用编辑器保存时选「UTF-8 with BOM」,或命令行补一次:
$f = 'monitor.ps1'
$text = [System.IO.File]::ReadAllText($f, [System.Text.Encoding]::UTF8)
[System.IO.File]::WriteAllText($f, $text, [System.Text.UTF8Encoding]::new($true))
另外:用 Start-Process 传带空格的路径会被参数拆分吃掉一段,
所以 monitor.ps1 支持用环境变量 FH_PROJECT 指定项目目录。
配置(~/.dsh/settings.yaml → file-history 段,改动热生效)
开关是项目级的:enabled 是全局默认,projects 是逐项目覆盖(键=项目目录绝对路径,小写规范化)。
生效值 = 项目覆盖 > 全局默认。右上角面板和「设置 → 通用」里都能直接切当前项目,并能在两种状态间来回。
file-history:
enabled: true # 全局默认开关(没有单独设置过的项目用它)
projects: # 逐项目覆盖:{ "<项目目录>": true | false }
c:\work\my-proj: false
maxFileBytes: 2097152 # 超过只记指纹(默认 2MB)
maxGenerations: 5 # 每个源文件保留最近几代(默认 5 轮)
maxAgeDays: 30 # 备份保留天数
maxProjectBytes: 536870912 # 单个项目备份区容量上限(默认 512MB)
announceInPrompt: true # 把备份系统写进系统提示
gitignoreBackups: true # 自动把 .dsh-backup/ 加进项目 .gitignore
面板里的三个操作(对应 RPC set-enabled / reset-project):
| 操作 | 效果 |
|---|---|
| 关闭/开启本项目备份 | 写 projects[<当前项目>],只影响这个项目 |
| 改为跟随全局默认 | unset projects[<当前项目>],回到跟随 enabled |
| 全局默认:开/关 → 切换 | 写 enabled,只影响没有单独设置过的项目 |
关键实现决策
- 不改写工具参数:
tools/pre-execute无法改写exec.arguments,所以不做"挡下写入再由插件代写"—— 那会丢掉 harness 原生的 read-before-write 校验与展示层 diff 卡片。这里只做旁路备份 + 校验,原工具照常执行。 - fail-closed:备份读失败/并发修改 →
deny本次写入,并把原因回给模型。 - 同轮分组用
rootCallId:一次助手消息里的多次write/edit共享rootCallId,revert_turn按它成组回退; 同一文件多代时回退到本轮最早那代(即本轮开始时的样子)。 - 备份逐字节保存:不做行尾/编码转换,
restore是原样写回。 - 代数上限就地执行:每次备份后立刻把该源文件收敛到最近 N 代(不是只靠小时级 sweep),短时间连改也不会堆积。
- 自身免疫 + git 卫生:
.dsh-backup/内的文件不再被备份;从工作区向上找最近的 git 根 (工作区常是仓库的子目录),把带相对路径的条目追加进那个.gitignore。 - 项目外文件不污染无关目录:落到用户主目录的兜底区,由同一个
file_history工具统一索引。 - 按宿主真实契约写:见上文「宿主接口契约」——schema 用
s.object(...)而不是工厂函数, 定时器用ctx.timeout且声明inject: ['tools','settings','timer']。离线自测只覆盖调用约定, 真依赖语义由host-contract-check.mjs在 profile 内保证。
技能与排查
同机已安装配套技能:~/.dsh/skills/dsh-file-history/SKILL.md(含完整行为规则、排障表、file_history 用法)。
Comments
Loading…
Similar plugins
by zoyluoblue
dsh plugin: snapshot your workspace every turn and restore the files to any earlier point. A rewind/undo for DeepSeek Harness coding sessions — your own git repo is never touched.
★ 0
MIT
TypeScript
Aug 19, 2026
dsh plugin --profile web add @zoytown/dsh-rewindby Yazzyk
DeepSeek Harness (dsh) 插件:在 Web GUI 里手动点选文件或目录,屏蔽 agent 对它们的读取、搜索、写入与编辑。A dsh plugin that denies an agent read/search/write/edit access to chosen files and directories, picked from the plugin's own We
★ 2
MIT
JavaScript
Oct 10, 2026
dsh plugin --profile web add dsh-file-shieldby jackylong1987
DeepSeek Harness (DSH) web plugin: upload manager, workspace output files with preview/edit/maximize, skill docs browser, account balance, and turn-ruler history navigation.
★ 0
MIT
JavaScript
Sep 25, 2026
dsh plugin --profile web add dsh-long-pluginsby caishengshuo
Every file, one version before it breaks. A DSH plugin: snapshots a file the instant before an agent rewrites it, with a per-session GUI timeline where any point can be restored in one click.
★ 0
MIT
JavaScript
Oct 24, 2026
dsh plugin --profile web add dsh-beforewriteby ZSeven-W
DeepSeek Harness (DSH) plugin: a read-only ledger for the plugins you already have installed — a capability inventory with file:line evidence, declared-vs-detected reconciliation, cross-profile versio
★ 25
↓ 101/wk
MIT
JavaScript
Nov 1, 2026
dsh plugin --profile web add @zseven-w/dsh-harborby xusuyang030218
DSH (DeepSeek Harness) file preview & editor plugin: in-browser workspace file tree, multi-format preview, online editing, version history, search. DSH 文件预览插件。
★ 0
MIT
JavaScript
Aug 24, 2026
dsh plugin --profile web add dsh-preview-ui