状态:v0.3 · Windows / macOS / Linux / Android(adb)已实现并通过端到端测试,位于
plugins/computer-use(见 §11、§12)· 目标:在 Windows / macOS / Linux / Android 上让 Agent 像 Codex Computer Use 一样操作本机应用,默认不依赖截屏。
| # | 要求 | 设计回应 |
|---|---|---|
| 1 | 达到 Codex 的体验 | 按应用授权、后台语义操作(不抢鼠标)、操作后返回增量变化、结果可验证 |
| 2 | 不依赖截屏 | 主通道用系统无障碍树(UIA / AX / AT-SPI / AccessibilityService),模型读的是结构化文本;截图只是最后的兜底,且可以关闭 |
| 3 | 四端支持 | 只有一套与平台无关的工具和快照引擎(TypeScript),平台差异全部放在原生 Driver 里,Driver 之间用同一个 JSON-RPC 协议 |
| 4 | 符合 Prometheus 插件体系 | 作为 plugins/computer-use 工作区插件,通过 ctx.extension 注册工具,用 ctx.panel 提供授权和审计页面,Driver 由工具商店(runtimes.json)分发 |
| 5 | 安全 | 和权限模式联动,按应用白名单,密码框脱敏,全局急停,审计日志 |
为什么不用截屏:截屏 + 视觉模型的方式每一步都要上千 token,坐标点击容易出错,还要求模型支持视觉。无障碍树能直接给出控件的角色、名称、值、状态和可执行的动作,用 invoke / setValue 操作,不需要猜坐标,大多数情况下也不需要把窗口切到前台。
不做的事:不做游戏或画布类应用的像素级自动化;不做绕过系统权限弹窗的操作;不做无人值守的远程控制木马式能力(远程设备必须由用户在设备端主动配对)。
flowchart TB
subgraph Host["Prometheus Host(Node)"]
Tools["LLM 工具层<br/>computer_* tools"]
Engine["快照引擎<br/>归一化 / 裁剪 / 引用 / Diff / Token 预算"]
Policy["策略层<br/>应用授权 / 权限模式 / 脱敏 / 审计"]
DM["DeviceManager<br/>本机 / SSH / 反向连接设备"]
end
subgraph Drivers["原生 Driver(同一 JSON-RPC 协议)"]
Win["cu-driver (Windows)<br/>UI Automation"]
Mac["cu-driver (macOS)<br/>AXUIElement + AXObserver"]
Lin["cu-driver (Linux)<br/>AT-SPI2 over D-Bus"]
And["Android AccessibilityService<br/>(Prometheus APK 内)"]
Adb["adb 后端<br/>uiautomator(开发者模式)"]
end
Tools --> Policy --> Engine --> DM
DM -- "stdio" --> Win & Mac & Lin
DM -- "ssh stdio" --> Lin
DM -- "UDS / ws 反向连接" --> And
DM -- "adb shell" --> Adb
分三层:
- 工具层:给模型用的工具不超过 7 个,参数小,返回纯文本。
- 快照引擎:把各平台的无障碍树统一成同一种角色模型,裁剪成精简的文本,分配稳定的元素引用,并在每次操作后计算增量变化。这一层是体验好坏的关键。
- Driver:每个平台一个原生进程,只负责枚举、读取、执行动作和推送事件,不做任何策略判断。
| 工具 | 作用 | 关键参数 |
|---|---|---|
computer_apps |
列出正在运行的应用和窗口,也可以启动应用或打开 URL/文件 | device?, launch? |
computer_observe |
取得目标窗口的结构化快照 | app / window, scope?(ref 子树), query?(按文本/角色过滤), maxTokens? |
computer_act |
按引用执行一个或一批动作,返回增量变化 | actions: [{ref, op, value?}], settle? |
computer_find |
在不展开全树的情况下按名称、角色、文本查找元素 | text, role? |
computer_wait |
等待某个条件成立(元素出现/消失、值改变、窗口出现) | until, timeoutMs |
computer_keys |
发送快捷键或原始按键(兜底,需要焦点) | keys: "ctrl+s" |
computer_screenshot |
兜底,默认关闭:OCR 文本层,或把图片发给模型 | mode: "ocr" | "image" |
op 的取值(跨平台统一):click(优先语义 invoke)、double_click、right_click、focus、set_value、type(逐字输入)、clear、toggle、select、expand、collapse、scroll(up/down/left/right/into_view)、drag_to(目标 ref)、back/home/recents(Android)。
用缩进文本,不用 JSON(大约能省 40% token),每行一个元素:
app 记事本 pid=8812 · window "无标题 - 记事本" [focused]
[w1] window "无标题 - 记事本"
[e2] menubar
[e3] menuitem "文件" ▸
[e4] menuitem "编辑" ▸
[e5] document "文本编辑器" value="hello wor…(1.2k)" editable focused
[e6] statusbar "第 1 行,第 12 列"
[e7] button "关闭"
(折叠 23 个无名容器 · 2 个列表项超出预算,用 scope=e9 展开)
规则:
- 裁剪:去掉不可见、离屏、零尺寸元素;没有名称且只有一个子节点的容器直接压平;纯装饰元素不输出。
- 截断:长值只保留开头,并标出总长度;长列表只输出前 N 项和 "还有 K 项",模型可以用
scope或query继续深入。 - 预算:默认每次快照最多 2k token。超出时按以下优先级保留:焦点路径 > 可交互元素 > 带文本的元素 > 容器。
- 脱敏:密码框(
IsPassword/AXSecureTextField/isPassword()/ROLE_PASSWORD_TEXT)的值永远显示为••••。
引用 eN 由 Driver 维护:ref → 原生句柄 + 指纹。指纹的组成是 (role, name, automationId/identifier/resource-id, 在父节点中的位置, 祖先路径哈希)。
- 原生句柄失效时(窗口重绘、列表虚拟化),用指纹重新定位;有多个候选时报错
stale_ref,并附上最新的局部快照。 - 同一窗口的连续快照中,没变化的元素沿用原来的 ref,这样模型的上下文不会失效。
computer_act 执行后,Driver 通过事件订阅(UIA StructureChanged/PropertyChanged、AXObserver、AT-SPI object:* 信号、AccessibilityEvent)等待界面稳定(默认 300ms 内没有新事件,最长 3s),然后返回:
ok click e3
+ [e40] menu "文件"
+ [e41] menuitem "新建(N) Ctrl+N"
+ [e42] menuitem "保存(S) Ctrl+S"
~ [e5] document focused → (unfocused)
focus: e40
模型几乎每一步都不需要重新调用 observe,这也是减少轮次和 token 的主要手段。弹出新窗口或对话框时,自动附带它的快照。
插件在 before_agent_start 中追加一段使用准则:先 observe 一次,然后按 ref 操作,以返回的增量为准;优先用 set_value/click,keys 和坐标只作兜底;涉及支付、发送、删除等不可逆操作前要先向用户确认。
使用 JSON-RPC 2.0,按行分隔,经 stdio / UDS / WebSocket 传输。所有平台的 Driver 都实现同一组方法:
| 方法 | 说明 |
|---|---|
hello → {platform, version, capabilities[], permissions{}} |
握手,报告能力(如 background_input、ocr、events)和权限状态 |
apps.list / apps.launch / windows.list / window.activate |
应用和窗口管理 |
tree.snapshot {window, scope?, depth?, filter?} |
返回归一化后的节点(裁剪交给 Host) |
tree.find {window, text, role?} |
原生查询(UIA FindAll / AX 搜索),比全树快得多 |
node.act {ref, op, value?} |
执行动作,返回 {ok, method: "pattern" | "message" | "synthetic"} |
events.subscribe {window} → 通知 events.batch |
推送结构和属性变化 |
input.keys / input.pointer |
合成输入兜底 |
screen.ocr {window} / screen.capture |
兜底视觉通道(可选能力) |
归一化节点:
interface CUNode {
ref: string; role: Role; name?: string; value?: string; description?: string;
states: ("focused"|"disabled"|"checked"|"selected"|"expanded"|"collapsed"|"editable"|"password"|"offscreen")[];
actions: Op[]; // 该节点真正支持的动作
bounds?: [x, y, w, h]; // 仅用于兜底点击和 OCR 对齐,不发给模型
children?: CUNode[];
}Role 是统一后的约 40 种角色(button、link、textbox、document、checkbox、radio、combobox、list、listitem、tree、treeitem、table、row、cell、tab、tablist、menu、menuitem、menubar、dialog、window、toolbar、slider、progressbar、image、text、group…),各平台各维护一张映射表。
Driver 用 Rust 写成一个 crate(crates/cu-driver),按平台条件编译;Android 因为必须是 Java/Kotlin 的 AccessibilityService,单独用 Kotlin 实现。桌面二进制作为 Tauri sidecar 随应用打包,同时发布到工具商店,供纯 Server、TUI 和 SSH 远端使用。
- 绑定:
uiautomation/windowscrate,使用IUIAutomation和 CacheRequest 批量取属性(性能的关键,否则一棵树要几千次跨进程调用)。 - 读取:
ControlViewWalker,取Name、ControlType、AutomationId、ValuePattern.Value、IsPassword、IsOffscreen、BoundingRectangle。 - 动作映射:
click→InvokePattern/SelectionItemPattern.Select/TogglePattern/LegacyIAccessible.DoDefaultAction;set_value→ValuePattern.SetValue;expand→ExpandCollapsePattern;scroll→ScrollPattern/ScrollItemPattern.ScrollIntoView。这些全部不移动鼠标,也不需要窗口在前台。 - 兜底顺序:
PostMessage(WM_CHAR/WM_KEYDOWN)发到目标 HWND(后台),然后才用SendInput(需要前台,执行前先SetForegroundWindow,并在界面上提示)。 - 事件:
AddStructureChangedEventHandler、AddPropertyChangedEventHandler、AddFocusChangedEventHandler,运行在 MTA 线程。 - 坑:UIPI 规定低完整性进程无法操作管理员窗口,此时报
elevation_required;Chromium/Electron 收到 UIA 客户端请求后才会懒加载无障碍树,第一次快照需要重试一次。 - OCR 兜底:
Windows.Media.Ocr,在本地完成。
- 绑定:
objc2+ApplicationServicesFFI(AXUIElementCreateApplication、AXUIElementCopyMultipleAttributeValues批量读取)。 - 权限:辅助功能(TCC)。sidecar 由 Prometheus.app 启动,"负责进程"是 Prometheus,所以用户只需要给 Prometheus 授权一次。
hello时通过AXIsProcessTrustedWithOptions检查,未授权时在面板中引导用户打开系统设置。截图 / OCR 才需要"屏幕录制"权限,默认不申请。 - 动作:
AXPress、AXShowMenu、AXIncrement/Decrement、AXRaise、设置kAXValueAttribute/kAXFocusedAttribute/kAXSelectedAttribute,都可以在后台执行。 - 后台按键:
CGEventPostToPid定向投递给目标进程,不影响用户当前的前台应用(Codex 后台操作的关键手段)。 - 事件:每个目标应用一个
AXObserver(kAXValueChangedNotification、kAXCreatedNotification、kAXUIElementDestroyedNotification、kAXFocusedUIElementChangedNotification),放在 Driver 的 CFRunLoop 线程里。 - 坑:Electron/Chrome 需要设置
AXManualAccessibility = true才会暴露完整的树;部分应用的AXChildren很大,必须按深度和数量限流。 - OCR 兜底:Vision
VNRecognizeTextRequest。
- 绑定:
atspicrate(zbus),连接无障碍总线(org.a11y.Bus)。 - 读取:
Accessible(role/name/children/states),Text、Value、Component(bounds)。用GetTree/Cache 接口批量拉取。 - 动作:
Action.DoAction(click/activate/press)、EditableText.SetTextContents、Value.SetCurrentValue、Selection、Component.GrabFocus,基本都能在后台执行。 - 前置条件:GTK3/4 默认开启;Qt 需要
QT_ACCESSIBILITY=1;Electron 需要--force-renderer-accessibility。Driver 的hello检测org.a11y.Status.IsEnabled,未开启时提示用户并可以一键开启gsettings set org.gnome.desktop.interface toolkit-accessibility true。 - 合成输入兜底:X11 用 XTest;Wayland 用
xdg-desktop-portalRemoteDesktop(首次需要用户在系统弹窗中同意)或libei;都不可用时报input_unavailable,不尝试uinput提权。 - 无界面服务器:可以选择在
Xvfb+ 独立会话中运行目标应用,Agent 在虚拟显示器中操作,完全不打扰用户(为以后的"沙箱桌面"预留)。 - OCR 兜底:可选依赖
tesseract,没有安装时报告该能力不可用。
Android 只有两种可行的方式,两种都支持:
A. APK 内置 AccessibilityService(主路径)
- 在 Prometheus APK 中加入
PrometheusAccessibilityService(Kotlin),用户在 系统设置 → 无障碍 中手动开启(系统强制要求,无法绕过)。 - 读取:
getWindows()+AccessibilityNodeInfo遍历,取className、text、contentDescription、viewIdResourceName、isPassword、isEditable、getBoundsInScreen。 - 动作:
performAction(ACTION_CLICK / ACTION_LONG_CLICK / ACTION_SET_TEXT / ACTION_SCROLL_FORWARD / ACTION_SCROLL_BACKWARD / ACTION_FOCUS / ACTION_SELECT / ACTION_EXPAND);performGlobalAction(GLOBAL_ACTION_BACK / HOME / RECENTS / NOTIFICATIONS);兜底用dispatchGesture按 bounds 点击或滑动。 - 事件:
onAccessibilityEvent(TYPE_WINDOW_STATE_CHANGED、TYPE_WINDOW_CONTENT_CHANGED、TYPE_VIEW_TEXT_CHANGED),用于计算稳定时机。 - 通道:
- 本机模式(APK 内置 Node Server):Service 监听 abstract UNIX socket
@prometheus-cu-<uid>,并校验对端 uid 必须是本应用,再加一次性 token,Node 端作为普通 Driver 连接。 - 瘦客户端模式(连接远程 Server):用户在手机上点击"允许此服务器操作本机",Service 经过现有 WebSocket 反向注册为设备
android:<型号>,Server 端通过 DeviceManager 转发 RPC。断开连接或关闭开关后立即失效。
- 本机模式(APK 内置 Node Server):Service 监听 abstract UNIX socket
- 发版注意:Google Play 对无障碍 API 有专门的政策审核,需要在声明中说明用途,并在应用内显示醒目的披露说明;GitHub 发布的 APK 不受此限制。
- 截图兜底:
AccessibilityService.takeScreenshot(API 30+),默认关闭;OCR 用 ML Kit(可选的独立模块,避免 APK 体积暴涨)。
B. adb 后端(开发者模式,不需要安装任何东西)
- 从桌面端控制一台通过 USB / 无线调试连接的手机:
adb exec-out uiautomator dump /dev/tty取 XML 树,input tap/text/keyevent执行操作。 - 缺点:每次 dump 需要 1–2s、没有事件,
input text不支持中文(需要配合 ADBKeyboard)。适合测试和自动化场景,作为次要路径。
| 能力 | Windows | macOS | Linux X11 | Linux Wayland | Android |
|---|---|---|---|---|---|
| 结构化树 | UIA ✅ | AX ✅ | AT-SPI ✅ | AT-SPI ✅ | A11yService ✅ |
| 后台语义动作 | ✅ | ✅ | ✅ | ✅ | ✅ |
| 后台按键 | PostMessage(部分) | CGEventPostToPid ✅ | XTest(需要焦点) | ❌ | ACTION_SET_TEXT ✅ |
| 变化事件 | ✅ | ✅ | ✅ | ✅ | ✅ |
| 合成指针 | SendInput | CGEvent | XTest | Portal / libei | dispatchGesture |
| 本地 OCR | Windows.Media.Ocr | Vision | tesseract(可选) | tesseract(可选) | ML Kit(可选) |
当无障碍树不够用时(自绘 UI、Canvas、游戏、远程桌面、Java Swing 没开 Access Bridge、部分 Flutter 应用),按以下顺序逐级降级,每一级都要在工具结果中写明"当前使用的是降级通道":
- 原生无障碍树(默认)。
- 框架专用通道:Chromium 系应用如果开启了远程调试端口,就用 CDP
Accessibility.getFullAXTree/ DOM(与浏览器自动化共享实现);Java 应用使用 Java Access Bridge。 - 本地 OCR 伪元素:在本机识别文字和坐标,生成
[o12] text "确定" (ocr)这样的元素,可以用 ref 点击(坐标点击)。图片不离开本机,也不发给模型。 - 截图发给模型:只有当用户在设置中打开"允许发送截图"、且当前模型支持视觉输入时才可用;图片缩到长边 1280px,并叠加元素编号(Set-of-Mark),让模型仍然可以用 ref 操作。
和 Codex 一样,按应用授权,并接入 Prometheus 现有的权限模式:
| 模式 | 行为 |
|---|---|
| plan | 只允许 apps / observe / find,禁止一切动作 |
| default | 每个会话第一次操作某个应用时弹窗确认(允许一次 / 本会话 / 始终允许该应用);不可逆动作(命中"发送/支付/删除/提交"等语义的按钮)单独确认 |
| accept edits | 白名单内的应用不再询问,不可逆动作仍然要确认 |
| bypass | 白名单内全部放行;拒绝名单永远生效 |
| 自动化(无人值守) | 只有 access: full 才能使用,并且只能操作预先加入白名单的应用,永远不会弹窗 |
其他措施:
- 拒绝名单(默认):密码管理器、系统钥匙串和凭据管理器、系统安全和隐私设置、UAC/sudo 提权对话框、Prometheus 自身窗口(防止 Agent 修改自己的权限)。
- 脱敏:密码框只能写不能读,往密码框写入前必须确认;快照中匹配到疑似密钥、验证码的值会被打码。
- 急停:全局热键(默认
Ctrl/Cmd+Shift+Esc,可以修改)和托盘/状态栏按钮会立即中止当前工具调用并暂停该会话的 Computer Use;Android 上使用常驻通知中的"停止"按钮。 - 可见性:Agent 操作期间,托盘图标切换状态,目标窗口显示细边框高亮(Windows/macOS 用透明置顶窗口实现,不截屏)。
- 审计:每个动作记录为
{time, session, device, app, ref指纹, op, 是否脱敏},保存到<home>/plugin-data/computer-use/audit.jsonl,可以在面板中查看。 - 远程设备:只能由设备端发起配对(扫码或输入一次性码),Server 不能主动连接设备;配对凭证可以在两端随时撤销。
plugins/computer-use/
├─ package.json
├─ src/
│ ├─ index.ts # definePlugin:注册工具、提示注入、面板、RPC
│ ├─ tools.ts # computer_* 工具定义(TypeBox)
│ ├─ engine/
│ │ ├─ normalize.ts # 平台角色 → 统一 Role
│ │ ├─ render.ts # 节点树 → 缩进文本,预算与裁剪
│ │ ├─ diff.ts # 快照增量
│ │ └─ redact.ts
│ ├─ policy.ts # 应用授权、拒绝名单、不可逆动作识别
│ ├─ devices/
│ │ ├─ manager.ts # 设备注册、选择、心跳
│ │ ├─ stdio.ts # 本机 sidecar / ssh stdio
│ │ ├─ socket.ts # Android UDS / 反向 WebSocket
│ │ └─ adb.ts # uiautomator 后端
│ ├─ audit.ts
│ └─ panel/ # 授权管理、设备列表、审计、权限自检页面
└─ test/ # 快照引擎的金样测试(各平台录制的树 fixture)
crates/cu-driver/ # Rust:win.rs / mac.rs / linux.rs + 协议
apps/desktop/src-tauri/gen/android/.../PrometheusAccessibilityService.kt
设置(pluginSettings["computer-use"]):allowScreenshots、ocr、snapshotTokenBudget、settleMs、killHotkey、apps.allow[]、apps.deny[]。
TUI:工具一样可用;授权确认使用 ctx.ui.confirm;面板功能用 /computer 命令代替。
| 阶段 | 内容 | 验收 |
|---|---|---|
| M0 | 协议、快照引擎、工具层、Windows UIA Driver、策略和审计 | 记事本写入并保存文件;在系统设置中切换深色模式;全程不截屏 |
| M1 | macOS AX Driver、后台按键、权限引导 | 备忘录、Finder 重命名、在后台操作时用户能正常使用其他应用 |
| M2 | Linux AT-SPI Driver(GNOME/KDE,X11 + Wayland)、SSH 远端 Driver | gedit/Files 任务;通过 SSH 操作另一台机器 |
| M3 | Android AccessibilityService(本机 + 反向连接)、adb 后端 | 在设置中打开 Wi-Fi,在任意 IM 中输入中文草稿(不发送) |
| M4 | OCR 伪元素、CDP 通道、Set-of-Mark 截图兜底 | 自绘 UI 用例通过且图片不出本机 |
| M5 | 评测集:每个平台 30 个任务,记录成功率、步数、token、耗时,作为回归基线 | 平均每任务 token 比"截图 + 视觉"方案低 5 倍以上 |
一些小的判断交给外部的决策模型(分类器)通过 API 完成,例如"这个操作能不能撤销""用户说的是哪一个元素"。插件不内置模型:没有配置时只用规则,配置之后由规则和模型一起判断。
| 方式 | 配置 | 覆盖范围 |
|---|---|---|
registry |
/computer decider registry [provider/model];不写模型时,自动选一个可用的决策模型(优先 Laya / Julia / Kev / Jev / Clef) |
通过 PI 的 ctx.modelRegistry.classify() 调用:llama.cpp 路由上的决策模型(Laya 等,走 /v1/systemone)和聊天模型、TypeSafe Jev、Cloudflare Clef、OpenAI Decisions API。凭证复用 PI 已有的 |
gateway |
/computer decider gateway <网关id> <模型> |
Prometheus 中任意一个 openai-completions 格式的 AI 网关 |
openai |
/computer decider openai <baseUrl> <模型> [API_KEY_ENV] |
任意 OpenAI 兼容端点(vLLM、Ollama、LM Studio、llama-server…)。每个标签只占一个 token,概率从 logprobs 读取;服务端不支持 logprobs 时退回只读答案文本 |
用 /computer decider test 可以验证配置是否可用。配置保存在 <home>/plugin-data/computer-use/settings.json(仅所有者可读写);也可以写在 config.json → pluginSettings["computer-use"].decider。
当前的用途:
- 不可逆操作:在规则没有命中的情况下,对 click / select / toggle 一个有名称的元素,以及在非聊天应用中按 Enter,询问模型(bool 问题),概率 ≥ 0.5 时要求用户确认。
- 元素消歧:
computer_find没有文本命中、或者多个候选同分时,让模型在最多 20 个候选中选一个(choice 问题)。
安全约束:模型只能让插件更谨慎,例如多问一次确认,但不能跳过规则要求的确认。原因是模型读到的界面文本来自任意应用和网页,可能被诱导。模型调用超时(8 秒)或出错时视为"没有意见",由规则单独决定。
| 项目 | 设计稿 | M0 实际 | 原因 / 后续 |
|---|---|---|---|
| Windows Driver | Rust crate | C#(drivers/windows/driver.cs),首次使用时由 Windows PowerShell 的 Add-Type 调用 .NET Framework 自带的编译器编译,按源码哈希缓存 DLL |
不需要构建工具链,也不需要安装 .NET;任何 Windows 10/11 都能直接运行。协议不变,以后可以换成 Rust |
| 打包 | sidecar 二进制 | 驱动源码由 pnpm --filter @prometheus/plugin-computer-use embed 内联进 src/driver/embedded.generated.ts,运行时释放到 plugin-data/computer-use/drivers/ |
与 modes 插件的资源内联方式一致,esbuild 打包后也能工作 |
| 稳定判定 | UIA 事件订阅 | 重复读取快照,直到连续两次的签名相同(settleMs 250,最长 settleTimeoutMs 3000) |
实现简单、跨平台通用;文件对话框这类大窗口会慢(实测 Ctrl+S 约 5.8s),M1 改为事件驱动 |
| 工具 | 7 个,含 screenshot | computer_apps / observe / find / wait / act / keys / launch;截图兜底推迟到 M4 |
launch 单独拆成一个工具,这样 computer_apps 可以保持只读(plan 模式可用) |
| 权限 | 插件内按应用授权 | 沿用主机的权限门禁:只读工具(apps / observe / find / wait)带 readOnlyHint,plan 模式可用;act / keys / launch 在 default 模式下逐次询问。插件额外提供 allowApps / denyApps、内置拒绝名单、不可逆操作和密码框确认 |
不再另建一套重复的授权弹窗 |
| 急停 | 全局热键 | /computer stop(中止当前回合并暂停)/ resume,以及 RPC pause / resume 供后续面板使用 |
全局热键需要 Tauri 侧配合,放到 M1 |
| 运行模式识别 | — | 从系统提示中识别 auto / unattended:auto 模式不确认;无人值守运行拒绝不可逆操作(除非开启 allowIrreversibleUnattended) |
验证:plugins/computer-use/test 有 27 个单元测试(快照引擎、diff、脱敏、策略、决策模型的三种后端、用假驱动测试的服务流程)。scripts/e2e-notepad.ts 是真机验收脚本:启动记事本 → 写入中英文 → Ctrl+S → 在另存为对话框中填写路径并点保存 → 校验文件内容 → 关闭。全程没有截屏,结果 PASS。
四个平台都走同一套 JSON-RPC 协议和同一个快照引擎。热加载的内容(驱动源码)由 pnpm --filter @prometheus/plugin-computer-use embed 内联。
| 平台 | 驱动 | 验证方式 | 结果 |
|---|---|---|---|
| Windows | C# + UI Automation,由 PowerShell Add-Type 编译 |
本机记事本:写入、另存为、校验文件 | PASS |
| macOS | Swift + AXUIElement / AXObserver,首次使用由 swiftc 编译并缓存(也支持 PROMETHEUS_CU_DRIVER 指向预编译二进制) |
GitHub Actions macos-latest(macOS 26)上的 TextEdit:设置值、在光标处插入、读回中文 |
PASS |
| Linux | Python + AT-SPI2(python3-gi) |
GitHub Actions ubuntu-24.04 + Xvfb,gedit |
PASS |
| Android | adb + uiautomator dump / input(src/driver/adb.ts) |
GitHub Actions Android 14 模拟器,设置应用:启动、读取、点击「网络和互联网」、返回 | PASS |
macOS 的做法:
- 点击、设置值、展开、滚动都用 AX 动作和属性,不移动用户的鼠标,也不把应用切到前台;只有最后兜底的合成点击才会激活窗口,并把鼠标放回原位。
- 按键用
CGEvent.postToPid直接投递给目标进程,用户可以继续用自己的应用。ctrl在 macOS 上按 Command 处理(与其他平台的习惯一致),需要真正的 Control 键写control。 - 稳定判定用 AXObserver 事件(
ui.settle);没有事件能力的驱动仍然由主机轮询。Chromium / Electron 会自动打开AXManualAccessibility。 - 菜单栏作为窗口树的子树返回(默认折叠,
scope=<ref>展开)。 - 权限:需要给启动驱动的应用(Prometheus 或终端)授予「辅助功能」。未授权时驱动会弹出系统提示,并返回
permission_denied和具体的设置路径。
Linux 的限制:Wayland 不允许合成按键和指针,驱动会明确报 input_blocked,元素操作(点击 / 设置值 / 插入文本)仍然可用。
Android 的限制(adb 后端):每次读取 1–2 秒;没有变化事件;input text 不支持中文,需要在手机上装并启用 ADB Keyboard;密码框的文本不会出现在名称或值里。用「设置 → computer-use → device: "adb"」切换(可选 adbPath、adbSerial)。仍未实现:Prometheus APK 内的 AccessibilityService(不需要 adb、有事件、支持中文输入),以及手机反向连接服务器。
- 是否需要"沙箱桌面"(Windows 独立 Desktop / Linux Xvfb / macOS 无官方方案),让 Agent 在完全隔离的环境中运行?建议放到 M5 之后。
- Driver 发布方式:随安装包打包(体积约增加 3–5MB)还是首次使用时从工具商店下载?建议桌面端随包、Server/TUI 按需下载。
- 不可逆动作的识别准确率:先用关键词加角色规则,后续可以考虑本地分类器。
- 多显示器、高 DPI 和缩放下的 bounds 换算(只影响兜底通道)。