Files
wehub-resource-sync 91e75e620b
CI: cua-driver distro-compat matrix / debian:12 (glibc 2.36) (push) Has been cancelled
CI: SPDX Headers / Check SPDX headers (warn-only) (push) Has been cancelled
CD: Docs MCP Server / build (linux/amd64) (push) Has been cancelled
CD: Docs MCP Server / build (linux/arm64) (push) Has been cancelled
CD: Docs MCP Server / merge (push) Has been cancelled
CI: cua-driver distro-compat matrix / Resolve release version (push) Has been cancelled
CI: cua-driver distro-compat matrix / fedora:41 (glibc 2.40) (push) Has been cancelled
CI: cua-driver distro-compat matrix / rockylinux:9 (glibc 2.34) (push) Has been cancelled
CI: cua-driver distro-compat matrix / ubuntu:22.04 (glibc 2.35) (push) Has been cancelled
CI: cua-driver distro-compat matrix / ubuntu:24.04 (glibc 2.39) (push) Has been cancelled
CI: cua-driver distro-compat matrix / Distro compat summary (push) Has been cancelled
CI: Rust Linux unit / Rust Linux unit and compile (push) Has been cancelled
CI: Rust Windows unit / Rust Windows unit and compile (push) Has been cancelled
CI: Nix Linux Rust source / Nix / compositor build (push) Has been cancelled
CI: Nix Linux Rust source / Nix / driver package (push) Has been cancelled
CI: Nix Linux Rust source / Nix / Rust unit tests (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:03:19 +08:00

145 lines
6.2 KiB
Swift

// SPDX-License-Identifier: MIT
// Copyright (c) 2026 Cua AI, Inc.
// ExampleAgentHarness — minimal reference host for embedding cua-driver.
// Mirrored verbatim in Skills/cua-driver/EMBEDDING.md ("Minimal host
// example") — keep the two in sync.
//
// Runs the one-grant demo sequence from EMBEDDING.md end to end:
// 1. Requests Accessibility + Screen Recording AS THE HOST (the only
// prompts the user ever sees), then
// 2. spawns cua-driver as a direct child in embedded mode and, over
// stdio MCP, verifies attribution, takes a background screenshot,
// reads a background app's window state, and glides the agent-cursor
// overlay — with zero driver-side prompts.
//
// Launched via `open` (see demo.sh) the app has no terminal, so all
// output also goes to /tmp/cua-embedded-demo.log.
import Foundation
import ApplicationServices
import CoreGraphics
let logPath = "/tmp/cua-embedded-demo.log"
FileManager.default.createFile(atPath: logPath, contents: nil)
let logFile = FileHandle(forWritingAtPath: logPath)!
func log(_ line: String) {
print(line)
logFile.write((line + "\n").data(using: .utf8)!)
}
// 1. Request both grants AS THE HOST — the only prompts in the whole flow.
let axOpts = ["AXTrustedCheckOptionPrompt": true] as CFDictionary
let ax = AXIsProcessTrustedWithOptions(axOpts)
let sr = CGRequestScreenCaptureAccess()
log("host grants — accessibility: \(ax), screen recording: \(sr)")
// Keep going even without grants: the run registers BOTH rows in one pass
// (the AX request above, plus — on newer macOS, where the app only appears
// in the Screen Recording pane after a real ScreenCaptureKit attempt — the
// embedded driver's live probe below, registered as THE HOST, which is the
// point of embedding). Grant both in one Settings visit, then re-run.
if !ax || !sr {
log("after this run: grant the missing item(s) in System Settings, then re-run")
}
// 2. Spawn cua-driver as a DIRECT child (never via `open`/NSWorkspace —
// that breaks responsibility inheritance) in embedded mode.
let driverPath = ProcessInfo.processInfo.environment["CUA_DRIVER_PATH"]
?? "/usr/local/bin/cua-driver"
let driver = Process()
driver.executableURL = URL(fileURLWithPath: driverPath)
driver.arguments = ["mcp"]
var env = ProcessInfo.processInfo.environment
env["CUA_DRIVER_EMBEDDED"] = "1"
env["CUA_DRIVER_HOST_BUNDLE_ID"] = Bundle.main.bundleIdentifier ?? ""
driver.environment = env
let toDriver = Pipe(), fromDriver = Pipe()
driver.standardInput = toDriver
driver.standardOutput = fromDriver
try driver.run()
// 3. Line-delimited JSON-RPC 2.0 over the child's stdio.
var buffer = Data()
func send(_ obj: [String: Any]) {
var data = try! JSONSerialization.data(withJSONObject: obj)
data.append(0x0A)
toDriver.fileHandleForWriting.write(data)
}
func readMessage() -> [String: Any] {
while true {
if let nl = buffer.firstIndex(of: 0x0A) {
let line = buffer.subdata(in: buffer.startIndex..<nl)
buffer.removeSubrange(buffer.startIndex...nl)
if line.isEmpty { continue }
return (try? JSONSerialization.jsonObject(with: line)) as? [String: Any] ?? [:]
}
let chunk = fromDriver.fileHandleForReading.availableData
if chunk.isEmpty { log("driver exited unexpectedly"); exit(1) }
buffer.append(chunk)
}
}
var nextId = 0
func call(_ tool: String, _ args: [String: Any] = [:]) -> [String: Any] {
nextId += 1
send(["jsonrpc": "2.0", "id": nextId, "method": "tools/call",
"params": ["name": tool, "arguments": args]])
while true {
let msg = readMessage()
if msg["id"] as? Int == nextId {
if let error = msg["error"] as? [String: Any] {
log("RPC error for \(tool): \(error)")
}
return msg["result"] as? [String: Any] ?? [:]
}
}
}
nextId += 1
send(["jsonrpc": "2.0", "id": nextId, "method": "initialize", "params": [
"protocolVersion": "2024-11-05", "capabilities": [:],
"clientInfo": ["name": "ExampleAgentHarness", "version": "0.1"]]])
_ = readMessage()
send(["jsonrpc": "2.0", "method": "notifications/initialized"])
log("embedded cua-driver started (\(driverPath)) — no driver prompt should have appeared")
// 4. check_permissions must report attribution "host" and never prompt.
let perms = call("check_permissions")
let structured = perms["structuredContent"] as? [String: Any] ?? [:]
let source = structured["source"] as? [String: Any] ?? [:]
let attribution = source["attribution"] as? String ?? "?"
log("check_permissions — attribution: \(attribution) (want: host), " +
"capturable: \(structured["screen_recording_capturable"] ?? "?")")
// 5. Background AX read + window screenshot — proves both grants
// inherited without focusing anything. launch_app resolves pid +
// windows without foregrounding; get_window_state returns the AX
// element tree AND a screenshot of the (background) window.
let launch = call("launch_app", ["bundle_id": "com.apple.finder"])
let launched = launch["structuredContent"] as? [String: Any] ?? [:]
let pid = launched["pid"] as? Int ?? 0
let windows = launched["windows"] as? [[String: Any]] ?? []
let windowId = windows.first?["window_id"] as? Int ?? 0
log("launch_app(Finder) — pid: \(pid), windows: \(windows.count)")
let state = call("get_window_state", ["pid": pid, "window_id": windowId])
let images = (state["content"] as? [[String: Any]] ?? [])
.filter { $0["type"] as? String == "image" }
let hasTree = (state["structuredContent"] as? [String: Any])?["elements"] != nil
log("get_window_state(Finder) — tree: \(hasTree ? "ok" : "EMPTY"), " +
"screenshot: \(images.count) image(s) (want: ≥1)")
// 6. Agent-cursor glide — shows the overlay, no real-pointer move.
log("watch the agent cursor glide now (no real-pointer move)…")
let cursor1 = call("move_cursor", ["x": 200, "y": 200])
Thread.sleep(forTimeInterval: 2)
let cursor2 = call("move_cursor", ["x": 900, "y": 500])
Thread.sleep(forTimeInterval: 2)
let cursorOk = (cursor1["isError"] as? Bool) != true &&
(cursor2["isError"] as? Bool) != true
log("move_cursor — \(cursorOk ? "ok" : "FAILED")")
let pass = attribution == "host" && !images.isEmpty && hasTree && cursorOk
log(pass ? "DEMO COMPLETE: PASS" : "DEMO COMPLETE: FAIL")
driver.terminate()
exit(pass ? 0 : 1)