fix: make boss agent permissions match computer use minimum
This commit is contained in:
@@ -290,8 +290,11 @@ final class AppDelegate: NSObject, NSApplicationDelegate, WKNavigationDelegate {
|
||||
}
|
||||
|
||||
private func requestNativePermission(for target: String) {
|
||||
let targets = target == "all"
|
||||
? [
|
||||
let targets: [String]
|
||||
if target == "core" {
|
||||
targets = ["accessibility", "screenRecording"]
|
||||
} else if target == "all" {
|
||||
targets = [
|
||||
"accessibility",
|
||||
"screenRecording",
|
||||
"automation",
|
||||
@@ -302,7 +305,9 @@ final class AppDelegate: NSObject, NSApplicationDelegate, WKNavigationDelegate {
|
||||
"camera",
|
||||
"localNetwork",
|
||||
]
|
||||
: [target]
|
||||
} else {
|
||||
targets = [target]
|
||||
}
|
||||
|
||||
for permission in targets {
|
||||
requestSingleNativePermission(permission)
|
||||
@@ -447,6 +452,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, WKNavigationDelegate {
|
||||
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",
|
||||
|
||||
@@ -16,15 +16,15 @@ const PERMISSION_DEFS = [
|
||||
description: "用于识别桌面画面和系统弹窗",
|
||||
tier: "core",
|
||||
},
|
||||
{
|
||||
key: "automation",
|
||||
label: "自动化控制",
|
||||
description: "用于控制 Finder、浏览器和企业软件",
|
||||
tier: "core",
|
||||
},
|
||||
];
|
||||
|
||||
const EXTENDED_PERMISSION_DEFS = [
|
||||
{
|
||||
key: "automation",
|
||||
label: "自动化控制",
|
||||
description: "用于 AppleScript 控制 Finder、浏览器和企业软件;基础桌面控制不强制依赖",
|
||||
tier: "extended",
|
||||
},
|
||||
{
|
||||
key: "fullDiskAccess",
|
||||
label: "全磁盘访问",
|
||||
@@ -65,6 +65,7 @@ const EXTENDED_PERMISSION_DEFS = [
|
||||
|
||||
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",
|
||||
@@ -204,10 +205,10 @@ function resolvePermissionReadiness(coreItems, extendedItems) {
|
||||
const coreReady = coreGrantedCount === coreItems.length;
|
||||
const fullControlReady = coreReady && extendedGrantedCount === extendedItems.length;
|
||||
const summary = fullControlReady
|
||||
? "完整接管权限已具备"
|
||||
? "基础桌面控制和扩展能力权限已具备"
|
||||
: coreReady
|
||||
? "核心桌面控制已具备,完整接管待补齐"
|
||||
: "核心桌面控制待授权,完整接管不可用";
|
||||
? "基础桌面控制已可用,扩展权限按场景启用"
|
||||
: "基础桌面控制待授权,桌面接管不可用";
|
||||
|
||||
return {
|
||||
coreReady,
|
||||
@@ -218,7 +219,7 @@ function resolvePermissionReadiness(coreItems, extendedItems) {
|
||||
extendedTotal: extendedItems.length,
|
||||
summary,
|
||||
detail:
|
||||
"辅助功能、屏幕录制、自动化控制是桌面点击输入与画面识别的核心权限;完整接管还需要按业务场景补齐全磁盘访问、输入监控、通知、麦克风、摄像头和本地网络。",
|
||||
"参考 Codex Computer Use 的最小权限模型,基础桌面控制只要求辅助功能和屏幕录制;自动化控制、全磁盘访问、输入监控、通知、麦克风、摄像头和本地网络都按具体任务场景再单独启用。",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -229,31 +230,38 @@ function buildPermissionSetupPlan(coreItems, extendedItems, readiness) {
|
||||
description: item.description,
|
||||
tier: item.tier,
|
||||
status: item.status,
|
||||
requiredForSilentControl: true,
|
||||
requiredForSilentControl: item.tier === "core",
|
||||
canPreflight: AUTO_PREFLIGHT_PERMISSION_KEYS.has(item.key),
|
||||
settingsUrl: MACOS_PERMISSION_SETTINGS[item.key] ?? MACOS_PERMISSION_SETTINGS.all,
|
||||
openUrl: `/api/v1/boss-agent/permissions/open?target=${encodeURIComponent(item.key)}&returnTab=permissions`,
|
||||
owner: "boss-agent.app",
|
||||
}));
|
||||
const missingActions = actions.filter((action) => action.status !== "granted");
|
||||
const missingRequiredActions = actions.filter(
|
||||
(action) => action.requiredForSilentControl && action.status !== "granted",
|
||||
);
|
||||
const optionalMissingActions = actions.filter(
|
||||
(action) => !action.requiredForSilentControl && action.status !== "granted",
|
||||
);
|
||||
|
||||
return {
|
||||
mode: "one_time_setup",
|
||||
title: "一次完整授权",
|
||||
goal: "首次把完整接管需要的权限集中配置好,后续控制过程中只做状态校验和静默使用。",
|
||||
silentUseReady: missingActions.length === 0,
|
||||
mode: "minimal_computer_use",
|
||||
title: "基础桌面控制授权",
|
||||
goal: "按 Codex Computer Use 的思路,先拿辅助功能和屏幕录制两项最小权限;其他能力等任务需要时再申请。",
|
||||
silentUseReady: missingRequiredActions.length === 0,
|
||||
primaryAction: {
|
||||
label: "打开完整授权向导",
|
||||
href: "/api/v1/boss-agent/permissions/open?target=all&returnTab=permissions",
|
||||
settingsUrl: MACOS_PERMISSION_SETTINGS.all,
|
||||
label: "打开基础授权",
|
||||
href: "/api/v1/boss-agent/permissions/open?target=core&returnTab=permissions",
|
||||
settingsUrl: MACOS_PERMISSION_SETTINGS.core,
|
||||
},
|
||||
actions,
|
||||
missingKeys: missingActions.map((action) => action.key),
|
||||
summary: readiness.fullControlReady
|
||||
? "完整授权已满足,后续可静默执行。"
|
||||
: "仍有权限未确认,请在首次配置阶段一次性补齐,避免后续任务执行中断。",
|
||||
missingKeys: missingRequiredActions.map((action) => action.key),
|
||||
missingRequiredKeys: missingRequiredActions.map((action) => action.key),
|
||||
optionalMissingKeys: optionalMissingActions.map((action) => action.key),
|
||||
summary: readiness.coreReady
|
||||
? "基础桌面控制已可用;扩展权限不会阻塞接管,只在对应任务需要时提示。"
|
||||
: "仍缺少基础桌面控制权限,请先授权辅助功能和屏幕录制。",
|
||||
persistenceNote:
|
||||
"macOS 会把授权持久写入系统隐私数据库;除非用户撤销授权、重装应用或更换运行时签名,否则后续控制不需要重复申请。",
|
||||
"macOS 会把授权持久写入系统隐私数据库;稳定签名后,后续更新不会因为二进制哈希变化反复丢失授权。",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -381,7 +389,11 @@ function setupActionRows(status) {
|
||||
return status.permissionSetup.actions
|
||||
.map((action) => {
|
||||
const tone = statusTone(action.status);
|
||||
const preflight = action.canPreflight ? "可预触发" : "需手动开启";
|
||||
const preflight = action.requiredForSilentControl
|
||||
? "基础必需"
|
||||
: action.canPreflight
|
||||
? "按场景预触发"
|
||||
: "按场景启用";
|
||||
return `<div class="setup-action">
|
||||
<div>
|
||||
<div class="permission-name">${escapeHtml(action.label)}</div>
|
||||
|
||||
@@ -55,8 +55,8 @@ test("boss-agent status exposes unbound QR binding and local permission states",
|
||||
assert.equal(status.skills.syncOk, true);
|
||||
assert.equal(status.permissionReadiness.coreReady, false);
|
||||
assert.equal(status.permissionReadiness.fullControlReady, false);
|
||||
assert.match(status.permissionReadiness.summary, /完整接管/);
|
||||
assert.equal(status.permissionSetup.mode, "one_time_setup");
|
||||
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.equal(
|
||||
@@ -80,6 +80,11 @@ test("boss-agent status exposes unbound QR binding and local permission states",
|
||||
[
|
||||
["accessibility", "granted"],
|
||||
["screenRecording", "missing"],
|
||||
],
|
||||
);
|
||||
assert.deepEqual(
|
||||
status.permissions.extendedItems.slice(0, 1).map((item) => [item.key, item.status]),
|
||||
[
|
||||
["automation", "unknown"],
|
||||
],
|
||||
);
|
||||
@@ -179,10 +184,11 @@ test("boss-agent permission and skill menu entries render as separate tab pages"
|
||||
|
||||
const permissionsHtml = renderBossAgentHtml(status, { activeTab: "permissions" });
|
||||
assert.match(permissionsHtml, /class="active" href="\/boss-agent\?tab=permissions"/);
|
||||
assert.match(permissionsHtml, /<h2>一次完整授权<\/h2>/);
|
||||
assert.match(permissionsHtml, /完整接管待补齐/);
|
||||
assert.match(permissionsHtml, /<h2>基础桌面控制授权<\/h2>/);
|
||||
assert.match(permissionsHtml, /基础桌面控制已可用/);
|
||||
assert.match(permissionsHtml, /扩展权限不会阻塞接管/);
|
||||
assert.match(permissionsHtml, /后续静默使用/);
|
||||
assert.match(permissionsHtml, /api\/v1\/boss-agent\/permissions\/open\?target=all&returnTab=permissions/);
|
||||
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, /<h2>Skill 部署情况<\/h2>/);
|
||||
|
||||
@@ -190,7 +196,45 @@ test("boss-agent permission and skill menu entries render as separate tab pages"
|
||||
assert.match(skillsHtml, /class="active" href="\/boss-agent\?tab=skills"/);
|
||||
assert.match(skillsHtml, /<h2>Skill 部署情况<\/h2>/);
|
||||
assert.match(skillsHtml, /bb-browser/);
|
||||
assert.doesNotMatch(skillsHtml, /<h2>一次完整授权<\/h2>/);
|
||||
assert.doesNotMatch(skillsHtml, /<h2>基础桌面控制授权<\/h2>/);
|
||||
});
|
||||
|
||||
test("boss-agent treats accessibility and screen recording as the minimal computer-use permission set", () => {
|
||||
const status = buildBossAgentStatus(
|
||||
{
|
||||
deviceId: "macbook-air",
|
||||
name: "MacBook Air",
|
||||
account: "krisolo",
|
||||
token: "boss-secret-token",
|
||||
},
|
||||
{
|
||||
lastHeartbeatOk: true,
|
||||
},
|
||||
{
|
||||
permissions: {
|
||||
accessibility: "granted",
|
||||
screenRecording: "granted",
|
||||
automation: "missing",
|
||||
fullDiskAccess: "missing",
|
||||
inputMonitoring: "missing",
|
||||
microphone: "missing",
|
||||
camera: "missing",
|
||||
localNetwork: "missing",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
assert.equal(status.permissionReadiness.coreReady, true);
|
||||
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"],
|
||||
);
|
||||
});
|
||||
|
||||
test("boss-agent native permission overrides update app-owned camera and microphone state", () => {
|
||||
@@ -255,6 +299,7 @@ test("boss-agent mac app intercepts permission links and triggers native app per
|
||||
assert.match(swiftSource, /handleGetUrlEvent/);
|
||||
assert.match(swiftSource, /--request-permission/);
|
||||
assert.match(swiftSource, /--return-tab/);
|
||||
assert.match(swiftSource, /target == "core"/);
|
||||
assert.match(swiftSource, /lastPermissionRequestTarget/);
|
||||
assert.match(swiftSource, /AXIsProcessTrustedWithOptions/);
|
||||
assert.match(swiftSource, /CGRequestScreenCaptureAccess/);
|
||||
|
||||
Reference in New Issue
Block a user