name: cua-vision-hybrid-control
description: Hybrid control loop using Cua Driver (Accessibility) + Vision AI (Screenshot Analysis) for macOS desktop apps. Full pipeline: screencapture → vision_analyze → Cua hotkey/click → screencapture verify.
version: 3.0
Cua + Vision Hybrid Control
時機
當你需要自動化操作 macOS desktop app,但:
- Cua Driver 嘅 AX element tree 唔夠完整(例如 Safari 只出 MenuBar,唔出 content)
- Pixel click 嘅 window-local vs screen coordinate mapping 唔準
- 需要 adaptive vision-based automation loop
Pipeline (支援兩種連接模式)
Mode A: MCP (正常 — 自動 reconnect 需 context 一致)
MCP tools 直接 call → 最快
cua-hotkey / cua-type_text / cua-press_key → screencapture → vision_analyze verify
Mode B: CLI direct (fallback — context compaction/斷線都 work)
用 cua-driver call stdin JSON pipe,唔依賴 MCP 連接
screencapture → vision_analyze →
echo '{"pid":PID,"keys":["cmd","l"]}' | cua-driver call hotkey →
echo '{"pid":PID,"text":"..."}' | cua-driver call type_text →
echo '{"pid":PID,"key":"return"}' | cua-driver call press_key →
screencapture → vision_analyze verify
場景切換: Hermes session 內第一句 call MCP 就得。Context compaction 後 daemon 仲喺度但 MCP client 斷咗 → 轉 CLI direct mode。
步驟
1. 確認 Cua Driver 狀態
cua-driver status
cua-driver permissions status --json
2. Permission bypass
如果未 grant,用 env var 跳過 gate:
CUA_DRIVER_RS_PERMISSIONS_GATE=0 cua-driver serve
bypass 後 AX tree / screenshot 可能唔完整。
3. 截圖(用 Terminal TCC)
screencapture -x /tmp/hybrid_step.png
4. Vision 分析
vision_analyze(
image_url="/tmp/hybrid_step.png",
question="目標UI元件嘅坐標範圍?"
)
5. Cua 操作
Hotkey(最可靠):
echo '{"jsonrpc":"2.0","id":1,"method":"tools/call",
"params":{"name":"hotkey",
"arguments":{"pid":PID,"keys":["cmd","l"]}}}' | cua-driver mcp
Pixel Click(window-local coordinate!):
echo '{"jsonrpc":"2.0","id":1,"method":"tools/call",
"params":{"name":"click",
"arguments":{"pid":PID,"x":X,"y":Y}}}' | cua-driver mcp
Press key(dispatch 去 pid,唔 steal focus):
echo '{"jsonrpc":"2.0","id":1,"method":"tools/call",
"params":{"name":"press_key",
"arguments":{"pid":PID,"key":"escape"}}}' | cua-driver mcp
6. Verify
每次操作後都再截圖 + vision_analyze 驗證。
🏆 實戰驗證 2026-06-29:完整端到端 Computer Use 循環
測試場景
Safari → Google → 搜尋 "Hermes Agent" → 確認第一名結果係 hermes-agent.org ✅
關鍵發現
cua-driver call CLI mode 係 MCP fallback 最佳方案 — 唔需要 MCP connection,直接 echo '{"pid":N,"keys":["cmd","l"]}' | cua-driver call hotkey。context compaction 後 MCP 斷線時用呢個模式。
- type_text 回傳
path:"key_events" 唔係 path:"ax" 都照樣 work — CGEvent fallback 插入文字後按 Return 都可以令 page navigate。
- 唔需要 osascript bring Safari to front — hotkey dispatch 去 pid 就夠,唔 steal focus。
- 每次 operation 後 sleep 0.3-3s 好重要 — 尤其係 type_text 同 Return 之間要等 browser 處理完。
cua-driver call list_windows pid=N 用 stdin JSON pipe — echo '{"pid":N}' | cua-driver call list_windows
完整測試日誌
Step 1: screencapture → vision_analyze → 桌面 Finder ✅
Step 2: hotkey cmd+l → type_text google.com → return → verify Google 首頁 ✅
Step 3: type_text "Hermes Agent" → return → verify 搜尋結果頁面 ✅
成個 loop 由 screenshot → vision → action → verify 完整閉環,process 自始至終冇斷過。
Safari AX Tree 限制
Safari window state 只出 MenuBar items(~530 elements),唔出 content 嘅 AXTextField/AXWebArea。
解決:用 AppleScript browser navigation,或用 Cua hotkey Cmd+L focus URL bar。
實戰證明:Cmd+L 有時會觸發 system shortcut 而非 Safari URL bar(尤其係 Bluetooth control panel 彈出時)。
解決:先 click Safari content area(x=400,y=250)focus window,再用 Cmd+L。
type_text 實戰模式
Cua Driver 嘅 type_text 支援兩種 mode:
- AX path (
"path":"ax"):插入 focused AXTextField(最準確)
- CGEvent fallback (
"path":"key_events"):無 focused element 時用,可加 delay_ms 參數
實戰中 type_text + Return 成功令 Safari navigate 去 URL。
經驗: 即使 type_text 回傳 "path":"key_events"(CGEvent fallback mode),文字仍然成功輸入 URL bar。key_events != fail。
cua-driver call CLI syntax(stdio pipe + 輸出注意)
MCP 斷線時嘅最佳替代方案。
🚨 Critical: Action Tools Return Text, Not JSON
cua-driver call hotkey/type_text/press_key/click 呢啲 action tools 回傳 human-readable text,唔係 JSON。
而 list_windows / get_window_state 呢啲 discovery tools 回傳 JSON。
呢個 inconsistency 係已知嘅 API 設計問題(已準備 GitHub PR fix)。用「python 方式 parsing」—— 先 try JSON decode,fail 咗就當 text 處理。
如果直接寫 bash,留意 output 係 text:
echo '{"pid":4354,"keys":["cmd","l"]}' | cua-driver call hotkey
echo '{"pid":4354,"text":"hello"}' | cua-driver call type_text
echo '{"pid":4354,"key":"return"}' | cua-driver call press_key
echo '{"pid":4354,"x":800,"y":334}' | cua-driver call click
推薦做法:用 temp file pipe 代替 inline echo(避免 shell escaping bug):
cat /tmp/args.json | cua-driver call hotkey
Python 做法:用 core.py 嘅 cua_call() — 自動 handle text/JSON 雙重輸出:
from hybrid_vision_loop.core import cua_call
result = cua_call("hotkey", {"pid": 4354, "keys": ["cmd", "l"]})
Permission bypass 完整流程
cua-driver stop
export CUA_DRIVER_RS_PERMISSIONS_GATE=0
cua-driver mcp
echo '{"jsonrpc":"2.0","id":1,"method":"tools/call",
"params":{"name":"get_screen_size","arguments":{}}}' | cua-driver mcp
完整端到端 Computer Use Demo(已實戰驗證)
Safari → Google → 搜尋 "Hermes Agent" → 確認結果 ✅
完整指令序列(實戰 work)
cua-driver status
screencapture -x /tmp/s1.png
echo '{"pid":SAFARI_PID,"keys":["cmd","l"]}' | cua-driver call hotkey
sleep 0.5
echo '{"pid":SAFARI_PID,"text":"https://www.google.com"}' | cua-driver call type_text
sleep 0.3
echo '{"pid":SAFARI_PID,"key":"return"}' | cua-driver call press_key
sleep 3
screencapture -x /tmp/s2.png
echo '{"pid":SAFARI_PID,"text":"Hermes Agent"}' | cua-driver call type_text
sleep 0.3
echo '{"pid":SAFARI_PID,"key":"return"}' | cua-driver call press_key
sleep 3
screencapture -x /tmp/s3.png
System-level dialogs(Edge permission prompt)
唔屬於任何 pid 嘅 child window,dispatch 去 pid 會 miss。
解決:用 AppleScript 或手動 dismiss。
AppleScript keystroke 限制
macOS SIP/TCC block osascript keystroke — 用 Cua hotkey 代替。
Hermes MCP connection caching
第一次連線 fail 後 cache 做 unreachable 直到 session 結束。
解決:
- raw stdio pipe:
echo '...' | cua-driver mcp
delegate_task spawn 新 context
- 唔好糾纏 MCP unreachable
Window-local vs Screen coordinates (with Retina scaling)
Cua Driver pixel click 嘅 (x,y) 係 window-local points,唔係 screen pixels。
Retina M1 嘅要點:
screencapture -x 出 3200×1800 pixels(backing store resolution)
list_windows 回傳嘅 bounds 係 logical points(1600×900)
- scale_factor = 2.0
- 換算公式:
window_local_x = (screenshot_pixel_x / scale_factor) - window_bounds.x
reverse: screenshot_pixel_x = (window_local_x + window_bounds.x) * scale_factor
實戰驗證 2026-06-29:
- Safari window bounds = (0.0, 30.0) 1600.0×796.0 logical points
- Window-local click at (800.0, 334.32) → 成功 hit Google search bar
- 然後 type "pixel click test" → text 正確顯示 + autosuggestions 出現 ✅
證明 CoordinateConverter 數學完全正確。
cc = CoordinateConverter(screen_size=(1600, 900), scale_factor=2.0)
wx, wy = cc.screenshot_to_window(sx, sy, window_bounds)
常用 shortcuts
- Cmd+L: Safari/Chrome URL bar focus
- Cmd+T: New tab
- Cmd+W: Close tab/window
- Cmd+R: Refresh
- Escape: Dismiss dialog
- Return/Enter: Confirm
陷阱
- Action tools 回傳 text 唔係 JSON —
cua-driver call hotkey/click/press_key 回傳 human-readable text。只有 list_windows/get_window_state 回傳 JSON。用 Python cua_call() 自動處理。
- Pixel click coordinates — Cua Driver 係 window-local space,唔係 screen space。要用
list_windows 攞 bounds 換算。Retina M1 有 2x scaling。
- Permission dialog 係 system-level — dispatch 去 pid 會 miss。用 AppleScript 或手動 dismiss。
- Safari 多 windows — 每次操作可能產生新 window。用
list_windows(pid) check 最新 window_id。
- screencapture filesize — 1600×900 PNG ~400KB-1MB。每次 overwrite 舊 file。
- MCP unreachable caching — 第一次 fail 後成個 session cache。用 raw stdio pipe bypass。