agent-desktop 实测:Vercel 给 AI Agent 的电脑操控 CLI,我在 M4 Mac 上跑通了输入、保存、关窗全链路

我的 Agent 已经能开无头浏览器扫网页(Lightpanda 那篇),能通过 MCP 管家里的灯(ha-mcp 那篇),能听我指着屏幕说话(Point and Yap 那篇)。但有一个能力一直空着:让它直接操作 Finder、TextEdit 这些原生桌面 App。截图方案太重,像素匹配太脆。11 月 13 日,Vercel Labs 给 agent-desktop 推了 v1.3.1——一个不截图、不匹配像素,直接读操作系统无障碍树的电脑操控 CLI。我当天在 16GB M4 MacBook 上把能测的都测了一遍,包括一条「写入文字→保存→关窗」的完整链路,也踩了三个值得写下来的坑。

它是怎么工作的

agent-desktop 的思路和浏览器里的 Playwright 很像:把目标 App 的无障碍树(macOS 走 AX API,Windows 走 UI Automation,Linux 走 AT-SPI)读成 JSON,每个可交互元素发一个 ref(格式 @快照id:元素号),Agent 拿 ref 去点击、输入、读状态,再开新快照验证结果。循环完全住在你的 Agent 侧,工具本体里没有模型。我用 GitHub API 核了一下它的底:23,304 star、Apache-2.0、2026 年 2 月创建,最新提交就在发版当天,本周涨了六千多个 star。

安装一行命令,原生二进制随 npm 包一起下来:

Bash
npm install -g agent-desktop
agent-desktop --version          # 1.3.1
agent-desktop status             # 查权限、平台、支持的 surface

第一道坎:权限,而且 status 会「说谎」

装完第一件事当然是 status。它返回三组权限:accessibility、automation、screen_recording。我第一次跑,accessibility 显示 denied——合理,WorkBuddy 这个宿主 App 没在系统设置的辅助功能名单里。但接下来发生的事情值得展开讲,因为它直接影响你怎么判断「这工具坏没坏」:

  • list-apps 不需要辅助功能权限,denied 状态下照样列出我机器上五十多个进程,名字、bundle ID、pid、前后台状态全有;
  • snapshot、screenshot、list-windows 全部吃权限墙。最意外的是 screenshot:我的 screen_recording 明明是 granted,它还是返回 PERM_DENIED: Accessibility permission not granted——截图在这套体系里被归进了辅助功能门下,不是单纯的屏幕录制权限;
  • 权限在系统设置里授权后立即生效,不用重启终端、不用重装,status 一刷就是 granted。授权这步只能人工点(agent-desktop permissions --guide 会帮你把系统设置开到对应面板),自动化脚本没法替你完成。

所以如果你的 Agent 反复报 PERM_DENIED,先看 status,再去系统设置 → 隐私与安全性 → 辅助功能,把运行 agent-desktop 的那个 App 拖进去。别急着怀疑安装出了问题。

外围命令:不开权限也有一半能用

权限还没通的时候,我把不依赖辅助功能的命令全跑了一遍,这些是真实输出:

剪贴板三连全部成功。clipboard-set 返回 delivered_verified(写入后读回来验证过),clipboard-clear 只给 delivered_unverified——清空没法验证,它就诚实标 unverified。这个信封里的 disposition.delivery 字段贯穿全部命令,是判断「动作到底落没落地」的唯一权威来源。

batch 能把多条命令打包成一次进程调用。我测了 set→get→clear 三条剪贴板操作,34 毫秒完成,上限 64 条、输入 1MB、输出 4MB。

launch 和 close-app 这对 App 生命周期命令也通了:launch TextEdit 返回 pid 和窗口元数据,close-app 走 graceful 终止,返回里带 evidence: process_instance_exited——进程真的退了它才说 verified。不过 launch 的窗口处理有个坑,后面细说。

权限通了之后:Finder 快照和一次「假失败」

权限生效后第一发就是 Finder。snapshot --app Finder -i --compact 返回 76 个可交互元素,侧栏每个收藏项都是 treeitem + cell 两层,各带 ref 和 available_actions。JSON 信封里 snapshot_id、ref_count、完整树一次给全,Finder 这样一个中型窗口压缩模式的原始返回实测 44.7 KB,直接塞进 Agent 上下文已经不算小,官方给的解法是先用 --skeleton 看骨架再 --root 钻取(这次没展开测)。

然后我点了侧栏的「下载」,这里出了全文最反直觉的一幕:click 命令返回失败——

Bash
agent-desktop click "@s1mw54drz52fhe:e16"
# ACTION_FAILED: AXOpen did not establish a verifiable effect
# disposition: delivery_uncertain, retry: unsafe

它说「效果无法验证,结果不确定,别盲目重试」。但紧接着的新快照告诉我:「下载」已经是 selected + focused 状态,ref_count 从 76 涨到 341——文件列表加载出来了,点击实际成功了。agent-desktop 的验证策略偏保守:AXOpen 拿不到可核实的回执时宁可报 uncertain 也不报成功。这其实是正确的设计(宁可假失败不可假成功),但如果你照着「报错即失败」的直觉去无限重试,就掉它给你标 retry: unsafe 的坑里了。正确姿势永远是:开新快照,用状态说话。

顺带两个错误行为的实测:光秃秃的 @e8 会被 INVALID_ARGS 拒掉(ref 必须带快照前缀),退出码 1;格式合法但从没快照过的 ref 会先撞 PERM_DENIED——AX 权限检查排在 ref 解析前面。

TextEdit 全链路:三个坑都踩在这里

权限通之后,我给自己出了一道完整的题:让 agent-desktop 往一个 txt 文件里写一句话、保存、然后关掉 TextEdit,最后用 cat 对答案。这一条链踩了三个坑,个个都有实测证据。

坑一:launch 打不开文稿窗口。 launch TextEdit --arg /tmp/agd-test.txt 返回 ok,pid 也有,但窗口元数据是 4×5 像素的幽灵窗口,文稿窗口根本没出现;加 --activate 等窗口,直接 TIMEOUT: CoreGraphics window inventory deadline elapsed。解法是绕开 launch 开文稿这一步:先用系统 open 命令把文件在 TextEdit 里打开,再让 agent-desktop 接管。另外后台打开的 App 还会 WINDOW_NOT_FOUND,要先激活(AppleScript activate 或 focus-window)快照才能匹配上窗口。

坑二:press 是全局键盘事件,打给的是前台 App。 我先 press cmd+s,返回 delivered_unverified(机制是 CGEventPost 物理注入)——但文件纹丝不动。一查前台进程:是 Electron,也就是 WorkBuddy 自己。快捷键被我的终端宿主吃掉了。正确顺序是先 focus-window --app TextEdit(返回里 is_focused: true 才算数),再 press。这条对所有键盘类命令(press、type 的物理路径)通用:headless 模式不会替你切前台,焦点管理是调用方的责任。

坑三:set-value 和 type 不是一回事,差别藏在文稿的「脏标记」里。 textfield 元素的可用动作是 SetValue、TypeText 两种。我先试 set-value --stdin,delivered_verified,get 回读确认文字在内存里——但之后无论怎么 Cmd+S 文件都不落盘。合理推断:set-value 走 AX 直写属性值,TextEdit 的 NSDocument 不知道自己「被修改过」,Cmd+S 对未修改文稿是个空操作;而 close-app 优雅退出时 macOS 的自动保存机制把内存内容写回了磁盘(文件在我关 App 后突然有了内容,就是这条路径)。换成 type(模拟真实键入)再 Cmd+S,文件立刻落盘,cat 和文档内存值完全一致。还有个小细节:type 是在光标处插入,不清空原文,我的测试文字被拼在了原有内容前面——先 clear 或 set-value 再 type 才是幂等写法。

把这条链路压缩成可复现的命令序列:

Bash
# 0. 前置:系统设置里给宿主 App 辅助功能权限
open -a TextEdit /tmp/agd-test.txt           # 坑一:用系统 open 开文稿
osascript -e 'tell application "TextEdit" to activate'
agent-desktop focus-window --app TextEdit    # 确认 is_focused: true
agent-desktop snapshot --app TextEdit -i     # 找 textfield 的 ref
agent-desktop type "@<快照id>:e2" --stdin < 输入.txt   # 坑三:用 type 别用 set-value
agent-desktop focus-window --app TextEdit
agent-desktop press "cmd+s"                  # 坑二:先聚焦再按键
agent-desktop close-app TextEdit             # graceful,evidence: process_instance_exited

MCP 模式和内置指南的 token 账

对不想写 shell 循环的 Agent 框架,agent-desktop mcp 把全部命令挂成 MCP 工具。我用 stdio 走了一遍 initialize + tools/list:75 个工具(desktop_snapshot、desktop_click、desktop_set_value……)。注意口径:README 写的是「61 个命令名、57 个可操作命令」,MCP 层是 75——三个数字说的不是同一件事,引用时别混。

它还内置了两份给 Agent 读的操作指南,agent-desktop skills get desktop 和 skills get platform。我用 o200k_base 数了:desktop 指南 9,847 字符 / 2,338 token,platform 7,956 字符 / 1,886 token。两份全读约 4,200 token,比上一期测过的 modern-web-guidance 全量 208 条指南 8,256 token 还省一半,塞进系统提示词的代价可以接受。也可以通过 skills.sh 装进 Claude Code / OpenCode 这类 harness。

一个没复现的行为,如实记

README 说 UI 变化后动作会返回 STALE_REF。我拿旧快照的 ref 去 get 一个还活着的 textfield,结果正常返回——ref 的失效看起来是「按元素是否还能解析」判定的,不是严格按快照新鲜度。窗口没重构、元素还活着时旧 ref 依然能用。STALE_REF 真正的触发条件(元素被销毁重建)这次没造出来,我把它留在未实测清单里,不做断言。

什么时候不该用它

  • 网页自动化别用它。无障碍树读 Chrome 的 UI 壳没问题,但页面内的 DOM 它够不着,这种活交给 Lightpanda 或 Playwright 才是正路;
  • 依赖视觉的场景(确认渲染效果、比对设计稿)它无能为力,它只看得见语义结构,看不见像素。需要看脸的活还是得配截图模型;
  • 没有辅助功能授权的 CI 环境直接过不了第一道坎,权限这一步天然需要人在场;
  • 无障碍树残缺的老 App(自绘 UI、游戏、Electron 老版本)读出来的树可能稀碎,ref 拿不到等于白装。

未实测清单

以下没有跑过,不做任何评价:Windows / Linux 表现;--headed 物理输入模式;sandbox 子系统(install/create/exec 一整套);session 与 trace;稠密 App 的 skeleton→root 两段式快照;通知类命令;真实 Agent harness 里装 skill 后的端到端编排(本文所有操作都是我手动下的命令)。官方文档在 agent-browser.dev,命令参考相当完整。

写在最后

agent-desktop 是我近几个月测的工具里「设计洁癖」最重的一个:每个返回都带 disposition 和 evidence,验证不了的就说 uncertain,危险动作标 retry: unsafe,连剪贴板清空都诚实标 unverified。代价是你会频繁看到「假失败」——动作明明成了,它说不确定。判断标准只有一条:开新快照,看状态。这台 M4 上从安装到跑通全链路不到一小时,其中大半时间花在了权限和 TextEdit 的三个坑上,都替你踩完了。

推荐阅读

  • modern-web-guidance 实测:Chrome/Edge/Firefox/MDN 联手给 AI 编码 Agent 塞了 208 条现代 Web 指南,我跑了一组 A/B 对照

    Chrome/Edge/Firefox/MDN 联合出品 modern-web-guidance,把 250 项现代 Web 特性喂给编码 Agent。我在 M4 Mac 实测 CLI 三板斧与 le…

  • ha-mcp 实测:让编码 Agent 接管 Home Assistant,M4 Mac 上全链路跑通

    ha-mcp 让 AI Agent 直接接管 Home Assistant:读写自动化、脚本、场景与仪表盘。我在 M4 Mac 上全链路实测:绕过 macOS 隐私崩溃坑、77 个工具直调 0.44 …

  • caveman 实测:给 AI 编码 Agent 装”省 token 石器”,我在 Mac 上真实跑出来的成绩单

    caveman 让 AI 编码助手少说少读省 token,被 Adobe 论文与 JetBrains A/B 背书。我在 M4 Mac 上实测:shrink 把 40 工具 MCP 目录压掉 36.6…

  • sand3d 实测:3,600 token 就能让模型造 3D 模型,我在 16GB Mac 上跑完了它全部官方案例

    sand3d 让模型写 3D 程序,而不是生成网格:全套 API 约 3,600 token,几十行 Python 就能造出带关节、可改参数的零件。我在 M4 Mac 上跑完它全部 9 个官方程序,用…