feat(cua): CUA Driver 支持 — 通过 MCP tool 操作 macOS 桌面 - #616
Conversation
- 新增 services/cua 模块:CuaDriver trait + macOS 实现(osascript / screencapture / cliclick) - CuaStore 持有 enabled / allowed_owners / audit_log_limit 配置 + 最近 N 条审计日志,enforce() 在每条命令前守门 - CuaError 结构化错误(kind + params + message),Locale-aware 渲染入口 - 12 个 Tauri Command:cua_status / set_config / clear_audit / list_windows / focus_window / screenshot / click / double_click / type / key / scroll / drag - force_activate_main_window:NSApp.activate + makeKeyAndOrderFront + makeFirstResponder(WKWebView),带 respondsToSelector 守门与 objc2::exception::catch,WKWebView accessibilityChildren 注册 + NSAccessibilityUIElementCreatedNotification 广播 - 新增 cua_window_ready / cua_refresh_a11y IPC 命令,供前端路由切换后主动重激活 a11y
- 新增 lib/cua/cuaService.ts:Tauri invoke 桥接层(desktopCuaService),统一错误归一化(normalizeCuaError) - 新增 lib/tools/cuaTools.ts:9 个 builtin tool(cua_list_windows / focus_window / screenshot / click / double_click / type / key / scroll / drag),screenshot 返回多模态 ImageContent - builtinRegistry 挂载 cua groupId,仅在用户设置 cua.enabled === true 时注册 - runAgentConversationTurn / useSendChatTurn 透传 getLocale,cua tool 错误按当前 locale 渲染 - i18n/config.ts 新增 settings.cua.* 文案(zh-CN / en-US) - MacOsTitleBarSpacer 加 data-testid='open-settings' + aria-label - App.tsx 监听 overlay 路由切换,自动调 cua_refresh_a11y - vite.config.ts:collectionsNodeResolvePath 支持跨目录 unplugin-icons 解析 - 新增 test/cua/format-cua-error.test.mjs
- 新增 pages/settings/CuaSection.tsx:总开关 + 平台标签 + 白名单 + 审计上限 + 清空 + 实时审计列表,全部 data-testid 锚点 - 新增 lib/cua/formatCuaError.ts:按 locale 渲染 CuaError,中文 / 英文双套模板,支持结构化 params 插值 - SettingsShell 加 data-testid='settings-nav-<id>' 与 data-active;SettingsPage 注册 cua nav 项 - enUSSettings / zhCNSettings 新增 settings.cua.* / cua.errors.* / platformNotes.* 翻译 - ChatHistorySidebar:sidebar Settings Button 加 data-testid='open-settings' + aria-label='Open settings'(与 titlebar 一致) - contracts/builtinTools + settings/index + settings/types:CuaRuntimeConfig 双向持久化(localStorage 镜像 + 后端权威双写)
- 新增 scripts/dev-prepare.mjs: - dismissMacProblemReporterDialogs:osascript AXCloseButton + pkill 双路径,SIGTERM->SIGKILL 升级 - killStaleLiveagentProcesses:读 dev-stack 状态文件,豁免 in-flight PID,只杀孤儿 stale - --watch 模式周期 sweep(7s)新出现的 Problem Reporter - probeScreenCaptureReadiness:cua-driver get_desktop_state 主动探测,TCC 授权 + daemon 重启恢复 - LIVEAGENT_CUA_DRIVER_NO_OVERLAY=1:检测到 overlay daemons 时用 serve --no-overlay 重启 - dev-stack.mjs desktop 分支:绝对路径 spawn crates/agent-gui,workdir 修复 ~icons 解析;--watch 后台 sweep;start 前先调 dev-prepare
|
PR governance checks failed — this PR has been converted to draft.
Fix the items above, then click Ready for review to re-run the checks. |
- crates/agent-gateway/web/src/pages/settings/types.ts:SectionId 加 'cua';SettingsPageProps 加 cuaService?: never(WebUI 不提供 cuaService,条件渲染跳过 CUA 段) - agent-gui + agent-ui + gateway-webui 全量 Biome check --write:sort imports / useMemo deps / 格式统一
|
方向本身有明确的业界对标(Claude Code / Codex 都在做桌面 CUA,且都是 macOS 先行),后端 不过有两个层面的问题,建议合入前对齐一下: 1. 安全门控与现有审批体系脱节CUA 可以操作任意桌面应用、发任意组合键,风险面不小于 Bash,但目前的门控存在几个缺口:
前三条改动成本都不大,风险收益比很高。 2. 建议评估:直接接 cua.ai 的 cua-driver,而非自研 shell-out 驱动一个架构层面的观察:这个 PR 的端到端测试全程跑在 cua.ai 的 cua-driver 上(
建议考虑两段式:先把 cua-driver 作为推荐 MCP 预设接进来(走现有 MCP 基建,零新协议,安全上还能直接复用 |
按 [cua.ai 官方文档](https://cua.ai/docs/how-to-guides/driver/install) 实现: 后端 (Rust): - services/cua/installer.rs:CUA Driver 检测/安装/更新/daemon 启动逻辑 - 平台分发:macOS/Linux 走 /bin/bash -c 'curl -fsSL https://cua.ai/driver/install.sh | bash -s --';Windows 走 PowerShell irm ... | iex + cua-driver autostart kick - Linux 额外提示 libxi6 + at-spi2-core apt 依赖 - macOS daemon 启动:open -n -g -a CuaDriver --args serve - 30 分钟超时 + 进度事件流(cua_install_progress: starting / downloading / installing / startingDaemon / completed | failed) - services/cua/error.rs:6 个 installer_* 错误变体(network / curl / signature / permission / unsupported / timeout) - commands/cua.rs:5 个新 Tauri Command(cua_driver_detect / install / update / start_daemon / install_preview) - lib.rs:注册到 invoke handler 前端 (agent-ui / agent-gui): - pages/settings/CuaInstaller.ts:CUA Installer 类型 + 服务接口 - pages/settings/CuaInstallerPanel.tsx:610 行 UI——status badge / 检测/安装/重启/检查更新/应用更新按钮 / 进度条 / 折叠 log / macOS 权限卡片(含 x-apple.systempreferences deep link)/ Linux apt 依赖提示 - pages/settings/CuaSection.tsx:挂载 installer 面板 - lib/cua/cuaService.ts:5 个新 Tauri invoke 桥接方法 + 进度订阅 - App.tsx:visibilitychange / focus 事件调 cua_refresh_a11y - i18n:agent-gui/config.ts + enUSSettings / zhCNSettings 新增 ~30 个 settings.cua.installer.* + cua.errors.installer.* 文案 data-testid 锚点:cua-driver-status / install-button / update-button / restart-daemon-button / install-progress / install-log 等 17 个 测试:cargo test 957 pass(4 新 installer 测试)/ pnpm test 2530 pass(6 新 cuaService 契约测试)
…034) 问题:useSettingsOverlay 用 requestAnimationFrame 链推进 overlay 状态机(entering → open / leaving → closed)。WebKit 在 document.visibilityState === 'hidden' 时暂停 rAF 派发,且不发出 transitionend 事件,导致: - open 路径:overlay 永远停在 opacity-0 translate-y-6(用户看不见设置) - close 路径:panel 永远卡在 leaving 状态保留在 DOM 中(每次开关累积) 修复:open 与 close 都加 350ms setTimeout 兜底 + visibilitychange 监听(hidden→visible 时强制 promote)。对称的 promoteEntering / promoteLeaving 收敛状态推进,clearEnterFallback / clearLeaveFallback 互不干扰。open/close 互相切换时清掉对方的 pending timer。 测试:jsdom 模拟 visibilityState 端到端 16/16 assertion 通过(含 hidden close、visible close 保留动画、re-open 时 cancel pending 三场景)
CUA-033/034 的 useSettingsOverlay 修复只覆盖外层 overlay 容器(React-driven opacity/translate 类)。但 .settings-section-enter / .settings-section-title-enter 的 CSS keyframe 动画(settingsSectionIn)在 document.visibilityState === 'hidden' 时由 WebKit 暂停,opacity/transform 卡在 from 状态(opacity:0, translateY(14px) scale(0.985))。结果是 Settings overlay 已 open 但右侧内容空白。 修复:SettingsShell 加 useIsDocumentHidden() 监听 visibilitychange;hidden 时挂 data-anim-suspended='true' + inline style (animation:none; opacity:1; transform:none);visible 时撤回,CSS 动画按原节奏播放。common-settings.css 加匹配 CSS 规则作 defense-in-depth。 测试:typecheck + cargo check 通过;2530 测试通过;cua-driver 截图验证 hidden 状态下右侧主区域内容完整渲染。
根因:Tauri WKWebView 切换 activeSection 时,sidebar 子树的合成图层存在 stale frame 缓存。DOM class、data-active、右侧内容都正确更新,但视觉渲染卡在前一个活动项(bg-accent + font-medium 不掉)。inline style 写入也无法刷新截图。
修复:sidebar nav-item 的 React key 从 definition.id 改为 `${definition.id}-${active ? 'active' : 'idle'}`。activeSection 改变时旧 active 与新 active 按钮都被 React 视为不同元素,触发 unmount + remount,WebView 必须为全新 DOM 节点从零计算样式,绕开 stale 合成图层。
验证:tsc PASS × 2;cua-driver foreground delivery 4 次切换(系统设置→CUA→供应商→系统工具→Remote)均同步转移。
Tauri/Wry WKWebView 对 SVG 元素的 CSS transform/rotate transition 有持续 bug:rotate-180 class 挂到 ChevronDown SVG 上时 Web Animations API 报告 playState=running / currentTime=0 / startTime=null,动画时钟不推进;aria-expanded 已切但视觉不旋转。 修复:把 transition-transform + logExpanded 条件 rotate-180 从 SVG 节点移到外层包装 <span>,SVG 节点只保留 h-3 w-3 尺寸。绕开 SVG transform transition 的 WebView bug,aria-expanded 与 data-testid 不变。 验证:tsc 通过;Vite HMR bundle 已 serve 修复后代码;截图确认展开态 chevron 呈 ∧ 朝上。
…死(CUA-039) CUA-038 把 chevron 从 SVG 移到父级 span(Tailwind rotate-180 + transition-transform)。但 Tailwind v4 的 .rotate-180 编译成 'rotate: 180deg' 独立 CSS 属性,与 .transition-transform 级联顺序不可靠,expand 路径 animation 卡在 currentTime=0。 修复:chevron span 改用 inline style.transform + transition(浏览器原生处理 transition,行为跨 Tailwind 版本稳定)。CDP 实测 expand 与 collapse 双向 currentTime 从 0 推进到 ~33ms,computed transform 正确收敛到 0deg / 180deg。 测试:新增 test/cua/cua-installer-panel-chevron.test.mjs(React+jsdom 4/4 通过);2534 全量测试通过;pnpm typecheck + build 通过。
Closes #617
摘要
为 LiveAgent 增加 CUA (Computer Use Agent) Driver 支持,使 Agent 可通过
cua_*工具族操作 macOS 桌面 GUI(截屏、识别窗口、点击、输入、滚动、拖拽、按键),并通过现有 MCP tool 注册协议暴露给内置 Agent 使用。截图
测试期间产出的关键截图(来自 cua-driver 自动化测试会话,
/tmp/liveagent_round7_*.png与/tmp/cua_round6/):data-testid="open-settings"+aria-label="Open settings"注:截图来自自动化测试 sandbox 环境(headless macOS),用于回归验证,UI 在普通用户会话下应一致。
主要功能
后端 (Rust + Tauri)
services/cua模块:CuaDrivertrait + macOS 实现 (osascript / screencapture / cliclick)CuaStore:enabled/allowed_owners/audit_log_limit配置 + 最近 N 条审计日志,enforce()每条命令前守门CuaError结构化错误(kind+params+message),locale-aware 渲染入口cua_status/set_config/clear_audit/list_windows/focus_window/screenshot/click/double_click/type/key/scroll/dragforce_activate_main_window:NSApp.activate + makeKeyAndOrderFront + makeFirstResponder(WKWebView),带respondsToSelector守门与objc2::exception::catch兜底NSAccessibilityUIElementCreatedNotification广播cua_window_ready/cua_refresh_a11yIPC 命令前端 (agent-gui + agent-ui)
cua_list_windows/focus_window/screenshot/click/double_click/type/key/scroll/dragscreenshot返回多模态 ImageContentCUA 驱动一节(总开关 / 平台标签 / 白名单 / 审计上限 / 清空 / 实时审计列表)settings.cua.*/cua.errors.*/platformNotes.*dev 环境就绪
scripts/dev-prepare.mjs:--watch模式周期 sweep 新出现的 Problem ReporterLIVEAGENT_CUA_DRIVER_NO_OVERLAY=1让 cua-driver 不显示 overlayscripts/dev-stack.mjsdesktop 分支从crates/agent-gui启动,避免根目录启动导致~icons解析失败vite.config.ts加collectionsNodeResolvePath支持跨目录 unplugin-icons权限与安全
allowed_owners列表)守门,非允许目标直接拒绝测试覆盖
9 轮迭代,发现 25 问题,修复 23(medium 6, high 17),2 个 low 仍 Open:
测试结果:
cargo check/cargo test --lib(951 通过)/tsc --noEmit/ 端到端 cua-driver 验证(MCP-bridge workaround)。平台支持
UnsupportedDriverstub,清晰报PlatformError::Unsupported,后续可扩展已知限制
brew install cliclick(AppleScript 无原生 drag API)Info.plist未加NSScreenCaptureUsageDescription/NSAppleEventsUsageDescription(正式分发前需补)ax_window_unresolved(需真实 GUI 会话验证)提交列表