跳转到内容

解释 Global Pet Assistant 如何用原生 macOS 运行时、事件路由和动作系统组成桌面助手。

GlobalPetAssistant 的核心不是“桌面宠物动画”,而是一个本地事件运行时。宠物只是可视化表面;真正的系统边界是本地事件协议、事件路由、状态机、动作执行和原生 macOS 呈现层。

它的关键设计是把 Codex、Claude Code、CI、脚本和本地应用都降级为普通事件源。每个事件源只负责描述“发生了什么”,不直接控制窗口、动画或文件打开行为。

系统可以按五层理解:

Codex / Claude Code / CI / local scripts
-> localhost event API / petctl
-> EventRouter + priority queue
-> PetBehaviorController + ActionHandler
-> AppKit NSPanel + Core Animation sprite renderer

每层只处理一个问题:

  • 事件源:把外部生命周期转换成统一事件。
  • 事件接口:接收 JSON,做大小限制、速率限制和动作校验。
  • 路由层:按来源、去重键、TTL 和优先级维护当前活动事件。
  • 行为层:把基础事件状态和用户 hover、click、drag 等临时交互合成最终动画。
  • 呈现层:用原生透明浮窗和 spritesheet 动画把状态显示到桌面。

实现选择 Swift Package + AppKit,而不是 Electron。AppKit 负责应用生命周期、菜单栏、透明 NSPanel、多屏幕位置、边缘吸附、右键菜单、启动登录项和窗口持久化。

动画由 Core Animation 驱动。宠物资源被预加载并切成帧,运行时只切换图层内容,不在动画循环里解码图片。这使桌面常驻进程的资源消耗和故障面都比较低。

资源层兼容 Codex pet atlas,但不依赖 Codex App 内部状态。当前约定是 8 列 x 9 行的透明 spritesheet,每一行对应一个动画状态,例如 idlerunningwaitingfailedreviewjumping

应用自己的状态目录是:

~/.global-pet-assistant/

宠物包优先从应用自有目录加载,再把 ~/.codex/pets 作为兼容导入或开发期 fallback。这个设计把“复用 Codex 资源格式”和“依赖 Codex 运行时”分开,避免桌面助手写入或假设 Codex 的内部状态。

EventRouter 是产品语义的中心。它不关心事件来自 Codex 还是 CI,只关心事件的来源、级别、目标状态、TTL、去重键和可执行动作。

路由规则的重点是解决多事件冲突:

failed > waiting > running > review > idle

同一个来源的新事件替换旧事件;相同 dedupeKey 的事件会被合并;过期事件自动消失;没有活动事件时回到 idle。因此宠物显示的是当前最需要注意的工作状态,而不是简单地播放最近一条消息。

事件状态不是直接绑定动画。PetBehaviorController 在事件状态之外再叠加桌面交互:hover、click、drag-left、drag-right 等临时行为可以短暂覆盖动画,但完成后会回到路由器选出的基础状态。

这个分层让宠物既能反映任务状态,也能保留桌面伴随感。failedwaiting 这类高优先级状态不会因为用户悬停或点击而永久丢失。

事件可以携带动作,例如打开 URL、文件、文件夹或应用。动作不由事件源直接执行,而是进入 ActionHandler 做来源策略校验:允许的 action type、URL host、文件根目录和 bundle id 都由配置决定。

默认策略偏保守:未知来源可以更新状态,但不能触发动作。这样第三方工具可以把状态推给宠物,但不能借宠物任意打开本机资源。

这个架构的长期价值是协议优先。只要本地事件接口稳定,渲染形态可以继续演进:气泡、尺寸控制、更多交互、Unix socket、全局 Codex hook 或更多 agent adapter 都不需要重写事件源。

因此 GlobalPetAssistant 更像一个本地的桌面状态总线,而不是某个工具的插件。