WorkBuddy 已删任务目录清理
概述
WorkBuddy 的每个任务(会话)在 ~/WorkBuddy/<时间戳> 下都有一个本地工作区文件夹。任务在应用里被删除后,本地文件夹不会被自动清理,逐渐堆积残留。本 skill 通过只读对比 WorkBuddy 数据库(~/.workbuddy/workbuddy.db 的 sessions 表)和磁盘上的文件夹,找出"已删除任务对应的残留文件夹",生成对照单供用户勾选,并在用户明确选择后把它们移入系统废纸篓/回收站。
为什么需要它:WorkBuddy 没有「删除任务时联动删除本地文件」的钩子,残留只能靠事后比对清理。用了几周的实例,残留目录动辄几十个、占几百 MB。
和"扫空文件夹"那类清理方式的区别:不读数据库的清理方式只能凭"目录是否为空""是否命中受保护名单"去猜,无法区分「任务还活着但目录恰好是空的」和「任务已被删除」——前者一删就把正在用的工作区弄没了。本 skill 的唯一判定依据是数据库里的 sessions.deleted_at,只有确认任务已删除的目录才会进入候选清单,存活任务、自动化目录、当前会话目录一律带保护标记出现在对照单的"受保护"区,不进候选。判断是否"仍在占用"用的是目录包含关系(受保护路径只要落在这个目录里面,整目录就受保护),不是简单的路径相等——存活会话在 <任务目录>/src 里工作时,该任务目录同样不会被清。
跨平台支持(脚本自动识别):
| 平台 | 数据库 | 废纸篓机制 |
|---|---|---|
| macOS | ~/.workbuddy/workbuddy.db |
Finder(失败降级 ~/.Trash) |
| Windows | %USERPROFILE%\.workbuddy\workbuddy.db |
PowerShell + .NET SendToRecycleBin |
| Linux | ~/.workbuddy/workbuddy.db |
gio trash |
零第三方依赖,只需 Python 3.10+(脚本只用标准库)。
安装
把本目录放到 WorkBuddy 的用户级 skills 目录,重启会话后生效:
# macOS / Linux:放到 ~/.workbuddy/skills/workbuddy-deleted-task-cleaner/
cp -R workbuddy-deleted-task-cleaner ~/.workbuddy/skills/
Windows 放到 %USERPROFILE%\.workbuddy\skills\workbuddy-deleted-task-cleaner\。
计费(闸门按次 ¥0.01 · SkillHub 展示价一致)
官方 SkillPay 规范:服务端(闸门)负责验付与交付,Skill 负责发起请求和调用支付能力。SkillHub 计费页的「单次调用价格」只是展示价,实际收费以闸门的 402 账单为准——官方要求支付宝登记价、闸门账单价、SkillHub 展示价三处保持一致,本 skill 三处均为 ¥0.01 / 次。
| 位置 | 值 | 说明 |
|---|---|---|
| 闸门 402 账单 | 0.01 元/次 |
实际收费点,付款后签发一次性授权 |
| 支付宝服务登记价 | 0.01 元/次 |
与账单必须一致,否则验付按金额不符拒绝 |
| SkillHub 计费页「单次调用价格」 | 0.01 元/次 |
仅展示;与上两处保持一致 |
| 动作 | 收费 | 说明 |
|---|---|---|
cleanup.py check / report |
免费 | 只读扫描、生成对照单,随便跑 |
cleanup.py clean --all / --paths |
¥0.01/次 | 执行前必须持有有效授权,否则退出码 6,不删除任何东西 |
授权流程(脚本 scripts/paid.py,只依赖标准库;真正付款交给官方 alipay-bot):
python3 scripts/paid.py probe # 1) 生成 402 账单:金额、商品名、订单号、15 分钟有效期
python3 scripts/paid.py pay # 2) AI 支付:钱包已绑定则由 AI 直接扣款;下单后自动轮询付款结果并直接验付签发授权
python3 scripts/paid.py pay --scan # 人工支付:终端画二维码 + 落盘 png,本人用支付宝扫
python3 scripts/paid.py pay --no-wait # 只下单不等待(需要自己再跑 complete)
python3 scripts/paid.py complete # 3) 手动补验付(--trade-no 可在无查询号时按交易号查)
python3 scripts/paid.py status # 查看当前授权(是否已用、何时过期)
支付有两条路,同一张 402 账单都能用:
| 方式 | 命令 | 行为 |
|---|---|---|
| AI 支付 | paid.py pay |
钱包已绑定时由 AI 直接完成扣款,无需人工介入 |
| 人工支付 | paid.py pay --scan |
不自动扣款;由 alipay-bot 在终端画出二维码(并另存 png),本人用支付宝 App 扫码确认 |
--scan 之所以要单独的开关:默认走的 --json 输出只回结构化结果、不画二维码,脚本也捕获了输出;
扫码模式改用文本输出并把输出直接透传到终端,二维码才看得见。
规则:
- 授权一次一用、默认 30 分钟有效;
clean成功后脚本自动写入used_at核销 - 支付前必须把账单金额给用户看,用户点头才执行
pay - AI 支付走不通(钱包未开通、状态接口查不到)时,用
pay --scan改人工扫码,账单不用重新生成 complete只有在服务端确认付款(success=true且有交付物/交易号)时才签发授权- 闸门地址/分享码可用
SKILLHUB_CLEANUP_GATE、SKILLHUB_CLEANUP_SHARECODE覆盖
交付回执
付款成功、产物交付之后必须生成一份回执,把「付了什么」和「交付了什么」一起返还给用户:
python3 scripts/receipt.py --auth <authorization.json> --artifact <产物路径> \
--slug <skill-slug> --version <版本> --status delivered
- 终端会打印一行
上传信息: {"slug":...,"version":...,"status":...,"artifact_count":...,"delivered_at":...,"trade_no":...,"amount":...} - 回执 JSON 默认落在
authorization.json同级receipts/<slug>-<时间戳>.json,字段含trace(交易号/金额/资源号)、artifacts(路径/大小/sha256 前 12 位)、delivered_at - 无授权文件也能跑(只记录交付物);交付失败或退款时不生成回执,改为如实报错
- 回执是「已付费且已交付」的唯一凭据,不要用它替代
authorization.json(后者才是一次性授权凭证)
单次价格 ¥0.01,回执只记录事实,不含任何可复用的支付凭证。
核心工作流(必须按顺序执行)
调用方式(下文用 SCRIPT 代指脚本路径,按平台替换):
# macOS / Linux
SCRIPT=~/.workbuddy/skills/workbuddy-deleted-task-cleaner/scripts/cleanup.py
python3 "$SCRIPT" <mode>
# Windows
set SCRIPT=%USERPROFILE%\.workbuddy\skills\workbuddy-deleted-task-cleaner\scripts\cleanup.py
python "%SCRIPT%" <mode>
第 1 步:只读扫描
python3 "$SCRIPT" check
输出 JSON:
residuals:残留文件夹列表(path、对应已删除任务的 title、大小、文件数、mtime)protected:仍被未删除任务/自动化/当前会话占用的目录(永不清理)skipped_non_task_dirs:不符合任务目录命名模式的目录(如Claw),一律不动
第 2 步:在对话中展示对照单并让用户选择
python3 "$SCRIPT" report
report 直接向标准输出打印 markdown 对照单(不要用 --out 落盘,不要在本地保存对照单文件),把内容整理成表格展示在对话中。对照单分三部分:
- 已删除任务对应的本地残留——带编号表格(编号、本地文件夹、对应任务名、大小、文件数、最后修改时间),按大小降序
- 受保护目录——存活任务/自动化/当前会话,本次不动
- 非任务目录——永不处理
展示对照单后,固定提供以下两个选项让用户选择(未选择前禁止清理):
- 全部清理(默认含空白文件夹)→
clean --all - 手动选择 → 用户回复编号(如"删 1、3、5"或"删 1-5")→
clean --paths <所选路径>
空白文件夹默认一并删除,无需单独询问。用户也可以回复"不删"→ 停止,不执行任何删除。
第 3 步:移入废纸篓/回收站
按用户选择的选项执行(见第 2 步中的对应命令),例如:
# 选项 1 全部清理
python3 "$SCRIPT" clean --all
# 选项 2 手动选择(把编号换成实际路径)
python3 "$SCRIPT" clean --paths ~/WorkBuddy/2026-10-24-13-53-10 ...
clean 属于付费动作:没有有效授权会直接以退出码 6 结束,不删除任何目录。
若报"未找到有效的付费授权",按上文「计费」一节先走 paid.py probe → pay → complete。
脚本内置的安全约束(不得绕过):
clean执行前会重新扫描,只清理"此刻仍判定为残留"的目录- 一律移入系统废纸篓/回收站(可恢复),绝不使用直接删除命令(含递归强删形式)
- 只处理
~/WorkBuddy/一级目录下符合YYYY-MM-DD-HH-MM-SS或automation-YYYY-MM-DD-HH-MM-SS命名的目录 - 当前会话目录、未删除任务目录、自动化目录、非任务命名目录永远被拒绝
- 受保护判定按目录包含而非路径相等:任何存活会话 cwd 或运行中进程 cwd 只要落在候选目录内部(含多级子目录),该目录即受保护
- 路径比对按文件系统语义:macOS / Windows 大小写不敏感,
~/WorkBuddy/Abc与~/workbuddy/abc视为同一目录,不会因写法差异漏判 automation-*一律硬拒绝,不参考automations表登记情况(未登记的自动化目录同样不动)- 搬入废纸篓前还有一道独立闸门:非
~/WorkBuddy/一级子目录、自动化目录、内部有存活进程活动的目录,逐个拒绝
这些承诺可以被验证,不是文档口头保证:
python3 tests/verify_guard.py # 17 项断言,全通过退出码 0
它用临时目录 + 合成数据库复现真实场景(存活会话 cwd 在 <任务目录>/src 内、未登记的 automation-*、大小写不同写法、symlink 冒充、clean --all / clean --paths 实跑),并附带变异测试:把包含判定回退成「路径相等」后,对应的保护断言必须失败——证明测试真能抓到该漏洞,而不是空转。
判定原理(供排查时参考)
sessions表:deleted_at IS NULL OR -1= 存活任务;deleted_at为时间戳 = 已删除任务automations表的cwds字段(JSON 数组)= 自动化的工作目录,视为受保护- 残留 = 磁盘上的任务目录 −(存活任务 cwd ∪ 自动化 cwd ∪ 当前会话 cwd ∪ 运行中进程 cwd),且判定按包含关系:候选目录只要覆盖了任一条受保护路径就转为受保护
automation-*目录不进候选,直接进受保护区- 运行中进程 cwd 通过
lsof -d cwd尽力采集(取不到就跳过),用于兜住数据库里没登记的活动目录 - 数据库以"复制到临时目录 + Python 内置 sqlite3 只读打开"方式访问,跨平台、无需外部 sqlite3 命令,且不锁 WAL
注意事项
- WorkBuddy 目前没有任务删除时的钩子,无法做到"删任务同时删文件";本 skill 即兜底方案:定期(或用户提起时)检查并清理不一致的残留。
- 用户要求"以后每次都自动检查"时,可以建议创建一个自动化任务定期调用本 skill 的
check并汇报结果,但清理动作仍需每次经用户确认。 - 脚本报告"(无对应已删除任务记录)"的目录可能是异常情况,向用户展示时应特别标注,默认不要清理。
- Windows 上若 PowerShell 执行策略受限,提示用户以当前用户身份运行
Set-ExecutionPolicy -Scope CurrentUser RemoteSigned后重试。
变更记录
-
1.2.2 — 验付判据收紧:旧逻辑只要返回里有交易号就签授权,
status=payment_unconfirmed(付款未确认)会被误判成已付款;现在必须有isFulfillmentSuccess=true/resourceDelivered=true/ 真实交付正文才放行,PAYMENT_UNCONFIRMED|PENDING|PROCESSING一律拒绝。pay --scan改为 JSON 下单并自动轮询(最多约 100 秒),付完自动验付签授权。付款并清理后新增交付回执:scripts/receipt.py --auth <authorization.json> --artifact <清理记录>落盘receipts/<slug>-<时间戳>.json并打印上传信息(交易号 + 产物指纹),文件名带毫秒避免同秒覆盖;references/skillpay.md同步严判口径与回执验收项。 -
1.2.1 — 付款前自检钱包、付款后自动识别。
pay先跑alipay-bot check-wallet:未开通就只发开通码(apply-wallet链接 + 二维码),并默认每 5 秒轮询、约 90 秒内检测到bound就继续出支付码(--no-wait-wallet关掉等待);下单后自动poll-payment-status,outcome=success即自动验付签发授权(--no-wait关掉)。修付款完成的「检测不到」:pay拿到needPolling的订单后默认自动轮询(alipay-bot poll-payment-status,每 5 秒、最多约 80 秒),一旦outcome=success立即自动验付签发授权;--no-wait可退回「下单即返回」。同时补complete --trade-no兜底:moveToMobile等支付方式只回交易号、不回查询号,原先会误报「没有可用的支付凭证」。交付解析改为整棵 JSON 递归取正文,兼容 CLI 包一层的resourceResponse.body.content;以fulfillmentResponse.isFulfillmentSuccess作为履约成功判据。 -
1.2.0 — 发布包里彻底去掉免付旁路:自用开关只在开发版存在,构建时整段裁剪(含文档说明),买家拿到的包里找不到任何免付开关;
tests/verify_guard.py改用临时授权文件走正常付费校验,不再依赖旁路,回归仍是 17/17。 - 1.1.9 — 支付支持人工扫码:新增
paid.py pay --scan,改用文本输出并透传终端,由alipay-bot直接画出支付二维码(同时落盘 png),本人用支付宝扫码确认;默认pay仍为 AI 自动扣款。钱包未开通/状态接口查不到时,错误提示会直接引导改用--scan,账单沿用、不必重新生成。 - 1.1.8 — 依据官方 SkillPay 个人接入教程纠正计费模型:平台不代扣,计费页「单次调用价格」仅为展示价,实际收费以闸门 402 账单为准(支付宝登记价、账单价、展示价三处一致)。恢复
clean前强制校验授权,撤销 1.1.7 的默认放行。 - 1.1.7 — 误按「平台按次代扣」理解计费页字段,将本地闸门改为默认关闭;该理解与官方教程不符,已在 1.1.8 回滚。
- 1.1.6 — 收费模型改为单层按次:商店免费上架(
listing: free),仅闸门按次 ¥0.01;frontmatter 改用pricing: per_call/per_call_price声明,不再写商店挂牌价price_per_call,避免买家用时被重复收费。 - 1.1.5 — 路径比对改用文件系统语义(macOS/Windows 折叠大小写):库里 cwd 与目录名仅大小写不同时,原先的包含判定会失效、存活任务目录被判残留;已修复并纳入回归。
- 1.1.4 — 修复残留误判:受保护判定从「realpath 精确相等」改为「目录包含关系」,存活会话 cwd 位于任务目录内部时不再被判残留;
automation-*改为无条件硬拒绝(不再依赖automations表登记);新增运行中进程 cwd 探测作为第二重保护;trash()增加一级子目录、自动化目录、存活进程活动的独立复核。 - 1.1.0 — 接入 SkillPay:
scripts/paid.py(probe/pay/complete/status)+clean前强校验一次性授权(¥0.01/次,30 分钟、一次一用),check/report保持免费;新增--auth与自用旁路开关(仅开发版,发布包裁剪)。 - 1.0.1 — 目录/技能名统一为
workbuddy-deleted-task-cleaner(与商店 slug 一致),安装路径与调用示例同步更新。 - 1.0.0 — 首个公开版本:只读扫描、对话内对照单、两档清理(全部 / 手动选择)、跨平台废纸篓、删除前二次扫描校验。发行 slug 为
workbuddy-deleted-task-cleaner。