Skip to content
←返回开源项目

OPEN SOURCE DEEP DIVE

Desktop AutomationComputer UseAccessibilityUIAutomation

agent-desktop:给桌面智能体一条可验证的操作链

一套跨平台桌面无障碍控制层,通过快照限定引用、实时严格重识别和明确的交付证据,让智能体可靠操作办公软件。

vercel-labs/agent-desktop7.4kRustApache-2.02 min read

桌面智能体最常见的失败,不是“不会想”,而是“做完以后不知道自己到底做了什么”。模型可以规划出一份周报、修改表格或者发送邮件,但真正落到 Finder、Excel、PowerPoint、Outlook 这些原生应用时,坐标点击没有身份,键盘输入没有确认,窗口一变就可能把下一步操作送错地方。Vercel Labs 的 agent-desktop 选择了一条更工程化的路径:不靠截图找像素,而是读取操作系统暴露的无障碍树,用一个带快照身份的引用完成动作,再重新观察界面,证明动作没有落到错误控件上。

本次精读基于提交 9eb9b296。它不是一个新的桌面大模型,也不是替你完成任务的自主代理,而是一套面向代理运行时的底层控制层:提供 Rust 命令行工具、C FFI 和三平台原生适配器,把“观察、定位、操作、复核”做成可检查的协议。对于走 AI Office 路线的产品,这层协议正好补上了从自然语言意图到真实桌面应用之间的执行空隙。

agent-desktop 项目封面:通过无障碍树观察和操作桌面应用
agent-desktop 的核心不是替智能体做规划,而是让每次桌面动作都有可追踪的目标和结果。

为什么办公自动化需要“可验证”,而不是更快的点击

传统桌面自动化常把屏幕当成一张图片:识别按钮、换算坐标、发送鼠标事件。这个方案在固定分辨率和固定布局里能跑,但办公软件恰好是最不稳定的界面环境。窗口可以缩放,菜单会按上下文增删,表格会虚拟化行,系统对话框可能盖住主窗口,同一应用还会同时打开多个标题相近的文档。只验证“鼠标事件已发送”没有意义,代理需要知道它点的是哪个窗口里的哪个语义控件,以及这个控件在动作发生时是否仍然存在。

agent-desktop 把无障碍树当作事实来源。一次快照返回 JSON,树中的可操作元素带有引用;代理点击引用而不是坐标,随后重新取快照检查状态变化。这里的关键是引用带有快照编号,例如 @s8f3k2p9:e8,而不是可以被随意复用的裸 @e8。引用属于产生它的观察,跨快照直接猜测会被拒绝。这个约束看起来增加了步骤,实际上把“旧界面上的目标”和“新界面上的目标”明确分开,避免在页面重排后把一次编辑、删除或发送送到错误位置。

快照、引用动作、再次观察构成的桌面代理闭环
观察、动作、再观察是默认工作流。动作命令返回成功,并不能代替应用层面的结果检查。

这套闭环对办公场景尤其重要。修改单元格、切换幻灯片版式、给邮件加收件人,都不是天然幂等的操作。一次重复键入可能产生重复段落,一次重复发送可能真的发出第二封邮件。项目把交付证据和重试许可分开表达:not_delivered 才可安全重试,其余已知交付状态属于不安全,unknown 也不能被自动重试。代理必须先读清状态,再决定是否继续,而不是看到一次超时就把整段动作重放。

架构:共享核心、三个原生适配器、两种调用入口

仓库由 Rust workspace 组织。共享核心定义 PlatformAdapter、引用、快照、动作可靠性判断和统一 JSON 契约;macOS、Windows、Linux 各自提供平台 crate,负责把本机无障碍 API 映射成核心认识的角色、状态、名称和动作能力。平台 crate 之间不能互相导入,CLI 和 C 库也只选择一个平台适配器参与编译。这条边界很重要:它强制平台差异留在适配器内部,避免某个操作系统特有的实现悄悄污染另外两套行为。

对代理框架来说,最直接的入口是命令行。每次调用输出一个 JSON envelope,包含 ok、data 或 error;错误还带 code、disposition 和通常可执行的建议。需要减少进程启动开销的宿主,则可以通过 libagent_desktop_ffi 在进程内调用。两条入口共享同一套严格引用解析和交付语义,不会出现 CLI 能识别旧按钮、FFI 却直接按坐标发送的分叉。

agent-desktop 的共享核心、macOS、Windows、Linux 适配器、CLI 与 FFI 架构
共享 core 固定语义契约,平台适配器只实现本机能力;CLI 与 C FFI 复用同一套解析和动作路径。
平台无障碍来源关键运行条件办公场景中的主要边界
macOSAccessibility API需要辅助功能授权;截图另需屏幕录制授权原生控件覆盖好,但无权限时树可能表现为空,不能误判成应用没有控件。
WindowsUI Automation同完整性级别应用通常无需额外授权;提权目标需要提权调用方受 UIPI 影响,普通进程不能把输入送进更高权限窗口。
LinuxAT-SPI over D-Bus运行在用户图形会话中,并安装 at-spi2-coreWayland 会收窄指针与原生窗口操作;X11 与 GNOME 的覆盖并不相同。

平台能力不能用一句“跨平台”抹平。比如 Windows 的 type 没有严格 headless 的 UI Automation 写入路径,表单文本应优先使用 set-value;Linux 的鼠标物理输入依赖 X11,截图在 Wayland 下可能走 portal;macOS 的无障碍授权和屏幕录制授权是两个不同门槛。部署桌面代理时,第一步应当是 agent-desktop status 与 agent-desktop permissions,而不是直接取快照然后猜测为什么树是空的。

快照限定引用如何避免操作到错误控件

快照会把元素引用和持久化的 RefMap 一起保存。动作发生前,核心不盲信旧引用里的路径,而是按分级策略重新识别:先尝试原来的位置路径,再做有边界的广域搜索;身份匹配优先使用开发者标识和角色条件下的稳定文本,只有完全没有可用身份证据、且几何区域唯一的控件,才会把几何位置当作窄范围兜底。路径和边界只是定位线索,不是身份本身,因此每次匹配仍要回到存储的身份证据上核验。

这个失败关闭策略给出三种不同结果。实时界面中已经没有匹配时返回 STALE_REF;出现多个无法区分的候选时返回 AMBIGUOUS_TARGET;原快照缺失或过期时返回 SNAPSHOT_NOT_FOUND。三者都在阻止代理继续猜测,而不是把“差不多像”的控件当作目标。多窗口应用也会显式返回候选窗口 ID,让调用方选择 --window-id,而不是依赖当前最前面的窗口。

agent-desktop 从存储引用到实时元素的严格重识别流程
严格重识别优先使用标识符与稳定文本;唯一几何匹配只是没有其他身份证据时的末级兜底。

状态存储也有明确上限。每个命名空间保留 128 份快照,超过后裁剪回 96 份;单个持久化 RefMap 最大 1,048,576 字节,避免长时间运行的代理把磁盘和解析成本无限推高。项目同时区分部分快照与完整快照:普通观察可以返回尚未遍历完的树并沿节点标出边界,但从已有引用向下钻取的 drill-down 必须完整,否则拒绝覆盖原有 RefMap,防止把还没观察到的子元素误删。

默认 headless:语义动作不抢焦点,失败也不偷偷改用鼠标

引用动作默认走 headless 语义路径,通过平台无障碍接口执行,不主动激活前台、不移动系统指针、不合成键盘输入,也不借用剪贴板。这个默认值适合代理在用户仍使用电脑时做后台任务:它可以点击某个按钮、设置输入框的值或展开列表,而不把焦点从当前应用抢走。若语义路径不可用,动作会按策略失败,而不是悄悄切换成物理鼠标。

需要物理输入的场景必须显式选择 --headed,例如拖动、悬停、原始坐标点击,或某些平台的右键操作。项目明确反对“headless 失败就加 headed 重试”:前一次语义调用可能已经改变了状态,再发一次物理输入可能造成重复。headed 模式会允许聚焦、移动指针或合成输入,代理应当把它视为用户授权和风险边界,而不是万能恢复开关。

当前版本对外列出 60 个命令名,其中 56 个是可用命令。key-down、key-up、mouse-down、mouse-up 四个按住或释放边沿命令被保留但禁用,因为缺少一个能够可靠接管 hold 生命周期并在异常时释放的守护进程。代理应使用 press、mouse-click 或 drag,不要让一个按键或鼠标按钮跨进程悬在半按下状态。

成功信封不等于任务成功:读懂交付与复核

这个项目最值得其他桌面代理借鉴的,是它没有把“命令返回成功”写成“任务已经完成”。结果中的 disposition.delivery 区分 not_delivered、delivery_uncertain、delivered_unverified 和 delivered_verified;另一个 retry 字段单独表示是否允许安全重试。只有观察到输入没有送达时,重试才是安全的。未知交付状态不能自动重试,因为代理并不知道第一次动作有没有落地。

字段或状态含义代理应采取的下一步
ok: true命令已送达,或目标本来就满足请求继续读取应用结果;不能把信封成功当作业务成功。
delivered_verified适配器读回它声明的后置条件检查复核范围;有状态控件通常只证明控件值,不证明应用已持久化。
delivered_unverified动作已发出,但没有可靠的后置读取证据重新观察界面或目标应用状态,不要立即重复有副作用的动作。
not_delivered能够证明没有输入落地,或操作本来就已满足retry: safe 时才可按同一意图重试。
unknown没有足够证据判断是否送达禁止自动重试;先取新快照、读状态或交给人工判断。

batch 的语义同样需要认真读。它只减少进程启动次数,按顺序执行,不是事务,也不会因为中间某项失败自动回滚。除非传入 --stop-on-error,失败后的后续条目仍会运行;批次外层返回 ok: true 也不代表每个条目都成功。适合批处理的应当是可分解、可单独复核的小动作;像“填写表单并提交订单”这种跨步骤副作用,需要代理自己维护事务边界和恢复策略。当前约束还包括最多 64 个条目、输入上限 1 MiB、输出上限 4 MiB,以及默认 60 秒预算。

异步界面不要用固定睡眠硬等。项目提供文本、元素可操作状态、窗口出现、菜单关闭和通知到达等等待条件。代理应当等待真实信号,再复用新快照中的引用。对于“点击后可能已经送出请求”的超时,正确动作是先观察结果,而不是再点击一次。

深树与局部重钻:让复杂办公软件保持在可读规模

Excel、PowerPoint、Outlook 和大型 Electron 应用的树很容易超过模型的上下文预算。agent-desktop 的 snapshot --skeleton 默认只展开语义深度 3,先返回一份可导航的骨架;代理选中区域内某个引用后,用 snapshot --root 下钻。下钻只替换该根节点下的引用,骨架中的其他引用和已经钻过的其他区域仍然有效。

这种局部重钻不是简单截断。项目区分“原始深度”和“逻辑深度”,会跳过没有名称、值、开发者标识和可操作能力的 Web wrapper,因为这些匿名容器只消耗遍历预算,不提供可操作语义。相反,带名称或动作的容器仍会占一层逻辑深度。这样既能穿过 Chromium、Electron 和 WebView 生成的匿名包装,又不会把真正的控件当成噪声跳过。

agent-desktop 在局部区域下钻后只替换该区域引用
局部重钻保留根节点和区域外引用,不复用旧编号;代理只需重新读取发生变化的子树。

对于办公代理,推荐的操作顺序是:先用骨架找到“工作表”“邮件列表”“幻灯片缩略图”等大区域;只下钻当前任务需要的区域;完成动作后重新观察同一区域。若界面已经重排,旧引用应自然失败,代理再根据新骨架决定下一步。不要把一次全量快照缓存很久,也不要把窗口级引用当成永久 ID。

落到 AI Office:哪些任务适合,哪些不该硬做

agent-desktop 的价值集中在“必须进入原生桌面应用,并且结果需要被权限、菜单、对话框或本地文件系统确认”的环节。它不负责理解自然语言需求,也不负责生成文档内容;上游模型可以用自己的工具生成草稿、表格公式或演示结构,再把需要落到桌面应用的动作交给这一层。

agent-desktop 快速上手演示:读取应用快照并使用限定引用完成桌面操作
演示把代理工作流压缩成可检查的步骤:先看应用和控件,再对限定引用执行动作,最后重新观察结果。
办公流程推荐操作复核点
把结构化内容写入 Excel打开目标工作簿,定位工作表与单元格区间,优先使用 set-value;必要时使用骨架下钻和元素等待。重新读取关键单元格、行数、公式错误和保存状态;控件值正确不等于文件已经落盘。
在 PowerPoint 中替换文本或版式使用窗口与控件引用选择幻灯片、占位符和版式;处理覆盖在主窗口上的对话框。重新观察幻灯片与占位符内容,确认没有写到母版或另一份演示文稿。
整理 Outlook 邮件定位邮件列表项与工具栏,使用语义点击;异步加载时等待文本或元素状态。删除、移动、发送前再次读取选中项;发送是不可随意重试的高风险动作。
填写系统保存或导出对话框等待对话框出现,快照 sheet、alert 或 popover,再操作其中控件。确认对话框关闭、目标文件存在且路径正确;不要靠固定等待猜测保存完成。
处理原生菜单与通知用 find --surface menubar、菜单等待和引用点击完成明确命令;必要时显式 headed。确认菜单项确实执行、通知目标应用和标题匹配,避免在重排后的通知列表中按旧序号操作。

不适合的边界也要说清楚。无障碍树没有暴露的控件无法被语义控制;canvas 自绘内容、未提供可访问性的旧软件、被安全策略隔离的窗口,都可能没有可操作引用。项目也不做像素模板匹配,所以“看截图猜按钮”不是它的备用路径。Chromium 应用里密集的网页内容应交给 agent-browser 或其他 CDP 客户端,agent-desktop 保留原生菜单、文件对话框、窗口、通知和截图这些更适合操作系统语义层的部分。

此外,不要让代理为了“恢复”一个 headless 失败而自动打开 headed 模式。更合理的策略是:先读 disposition.retry,再看当前界面和业务状态;确认没有送达后才重试,确认语义路径不支持时再请求用户授权物理输入。对于发送邮件、删除文件、提交表单这类不可逆操作,代理还应在动作前保存明确的目标身份,在动作后通过应用状态或外部副作用复核,而不是只相信返回码。

隐私、权限与工程边界

无障碍快照可能包含窗口标题、文档名称、输入框内容、通知、剪贴板、截图和调试轨迹,这些都是真实的桌面数据。项目文档明确提醒不要把快照、截图和 trace 上传到公共日志。宿主在接入模型时,应当先在本地做字段白名单、敏感控件遮蔽和日志留存策略;密码字段即使在动作侧可以写入,也不应把尝试写入的明文回显到结果或错误详情中。

权限不是一次性设置。macOS 的辅助功能与屏幕录制权限需要分别处理;Windows 的完整性级别决定了能否操作提权窗口;Linux 需要和用户相同的图形会话以及可用的 AT-SPI 总线。代理启动时先检查状态,任务失败时区分“没有权限”“应用不响应”“引用过期”和“动作已交付但未验证”,比把所有问题都归因于模型能力更可靠。

这正是 agent-desktop 在 AI Office 生态中的位置:它不是替代文档生成模型,也不是把桌面变成网页;它提供一条从语义观察到可审计动作的控制链。对于需要操作本地 Office、文件管理器、系统对话框和原生效率工具的代理产品,这种以引用身份、交付证据和失败关闭为核心的底座,比再训练一个“会更准点击”的模型更能解决真实生产问题。完整的契约与代理工作流可以从项目的 概念文档和 JSON 输出契约继续展开。

作为亚马逊联盟会员,我们可能从符合条件的购买中获得佣金。