feat(cua-driver): embedded mode — inherit the host app's TCC grants, never prompt#2102
Conversation
|
@injaneity is attempting to deploy a commit to the Cua Team on Vercel. A member of the Team first needs to authorize it. |
|
Important Review skippedAuto incremental reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughThis PR adds an "embedded mode" to cua-driver allowing it to run inside a macOS host app and inherit the host's Accessibility and Screen Recording TCC grants. Changes span core config constants, CLI flags/docs, responsibility disclaim logic, permissions gate opt-out, check_permissions attribution, an example Swift host harness with demo script, and documentation. ChangesEmbedded mode implementation
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant Host as Host App
participant Driver as cua-driver (embedded)
participant Tool as CheckPermissionsTool
Host->>Driver: spawn as direct child (CUA_DRIVER_EMBEDDED=1)
Host->>Driver: initialize / notifications/initialized
Host->>Driver: check_permissions(prompt=true)
Driver->>Tool: invoke()
Tool->>Tool: embedded_mode() true, suppress prompt
Tool->>Tool: permission_source() -> attribution "host"
Tool-->>Driver: source.attribution=host, embedded=true
Driver-->>Host: JSON-RPC response
Host->>Driver: launch_app, get_window_state, move_agent_cursor
Driver-->>Host: screenshots, AX tree, cursor moves
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
7ec28d2 to
bd110a4
Compare
Adds explicit embedded mode (CUA_DRIVER_EMBEDDED=1 / --embedded [--host-bundle-id <id>]) so a host app (e.g. OpenClaw) can embed the driver as a direct child and have macOS attribute Accessibility + Screen Recording to the host it already trusted — one grant, one Settings entry, no com.trycua.driver prompts. In embedded mode the driver: - skips the responsibility-disclaim re-exec (stays in the host's chain) - skips the daemon auto-relaunch via the CuaDriver.app bundle - opts out of the startup permissions gate and never calls request_accessibility / request_screen_recording (check_permissions ignores prompt=true) - reports check_permissions source.attribution = "host" with the advisory host bundle id, keeping the live SCShareableContent probe Fail-closed property preserved: the caller-controlled env var only ever downgrades attribution (host, never driver-daemon); daemon attribution stays keyed on the non-spoofable bundle-path signal. Standalone behavior is unchanged when the flag is off. Ships Skills/cua-driver/EMBEDDING.md (integration guide + troubleshooting) and examples/embedded-host-macos/ (signed AppKit reference host + one-grant demo script).
bd110a4 to
826fbef
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
libs/cua-driver/rust/Skills/cua-driver/EMBEDDING.md (1)
149-295: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winManual doc/code sync has no automated guardrail.
This code block is a manual mirror of
ExampleAgentHarness.swift, kept in sync only by comment convention on both sides. Consider a small CI check that extracts this fenced block and diffs it against the actual source file, so drift fails the build instead of silently accumulating.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@libs/cua-driver/rust/Skills/cua-driver/EMBEDDING.md` around lines 149 - 295, The EMBEDDING.md mirrored ExampleAgentHarness.swift block can drift from the real source because there is no automated sync check. Add a small CI guardrail that extracts the fenced “Minimal host example” block from EMBEDDING.md and diffs it against ExampleAgentHarness.swift, so any mismatch fails the build; keep the check keyed to the shared ExampleAgentHarness reference and the markdown block itself.libs/cua-driver/rust/examples/embedded-host-macos/ExampleAgentHarness.swift (1)
79-89: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
call()silently swallows JSON-RPC error responses.If the driver returns a JSON-RPC
errorinstead ofresult(e.g. a bad tool call),call()returns[:]with no indication of what failed. Given this harness exists specifically to surface embedding problems, logging the error payload would make failures far easier to diagnose.♻️ Suggested diff
while true { let msg = readMessage() if msg["id"] as? Int == nextId { - return msg["result"] as? [String: Any] ?? [:] + if let error = msg["error"] as? [String: Any] { + log("RPC error for \(tool): \(error)") + } + return msg["result"] as? [String: Any] ?? [:] } }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@libs/cua-driver/rust/examples/embedded-host-macos/ExampleAgentHarness.swift` around lines 79 - 89, The call(_:_:) helper currently ignores JSON-RPC error responses and returns an empty dictionary, which hides tool-call failures. Update ExampleAgentHarness.call to inspect each matching message for an error payload as well as result, and when tools/call returns an error, log or print the error details before continuing or failing the call path. Use the existing call, readMessage, and send flow to surface the JSON-RPC error information instead of silently returning [:].
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@libs/cua-driver/rust/examples/embedded-host-macos/demo.sh`:
- Around line 73-76: The wait loop in the demo harness is watching for a log
string that never appears, so it can’t exit early on the missing-grant branch.
Update the grep condition in the loop around the demo completion check to match
the actual harness log text emitted for missing items (the “grant the missing
item(s)...” message), while still preserving the existing DEMO COMPLETE exit
path. Locate this in the shell loop that polls "$LOG" so the early break
triggers for both completion and missing-grant cases.
In `@libs/cua-driver/rust/examples/embedded-host-macos/ExampleAgentHarness.swift`:
- Around line 1-15: Add the required SPDX license header to ExampleAgentHarness
so CI recognizes the file’s license metadata; place the SPDX identifier at the
top of the file before the existing introductory comments, and keep the
EMBEDDING.md mirrored example content unchanged otherwise.
---
Nitpick comments:
In `@libs/cua-driver/rust/examples/embedded-host-macos/ExampleAgentHarness.swift`:
- Around line 79-89: The call(_:_:) helper currently ignores JSON-RPC error
responses and returns an empty dictionary, which hides tool-call failures.
Update ExampleAgentHarness.call to inspect each matching message for an error
payload as well as result, and when tools/call returns an error, log or print
the error details before continuing or failing the call path. Use the existing
call, readMessage, and send flow to surface the JSON-RPC error information
instead of silently returning [:].
In `@libs/cua-driver/rust/Skills/cua-driver/EMBEDDING.md`:
- Around line 149-295: The EMBEDDING.md mirrored ExampleAgentHarness.swift block
can drift from the real source because there is no automated sync check. Add a
small CI guardrail that extracts the fenced “Minimal host example” block from
EMBEDDING.md and diffs it against ExampleAgentHarness.swift, so any mismatch
fails the build; keep the check keyed to the shared ExampleAgentHarness
reference and the markdown block itself.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 7e2966c5-b66e-4753-8555-bc5f962dff3c
📒 Files selected for processing (12)
docs/content/docs/reference/cua-driver/cli-reference.mdxlibs/cua-driver/rust/Skills/cua-driver/EMBEDDING.mdlibs/cua-driver/rust/Skills/cua-driver/README.mdlibs/cua-driver/rust/crates/cua-driver-core/src/lib.rslibs/cua-driver/rust/crates/cua-driver/src/cli.rslibs/cua-driver/rust/crates/cua-driver/src/responsibility.rslibs/cua-driver/rust/crates/platform-macos/src/permissions/gate.rslibs/cua-driver/rust/crates/platform-macos/src/permissions/mod.rslibs/cua-driver/rust/crates/platform-macos/src/tools/check_permissions.rslibs/cua-driver/rust/examples/embedded-host-macos/.gitignorelibs/cua-driver/rust/examples/embedded-host-macos/ExampleAgentHarness.swiftlibs/cua-driver/rust/examples/embedded-host-macos/demo.sh
2cb4061 to
71272fc
Compare
Adds an explicit embedded mode to cua-driver-rs so a host macOS app (an agent harness) can run the driver as a child process and have it inherit the host's Accessibility + Screen Recording grants — one permission prompt total, one entry in System Settings, no
com.trycua.driverdialogs. Activated viaCUA_DRIVER_EMBEDDED=1orcua-driver mcp --embedded [--host-bundle-id <id>].Why
Standalone cua-driver deliberately makes itself its own TCC responsible process (disclaim re-exec +
open -a CuaDriverrelaunch) so grants attach to a stable bundle identity. That's right for standalone, wrong for embedding: a harness that already holds both grants ships a second app identity and its users hit a second set of permission prompts. macOS attributes TCC to the responsible process, so a directly-spawned child inherits the host's grants for free — the driver just has to stop opting out.What changed
Behavior (~25 lines, all gated on the flag; standalone is unchanged when it's off):
responsibility.rs— skip the disclaim re-exec (stay in the host's chain). Decision factored into a pureshould_skip_disclaim()seam for testability.cli.rsshould_use_daemon_proxy— never auto-relaunch via theCuaDriver.appbundle (both platform branches; an embedded driver answers in-process).gate.rs) — embedded mode opts out; the driver never raises its own prompts or panel.check_permissions—prompt:trueis ignored (this + the gate are the only tworequest_*call sites, so prompts are unreachable), andpermission_source()reportsattribution: "host"with the advisory host bundle id and adisclaim_envdebug field. The liveSCShareableContentcapture probe is untouched.Docs + example:
Skills/cua-driver/EMBEDDING.md— integration guide: TCC responsibility model, launch contract, standalone-vs-embedded table,check_permissionsattribution semantics, copy-paste host example, troubleshooting (second prompt / black screenshot / empty AX / re-sign), Windows/Linux notes. Ships in the skill pack, so it links by repo path, not relative links.examples/embedded-host-macos/—ExampleAgentHarness.swift, a 122-line no-UI reference host mirrored verbatim in the guide (grants → embedded spawn → attribution check → background screenshot → background AX read → agent-cursor glide), plus a self-verifyingdemo.sh(build + sign +tccutil reset+ tail-until-DEMO COMPLETE: PASS).--embedded/--host-bundle-idadded to--help,manifest, anddump-docs;cli-reference.mdxregenerated viascripts/docs-generators/cua-driver.ts(no hand edits to generated docs).Security notes
host). It never feeds thedriver-daemonattribution decision, which stays keyed on the non-spoofable bundle-path signal. New tests pin this, including embedded + disclaim env set together never yieldingdriver-daemon.1enables the mode (fail-safe);--host-bundle-idis an advisory label, not a trust signal — trust comes from the OS responsibility chain.Process/posix_spawn); launching viaopen(1)/NSWorkspacebreaks inheritance. Documented, with the TCCAttributionChainlog-stream command for debugging.Testing
gateandcheck_permissionsnow share one crate-wide lock (they race under separate per-module mutexes).swiftc; verified byte-identical to each other.tccutil): host granted once, embedded driver started with zero driver-side prompts,check_permissionsreportedattribution: hostwith the live capture probe true, background Finder AX tree + window screenshot returned, agent-cursor overlay glided,DEMO COMPLETE: PASS. Settings listed only the harness — no CuaDriver entry. Still not CI-able (needs interactive grants).tccutil reseton re-runs);get_window_stateneedspid+window_idvialaunch_app; stock macOS has notimeout(1).Not in this PR
cua-driver-uiaworker — the host must manage it), and Wayland capture (XDG portals prompt per-session and cannot be pre-granted; X11 is the supported path).Summary by CodeRabbit
New Features
cua-driver, including a host bundle ID label and updated permission behavior for host-owned grants.Documentation