feat: queue codex remote control actions
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
import { NextRequest } from "next/server";
|
||||
import { jsonNoStore } from "@/lib/api-response";
|
||||
import { buildRequestAuditMeta } from "@/lib/boss-audit";
|
||||
import { requireRequestSession } from "@/lib/boss-auth";
|
||||
import {
|
||||
appendPermissionAuditLog,
|
||||
queueCodexRemoteControlTask,
|
||||
readState,
|
||||
type CodexRemoteControlAction,
|
||||
} from "@/lib/boss-data";
|
||||
import { canAccessDevice } from "@/lib/boss-permissions";
|
||||
|
||||
function normalizeAction(value: unknown): CodexRemoteControlAction | null {
|
||||
return value === "start" || value === "stop" ? value : null;
|
||||
}
|
||||
|
||||
async function recordDenied(input: {
|
||||
actorAccount: string;
|
||||
deviceId: string;
|
||||
reason: string;
|
||||
request: NextRequest;
|
||||
}) {
|
||||
const auditMeta = buildRequestAuditMeta(input.request);
|
||||
await appendPermissionAuditLog({
|
||||
actorAccount: input.actorAccount,
|
||||
action: "task.denied",
|
||||
deviceId: input.deviceId,
|
||||
permissions: ["computer.control"],
|
||||
detail: input.reason,
|
||||
...auditMeta,
|
||||
});
|
||||
}
|
||||
|
||||
export async function POST(
|
||||
request: NextRequest,
|
||||
context: { params: Promise<{ deviceId: string }> },
|
||||
) {
|
||||
const session = await requireRequestSession(request);
|
||||
if (!session) {
|
||||
return jsonNoStore({ ok: false, message: "UNAUTHORIZED" }, { status: 401 });
|
||||
}
|
||||
|
||||
const { deviceId } = await context.params;
|
||||
const body = (await request.json().catch(() => ({}))) as {
|
||||
action?: unknown;
|
||||
confirmed?: unknown;
|
||||
reason?: unknown;
|
||||
};
|
||||
const action = normalizeAction(body.action);
|
||||
if (!action) {
|
||||
return jsonNoStore({ ok: false, message: "CODEX_REMOTE_CONTROL_ACTION_INVALID" }, { status: 400 });
|
||||
}
|
||||
if (body.confirmed !== true) {
|
||||
return jsonNoStore({ ok: false, message: "CODEX_REMOTE_CONTROL_CONFIRMATION_REQUIRED" }, { status: 400 });
|
||||
}
|
||||
|
||||
const state = await readState();
|
||||
const device = state.devices.find((item) => item.id === deviceId);
|
||||
if (!device) {
|
||||
return jsonNoStore({ ok: false, message: "DEVICE_NOT_FOUND" }, { status: 404 });
|
||||
}
|
||||
if (device.status !== "online") {
|
||||
await recordDenied({
|
||||
actorAccount: session.account,
|
||||
deviceId,
|
||||
reason: `codex_remote_control:${action}:device_offline`,
|
||||
request,
|
||||
});
|
||||
return jsonNoStore({ ok: false, message: "DEVICE_OFFLINE" }, { status: 409 });
|
||||
}
|
||||
if (!canAccessDevice(state, session, deviceId, "computer.control")) {
|
||||
await recordDenied({
|
||||
actorAccount: session.account,
|
||||
deviceId,
|
||||
reason: `codex_remote_control:${action}:forbidden`,
|
||||
request,
|
||||
});
|
||||
return jsonNoStore({ ok: false, message: "FORBIDDEN" }, { status: 403 });
|
||||
}
|
||||
|
||||
const task = await queueCodexRemoteControlTask({
|
||||
deviceId,
|
||||
action,
|
||||
requestedBy: session.displayName || session.account,
|
||||
requestedByAccount: session.account,
|
||||
reason: typeof body.reason === "string" ? body.reason : undefined,
|
||||
auditMeta: buildRequestAuditMeta(request),
|
||||
});
|
||||
|
||||
return jsonNoStore({
|
||||
ok: true,
|
||||
task,
|
||||
message: action === "start" ? "CODEX_REMOTE_CONTROL_START_QUEUED" : "CODEX_REMOTE_CONTROL_STOP_QUEUED",
|
||||
});
|
||||
}
|
||||
@@ -477,8 +477,11 @@ export type MasterAgentTaskType =
|
||||
| "group_dispatch_plan"
|
||||
| "dispatch_execution"
|
||||
| "device_import_resolution"
|
||||
| "device_maintenance"
|
||||
| "browser_control"
|
||||
| "desktop_control";
|
||||
export type DeviceMaintenanceKind = "codex_remote_control";
|
||||
export type CodexRemoteControlAction = "start" | "stop";
|
||||
export type ComputerControlIntentCategory =
|
||||
| "discussion_only"
|
||||
| "project_development"
|
||||
@@ -1353,6 +1356,8 @@ export interface MasterAgentTask {
|
||||
deviceImportDraftId?: string;
|
||||
deviceImportCandidateId?: string;
|
||||
deviceImportCandidateFolderName?: string;
|
||||
maintenanceKind?: DeviceMaintenanceKind;
|
||||
codexRemoteControlAction?: CodexRemoteControlAction;
|
||||
projectUnderstandingTargetProjectId?: string;
|
||||
projectUnderstandingReason?: "heartbeat_activity" | "thread_reply";
|
||||
projectUnderstandingReplyProjectId?: string;
|
||||
@@ -4769,6 +4774,12 @@ export function migrateBossState(raw: Partial<BossState> | undefined): BossState
|
||||
deviceImportDraftId: task.deviceImportDraftId,
|
||||
deviceImportCandidateId: task.deviceImportCandidateId,
|
||||
deviceImportCandidateFolderName: task.deviceImportCandidateFolderName,
|
||||
maintenanceKind:
|
||||
task.maintenanceKind === "codex_remote_control" ? task.maintenanceKind : undefined,
|
||||
codexRemoteControlAction:
|
||||
task.codexRemoteControlAction === "start" || task.codexRemoteControlAction === "stop"
|
||||
? task.codexRemoteControlAction
|
||||
: undefined,
|
||||
projectUnderstandingTargetProjectId: task.projectUnderstandingTargetProjectId,
|
||||
projectUnderstandingReason:
|
||||
task.projectUnderstandingReason === "heartbeat_activity" || task.projectUnderstandingReason === "thread_reply"
|
||||
@@ -6020,6 +6031,14 @@ function normalizeExecutionProgressStreamEvents(
|
||||
}
|
||||
|
||||
function defaultExecutionProgressStepTexts(task: Pick<MasterAgentTask, "taskType" | "relayViaMasterAgent">) {
|
||||
if (task.taskType === "device_maintenance") {
|
||||
return [
|
||||
"接收设备维护指令",
|
||||
"确认设备权限和绑定状态",
|
||||
"执行本机维护动作",
|
||||
"回写维护结果",
|
||||
];
|
||||
}
|
||||
if (task.taskType === "browser_control") {
|
||||
return [
|
||||
"接收远程控制指令",
|
||||
@@ -6098,6 +6117,9 @@ function normalizeExecutionProgressSteps(
|
||||
}
|
||||
|
||||
function resolveTaskExecutionProgressProjectId(task: Pick<MasterAgentTask, "projectId" | "taskType" | "targetProjectId">) {
|
||||
if (task.taskType === "device_maintenance") {
|
||||
return task.projectId?.trim() || "";
|
||||
}
|
||||
if (task.taskType === "browser_control" || task.taskType === "desktop_control") {
|
||||
return task.projectId?.trim() || "";
|
||||
}
|
||||
@@ -6111,6 +6133,9 @@ function resolveTaskExecutionProgressProjectId(task: Pick<MasterAgentTask, "proj
|
||||
}
|
||||
|
||||
function shouldShowTaskExecutionProgress(task: Pick<MasterAgentTask, "projectId" | "taskType" | "targetProjectId" | "targetThreadId">) {
|
||||
if (task.taskType === "device_maintenance") {
|
||||
return Boolean(task.projectId?.trim());
|
||||
}
|
||||
if (task.taskType === "browser_control" || task.taskType === "desktop_control") {
|
||||
return Boolean(task.projectId?.trim());
|
||||
}
|
||||
@@ -6135,7 +6160,11 @@ function buildExecutionProgressSnapshot(
|
||||
}
|
||||
const normalizedStatus = normalizeExecutionProgressStatus(status);
|
||||
const steps = normalizeExecutionProgressSteps(task, normalizedStatus, input?.steps);
|
||||
const nativeRemoteControl = task.taskType === "browser_control" || task.taskType === "desktop_control";
|
||||
const nativeRemoteControl =
|
||||
task.taskType === "browser_control" ||
|
||||
task.taskType === "desktop_control" ||
|
||||
task.taskType === "device_maintenance";
|
||||
const maintenanceControl = task.taskType === "device_maintenance";
|
||||
return {
|
||||
taskId: task.taskId,
|
||||
projectId,
|
||||
@@ -6145,7 +6174,7 @@ function buildExecutionProgressSnapshot(
|
||||
runtimeKind: task.runtimeKind,
|
||||
controlPlatform: task.controlPlatform,
|
||||
computerUseProvider: task.computerUseProvider,
|
||||
title: nativeRemoteControl ? "远程控制进度" : "进度",
|
||||
title: maintenanceControl ? "设备维护进度" : nativeRemoteControl ? "远程控制进度" : "进度",
|
||||
status: normalizedStatus,
|
||||
steps,
|
||||
branch: nativeRemoteControl ? undefined : normalizeExecutionProgressBranch(input?.branch),
|
||||
@@ -8882,6 +8911,8 @@ export async function queueMasterAgentTask(payload: {
|
||||
orchestrationBackendLabel?: string;
|
||||
deviceImportCandidateId?: string;
|
||||
deviceImportCandidateFolderName?: string;
|
||||
maintenanceKind?: DeviceMaintenanceKind;
|
||||
codexRemoteControlAction?: CodexRemoteControlAction;
|
||||
projectUnderstandingTargetProjectId?: string;
|
||||
projectUnderstandingReason?: "heartbeat_activity" | "thread_reply";
|
||||
projectUnderstandingReplyProjectId?: string;
|
||||
@@ -8956,6 +8987,12 @@ export async function queueMasterAgentTask(payload: {
|
||||
orchestrationBackendLabel: payload.orchestrationBackendLabel,
|
||||
deviceImportCandidateId: payload.deviceImportCandidateId,
|
||||
deviceImportCandidateFolderName: payload.deviceImportCandidateFolderName,
|
||||
maintenanceKind:
|
||||
payload.maintenanceKind === "codex_remote_control" ? payload.maintenanceKind : undefined,
|
||||
codexRemoteControlAction:
|
||||
payload.codexRemoteControlAction === "start" || payload.codexRemoteControlAction === "stop"
|
||||
? payload.codexRemoteControlAction
|
||||
: undefined,
|
||||
projectUnderstandingTargetProjectId: payload.projectUnderstandingTargetProjectId,
|
||||
projectUnderstandingReason: payload.projectUnderstandingReason,
|
||||
projectUnderstandingReplyProjectId: payload.projectUnderstandingReplyProjectId,
|
||||
@@ -9027,6 +9064,55 @@ export async function queueMasterAgentTask(payload: {
|
||||
return task;
|
||||
}
|
||||
|
||||
export async function queueCodexRemoteControlTask(input: {
|
||||
deviceId: string;
|
||||
action: CodexRemoteControlAction;
|
||||
requestedBy: string;
|
||||
requestedByAccount: string;
|
||||
reason?: string;
|
||||
auditMeta?: PermissionAuditMeta;
|
||||
}) {
|
||||
if (input.action !== "start" && input.action !== "stop") {
|
||||
throw new Error("CODEX_REMOTE_CONTROL_ACTION_INVALID");
|
||||
}
|
||||
const actionLabel = input.action === "start" ? "启动" : "停止";
|
||||
const reason = trimToDefined(input.reason) ?? `${actionLabel} Codex Remote Control。`;
|
||||
const task = await queueMasterAgentTask({
|
||||
projectId: "master-agent",
|
||||
taskType: "device_maintenance",
|
||||
requestMessageId: randomToken("msg-maintenance"),
|
||||
requestText: reason,
|
||||
executionPrompt: `设备维护:${actionLabel} Codex Remote Control。`,
|
||||
requestedBy: input.requestedBy,
|
||||
requestedByAccount: input.requestedByAccount,
|
||||
authorizedDeviceIds: [input.deviceId],
|
||||
requiredPermissions: ["computer.control"],
|
||||
deviceId: input.deviceId,
|
||||
maintenanceKind: "codex_remote_control",
|
||||
codexRemoteControlAction: input.action,
|
||||
runtimeKind: "codex-thread-runtime",
|
||||
confirmationPolicy: "strong_confirm",
|
||||
requiresUserConfirmation: true,
|
||||
confirmationScopeKey: `device:${input.deviceId}:codex_remote_control`,
|
||||
});
|
||||
await appendPermissionAuditLog({
|
||||
actorAccount: input.requestedByAccount,
|
||||
action: "task.authorized",
|
||||
deviceId: input.deviceId,
|
||||
permissions: ["computer.control"],
|
||||
detail: `codex_remote_control:${input.action}`,
|
||||
ipAddress: input.auditMeta?.ipAddress,
|
||||
userAgent: input.auditMeta?.userAgent,
|
||||
requestId: input.auditMeta?.requestId,
|
||||
afterJson: {
|
||||
taskId: task.taskId,
|
||||
maintenanceKind: "codex_remote_control",
|
||||
codexRemoteControlAction: input.action,
|
||||
},
|
||||
});
|
||||
return task;
|
||||
}
|
||||
|
||||
export async function createDispatchPlan(input: {
|
||||
groupProjectId: string;
|
||||
requestMessageId: string;
|
||||
|
||||
Reference in New Issue
Block a user