WorkBuddy C 盘空间治理(迁移 + 清理 + 更新控制)
本技能把三件相关的事收拢到一起,避免重复逆向、漏掉已知坑。三件事可以独立做,也可以按"先迁移 → 再处理备份/清理 → 最后关更新+设自动清理"的顺序串联。
- 模块一:迁移数据目录(C → 其它盘,免重装)
- 模块二:清理 C 盘残留 + 处理 .migrated.bak
- 模块三:关闭自动更新 + 自动清理安装包
模块一:迁移数据目录(C → 其它盘,免重装)
用途
把 C:\Users\<用户名>\.workbuddy 整体迁到另一块盘(默认 D:\WorkBuddy),用目录联接(junction)替代原目录。软件无感,数据实际落在目标盘,释放 C 盘空间,不重装软件。
何时用
"把 .workbuddy 移到 D 盘""C 盘满了迁移 WorkBuddy 数据""WorkBuddy 装 C 盘太占空间"。
前提确认(只读检查)
- 确认
%USERPROFILE%\.workbuddy存在并量大小(binaries/vendor/app/workspace/plugins是大头,常 1.5~2.5 GB)。 - 确认目标盘剩余空间充足(默认
D:\WorkBuddy)。 - 确认当前不是联接:
(Get-Item %USERPROFILE%\.workbuddy -Force).LinkType应为空;已是Junction则直接报告"已完成"。
执行流程
- 生成本地迁移脚本:把下方"迁移脚本"代码块全部内容原样写入
D:\WorkBuddy\finish_move.bat(CRLF 行尾,记事本默认即可)。换目标盘只改set "DST=..."一行。 - 验证算法(关键,不要跳过):沙箱禁止直接跑 .bat 且禁
Add-Type,用 PowerShell 在临时空目录把"robocopy 同步 → D⊇S 校验 →ren改名保留 →mklink /J建联接 → 经联接读到数据"原样跑一遍,再清临时文件。 - 交给用户执行:明确顺序——彻底退出 WorkBuddy(含关闭对话窗口、托盘无图标)→ Win+E 进目标盘目录 → 双击
finish_move.bat→ 看到SUCCESS后重开软件。WorkBuddy 运行时锁目录且持续写入,绝不能替用户跑最后改名/建联接那步。 - 用户回"完成"后独立核验:
(Get-Item %USERPROFILE%\.workbuddy -Force).LinkType应返回Junction、.Target应等于目标路径;C:\Users\<用户名>\.workbuddy.migrated.bak应存在(可恢复备份);经联接读取文件数与目标盘一致即成功。优先用LinkType/Target,别解析fsutil文本(中文 Windows 的 fsutil 字段叫「替换名称」不是Print Name,抓英文字段会抛异常)。
必须避开的坑(实战教训)
- 不用回收站 API 删/移原目录:
Microsoft.VisualBasic.FileIO.DeleteDirectory(SendToRecycleBin)在本机会挂起不返回,脚本卡死。改用ren原地改名保留为.workbuddy.migrated.bak,数据原样可恢复。 - .bat 里 echo 文本不要带小括号:
()会被当代码块括号,整段解析失败。全程用goto标签跳转,所有 echo/日志文本保持零括号。 - 校验用 D⊇S,不字节完全相等:
app\session/日志/缓存运行时易变,C/D 永远对不齐。标准="D 盘包含 C 全部文件且大小一致"。 - 杀进程要干净:不只
taskkill /IM WorkBuddy.exe,按$_.Name -match 'workbuddy'结束所有匹配进程(含 Helper),"杀 → 等 2 秒 → 复检"。 - WorkBuddy 运行时做不了最后一步:务必让用户退出后再双击 bat;脚本内置进程复检,同步期间用户重开则中止。
- 绝不彻底删用户数据:保留
.migrated.bak可恢复;任一步失败即中止且不删任何东西。
迁移脚本(本地生成,双击执行)
逻辑:探测是否已是联接 → 结束全部 workbuddy 进程 → robocopy 同步 → D⊇S 校验 → 改名保留为 .migrated.bak → 建目录联接。任一步失败即中止且不删数据。
@echo off
setlocal
set "SRC=%USERPROFILE%\.workbuddy"
set "DST=D:\WorkBuddy\.workbuddy"
set "BAK=%USERPROFILE%\.workbuddy.migrated.bak"
set "LOG=D:\WorkBuddy\finish_move.log"
echo ===== WorkBuddy move started %date% %time% ===== >> "%LOG%"
if not exist "%SRC%" (
echo SRC_NOT_FOUND: %SRC% does not exist >> "%LOG%"
goto END
)
fsutil reparsepoint query "%SRC%" >nul 2>&1
if %errorlevel%==0 (
echo ALREADY_DONE: %SRC% is already a junction pointing to D >> "%LOG%"
goto END
)
echo [1] closing all WorkBuddy processes >> "%LOG%"
taskkill /F /IM WorkBuddy.exe >nul 2>&1
powershell -NoProfile -Command "Get-Process -ErrorAction SilentlyContinue | Where-Object { $_.Name -match 'workbuddy' } | ForEach-Object { Stop-Process -Force -Id $_.Id -ErrorAction SilentlyContinue }" >> "%LOG%" 2>&1
timeout /t 2 >nul
echo [2] syncing C to D via robocopy >> "%LOG%"
if not exist "%DST%" mkdir "%DST%"
robocopy "%SRC%" "%DST%" /E /COPY:DAT /R:2 /W:1 /NFL /NDL /NP >> "%LOG%" 2>&1
echo [3] verifying D contains all C files >> "%LOG%"
powershell -NoProfile -Command "$s=Get-ChildItem '%SRC%' -Recurse -Force -ErrorAction SilentlyContinue | Where-Object { -not $_.PSIsContainer }; $d=Get-ChildItem '%DST%' -Recurse -Force -ErrorAction SilentlyContinue | Where-Object { -not $_.PSIsContainer }; $m=@{}; $d|ForEach-Object { $m[$_.FullName.Substring('%DST%'.Length).TrimStart('\')]=$_.Length }; $miss=0; $bad=0; foreach($f in $s){ $r=$f.FullName.Substring('%SRC%'.Length).TrimStart('\'); if(-not $m.ContainsKey($r)){$miss++} elseif($m[$r]-ne $f.Length){$bad++} }; if($miss-eq 0 -and $bad-eq 0){exit 0}else{Write-Output ('VERIFY_FAIL missing='+$miss+' bad='+$bad);exit 1}" >> "%LOG%" 2>&1
if errorlevel 1 goto VERIFY_FAIL
echo VERIFY OK >> "%LOG%"
powershell -NoProfile -Command "if(@(Get-Process -ErrorAction SilentlyContinue | Where-Object { $_.Name -match 'workbuddy' }).Count -gt 0){exit 1}else{exit 0}" >nul 2>&1
if errorlevel 1 goto REOPENED
echo [4] renaming source aside as recoverable backup >> "%LOG%"
if exist "%BAK%" goto BAK_EXISTS
ren "%SRC%" ".workbuddy.migrated.bak" >> "%LOG%" 2>&1
if exist "%SRC%" goto REN_FAIL
echo [5] creating directory junction >> "%LOG%"
mklink /J "%SRC%" "%DST%" >> "%LOG%" 2>&1
fsutil reparsepoint query "%SRC%" >nul 2>&1
if errorlevel 1 goto LINK_FAIL
echo SUCCESS: %SRC% now points to %DST% >> "%LOG%"
echo ===== Finished %date% %time% ===== >> "%LOG%"
goto END
:VERIFY_FAIL
echo VERIFY_FAIL: D missing some C files - aborted, nothing deleted >> "%LOG%"
goto END
:REOPENED
echo REOPENED: WorkBuddy reopened during sync - aborted >> "%LOG%"
goto END
:BAK_EXISTS
echo BAK_EXISTS: a previous .migrated.bak exists - remove it first then re-run >> "%LOG%"
goto END
:REN_FAIL
echo REN_FAIL: could not rename source - aborted >> "%LOG%"
goto END
:LINK_FAIL
echo LINK_FAIL: junction creation failed - check %LOG% >> "%LOG%"
goto END
:END
echo Done. See %LOG%
pause
> 技能包内不直接附带 .bat(SkillHub 禁传可执行脚本),由 WorkBuddy 在目标机现场生成再交用户双击。
模块二:清理 C 盘残留 + 处理 .migrated.bak
迁移后 C 盘真实占用(正确口径)
迁移完成后 C 盘 WorkBuddy 残留已很小(本机实测约 520 MB)。绝不能用 Get-ChildItem -Recurse 直接扫 %USERPROFILE%\.workbuddy —— PowerShell 5.1 的 -Recurse 会跟随联接钻进 D 盘,把 1 GB 算成 C 盘占用,结论完全反。默认处理:
- .workbuddy 若 LinkType=Junction,占 0,只是个重分析点(几十字节)。
- 逐项枚举候选时先判 Attributes -band [IO.FileAttributes]::ReparsePoint,是重分析点就跳过记 0,别递归进去。
- 程序本体通常在非系统盘(本机 E:\workbuddy),C:\Program Files 下无 WorkBuddy,确认后再下结论。
真正要清点的是这些(本机实测):
| 位置 | 本机实测 |
|---|---|
%LOCALAPPDATA%\Temp\workbuddy-update-x64\WorkBuddy-Setup-*.exe |
507.5 MB |
%LOCALAPPDATA%\Temp\workbuddy-product-spill-*(约 30 个) |
~12 MB |
%LOCALAPPDATA%\@genieworkbuddy-desktop-updater\ |
删过则为 0 |
%APPDATA%\WorkBuddy |
~0 |
%USERPROFILE%\WorkBuddy(会话工作区) |
0.1 MB |
%USERPROFILE%\.workbuddy-key-fallback、Packages\WorkBuddyShareTarget_* |
~0 |
收尾建议(迁移成功后告知用户)
- 更新包在 C 盘上有两份,只删一份等于没删干净(本机实测):
1.C:\Users\<用户名>\AppData\Local\@genieworkbuddy-desktop-updater\installer.exe(约 500 MB)
2.C:\Users\<用户名>\AppData\Local\Temp\workbuddy-update-x64\WorkBuddy-Setup-<版本号>.exe(约 507 MB)
两份都删才拿回约 1 GB。判据:文件名版本号 == 当前已装版本(如5.5.6.38337834)时该包已无用,删后下次更新会重下。 .workbuddy.migrated.bak真的占 C 盘空间(就是源目录那一份真实副本,本机实测 1.05 GB / 33912 文件)——它不是"和 D 盘同一份数据、不占额外空间"。所以迁移完成那刻 C 盘释放量为 0,真正腾空间要等删掉.bak。迁移的即时收益是"以后写入/增长都走 D 盘",不是"立刻腾 N GB"。必须如实说清,别让用户误以为迁移完 C 盘就轻了。
.migrated.bak 怎么处理(优先推荐搬到别的盘)
先说结论:别送回收站。 C 盘回收站就在 C 盘上,1 GB 进回收站 C 盘可用空间一点都不变,得等清空回收站才释放,纯无效操作。
给用户三个选项,按优先序:
1. 搬到另一块盘保留(推荐):robocopy 复制到如 E:\WorkBuddyRollback\.workbuddy.migrated.bak → 校验 E⊇C → 再删 C 盘那份。C 盘照样释放且保住回滚退路,代价是另一块盘占同等空间。
2. 直接永久删除:释放最多,但删完无退路。仅用户明确要"清空间"且确认可承受时做。
3. 只删 installer.exe(约 500 MB),.bak 观察几天再说。
搬移脚本要点(本机已跑通):
- 目标路径不要选在 WorkBuddy 程序目录里(如 E:\workbuddy 是本体),另起独立目录名如 E:\WorkBuddyRollback。
- robocopy $src $dst /E /COPY:DAT /R:2 /W:1 /NFL /NDL /NP,exitcode 0 或 1 都算成功。
- 删 C 盘那份前必须复检 E 盘完整性:文件数 == 源快照数且关键文件(SOUL.md/IDENTITY.md/USER.md/settings.json/workbuddy.db)哈希一致。不通过就中止,一个字节都别删。
两个工程性教训(很重要)
- 删 3 万多个小文件不要用
Remove-Item -Recurse -Force:慢,且一旦宿主会话结束进程被杀会停在半路留部分删除残留。改用空目录 +robocopy /MIR掏空:
powershell $empty = Join-Path $env:TEMP "_wb_empty_$([guid]::NewGuid().ToString('N'))" New-Item -ItemType Directory -Path $empty -Force | Out-Null robocopy $empty $target /MIR /R:0 /W:0 /NFL /NDL /NP Remove-Item -LiteralPath $target -Recurse -Force Remove-Item -LiteralPath $empty -Recurse -Force
(mirror_exitcode = 2表示"目标里有源没有的文件/目录已删除",正是我们要的。) - 长任务脚本必须写成"带阶段标记、可续跑"的:本机实测,一次 C→E 搬移跑到第 4 步时宿主会话结束、进程被杀,脚本停半路。因每一步都往日志写
--- stage N: ... ---和结果,续跑时能精确判断"复制已完成且已校验,只剩删除"而安全收尾,不从头来。任何跨盘搬移/大批量删除脚本照此写:先写日志标记再做动作,危险动作前都有一次独立复检。
模块三:关闭自动更新 + 自动清理安装包
核心事实(从 E:\workbuddy\resources\app.asar 逆向确认)
没有官方开关。 不要找设置项,浪费时间。
| 事实 | 证据 |
|---|---|
| 无"自动更新"开关 | 「设置 → 关于」页面 i18n 键只有 9 个:settings.about.{aboutProduct, checkUpdates, currentVersion, followUs, officialAccount, openOfficialWebsite, submitFeedback, videoAccount, viewDocs},只有「检查更新」按钮,无开关项 |
| 自动检查间隔 10 分钟 | var UPDATE_INTERVAL_MS = 600 * 1e3; |
| 更新接口 | https://copilot.tencent.com/v2/update?platform=workbuddy-<平台>&version=<当前版本> |
| 唯一覆盖点 | 环境变量 WORKBUDDY_UPDATE_URL,优先级高于 updateBaseUrlProvider(运行时租户 endpoint)和硬编码默认值 |
| Windows 安装方式 | 静默 NSIS 安装 + 自动重启(update-service.win32.ts,配 resources/scripts/update-progress.ps1 过渡窗) |
| 应用故意保留当前版本安装包 | update-service.win32.ts::cleanupCache(exceptVersion) 只删"非当前版本"的产物 → 当前版本那份 500 MB 永远躺着。指望它自己清干净等不到 |
⚠️ 别被名字骗了(误判陷阱)
autoUpdateEnabled/useAutoUpdatePreference/skills_auto_update_enabled—— 全是技能(skill)的自动更新,与桌面端自动更新无关。SKIP_CHECK_UPGRADE=smartsheet_skip_check_upgrade—— 是腾讯文档表格灰度开关,无关。UPDATE_INTERVAL_MS有多个同名常量(500 ms 那个是 MCP inspector 的),认准= 600 * 1e3那个。sfupdatemgr.exe路径C:\Program Files\SF\EDR\agent\bin\—— 是深信服 EDR,不是 WorkBuddy 更新器。WorkBuddy.exe的ProductVersion只到5.5.6.0,不含构建号;比对版本要看插件目录里的完整构建号(如5.5.6-wb.38337834),否则会误判"版本不符"。
关闭自动更新(唯一可行方案)
把 WORKBUDDY_UPDATE_URL 设成不可达地址,让更新检查直接失败。用户级环境变量,写入 HKCU\Environment,不需要管理员。
[Environment]::SetEnvironmentVariable('WORKBUDDY_UPDATE_URL','http://127.0.0.1:9','User')
# 改完 re-read 注册表确认写入成功
[Environment]::GetEnvironmentVariable('WORKBUDDY_UPDATE_URL','User') # 应输出 http://127.0.0.1:9
(Get-ItemProperty 'HKCU:\Environment' -Name 'WORKBUDDY_UPDATE_URL').WORKBUDDY_UPDATE_URL
安全性已验证:WORKBUDDY_UPDATE_URL 在整个 app.asar 只出现 4 处,全部在 update-service 内用于拼 /v2/update 的 URL,无任何其它消费者,不影响 AI 对话、插件市场、连接器。
必须告诉用户的代价(不能瞒):
- 应用内「检查更新」走同一接口,手动检查也一起失效,点它只得到「检查更新失败」(toast update.toast.failed)。"手动更新"实际变成去官网下载安装包手动装。
- 需重启 WorkBuddy 才生效(环境变量只对新进程生效)。设置完提醒用户自己重启,重启后确认 AI 对话仍正常。
- 恢复自动更新:删掉变量 + 重启 —— [Environment]::SetEnvironmentVariable('WORKBUDDY_UPDATE_URL',$null,'User')。完全可逆。
自动清理更新安装包
用 WorkBuddy 自己的定时自动化(automation_update,scheduleType=recurring)实现 —— 这是受支持的方式,不要用计划任务 / 注册表 / 开机启动(本机策略禁止,不绕过)。
清理目标(不存在则跳过,不报错):
| 路径 | 大小 |
|---|---|
%LOCALAPPDATA%\Temp\workbuddy-update-x64\WorkBuddy-Setup-*.exe |
约 507 MB(最大头) |
%LOCALAPPDATA%\@genieworkbuddy-desktop-updater\installer.exe |
约 507 MB |
%LOCALAPPDATA%\Temp\workbuddy-product-spill-*(约 30 个) |
每个约 0.5 MB |
%LOCALAPPDATA%\Temp\workbuddy-prompt-vars / workbuddy-host-cli / workbuddy-mcp-app-inspector |
~0.1 MB |
> 更新包在 C 盘有两份(上表前两行),路径不同、大小几乎一样,只删一份只拿回一半。
定时任务 prompt 模板(直接复用到 automation_update):
清理 WorkBuddy 桌面端在 C 盘留下的更新安装包残留,释放系统盘空间。用 PowerShell 执行清理,完成后简要报告删了什么、释放多少 MB。
要清理的目标(不存在则跳过,不要报错):
1. %LOCALAPPDATA%\Temp\workbuddy-update-x64\ 下的 WorkBuddy-Setup-*.exe(约 500 MB)
2. %LOCALAPPDATA%\@genieworkbuddy-desktop-updater\installer.exe(约 500 MB)
3. %LOCALAPPDATA%\Temp\workbuddy-product-spill-* 目录(每个约 0.5 MB)
4. %LOCALAPPDATA%\Temp\workbuddy-prompt-vars、workbuddy-host-cli、workbuddy-mcp-app-inspector
安全约束(必须严格遵守):
- 只删最后修改时间超过 24 小时的文件和目录。新下载的更新包可能对应一次尚未完成的升级,绝对不能删。
- 绝对不要动 %USERPROFILE%\.workbuddy(指向 D 盘的数据目录联接)、E:\workbuddy(程序本体)、E:\WorkBuddyRollback(回滚副本)。
- 删除文件用 Remove-Item -LiteralPath <路径> -Force;删除目录用 Remove-Item -LiteralPath <路径> -Recurse -Force。被进程占用删不掉则跳过并记录,不要反复重试到卡住。
- 不要用回收站(C 盘回收站不释放空间,且该 API 在本机会挂死)。
- 先记录 C 盘可用空间,再删,删完再记录一次。
报告格式:删除了 N 项文件 / 释放 X MB / C 盘可用空间从 A GB 变为 B GB。若全部跳过,一句话说明「本次无需清理」。
安全闸门(写进 prompt 的硬约束):① 只删修改时间超 24 小时;② 禁止触碰 .workbuddy、E:\workbuddy、E:\WorkBuddyRollback;③ 不用回收站;④ 删文件 -Force、删目录加 -Recurse,被占用则跳过;⑤ 删 3 万+ 小文件用空目录 + robocopy /MIR 掏空(见模块二教训 1)。
验收与回滚
- 验收:环境变量返回值正确;
Get-ChildItem "$env:LOCALAPPDATA\Temp" -Force | Where-Object { $_.Name -match '^workbuddy' }正常只剩运行时会重建的空目录(0 KB);C 盘可用空间前后对比。 - 回滚:删环境变量 + 重启,自动更新即恢复。清理任务只删缓存产物,无回滚需求。
典型串联工作流(首次治理 C 盘)
- 模块一:生成
finish_move.bat→ 交用户退出后双击 → 回"完成"后核验。 - 模块二:测算真实占用 → 把
.migrated.bak搬 E 盘(或按用户意愿删/观察)→ 清 Temp 残留。 - 模块三:设
WORKBUDDY_UPDATE_URL关自动更新 → 建每日清理安装包的定时自动化。 - 全程不在 WorkBuddy 运行时做危险改动;任何跨盘/大批删除脚本都写成带阶段标记、可续跑、危险动作前独立复检的形式。
Resources
references/migration-guide.md— 迁移深度避坑说明、失败案例与脚本逐行注释,供深度参考。