feat: harden enterprise control plane

This commit is contained in:
AI Bot
2026-05-17 02:20:08 +08:00
parent 67511c31f4
commit e1aed590f8
112 changed files with 10977 additions and 2004 deletions

View File

@@ -434,6 +434,179 @@ test("POST /api/v1/projects/[projectId]/messages lets @主Agent create browser c
assert.equal(task?.requiresUserConfirmation, undefined);
});
test("POST /api/v1/projects/[projectId]/messages routes direct GUI browser commands to browser control", async () => {
await setup();
const singleProject = await ensureSingleThreadProject();
assert.ok(singleProject, "expected a seeded single-thread project");
await resetThreadExecutionState(singleProject.id);
await setProjectTakeover(singleProject.id, false);
const state = await readState();
const targetDevice = state.devices.find((device) => device.id === singleProject.deviceIds[0]);
assert.ok(targetDevice, "expected a seeded target device");
targetDevice.preferredExecutionMode = "gui";
targetDevice.capabilities = {
...(targetDevice.capabilities ?? {}),
browserAutomation: {
...(targetDevice.capabilities?.browserAutomation ?? {}),
connected: true,
},
};
await writeState(state);
const response = await postMessageRoute(
await createAuthedRequest(
`http://127.0.0.1:3000/api/v1/projects/${singleProject.id}/messages`,
"POST",
{ body: "打开浏览器,用浏览器打开 YouTube找一个 MV 播放" },
),
{ params: Promise.resolve({ projectId: singleProject.id }) },
);
assert.equal(response.status, 200);
const payload = (await response.json()) as {
ok: boolean;
replyPresenter?: "thread" | "master";
task?: { taskId: string; taskType: string; status: string } | null;
executionMode?: string;
riskLevel?: string;
};
assert.equal(payload.ok, true);
assert.equal(payload.replyPresenter, "master");
assert.equal(payload.task?.taskType, "browser_control");
assert.equal(payload.task?.status, "queued");
assert.equal(payload.executionMode, "browser");
assert.equal(payload.riskLevel, "medium");
const nextState = await readState();
const task = nextState.masterAgentTasks.find((item) => item.taskId === payload.task?.taskId);
assert.ok(task, "expected a queued browser_control task");
assert.equal(task?.projectId, singleProject.id);
assert.equal(task?.deviceId, singleProject.deviceIds[0]);
assert.equal(task?.taskType, "browser_control");
assert.equal(task?.intentCategory, "browser_control");
assert.equal(task?.runtimeKind, "browser-automation-runtime");
const staleConversationTask = nextState.masterAgentTasks.find(
(item) =>
item.projectId === singleProject.id &&
item.taskType === "conversation_reply" &&
item.requestText === "打开浏览器,用浏览器打开 YouTube找一个 MV 播放",
);
assert.equal(staleConversationTask, undefined, "direct GUI control should not queue an unclaimable thread task");
});
test("POST /api/v1/projects/[projectId]/messages creates native remote progress for direct GUI control", async () => {
await setup();
const singleProject = await ensureSingleThreadProject();
assert.ok(singleProject, "expected a seeded single-thread project");
await resetThreadExecutionState(singleProject.id);
await setProjectTakeover(singleProject.id, false);
const state = await readState();
const targetDevice = state.devices.find((device) => device.id === singleProject.deviceIds[0]);
assert.ok(targetDevice, "expected a seeded target device");
targetDevice.preferredExecutionMode = "gui";
targetDevice.capabilities = {
...(targetDevice.capabilities ?? {}),
browserAutomation: {
...(targetDevice.capabilities?.browserAutomation ?? {}),
connected: true,
},
};
await writeState(state);
const response = await postMessageRoute(
await createAuthedRequest(
`http://127.0.0.1:3000/api/v1/projects/${singleProject.id}/messages`,
"POST",
{ body: "打开浏览器,用浏览器打开 YouTube找一个 MV 播放" },
),
{ params: Promise.resolve({ projectId: singleProject.id }) },
);
assert.equal(response.status, 200);
const payload = (await response.json()) as { task?: { taskId: string } | null };
const nextState = await readState();
const progressMessage = nextState.projects
.find((project) => project.id === singleProject.id)
?.messages.find((message) => message.executionProgress?.taskId === payload.task?.taskId);
const progress = progressMessage?.executionProgress as
| (NonNullable<typeof progressMessage>["executionProgress"] & {
controlMode?: string;
runtimeKind?: string;
controlPlatform?: string;
computerUseProvider?: string;
})
| undefined;
assert.ok(progressMessage, "expected native control task to render its own progress card");
assert.equal(progress?.title, "远程控制进度");
assert.equal(progress?.controlMode, "native_remote_control");
assert.equal(progress?.runtimeKind, "browser-automation-runtime");
assert.equal(progress?.controlPlatform, "macos");
assert.equal(progress?.computerUseProvider, "openai-computer-use");
assert.ok(progress?.steps.some((step) => step.text.includes("连接目标电脑")));
assert.ok(!progress?.steps.some((step) => /Codex|Git|线程记录/.test(step.text)));
assert.equal(progress?.branch, undefined);
assert.equal(progress?.agents, undefined);
});
test("POST /api/v1/master-agent/tasks/[taskId]/complete appends a visible master summary after control tasks", async () => {
await setup();
const singleProject = await ensureSingleThreadProject();
assert.ok(singleProject, "expected a seeded single-thread project");
await resetThreadExecutionState(singleProject.id);
const task = await queueMasterAgentTask({
projectId: singleProject.id,
taskType: "browser_control",
requestMessageId: "msg-browser-summary",
requestText: "打开浏览器,用浏览器打开 YouTube找一个周杰伦 MV 播放",
executionPrompt: "打开浏览器并搜索周杰伦 MV",
requestedBy: "Boss 超级管理员",
requestedByAccount: TEST_ACCOUNT,
deviceId: "mac-studio",
accountLabel: "主 GPT",
intentCategory: "browser_control",
runtimeKind: "browser-automation-runtime",
riskLevel: "medium",
});
const response = await completeMasterTaskRoute(
await createAuthedRequest(
`http://127.0.0.1:3000/api/v1/master-agent/tasks/${task.taskId}/complete`,
"POST",
{
deviceId: task.deviceId,
status: "completed",
replyBody: "浏览器控制已完成:打开浏览器,用浏览器打开 YouTube找一个周杰伦 MV 播放",
targetUrl: "https://www.youtube.com/results?search_query=%E5%91%A8%E6%9D%B0%E4%BC%A6%20MV",
},
),
{ params: Promise.resolve({ taskId: task.taskId }) },
);
assert.equal(response.status, 200);
const nextState = await readState();
const updatedProject = nextState.projects.find((project) => project.id === singleProject.id);
const controlSummary = updatedProject?.messages.find(
(message) => message.kind === "control_summary" && message.executionProgress?.taskId !== task.taskId,
);
const visibleSummary = updatedProject?.messages.at(-1);
assert.equal(controlSummary?.body, "浏览器控制已完成:打开浏览器,用浏览器打开 YouTube找一个周杰伦 MV 播放");
assert.equal(visibleSummary?.sender, "master");
assert.equal(visibleSummary?.senderLabel, "主 Agent · 主 GPT");
assert.equal(visibleSummary?.kind, "text");
assert.match(visibleSummary?.body ?? "", /任务小结:浏览器控制已完成/);
assert.match(visibleSummary?.body ?? "", /周杰伦 MV/);
assert.ok(visibleSummary?.body.includes("youtube.com/results"));
assert.ok(!visibleSummary?.body.includes("\n"));
assert.equal(updatedProject?.preview, visibleSummary?.body);
assert.equal(updatedProject?.unreadCount, 1);
});
test("POST /api/v1/projects/[projectId]/messages lets @主Agent trigger project summary sync that writes back to top entries", async () => {
await setup();
const singleProject = await ensureSingleThreadProject();