dsh-safety-remove · 可还原删除
A DeepSeek Harness plugin that turns every deletion into a recoverable move. 把「删除」换成「移进暂存夹」——每一次误删都能还原。
为什么做这个
DSH 里模型执行 shell 命令删文件时,删掉就是删掉了。通配符写宽一点、脚本里带一句 rm -rf、手滑点错——后果都不可逆。
更麻烦的是它往往发生在你看不见的地方:模型为了「清理中间产物」顺手删掉的东西,等你发现时已经找不回来。
这个插件的做法很简单:不拦着删,但删的东西先挪进一个能找回的地方。
它做什么
| 场景 | 行为 |
|---|---|
Agent 跑一条「只删明确路径」的 shell 命令(Remove-Item / rm / del / rmdir) |
目标先被搬进本 Session 的暂存夹,原命令被拒绝执行,并告知 Agent「按已完成报告、不要重试」 |
删除混在别的操作里、纯通配符、git clean、脚本内的删除 |
拒绝而不是猜,要求改写成一条只列绝对路径的命令 |
| Agent 首次覆写本 Session 中已存在的文件 | 复制一份进暂存夹(原件留在原位),同一文件再改不再复制 |
| 磁盘放不下这份可还原副本 | 拒绝并报出「需要多少 MB / 剩余多少 MB」,要求先问用户 |
| Agent 想主动删东西 | 调 safety_remove 工具,一行一个路径 |
| Agent 发现文件莫名不见了 | 调 safety_list 查是否被暂存 |
三种暂存位置
每种都是「根目录 + 一个可见的会话文件夹」,所以同一个 Session 的条目永远不会散落在无关文件之间。
跟随预设根目录(默认)
D:\dsh-safety-trash\
default-workspace-trash-root\ ← 按工作区分组
w1-21f83772-trash\ ← 一个 Session 一个夹
w1-a3c91b04-trash\
跟随工作区
D:\my-project\
.dsh-trash\
21f83772-trash\
d4e8a013-trash\
自订资料夹
<你选的目录>\
21f83772-trash\
w1、w2 是第一次见到该工作区时分配并持久化的编号,之后不变。
右侧栏面板
右栏新增「删除快取」分页:
- 两种视图:
清单(一条一个暂存项,显示原名/原路径/原因/大小/时间/状态)与资料夹(从真实的files\开始,像资源管理器一样逐层展开) - 三种预览:行内缩略图 / 点行展开内嵌预览 / 点
↗用 DSH 自带的预览器在右栏另开一个分页(文本、图片、HTML、GLB 都支持) - 多选:勾选框、Shift 连选、全选 / 全不选
- 三态勾选:勾父目录会连带其已展开的子项;子项里任何一个取消,父目录自动取消
- 三个动作:还原(回原路径,撞名自动加
(restored-1))/移至回收站/永久删除(按钮变「再按一次确认」后第二下才执行) - 快捷键:
Delete移回收站、Shift+Delete永久删除 - 以档案总管开启:直接打开该 Session 的暂存夹
手动放进暂存夹的文件也会被扫到,标为「手动放入」——能预览、能删除,但没有原路径所以不能还原。
设置层级
Session 设定 > 工作区设定 > 插件预设 > cordis.patch.yml
逐字段继承:Session 只改了资料夹时,「放行删除」仍沿用工作区/插件的值。
| 入口 | 位置 |
|---|---|
| Session 设定 | 对话页上方「⚙ 删除快取设定」按钮 |
| 工作区预设 | 同一浮层里的「另存为本工作区预设」,或设置页「工作区预设」分页 |
| 插件预设 | 设置 → 删除快取 → 插件预设 |
放行(bypass):打开后该 Session 完全等同没有这个插件——不拦截、也不备份。
安装
本插件没有任何第三方运行时依赖(纯 ESM,只用 node: 内置模块)。
# 方式一:从 GitHub 直接安装
dsh plugin add github:CatBox215/dsh-safety-remove
# 方式二:本地目录
dsh plugin add /path/to/dsh-safety-remove
# 方式三:从发布包
dsh plugin add ./dsh-safety-remove-0.2.0.tgz
也可以打开 DSH → 插件 → 添加插件 → 选择这个目录。
装好后重启一次 DSH。右栏的「+」或引导页里会出现「删除快取」胶囊。
给模型的两条通道
- 自动运行:拦截、搬运、备份都在 Host 的工具管道里完成,不依赖模型配合。
- 系统提示:注册了一段
systemPrompt.section,每次请求都在场——告诉模型「被拒绝 = 已完成、不要重试」「找不到文件先查safety_list」。 - 工具:
safety_remove(主动安全删除)、safety_list(只读查询暂存区)。
刻意没有做成 skill:skill 是按任务匹配才加载的,而这是要始终在场的约束。
一次真实的失败
这张流程图的第一版是这样的:
它错在哪:DSH 的插件详情图规范里有一条「避免 <text> 元素」(担心不同机器缺字体),而我把「避免」执行成了「禁止」,结果图画得干干净净、却一个字都没有——看得懂形状,看不懂意思。
怎么发现的:交付前先用 @resvg/resvg-js 把它栅格化成 PNG 看了一眼,才发现这个问题。改回带文字之后又渲染了一次,抓出另外三处:
- 绿色连线穿过了「复制一份后放行」那张卡片
- 「判斷得出/判斷不出」标签塞不进 50px 间隙,互相压住
- 底部三个色条是硬编码坐标,和文字对不齐
三处都是只有渲染成像素才看得见的问题。完整复盘见 docs/FAILED-CASE.md。
教训:规范里的「避免」通常有它的理由,但照字面执行到极端会产出没有实用价值的东西。取舍要写进文档,而不是默默做掉。
已知边界
这些是设计边界,不是 bug:
- 守卫读命令文本,不读意图。
node -e、npm run clean、.ps1里的删除不拦也不搬——那种情况文件是真没了,safety_list里也查不到。 - 是拒绝,不是改写。DSH 不允许插件改写工具调用参数,所以被拦的删除结算为一条解释「已搬运」的
Error:,而不是伪装成成功命令。 - 一个 Session 一个文件夹,面板只看得到本 Session 的。
- 回收站是真回收站:进系统回收筒的条目用的是暂存夹里的名字,要保原位置请用「还原」。
- 不自动过期:不会自己清理,会一直长到用户清空。
- Windows 优先:回收站用 Visual Basic 文件 API 实现,其他平台退化为直接删除。
出问题怎么查
完整排查手册:docs/FLOW.md(决策流程图、规则速查表、文件位置、症状→原因→处理对照表、给 AI 的取证步骤)。
最常用的三条:
- 文件不见了 → 调
safety_list(有记录 = 被暂存,可还原;没记录 = 真删了) - 按钮点了没反应 → 大概率是 Host 未重启、新端点 404
- 右栏空白 → 看浮层里「目前生效」那行解析出的根目录对不对
开发
# 31 项自测,只在自己建的临时目录里操作
node tools/selftest.mjs
# 把流程图渲染成 PNG 用眼睛检查(依赖 @resvg/resvg-js)
NODE_PATH=<profile>/node_modules node tools/render-svg.cjs docs/flow.svg out.png
改图之后一定要先渲染再看——连线穿框、标签重叠这类问题读 SVG 源码是看不出来的。
目录
lib/index.js Host 入口:删除闸门、两个工具、系统提示、HTTP 挂载
lib/trash.mjs 搬运/复制备份/列表/还原/清理/目录扫描/元数据/磁盘检测
lib/guard.mjs 命令删除识别器(宁愿拒绝也不猜)
lib/state.mjs 三级设置持久化与路径解析、工作区编号分配
lib/http.mjs /api/safety-remove 全部端点
lib/client.js 右侧栏分页、会话浮层、设置页
tools/selftest.mjs 自测(31 项)
tools/render-svg.cjs SVG 栅格化自查工具
docs/DETAIL*.md 插件页介绍(en / zh-TW / zh-CN)
docs/FLOW.md 运作流程与排查手册
docs/FAILED-CASE.md 失败案例复盘
docs/flow.svg 特性图(现行)
docs/flow-v1-failed.svg 特性图(第一版,失败案例)
locale/*.json 插件页显示元数据
icon.svg 原创图标
许可
MIT
No comments yet. Be the first to write one.