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 动画把状态显示到桌面。
原生 macOS 运行时
Section titled “原生 macOS 运行时”实现选择 Swift Package + AppKit,而不是 Electron。AppKit 负责应用生命周期、菜单栏、透明 NSPanel、多屏幕位置、边缘吸附、右键菜单、启动登录项和窗口持久化。
动画由 Core Animation 驱动。宠物资源被预加载并切成帧,运行时只切换图层内容,不在动画循环里解码图片。这使桌面常驻进程的资源消耗和故障面都比较低。
宠物资源模型
Section titled “宠物资源模型”资源层兼容 Codex pet atlas,但不依赖 Codex App 内部状态。当前约定是 8 列 x 9 行的透明 spritesheet,每一行对应一个动画状态,例如 idle、running、waiting、failed、review、jumping。
应用自己的状态目录是:
~/.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 等临时行为可以短暂覆盖动画,但完成后会回到路由器选出的基础状态。
这个分层让宠物既能反映任务状态,也能保留桌面伴随感。failed 或 waiting 这类高优先级状态不会因为用户悬停或点击而永久丢失。
事件可以携带动作,例如打开 URL、文件、文件夹或应用。动作不由事件源直接执行,而是进入 ActionHandler 做来源策略校验:允许的 action type、URL host、文件根目录和 bundle id 都由配置决定。
默认策略偏保守:未知来源可以更新状态,但不能触发动作。这样第三方工具可以把状态推给宠物,但不能借宠物任意打开本机资源。
这个架构的长期价值是协议优先。只要本地事件接口稳定,渲染形态可以继续演进:气泡、尺寸控制、更多交互、Unix socket、全局 Codex hook 或更多 agent adapter 都不需要重写事件源。
因此 GlobalPetAssistant 更像一个本地的桌面状态总线,而不是某个工具的插件。