dsh-turn-scratch
一款专门用于清理 dsh 每轮产生的多余临时文件的插件。
介绍
是不是还在因为dsh忙完工作后,工作区里到处都是它产生的临时文件而苦恼?
您是否经常感觉到自己不知道这些文件的作用?删除时又怕这些文件会影响你的电脑?
或者是自己还要在每次对话时做出删除临时文件这种重复性动作?
不用担心,dsh-turn-scratch 这款插件能帮你的忙。
安装
dsh plugin --profile desktop add "git+https://github.com/popujiang/dsh-turn-scratch.git#v0.4.0"
“小而美”
插件体积很小,让你放心。
当然,真的是小而美:
很多操作无感完成,默认无需配置,开箱直用!
为什么?因为它是全自动的。
不过想调的话,配置项都在——在 profile 的 cordis.patch.yml 里给 turn-scratch 行加 config: 即可。
但是你怕dsh误删?
早就想好了这一点!
特色
它并不是直接删文件
每轮结束,临时文件全都会先被挪进一个隔离文件夹(不是你电脑里的回收站),再逐个判断:
~/.dsh/turn-scratch/<工作区名-指纹>/<会话>/turn-<轮次>/
它不在你的工作区里。
一个专门保持工作区干净的插件,自己的东西当然也不该堆在工作区 —— 所以隔离区放在用户目录,工作区从头到尾一个多余文件夹都没有。
能确定的,代码直接决定去留。
拿不准的,才交给一次独立的模型调用判断。
真正的删除,只在你手动清空时发生。
在你还什么都没察觉的时候,分类已经发生,连工作区的界面也跟着清爽了起来。
所以最坏的情况其实是文件被隔离开来,而不是被误删
工作和隔离区不在同一块盘,会怎样?
那"挪"就变成了"真的拷一份字节"(Windows 上跨盘改名会直接失败)。
顺序是:复制 → 校验文件数与字节数 → 落库 → 最后才删原件。任何一步崩掉,原件都还在,最坏只是多留一份重复 —— 不会两头落空。
代价是大目录会慢一点。想避开,把 storeRoot 指到和工作区同一块盘就行:
storeRoot: "D:/.dsh-turn-scratch" # 同盘,搬运恢复成瞬时的改名
干活干到一半,工作区就已经乱了?
上面说的,都是回合结束时的收尾。
可一个长回合干到一半,中间产物其实早就散在工作区里了 —— 等它结束才动手,中间那段时间你还是得对着一堆不认识的文件发呆。
所以插件给 dsh 备了一块暂存区:
~/.dsh/turn-scratch/<工作区名-指纹>/work/
中间产物直接写这儿,工作区从第一秒就是干净的。
那这些文件就不管了?
当然不是。回合结束时,它们会被搬进本轮的隔离文件夹 —— 照样有记录、照样能复核、照样随时能还原。然后暂存区清空,下一轮又是一个干净的工作台。
路径固定不变,不用每轮重新找。
这不是"只扫暂存区"
暂存区是多出来的一路来源,不是替换。
原来的全工作区扫描一条没动:标记过的、本轮新建的、临时命名的目录、孤儿库 —— 照扫不误。哪怕 dsh 一次都没往暂存区写,散在工作区里的东西照样会在回合结束时被收走。
暂存区只是让它从一开始就不用弄脏工作区。
本轮新建的东西,不分工具
暂存区靠的是 dsh 主动往里写。可现实里它经常不写 —— 它直接用 shell 干活。
于是就有最常见的那个场景:作业做完了,交付的 docx 在,但旁边还躺着一堆 build_docx.py、tpl_preview/、pipwork/、read_docx.py…… 这些全是本轮 shell 现场生成的,以前插件对它们完全失明:它只认 write/edit 两个工具产生的文件,shell 写的一概看不见。
现在多了一条路:按创建时间判定,不看是哪个工具建的。
回合结束时扫一遍工作区,凡是「创建于本轮」的文件都收走(sweepNewFiles,默认开),然后把这些文件腾空了的目录一并删掉 —— 文件走了、空壳还在,工作区仍然不算干净。
四个豁免,保证不碰你真正要的东西:
- 交付物扩展名(
createdKeepExtensions):.docx .doc .pdf .pptx .ppt .xlsx .xls .odt .odp .ods .epub .zip .7z .rar .tar .gz .mp4 .mov .mp3 .wav; - 你本轮原话里点过名的路径或文件名;
.git/、node_modules/、__pycache__/、.venv/等保护目录;- 早就存在、只是被改过的文件 —— 只看创建时间,改旧文件永远不会被收。
还有一条比豁免更要紧的前置条件:复核得真的会跑。收走从来不是终点 —— 清单会交给复核模型看一遍,谁其实是你要的东西就摆回来;这类条目模型没表态时默认方向是还原,不是留着。所以只要复核落不了地(aiReview 关着,或者 aiReview.provider/model 没配、settings 里也没有默认模型),这条路整条不启用:本轮新建的文件原地不动,日志里说明原因。想让它在无复核时硬跑,写 sweepNewFiles: "force"(后果自负:没人往回捞)。
sweepNewFiles: false # 关掉「本轮新建全收」,回到只看工具与临时命名
createdKeepExtensions: [".docx", ".pdf"] # 想多保几种类型就自己加
createdSkipDirs: ["node_modules", ".git"] # 扫描永不进入的目录名
扫描本身有上限(createdMaxEntries,默认两万条;深 6 层),大工作区也不会把回合收尾拖死。
收走的文件,直接进系统回收站(recycleRemoved,默认开着)
隔离库只有 agent 看得见。为了让你也看得见,每轮收走的文件会按原位置交给系统回收站:打开资源管理器、右键「还原」,就回到它原来的地方。回收站接手之后,隔离库里那份随即删掉 —— 反正不是永久删除,没必要在两个地方各留一份:既省盘,也不会让人分不清该去哪儿找。
- 只对文件生效。整棵目录搬回原位再送回收站太贵,而且回收站按目录还原容易撞名,目录条目继续留在隔离库(日志和
scratch_status里仍看得到,scratch_restore也能捞)。 - 送过回收站的文件,
scratch_restore会直接告诉你「已交系统回收站,去资源管理器还原」,不再含糊地报「源缺失」。 - 送之前先跑一遍探针(
recyclePreflight,默认开着):被降权的进程里SendToRecycleBin会不报错地退化成永久删除,所以「没报错 + 文件不见了」不算数,只有「回收站里真出现了这个名字」才算。 - 后端不可用、探针没过、副本没造出来 —— 任何一步失败都只是「你没看见」,文件仍在隔离库兜底,工作区保持「文件不见了」的原样,不会多丢一个。
- 为过路副本临时造出来的空目录,送完就清掉,不留下空壳。
recycleRemoved: false # 关掉:文件只留在隔离库,回收站里看不到
recyclePreflight: false # 不跑探针(不推荐:权限不足时会退化成永久删除)
关掉会怎样?
workDir: ""
回到原来的样子:
- 暂存区不再创建,
scratch_workdir会明确回你一句"未启用" - dsh 写哪儿由它自己决定(通常就是工作区)
- 全工作区扫描照旧,回合结束该收的还是收
所以关掉的只是"提前变干净"这条路 —— 收尾那套一点没少。
让"最后一步"进系统回收站(可选)
scratch_purge 是本插件唯一不可逆的操作:真删。想给这一步再套一层系统级撤销:
purgeToRecycleBin: true
开了之后 purge 不再 rm,而是把整个桶交给操作系统回收站,用户还能自己从回收站捞回来。
后端按这个顺序找:
- 显式指定的助手程序(
trashHelper: C:\tools\genie-trash\win32-x64.exe)—— 给了它就只用它,起不来就整批拒绝,不会退到别的后端; - 系统现成的:Windows 用
powershell调Microsoft.VisualBasic.FileIO的SendToRecycleBin,macOS 用/usr/bin/trash(macOS 15+)或osascript+ Finder,Linux 用gio trash/trash-put; - 都没有(比如 Linux 上没装任何 trash 工具)就回
refused,一个桶都不动。
这条路和 WorkBuddy 的 genie-trash 是同一个做法:自带/调用一个「把东西搬进回收站」的助手进程,而不是依赖 Electron(DSH Desktop 把整个 profile 放在 ELECTRON_RUN_AS_NODE=1 的子进程里跑,那里 import("electron") 拿到的是个路径字符串,shell.trashItem 根本不在)。
探针预检(purgePreflight,默认开着)
purgeToRecycleBin: true 之后,scratch_purge 不会一上来就动你的桶:它先在系统临时区造一个探针小文件(dsh-scratch-preflight-<时间>-<随机>.tmp),用与桶完全相同的后端送走它,然后去回收站里找这个文件名:
- 找到了 → 后端在当前权限下可信,继续处理真实桶;
- 被删掉了、但回收站里没有它 → 这个后端已经退化成永久删除,整批拒绝(回
refused),一个桶都不动; - 枚举不出回收站(COM 不可用等)→ 同样按不可信处理,拒绝;
- 探针还躺在磁盘上 → 后端没真正搬运,拒绝。
为什么值得多这一跳:「调用没报错 + 文件确实没了」并不等于「东西进了回收站」——被降权的进程里 SendToRecycleBin 恰好就是这个样子,而那是永久删除。只有"回收站里出现了同名条目"才算证据。
确认过这台机器、这个权限下回收站确实可用之后,可以 purgePreflight: false 跳过这次往返(代价是每批 purge 多一次枚举回收站的开销 —— 一个探针文件加一次目录列表而已)。
三条刻意的取舍:
- 拿不到回收站后端就拒绝执行:保留全部桶,回一句
refused。绝不悄悄退回真删 —— 那等于在用户以为有系统撤销的时候永久删除。 - purge 之前先用探针确认回收站真的可用(
purgePreflight,默认开):确认不了就整批拒绝。 - 后端返回成功也要复查桶是否真的离开磁盘,没离开算失败(和跨盘搬运同一条纪律)。
- 助手进程起不来(缺可执行文件、被沙箱拦住
spawn EPERM)同样算失败:桶留下,报错里说清楚原因,绝不rm兜底。
⚠️ 别在被降权的沙箱里用这条路:低完整性令牌写不进 C:\$Recycle.Bin,SendToRecycleBin 会静默退化成永久删除(调用不报错、文件也没了,但回收站里一项都没有 —— 已实测)。插件自己的工具跑在宿主子进程里(正常完整性),所以能用;但如果你把插件塞进别的沙箱执行环境,默认开着的探针预检会替你拦下这种环境(它拒绝执行,就说明这条路在这台机器上不可信)。
注意回收站会被系统自动清空(按卷容量上限、存储感知的"超过 N 天自动删除"),所以它只配当最后一步的兜底,不能当存储根 —— 隔离库仍然是唯一真相来源。
包含的工具及其作用
| 工具 | 作用 |
|---|---|
| scratch_status | 看现在扣着什么、已还原什么 |
| scratch_restore | 手动把一个文件捞回工作区 |
| scratch_purge | 清空隔离区(真删;开了 purgeToRecycleBin 则是送进系统回收站) |
| scratch_mark | 告诉 dsh 某个文件是一次性脚手架,可以收走 |
| scratch_workdir | 拿到一个暂存区路径,中间产物写那儿,不弄脏工作区 |
安全
默认只隔离,不删除。
scratch_purge 是唯一的例外,而它是显式人工操作(要求 confirm: true)。想连这一步都不留死角,就打开 purgeToRecycleBin:真删改成送进系统回收站,拿不到回收站、或者探针预检(purgePreflight,默认开)验证不出回收站真的可用,就整批拒绝执行。
跨盘搬运时确实要移除原件,但那是在副本校验通过之后才做的,顺序保证任何时候崩溃原件都还在。
拿不准的会被还回工作区,不会误删。
模型只能还原、不能删除,最后文件的生杀大权在你手里。
三套测试共 189 项断言全绿。最坏的情况是文件被隔离,随时可还原,请放心食用。
其他
插件如果有任何问题,希望大家能多多反馈。 未来想增加UI界面方便操作,不过现在你直接与dsh对话就可以删除。
No comments yet. Be the first to write one.