
字节笔记本
2026年10月24日 · 约 20 分钟读完
Agent 终于能操作你的 Mac 了,而且不抢你的鼠标
GUI(图形界面)四十多年,一直是给人设计的接口。
现在它正在变成 agent 也能直接操作的界面。
meng shao(@shao__meng)刚给 ZCode(就是你现在读这篇文章用的工具,@Zai_org 出品)装了一个叫 Cua 的插件(@trycua)。结果是,agent 现在能真正操作 macOS:截图、读无障碍树、点击、打字。 而且关键的是,它绕开了 Apple Events 的权限死结,改走 CGEvent + Accessibility API。
演示里,agent 自己算了个 7×6=42,又打开 Chrome 导航到微博和 X,就是发这条推文的浏览器。整个过程,用户的鼠标不动、焦点不切、Space 不跳。
这听起来像魔法。今天就来拆透,它是怎么做到的,为什么这件事重要。


ZCode 装 Cua 后,agent 直接操作 Chrome 打开微博和 X。
一、问题:为什么"让 agent 操作 GUI"这么难
先说清楚要解决的难题。
让 AI 操作电脑,最直觉的思路是,模拟鼠标键盘。macOS 提供了 CGEventPost,能把合成的鼠标点击丢进 HID 事件流(就是物理鼠标走的同一条管道),WindowServer 看到点击就移动光标。
但这有个致命问题:它会抢你的鼠标。
agent 一操作,你的光标就被拽走,焦点切到 agent 操作的窗口,如果你在别的 Space(桌面),macOS 还会把你拽过去。你正在打字写代码,agent 突然把你的鼠标抢走点别的窗口,完全没法用。
这就是为什么过去几年,绝大多数 GUI agent 产品都失败了。Cua 博客原话:
That constraint is exactly why... we've been recommending isolated VMs and GUI containers as a targeted action space for computer agents, and never evangelized our users to install the computer-server component directly on one's own desktop host.
(这个限制正是为什么……我们一直推荐用隔离的 VM 和 GUI 容器作为 agent 的操作空间,从不敢让用户把 computer-server 装在自己桌面上。)
换句话说,以前的方案是"给 agent 一个隔离的虚拟桌面,让它在里面操作"。但这意味着 agent 操作的不是你真实的工作环境,价值大打折扣。
真正的圣杯是:agent 在你真实的 Mac 上操作,但你完全不受打扰。 像"第二个隐形光标"。

二、Cua 的解法:后台 Computer Use
Cua 团队(trycua)做到了。核心产物叫 cua-driver,一个开源的 macOS 驱动,让任何 agent(Claude Code、Codex、ZCode、你自己的 harness)能在后台驱动任何 Mac 应用。
博客的 TL;DR 说得明白:
The user's cursor doesn't move, focus doesn't change, and macOS doesn't drag them across Spaces.
(用户光标不动、焦点不变、macOS 不会把你拽到别的 Space。)
它是怎么做到的?作者 Francesco Bonacci 逆向了 macOS 窗口管理的底层,挖出了一堆未公开的 Apple 私有 API。下面逐层拆。
三、技术拆解:四道关卡
关卡 1:点击不抢光标 , SkyLight 登场
失败尝试 A:CGEventPost , 丢进 HID 流,但会移动光标。❌
失败尝试 B:CGEvent.postToPid , 发给特定进程而非全局流,光标不跳。对大多数 app 有效,但对 Chrome 无效。Chrome 在渲染器 IPC 边界过滤合成事件,如果你的点击没有真实手势带的特定遥测数据(mouseEventSubtype 字节、clickState 计数器、私有 SPI 设的窗口局部坐标戳),渲染器当成"不可信"默默丢弃。❌
失败尝试 C:先激活目标、发 HID、再取消激活 , 能用,但这正是所有现有工具的做法,会 raise 窗口、拉 Spaces、抢焦点。正是要避免的。❌
真正的解法:SkyLight.framework 的 SLEventPostToPid。
SkyLight 是 Apple 未公开的 C 层框架,WindowServer 用它驱动屏幕上的每个窗口。SLEventPostToPid 签名和 CGEvent.postToPid 类似,但事件走完全不同的代码路径:一条经过 auth 签名的 SkyLight 通道,绕开 IOHIDPostEvent。
作者猜测:SLEventPostToPid 在事件记录里盖了个"源自 WindowServer 信任信封"的标记,Chrome 的过滤器读这个 bit 就放行了。这样点击直达目标进程,光标纹丝不动。
怎么找到的?作者 grep 了 SkyLight 的符号导出,它不出现在任何 Apple 头文件里。封装只用了 20 行 Swift,通过 dlopen + dlsym 加载 /System/Library/PrivateFrameworks/SkyLight.framework。
关卡 2:激活窗口但不 raise , 抄 yabai 的作业
光能发事件还不够,还得让目标窗口"AppKit-active"(能接收事件路由),但不能 raise(不能置顶、不能拉 Space)。
作者从 yabai(macOS 平铺窗口管理器)的源码找到了答案。yabai 有个 window_manager_focus_window_without_raise 函数,约 40 行 C,干的就是这事。
原理:AppKit 激活分两步,
- 一步告诉 app"你现在是输入路由的焦点 app"(通过
SLPSPostEventRecordTo翻转内部状态) - 另一步告诉 WindowServer"把窗口 raise 并 reparent 到当前 Space"(
SLPSSetFrontProcessWithOptions)
你可以只做第一步不做第二步。 yabai 这么干了好多年。Cua 抄了这个模式:两次 SLPSPostEventRecordTo 调用,目标就 AppKit-active 了,但窗口在 z-stack 里原位不动。
关卡 3:骗过 Chrome 的 user-activation gate , (-1,-1) 诱饵点击
即便用 SkyLight 把点击送进去了,Chromium 还有一道关卡:user-activation gate。如果渲染器最近没见到"可信用户手势",gate 就拒绝让点击触发视频播放、window.open、全屏 API。
解法是诱饵点击。 在 (-1, -1)(屏幕上所有窗口之外的像素坐标)发一对 LeftMouseDown/LeftMouseUp。Chromium 因为没有窗口认领那个坐标而丢弃这个点击,但 user-activation gate 仍然往前走一格。几毫秒后真正的点击到来,被当成那个手势的可信延续。
这是整个项目最难找的一块。作者读 content/browser/renderer_host 源码读到眼出血,才发现 (-1, -1) 这个 off-screen primer 能用。
关卡 4:键盘 , 意外地简单
键盘是无聊的故事。CGEvent.postToPid 就够,不需要 SkyLight。按键发给特定 pid,落到那个 app 的事件队列,不到别处。原因是 macOS 没有 Chromium 那种全局按键过滤器,app 接到什么就处理什么。
四、Electron 应用的特殊处理
大多数现代 app(Chrome、Slack、VS Code、Discord、Notion)都是 Electron。它们有个怪癖:窗口被遮挡时,无障碍树(accessibility tree)停止更新,因为 Blink 的无障碍代码短路了,以为没人在看。
公开的 AXObserverAddNotification 不标记观察者为"remote-aware",Blink 不知道有人在听。但另一个私有变体 _AXObserverAddNotificationAndCheckRemote 可以。一个 dlsym 调用,AX 树就在整个"启动-快照-行动-验证"循环里保持活跃,哪怕目标窗口被遮、在别的 app 后面、在别的 Space。
作者怎么发现的?对比 Accessibility Inspector 遮挡 Electron 窗口时的行为,发现 Inspector 的调用路径多碰了一个符号。
五、三种捕获模式(agent 怎么"看")
cua-driver 提供三种模式让 agent 理解屏幕:
| 模式 | 返回什么 | 适合 |
|---|---|---|
ax | 简化的无障碍树(Markdown 大纲,每个可操作节点带索引) | 系统 app(计算器、备忘录、iMessage)和 AppKit/SwiftUI app,不需要屏幕录制权限 |
vision | 目标窗口的 PNG | 视觉优先的 VLM,payload 最小最快,但 agent 要自己做空间推理 |
som(默认) | AX 树 + 截图(set-of-mark) | 树告诉 agent 什么可点,截图消歧义 |
默认 som 是因为,元素索引点击(click({pid, window_id, element_index}))是主寻址模式,直接触发底层 AX action,对隐藏/遮挡目标有效,不涉及坐标。像素点击(click({pid, x, y}))是 fallback,给 canvas/WebGL 这类非 AX 表面用。
六、推文提到的"绕开 Apple Events"是什么意思
meng shao 推文说"绕开 Apple Events 权限死结,改走 CGEvent + Accessibility API"。结合 Cua 的实现,这句话的意思是:
Apple Events 路线(传统 AppleScript 自动化)的问题,每次操作要弹权限确认,TCC(透明度同意控制)越来越严,很多操作被沙盒限制。
Cua 的路线,用 CGEvent(输入注入)+ Accessibility API(读 UI 树)+ SkyLight 私有 API(后台投递)。这条路:
- 不走 AppleScript,不触发 Apple Events 权限弹窗
- 用 Accessibility 权限(一次授权)读 UI 树、注入输入
- 用 SkyLight 私有 API 实现后台操作不抢光标
这是从"脚本化自动化"到"输入级自动化"的范式切换。 AppleScript 是高层语义操作("给张三发消息"),Cua 是低层输入操作("在这个坐标点一下、打这些字")。后者更通用、更像人、不依赖 app 暴露的脚本接口。
七、这能用来干什么
博客列了四个作者自己在用的场景,全部依赖"agent 像第二光标,而非取代第一个":
1. 真正的 dev-loop QA agent 跑"复现-修复-验证"循环,你在编辑器里继续打字。Claude Code 通过 cua-driver 驱动目标 app、读像素、读 AX 树、改源码、重建、查截图。你的 harness 从不失焦,滚动位置从不变。修复成没成,agent 告诉你。
2. 帮你发忘掉的消息 轻量个人助理,发条 iMessage、查日历、从邮件里抠快递单号。屏幕不变,操作静默发生。
3. 从你没在看的 app 拉视觉上下文 Claude Code 读 Figma 画布、读 Preview 窗口、读 YouTube 页面,都不把这些窗口带到前台。后台像素点击让 YouTube 全屏切换落在从未 raise 的窗口上。
4. 委托录制 demo 让 agent 去捕获那些你不想手动录的操作演示。
八、为什么"后台 Computer Use"是大事
拆完技术,这件事的意义其实超出"agent 能操作 Mac"本身:
1. 从"隔离沙盒"到"真实环境"
以前的 computer-use agent 都在 VM/容器里跑,碰不到你的真实工作环境。cua-driver 让 agent 在你正在用的真实 Mac 上操作,这意味着它能看你正在看的东西、操作你正在用的 app,价值量级完全不同。
2. 解决了"人机共驾"的核心矛盾
agent 自动化最大的障碍不是能力,是打扰。agent 一动就抢你鼠标,你没法同时干活。后台 Computer Use 让 agent 和人并行工作在同一台机器上,人打字、agent 操作别的窗口,互不打扰。这是"人机协作"从理论到实用的关键一跃。
3. 把 GUI 变成 agent 的原生接口
推文那句"GUI 四十年都是给人设计的,现在正变成 agent 也能直接操作的界面",点破了趋势。以前 agent 要操作 GUI,得"看截图猜该点哪"(视觉模型),现在它能读 AX 树(结构化)+ 注入输入(确定性),从"猜"变成"知道"。
4. 开源 = 让后台 Computer Use 成为基础设施
作者明确说:
A background computer-use driver should be a commodity, not a feature of one agent's product like OAI.
(后台 computer-use 驱动应该是基础设施,不该像 OpenAI 那样做成单个 agent 产品的特性。)
OpenAI 的 Codex 也有类似能力,但它是闭源的、绑定自家产品。Cua 把这套技术开源(MIT),让任何 harness(Claude Code、Codex、ZCode、你自己的)都能接入。这正是 meng shao 能把它接到 ZCode 上的原因。
九、和 ZCode 的关系(特别说明)
这条推文特别有意思的地方,它讲的就是你现在正在用的 ZCode 本身。@Zai_org 的 ZCode 通过装 Cua 插件,获得了操作 macOS 的能力。
这意味着:你和我协作的这个工具,现在能"看见"你的屏幕、"操作"你的 app。 这打开了一类全新的协作可能,
- 你说"帮我看看那个 Figma 设计稿",我能真的去读
- 你说"把那个 Excel 里的数字核对一下",我能真的去操作
- 你说"录一段我操作流程的 demo",我能真的去捕获
但同时也要清楚边界,Computer Use 涉及 Accessibility 权限和屏幕录制权限,是高敏感操作。任何 agent 用它操作生产环境、支付、敏感数据,都应该有明确授权。前面 CJ Zafir 那段 Codex 电池优化 prompt 说的"可验证可撤销",在这里尤其重要。
十、怎么用
装 Cua 到 ZCode
meng shao 演示的就是这条路,通过 ZCode 的插件机制装 Cua。具体安装方式以 ZCode 文档和 Cua 仓库为准。
直接用 cua-driver
如果你是开发者,想给自己的 agent harness 加 computer use:
git clone https://github.com/trycua/cua.git仓库里有完整的 SDK、driver、benchmarks、notebooks。libs/cua-driver 是核心驱动,blog/inside-macos-window-internals.md 是必读的技术深挖。
注意
- 需要 macOS Accessibility 权限(读 AX 树 + 注入输入)
- 用了 SkyLight 等私有 API,意味着未来 macOS 更新可能破坏它(私有 API 没有 ABI 保证)
- 目前是 v0.1 早期预览,生产环境慎用
十一、小结
一句话:Cua 让任何 agent(包括 ZCode)能在后台操作你的 Mac,点击、打字、读 UI 树,而你的鼠标不动、焦点不切、Space 不跳。它靠逆向 macOS 未公开的 SkyLight 私有 API 实现,开源免费。
这件事的深远意义在于,它把 GUI 从"人专用接口"变成了"人和 agent 共用的接口"。 四十年来 GUI 是为人的手和眼设计的,现在 agent 也能"原生地"操作它,而不是笨拙地"看截图猜"。
而 Cua 选择开源这一切,让这个能力成为基础设施而非某家公司的护城河,这大概是我们能在这个领域看到的最好的姿态。
meng shao 那句收尾值得记住:
GUI 四十年都是给人设计的接口,现在正变成 agent 也能直接操作的界面。
本文基于 meng shao(@shao__meng)的 X 推文及 trycua/cua 仓库整理。原文 https://x.com/shao__meng/status/2070502145208815755 ,仓库 https://github.com/trycua/cua 。核心技术细节来自 Francesco Bonacci 的博客《Inside macOS window internals》。ZCode 是 @Zai_org 出品。注意 cua-driver 使用了 macOS 私有 API,未来系统更新可能影响兼容性。
推荐配合阅读:前面写的 Clips/agent-native(同样是"agent 操作真实应用"的方向,但走 action 复用而非输入注入)、Anthropic 8 趋势报告(Trend 5"扩展到新场景"的极致体现)。


