fix: align agent permissions with native app

This commit is contained in:
AI Bot
2026-05-12 23:22:27 +08:00
parent 5b3f43014d
commit 29740f35c7
5 changed files with 233 additions and 109 deletions

View File

@@ -1,9 +1,19 @@
import Cocoa
import WebKit
import ApplicationServices
import AVFoundation
import Network
import UserNotifications
private let bossInputMonitoringTapCallback: CGEventTapCallBack = { _, _, event, _ in
Unmanaged.passUnretained(event)
}
final class AppDelegate: NSObject, NSApplicationDelegate, WKNavigationDelegate {
private var window: NSWindow?
private var webView: WKWebView?
private var inputMonitoringTap: CFMachPort?
private var localNetworkBrowser: NWBrowser?
func applicationDidFinishLaunching(_ notification: Notification) {
NSApp.setActivationPolicy(.regular)
@@ -48,6 +58,173 @@ final class AppDelegate: NSObject, NSApplicationDelegate, WKNavigationDelegate {
loadFallback()
}
func webView(
_ webView: WKWebView,
decidePolicyFor navigationAction: WKNavigationAction,
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void
) {
guard let url = navigationAction.request.url else {
decisionHandler(.allow)
return
}
if isPermissionSetupUrl(url) {
decisionHandler(.cancel)
handlePermissionSetupNavigation(url)
return
}
decisionHandler(.allow)
}
private func isPermissionSetupUrl(_ url: URL) -> Bool {
url.path == "/api/v1/boss-agent/permissions/open"
}
private func handlePermissionSetupNavigation(_ url: URL) {
let target =
URLComponents(url: url, resolvingAgainstBaseURL: false)?
.queryItems?
.first(where: { $0.name == "target" })?
.value ?? "all"
requestNativePermission(for: target)
if let settingsUrl = systemSettingsUrl(for: target) {
NSWorkspace.shared.open(settingsUrl)
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.8) { [weak self] in
self?.loadAgentPanel()
}
}
private func requestNativePermission(for target: String) {
let targets = target == "all"
? [
"accessibility",
"screenRecording",
"automation",
"fullDiskAccess",
"inputMonitoring",
"notifications",
"microphone",
"camera",
"localNetwork",
]
: [target]
for permission in targets {
requestSingleNativePermission(permission)
}
}
private func requestSingleNativePermission(_ permission: String) {
switch permission {
case "accessibility":
requestAccessibilityPermission()
case "screenRecording":
requestScreenRecordingPermission()
case "automation":
requestAutomationPermission()
case "fullDiskAccess":
preflightFullDiskAccess()
case "inputMonitoring":
requestInputMonitoringPermission()
case "notifications":
requestNotificationPermission()
case "microphone":
AVCaptureDevice.requestAccess(for: .audio) { _ in }
case "camera":
AVCaptureDevice.requestAccess(for: .video) { _ in }
case "localNetwork":
requestLocalNetworkPermission()
default:
break
}
}
private func requestAccessibilityPermission() {
let promptKey = kAXTrustedCheckOptionPrompt.takeUnretainedValue() as String
let options = [promptKey: true] as CFDictionary
_ = AXIsProcessTrustedWithOptions(options)
}
private func requestScreenRecordingPermission() {
if #available(macOS 10.15, *) {
_ = CGRequestScreenCaptureAccess()
} else {
_ = CGPreflightScreenCaptureAccess()
}
}
private func requestAutomationPermission() {
let script = NSAppleScript(source: "tell application \"Finder\" to get name")
var error: NSDictionary?
_ = script?.executeAndReturnError(&error)
}
private func preflightFullDiskAccess() {
let candidatePaths = [
"\(NSHomeDirectory())/Library/Safari/Bookmarks.plist",
"\(NSHomeDirectory())/Library/Mail",
"/Library/Application Support/com.apple.TCC/TCC.db",
]
for path in candidatePaths {
if FileManager.default.fileExists(atPath: path) {
_ = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe)
}
}
}
private func requestInputMonitoringPermission() {
let eventMask = CGEventMask(1 << CGEventType.keyDown.rawValue)
inputMonitoringTap = CGEvent.tapCreate(
tap: .cgSessionEventTap,
place: .headInsertEventTap,
options: .listenOnly,
eventsOfInterest: eventMask,
callback: bossInputMonitoringTapCallback,
userInfo: nil
)
if let tap = inputMonitoringTap {
CFMachPortInvalidate(tap)
inputMonitoringTap = nil
}
}
private func requestNotificationPermission() {
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { _, _ in }
}
private func requestLocalNetworkPermission() {
let parameters = NWParameters.tcp
parameters.includePeerToPeer = true
let browser = NWBrowser(for: .bonjour(type: "_http._tcp", domain: nil), using: parameters)
browser.stateUpdateHandler = { _ in }
localNetworkBrowser = browser
browser.start(queue: .main)
DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) { [weak self] in
self?.localNetworkBrowser?.cancel()
self?.localNetworkBrowser = nil
}
}
private func systemSettingsUrl(for target: String) -> URL? {
let mapping = [
"all": "x-apple.systempreferences:com.apple.preference.security?Privacy",
"accessibility": "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility",
"screenRecording": "x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture",
"automation": "x-apple.systempreferences:com.apple.preference.security?Privacy_Automation",
"fullDiskAccess": "x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles",
"inputMonitoring": "x-apple.systempreferences:com.apple.preference.security?Privacy_ListenEvent",
"notifications": "x-apple.systempreferences:com.apple.Notifications-Settings.extension",
"microphone": "x-apple.systempreferences:com.apple.preference.security?Privacy_Microphone",
"camera": "x-apple.systempreferences:com.apple.preference.security?Privacy_Camera",
"localNetwork": "x-apple.systempreferences:com.apple.preference.security?Privacy_LocalNetwork",
]
return URL(string: mapping[target] ?? mapping["all"]!)
}
private func loadFallback() {
let html = """
<!doctype html>