feat: queue codex remote control actions
This commit is contained in:
@@ -28,11 +28,12 @@ test("independent Boss admin web app uses the backoffice BFF with cookie session
|
||||
assert.match(apiSource, /\/api\/v1\/admin\/risks\/actions/);
|
||||
assert.match(apiSource, /\/api\/v1\/admin\/skills\/requests/);
|
||||
assert.match(apiSource, /\/api\/v1\/admin\/backups/);
|
||||
assert.match(apiSource, /\/api\/v1\/devices\/\$\{encodeURIComponent\(deviceId\)\}\/codex-remote-control/);
|
||||
assert.match(apiSource, /credentials:\s*["']include["']/);
|
||||
assert.match(apiSource, /menuTree/);
|
||||
assert.match(apiSource, /tenants/);
|
||||
assert.match(apiSource, /resourceGroups/);
|
||||
for (const fn of ["postAdminAccess", "postRiskAction", "postSkillLifecycleRequest", "fetchAdminBackups", "createAdminBackup", "restoreAdminBackup"]) {
|
||||
for (const fn of ["postAdminAccess", "postRiskAction", "postSkillLifecycleRequest", "postDeviceCodexRemoteControl", "fetchAdminBackups", "createAdminBackup", "restoreAdminBackup"]) {
|
||||
assert.match(apiSource, new RegExp(`function\\s+${fn}|const\\s+${fn}`));
|
||||
}
|
||||
for (const action of ["create_snapshot", "restore_snapshot"]) {
|
||||
@@ -103,6 +104,8 @@ test("independent Boss admin web app exposes management actions instead of read
|
||||
"关闭风险",
|
||||
"创建工单",
|
||||
"创建 Skill 请求",
|
||||
"启动远控",
|
||||
"停止远控",
|
||||
"创建状态快照",
|
||||
"恢复到此快照",
|
||||
"快照清单",
|
||||
@@ -128,6 +131,7 @@ test("independent Boss admin web app exposes management actions instead of read
|
||||
assert.match(appSource, /loadBackupSnapshots/);
|
||||
assert.match(appSource, /createAdminBackup/);
|
||||
assert.match(appSource, /restoreAdminBackup/);
|
||||
assert.match(appSource, /handleCodexRemoteControl/);
|
||||
});
|
||||
|
||||
test("root Next project isolates the independent Vue admin workspace", async () => {
|
||||
|
||||
178
tests/device-codex-remote-control-route.test.ts
Normal file
178
tests/device-codex-remote-control-route.test.ts
Normal file
@@ -0,0 +1,178 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { mkdtemp, rm } from "node:fs/promises";
|
||||
import { NextRequest } from "next/server";
|
||||
|
||||
let runtimeRoot = "";
|
||||
let data: typeof import("../src/lib/boss-data");
|
||||
let authCookie = "";
|
||||
let postRemoteControl: (typeof import("../src/app/api/v1/devices/[deviceId]/codex-remote-control/route"))["POST"];
|
||||
let baseState: Awaited<ReturnType<typeof import("../src/lib/boss-data")["readState"]>>;
|
||||
|
||||
async function setup() {
|
||||
if (runtimeRoot) return;
|
||||
runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-device-codex-remote-control-"));
|
||||
process.env.BOSS_RUNTIME_ROOT = runtimeRoot;
|
||||
process.env.BOSS_STATE_FILE = path.join(runtimeRoot, "boss-state.json");
|
||||
|
||||
const [dataModule, authModule, routeModule] = await Promise.all([
|
||||
import("../src/lib/boss-data.ts"),
|
||||
import("../src/lib/boss-auth.ts"),
|
||||
import("../src/app/api/v1/devices/[deviceId]/codex-remote-control/route.ts"),
|
||||
]);
|
||||
data = dataModule;
|
||||
authCookie = authModule.AUTH_SESSION_COOKIE;
|
||||
postRemoteControl = routeModule.POST;
|
||||
baseState = structuredClone(await data.readState());
|
||||
}
|
||||
|
||||
test.beforeEach(async () => {
|
||||
await setup();
|
||||
await rm(runtimeRoot, { recursive: true, force: true });
|
||||
const state = structuredClone(baseState);
|
||||
const now = "2026-06-04T10:00:00+08:00";
|
||||
state.authAccounts = [
|
||||
{
|
||||
id: "account-owner",
|
||||
account: "owner@boss.test",
|
||||
passwordHash: "secret",
|
||||
displayName: "企业老板",
|
||||
role: "highest_admin",
|
||||
primaryDeviceId: "mac-1",
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
},
|
||||
{
|
||||
id: "account-operator",
|
||||
account: "operator@boss.test",
|
||||
passwordHash: "secret",
|
||||
displayName: "设备操作者",
|
||||
role: "member",
|
||||
primaryDeviceId: "mac-1",
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
},
|
||||
];
|
||||
state.authSessions = [];
|
||||
state.accountDeviceGrants = [];
|
||||
state.permissionAuditLogs = [];
|
||||
state.devices = [
|
||||
{
|
||||
id: "mac-1",
|
||||
name: "客户 Mac",
|
||||
avatar: "M",
|
||||
account: "owner@boss.test",
|
||||
source: "production",
|
||||
status: "online",
|
||||
projects: ["master-agent"],
|
||||
quota5h: 0,
|
||||
quota7d: 0,
|
||||
lastSeenAt: now,
|
||||
preferredExecutionMode: "cli",
|
||||
capabilities: {
|
||||
gui: { connected: true, lastSeenAt: now },
|
||||
cli: { connected: true, lastSeenAt: now },
|
||||
browserAutomation: { connected: true, lastSeenAt: now },
|
||||
computerUse: { connected: true, lastSeenAt: now },
|
||||
codexAppServer: {
|
||||
connected: true,
|
||||
lastSeenAt: now,
|
||||
metadata: {
|
||||
remoteControlSummary: {
|
||||
supported: true,
|
||||
startCommandLabel: "codex remote-control start --json",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
await data.writeState(state);
|
||||
});
|
||||
|
||||
test.after(async () => {
|
||||
if (runtimeRoot) {
|
||||
await rm(runtimeRoot, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
async function sessionCookie(account: string, role: "member" | "admin" | "highest_admin") {
|
||||
const session = await data.createAuthSession({
|
||||
account,
|
||||
role,
|
||||
displayName: account,
|
||||
loginMethod: "password",
|
||||
});
|
||||
return `${authCookie}=${session.sessionToken}`;
|
||||
}
|
||||
|
||||
async function postAs(
|
||||
account: string,
|
||||
role: "member" | "admin" | "highest_admin",
|
||||
body: Record<string, unknown>,
|
||||
) {
|
||||
return postRemoteControl(
|
||||
new NextRequest("http://127.0.0.1:3000/api/v1/devices/mac-1/codex-remote-control", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
cookie: await sessionCookie(account, role),
|
||||
"x-request-id": "req-remote-control",
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
}),
|
||||
{ params: Promise.resolve({ deviceId: "mac-1" }) },
|
||||
);
|
||||
}
|
||||
|
||||
test("codex remote control action requires login, explicit confirmation, and computer control permission", async () => {
|
||||
await setup();
|
||||
|
||||
const anonymous = await postRemoteControl(
|
||||
new NextRequest("http://127.0.0.1:3000/api/v1/devices/mac-1/codex-remote-control", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ action: "start", confirmed: true }),
|
||||
}),
|
||||
{ params: Promise.resolve({ deviceId: "mac-1" }) },
|
||||
);
|
||||
assert.equal(anonymous.status, 401);
|
||||
|
||||
const missingConfirmation = await postAs("owner@boss.test", "highest_admin", { action: "start" });
|
||||
assert.equal(missingConfirmation.status, 400);
|
||||
|
||||
const forbidden = await postAs("operator@boss.test", "member", { action: "start", confirmed: true });
|
||||
assert.equal(forbidden.status, 403);
|
||||
|
||||
const state = await data.readState();
|
||||
assert.equal(state.masterAgentTasks.length, 0);
|
||||
assert.equal(state.permissionAuditLogs.at(0)?.action, "task.denied");
|
||||
});
|
||||
|
||||
test("highest admin can queue a device maintenance task for codex remote control start", async () => {
|
||||
const response = await postAs("owner@boss.test", "highest_admin", {
|
||||
action: "start",
|
||||
confirmed: true,
|
||||
reason: "开启 Codex Remote Control 供 Boss App 真时控制。",
|
||||
});
|
||||
|
||||
assert.equal(response.status, 200);
|
||||
const payload = await response.json();
|
||||
assert.equal(payload.ok, true);
|
||||
assert.equal(payload.task.taskType, "device_maintenance");
|
||||
assert.equal(payload.task.maintenanceKind, "codex_remote_control");
|
||||
assert.equal(payload.task.codexRemoteControlAction, "start");
|
||||
assert.deepEqual(payload.task.requiredPermissions, ["computer.control"]);
|
||||
|
||||
const claimed = await data.claimNextMasterAgentTask("mac-1");
|
||||
assert.equal(claimed?.taskId, payload.task.taskId);
|
||||
assert.equal(claimed?.maintenanceKind, "codex_remote_control");
|
||||
assert.equal(claimed?.codexRemoteControlAction, "start");
|
||||
|
||||
const state = await data.readState();
|
||||
const audit = state.permissionAuditLogs.find((item) => item.action === "task.authorized");
|
||||
assert.equal(audit?.actorAccount, "owner@boss.test");
|
||||
assert.equal(audit?.deviceId, "mac-1");
|
||||
assert.equal(audit?.detail, "codex_remote_control:start");
|
||||
});
|
||||
Reference in New Issue
Block a user