import Cocoa import WebKit import ApplicationServices final class AppDelegate: NSObject, NSApplicationDelegate, WKNavigationDelegate { private var window: NSWindow? private var webView: WKWebView? private var activeTab = "overview" func applicationDidFinishLaunching(_ notification: Notification) { NSApp.setActivationPolicy(.regular) NotificationCenter.default.addObserver( self, selector: #selector(handleApplicationDidBecomeActive), name: NSApplication.didBecomeActiveNotification, object: nil ) NSAppleEventManager.shared().setEventHandler( self, andSelector: #selector(handleGetUrlEvent(_:withReplyEvent:)), forEventClass: AEEventClass(kInternetEventClass), andEventID: AEEventID(kAEGetURL) ) 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(tab: activeTab) NSApp.activate(ignoringOtherApps: true) handleLaunchPermissionRequestIfNeeded() } private func loadAgentPanel(tab: String? = nil) { activeTab = normalizedTab(tab ?? activeTab) var components = URLComponents() components.scheme = "http" components.host = "127.0.0.1" components.port = 4317 components.path = "/boss-agent" components.queryItems = [URLQueryItem(name: "tab", value: activeTab)] + nativePermissionQueryItems() guard let url = components.url else { loadFallback() return } webView?.load(URLRequest(url: url, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData)) } @objc private func handleApplicationDidBecomeActive() { loadAgentPanel(tab: activeTab) } private func handleLaunchPermissionRequestIfNeeded() { let arguments = CommandLine.arguments guard let targetIndex = arguments.firstIndex(of: "--request-permission"), arguments.indices.contains(targetIndex + 1) else { return } let target = arguments[targetIndex + 1] let returnTab = commandLineValue(after: "--return-tab", in: arguments) ?? "permissions" DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) { [weak self] in self?.handlePermissionTarget(target, returnTab: returnTab) } } private func commandLineValue(after flag: String, in arguments: [String]) -> String? { guard let index = arguments.firstIndex(of: flag), arguments.indices.contains(index + 1) else { return nil } return arguments[index + 1] } @objc private func handleGetUrlEvent(_ event: NSAppleEventDescriptor, withReplyEvent replyEvent: NSAppleEventDescriptor) { guard let urlString = event.paramDescriptor(forKeyword: keyDirectObject)?.stringValue, let url = URL(string: urlString), isBossAgentDeepLink(url) else { return } handleBossAgentDeepLink(url) } func application(_ application: NSApplication, open urls: [URL]) { for url in urls where isBossAgentDeepLink(url) { handleBossAgentDeepLink(url) } } 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 } if isAgentPanelUrl(url) && !hasNativePermissionQuery(url) { decisionHandler(.cancel) loadAgentPanel(tab: queryValue("tab", in: url)) return } if isAgentPanelUrl(url), let tab = queryValue("tab", in: url) { activeTab = normalizedTab(tab) } decisionHandler(.allow) } private func isPermissionSetupUrl(_ url: URL) -> Bool { url.path == "/api/v1/boss-agent/permissions/open" } private func isBossAgentDeepLink(_ url: URL) -> Bool { url.scheme == "boss-agent" } private func handleBossAgentDeepLink(_ url: URL) { if url.host == "permissions" && url.path == "/open" { handlePermissionTarget( queryValue("target", in: url) ?? "core", returnTab: queryValue("returnTab", in: url) ?? "permissions" ) return } if url.host == "tab" { loadAgentPanel(tab: String(url.path.dropFirst())) } } private func isAgentPanelUrl(_ url: URL) -> Bool { let host = url.host ?? "" return (host == "127.0.0.1" || host == "localhost") && url.port == 4317 && (url.path == "/boss-agent" || url.path == "/") } private func hasNativePermissionQuery(_ url: URL) -> Bool { URLComponents(url: url, resolvingAgainstBaseURL: false)? .queryItems? .contains(where: { $0.name.hasPrefix("native") }) == true } private func queryValue(_ name: String, in url: URL) -> String? { URLComponents(url: url, resolvingAgainstBaseURL: false)? .queryItems? .first(where: { $0.name == name })? .value } private func normalizedTab(_ value: String?) -> String { switch value { case "permissions", "skills", "license", "logs", "overview": return value ?? "overview" default: return "overview" } } private func handlePermissionSetupNavigation(_ url: URL) { handlePermissionTarget( queryValue("target", in: url) ?? "core", returnTab: queryValue("returnTab", in: url) ?? activeTab ) } private func handlePermissionTarget(_ target: String, returnTab rawReturnTab: String) { let permissionTarget = normalizedPermissionTarget(target) let returnTab = normalizedTab(rawReturnTab) activeTab = returnTab UserDefaults.standard.set(permissionTarget, forKey: "lastPermissionRequestTarget") UserDefaults.standard.set(Date().timeIntervalSince1970, forKey: "lastPermissionRequestAt") NSLog("boss-agent permission request target=%@ returnTab=%@", permissionTarget, returnTab) NSApp.activate(ignoringOtherApps: true) requestNativePermission(for: permissionTarget) DispatchQueue.main.asyncAfter(deadline: .now() + 0.45) { [weak self] in if let settingsUrl = self?.systemSettingsUrl(for: permissionTarget) { NSWorkspace.shared.open(settingsUrl) } } DispatchQueue.main.asyncAfter(deadline: .now() + 0.8) { [weak self] in self?.loadAgentPanel(tab: returnTab) } } private func nativePermissionQueryItems() -> [URLQueryItem] { let accessibility = AXIsProcessTrusted() ? "granted" : "missing" let screenRecording = screenRecordingStatus() UserDefaults.standard.set(accessibility, forKey: "native.accessibility") UserDefaults.standard.set(screenRecording, forKey: "native.screenRecording") return [ URLQueryItem(name: "nativeAccessibility", value: accessibility), URLQueryItem(name: "nativeScreenRecording", value: screenRecording), ] } private func screenRecordingStatus() -> String { if #available(macOS 10.15, *) { if CGPreflightScreenCaptureAccess() { return "granted" } if CGRequestScreenCaptureAccess() { return "granted" } return tccPermissionStatus(service: "kTCCServiceScreenCapture") ?? "missing" } return "unknown" } private func tccPermissionStatus(service: String) -> String? { let clients = service == "kTCCServiceScreenCapture" ? "'com.hyzq.boss.agent','site.hyzq.boss.computer-use-helper'" : "'com.hyzq.boss.agent'" let query = "select auth_value from access where client in (\(clients)) and service='\(service)' order by auth_value desc limit 1;" let databasePaths = [ "/Library/Application Support/com.apple.TCC/TCC.db", "\(NSHomeDirectory())/Library/Application Support/com.apple.TCC/TCC.db", ] for databasePath in databasePaths where FileManager.default.fileExists(atPath: databasePath) { let process = Process() process.executableURL = URL(fileURLWithPath: "/usr/bin/sqlite3") process.arguments = [databasePath, query] let output = Pipe() process.standardOutput = output process.standardError = Pipe() do { try process.run() process.waitUntilExit() } catch { continue } let data = output.fileHandleForReading.readDataToEndOfFile() let value = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines) if value == "2" { return "granted" } if value == "0" { return "missing" } } return nil } private func requestNativePermission(for target: String) { let targets: [String] if target == "core" { targets = ["accessibility", "screenRecording"] } else { targets = [target] } for permission in targets { requestSingleNativePermission(permission) } } private func requestSingleNativePermission(_ permission: String) { switch permission { case "accessibility": requestAccessibilityPermission() case "screenRecording": requestScreenRecordingPermission() 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 systemSettingsUrl(for target: String) -> URL? { let mapping = [ "core": "x-apple.systempreferences:com.apple.settings.PrivacySecurity.extension?Privacy_Accessibility", "accessibility": "x-apple.systempreferences:com.apple.settings.PrivacySecurity.extension?Privacy_Accessibility", "screenRecording": "x-apple.systempreferences:com.apple.settings.PrivacySecurity.extension?Privacy_ScreenCapture", ] return URL(string: mapping[normalizedPermissionTarget(target)] ?? mapping["core"]!) } private func normalizedPermissionTarget(_ target: String) -> String { switch target { case "accessibility", "screenRecording", "core": return target default: return "core" } } private func loadFallback() { let html = """

boss-agent 未启动

请先启动本机 local-agent 服务,然后重新打开 boss-agent。

""" webView?.loadHTMLString(html, baseURL: nil) } } let app = NSApplication.shared let delegate = AppDelegate() app.delegate = delegate app.run()