diff --git a/native/computer-use-macos/Sources/OrcaComputerUseMacOS/main.swift b/native/computer-use-macos/Sources/OrcaComputerUseMacOS/main.swift index 8c7d6a4a4..256c71733 100644 --- a/native/computer-use-macos/Sources/OrcaComputerUseMacOS/main.swift +++ b/native/computer-use-macos/Sources/OrcaComputerUseMacOS/main.swift @@ -157,7 +157,6 @@ enum ScreenshotStatus { final class Provider { private var snapshots: [String: Snapshot] = [:] - private var hasPromptedForAccessibility = false func handle(method: String, params: [String: JSONValue]) throws -> Any { switch method { @@ -301,14 +300,6 @@ final class Provider { } } - private func promptForAccessibilityOnce() { - guard !hasPromptedForAccessibility else { - return - } - hasPromptedForAccessibility = true - _ = promptForAccessibility() - } - private func listApps() -> [AppDescriptor] { var seen = Set() return NSWorkspace.shared.runningApplications @@ -467,8 +458,12 @@ final class Provider { restoreWindow: Bool ) throws -> Snapshot { guard accessibilityTrusted() else { - promptForAccessibilityOnce() - throw ProviderError.coded("permission_denied", "Accessibility permission is required for Orca Computer Use.") + // Why: agents retry failed observations. Only the explicit setup flow + // should open macOS privacy prompts/settings; runtime calls stay quiet. + throw ProviderError.coded( + "permission_denied", + "Accessibility permission is required for Orca Computer Use. Run `orca computer permissions` or open Settings > Computer Use, grant Accessibility to Orca Computer Use, then retry." + ) } let appElement = AXUIElementCreateApplication(app.pid) enableManualAccessibilityIfNeeded(appElement, app: app) @@ -480,7 +475,14 @@ final class Provider { allowRecovery: restoreWindow ) let focusedTitle = stringAttribute(focused, kAXTitleAttribute as String) ?? app.name - guard let capture = WindowCapture.resolve(candidates: windowCandidates, titleHint: focusedTitle, windowId: windowId, windowIndex: windowIndex) else { + let canCaptureScreenshot = includeScreenshot && screenCaptureTrusted() + guard let capture = WindowCapture.resolve( + candidates: windowCandidates, + titleHint: focusedTitle, + windowId: windowId, + windowIndex: windowIndex, + captureImage: canCaptureScreenshot + ) else { throw ProviderError.coded("window_not_found", "app '\(app.name)' has no on-screen window") } guard let window = matchingWindow(appElement: appElement, capture: capture, focused: focused, explicitTarget: windowId != nil || windowIndex != nil) else { @@ -492,7 +494,7 @@ final class Provider { let screenshot = includeScreenshot ? capture.screenshotPayload() : nil let screenshotStatus: ScreenshotStatus = if screenshot != nil { .captured - } else if includeScreenshot && !screenCaptureTrusted() { + } else if includeScreenshot && !canCaptureScreenshot { .failed("Screen Recording permission is required for Orca Computer Use; grant permission or pass --no-screenshot to inspect accessibility state only.") } else if includeScreenshot { .failed("window screenshot capture returned no image; retry with --no-screenshot if accessibility state is sufficient.") @@ -817,11 +819,6 @@ private func accessibilityTrusted() -> Bool { AXIsProcessTrusted() } -private func promptForAccessibility() -> Bool { - let options = ["AXTrustedCheckOptionPrompt": true] as CFDictionary - return AXIsProcessTrustedWithOptions(options) -} - private func screenCaptureTrusted() -> Bool { CGPreflightScreenCaptureAccess() } @@ -1628,19 +1625,37 @@ private struct WindowCapture { let title: String? let image: CapturedImage? - static func resolve(pid: pid_t, titleHint: String?, windowId: CGWindowID?, windowIndex: Int?) -> WindowCapture? { - resolve(candidates: candidates(pid: pid), titleHint: titleHint, windowId: windowId, windowIndex: windowIndex) + static func resolve( + pid: pid_t, + titleHint: String?, + windowId: CGWindowID?, + windowIndex: Int?, + captureImage: Bool + ) -> WindowCapture? { + resolve( + candidates: candidates(pid: pid), + titleHint: titleHint, + windowId: windowId, + windowIndex: windowIndex, + captureImage: captureImage + ) } - static func resolve(candidates: [WindowCandidate], titleHint: String?, windowId: CGWindowID?, windowIndex: Int?) -> WindowCapture? { + static func resolve( + candidates: [WindowCandidate], + titleHint: String?, + windowId: CGWindowID?, + windowIndex: Int?, + captureImage: Bool + ) -> WindowCapture? { if let windowId { guard let candidate = candidates.first(where: { $0.windowId == windowId }) else { return nil } - return WindowCapture(candidate: candidate) + return WindowCapture(candidate: candidate, captureImage: captureImage) } if let windowIndex { let visibleWindows = candidates.filter { $0.layer == 0 } guard visibleWindows.indices.contains(windowIndex) else { return nil } - return WindowCapture(candidate: visibleWindows[windowIndex]) + return WindowCapture(candidate: visibleWindows[windowIndex], captureImage: captureImage) } guard let best = candidates.sorted(by: { lhs, rhs in if let titleHint, lhs.title == titleHint, rhs.title != titleHint { return true } @@ -1649,15 +1664,21 @@ private struct WindowCapture { }).first else { return nil } - return WindowCapture(candidate: best) + return WindowCapture(candidate: best, captureImage: captureImage) } - private init(candidate: WindowCandidate) { + private init(candidate: WindowCandidate, captureImage: Bool) { self.windowId = candidate.windowId self.layer = candidate.layer self.bounds = candidate.bounds self.title = candidate.title - self.image = Self.captureImage(windowId: candidate.windowId, bounds: candidate.bounds) + // Why: probing image APIs before TCC preflight can raise Screen + // Recording prompts, even for --no-screenshot calls. + if captureImage { + self.image = Self.captureImage(windowId: candidate.windowId, bounds: candidate.bounds) + } else { + self.image = nil + } } static func candidates(pid: pid_t) -> [WindowCandidate] {