dsh-disk-cleanerv1.4.0
stallenlu/dsh-disk-cleaner
dsh plugin --profile web add github:stallenlu/dsh-disk-cleaner#4dc59a2fee43fa06b6575b5ac5aba004ce8e211a
介绍
Disk Cleaner for DeepSeek Harness: scan known-safe caches, assess what cannot be cleaned, get a ranked plan
标签
概述
dsh-disk-cleaner
English | 中文
| version | 1.4.0 |
| DSH | 0.2.1-rc.5(开发与实测版本;用到 plugins.bundle.config、settings.section) |
| license | MIT |
| Node | >= 22(仅运行冒烟测试需要) |
| 平台 | Windows(清理路径按 Windows 目录布局构造) |
English
dsh-disk-cleaner is a disk-space plugin for DeepSeek Harness (DSH). It does two different jobs, and keeps them apart on purpose:
- Clean — twenty-eight known-safe locations (temp folders, package manager stores, browser caches, crash dumps, the Recycle Bin, leftover update installers, per-app caches and logs, superseded plugin versions and applied update packages). Only these are ever deleted.
- Assess — everything the plugin will not delete (installed programs, the WSL virtual disk, the Windows component store, chat-app data, your own folders). Measured and listed, never removed.
The assessment ends with a plan: the same numbers ordered by what it costs you to act, with the exact Windows command for the steps the plugin cannot perform itself.
Both the panel and the agent reach the same operations: the cleaning logic lives once in lib/catalog.js, and the HTTP route and the disk_cleaner tool are two callers of it.
Features
| Feature | Description |
|---|---|
| Scan | Measures all twenty-eight targets concurrently and shows what each occupies |
| Per-drive filter | Chip row of mounted volumes; scanning or cleaning can be limited to one drive, and rows with nothing on that drive disappear |
| Risk labels | Every target is safe or caution, with a one-line reason (in-use / rebuild / elevation / pending-update / rollback / superseded / history / duplicate / database-backup) and a permanent legend, so "caution" never means "unspecified" |
| Aged deletion | temp and windows-temp only remove files older than one day; files a running program still holds are skipped |
| Space assessment | Walks the disk read-only and groups the result into Windows / installed software / your own files, plus the ten largest app-data folders. Any row opens its folder in Explorer — click, right click, or Enter |
| Honest re-run | Re-running the assessment re-measures; a plain call still answers from the ten-minute cache |
| Plan | Nine possible steps, ordered from zero-cost to your-decision-only; the steps the plugin can run have a button, the rest have a copyable command |
| Entry placement | Sidebar icon, settings section, or both (default) — switched from the plugin's own config card or the panel's ⚙ button |
| Behaviour settings | What is preselected after a scan, and whether deleting asks first |
| Panel memory | Scan results, the selection and the assessment survive switching to another panel and back |
| Agent tool | disk_cleaner exposes the same five actions to the model, in readable prose |
| Bilingual | Follows the host locale service; zh and en dictionaries ship together |
Cleanup targets
Every path is built from this table — a caller selects an id, never a path, so no user-supplied string can reach an unlink.
| id | What it is | Kind | Risk | Why |
|---|---|---|---|---|
temp | %TEMP%, %TMP%, %LOCALAPPDATA%Temp | aged, 1 day | caution | a running program may hold some files |
windows-temp | C:WindowsTemp | aged, 1 day | caution | a service may still hold files; only older ones go |
npm-cache | %LOCALAPPDATA%npm-cache | purge | safe | pure cache |
pnpm-store | %LOCALAPPDATA%pnpmstore | purge | safe | pure cache |
pip-cache | %LOCALAPPDATA%pipCache | purge | safe | pure cache |
uv-cache | %LOCALAPPDATA%uvcache | purge | safe | pure cache |
crash-dumps | %LOCALAPPDATA%CrashDumps | purge | safe | nothing to lose |
chrome-cache | per-profile Cache, Code Cache, GPUCache, DawnCache, ShaderCache, GrShaderCache | purge | caution | rebuilt; next open is slower |
edge-cache | same set, Edge profiles | purge | caution | rebuilt; next open is slower |
code-cache | %APPDATA%Code{Cache,CachedData,CachedExtensionVSIXs} | purge | caution | rebuilt; next start is slower |
windows-update | C:WindowsSoftwareDistributionDownload | purge | caution | needs elevation |
recycle-bin | $Recycle.BinS-1-5-* on every mounted volume | purge | safe | it is the Recycle Bin |
inet-cache | %LOCALAPPDATA%MicrosoftWindowsINetCache | purge | caution | rebuilt |
app-updaters | *-updater folders under %LOCALAPPDATA% and %APPDATA% — the installers app updaters leave behind | aged, 3 days | caution | a download from the last three days may still be a pending update |
app-caches | named cache folders inside %APPDATA%: the WPS CEF cache, FTNN cef_cache, and the Cache / Code Cache / CachedData / GPUCache of Qoder, Trae, the DSH desktop app, PersonalCenter, the WeChat devtools and QQ PC Manager | purge | caution | CEF and Chromium caches rebuild on next start |
app-logs | log folders inside %APPDATA%: FTNN, WeChat, Qoder, Trae, DingTalk, PersonalCenter, Clash Verge | purge | safe | nothing reads an old log back at runtime |
app-temp | scratch folders apps keep inside their own %APPDATA% folder instead of %TEMP%: WorkDaddyclient-tmp, PersonalCenterTemp | aged, 2 days | caution | the owning app may be running and holding files |
plugin-old-versions | every version but the newest of each family in the WPS plugin pool kingsoftwpsaddonspoolwin-i386 | purge | caution | the manager loads the newest; the rest are rollback copies |
update-packages | update packages already applied: FTNN .spd patches whose build number is below the newest Program FilesFTNNapp[version], and WorkDaddyupdate installers below the installed version | purge | caution | decided by version comparison, not by age |
session-backups | modify_backup folders under %USERPROFILE%.workbuddyworkspacesessions* — a whole-file snapshot of edits per agent session | purge | caution | the session itself lives beside the snapshot and does not read it back |
agent-logs | date-named log folders under %USERPROFILE%.workbuddylogs and .workbuddy-ailogs | aged, 3 days | safe | nothing reads an old log back |
toolchain-snapshots | duplicate snapshots under %USERPROFILE%.dshdsh-opsbash-ipc: folders whose .ops-ipc.json carries the same patchedSha256 collapse to the newest | purge | caution | a folder without that metadata is never touched |
editor-server-bundles | every bundle but the newest under %USERPROFILE%vscode-remote-wslstable | purge | caution | the editor re-downloads the one it needs |
database-backups | zotero.sqlite*.bak under %USERPROFILE%Zotero | purge | caution | the live zotero.sqlite sits beside the copies |
editor-builds | complete build trees under %LOCALAPPDATA%ProgramsMicrosoft VS Code that bincode.cmd does not name, plus old_* files whose original still sits beside them | purge | caution | no launcher, a launcher naming a missing build, or a lone build means nothing is dropped |
agent-app-caches | Cache / Code Cache / GPUCache under %USERPROFILE%.workbuddyapp and .workbuddy-aiapp | purge | caution | the sibling cache folder holds spilled conversation text and is never a target |
rewind-snapshots | session-* folders under %USERPROFILE%.dshrewind-snapshots | aged, 30 days | caution | a recent session stays restorable |
rotated-logs | *.old.log and *.log.N files in the logs folders of the agent apps | aged, 3 days | safe | nothing reads a rotated file back |
aged targets skip anything modified within the retention window; purge targets remove every entry. Both keep the target directory itself and leave locked entries alone, counting them as skipped.
Space assessment areas
wsl (ext4.vhdx images), winsxs, system-files (hiberfil / pagefile / swapfile), programs (user scope), program-files (machine scope), old-versions (builds an installer kept for rollback), update-payloads (setup and installer files sitting in temp and app-data), downloads, documents, pictures, videos, desktop. An area that does not exist is dropped rather than reported as zero. The ten largest folders under %LOCALAPPDATA% and %APPDATA% (≥100 MB each) are listed separately, because "3.6 GB in Roamingkingsoft" points somewhere a Settings page does not.
Plan steps
| code | When it appears | Runnable here |
|---|---|---|
reclaim-safe | safe targets ≥200 MB | yes, one button |
reclaim-caution | caution targets ≥200 MB (elevation excluded) | yes, one button |
wsl-compact | WSL images ≥1 GB | no — wsl --shutdown + diskpart compact vdisk |
winsxs-cleanup | WinSxS ≥1 GB | no — Dism.exe /Online /Cleanup-Image /StartComponentCleanup |
hibernate-off | system-files ≥200 MB | no — powercfg.exe /hibernate off |
elevation-clean | elevation targets ≥200 MB | no — run the plugin elevated |
app-review | an app-data folder ≥1 GB, top 3, not already covered | no — uninstall, never delete the folder |
move-folder | a personal folder ≥2 GB with another drive having room | no — Properties → Location |
pressure | system drive free < 12% | no — context for the steps above |
Bytes the plugin cannot reach from an unelevated process are never counted in reclaimNow: a delete that will fail must not appear as reclaimable.
Settings
| Setting | Default | Options | Stored as |
|---|---|---|---|
| Panel entry | Everywhere | Everywhere / Sidebar only / Settings page only | dsc.placement.v2 |
| Preselect after a scan | Safe targets only | Safe targets only / Safe and caution / Nothing | dsc.pick |
| Ask before deleting | Always | Always / Never | dsc.confirm |
Settings live in the browser's localStorage and are reachable from three places that share one implementation: the plugin's own card on its page in the plugin manager, the settings.section page, and the panel's ⚙ button.
Panel state (drives, drive filter, scan rows, selection, notices, assessment) is kept in a module-scope Map, so switching panels does not throw it away. It does not survive a page reload — only the three settings do.
Agent tool
disk_cleaner, same logic as the panel:
| Argument | Type | Notes | ||||
|---|---|---|---|---|---|---|
action | overview | scan | clean | audit | reveal | required |
ids | string[] | catalog ids; required for clean, max 32 | ||||
drive | string | C: / D: — limits the work to one volume | ||||
confirm | boolean | without it, clean is a dry run | ||||
force | boolean | audit re-measures instead of answering from the 10-minute cache | ||||
area / scope + name / index | reveal names what to open; never a path |
clean without confirm: true deletes nothing and reports what it would free — that is the approval path. audit is read-only and returns the plan in prose, ending with "Report that plan to the user; never run those commands yourself."
HTTP surface
One route, POST /api/disk-cleaner/, used by the panel:
{"action":"overview"} // drives + target ids
{"action":"scan","drive":"C:"} // measured targets
{"action":"audit","force":true} // areas + largest apps + plan
{"action":"reveal","area":"downloads"} // opens it in Explorer
{"action":"clean","ids":["temp"],"confirm":true}
POST only (405 otherwise), same-origin only (403 otherwise), body ≤4096 bytes, at most 32 ids, unknown ids or actions rejected with 400. Handlers never receive a path: reveal names an assessment area (or an app folder by scope and name), the host rebuilds the path from the same table that measured it, and hands that to explorer.exe.
Safety rules
These are enforced in lib/catalog.js and must not be relaxed:
- Only paths built by the catalog are touched. No API accepts a path.
- A candidate must have at least three path segments and must not be a volume root (
tooBroad). - After
realpath, the candidate must still sit inside the environment root it was built from — a junction planted in a cache directory cannot widen the blast radius. - Symlinks and junctions met during a walk are skipped, never followed or deleted.
- Locked or denied entries are counted as skipped and the walk continues.
- The target directory itself is kept; only its contents go.
- Rules that pick "the newest of a group" fail closed: a family with only one member, a folder whose metadata file is missing or whose hash is not a string, and a
vscode-remote-wslstablethat holds a single bundle all resolve to nothing to delete. The newest member is never returned. editor-buildsdrops a build only whenbincode.cmdexists and names a different build that is also on disk;old_xgoes only whilexis still beside it.database-backupsmatches.sqlite*.bakby name only; the livezotero.sqliteis never a candidate.- The directory Windows refuses to let an unelevated process write (
SoftwareDistributionDownload) is labelledelevationand excluded from "reclaimable now".C:WindowsTempis not one of them on a default ACL: it grantsBUILTINUserscreate and delete, so only EBUSY on a live file stops a delete there, which the age limit covers.
Install
Published on npm as dsh-disk-cleaner:
npm i dsh-disk-cleaner
Then enable it in the profile — in $DSH_PROFILE_DIR/package.json:
{
"dependencies": {
"dsh-disk-cleaner": "^1.0.0"
},
"dsh": {
"profile": {
"bundles": ["dsh-disk-cleaner"]
}
}
}
Or point the plugin manager's install_bundle at a local checkout, using a link: dependency instead of a version range:
git clone https://github.com/stallenlu/dsh-disk-cleaner
The bundle's own cordis.patch.yml inserts the plugin row; no profile patch edit is needed. A link: dependency is a symlink, so client changes take effect on a page refresh, while host changes (lib/index.js) need the bundle re-enabled or DSH restarted. An installed npm copy is the opposite: bump the version to move it.
Development
node smoke.mjs # 68 assertions, zero dependencies
node --check lib/client.js # and lib/index.js, lib/catalog.js, lib/audit.js, lib/plan.js
smoke.mjs executes the real client bundle in a throwaway DOM (fake window / document / require): the slot lifecycle runs for real, so switching the entry off and on again is proven by disposers being called, not by matching source text. The host half is captured through a throwaway ctx to prove the route and the tool are registered.
Negative control (proving an assertion bites): change reclaimable.filter((row) => row.note !== 'elevation') back to reclaimable in lib/plan.js — elevation steps are never counted as reclaimable now must fail. Restore afterwards.
What the smoke test deliberately does not cover: anything that walks a real volume (scan / clean / audit take tens of seconds) and anything visual. Those were verified by hand against a live host — see below.
Verification status
| Checked | How |
|---|---|
| Client bundle executes, slots register and dispose | node smoke.mjs (real bundle in a fake DOM) |
| Settings reach the plugin page, the settings section and the panel | node smoke.mjs + cordis_inspect_query slot occupants |
| Route and tool registered | node smoke.mjs (throwaway ctx) |
overview / scan / clean / audit / reveal round-trip | Real POST /api/disk-cleaner/ against a live host |
| Re-run bypasses the cache, a plain call still uses it | audit twice: cached: false, then true |
reveal refuses a path and a traversal | name: "../..", and a request with neither area nor name → 400 |
| Real deletion keeps the target directory, honours the age limit | Temp directory seeded with old and new files |
| Dry run deletes nothing | confirm omitted, byte counts unchanged |
| Drive filter rejects unmounted and malformed volumes | C:extra, Z:, abc → 400 |
Plan excludes elevation bytes from reclaimNow | Live audit against this machine |
| "Keep only the newest of a group" fails closed | Throwaway fixture tree: a same-hash duplicate collapses to the newest, a folder with no metadata is left alone, a different hash is kept, the live database survives (9/9) |
| Old editor builds need a launcher that proves which one runs | Throwaway fixture tree: a build the launcher does not name goes, no launcher deletes nothing, a launcher naming a missing build deletes nothing, a lone build stays, old_x goes only while x exists (7/7) |
| A dry run reports what a real run frees | The 30-day rewind window reports 263 KB not 283 MB, and scan matches the dry run |
| Visual layout | Checked by hand in the running Web UI. No browser automation here, so this one is a human check, not a test. |
Implementation notes
- Client halves of static boot bundles receive only
requirein their factory —styles/host/harnessdo not exist there. Styles are installed as a<style data-plugin>element appended todocument.head, and removed on dispose. - All colours go through
--dsw-alias-*theme tokens; no literal colours in the CSS, and every class is prefixeddsc-. - A slot cannot be registered conditionally, so a setting that removes an entry is expressed by mounting and disposing the injection (
bindWhile), deferred withqueueMicrotask— the control that removes a section may itself live inside that section. plugins.bundle.configreturnsnullforview: 'summary', the same way other bundles do; the rows render only on the plugin page.apply()registers everything throughctx.effectso the host can dispose it as a unit.
Known ceilings
- Windows only. Paths are built from the Windows layout; there is no POSIX branch.
- First assessment takes 30–60 s. Named areas and the per-app folder list walk overlapping trees. A result is cached for 10 minutes and invalidated by a real deletion, so only the first caller pays; the panel's re-run button passes
forceand always pays it.ponytail:comment inlib/audit.jsrecords the shared-map alternative. - Leftover installers are cleaned by age, not by knowledge.
*-updaterfolders are recognised by name; a download from the last three days is left alone because only the app knows whether it has been applied. - No elevation. The one Windows directory that refuses an unelevated write is reported, never attempted.
- No uninstall, no move. Steps the plugin cannot perform carry a command instead of a button.
- Traversal caps at 2,000,000 entries per walk, so an absurdly large tree is measured partially rather than hanging.
Architecture
├─ package.json dsh.bundle.patch + dsh.client { platform: "web" }
├─ cordis.patch.yml - insert: id: dsh-disk-cleaner
├─ lib/index.js (411) Host: HTTP route + disk_cleaner tool, same run() for both; reveal
├─ lib/catalog.js (969) The 28 targets, measurePath / purgePath / resolveTargets / drives
├─ lib/audit.js (365) Read-only assessment, 10-minute cache, areaPaths, exposes no deletion
├─ lib/plan.js (150) Pure: measurements → ordered steps
├─ lib/client.js (1379) Client half: slots, panel, preferences, zh/en dictionaries, CSS
├─ locale/{en,zh}.json Display metadata (meta.title / meta.description)
├─ icon.svg Plugin icon
├─ README.md This file
├─ LICENSE MIT
└─ smoke.mjs 68 assertions, zero dependencies
Client-half slots: sidebar.panellist (order 50, disposed in settings-only mode), main (keyed by the package name), settings.section (order 30, disposed in sidebar-only mode), plugins.bundle.config (keyed by the package name).
中文
dsh-disk-cleaner 是 DeepSeek Harness(DSH)的磁盘空间插件。它做两件不同的事,并且刻意分开:
- 清理 —— 二十八个已知安全的位置(临时目录、包管理器存储、浏览器缓存、崩溃转储、回收站、更新安装包残留、各应用的缓存与日志、被淘汰的插件版本与已应用的更新包)。只有这些会被删除。
- 评估 —— 所有本插件不会删的东西(已装程序、WSL 虚拟磁盘、Windows 组件库、聊天软件数据、你自己的文件夹)。只测量、只列出,绝不删除。
评估的最后是一份处理方案:同一批数字按「你要付出多少代价」排序,本插件做不到的步骤附上确切的 Windows 命令。
面板和 agent 用的是同一套操作:清理逻辑只在 lib/catalog.js 里写一次,HTTP 路由和 disk_cleaner 工具都是它的调用方。
功能
| 功能 | 说明 |
|---|---|
| 扫描 | 并发测量全部二十八个目标,显示各自占用 |
| 按盘过滤 | 已挂载卷的胶囊按钮;扫描与清理都可限定单盘,该盘上为空的项会隐藏 |
| 风险标注 | 每个目标都标 安全 或 注意,并给一行理由(in-use / rebuild / elevation / pending-update / rollback / superseded / history / duplicate / database-backup),图例常驻,所以「注意」永远不是「说不清」 |
| 保留期删除 | temp 与 windows-temp 只删一天前的文件;被运行中程序占住的自动跳过 |
| 空间评估 | 只读遍历磁盘,按 Windows 自身 / 已装软件 / 你自己的文件 分组,另列占用最大的十个应用数据目录。每一行都能打开所在目录——单击、右键或回车 |
| 重新评估真的重测 | 「重新评估」会重新测量;普通调用仍然走十分钟缓存 |
| 处理方案 | 九种可能步骤,从零风险到只能你决定;本插件能执行的带按钮,其余给可复制的命令 |
| 入口位置 | 侧边栏图标、设置页分区、或两者都要(默认)——在插件自己的配置卡或面板 ⚙ 里切换 |
| 行为设置 | 扫描后默认勾选什么、删除前是否确认 |
| 面板记忆 | 扫描结果、勾选、评估结果在切走再回来时都还在 |
| Agent 工具 | disk_cleaner 把同样的五个动作以可读文本交给模型 |
| 双语 | 跟随宿主语言服务,zh / en 字典一起发布 |
清理目标
所有路径都由这张表构造——调用方只选 id,从不传路径,所以任何用户输入的字符串都到不了 unlink。
| id | 是什么 | 方式 | 风险 | 原因 |
|---|---|---|---|---|
temp | %TEMP%、%TMP%、%LOCALAPPDATA%Temp | 保留期 1 天 | 注意 | 可能有程序正在使用其中的文件 |
windows-temp | C:WindowsTemp | 保留期 1 天 | 注意 | 可能有服务正占着文件;只删一天前的 |
npm-cache | %LOCALAPPDATA%npm-cache | 全清 | 安全 | 纯缓存 |
pnpm-store | %LOCALAPPDATA%pnpmstore | 全清 | 安全 | 纯缓存 |
pip-cache | %LOCALAPPDATA%pipCache | 全清 | 安全 | 纯缓存 |
uv-cache | %LOCALAPPDATA%uvcache | 全清 | 安全 | 纯缓存 |
crash-dumps | %LOCALAPPDATA%CrashDumps | 全清 | 安全 | 没有可丢失的数据 |
chrome-cache | 各配置目录下的 Cache、Code Cache、GPUCache、DawnCache、ShaderCache、GrShaderCache | 全清 | 注意 | 会重建,下次打开更慢 |
edge-cache | 同上,Edge 的配置目录 | 全清 | 注意 | 会重建,下次打开更慢 |
code-cache | %APPDATA%Code{Cache,CachedData,CachedExtensionVSIXs} | 全清 | 注意 | 会重建,下次启动更慢 |
windows-update | C:WindowsSoftwareDistributionDownload | 全清 | 注意 | 需要管理员权限 |
recycle-bin | 各已挂载卷的 $Recycle.BinS-1-5-* | 全清 | 安全 | 就是回收站 |
inet-cache | %LOCALAPPDATA%MicrosoftWindowsINetCache | 全清 | 注意 | 会重建 |
app-updaters | %LOCALAPPDATA% 与 %APPDATA% 下的 *-updater 目录——升级程序留下的安装包 | 保留期 3 天 | 注意 | 三天内的下载可能还是待安装的更新 |
app-caches | %APPDATA% 里点名列出的缓存目录:WPS 的 CEF 缓存、FTNN 的 cef_cache,以及 Qoder、Trae、DSH 桌面端、PersonalCenter、微信开发者工具、腾讯电脑管家的 Cache / Code Cache / CachedData / GPUCache | 全清 | 注意 | CEF 与 Chromium 缓存会在下次启动时重建 |
app-logs | %APPDATA% 里的日志目录:FTNN、微信、Qoder、Trae、钉钉、PersonalCenter、Clash Verge | 全清 | 安全 | 旧日志运行时不会被读回 |
app-temp | 应用自己放在 %APPDATA% 里的临时目录(不走 %TEMP%):WorkDaddyclient-tmp、PersonalCenterTemp | 保留期 2 天 | 注意 | 应用可能正在运行并占用其中的文件 |
plugin-old-versions | WPS 插件池 kingsoftwpsaddonspoolwin-i386 里,每个插件族除最高版本以外的全部版本 | 全清 | 注意 | 管理器加载最高版本,其余是回滚副本 |
update-packages | 已应用的更新包:FTNN 的 .spd 补丁(build 号低于 Program FilesFTNNapp[版本] 中最新者),以及 WorkDaddyupdate 里低于已装版本的安装包 | 全清 | 注意 | 按版本号比较判断,不按时间 |
session-backups | %USERPROFILE%.workbuddyworkspacesessions* 下的 modify_backup 目录——每个 agent 会话的整文件修改快照 | 全清 | 注意 | 会话本体在快照旁边,不会读回这些文件 |
agent-logs | %USERPROFILE%.workbuddylogs 与 .workbuddy-ailogs 下按日期命名的日志目录 | 保留期 3 天 | 安全 | 旧日志不会被读回 |
toolchain-snapshots | %USERPROFILE%.dshdsh-opsbash-ipc 下重复的快照:.ops-ipc.json 里 patchedSha256 相同的目录只留最新一份 | 全清 | 注意 | 没有该元数据的目录永不触碰 |
editor-server-bundles | %USERPROFILE%vscode-remote-wslstable 下除最新以外的服务包 | 全清 | 注意 | 编辑器需要时会重新下载 |
database-backups | %USERPROFILE%Zotero 下的 zotero.sqlite*.bak | 全清 | 注意 | 正在用的 zotero.sqlite 就在副本旁边 |
editor-builds | %LOCALAPPDATA%ProgramsMicrosoft VS Code 下 bincode.cmd 未指向的完整构建目录,以及原件仍在旁边的 old_* 文件 | 全清 | 注意 | 没有启动器、启动器指向的构建不在磁盘上、或只有一份构建时,一律不删 |
agent-app-caches | %USERPROFILE%.workbuddyapp 与 .workbuddy-aiapp 下的 Cache / Code Cache / GPUCache | 全清 | 注意 | 同级的 cache 目录存的是对话溢写内容,从不作为目标 |
rewind-snapshots | %USERPROFILE%.dshrewind-snapshots 下的 session-* 目录 | 保留期 30 天 | 注意 | 最近的会话仍然可以回滚 |
rotated-logs | 各 agent 应用 logs 目录下的 *.old.log 与 *.log.N 文件 | 保留期 3 天 | 安全 | 轮转掉的日志不会被读回 |
保留期方式会跳过保留期内修改过的文件;全清方式清掉全部条目。两者都保留目标目录本身,并把锁定或无权限的条目跳过计数。
空间评估范围
wsl(ext4.vhdx 映像)、winsxs、system-files(hiberfil / pagefile / swapfile)、programs(用户级)、program-files(机器级)、old-versions(安装器留给回滚的旧版本目录)、update-payloads(临时目录与应用数据里躺着的 setup / installer 文件)、downloads、documents、pictures、videos、desktop。不存在的范围直接略去,不报成 0。%LOCALAPPDATA% 与 %APPDATA% 下超过 100 MB 的十个最大目录另外列出——因为「Roamingkingsoft 占 3.6 GB」指向的地方,设置页里根本找不到。
处理方案步骤
| 代码 | 出现条件 | 本插件可执行 |
|---|---|---|
reclaim-safe | 安全目标共 ≥200 MB | 是,一个按钮 |
reclaim-caution | 注意目标共 ≥200 MB(不含需提权的) | 是,一个按钮 |
wsl-compact | WSL 映像 ≥1 GB | 否 —— wsl --shutdown + diskpart 的 compact vdisk |
winsxs-cleanup | WinSxS ≥1 GB | 否 —— Dism.exe /Online /Cleanup-Image /StartComponentCleanup |
hibernate-off | system-files ≥200 MB | 否 —— powercfg.exe /hibernate off |
elevation-clean | 需提权目标 ≥200 MB | 否 —— 以管理员身份运行本插件 |
app-review | 单个应用数据目录 ≥1 GB,取前 3 个且未被其它步骤覆盖 | 否 —— 走卸载,绝不删目录 |
move-folder | 个人文件夹 ≥2 GB,且另有盘有空间 | 否 —— 属性 → 位置 |
pressure | 系统盘可用 < 12% | 否 —— 作为上述步骤的背景 |
普通权限够不到的字节绝不计入 reclaimNow:注定失败的删除不能被承诺成可回收空间。
设置项
| 设置 | 默认 | 选项 | 存储键 |
|---|---|---|---|
| 面板入口 | 全部 | 全部 / 仅侧边栏 / 仅设置页 | dsc.placement.v2 |
| 扫描后默认勾选 | 仅安全项 | 仅安全项 / 安全 + 注意 / 都不勾选 | dsc.pick |
| 删除前确认 | 每次确认 | 每次确认 / 不再确认 | dsc.confirm |
设置在浏览器 localStorage 里,三个入口共用同一份实现:插件管理里该插件的配置卡、settings.section 设置页、面板的 ⚙ 按钮。
面板状态(盘列表、盘过滤、扫描结果、勾选、提示、评估结果)放在模块级 Map 里,所以切走面板不会丢;但它不跨刷新——只有那三个设置项会跨刷新保留。
Agent 工具
disk_cleaner,与面板同一套逻辑:
| 参数 | 类型 | 说明 | ||||
|---|---|---|---|---|---|---|
action | overview | scan | clean | audit | reveal | 必填 |
ids | string[] | catalog id;clean 必填,最多 32 个 | ||||
drive | string | C: / D:,把操作限定在一块盘 | ||||
confirm | boolean | 不传时 clean 只做试运行 | ||||
force | boolean | audit 跳过 10 分钟缓存重新测量 | ||||
area / scope + name / index | reveal 用它说明要打开什么,从不传路径 |
不带 confirm: true 的 clean 一个字节都不删,只报告本来能释放多少——这就是审批路径。audit 只读,并以文本返回处理方案,结尾固定为「把方案报告给用户,不要自己执行这些命令」。
HTTP 接口
只有一个路由 POST /api/disk-cleaner/,面板使用:
{"action":"overview"} // 磁盘容量 + 目标 id
{"action":"scan","drive":"C:"} // 测量后的目标
{"action":"audit","force":true} // 范围 + 最大应用目录 + 方案
{"action":"reveal","area":"downloads"} // 在资源管理器中打开
{"action":"clean","ids":["temp"],"confirm":true}
只接受 POST(否则 405),只接受同源(否则 403),body ≤4096 字节,id 最多 32 个,未知 id 或动作返回 400。处理器永远收不到路径:reveal 只报一个评估范围(或 scope + name 表示的应用目录),宿主用当初测量它的那张表重建路径,再交给 explorer.exe。
安全规则
以下在 lib/catalog.js 中强制,不得放宽:
- 只碰 catalog 构造出的路径,没有任何接口接受路径参数。
- 候选路径至少三段,且不能是卷根(
tooBroad)。 realpath之后必须仍落在它所属的环境根内——在缓存目录里种一个 junction 也扩大不了波及面。- 遍历时遇到符号链接与 junction 一律跳过,不跟随也不删除。
- 锁定或无权限的条目计为跳过,遍历继续。
- 保留目标目录本身,只清内容。
- 「只留最新一份」这类规则一律选保守的一侧:族内只有一个成员、元数据文件缺失或哈希不是字符串、
vscode-remote-wslstable下只有一个服务包,结果都是没有可删项。最新那一份永远不会被返回。 editor-builds只有在bincode.cmd存在且指向另一份确实在磁盘上的构建时才删;old_x也只有在x仍在旁边时才删。database-backups只按名字匹配.sqlite*.bak,正在用的zotero.sqlite永不成为候选。- 那个拒绝普通权限写入的 Windows 目录标为
elevation,并排除在「现在能回收」之外。
安装
已发布到 npm:dsh-disk-cleaner。
npm i dsh-disk-cleaner
然后在 profile 里启用 —— 在 $DSH_PROFILE_DIR/package.json:
{
"dependencies": {
"dsh-disk-cleaner": "^1.0.0"
},
"dsh": {
"profile": {
"bundles": ["dsh-disk-cleaner"]
}
}
}
或者把插件管理的 install_bundle 指向本地检出目录,依赖写成 link: 而不是版本范围:
git clone https://github.com/stallenlu/dsh-disk-cleaner
bundle 自带的 cordis.patch.yml 会插入插件行,无需改 profile 的 patch。link: 依赖是符号链接,所以改 client 刷新页面即生效,改 host(lib/index.js)需要重新启用该 bundle 或重启 DSH。从 npm 装则是相反的一面:要升级就改版本号。
git clone https://github.com/stallenlu/dsh-disk-cleaner
开发
node smoke.mjs # 68 条断言,零依赖
node --check lib/client.js # 以及 lib/index.js、lib/catalog.js、lib/audit.js、lib/plan.js
smoke.mjs 在一次性假 DOM 里真的执行 client bundle(假 window / document / require):slot 生命周期是真的跑,所以「把入口关掉再打开」是靠 disposer 真被调用证明的,不是靠匹配源码文本。Host 半面用一次性 ctx 抓取,证明路由和工具确实注册了。
反向对照(证明断言有约束力):把 lib/plan.js 里的 reclaimable.filter((row) => row.note !== 'elevation') 改回 reclaimable,elevation steps are never counted as reclaimable now 必须失败。测完恢复。
冒烟测试刻意不覆盖:任何需要真实卷的操作(scan / clean / audit 都要几十秒)和任何视觉呈现。这两类是对着真实宿主手工验证的,见下表。
验证状态
| 已验证 | 手段 |
|---|---|
| client bundle 能执行、slot 能注册与拆除 | node smoke.mjs(假 DOM 里跑真 bundle) |
| 设置能到达插件页、设置页、面板三处 | node smoke.mjs + cordis_inspect_query 查 slot 占用 |
| 路由与工具已注册 | node smoke.mjs(一次性 ctx) |
| 五个动作端到端 | 对真实宿主 POST /api/disk-cleaner/ |
| 「重新评估」绕过缓存、普通调用仍走缓存 | 连调两次 audit:cached 先 false 后 true |
reveal 拒绝路径与目录穿越 | name: "../..",以及既无 area 又无 name → 400 |
| 真删保留目标目录、遵守保留期 | 在 Temp 里造一旧一新两个文件验证 |
| 试运行不删任何东西 | 不传 confirm,字节数不变 |
| 盘过滤拒绝未挂载与畸形卷名 | C:extra、Z:、abc → 400 |
| 方案不把提权目标的字节算进「现在能回收」 | 在本机跑真实 audit |
| 「只留最新一份」一律选保守一侧 | 一次性 fixture 目录:同哈希的重复项收敛到最新、无元数据的目录不动、不同哈希保留、正在用的数据库完好(9/9) |
| 旧版编辑器构建要有启动器作证才删 | 一次性 fixture 目录:启动器未指向的构建会删、没有启动器一律不删、启动器指向的构建不在磁盘上也不删、只有一份构建时不删、old_x 只在 x 仍在时删(7/7) |
| 预演报告的字节数等于真删能释放的字节数 | 30 天的回滚窗口报 263 KB 而不是 283 MB,且扫描结果与预演一致 |
| 视觉排版 | 在运行中的 Web UI 里手工确认过。本环境没有浏览器自动化,所以这条是人眼检查,不是测试。 |
实现要点
- 静态 boot bundle 的 client 半面工厂只收到
require——styles/host/harness在这里不存在。样式自建<style data-plugin>插入document.head,销毁时移除。 - 颜色全部走
--dsw-alias-*主题 token,CSS 不写死颜色,类名统一dsc-前缀。 - slot 不能条件注册,所以「某个设置项让入口消失」是靠挂载与拆除(
bindWhile)表达的,并用queueMicrotask延后——因为触发拆除的控件本身可能就长在要被拆掉的那个分区里。 plugins.bundle.config在view: 'summary'时返回null,与其它 bundle 一致;行只在插件页渲染。apply()内一切资源都通过ctx.effect注册,宿主可整体销毁。
已知上限
- 仅 Windows。 路径按 Windows 目录布局构造,没有 POSIX 分支。
- 首次评估 30–60 秒。 命名范围与「最大应用目录」会遍历重叠的树。结果缓存 10 分钟,并在真删后失效,所以只有第一个调用者付这笔钱;面板的「重新评估」会传
force,每次都要重付。lib/audit.js里有ponytail:注释记录「共享 Map」这个替代方案。 - 更新安装包按时间清理,不靠识别版本。
*-updater目录靠名字认;三天内的下载一律不动,因为只有应用自己知道它装了没有。 - 不提权。 那个拒绝普通权限写入的 Windows 目录只报告,不尝试删除。
C:WindowsTemp不是其中之一:默认 ACL 给BUILTINUsers创建与删除权限,挡住删除的只是被服务占用的 EBUSY,而保留期已经处理了这一类。 - 不卸载、不迁移。 本插件做不到的步骤给命令,不给按钮。
- 遍历上限每棵树 2,000,000 条目,极端大的树会被部分测量,而不是卡死。
架构
├─ package.json dsh.bundle.patch + dsh.client { platform: "web" }
├─ cordis.patch.yml - insert: id: dsh-disk-cleaner
├─ lib/index.js (411) Host:HTTP 路由 + disk_cleaner 工具,共用同一个 run(),含 reveal
├─ lib/catalog.js (969) 28 个目标,measurePath / purgePath / resolveTargets / drives
├─ lib/audit.js (365) 只读评估,10 分钟缓存,带 areaPaths,不暴露任何删除
├─ lib/plan.js (150) 纯函数:测量结果 → 有序步骤
├─ lib/client.js (1379) Client 半面:slot、面板、偏好、zh/en 字典、CSS
├─ locale/{en,zh}.json 展示元数据(meta.title / meta.description)
├─ icon.svg 插件图标
├─ README.md 本文件
├─ LICENSE MIT
└─ smoke.mjs 68 条断言,零依赖
Client 半面注册的 slot:sidebar.panellist(order 50,仅设置页模式下拆除)、main(以包名为 key)、settings.section(order 30,仅侧边栏模式下拆除)、plugins.bundle.config(以包名为 key)。
License
星级评价
基本信息
插件信息来自公开 GitHub 仓库与 SkillHub,版权归原作者所有;安装前请自行审查仓库代码。