Keep macOS Computer Use runtime permission checks quiet (#2764)
This commit is contained in:
parent
be2dfe4bac
commit
0adb030bbb
|
|
@ -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<String>()
|
||||
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] {
|
||||
|
|
|
|||
Loading…
Reference in New Issue