feat: route desktop control to authorized devices

This commit is contained in:
AI Bot
2026-05-12 12:15:43 +08:00
parent bc199dcf5c
commit 4de64ac01c
8 changed files with 407 additions and 5 deletions

View File

@@ -308,6 +308,62 @@ export function classifyMasterAgentControlIntent(
export const classifyMasterAgentControlIntentForTesting = classifyMasterAgentControlIntent;
type ControlTargetDeviceInput = {
replyProjectId: string;
intentCategory: "browser_control" | "desktop_control";
preferredDeviceId?: string;
authorizedDeviceIds: string[];
devices: Array<{
id: string;
status?: string;
capabilities?: {
browserAutomation?: { connected?: boolean };
computerUse?: { connected?: boolean };
};
}>;
projects: Array<{
id: string;
deviceIds?: string[];
}>;
};
function isControlDeviceCapable(
device: ControlTargetDeviceInput["devices"][number] | undefined,
intentCategory: ControlTargetDeviceInput["intentCategory"],
) {
if (!device || device.status !== "online") return false;
if (intentCategory === "browser_control") {
return device.capabilities?.browserAutomation?.connected === true;
}
return device.capabilities?.computerUse?.connected === true;
}
function resolveMasterAgentControlTargetDeviceId(input: ControlTargetDeviceInput) {
const authorized = new Set(input.authorizedDeviceIds);
const deviceById = new Map(input.devices.map((device) => [device.id, device]));
const project = input.projects.find((item) => item.id === input.replyProjectId);
const projectDevice = project?.deviceIds
?.find((deviceId) => authorized.has(deviceId) && isControlDeviceCapable(deviceById.get(deviceId), input.intentCategory));
if (projectDevice) return projectDevice;
const authorizedCapableDevices = input.authorizedDeviceIds.filter((deviceId) =>
isControlDeviceCapable(deviceById.get(deviceId), input.intentCategory),
);
if (authorizedCapableDevices.length === 1) return authorizedCapableDevices[0];
if (
input.preferredDeviceId &&
authorized.has(input.preferredDeviceId) &&
isControlDeviceCapable(deviceById.get(input.preferredDeviceId), input.intentCategory)
) {
return input.preferredDeviceId;
}
return authorizedCapableDevices[0] ?? input.preferredDeviceId ?? input.authorizedDeviceIds[0] ?? "mac-studio";
}
export const resolveMasterAgentControlTargetDeviceIdForTesting = resolveMasterAgentControlTargetDeviceId;
const GENERIC_COMPATIBLE_MODEL_OPTIONS = ["gpt-5.4-mini", "gpt-5.4", "gpt-5.1", "gpt-4.1"];
type QueuedMasterAgentReplyEnvelope = {
@@ -3709,7 +3765,14 @@ export async function replyToMasterAgentUserMessage(params: {
if (
controlIntent.intentCategory === "browser_control" || controlIntent.intentCategory === "desktop_control"
) {
const deviceId = runtime.account.nodeId || state.user.boundDeviceId || "mac-studio";
const deviceId = resolveMasterAgentControlTargetDeviceId({
replyProjectId,
intentCategory: controlIntent.intentCategory,
preferredDeviceId: runtime.account.nodeId || state.user.boundDeviceId || "mac-studio",
authorizedDeviceIds: authorizedScope.authorizedDeviceIds,
devices: state.devices,
projects: state.projects,
});
const taskType = controlIntent.intentCategory;
const task = await queueMasterAgentTask({
projectId: replyProjectId,
@@ -3803,6 +3866,14 @@ export async function replyToMasterAgentUserMessage(params: {
? "browser-automation-runtime"
: "computer-use-runtime";
const taskType = controlIntent.intentCategory;
const controlDeviceId = resolveMasterAgentControlTargetDeviceId({
replyProjectId,
intentCategory: controlIntent.intentCategory,
preferredDeviceId: deviceId,
authorizedDeviceIds: authorizedScope.authorizedDeviceIds,
devices: state.devices,
projects: state.projects,
});
const task = await queueMasterAgentTask({
projectId: replyProjectId,
taskType,
@@ -3811,7 +3882,7 @@ export async function replyToMasterAgentUserMessage(params: {
executionPrompt: masterExecutionPrompt,
requestedBy: params.requestedBy,
requestedByAccount: params.requestedByAccount,
deviceId,
deviceId: controlDeviceId,
accountId: selectedMasterAccount.accountId,
accountLabel: selectedMasterAccount.label || runtime.summary.roleLabel,
...masterTaskAuthorization(["master_agent.ask", "computer.control"]),
@@ -3820,7 +3891,7 @@ export async function replyToMasterAgentUserMessage(params: {
riskLevel: controlIntent.riskLevel,
confirmationPolicy: controlIntent.riskLevel === "high" ? "strong_confirm" : "light_confirm",
requiresUserConfirmation: false,
confirmationScopeKey: `${deviceId}:${replyProjectId}`,
confirmationScopeKey: `${controlDeviceId}:${replyProjectId}`,
externalReplyTarget: params.externalReplyTarget,
});