fix: remove nonessential boss agent permission requests
This commit is contained in:
@@ -1,22 +1,10 @@
|
||||
import Cocoa
|
||||
import WebKit
|
||||
import ApplicationServices
|
||||
import AVFoundation
|
||||
import IOKit.hid
|
||||
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 globalKeyMonitor: Any?
|
||||
private var inputMonitoringManager: IOHIDManager?
|
||||
private var localNetworkBrowser: NWBrowser?
|
||||
private var activeTab = "overview"
|
||||
|
||||
func applicationDidFinishLaunching(_ notification: Notification) {
|
||||
@@ -166,7 +154,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, WKNavigationDelegate {
|
||||
private func handleBossAgentDeepLink(_ url: URL) {
|
||||
if url.host == "permissions" && url.path == "/open" {
|
||||
handlePermissionTarget(
|
||||
queryValue("target", in: url) ?? "all",
|
||||
queryValue("target", in: url) ?? "core",
|
||||
returnTab: queryValue("returnTab", in: url) ?? "permissions"
|
||||
)
|
||||
return
|
||||
@@ -206,22 +194,23 @@ final class AppDelegate: NSObject, NSApplicationDelegate, WKNavigationDelegate {
|
||||
|
||||
private func handlePermissionSetupNavigation(_ url: URL) {
|
||||
handlePermissionTarget(
|
||||
queryValue("target", in: url) ?? "all",
|
||||
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(target, forKey: "lastPermissionRequestTarget")
|
||||
UserDefaults.standard.set(permissionTarget, forKey: "lastPermissionRequestTarget")
|
||||
UserDefaults.standard.set(Date().timeIntervalSince1970, forKey: "lastPermissionRequestAt")
|
||||
NSLog("boss-agent permission request target=%@ returnTab=%@", target, returnTab)
|
||||
NSLog("boss-agent permission request target=%@ returnTab=%@", permissionTarget, returnTab)
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
requestNativePermission(for: target)
|
||||
requestNativePermission(for: permissionTarget)
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.45) { [weak self] in
|
||||
if let settingsUrl = self?.systemSettingsUrl(for: target) {
|
||||
if let settingsUrl = self?.systemSettingsUrl(for: permissionTarget) {
|
||||
NSWorkspace.shared.open(settingsUrl)
|
||||
}
|
||||
}
|
||||
@@ -235,9 +224,6 @@ final class AppDelegate: NSObject, NSApplicationDelegate, WKNavigationDelegate {
|
||||
[
|
||||
URLQueryItem(name: "nativeAccessibility", value: AXIsProcessTrusted() ? "granted" : "missing"),
|
||||
URLQueryItem(name: "nativeScreenRecording", value: screenRecordingStatus()),
|
||||
URLQueryItem(name: "nativeInputMonitoring", value: inputMonitoringStatus()),
|
||||
URLQueryItem(name: "nativeMicrophone", value: microphonePermissionStatus()),
|
||||
URLQueryItem(name: "nativeCamera", value: cameraPermissionStatus()),
|
||||
]
|
||||
}
|
||||
|
||||
@@ -248,63 +234,10 @@ final class AppDelegate: NSObject, NSApplicationDelegate, WKNavigationDelegate {
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
private func microphonePermissionStatus() -> String {
|
||||
switch AVCaptureDevice.authorizationStatus(for: .audio) {
|
||||
case .authorized:
|
||||
return "granted"
|
||||
case .denied, .restricted:
|
||||
return "missing"
|
||||
case .notDetermined:
|
||||
return "unknown"
|
||||
@unknown default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
private func cameraPermissionStatus() -> String {
|
||||
switch AVCaptureDevice.authorizationStatus(for: .video) {
|
||||
case .authorized:
|
||||
return "granted"
|
||||
case .denied, .restricted:
|
||||
return "missing"
|
||||
case .notDetermined:
|
||||
return "unknown"
|
||||
@unknown default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
private func inputMonitoringStatus() -> String {
|
||||
if #available(macOS 10.15, *) {
|
||||
return CGPreflightListenEventAccess() ? "granted" : "missing"
|
||||
}
|
||||
|
||||
switch IOHIDCheckAccess(kIOHIDRequestTypeListenEvent) {
|
||||
case kIOHIDAccessTypeGranted:
|
||||
return "granted"
|
||||
case kIOHIDAccessTypeDenied:
|
||||
return "missing"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
private func requestNativePermission(for target: String) {
|
||||
let targets: [String]
|
||||
if target == "core" {
|
||||
targets = ["accessibility", "screenRecording"]
|
||||
} else if target == "all" {
|
||||
targets = [
|
||||
"accessibility",
|
||||
"screenRecording",
|
||||
"automation",
|
||||
"fullDiskAccess",
|
||||
"inputMonitoring",
|
||||
"notifications",
|
||||
"microphone",
|
||||
"camera",
|
||||
"localNetwork",
|
||||
]
|
||||
} else {
|
||||
targets = [target]
|
||||
}
|
||||
@@ -320,20 +253,6 @@ final class AppDelegate: NSObject, NSApplicationDelegate, WKNavigationDelegate {
|
||||
requestAccessibilityPermission()
|
||||
case "screenRecording":
|
||||
requestScreenRecordingPermission()
|
||||
case "automation":
|
||||
requestAutomationPermission()
|
||||
case "fullDiskAccess":
|
||||
preflightFullDiskAccess()
|
||||
case "inputMonitoring":
|
||||
requestInputMonitoringPermission()
|
||||
case "notifications":
|
||||
requestNotificationPermission()
|
||||
case "microphone":
|
||||
requestMicrophonePermission()
|
||||
case "camera":
|
||||
requestCameraPermission()
|
||||
case "localNetwork":
|
||||
requestLocalNetworkPermission()
|
||||
default:
|
||||
break
|
||||
}
|
||||
@@ -345,18 +264,6 @@ final class AppDelegate: NSObject, NSApplicationDelegate, WKNavigationDelegate {
|
||||
_ = AXIsProcessTrustedWithOptions(options)
|
||||
}
|
||||
|
||||
private func requestMicrophonePermission() {
|
||||
if AVCaptureDevice.authorizationStatus(for: .audio) == .notDetermined {
|
||||
AVCaptureDevice.requestAccess(for: .audio) { _ in }
|
||||
}
|
||||
}
|
||||
|
||||
private func requestCameraPermission() {
|
||||
if AVCaptureDevice.authorizationStatus(for: .video) == .notDetermined {
|
||||
AVCaptureDevice.requestAccess(for: .video) { _ in }
|
||||
}
|
||||
}
|
||||
|
||||
private func requestScreenRecordingPermission() {
|
||||
if #available(macOS 10.15, *) {
|
||||
_ = CGRequestScreenCaptureAccess()
|
||||
@@ -365,105 +272,22 @@ final class AppDelegate: NSObject, NSApplicationDelegate, WKNavigationDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
var listenRequestResult = false
|
||||
if #available(macOS 10.15, *) {
|
||||
listenRequestResult = CGRequestListenEventAccess()
|
||||
} else {
|
||||
listenRequestResult = IOHIDRequestAccess(kIOHIDRequestTypeListenEvent)
|
||||
}
|
||||
NSLog("boss-agent input monitoring listen request result=%@", listenRequestResult ? "true" : "false")
|
||||
|
||||
if globalKeyMonitor == nil {
|
||||
globalKeyMonitor = NSEvent.addGlobalMonitorForEvents(matching: [.keyDown]) { _ in }
|
||||
}
|
||||
preflightKeyboardStateRead()
|
||||
primeInputMonitoringHidPath()
|
||||
|
||||
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 preflightKeyboardStateRead() {
|
||||
for keyCode in [CGKeyCode(0), CGKeyCode(1), CGKeyCode(49), CGKeyCode(53)] {
|
||||
_ = CGEventSource.keyState(.combinedSessionState, key: keyCode)
|
||||
}
|
||||
}
|
||||
|
||||
private func primeInputMonitoringHidPath() {
|
||||
let manager = IOHIDManagerCreate(kCFAllocatorDefault, IOOptionBits(kIOHIDOptionsTypeNone))
|
||||
let keyboardMatching = [
|
||||
kIOHIDDeviceUsagePageKey as String: kHIDPage_GenericDesktop,
|
||||
kIOHIDDeviceUsageKey as String: kHIDUsage_GD_Keyboard,
|
||||
] as CFDictionary
|
||||
IOHIDManagerSetDeviceMatching(manager, keyboardMatching)
|
||||
let openResult = IOHIDManagerOpen(manager, IOOptionBits(kIOHIDOptionsTypeNone))
|
||||
NSLog("boss-agent input monitoring hid open result=%d", openResult)
|
||||
inputMonitoringManager = manager
|
||||
}
|
||||
|
||||
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.settings.PrivacySecurity.extension?Security",
|
||||
"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",
|
||||
"automation": "x-apple.systempreferences:com.apple.settings.PrivacySecurity.extension?Privacy_Automation",
|
||||
"fullDiskAccess": "x-apple.systempreferences:com.apple.settings.PrivacySecurity.extension?Privacy_AllFiles",
|
||||
"inputMonitoring": "x-apple.systempreferences:com.apple.settings.PrivacySecurity.extension?Privacy_ListenEvent",
|
||||
"notifications": "x-apple.systempreferences:com.apple.Notifications-Settings.extension",
|
||||
"microphone": "x-apple.systempreferences:com.apple.settings.PrivacySecurity.extension?Privacy_Microphone",
|
||||
"camera": "x-apple.systempreferences:com.apple.settings.PrivacySecurity.extension?Privacy_Camera",
|
||||
"localNetwork": "x-apple.systempreferences:com.apple.settings.PrivacySecurity.extension?privacy-localnetwork",
|
||||
]
|
||||
return URL(string: mapping[target] ?? mapping["all"]!)
|
||||
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() {
|
||||
|
||||
@@ -18,73 +18,17 @@ const PERMISSION_DEFS = [
|
||||
},
|
||||
];
|
||||
|
||||
const EXTENDED_PERMISSION_DEFS = [
|
||||
{
|
||||
key: "automation",
|
||||
label: "自动化控制",
|
||||
description: "用于 AppleScript 控制 Finder、浏览器和企业软件;基础桌面控制不强制依赖",
|
||||
tier: "extended",
|
||||
},
|
||||
{
|
||||
key: "fullDiskAccess",
|
||||
label: "全磁盘访问",
|
||||
description: "用于读取和写入企业授权目录、日志与开发资产",
|
||||
tier: "extended",
|
||||
},
|
||||
{
|
||||
key: "inputMonitoring",
|
||||
label: "输入监控",
|
||||
description: "用于低层热键、复杂输入和部分不可访问控件兜底",
|
||||
tier: "extended",
|
||||
},
|
||||
{
|
||||
key: "notifications",
|
||||
label: "通知权限",
|
||||
description: "用于后台任务、接管结果和风险告警提醒",
|
||||
tier: "extended",
|
||||
},
|
||||
{
|
||||
key: "microphone",
|
||||
label: "麦克风",
|
||||
description: "用于语音指令、会议和音频协作场景",
|
||||
tier: "extended",
|
||||
},
|
||||
{
|
||||
key: "camera",
|
||||
label: "摄像头",
|
||||
description: "用于视觉协作、会议和现场画面确认",
|
||||
tier: "extended",
|
||||
},
|
||||
{
|
||||
key: "localNetwork",
|
||||
label: "本地网络",
|
||||
description: "用于发现和连接局域网设备、开发板与企业内网服务",
|
||||
tier: "extended",
|
||||
},
|
||||
];
|
||||
|
||||
const MACOS_PERMISSION_SETTINGS = {
|
||||
all: "x-apple.systempreferences:com.apple.settings.PrivacySecurity.extension?Security",
|
||||
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",
|
||||
automation: "x-apple.systempreferences:com.apple.settings.PrivacySecurity.extension?Privacy_Automation",
|
||||
fullDiskAccess: "x-apple.systempreferences:com.apple.settings.PrivacySecurity.extension?Privacy_AllFiles",
|
||||
inputMonitoring: "x-apple.systempreferences:com.apple.settings.PrivacySecurity.extension?Privacy_ListenEvent",
|
||||
notifications: "x-apple.systempreferences:com.apple.Notifications-Settings.extension",
|
||||
microphone: "x-apple.systempreferences:com.apple.settings.PrivacySecurity.extension?Privacy_Microphone",
|
||||
camera: "x-apple.systempreferences:com.apple.settings.PrivacySecurity.extension?Privacy_Camera",
|
||||
localNetwork: "x-apple.systempreferences:com.apple.settings.PrivacySecurity.extension?privacy-localnetwork",
|
||||
};
|
||||
|
||||
const AUTO_PREFLIGHT_PERMISSION_KEYS = new Set(["accessibility", "screenRecording", "automation"]);
|
||||
const AUTO_PREFLIGHT_PERMISSION_KEYS = new Set(["accessibility", "screenRecording"]);
|
||||
|
||||
const NATIVE_PERMISSION_QUERY_PARAMS = {
|
||||
accessibility: "nativeAccessibility",
|
||||
screenRecording: "nativeScreenRecording",
|
||||
inputMonitoring: "nativeInputMonitoring",
|
||||
microphone: "nativeMicrophone",
|
||||
camera: "nativeCamera",
|
||||
};
|
||||
|
||||
function nonEmpty(value) {
|
||||
@@ -204,11 +148,7 @@ function resolvePermissionReadiness(coreItems, extendedItems) {
|
||||
const extendedGrantedCount = extendedItems.filter((item) => item.status === "granted").length;
|
||||
const coreReady = coreGrantedCount === coreItems.length;
|
||||
const fullControlReady = coreReady && extendedGrantedCount === extendedItems.length;
|
||||
const summary = fullControlReady
|
||||
? "基础桌面控制和扩展能力权限已具备"
|
||||
: coreReady
|
||||
? "基础桌面控制已可用,扩展权限按场景启用"
|
||||
: "基础桌面控制待授权,桌面接管不可用";
|
||||
const summary = coreReady ? "基础桌面控制已可用" : "基础桌面控制待授权,桌面接管不可用";
|
||||
|
||||
return {
|
||||
coreReady,
|
||||
@@ -219,12 +159,12 @@ function resolvePermissionReadiness(coreItems, extendedItems) {
|
||||
extendedTotal: extendedItems.length,
|
||||
summary,
|
||||
detail:
|
||||
"参考 Codex Computer Use 的最小权限模型,基础桌面控制只要求辅助功能和屏幕录制;自动化控制、全磁盘访问、输入监控、通知、麦克风、摄像头和本地网络都按具体任务场景再单独启用。",
|
||||
"参考 Codex Computer Use 的最小权限模型,boss-agent 只要求辅助功能和屏幕录制:辅助功能负责点击输入,屏幕录制负责画面识别。",
|
||||
};
|
||||
}
|
||||
|
||||
function buildPermissionSetupPlan(coreItems, extendedItems, readiness) {
|
||||
const actions = [...coreItems, ...extendedItems].map((item) => ({
|
||||
function buildPermissionSetupPlan(coreItems, readiness) {
|
||||
const actions = coreItems.map((item) => ({
|
||||
key: item.key,
|
||||
label: item.label,
|
||||
description: item.description,
|
||||
@@ -232,21 +172,18 @@ function buildPermissionSetupPlan(coreItems, extendedItems, readiness) {
|
||||
status: item.status,
|
||||
requiredForSilentControl: item.tier === "core",
|
||||
canPreflight: AUTO_PREFLIGHT_PERMISSION_KEYS.has(item.key),
|
||||
settingsUrl: MACOS_PERMISSION_SETTINGS[item.key] ?? MACOS_PERMISSION_SETTINGS.all,
|
||||
settingsUrl: MACOS_PERMISSION_SETTINGS[item.key] ?? MACOS_PERMISSION_SETTINGS.core,
|
||||
openUrl: `/api/v1/boss-agent/permissions/open?target=${encodeURIComponent(item.key)}&returnTab=permissions`,
|
||||
owner: "boss-agent.app",
|
||||
}));
|
||||
const missingRequiredActions = actions.filter(
|
||||
(action) => action.requiredForSilentControl && action.status !== "granted",
|
||||
);
|
||||
const optionalMissingActions = actions.filter(
|
||||
(action) => !action.requiredForSilentControl && action.status !== "granted",
|
||||
);
|
||||
|
||||
return {
|
||||
mode: "minimal_computer_use",
|
||||
title: "基础桌面控制授权",
|
||||
goal: "按 Codex Computer Use 的思路,先拿辅助功能和屏幕录制两项最小权限;其他能力等任务需要时再申请。",
|
||||
goal: "按 Codex Computer Use 的思路,只申请辅助功能和屏幕录制两项最小权限。",
|
||||
silentUseReady: missingRequiredActions.length === 0,
|
||||
primaryAction: {
|
||||
label: "打开基础授权",
|
||||
@@ -256,9 +193,9 @@ function buildPermissionSetupPlan(coreItems, extendedItems, readiness) {
|
||||
actions,
|
||||
missingKeys: missingRequiredActions.map((action) => action.key),
|
||||
missingRequiredKeys: missingRequiredActions.map((action) => action.key),
|
||||
optionalMissingKeys: optionalMissingActions.map((action) => action.key),
|
||||
optionalMissingKeys: [],
|
||||
summary: readiness.coreReady
|
||||
? "基础桌面控制已可用;扩展权限不会阻塞接管,只在对应任务需要时提示。"
|
||||
? "基础桌面控制已可用;后续控制只校验这两项权限。"
|
||||
: "仍缺少基础桌面控制权限,请先授权辅助功能和屏幕录制。",
|
||||
persistenceNote:
|
||||
"macOS 会把授权持久写入系统隐私数据库;稳定签名后,后续更新不会因为二进制哈希变化反复丢失授权。",
|
||||
@@ -270,7 +207,12 @@ export function mergeBossAgentNativePermissionOverrides(permissions = {}, queryP
|
||||
if (typeof queryParams.get === "function") return queryParams.get(name);
|
||||
return queryParams[name];
|
||||
};
|
||||
const merged = { ...permissions };
|
||||
const merged = {};
|
||||
for (const permissionKey of Object.keys(NATIVE_PERMISSION_QUERY_PARAMS)) {
|
||||
if (isPermissionStatus(permissions[permissionKey])) {
|
||||
merged[permissionKey] = permissions[permissionKey];
|
||||
}
|
||||
}
|
||||
for (const [permissionKey, queryKey] of Object.entries(NATIVE_PERMISSION_QUERY_PARAMS)) {
|
||||
const value = getQueryValue(queryKey);
|
||||
if (isPermissionStatus(value)) {
|
||||
@@ -312,9 +254,9 @@ export function buildBossAgentStatus(config = {}, runtime = {}, options = {}) {
|
||||
const serverOk = runtime.lastHeartbeatOk === true;
|
||||
const qrPayload = bound ? "" : buildBindingPayload(config);
|
||||
const corePermissionItems = permissionItems(PERMISSION_DEFS, permissions);
|
||||
const extendedPermissionItems = permissionItems(EXTENDED_PERMISSION_DEFS, permissions);
|
||||
const extendedPermissionItems = [];
|
||||
const permissionReadiness = resolvePermissionReadiness(corePermissionItems, extendedPermissionItems);
|
||||
const permissionSetup = buildPermissionSetupPlan(corePermissionItems, extendedPermissionItems, permissionReadiness);
|
||||
const permissionSetup = buildPermissionSetupPlan(corePermissionItems, permissionReadiness);
|
||||
|
||||
return {
|
||||
appName: "boss-agent",
|
||||
@@ -883,22 +825,27 @@ function runCommand(command, args, timeoutMs = 2500) {
|
||||
});
|
||||
}
|
||||
|
||||
export function resolveBossAgentPermissionSettingsUrl(target = "all") {
|
||||
return MACOS_PERMISSION_SETTINGS[target] ?? MACOS_PERMISSION_SETTINGS.all;
|
||||
function normalizePermissionTarget(target = "core") {
|
||||
return Object.hasOwn(MACOS_PERMISSION_SETTINGS, target) ? target : "core";
|
||||
}
|
||||
|
||||
export async function openBossAgentPermissionSettings(target = "all", platform = process.platform) {
|
||||
const settingsUrl = resolveBossAgentPermissionSettingsUrl(target);
|
||||
export function resolveBossAgentPermissionSettingsUrl(target = "core") {
|
||||
return MACOS_PERMISSION_SETTINGS[normalizePermissionTarget(target)];
|
||||
}
|
||||
|
||||
export async function openBossAgentPermissionSettings(target = "core", platform = process.platform) {
|
||||
const normalizedTarget = normalizePermissionTarget(target);
|
||||
const settingsUrl = resolveBossAgentPermissionSettingsUrl(normalizedTarget);
|
||||
if (platform !== "darwin") {
|
||||
return {
|
||||
ok: false,
|
||||
target,
|
||||
target: normalizedTarget,
|
||||
settingsUrl,
|
||||
message: "当前平台暂不支持自动打开系统隐私设置,请在系统设置中手动完成授权。",
|
||||
};
|
||||
}
|
||||
|
||||
const nativeUrl = `boss-agent://permissions/open?target=${encodeURIComponent(target)}&returnTab=permissions`;
|
||||
const nativeUrl = `boss-agent://permissions/open?target=${encodeURIComponent(normalizedTarget)}&returnTab=permissions`;
|
||||
const nativeLaunch = await runCommand(
|
||||
"open",
|
||||
[
|
||||
@@ -906,7 +853,7 @@ export async function openBossAgentPermissionSettings(target = "all", platform =
|
||||
"/Applications/boss-agent.app",
|
||||
"--args",
|
||||
"--request-permission",
|
||||
target,
|
||||
normalizedTarget,
|
||||
"--return-tab",
|
||||
"permissions",
|
||||
],
|
||||
@@ -915,7 +862,7 @@ export async function openBossAgentPermissionSettings(target = "all", platform =
|
||||
if (nativeLaunch.ok) {
|
||||
return {
|
||||
ok: true,
|
||||
target,
|
||||
target: normalizedTarget,
|
||||
settingsUrl,
|
||||
message: "已通过 boss-agent 发起系统权限申请。",
|
||||
nativeRequest: true,
|
||||
@@ -927,7 +874,7 @@ export async function openBossAgentPermissionSettings(target = "all", platform =
|
||||
if (nativeDeepLink.ok) {
|
||||
return {
|
||||
ok: true,
|
||||
target,
|
||||
target: normalizedTarget,
|
||||
settingsUrl,
|
||||
message: "已通过 boss-agent 发起系统权限申请。",
|
||||
nativeRequest: true,
|
||||
@@ -938,7 +885,7 @@ export async function openBossAgentPermissionSettings(target = "all", platform =
|
||||
const result = await runCommand("open", [settingsUrl], 2500);
|
||||
return {
|
||||
ok: result.ok,
|
||||
target,
|
||||
target: normalizedTarget,
|
||||
settingsUrl,
|
||||
message: result.ok
|
||||
? "已打开系统权限设置。"
|
||||
@@ -959,7 +906,6 @@ export async function detectLocalComputerPermissions(platform = process.platform
|
||||
return {
|
||||
accessibility: "unknown",
|
||||
screenRecording: "unknown",
|
||||
automation: "unknown",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -967,7 +913,6 @@ export async function detectLocalComputerPermissions(platform = process.platform
|
||||
"-e",
|
||||
'tell application "System Events" to get UI elements enabled',
|
||||
]);
|
||||
const automation = await runCommand("osascript", ["-e", 'tell application "Finder" to get name']);
|
||||
const screenshotPath = path.join(os.tmpdir(), `boss-agent-permission-${Date.now()}.png`);
|
||||
const screen = await runCommand("screencapture", ["-x", "-t", "png", screenshotPath], 3500);
|
||||
let screenRecording = "missing";
|
||||
@@ -985,6 +930,5 @@ export async function detectLocalComputerPermissions(platform = process.platform
|
||||
return {
|
||||
accessibility: accessibility.ok && /true/i.test(accessibility.stdout) ? "granted" : "missing",
|
||||
screenRecording,
|
||||
automation: automation.ok ? "granted" : "missing",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1037,7 +1037,7 @@ const server = createServer(async (request, response) => {
|
||||
}
|
||||
|
||||
if (requestUrl.pathname === "/api/v1/boss-agent/permissions/open") {
|
||||
const target = requestUrl.searchParams.get("target") || "all";
|
||||
const target = requestUrl.searchParams.get("target") || "core";
|
||||
const returnTab = normalizeBossAgentTab(requestUrl.searchParams.get("returnTab") ?? "permissions");
|
||||
const result = await openBossAgentPermissionSettings(target);
|
||||
const wantsJson = String(request.headers.accept || "").includes("application/json");
|
||||
|
||||
@@ -43,11 +43,7 @@ swiftc "$SOURCE_FILE" \
|
||||
-o "$BINARY_PATH" \
|
||||
-framework Cocoa \
|
||||
-framework WebKit \
|
||||
-framework ApplicationServices \
|
||||
-framework AVFoundation \
|
||||
-framework IOKit \
|
||||
-framework Network \
|
||||
-framework UserNotifications
|
||||
-framework ApplicationServices
|
||||
|
||||
chmod +x "$BINARY_PATH"
|
||||
|
||||
@@ -169,23 +165,8 @@ cat > "$CONTENTS_DIR/Info.plist" <<'PLIST'
|
||||
<string>13.0</string>
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<true/>
|
||||
<key>NSAppleEventsUsageDescription</key>
|
||||
<string>boss-agent 需要通过自动化控制 Finder、浏览器和授权的企业应用,以完成远程桌面级任务。</string>
|
||||
<key>NSScreenCaptureUsageDescription</key>
|
||||
<string>boss-agent 需要读取屏幕画面,用于识别桌面状态、系统弹窗和远程控制结果。</string>
|
||||
<key>NSInputMonitoringUsageDescription</key>
|
||||
<string>boss-agent 需要输入监控权限,用于低层热键、复杂输入和部分不可访问控件兜底。</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>boss-agent 需要麦克风权限,用于语音协作和远程指令输入。</string>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>boss-agent 需要摄像头权限,用于视觉协作和现场画面确认。</string>
|
||||
<key>NSLocalNetworkUsageDescription</key>
|
||||
<string>boss-agent 需要访问本地网络,用于发现和连接局域网设备、开发板和企业内网服务。</string>
|
||||
<key>NSBonjourServices</key>
|
||||
<array>
|
||||
<string>_http._tcp</string>
|
||||
<string>_boss-agent._tcp</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
PLIST
|
||||
|
||||
@@ -58,19 +58,14 @@ test("boss-agent status exposes unbound QR binding and local permission states",
|
||||
assert.match(status.permissionReadiness.summary, /基础桌面控制/);
|
||||
assert.equal(status.permissionSetup.mode, "minimal_computer_use");
|
||||
assert.equal(status.permissionSetup.silentUseReady, false);
|
||||
assert.equal(status.permissionSetup.actions.some((action) => action.key === "fullDiskAccess"), true);
|
||||
assert.deepEqual(
|
||||
status.permissionSetup.actions.map((action) => action.key),
|
||||
["accessibility", "screenRecording"],
|
||||
);
|
||||
assert.equal(
|
||||
status.permissionSetup.actions.every((action) => action.settingsUrl.startsWith("x-apple.systempreferences:")),
|
||||
true,
|
||||
);
|
||||
assert.equal(
|
||||
status.permissionSetup.actions.find((action) => action.key === "localNetwork")?.settingsUrl,
|
||||
"x-apple.systempreferences:com.apple.settings.PrivacySecurity.extension?privacy-localnetwork",
|
||||
);
|
||||
assert.equal(
|
||||
status.permissionSetup.actions.find((action) => action.key === "inputMonitoring")?.settingsUrl,
|
||||
"x-apple.systempreferences:com.apple.settings.PrivacySecurity.extension?Privacy_ListenEvent",
|
||||
);
|
||||
assert.equal(
|
||||
status.permissionSetup.actions.find((action) => action.key === "screenRecording")?.settingsUrl,
|
||||
"x-apple.systempreferences:com.apple.settings.PrivacySecurity.extension?Privacy_ScreenCapture",
|
||||
@@ -82,12 +77,7 @@ test("boss-agent status exposes unbound QR binding and local permission states",
|
||||
["screenRecording", "missing"],
|
||||
],
|
||||
);
|
||||
assert.deepEqual(
|
||||
status.permissions.extendedItems.slice(0, 1).map((item) => [item.key, item.status]),
|
||||
[
|
||||
["automation", "unknown"],
|
||||
],
|
||||
);
|
||||
assert.deepEqual(status.permissions.extendedItems, []);
|
||||
});
|
||||
|
||||
test("boss-agent status treats token-backed devices as bound and renders enterprise UI", () => {
|
||||
@@ -186,10 +176,9 @@ test("boss-agent permission and skill menu entries render as separate tab pages"
|
||||
assert.match(permissionsHtml, /class="active" href="\/boss-agent\?tab=permissions"/);
|
||||
assert.match(permissionsHtml, /<h2>基础桌面控制授权<\/h2>/);
|
||||
assert.match(permissionsHtml, /基础桌面控制已可用/);
|
||||
assert.match(permissionsHtml, /扩展权限不会阻塞接管/);
|
||||
assert.match(permissionsHtml, /后续静默使用/);
|
||||
assert.match(permissionsHtml, /api\/v1\/boss-agent\/permissions\/open\?target=core&returnTab=permissions/);
|
||||
assert.match(permissionsHtml, /api\/v1\/boss-agent\/permissions\/open\?target=fullDiskAccess&returnTab=permissions/);
|
||||
assert.doesNotMatch(permissionsHtml, /fullDiskAccess|inputMonitoring|automation|microphone|camera|localNetwork|notifications/);
|
||||
assert.doesNotMatch(permissionsHtml, /<h2>Skill 部署情况<\/h2>/);
|
||||
|
||||
const skillsHtml = renderBossAgentHtml(status, { activeTab: "skills" });
|
||||
@@ -199,7 +188,7 @@ test("boss-agent permission and skill menu entries render as separate tab pages"
|
||||
assert.doesNotMatch(skillsHtml, /<h2>基础桌面控制授权<\/h2>/);
|
||||
});
|
||||
|
||||
test("boss-agent treats accessibility and screen recording as the minimal computer-use permission set", () => {
|
||||
test("boss-agent only exposes accessibility and screen recording permission requests", () => {
|
||||
const status = buildBossAgentStatus(
|
||||
{
|
||||
deviceId: "macbook-air",
|
||||
@@ -228,16 +217,20 @@ test("boss-agent treats accessibility and screen recording as the minimal comput
|
||||
assert.equal(status.permissionSetup.silentUseReady, true);
|
||||
assert.equal(status.permissionSetup.primaryAction.href, "/api/v1/boss-agent/permissions/open?target=core&returnTab=permissions");
|
||||
assert.match(status.permissionReadiness.summary, /基础桌面控制已可用/);
|
||||
assert.equal(status.permissionSetup.actions.find((action) => action.key === "automation")?.requiredForSilentControl, false);
|
||||
assert.equal(status.permissionSetup.actions.find((action) => action.key === "fullDiskAccess")?.requiredForSilentControl, false);
|
||||
assert.deepEqual(status.permissionSetup.missingRequiredKeys, []);
|
||||
assert.deepEqual(
|
||||
status.permissions.items.map((item) => item.key),
|
||||
["accessibility", "screenRecording"],
|
||||
);
|
||||
assert.deepEqual(status.permissions.extendedItems, []);
|
||||
assert.deepEqual(
|
||||
status.permissionSetup.actions.map((action) => action.key),
|
||||
["accessibility", "screenRecording"],
|
||||
);
|
||||
assert.deepEqual(status.permissionSetup.optionalMissingKeys, []);
|
||||
});
|
||||
|
||||
test("boss-agent native permission overrides update app-owned camera and microphone state", () => {
|
||||
test("boss-agent native permission overrides only update core desktop-control permissions", () => {
|
||||
const merged = mergeBossAgentNativePermissionOverrides(
|
||||
{
|
||||
accessibility: "missing",
|
||||
@@ -258,10 +251,6 @@ test("boss-agent native permission overrides update app-owned camera and microph
|
||||
assert.deepEqual(merged, {
|
||||
accessibility: "granted",
|
||||
screenRecording: "granted",
|
||||
automation: "granted",
|
||||
camera: "granted",
|
||||
microphone: "missing",
|
||||
inputMonitoring: "granted",
|
||||
});
|
||||
});
|
||||
|
||||
@@ -303,41 +292,18 @@ test("boss-agent mac app intercepts permission links and triggers native app per
|
||||
assert.match(swiftSource, /lastPermissionRequestTarget/);
|
||||
assert.match(swiftSource, /AXIsProcessTrustedWithOptions/);
|
||||
assert.match(swiftSource, /CGRequestScreenCaptureAccess/);
|
||||
assert.match(swiftSource, /AVCaptureDevice\.requestAccess\(for: \.audio/);
|
||||
assert.match(swiftSource, /AVCaptureDevice\.requestAccess\(for: \.video/);
|
||||
assert.match(swiftSource, /UNUserNotificationCenter\.current\(\)\.requestAuthorization/);
|
||||
assert.match(swiftSource, /import IOKit\.hid/);
|
||||
assert.match(swiftSource, /CGRequestListenEventAccess\(\)/);
|
||||
assert.match(swiftSource, /CGPreflightListenEventAccess\(\)/);
|
||||
assert.match(swiftSource, /NSEvent\.addGlobalMonitorForEvents\(matching: \[\.keyDown\]\)/);
|
||||
assert.match(swiftSource, /CGEventSource\.keyState\(\.combinedSessionState/);
|
||||
assert.match(swiftSource, /IOHIDManagerOpen\(manager/);
|
||||
assert.match(swiftSource, /kHIDUsage_GD_Keyboard/);
|
||||
assert.match(swiftSource, /IOHIDRequestAccess\(kIOHIDRequestTypeListenEvent\)/);
|
||||
assert.match(swiftSource, /IOHIDCheckAccess\(kIOHIDRequestTypeListenEvent\)/);
|
||||
assert.match(swiftSource, /CGEvent\.tapCreate/);
|
||||
assert.doesNotMatch(swiftSource, /AVCaptureDevice\.requestAccess|UNUserNotificationCenter|import IOKit\.hid|CGRequestListenEventAccess|IOHIDRequestAccess|CGEvent\.tapCreate|NWBrowser/);
|
||||
assert.match(swiftSource, /NSWorkspace\.shared\.open/);
|
||||
assert.match(swiftSource, /deadline: \.now\(\) \+ 0\.45/);
|
||||
assert.match(swiftSource, /loadAgentPanel\(tab:/);
|
||||
assert.match(swiftSource, /returnTab/);
|
||||
assert.match(swiftSource, /nativePermissionQueryItems/);
|
||||
assert.match(swiftSource, /nativeCamera/);
|
||||
assert.match(swiftSource, /nativeMicrophone/);
|
||||
assert.match(swiftSource, /nativeInputMonitoring/);
|
||||
assert.match(swiftSource, /privacy-localnetwork/);
|
||||
assert.match(swiftSource, /com\.apple\.settings\.PrivacySecurity\.extension\?Privacy_ListenEvent/);
|
||||
assert.doesNotMatch(swiftSource, /nativeCamera|nativeMicrophone|nativeInputMonitoring|privacy-localnetwork|Privacy_ListenEvent/);
|
||||
assert.doesNotMatch(swiftSource, /com\.apple\.preference\.security\?Privacy_ListenEvent/);
|
||||
assert.match(swiftSource, /func application\(_ application: NSApplication, open urls: \[URL\]\)/);
|
||||
assert.match(swiftSource, /isBossAgentDeepLink/);
|
||||
assert.match(swiftSource, /AVCaptureDevice\.authorizationStatus\(for: \.video/);
|
||||
assert.match(swiftSource, /AVCaptureDevice\.authorizationStatus\(for: \.audio/);
|
||||
assert.match(swiftSource, /NSApplication\.didBecomeActiveNotification/);
|
||||
assert.match(buildScript, /NSMicrophoneUsageDescription/);
|
||||
assert.match(buildScript, /NSCameraUsageDescription/);
|
||||
assert.match(buildScript, /NSAppleEventsUsageDescription/);
|
||||
assert.match(buildScript, /NSLocalNetworkUsageDescription/);
|
||||
assert.match(buildScript, /NSInputMonitoringUsageDescription/);
|
||||
assert.match(buildScript, /framework IOKit/);
|
||||
assert.doesNotMatch(buildScript, /NSMicrophoneUsageDescription|NSCameraUsageDescription|NSAppleEventsUsageDescription|NSLocalNetworkUsageDescription|NSInputMonitoringUsageDescription|framework IOKit|framework Network|framework UserNotifications|framework AVFoundation/);
|
||||
assert.match(buildScript, /CFBundleURLTypes/);
|
||||
assert.match(buildScript, /boss-agent/);
|
||||
assert.match(buildScript, /CFBundleIconFile/);
|
||||
|
||||
Reference in New Issue
Block a user