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) let webConfiguration = WKWebViewConfiguration() let webView = WKWebView(frame: .zero, configuration: webConfiguration) webView.setValue(false, forKey: "drawsBackground") webView.navigationDelegate = self self.webView = webView let window = NSWindow( contentRect: NSRect(x: 0, y: 0, width: 1180, height: 780), styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView], backing: .buffered, defer: false ) window.title = "boss-agent" window.titlebarAppearsTransparent = true window.isMovableByWindowBackground = true window.contentView = webView window.center() window.makeKeyAndOrderFront(nil) self.window = window loadAgentPanel() NSApp.activate(ignoringOtherApps: true) } private func loadAgentPanel() { guard let url = URL(string: "http://127.0.0.1:4317/boss-agent") else { loadFallback() return } webView?.load(URLRequest(url: url, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData)) } func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { loadFallback() } func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) { 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 = """
请先启动本机 local-agent 服务,然后重新打开 boss-agent。