Clawd on Desk 发布流程全解:从版本号校验、跨平台构建到 WinGet 自动发布

原创2026-10-09 00:31:4274 阅读
文章标签:桌面应用交互助手

Clawd on Desk 发布流程全解:从版本号校验、跨平台构建到 WinGet 自动发布

Clawd on Desk(Clawd)是一个监视 Claude Code、Codex、Cursor 等 AI 编码 Agent 状态并作出实时反应的桌面宠物应用。本文以 docs/project/release-process.md 为骨架,完整讲解该项目的正式发布流程:打标签前的版本与资源校验、macOS Developer ID 签名与公证、v* 标签触发的草稿发布(Draft Release)、逐平台冒烟检查清单,以及 WinGet 清单的生成、校验与分阶段提交机制。读完本文,你将掌握一套"先人工冒烟、后打标签、再自动构建、最后分阶段提交包管理器"的可复制发布流水线,并理解其背后每一条守门(gate)的设计动机。

发布总览:一个三层守门的流程

Clawd 的发布不是"打一个 tag 就完事",而是由三层相互独立的守门构成:

  1. 打标签前的本地守门:版本一致性、发布说明文件、资源预算、官方主题快照,全部通过后才允许手动触发 Build & Release 工作流;
  2. 构建产物守门:手动工作流构建 Windows/macOS/Linux 三平台产物,逐一检查解包后的资源树、Koffi 原生负载、打包后正向调用冒烟与更新元数据,但不创建 GitHub Release;
  3. 发布后守门:推送 v* 标签后再次走同一构建流程,创建 draft Release,经人工下载冒烟后手动发布;发布动作再触发 .github/workflows/winget.yml,生成并校验 WinGet 清单,按配置决定是否向 microsoft/winget-pkgs 提交 PR。

关键原则在文档开篇即被强调:草稿发布(draft release)不面向普通用户,也不被应用内更新器消费;只有在手动构建产物与草稿资产都通过检查后,才应发布草稿。若任何 required 检查失败,修复问题并新建草稿,绝不发布已知有缺陷的草稿。

打标签前的准备(Before Tagging)

1. 版本号与发布说明文件

发布第一步是更新 package.json 到目标发布版本,并在 docs/releases/ 下新增 release-vX.Y.Z.md 发布说明。这一步有两个容易踩的坑:

  • .gitignore 白名单:由于 docs/releases/ 目录整体被忽略(参见 .gitignore 中的 !docs/releases/ 规则),必须在 .gitignore 中为每个新版本追加一行形如 !docs/releases/release-v1.2.0.md 的排除规则。缺少该行时,git add 会静默跳过发布说明文件,而 CI 的 validate-release 守门会因此跳过全部三个平台的构建——发布说明文件与版本号成了硬绑定。
  • 版本一致性契约:npm run verify:release 由两个脚本组成(见 package.json 的 scripts 字段):
    • scripts/verify-release-version.js:校验 package.json 的 version 符合 MAJOR.MINOR.PATCH 语义化格式、与 package-lock.json 顶层及根包版本一致、对应 docs/releases/release-v{version}.md 存在,且若在 tag 环境运行,GITHUB_REF_NAME 必须精确等于 v{version};
    • scripts/verify-release-contributors.js:通过 git log 扫描上一版本 tag 到 HEAD 之间的提交者与 Co-Authored-By 身份,映射为 GitHub 账号,并断言每个外部贡献者都出现在 Settings About / README 的贡献者列表中(以 src/settings-i18n.js 中 CONTRIBUTORS 数组为准)。

2. 本地测试命令

文档给出打标签前的完整本地验证命令序列:

npm run verify:release
npm test
npm run audit:assets

其中 npm test 会经 test/run-tests.js 运行全量测试套件;audit:assets 由 scripts/audit-repository-assets.js 执行,用于守住仓库跟踪树(tracked-tree)的资源预算。范围变更较小时也可以只跑与改动匹配的本地测试。

3. 官方主题快照与资源预算

官方可下载主题(如 Hash Sage 与 Whale-chan)作为带版本号的 GitHub Release 资产,存放在独立的 rullerzhou-afk/clawd-themes 仓库中,永远不会打进 Clawd 安装包。因此在打标签前需要:

  1. 运行 npm run update:official-theme-snapshot 刷新内置的官方主题目录快照(实现见 scripts/update-official-theme-catalog-snapshot.js),快照有变化就提交该 diff;
  2. 确认打包资源中不含 themes/hash-sage/** 或 themes/whale-chan/** 负载;
  3. 确认 npm run audit:assets 报告的跟踪树预算在策略范围内。

在 PR 上,audit:pr-history-assets 守门(scripts/audit-pr-history-assets.js)还会额外证明大型官方主题媒体没有进入 PR 的可达历史。

4. macOS 签名与公证前置

macOS Developer ID 证书创建、App Store Connect Team API Key 配置、本地验证以及 GitHub Actions 的确切 Secret 名称,全部细节见 docs/guides/release-signing.md。红线只有一条:绝不把 .p12、.p8、证书密码或解码后的 Secret 文件提交进仓库。签名相关行为可归纳为四种组合:

Secret 配置状态 构建行为
五个 macOS Secret 全部配置 产出 Developer ID 签名并公证(notarized)的应用,随后挂载两个生成的 DMG,验证其中每个应用包
五个 Secret 全部缺失 仅手动 workflow_dispatch 可走 ad-hoc 验证路径(显式保留)
只配置了一部分 立即失败
推送 v* 标签 缺任一 Secret 即失败关闭(fail closed),绝不静默产出 ad-hoc 的 macOS 官方构建

5. 手动触发 Build & Release

一切就绪后,在 main 分支上手动运行 Build & Release 工作流。该工作流:

  • 构建 Windows、macOS、Linux 三平台产物;
  • 检查每个解包后的 resources 树中是否存在已退役的 Telegram sidecar 二进制/源码(见下文"退休 Telegram Sidecar 守卫");
  • 对每个安装包执行目标原生 Koffi 负载守门、打包后正向调用冒烟,以及更新元数据校验——更新元数据必须同时匹配生成的产物与 package.json 中的精确发布版本;
  • 上传安装程序与 JSON 证据清单;
  • 不发布 GitHub Release。

Koffi 原生负载契约

每个暂存应用必须恰好包含一个物理 Koffi 原生插件,位于:

app.asar.unpacked/node_modules/koffi/build/koffi/<target-triplet>/koffi.node

原生清单审计必须拒绝除 electron-builder 托管的 Windows resources/elevate.exe ia32 助手之外的所有异构架构二进制。此外有一条明确约束:不要在 afterPack 中重写 app.asar——electron-builder 在该钩子之前就已记录 ASAR 完整性,因此 Koffi 清理只允许做物理文件剪除(physical-file pruning),实现见 scripts/after-pack-koffi.js,配套测试见 test/after-pack-koffi.test.js。

退休 Telegram Sidecar 守卫

旧版 Telegram sidecar 已于 v0.14.0 移除。发布构建必须针对每个解包目标运行 scripts/assert-no-retired-telegram-sidecar.js:Windows x64/arm64、macOS x64/arm64、Linux x64。该脚本的实现要点:

  • 同时扫描外层 resources 树与真实 app.asar(通过 @electron/asar 的 listPackage);
  • 判定路径集合包含 sidecars/cc-connect-clawd(及其目录树)、任意 cc-connect-clawd(.exe) 文件名,以及四个已退役源码路径:src/telegram-approval-client.js、src/telegram-approval-sidecar.js、src/telegram-owner-manager.js、src/telegram-sidecar-status-bridge.js;
  • 任一命中即为硬失败,退出码 1 并输出 JSON 报告(含 schemaVersion、文件清单、错误列表与摘要)。

工作流层面还有独立的 .github/workflows/telegram-retirement-package-audit.yml 与之配套。

草稿发布(Draft Release)

手动构建产物检查通过后,创建并推送最终版本标签:

git tag vX.Y.Z
git push origin vX.Y.Z

推送 v* 标签会再次运行同一构建工作流,并用生成的安装程序与发布说明创建一个 draft GitHub Release。发布前必须下载 draft 资产并逐项冒烟;若草稿有误,先修复再发布。

v1.2.0 草稿冒烟检查清单

文档附带了以 v1.2.0 为例的完整检查清单(docs/releases/release-v1.2.0.md 记录了该版本的真实验证结果)。规则是:使用草稿安装包/产物而非 npm start;Windows required 项是首要发布门槛;macOS/Linux 真机不可用时,在发布说明中明确记录"未在真机验证"。

启动前的准备工作

  • 下载被测平台的草稿资产;
  • macOS 上通过浏览器下载每个 DMG(携带 quarantine 元数据),确认无需"隐私与安全"覆盖即可打开,随后按签名指南用 spctl 与 stapler 验证拷贝的应用;
  • 确认打包应用显示 1.2.0 元数据;
  • 确认打包资源包含 app.asar.unpacked/hooks、app.asar.unpacked/agents、app.asar.unpacked/extensions、app.asar.unpacked/themes(对应 package.json 的 asarUnpack 配置);
  • 确认退役断言通过,sidecars/cc-connect-clawd 与 cc-connect-clawd(.exe) 均不存在;
  • 确认 Windows 产物是架构专属的 x64 / ARM64 安装程序,而非 universal NSIS;
  • 下载 native-package、Koffi prune/smoke、updater metadata 三类清单,确认目标恰好有一个匹配的 koffi.node、无外来原生负载、无未审查例外,且每条 updater metadata 的 version 与所列文件名都指向 1.2.0;
  • 迁移冒烟:先安装 v0.16.0 并保存旧 clawd-prefs.json 副本再升级;
  • 旧版飞书/Lark 迁移冒烟:在 v0.15.0 中以已保存的 App 凭据与审批人启用远程审批,升级时保留旧 feishu-approval.env 与 prefs 副本;
  • Reasonix 冒烟:准备一台已初始化 Reasonix 的机器(<Reasonix home>/ 存在;Windows 为 %APPDATA%\reasonix,macOS/Linux 为 ~/.reasonix)。Reasonix 缺失导致的安装跳过无法验证打包后的 hook 路径;
  • Remote SSH 冒烟:至少准备一个可通过 SSH 反向隧道连接的已保存 profile。

全平台 required 检查(节选关键项)

  • 全新安装:启动后宠物出现、无错误对话框;Footprints 默认启用,Today/Week/Month/Year 显示本地已接受活动与覆盖率,不支持指标保留为 -,不向存储写入内容或原始标识符;录制关闭/开启与待定完成期间的清理不得让旧计数复现,正常完成动画仍工作;恢复/锁定的偏好必须明确报告"录制已暂停",直到显式、被许可的 Settings 操作恢复它。
  • 时区:录制后向西移动系统时区,Today/Week 中冻结本地小时内的记录活动与覆盖率仍可见。
  • 权限溢出队列:以足够多的请求溢出小屏显示,演练队列加载/ACK 失败与原生窗口钳制;Allow/Deny 快捷键不得对部分裁剪或隐藏的目标做决定。
  • Codex 回合围栏:结束回合 A 后让延迟问题/输出到达 JSONL 监视器,它不得复活 A 或延长回合 B;真实当前回合问题仍保持活动任务存活;升级一个带长通用工作超时且无 Codex 专属值的 profile,需保留其此前有效的 Codex 时长。
  • 升级安装:覆盖 v1.1.0 升级启动无错误,既有 agent 安装/启用标记与用户主题/动画选择保持不变。
  • 版本与教程:Settings -> About 显示 v1.2.0(来源为 app.getVersion());全新 profile 首次运行教程只打开一次,Finish/Skip/OS 关闭均持久化 tutorialSeen=true 且重启不再打开;无 tutorialSeen 的 profile 升级后只见一次教程。
  • 偏好恢复:将 clawd-prefs.json 临时设为不可读并启动一次,启动警告与 Doctor 关键项都必须说明"agent 事件与审批已暂停";恢复访问并重启后继续。将 prefs 替换为截断 JSON 后启动,原始字节必须保留在 clawd-prefs.json.bak 中,启动与 Doctor 说明本次恢复的默认值非权威,所有 agent 事件/权限/同步门保持关闭直到 Settings 被审查并重启;再用路径冲突阻止 .bak 创建,确认主文件字节级不变、Settings 写入锁定、启动/Doctor 报告备份失败而不声称存在备份。
  • Hook 与真实会话:重装一个既有 hook 类 agent(如 Codex)确认打包 hook 脚本能 require() 依赖;运行一次真实 Claude Code 或 Codex 会话,确认宠物对状态变化有反应、Stop 时仍播放完成动画;完成的回合使用独立的默认完成音效而非普通确认音效。
  • OpenCode:运行真实 OpenCode 会话经历标题重命名、工具活动与 SessionEnd;HUD/Dashboard 显示有界标题、保持因果顺序,慢端点后不重放陈旧状态;OpenCode 运行中停止 Clawd 触发权限请求,插件必须把决定留在 OpenCode 原生 UI,不得把反向桥接凭据 POST 给 Clawd 端口范围内的其他监听者;Claude 会话活跃期间重启 Clawd 后让真实 hook 恢复并结束,Dashboard/HUD 全程保持唯一规范会话并无重复/幽灵恢复行。
  • 配件与动画:在 normal/interrupt/sleep/idle/reaction/mini 动画上演练手动配件;Animation Map 覆盖必须保持衣柜可用;无安全几何的帧只隐藏该帧配件;切换节日选项确认临时覆盖再恢复保存的手动配件。
  • 审批模式:演练 Ask every time、仅 Question prompts 与全局/会话级 Auto-approve;需要时确认门出现,非值守运行时提权在重启后降级。
  • 配额数据:从本地与 Remote SSH 源喂入 Claude 与 Codex 配额数据,确认各来源值出现在 Dashboard 与可配置的宠物 Orbit 环中,跨机器合并可开关,第三方 Claude statusline 被占用时保持原样直到显式启用链式。
  • 长 CJK 完成:触发长中文 CJK 的 Claude/Codex 完成,确认 Stop 事件无 413 到达且完成动画不丢失。
  • Hook 健康:Codex 官方 hook 禁用/未审查时 Agents 徽标或启动提示报告需关注,修复/审查后恢复健康;Claude hook 健康:删除一个受管 hook 脚本并原子替换 settings.json,确认 watcher/周期审计修复受支持损伤,而仍然缺失的声明核心事件绝不报告为成功的 Fix。
  • 自定义 HTTP agent:注册两个自定义 HTTP agent 并让两者发送相同原始 session_id,Dashboard 保持独立会话;禁用/删除其一后另一个保持完整;伪造/陈旧 custom- id 必须被拒绝。
  • WorkBuddy / MiMo / MiniMax:WorkBuddy 针对当前 ~/.workbuddy-ai/settings.json 路径安装,状态+Notification 事件到达且 Clawd 不接管审批;MiMo Code 装入带注释/尾逗号的 JSONC 配置,演练 Allow/Always/Deny 与 DND 回退,卸载后用户配置保留;MiniMax Code 通过 mcode plugin enable clawd-state@local 或插件面板安装/启用/卸载,状态事件到达、无 Clawd 权限气泡、无关插件保持完整。
  • OpenCode 2.x 打包验收:双键注册、idle→thinking→working→attention 状态流、Allow/Deny/同会话 Always/auto-tools 的阻塞气泡往返;带待决审批的会话被中断时气泡撤回;演练 a && b 复合 shell 命令并确认破坏性操作提醒与警告徽标逐条检查;opencode web / serve --hostname 0.0.0.0 回包路径经 loopback 到达宿主;Clawd 端点缺失时决定留在 OpenCode 原生 UI。该项扩展了 2026-09-24 的 macOS 源码/真机 v2.0.15 检查,打包资产仍需独立抽查。
  • 宿主版本控制:确认 v2 写 plugins、v1 只移除 Clawd 拥有的 v2 条目、未知宿主不动 plugins;CLAWD_OPENCODE_HOST 可从失败的主机检测中恢复,然后移除覆盖。OpenCode 2.x 更新插件后需运行 opencode service restart(仅开新会话可能沿用旧共享服务);1.x 重启 opencode。
  • Windows 打包 OpenCode 验收(#1026,需真实 opencode 1.18.31):安装 Program Files 版 Clawd,确认 opencode 配置指向 %USERPROFILE%\.clawd\integrations\...\generations\<hash>\opencode-plugin(绝不指向 app.asar.unpacked),受管五文件生成字节/哈希与打包源码匹配且无 deny-write ACL;真实会话确认每次交互恰好一个 Clawd 状态流与一个权限请求(无重复条目双重加载);重启 Clawd 两次确认启动同步幂等;修复单个遗留/缺失遗留条目并确认就地迁移、源不动;布置被修改的类 Clawd 副本确认 Install/Repair 失败关闭、Doctor 显示需审查且无 Fix;卸载后确认 proven-owned 条目消失、Settings 显示未安装/禁用、第三方插件/元组/选项不变、残留生成文件不再发事件。源码级测试不满足本项。
  • Windows 安装路径:Settings -> Agents 在路径含空格时安装 Reasonix 成功,需要时写入命令使用 EncodedCommand 路径;TraeCode 在 Node 位于 C:\Program Files 下安装、在 Trae CN 中启用 Sandbox 模式 hooks,全部六种事件类型退出码为 0,卸载后六个编码受管条目全部移除;REASONIX_HOME 指向未解析变量时安装/同步失败关闭且不在启动目录写 settings.json。
  • ZCode / QwenWork:ZCode 生命周期事件与真实 PermissionRequest 到达 Clawd,手动 Allow/Deny 后无决定回退到 ZCode 原生权限流、权限自动化保持不可用;从 Orca 窗格跳回会话并确认验证过的窗格键在本地与受管 Remote SSH 上聚焦正确窗格;QwenWork 在 Windows/macOS 安装后生命周期状态到达、PermissionRequest/PermissionDenied 保持仅观察、卸载只移除 Clawd 受管 hook 条目。
  • Remote SSH:带 connect-on-launch 的 profile 启动后连接;本地端口 23333 被占用时服务器绑定后续端口且隧道仍指向真实绑定端口;升级仍带旧 Codex 监视器 PID 文件的 Remote SSH 目标时部署/清理无 shell bad substitution;revoke-all 使当前与先前路由 nonce 同时失效;profile 隔离的普通编辑保留其运行时模式/键/布局。
  • Telegram 退役迁移:升级使用已退役 Telegram sidecar 的 profile,一次性启动提醒指向 Settings -> Remote Approval,保存的 token/recipient 值保留,审批与完成通知保持禁用直到真实原生验证回调成功;失败/超时不得重启已退役 sidecar。升级旧 v0.15.0 飞书/Lark profile:遗留配置保持失败关闭、一次性启动警告指向 Remote Approval、Doctor 报告绑定问题;重选平台与 App ID/App Secret 后重存审批人,重启后客户端就绪且不再警告。
  • DeepSeek Harness:受管根经文件系统符号链接到达时安装与 Doctor 都报告验证过的 generation 健康;同名外来包仍失败关闭;检查 0.1.5-rc.1 与 rc.3 的会话标题与上下文用量。桌面应用各在 macOS 与 Windows 上演练一次:双载体存在时安装并确认 "installed in desktop" 提示、Doctor 一行双端、真实桌面会话各一次 Allow/Deny、插件更新(generation 变更)后出现 "restart desktop" 提示、桌面重启后会话继续工作且提示仅在点击 "Got it" 后消失、DND 开启时桌面应用显示自己的审批对话框。
  • 破坏性操作提醒:开启后演练 auto-tools 与非值守下的识别破坏性命令,每一条必须暂停等人而非自动允许;关闭后恢复常规策略。含 git commit -m "$(cat <<'EOF' ... EOF)" 且正文奇数引号数或 (#N) 的命令不被拦截;普通 cat <<EOF heredoc 同正文则按文档保守拦截并显示 Settings 说明。
  • 通知与生命周期:发送者忙碌时排队 Slack 通知,无一丢失且权限警报走独立通道;Claude 回合结束后送达尾随 SubagentStop,完成动画与通知保留;空闲 Claude 会话重启 Clawd、正常结束回合(含后台工作)后重启,均不得回到 working 或 interrupted;Codex 记忆整合不出现 memories worker 卡片;既有导入 Codex Pet 从 v1.1.0 升级后杂耍姿势刷新一次且不丢导入主题;Discord Rich Presence 不开动画镜像时粗粒度状态文本稳定,开启镜像后受支持动画用仓库托管的 GIF,关闭后回到基于状态的呈现。

全平台 recommended 检查

  • 自由漫游(free roam):启用、等待空闲、确认移动、hitbox/HUD/气泡对齐,鼠标移动/状态变化/拖动/mini 模式/DND 时取消;轴关闭/水平/垂直与有效/无效/缺失围栏输入下目标可达且在屏内,无效输入安全回退。
  • 眩晕旋转(dizzy spin):Clawd 主题下快速环绕光标触发眩晕;Calico/Cloudling 主题下无不受支持状态的闪烁。
  • 低功耗空闲模式:验证 sleeping/Cloudling 静态睡眠行为,HUD 可回收/重开且无空白表面。
  • Whale-chan:从 Settings -> Theme 下载、安装、选择、卸载;确认主题不在打包资源中,其许可/署名从单独下载的主题包中可见。
  • Mini peek:开启 hold 与 sleep peek,确认在目标状态边界出现;selectable-only 空闲视觉仅在选择后出现;内置 Clawd 主题空闲气泡在左右半屏播放并确认右侧镜像;再以主动选择的空闲动画重复。
  • 其他:右键 Hide pet / Show pet 仍工作,隐藏期间新到达的权限请求按设计仍显示气泡;Settings -> About -> Check for updates 无错误;更新标签无 vv1.2.0 重复前缀;Telegram 审批卡对 Telegram 内与别处解决的审批显示最终结果;手机扫码移动 PWA 配对 URL 出现会话卡;重置/重新生成移动 token 后手机可用新 token 重连。

Windows 检查(required)

  • 运行真实打包 OpenCode 1.18.31 与 2.x 会话:v1 plugin 与 v2 plugins 注册、每次交互一个状态流与一个权限请求、Allow/Deny/Always 决定、中断清理、复合命令警告、卸载保留;包含代码页 936 下非 ASCII 字符的宿主路径检测;2.x 另跑 opencode service restart;记录确切 2.x 版本与 macOS v2.0.15 源码检查的打包差异。
  • 以 CC Switch 的方式顶替 Claude 受管 hooks,观察 Agents 注意徽标与暂停修复原因、重复修复失败或缺脚本;确认修复暂停的一次性托盘提示与修复验证后的健康徽标。
  • WSL agent 会话的 PID 不得在 Windows 宿主上被探测或被别名为无关本地进程。
  • 全屏自动隐藏:进入全屏应用并发新权限请求,本地界面保持隐藏;退出全屏只恢复仍待决的请求;手动 Hide pet 对新请求保持独立行为;远程审批与配置的自动关闭仍工作。
  • 冷启动打包应用两次(保存升级位置),首次渲染的宠物视觉必须出现在该位置,无需 "Bring Pet to Primary Display / 将桌宠拉回主屏"。
  • 全屏/无边框游戏或视频冒烟:overlay 模式开启时宠物浮于全屏应用之上,点击/拖动宠物不得将应用踢出全屏。
  • 锁定/睡眠/恢复或显示唤醒冒烟(低功耗空闲开启):渲染器报告唤醒恢复后视线追踪应恢复。
  • 拖拽文件夹到宠物上打开该目录终端;右键 New Session 启动 Claude Code 无 0x800700c1;Windows Terminal 下提交提示无可见 PowerShell 闪烁;cloak/sleep/display-wake 恢复后宠物与托盘图标恢复且无瞬时尺寸跳动。
  • recommended:焦点跳转指向正确终端;重启后宠物恢复保存位置,DPI/显示比例变化后 Keep size 不跨屏放大。

macOS 检查(required,真机可用时)

  • 手动将签名 v1.2.0 DMG 覆盖安装到 v1.1.0 一次,保留应用数据;在每个可用架构上从可更新构建验证签名 A→B 更新对,含 Restart Now 与 Later/退出/重开;记录确切版本与资产哈希。源码运行或模拟更新器不满足此门槛。
  • 切换 menu-bar 与 Dock 可见性、重启,两项偏好持久且 Settings 仍可重新获得焦点;测试 Dock 左/右/下与自动隐藏,物理边缘固定跨显示器保持在屏内;Ghostty 跨 Space 焦点切到目标 Space 而不把 Ghostty 窗口拽回当前桌面;以 Ctrl+Shift+Y 或 Ctrl+Shift+N 回答权限后焦点不被抢回 agent 终端;在权限/诱导气泡中编辑文本时宠物落到输入表面之后、IME 候选窗口保持可见,结束编辑恢复静止行为;Clawd 置于后台后各点击一次 Settings 与 Dashboard,首次点击必须到达页面;Remote SSH 监控在 Codex Desktop 线程中重启并重放真实回合切分回放,每线程一张卡、无虚构空闲行、无已结束回合复活为 working,并记录完整 SSH 部署/隧道/审批路径是否被测试。
  • recommended:跳回会话恢复最小化终端窗口;拖文件夹到宠物不打开终端且不崩溃(macOS 上有意禁用)。

Linux 检查(required,真机可用时)

  • Wayland 会话成功启动,可用时经 XWayland 重新启动;宠物透明与定位正常;MiMo JSONC 安装/卸载在 POSIX 文件系统上保持可执行位与保留注释的写入正确。
  • recommended(tmux 用户):焦点跳转到正确 tmux 窗格。

门槛规则:所有 required Windows 项在发布草稿前必须通过;macOS/Linux 的 required 项在对应机器可用时必须通过;任一项失败就修复并新建草稿。

WinGet 发布:从 release 事件到清单提交

发布草稿会触发 .github/workflows/winget.yml。该工作流分为 prepare 与可选的 submit 两个 job,设计目标是"生成与提交分离,令牌最小化":

  • prepare job 只用环境自带的只读 GITHUB_TOKEN。它用 Komac 生成清单、规范化 locale 元数据、校验完整生成树,并上传"恰好可以提交的那四个文件"作为 winget-generated-manifest 工件。
  • 可选 submit job 只有在仓库变量 WINGET_AUTO_SUBMIT 等于字符串 true 时才运行(GitHub 表达式比较不区分大小写)。只有最后一步拿到存储在 winget-submit 环境 Secret WINGET_TOKEN 中的经典 PAT。工作流的 ambient GITHUB_TOKEN 保持只读;PAT 则独立承载其所有者的全部权限,因此专用账号是最小爆炸半径的配置。

submit 会重新下载并重新校验工件,检查版本不在目录中或未在开放 PR 中,然后提交四个已验证文件且不让 Komac 重新生成。它只打开 PR:Microsoft 校验、审核员批准、合并、目录发布与原生 Windows 验收仍是外部门槛。

工作流必须已在 main 上

一个容易遗漏的部署事实:发布工作流必须在创建 tag 之前就已经存在于 main。对 release 事件,GitHub 从被 tag 的 ref 读取工作流定义,因此在文件落地前切的 tag 永远无法触发它——包括 2026-08-02 发布的 v0.14.0。工作流对工具链显式 checkout 默认分支,再通过 API 读取目标 tag 的 package.json 并以 --package-json 传入:如果没有显式 ref:,actions/checkout 会取触发运行的 ref(release 事件下即 tag,而旧版本的 tag 中根本没有这些工具)。两者分离使工具链保持最新,同时安装程序文件名仍绑定在真正产出发布资产的树上。

为什么提交是分阶段的

komac update 并不会把给定的 URL 变成安装程序条目:它读取上一个清单并按上一版条目逐条输出,将每条匹配到最佳新安装程序(Komac 源码 src/commands/update_version.rs -> src/match_installers.rs,迭代 previous_installers)。因此传入正确的 URL 并不能产生正确的清单——如果上游形态错误,Komac 会忠实复刻它。

历史背景:上游修复前,v0.14.0 清单有两个条目且都是 Architecture: x64——那两条其实是用户/机器作用域拆分(携带 /currentuser 与 /allusers),而非两种架构。对一组正确的新安装程序打分时,x64 安装程序得 8 分、arm64 得 6 分,导致两个旧条目都选走 x64、arm64 安装程序被丢弃。

该清单经 microsoft/winget-pkgs#416019 手工修复后,现役形态是四条而非两条:

Architecture Scope Installer Custom
x64 user ...-x64.exe /currentuser
x64 machine ...-x64.exe /allusers
arm64 user ...-arm64.exe /currentuser
arm64 machine ...-arm64.exe /allusers

塌缩成两条会丢掉 NSIS 安装程序支持的按用户/按机器选择(package.json 的 build.nsis 设置 oneClick: false 且无 perMachine)。

分阶段计划

  1. 仅 prepare 的管道——完成:托管运行成功演练了工作流、令牌、安装程序下载与工件路径;对当时损坏的上游清单复现了 komac 错误的双 x64 输出,印证了为何自动提交必须保持禁用。
  2. 修复上游——完成:microsoft/winget-pkgs#416019 将 v0.14.0 修复为上述四条,许可改为 AGPL-3.0-only,通过完整校验管道并于 2026-08-17 发布;竞争性 Dumplings 追踪器也已移除。
  3. 校验 Komac 输出——完成:生成输出守门解析 YAML 并断言包标识符/版本;精确的 {x64, arm64} x {user, machine} 集合;每条目的 URL、SHA256 与 Custom 开关;InstallerType: nullsoft;UpgradeBehavior: install;顶层 InstallerSwitches.Upgrade: --updated;ProductCode 3e932233-a8b2-5530-b285-e0ceb08488f2 同时出现在 installer 与 AppsAndFeaturesEntries 层。locale 清单必须携带 License: AGPL-3.0-only 与版本固定的 LicenseUrl/ReleaseNotesUrl。由于 Komac 会用仓库当前 licenseInfo.spdxId 覆盖 License(GitHub 报告为 AGPL-3.0,而非 package.json 中的 AGPL-3.0-only),守门会先重写再断言这些字段,而非接受原始输出。它只在完整树通过后写入规范化,输出 SHA256 证据报告,拒绝不受支持的根/嵌套键,且字节级幂等。提交过程在拷贝文件前会对照该报告重算全部四个哈希。
  4. 启用提交——已实现,待配置启用:工作流拆分为 prepare/submit,所有第三方 uses: 均固定到提交 SHA,PAT 只存在于最终提交步骤。账号、Secret 与启用变量作为一次独立的仓库配置变更设置(见下文)。

为什么安装程序文件名是一份契约

electron-builder 对 x64 与 arm64 目标都发出 32 位 x86 NSIS stub,因此 PE 头检查对两个安装程序都报告 x86。Komac 从 URL 解析架构并让该值覆盖任何二进制分析结果,所以 build.win.artifactName 中的 ${arch} 令牌是发布出去的唯一正确架构信号——正如 package.json 中的 "artifactName": "Clawd-on-Desk-Setup-${version}-${arch}.${ext}"。

npm run verify:winget-arch 强制这一点(实现见 scripts/verify-winget-arch-contract.js)。它移植了上游 Architecture::from_url 的定界符算法(右起最远的定界匹配优先、再按名称最长者优先),并在以下任一情况让发布失败:文件名无法再解析为构建架构、两个目标塌缩到一个架构、发布集合不是精确的 x64 与 arm64、文件名不再匹配工作流的 INSTALLERS_REGEX(^Clawd-on-Desk-Setup-.*-(x64|arm64)\.exe$)、发布 tag 与 package.json 不一致、或两个安装程序共享同一摘要。该守门校验的是 Komac 的输入而非输出;独立的 verify:winget-manifest(scripts/verify-winget-generated-manifest.js)在工件上传或提交 job 启动前校验并规范化生成 YAML。

这一守卫存在的直接原因:曾经拥有该清单的第三方 bot 只转发第一个匹配的 .exe。单 Windows 安装程序时无害;但从 v0.6.2(2026-04-27,首个并排发布 -x64.exe 与 -arm64.exe 的版本)到 v0.14.0——12 个版本——原始清单都声明两条 Architecture: x64 条目且都指向 arm64 安装程序。NSIS stub 能在 x64 上运行,安装报告成功,应用随后却无法启动。正是这段历史把"文件名即契约"写进了发布流程。

为什么直接调用 komac

显而易见的替代品 winget-releaser 是一个 composite action,其自身步骤会运行 cargo-bins/cargo-binstall@main(可变分支引用)与 cargo binstall komac -y(未固定构建)——两者在同一 job 中,且都在会接收 PAT 的步骤之前。即使把该 action 固定到提交 SHA,也只是冻结了包装器而冻结不了那两条链接。因此工作流自己从发布归档安装 komac,其 SHA-256 在 env 中固定(KOMAC_VERSION: "2.16.0"、KOMAC_SHA256 指向精确校验和)。升级 KOMAC_VERSION 必须在同一编辑中升级 KOMAC_SHA256,该校验和在 test/winget-arch-contract.test.js 中被断言。工作流使用的所有第三方 Action 都固定到完整提交 SHA;更新 Action 时解析并审查新 tag 目标并显式修改 SHA,不要换成可变的大版本标签。

可选自动提交设置

要启用自动提交,需要一次独立的仓库配置变更:

  1. 选择提交账号。优先专用低权限账号(其 PAT 也可写本源码仓库);所选账号的 winget-pkgs 仓库必须是 microsoft/winget-pkgs 的 fork,且账号需完成 Microsoft 的 CLA。
  2. 创建 winget-submit GitHub environment;只有希望在每次开 PR 时强制人工门时才加 required reviewer;保持任何 deployment-ref 规则与发布 tag 兼容。
  3. 为该账号创建带 public_repo 作用域的经典 PAT,存为 winget-submit environment 的 WINGET_TOKEN Secret,不要存成仓库级 Secret。fine-grained token 能写 fork 却无法打开针对上游仓库的必需 PR。
  4. 设置必需仓库变量 WINGET_FORK_OWNER 为提交账号登录名——工作流故意没有 owner 回退。
  5. 确认被移除的 SpecterShell/Dumplings 追踪器未回归后再启用提交。
  6. 设置仓库变量 WINGET_AUTO_SUBMIT 为 true(比较不区分大小写);移除或改为其他值会让工作流回到仅 prepare-and-upload 模式而不删除 Secret。

每次发布检查

  • 确认 prepare 同时通过 verify:winget-arch 与 verify:winget-manifest;上传的 winget-generated-manifest 工件是规范化、校验过的四文件树加证据报告。
  • 手动或自动提交前,将提交用 winget-pkgs fork 与上游 master 同步(例如 gh api -X POST repos/<fork>/merge-upstream -f branch=master)。上游前进后,陈旧 fork 的浅克隆无法推送提交分支。
  • 自动提交关闭时,从那个精确工件打开单版本 PR;启用时确认 submit job 报告新 PR URL 或刻意的 already-published / open-pull-request 跳过。
  • 跟踪 Microsoft 的校验、审核、合并与目录发布结果——Clawd 工作流成功或 PR 打开本身并不发布版本。
  • 历史 v0.15.0 locale 链接曾指向 v0.14.0;输出守门会把两个链接都重写到当前发布 tag,切勿手工复制旧版本元数据。
  • 目录刷新后,先在独立 Windows 机器上做一次 winget install 或 winget upgrade 冒烟,再把命令写进 README。

发布相关测试与产物对照

发布流程在仓库中留有完整的测试与证据链,可进一步深挖:

综上,Clawd 的发布流程把"人肉冒烟"与"机器守门"织成了一张网:版本与贡献者契约保证发布内容一致,打包审计保证原生负载与退役组件零泄漏,草稿发布保证坏包不上架,WinGet 分阶段机制则保证包管理器清单即使面对损坏的上游历史也不会静默发错架构。这套流程的任何一环都可以直接移植到其他 Electron 桌面应用的发布管道中复用。

登录后查看全文
clawd-on-desk