diff --git a/apps/.i18n/native-source.json b/apps/.i18n/native-source.json index 29105f23275e..2a2d45e01f87 100644 --- a/apps/.i18n/native-source.json +++ b/apps/.i18n/native-source.json @@ -29907,7 +29907,7 @@ }, { "kind": "conditional-branch", - "line": 767, + "line": 766, "path": "apps/macos/Sources/OpenClaw/AppState.swift", "source": "\\(user)@\\(host)", "surface": "apple", @@ -29915,7 +29915,7 @@ }, { "kind": "conditional-branch", - "line": 767, + "line": 766, "path": "apps/macos/Sources/OpenClaw/AppState.swift", "source": "\\(user)@\\(host):\\(port)", "surface": "apple", @@ -30697,6 +30697,46 @@ "surface": "apple", "id": "native.apple.cac171668ece3a4d" }, + { + "kind": "conditional-branch", + "line": 346, + "path": "apps/macos/Sources/OpenClaw/ComputerActionService.swift", + "source": "Granted", + "surface": "apple", + "id": "native.apple.c43101e53076ff01" + }, + { + "kind": "conditional-branch", + "line": 347, + "path": "apps/macos/Sources/OpenClaw/ComputerActionService.swift", + "source": "Missing permission", + "surface": "apple", + "id": "native.apple.1312a2f2fcfa9685" + }, + { + "kind": "conditional-branch", + "line": 348, + "path": "apps/macos/Sources/OpenClaw/ComputerActionService.swift", + "source": "Accessibility grant may be stale", + "surface": "apple", + "id": "native.apple.a7f2febd7dbf8978" + }, + { + "kind": "conditional-branch", + "line": 355, + "path": "apps/macos/Sources/OpenClaw/ComputerActionService.swift", + "source": "Accessibility, Event Posting, and Screen Recording are granted.", + "surface": "apple", + "id": "native.apple.7d37e7dfcd4b37c1" + }, + { + "kind": "conditional-branch", + "line": 357, + "path": "apps/macos/Sources/OpenClaw/ComputerActionService.swift", + "source": "Missing: \\(buckets.map(\\.displayName).joined(separator: \", \")). Grant access in System Settings → Privacy & Security, then reopen OpenClaw.", + "surface": "apple", + "id": "native.apple.055a88d0f9d3d2fe" + }, { "kind": "ui-call", "line": 82, @@ -32843,7 +32883,7 @@ }, { "kind": "ui-named-argument", - "line": 76, + "line": 83, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "General", "surface": "apple", @@ -32851,7 +32891,7 @@ }, { "kind": "ui-named-argument", - "line": 77, + "line": 84, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Everyday OpenClaw app behavior.", "surface": "apple", @@ -32859,7 +32899,7 @@ }, { "kind": "ui-call", - "line": 81, + "line": 88, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "App", "surface": "apple", @@ -32867,7 +32907,7 @@ }, { "kind": "ui-named-argument", - "line": 83, + "line": 90, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Launch at login", "surface": "apple", @@ -32875,7 +32915,7 @@ }, { "kind": "conditional-branch", - "line": 85, + "line": 92, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Automatically start OpenClaw after you sign in.", "surface": "apple", @@ -32883,7 +32923,7 @@ }, { "kind": "conditional-branch", - "line": 86, + "line": 93, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Move OpenClaw to Applications before enabling launch at login.", "surface": "apple", @@ -32891,7 +32931,7 @@ }, { "kind": "ui-named-argument", - "line": 91, + "line": 98, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Show Dock icon", "surface": "apple", @@ -32899,7 +32939,7 @@ }, { "kind": "ui-named-argument-multiline", - "line": 92, + "line": 99, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Keep OpenClaw visible in the Dock. When off, windows still show the Dock icon while open.", "surface": "apple", @@ -32907,7 +32947,7 @@ }, { "kind": "ui-named-argument", - "line": 98, + "line": 105, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Play menu bar icon animations", "surface": "apple", @@ -32915,7 +32955,7 @@ }, { "kind": "ui-named-argument", - "line": 99, + "line": 106, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Enable idle blinks and wiggles on the status icon.", "surface": "apple", @@ -32923,7 +32963,7 @@ }, { "kind": "ui-named-argument", - "line": 103, + "line": 110, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Quick Chat", "surface": "apple", @@ -32931,7 +32971,7 @@ }, { "kind": "ui-named-argument", - "line": 104, + "line": 111, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Show a floating composer for quick messages, summoned with a global shortcut.", "surface": "apple", @@ -32939,7 +32979,7 @@ }, { "kind": "ui-named-argument", - "line": 108, + "line": 115, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Quick Chat shortcut", "surface": "apple", @@ -32947,7 +32987,7 @@ }, { "kind": "ui-named-argument", - "line": 109, + "line": 116, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Global shortcut that opens a floating chat bar for the main thread.", "surface": "apple", @@ -32955,7 +32995,7 @@ }, { "kind": "ui-call", - "line": 117, + "line": 124, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Capabilities", "surface": "apple", @@ -32963,7 +33003,7 @@ }, { "kind": "ui-named-argument", - "line": 119, + "line": 126, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Allow Canvas", "surface": "apple", @@ -32971,7 +33011,7 @@ }, { "kind": "ui-named-argument", - "line": 120, + "line": 127, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Allow the agent to show and control the Canvas panel.", "surface": "apple", @@ -32979,7 +33019,7 @@ }, { "kind": "ui-named-argument", - "line": 124, + "line": 131, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Allow Camera", "surface": "apple", @@ -32987,7 +33027,7 @@ }, { "kind": "ui-named-argument", - "line": 125, + "line": 132, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Allow the agent to capture a photo or short video via the built-in camera.", "surface": "apple", @@ -32995,7 +33035,7 @@ }, { "kind": "ui-named-argument", - "line": 129, + "line": 136, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Allow Computer Control", "surface": "apple", @@ -33003,15 +33043,23 @@ }, { "kind": "ui-named-argument-multiline", - "line": 130, + "line": 137, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", - "source": "Let an authorized agent move the pointer, click, and type on this Mac. Also requires Accessibility, Screen Recording, and gateway command authorization. High risk.", + "source": "Starts enabled. After this Mac is paired and macOS access is granted, the paired Gateway can move the pointer, click, and type without per-action confirmation. High risk.", "surface": "apple", - "id": "native.apple.124d07cfaa03f5e5" + "id": "native.apple.a3d78847eaebbbd9" }, { "kind": "ui-named-argument", - "line": 137, + "line": 144, + "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", + "source": "Computer Control access", + "surface": "apple", + "id": "native.apple.96eed7824b1f1ed6" + }, + { + "kind": "ui-named-argument", + "line": 157, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Enable Peekaboo Bridge", "surface": "apple", @@ -33019,7 +33067,7 @@ }, { "kind": "ui-named-argument-multiline", - "line": 138, + "line": 158, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Allow signed tools (e.g. `peekaboo`) to drive UI automation via PeekabooBridge. Requires Computer Control; otherwise run Peekaboo's own Mac app.", "surface": "apple", @@ -33027,7 +33075,7 @@ }, { "kind": "ui-call", - "line": 147, + "line": 167, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Browser", "surface": "apple", @@ -33035,7 +33083,7 @@ }, { "kind": "ui-named-argument", - "line": 149, + "line": 169, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Browser login", "surface": "apple", @@ -33043,7 +33091,7 @@ }, { "kind": "ui-named-argument", - "line": 150, + "line": 170, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Copy cookies from a Chrome-family profile into an isolated managed profile.", "surface": "apple", @@ -33051,7 +33099,7 @@ }, { "kind": "ui-call", - "line": 153, + "line": 173, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Import…", "surface": "apple", @@ -33059,7 +33107,7 @@ }, { "kind": "ui-call", - "line": 175, + "line": 195, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Developer", "surface": "apple", @@ -33067,7 +33115,7 @@ }, { "kind": "ui-named-argument", - "line": 177, + "line": 197, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Enable debug tools", "surface": "apple", @@ -33075,7 +33123,7 @@ }, { "kind": "ui-named-argument", - "line": 178, + "line": 198, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Show the Debug page with development utilities.", "surface": "apple", @@ -33083,7 +33131,7 @@ }, { "kind": "ui-call", - "line": 185, + "line": 205, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "App session", "surface": "apple", @@ -33091,7 +33139,7 @@ }, { "kind": "ui-call", - "line": 187, + "line": 207, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Quit only when you want to stop the menu bar app completely.", "surface": "apple", @@ -33099,7 +33147,7 @@ }, { "kind": "ui-call", - "line": 192, + "line": 212, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Quit", "surface": "apple", @@ -33107,7 +33155,7 @@ }, { "kind": "conditional-branch", - "line": 212, + "line": 232, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "OpenClaw paused", "surface": "apple", @@ -33115,7 +33163,7 @@ }, { "kind": "ui-call", - "line": 222, + "line": 242, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "OpenClaw active", "surface": "apple", @@ -33123,7 +33171,7 @@ }, { "kind": "conditional-branch", - "line": 241, + "line": 261, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Processing messages through the local Gateway on this Mac.", "surface": "apple", @@ -33131,7 +33179,7 @@ }, { "kind": "conditional-branch", - "line": 243, + "line": 263, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Connected to a remote Gateway configuration.", "surface": "apple", @@ -33139,7 +33187,7 @@ }, { "kind": "conditional-branch", - "line": 245, + "line": 265, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Ready to run after you choose a Gateway connection.", "surface": "apple", @@ -33147,7 +33195,7 @@ }, { "kind": "ui-named-argument", - "line": 252, + "line": 272, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Connection", "surface": "apple", @@ -33155,7 +33203,7 @@ }, { "kind": "ui-named-argument", - "line": 253, + "line": 273, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Choose where the Gateway runs and how this Mac app reaches it.", "surface": "apple", @@ -33163,7 +33211,7 @@ }, { "kind": "ui-call", - "line": 318, + "line": 359, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "\\(Int(ping)) ms", "surface": "apple", @@ -33171,7 +33219,7 @@ }, { "kind": "conditional-branch", - "line": 352, + "line": 393, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Local Gateway", "surface": "apple", @@ -33179,7 +33227,7 @@ }, { "kind": "conditional-branch", - "line": 353, + "line": 394, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Remote Gateway direct", "surface": "apple", @@ -33187,7 +33235,7 @@ }, { "kind": "conditional-branch", - "line": 353, + "line": 394, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Remote Gateway via SSH", "surface": "apple", @@ -33195,7 +33243,7 @@ }, { "kind": "conditional-branch", - "line": 354, + "line": 395, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Gateway not configured", "surface": "apple", @@ -33203,7 +33251,7 @@ }, { "kind": "conditional-branch", - "line": 361, + "line": 402, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "OpenClaw starts and monitors the Gateway on this Mac.", "surface": "apple", @@ -33211,7 +33259,7 @@ }, { "kind": "conditional-branch", - "line": 370, + "line": 411, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Choose local or remote before the app can attach to a Gateway.", "surface": "apple", @@ -33219,7 +33267,7 @@ }, { "kind": "ui-call", - "line": 375, + "line": 416, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Gateway", "surface": "apple", @@ -33227,7 +33275,7 @@ }, { "kind": "ui-named-argument", - "line": 377, + "line": 418, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "OpenClaw runs", "surface": "apple", @@ -33235,7 +33283,7 @@ }, { "kind": "ui-named-argument", - "line": 378, + "line": 419, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Pick whether this app owns a local Gateway or attaches to another host.", "surface": "apple", @@ -33243,7 +33291,7 @@ }, { "kind": "ui-call", - "line": 381, + "line": 422, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Gateway location", "surface": "apple", @@ -33251,7 +33299,7 @@ }, { "kind": "ui-call", - "line": 382, + "line": 423, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Not configured", "surface": "apple", @@ -33259,7 +33307,7 @@ }, { "kind": "ui-call", - "line": 383, + "line": 424, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Local (this Mac)", "surface": "apple", @@ -33267,7 +33315,7 @@ }, { "kind": "ui-call", - "line": 384, + "line": 425, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Remote (another host)", "surface": "apple", @@ -33275,7 +33323,7 @@ }, { "kind": "ui-named-argument", - "line": 393, + "line": 434, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Setup needed", "surface": "apple", @@ -33283,7 +33331,7 @@ }, { "kind": "ui-named-argument-multiline", - "line": 394, + "line": 435, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Local is best for this Mac. Remote is best when the Gateway already runs on a Mac Studio or server.", "surface": "apple", @@ -33291,7 +33339,7 @@ }, { "kind": "ui-call", - "line": 425, + "line": 466, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Remote Access", "surface": "apple", @@ -33299,7 +33347,7 @@ }, { "kind": "ui-call", - "line": 436, + "line": 477, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Discovery & Status", "surface": "apple", @@ -33307,7 +33355,7 @@ }, { "kind": "ui-call", - "line": 452, + "line": 493, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Nearby gateways", "surface": "apple", @@ -33315,7 +33363,7 @@ }, { "kind": "ui-named-argument", - "line": 475, + "line": 516, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Remote test", "surface": "apple", @@ -33323,7 +33371,7 @@ }, { "kind": "ui-named-argument", - "line": 483, + "line": 524, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Control channel", "surface": "apple", @@ -33331,7 +33379,7 @@ }, { "kind": "ui-named-argument", - "line": 512, + "line": 553, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Recommended setup", "surface": "apple", @@ -33339,7 +33387,7 @@ }, { "kind": "conditional-branch", - "line": 514, + "line": 555, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Use Tailscale plus an SSH tunnel for stable private access.", "surface": "apple", @@ -33347,7 +33395,7 @@ }, { "kind": "conditional-branch", - "line": 515, + "line": 556, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Use Tailscale Serve so the gateway has a valid HTTPS certificate.", "surface": "apple", @@ -33355,7 +33403,7 @@ }, { "kind": "ui-call", - "line": 524, + "line": 565, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Advanced", "surface": "apple", @@ -33363,7 +33411,7 @@ }, { "kind": "ui-call", - "line": 527, + "line": 568, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Identity file", "surface": "apple", @@ -33371,7 +33419,7 @@ }, { "kind": "ui-named-argument", - "line": 529, + "line": 570, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "/Users/you/.ssh/id_ed25519", "surface": "apple", @@ -33379,7 +33427,7 @@ }, { "kind": "ui-call", - "line": 531, + "line": 572, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Project root", "surface": "apple", @@ -33387,7 +33435,7 @@ }, { "kind": "ui-named-argument", - "line": 533, + "line": 574, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "/home/you/Projects/openclaw", "surface": "apple", @@ -33395,7 +33443,7 @@ }, { "kind": "ui-call", - "line": 535, + "line": 576, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "CLI path", "surface": "apple", @@ -33403,7 +33451,7 @@ }, { "kind": "ui-named-argument", - "line": 537, + "line": 578, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "/Applications/OpenClaw.app/.../openclaw", "surface": "apple", @@ -33411,7 +33459,7 @@ }, { "kind": "ui-call", - "line": 542, + "line": 583, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "SSH command details", "surface": "apple", @@ -33419,7 +33467,7 @@ }, { "kind": "ui-named-argument", - "line": 562, + "line": 603, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Transport", "surface": "apple", @@ -33427,7 +33475,7 @@ }, { "kind": "ui-named-argument", - "line": 563, + "line": 604, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "SSH keeps the Gateway private; direct is best for HTTPS or Tailscale Serve.", "surface": "apple", @@ -33435,7 +33483,7 @@ }, { "kind": "ui-call", - "line": 566, + "line": 607, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "SSH tunnel", "surface": "apple", @@ -33443,7 +33491,7 @@ }, { "kind": "ui-call", - "line": 567, + "line": 608, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Direct (ws/wss)", "surface": "apple", @@ -33451,7 +33499,7 @@ }, { "kind": "ui-named-argument", - "line": 580, + "line": 621, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "SSH target", "surface": "apple", @@ -33459,7 +33507,7 @@ }, { "kind": "ui-named-argument", - "line": 580, + "line": 621, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "User and host for the remote Gateway machine.", "surface": "apple", @@ -33467,7 +33515,7 @@ }, { "kind": "ui-named-argument", - "line": 597, + "line": 638, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Gateway URL", "surface": "apple", @@ -33475,7 +33523,7 @@ }, { "kind": "ui-named-argument", - "line": 597, + "line": 638, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "The WebSocket URL exposed by the remote Gateway.", "surface": "apple", @@ -33483,7 +33531,7 @@ }, { "kind": "ui-call", - "line": 600, + "line": 641, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "wss://gateway.example.ts.net", "surface": "apple", @@ -33491,7 +33539,7 @@ }, { "kind": "ui-call", - "line": 606, + "line": 647, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Use wss:// for public hosts. ws:// is allowed for localhost, LAN, .local, and Tailnet hosts.", "surface": "apple", @@ -33499,7 +33547,7 @@ }, { "kind": "ui-named-argument", - "line": 617, + "line": 658, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Gateway token", "surface": "apple", @@ -33507,7 +33555,7 @@ }, { "kind": "ui-named-argument", - "line": 618, + "line": 659, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Used when the remote gateway requires token auth.", "surface": "apple", @@ -33515,7 +33563,7 @@ }, { "kind": "ui-call", - "line": 621, + "line": 662, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "remote gateway auth token (gateway.remote.token)", "surface": "apple", @@ -33523,7 +33571,7 @@ }, { "kind": "ui-call-concatenated", - "line": 626, + "line": 667, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "The current gateway.remote.token value is not plain text. OpenClaw for macOS cannot use it directly; enter a plaintext token here to replace it.", "surface": "apple", @@ -33531,7 +33579,7 @@ }, { "kind": "ui-call", - "line": 645, + "line": 686, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Test remote", "surface": "apple", @@ -33539,7 +33587,7 @@ }, { "kind": "ui-call", - "line": 668, + "line": 709, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Testing…", "surface": "apple", @@ -33547,7 +33595,7 @@ }, { "kind": "ui-call", - "line": 706, + "line": 747, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Installed: \\(gatewayVersion) · Required: \\(required)", "surface": "apple", @@ -33555,7 +33603,7 @@ }, { "kind": "ui-call", - "line": 710, + "line": 751, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Gateway \\(gatewayVersion) detected", "surface": "apple", @@ -33563,7 +33611,7 @@ }, { "kind": "ui-call", - "line": 716, + "line": 757, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Node \\(node)", "surface": "apple", @@ -33571,7 +33619,7 @@ }, { "kind": "ui-call", - "line": 728, + "line": 769, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Last failure: \\(failure)", "surface": "apple", @@ -33579,7 +33627,7 @@ }, { "kind": "ui-call", - "line": 733, + "line": 774, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Recheck", "surface": "apple", @@ -33587,7 +33635,7 @@ }, { "kind": "ui-call", - "line": 736, + "line": 777, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Gateway auto-starts in local mode via launchd (\\(gatewayLaunchdLabel)).", "surface": "apple", @@ -33595,7 +33643,7 @@ }, { "kind": "ui-call", - "line": 791, + "line": 832, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Retry now", "surface": "apple", @@ -33603,7 +33651,7 @@ }, { "kind": "ui-call", - "line": 796, + "line": 837, "path": "apps/macos/Sources/OpenClaw/GeneralSettings.swift", "source": "Open logs", "surface": "apple", diff --git a/apps/macos/Sources/OpenClaw/AppState.swift b/apps/macos/Sources/OpenClaw/AppState.swift index 3b4c44d908d0..0c086828c6d9 100644 --- a/apps/macos/Sources/OpenClaw/AppState.swift +++ b/apps/macos/Sources/OpenClaw/AppState.swift @@ -278,8 +278,7 @@ final class AppState { /// via its own Mac app instead of a second, separately toggled bridge here. func applyPeekabooBridgeHostState() { self.ifNotPreview { - let computerControlEnabled = UserDefaults.standard - .object(forKey: computerControlEnabledKey) as? Bool ?? false + let computerControlEnabled = isComputerControlEnabled() let shouldRun = self.peekabooBridgeEnabled && computerControlEnabled Task { await PeekabooBridgeHostCoordinator.shared.setEnabled(shouldRun) } } diff --git a/apps/macos/Sources/OpenClaw/ComputerActionService.swift b/apps/macos/Sources/OpenClaw/ComputerActionService.swift index f7514d6e32c8..f6a691d53470 100644 --- a/apps/macos/Sources/OpenClaw/ComputerActionService.swift +++ b/apps/macos/Sources/OpenClaw/ComputerActionService.swift @@ -1,4 +1,5 @@ import AppKit +import ApplicationServices import CoreGraphics import Foundation import OpenClawKit @@ -315,6 +316,101 @@ final class ComputerActionExecutionQueue { } } +struct ComputerControlPermissionSnapshot: Equatable, Sendable { + enum Access: Equatable, Sendable { + case granted + case missing + } + + enum Bucket: Equatable, Sendable { + case accessibility + case postEvent + case screenCapture + + var displayName: String { + switch self { + case .accessibility: "Accessibility" + case .postEvent: "Event Posting" + case .screenCapture: "Screen Recording" + } + } + } + + enum Diagnostic: Equatable, Sendable { + case granted + case missing([Bucket]) + case accessibilityGrantMayBeStale + + var statusText: String { + switch self { + case .granted: "Granted" + case .missing: "Missing permission" + case .accessibilityGrantMayBeStale: "Accessibility grant may be stale" + } + } + + var detailText: String { + switch self { + case .granted: + "Accessibility, Event Posting, and Screen Recording are granted." + case let .missing(buckets): + "Missing: \(buckets.map(\.displayName).joined(separator: ", ")). " + + "Grant access in System Settings → Privacy & Security, then reopen OpenClaw." + case .accessibilityGrantMayBeStale: + Self.staleAccessibilityRemediation + } + } + + static let staleAccessibilityRemediation = """ + OpenClaw may already appear enabled under System Settings → Privacy & Security → Accessibility. \ + If so, the grant is pinned to an older build: select OpenClaw, remove it with −, then re-add \ + /Applications/OpenClaw.app. + """ + } + + enum InputAccess: Equatable, Sendable { + case granted + case accessibilityMissing + case accessibilityGrantMayBeStale + case postEventMissing + } + + let accessibility: Access + let postEvent: Access + let screenCapture: Access + + static func probe() -> Self { + Self( + accessibility: AXIsProcessTrusted() ? .granted : .missing, + postEvent: CGPreflightPostEventAccess() ? .granted : .missing, + screenCapture: CGPreflightScreenCaptureAccess() ? .granted : .missing) + } + + var diagnostic: Diagnostic { + // Capture granted + AX denied is the observed stale cdhash signature after an app rebuild. + if self.accessibility == .missing, self.screenCapture == .granted { + return .accessibilityGrantMayBeStale + } + let missing = [ + (Bucket.accessibility, self.accessibility), + (.postEvent, self.postEvent), + (.screenCapture, self.screenCapture), + ].compactMap { bucket, access in + access == .missing ? bucket : nil + } + return missing.isEmpty ? .granted : .missing(missing) + } + + var inputAccess: InputAccess { + if self.accessibility == .missing { + return self.screenCapture == .granted + ? .accessibilityGrantMayBeStale + : .accessibilityMissing + } + return self.postEvent == .granted ? .granted : .postEventMissing + } +} + /// Fulfills `computer.act` on this Mac by driving the embedded Peekaboo /// automation engine in-process. Peekaboo covers single/right/double click, /// move, drag, scroll, and key/hold. A narrow CoreGraphics path handles @@ -339,6 +435,8 @@ final class ComputerActionService { enum ComputerActionError: LocalizedError { case accessibilityNotTrusted + case accessibilityGrantMayBeStale + case postEventAccessDenied case noDisplays case invalidScreenIndex(Int) case missingDisplayFrameId @@ -359,6 +457,10 @@ final class ComputerActionService { switch self { case .accessibilityNotTrusted: "Accessibility permission is required for computer control" + case .accessibilityGrantMayBeStale: + ComputerControlPermissionSnapshot.Diagnostic.staleAccessibilityRemediation + case .postEventAccessDenied: + "Event Posting permission is required for computer control" case .noDisplays: "No displays available for computer control" case let .invalidScreenIndex(idx): @@ -394,7 +496,6 @@ final class ComputerActionService { } private let automation: UIAutomationService - private let permissions: PermissionsService private let mouseButtonEventPoster: MouseButtonEventPoster private let mouseEventFactory: MouseEventFactory private let mouseEventPoster: MouseEventPoster @@ -436,7 +537,6 @@ final class ComputerActionService { init() { self.automation = UIAutomationService() - self.permissions = PermissionsService() self.mouseButtonEventPoster = Self.postMouseButtonEvent self.mouseEventFactory = Self.makeMouseEvent self.mouseEventPoster = Self.postMouseEvent @@ -446,7 +546,6 @@ final class ComputerActionService { #if DEBUG init(mouseButtonEventPoster: @escaping MouseButtonEventPoster) { self.automation = UIAutomationService() - self.permissions = PermissionsService() self.mouseButtonEventPoster = mouseButtonEventPoster self.mouseEventFactory = Self.makeMouseEvent self.mouseEventPoster = Self.postMouseEvent @@ -458,7 +557,6 @@ final class ComputerActionService { mouseEventPoster: @escaping MouseEventPoster) { self.automation = UIAutomationService() - self.permissions = PermissionsService() self.mouseButtonEventPoster = Self.postMouseButtonEvent self.mouseEventFactory = mouseEventFactory self.mouseEventPoster = mouseEventPoster @@ -467,7 +565,6 @@ final class ComputerActionService { init(textGraphemePoster: @escaping TextGraphemePoster) { self.automation = UIAutomationService() - self.permissions = PermissionsService() self.mouseButtonEventPoster = Self.postMouseButtonEvent self.mouseEventFactory = Self.makeMouseEvent self.mouseEventPoster = Self.postMouseEvent @@ -495,9 +592,7 @@ final class ComputerActionService { lifecycleGeneration: UInt64) async throws -> OpenClawComputerActResult { try self.executionQueue.checkExecutionAllowed(lifecycleGeneration: lifecycleGeneration) - guard self.permissions.checkAccessibilityPermission() else { - throw ComputerActionError.accessibilityNotTrusted - } + try Self.validateInputPermissions(ComputerControlPermissionSnapshot.probe()) let display = try await resolveDisplay(params: params) try executionQueue.checkExecutionAllowed(lifecycleGeneration: lifecycleGeneration) try await self.dispatch( @@ -509,6 +604,19 @@ final class ComputerActionService { return OpenClawComputerActResult(ok: true, cursorX: cursor.x, cursorY: cursor.y) } + static func validateInputPermissions(_ permissions: ComputerControlPermissionSnapshot) throws { + switch permissions.inputAccess { + case .granted: + return + case .accessibilityMissing: + throw ComputerActionError.accessibilityNotTrusted + case .accessibilityGrantMayBeStale: + throw ComputerActionError.accessibilityGrantMayBeStale + case .postEventMissing: + throw ComputerActionError.postEventAccessDenied + } + } + // MARK: - Dispatch private func dispatch( diff --git a/apps/macos/Sources/OpenClaw/Constants.swift b/apps/macos/Sources/OpenClaw/Constants.swift index e6653e12d77f..87df392b7291 100644 --- a/apps/macos/Sources/OpenClaw/Constants.swift +++ b/apps/macos/Sources/OpenClaw/Constants.swift @@ -40,6 +40,12 @@ let canvasEnabledKey = "openclaw.canvasEnabled" let quickChatEnabledKey = "openclaw.quickChatEnabled" let cameraEnabledKey = "openclaw.cameraEnabled" let computerControlEnabledKey = "openclaw.computerControlEnabled" + +func isComputerControlEnabled(defaults: UserDefaults = .standard) -> Bool { + // object(forKey:) preserves an explicit false; bool(forKey:) would conflate it with an unset default. + defaults.object(forKey: computerControlEnabledKey) as? Bool ?? true +} + let activeComputerPresenceEnabledKey = "openclaw.activeComputerPresenceEnabled" let locationModeKey = "openclaw.locationMode" let locationPreciseKey = "openclaw.locationPreciseEnabled" diff --git a/apps/macos/Sources/OpenClaw/GeneralSettings.swift b/apps/macos/Sources/OpenClaw/GeneralSettings.swift index be12f65b8b76..0c5f784a748a 100644 --- a/apps/macos/Sources/OpenClaw/GeneralSettings.swift +++ b/apps/macos/Sources/OpenClaw/GeneralSettings.swift @@ -16,7 +16,7 @@ struct GeneralSettings: View { @Bindable var state: AppState @AppStorage(cameraEnabledKey) private var cameraEnabled: Bool = false - @AppStorage(computerControlEnabledKey) private var computerControlEnabled: Bool = false + @AppStorage(computerControlEnabledKey) private var computerControlEnabled: Bool = true let page: Page let isActive: Bool private let healthStore = HealthStore.shared @@ -26,6 +26,7 @@ struct GeneralSettings: View { @State private var gatewayStatus: GatewayEnvironmentStatus = .checking @State private var remoteStatus: RemoteStatus = .idle @State private var showRemoteAdvanced = false + @State private var computerControlPermissions = ComputerControlPermissionSnapshot.probe() private let isPreview = ProcessInfo.processInfo.isPreview private var isNixMode: Bool { ProcessInfo.processInfo.isNixMode @@ -67,6 +68,12 @@ struct GeneralSettings: View { // Turning Computer Control on/off must start or stop the gated PeekabooBridge host. self.state.applyPeekabooBridgeHostState() } + .onReceive(NotificationCenter.default.publisher(for: NSApplication.didBecomeActiveNotification)) { _ in + self.refreshComputerControlPermissions() + } + .onReceive(NotificationCenter.default.publisher(for: .openclawPermissionsChanged)) { _ in + self.refreshComputerControlPermissions() + } .onDisappear { self.gatewayDiscovery.stop() } } @@ -128,11 +135,24 @@ struct GeneralSettings: View { SettingsCardToggleRow( title: "Allow Computer Control", subtitle: """ - Let an authorized agent move the pointer, click, and type on this Mac. \ - Also requires Accessibility, Screen Recording, and gateway command authorization. High risk. + Starts enabled. After this Mac is paired and macOS access is granted, the paired Gateway can \ + move the pointer, click, and type without per-action confirmation. High risk. """, binding: self.$computerControlEnabled) + SettingsCardRow( + title: "Computer Control access", + subtitle: .verbatim(self.computerControlPermissions.diagnostic.detailText)) + { + Label { + Text(verbatim: self.computerControlPermissions.diagnostic.statusText) + } icon: { + Image(systemName: self.computerControlPermissionIcon) + } + .font(.caption.weight(.medium)) + .foregroundStyle(self.computerControlPermissionColor) + } + SettingsCardToggleRow( title: "Enable Peekaboo Bridge", subtitle: """ @@ -283,6 +303,7 @@ struct GeneralSettings: View { private func updateActiveWork(active: Bool) { guard !self.isPreview else { return } if active { + self.refreshComputerControlPermissions() self.refreshGatewayStatus() if self.page == .connection { self.gatewayDiscovery.start() @@ -292,6 +313,26 @@ struct GeneralSettings: View { } } + private func refreshComputerControlPermissions() { + guard self.page == .general, self.isActive, !self.isPreview else { return } + self.computerControlPermissions = .probe() + } + + private var computerControlPermissionIcon: String { + switch self.computerControlPermissions.diagnostic { + case .granted: "checkmark.circle.fill" + case .missing: "exclamationmark.circle.fill" + case .accessibilityGrantMayBeStale: "exclamationmark.triangle.fill" + } + } + + private var computerControlPermissionColor: Color { + switch self.computerControlPermissions.diagnostic { + case .granted: .green + case .missing, .accessibilityGrantMayBeStale: .orange + } + } + private var connectionStatusPanel: some View { HStack(alignment: .center, spacing: 14) { ZStack { diff --git a/apps/macos/Sources/OpenClaw/NodeMode/MacNodeModeCoordinator.swift b/apps/macos/Sources/OpenClaw/NodeMode/MacNodeModeCoordinator.swift index a9dafd101da9..ba1257c86e00 100644 --- a/apps/macos/Sources/OpenClaw/NodeMode/MacNodeModeCoordinator.swift +++ b/apps/macos/Sources/OpenClaw/NodeMode/MacNodeModeCoordinator.swift @@ -163,7 +163,7 @@ final class MacNodeModeCoordinator: NSObject { self.refreshContinuation = refreshEvents.continuation self.lastObservedPaused = initialPaused ?? UserDefaults.standard.bool(forKey: pauseDefaultsKey) self.lastObservedComputerControlEnabled = initialComputerControlEnabled ?? - (UserDefaults.standard.object(forKey: computerControlEnabledKey) as? Bool ?? false) + isComputerControlEnabled() super.init() guard observeNotifications else { return } @@ -269,8 +269,7 @@ final class MacNodeModeCoordinator: NSObject { func refresh() { self.refresh( isPaused: UserDefaults.standard.bool(forKey: pauseDefaultsKey), - computerControlEnabled: UserDefaults.standard.object( - forKey: computerControlEnabledKey) as? Bool ?? false) + computerControlEnabled: isComputerControlEnabled()) } func currentCanvasPluginSurfaceRoute() async -> GatewayCanvasHostRoute? { @@ -799,8 +798,7 @@ final class MacNodeModeCoordinator: NSObject { claudeSessionCatalogEnabled: Bool) -> [String] { let rawLocationMode = UserDefaults.standard.string(forKey: locationModeKey) ?? "off" - let computerControlEnabled = - UserDefaults.standard.object(forKey: computerControlEnabledKey) as? Bool ?? false + let computerControlEnabled = isComputerControlEnabled() return Self.resolvedCaps( browserControlEnabled: browserControlEnabled, cameraEnabled: cameraEnabled, diff --git a/apps/macos/Sources/OpenClaw/NodeMode/MacNodeRuntime.swift b/apps/macos/Sources/OpenClaw/NodeMode/MacNodeRuntime.swift index 5593f4e36ef7..18ab62e7378a 100644 --- a/apps/macos/Sources/OpenClaw/NodeMode/MacNodeRuntime.swift +++ b/apps/macos/Sources/OpenClaw/NodeMode/MacNodeRuntime.swift @@ -554,6 +554,18 @@ extension MacNodeRuntime { req, code: .unavailable, message: "ACCESSIBILITY_REQUIRED: grant Accessibility permission to OpenClaw") + case .accessibilityGrantMayBeStale: + return Self.errorResponse( + req, + code: .unavailable, + message: "ACCESSIBILITY_REQUIRED: " + + ComputerControlPermissionSnapshot.Diagnostic.staleAccessibilityRemediation) + case .postEventAccessDenied: + return Self.errorResponse( + req, + code: .unavailable, + message: "POST_EVENT_REQUIRED: macOS denied Event Posting access; re-grant OpenClaw " + + "under System Settings → Privacy & Security → Accessibility") case .noDisplays, .invalidScreenIndex, .missingDisplayFrameId, .displayFrameChanged, .missingCoordinate, .coordinateOutOfBounds, .invalidReferenceWidth, .missingKeys, .emptyText, .invalidScroll, .invalidModifier, .buttonAlreadyHeld, .buttonNotHeld: @@ -939,7 +951,7 @@ extension MacNodeRuntime { } nonisolated static func computerControlEnabledDefault() -> Bool { - UserDefaults.standard.object(forKey: computerControlEnabledKey) as? Bool ?? false + isComputerControlEnabled() } private nonisolated static func locationMode() -> OpenClawLocationMode { diff --git a/apps/macos/Tests/OpenClawIPCTests/ComputerActionServiceTests.swift b/apps/macos/Tests/OpenClawIPCTests/ComputerActionServiceTests.swift index 8e34ee47861b..62355a8cbb82 100644 --- a/apps/macos/Tests/OpenClawIPCTests/ComputerActionServiceTests.swift +++ b/apps/macos/Tests/OpenClawIPCTests/ComputerActionServiceTests.swift @@ -127,6 +127,60 @@ struct ComputerActionServiceTests { } } + @Test func `capture grant distinguishes a stale accessibility grant`() { + let permissions = ComputerControlPermissionSnapshot( + accessibility: .missing, + postEvent: .granted, + screenCapture: .granted) + + #expect(permissions.diagnostic == .accessibilityGrantMayBeStale) + #expect(permissions.diagnostic.detailText == """ + OpenClaw may already appear enabled under System Settings → Privacy & Security → Accessibility. \ + If so, the grant is pinned to an older build: select OpenClaw, remove it with −, then re-add \ + /Applications/OpenClaw.app. + """) + #expect(permissions.inputAccess == .accessibilityGrantMayBeStale) + let error = self.validationError { + try ComputerActionService.validateInputPermissions(permissions) + } + if case .some(.accessibilityGrantMayBeStale) = error {} else { + Issue.record("expected stale Accessibility error, got \(String(describing: error))") + } + } + + @Test func `missing accessibility and capture is a plain missing permission`() { + let permissions = ComputerControlPermissionSnapshot( + accessibility: .missing, + postEvent: .granted, + screenCapture: .missing) + + #expect(permissions.diagnostic == .missing([.accessibility, .screenCapture])) + #expect(permissions.diagnostic.detailText == """ + Missing: Accessibility, Screen Recording. \ + Grant access in System Settings → Privacy & Security, then reopen OpenClaw. + """) + #expect(permissions.inputAccess == .accessibilityMissing) + let error = self.validationError { + try ComputerActionService.validateInputPermissions(permissions) + } + if case .some(.accessibilityNotTrusted) = error {} else { + Issue.record("expected missing Accessibility error, got \(String(describing: error))") + } + } + + @Test func `post event denial remains distinct from accessibility denial`() { + let permissions = ComputerControlPermissionSnapshot( + accessibility: .granted, + postEvent: .missing, + screenCapture: .granted) + + #expect(permissions.diagnostic == .missing([.postEvent])) + #expect(permissions.inputAccess == .postEventMissing) + #expect(throws: ComputerActionService.ComputerActionError.self) { + try ComputerActionService.validateInputPermissions(permissions) + } + } + @Test func `coordinate input requires the current display frame identity`() throws { let currentFrameId = "display-frame:v1:current" let missing = OpenClawComputerActParams( diff --git a/apps/macos/Tests/OpenClawIPCTests/ComputerControlSettingsTests.swift b/apps/macos/Tests/OpenClawIPCTests/ComputerControlSettingsTests.swift new file mode 100644 index 000000000000..c9c2fcec9f63 --- /dev/null +++ b/apps/macos/Tests/OpenClawIPCTests/ComputerControlSettingsTests.swift @@ -0,0 +1,19 @@ +import Foundation +import Testing +@testable import OpenClaw + +struct ComputerControlSettingsTests { + @Test func `computer control defaults on while preserving explicit choices`() throws { + let suiteName = "ComputerControlSettingsTests.\(UUID().uuidString)" + let defaults = try #require(UserDefaults(suiteName: suiteName)) + defer { defaults.removePersistentDomain(forName: suiteName) } + + #expect(isComputerControlEnabled(defaults: defaults)) + + defaults.set(false, forKey: computerControlEnabledKey) + #expect(!isComputerControlEnabled(defaults: defaults)) + + defaults.set(true, forKey: computerControlEnabledKey) + #expect(isComputerControlEnabled(defaults: defaults)) + } +} diff --git a/docs/docs_map.md b/docs/docs_map.md index 4e17bf6781f3..df838cb96063 100644 --- a/docs/docs_map.md +++ b/docs/docs_map.md @@ -5054,6 +5054,7 @@ Do not edit it by hand; run `pnpm docs:map:gen`. - H2: The computer.act node command - H2: Authorization - H2: Safety + - H2: macOS permission troubleshooting - H2: Relationship to other desktop-control paths ## nodes/images.md @@ -5526,6 +5527,7 @@ Do not edit it by hand; run `pnpm docs:map:gen`. - Headings: - H2: Requirements for stable permissions - H2: Accessibility grants for Node and CLI runtimes + - H2: Separate Computer Control grants - H2: Recovery checklist when prompts disappear - H2: Files and folders permissions (Desktop/Documents/Downloads) - H2: Related diff --git a/docs/nodes/computer-use.md b/docs/nodes/computer-use.md index 081ca6cb81b5..9c92aecdef33 100644 --- a/docs/nodes/computer-use.md +++ b/docs/nodes/computer-use.md @@ -14,8 +14,8 @@ The agent emits one uniform command, `computer.act`; it cannot tell how a node f ## Requirements - A paired, connected node advertising both `computer.act` and `screen.snapshot`, with `screen.snapshot` returning `displayFrameId`. -- **macOS fulfiller:** app setting **Allow Computer Control** enabled (default: off). -- **macOS fulfiller:** **Accessibility** permission granted to OpenClaw (for pointer/keyboard injection) and **Screen Recording** permission (for `screen.snapshot`). +- **macOS fulfiller:** app setting **Allow Computer Control** enabled. It defaults on; an explicit off choice stays off. +- **macOS fulfiller:** **Accessibility** and Event Posting access granted to OpenClaw (for pointer/keyboard injection), plus **Screen Recording** permission (for `screen.snapshot`). - **Windows/Linux fulfiller:** bundled `cua-computer` plugin enabled and a compatible `cua-driver` 0.10.x executable installed. - The pairing update that includes `computer.act` approved on the gateway. - A vision-capable agent model. @@ -80,7 +80,7 @@ Reads reuse `screen.snapshot`; there is no second capture path. See [Camera and ## Authorization -1. Enable the platform fulfiller: on macOS, enable **Settings → Allow Computer Control**, then grant **Accessibility** and **Screen Recording** under **Settings → Permissions**; on Windows/Linux, follow the experimental `cua-computer` setup above. +1. Enable the platform fulfiller: on macOS, **Settings → Allow Computer Control** starts enabled, then grant **Accessibility** and **Screen Recording** under **Settings → Permissions**; on Windows/Linux, follow the experimental `cua-computer` setup above. 2. Approve the pairing update on the gateway (a new command forces re-pairing). 3. Expose the tool to the vision-capable agent. For the default `coding` profile: @@ -96,6 +96,8 @@ Reads reuse `screen.snapshot`; there is no second capture path. See [Camera and Once the node-local control is enabled and the pairing update is approved, `computer.act` is durably available while the node continues to advertise it. There is no lease, expiry, or arm/disarm command. Disabling Computer Control locally removes the advertised command and the node rechecks the toggle at invocation time. +On macOS, default-on means a paired gateway can drive pointer and keyboard input as soon as the required macOS grants exist. There is no per-action confirmation. Turn off **Allow Computer Control** before pairing, or at any later time, to stop advertising and accepting `computer.act`. + `gateway.nodes.commands.deny` remains an explicit global revocation and always wins. `computer.act` does not need a `gateway.nodes.commands.allow` entry. An authenticated operator with `operator.write` can invoke an enabled, paired command through `node.invoke`; there is no per-action admin check. ## Safety @@ -105,6 +107,12 @@ Once the node-local control is enabled and the pairing update is approved, `comp - Screenshots are model-only and never auto-sent to chat (issue [#44759](https://github.com/openclaw/openclaw/issues/44759)). - Treat screen content as untrusted; it can carry prompt injection. +## macOS permission troubleshooting + +The Computer Control status in **Settings → General → Capabilities** checks Accessibility, Event Posting, and Screen Recording separately. Screen capture can work while input remains denied because macOS stores those grants in separate TCC buckets. + +If the status says **Accessibility grant may be stale**, OpenClaw may already appear enabled under **System Settings → Privacy & Security → Accessibility** even though macOS rejects it. This happens when the Accessibility entry is pinned to an older app build. Select OpenClaw in that list, remove it with **−**, then re-add `/Applications/OpenClaw.app`. Quit and reopen OpenClaw after changing the grant because macOS can cache Accessibility trust for the lifetime of the process. + ## Relationship to other desktop-control paths This is the agent-driven path. See [Peekaboo bridge](/platforms/mac/peekaboo) for how it relates to the PeekabooBridge host, Codex Computer Use, and the direct `cua-driver` MCP. diff --git a/docs/platforms/mac/permissions.md b/docs/platforms/mac/permissions.md index a7068f410cc8..7baaa2cd07d5 100644 --- a/docs/platforms/mac/permissions.md +++ b/docs/platforms/mac/permissions.md @@ -12,7 +12,7 @@ macOS permission grants are fragile. TCC associates a permission grant with the ## Requirements for stable permissions -- Same path: run the app from a fixed location (for OpenClaw, `dist/OpenClaw.app`). +- Same path: run a release app from `/Applications/OpenClaw.app`; keep development builds at one fixed path such as `dist/OpenClaw.app`. - Same bundle identifier: OpenClaw's bundle ID is `ai.openclaw.mac`; changing it creates a new permission identity. - Signed app: unsigned or ad-hoc signed builds do not persist permissions. - Consistent signature: use a real Apple Development or Developer ID certificate so the signature stays stable across rebuilds. @@ -31,6 +31,12 @@ Accessibility approval does not enable activity sharing. **Settings -> Permissio If you accidentally granted Accessibility to `node`, remove that entry from System Settings -> Privacy & Security -> Accessibility. Then grant the signed app or helper that should own UI automation. +## Separate Computer Control grants + +macOS keeps Accessibility, Event Posting, input listening, and Screen Recording in separate TCC buckets. One successful grant does not prove the others are usable. OpenClaw's Computer Control status checks Accessibility, Event Posting, and Screen Recording separately; this is why screenshots can succeed while clicks and typing fail. + +An Accessibility row can also remain visibly enabled while its code requirement is pinned to an older build. When OpenClaw reports **Accessibility grant may be stale**, select OpenClaw under **System Settings -> Privacy & Security -> Accessibility**, remove it with **-**, then re-add `/Applications/OpenClaw.app`. Quit and reopen OpenClaw afterward because Accessibility trust can remain cached in the running process. + ## Recovery checklist when prompts disappear 1. Quit the app. diff --git a/test/scripts/native-app-i18n.test.ts b/test/scripts/native-app-i18n.test.ts index 950664ce3f54..f5da95ed02bb 100644 --- a/test/scripts/native-app-i18n.test.ts +++ b/test/scripts/native-app-i18n.test.ts @@ -634,7 +634,7 @@ describe("native app i18n inventory", () => { entries.some( (entry) => entry.source === - "Let an authorized agent move the pointer, click, and type on this Mac. Also requires Accessibility, Screen Recording, and gateway command authorization. High risk.", + "Starts enabled. After this Mac is paired and macOS access is granted, the paired Gateway can move the pointer, click, and type without per-action confirmation. High risk.", ), ).toBe(true); expect(