test: harden remote control stress flow
This commit is contained in:
100
local-agent/master-task-completion.mjs
Normal file
100
local-agent/master-task-completion.mjs
Normal file
@@ -0,0 +1,100 @@
|
||||
function trimToUndefined(value) {
|
||||
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
||||
}
|
||||
|
||||
function normalizeCompletionStatus(status) {
|
||||
if (status === "failed") return "failed";
|
||||
if (status === "needs_user_action") return "needs_user_action";
|
||||
return "completed";
|
||||
}
|
||||
|
||||
function normalizeStringArray(value) {
|
||||
return Array.isArray(value)
|
||||
? value.map((item) => String(item).trim()).filter(Boolean)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
export function buildRemoteExecutionCompletionPayload(task, payload) {
|
||||
return {
|
||||
taskId: task.taskId,
|
||||
status: normalizeCompletionStatus(payload.status),
|
||||
requestId: trimToUndefined(payload.requestId),
|
||||
replyBody: trimToUndefined(payload.replyBody),
|
||||
errorMessage: trimToUndefined(payload.errorMessage),
|
||||
kind: trimToUndefined(payload.kind),
|
||||
dialogId: trimToUndefined(payload.dialogId),
|
||||
appName: trimToUndefined(payload.appName),
|
||||
platform: trimToUndefined(payload.platform),
|
||||
risk: trimToUndefined(payload.risk),
|
||||
summary: trimToUndefined(payload.summary),
|
||||
recommendedAction: trimToUndefined(payload.recommendedAction),
|
||||
availableActions: normalizeStringArray(payload.availableActions),
|
||||
dispatchExecutionId: trimToUndefined(payload.dispatchExecutionId),
|
||||
targetProjectId: trimToUndefined(payload.targetProjectId),
|
||||
targetThreadId: trimToUndefined(payload.targetThreadId),
|
||||
targetUrl: trimToUndefined(payload.targetUrl),
|
||||
targetApp: trimToUndefined(payload.targetApp),
|
||||
rawThreadReply: trimToUndefined(payload.rawThreadReply),
|
||||
executionProgress:
|
||||
payload.executionProgress && typeof payload.executionProgress === "object"
|
||||
? payload.executionProgress
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
export function buildComputerUseCompletionPayload(task, result) {
|
||||
if (result?.status === "needs_user_action") {
|
||||
return buildRemoteExecutionCompletionPayload(task, {
|
||||
status: "needs_user_action",
|
||||
requestId: result.requestId,
|
||||
kind: result.kind,
|
||||
dialogId: result.dialogId,
|
||||
appName: result.appName,
|
||||
platform: result.platform,
|
||||
risk: result.risk,
|
||||
summary: result.summary,
|
||||
recommendedAction: result.recommendedAction,
|
||||
availableActions: result.availableActions,
|
||||
dispatchExecutionId: task.dispatchExecutionId,
|
||||
targetProjectId: task.targetProjectId,
|
||||
targetThreadId: task.targetThreadId,
|
||||
targetApp: result.targetApp ?? result.appName,
|
||||
});
|
||||
}
|
||||
|
||||
return buildRemoteExecutionCompletionPayload(task, {
|
||||
status: result?.status === "failed" ? "failed" : "completed",
|
||||
requestId: result?.requestId,
|
||||
replyBody: result?.replyBody,
|
||||
errorMessage: result?.errorMessage,
|
||||
dispatchExecutionId: task.dispatchExecutionId,
|
||||
targetProjectId: task.targetProjectId,
|
||||
targetThreadId: task.targetThreadId,
|
||||
targetApp: result?.targetApp,
|
||||
});
|
||||
}
|
||||
|
||||
export function buildMasterAgentTaskCompletionRequestBody(config, payload) {
|
||||
return {
|
||||
deviceId: config.deviceId,
|
||||
status: payload.status,
|
||||
replyBody: payload.replyBody,
|
||||
errorMessage: payload.errorMessage,
|
||||
requestId: payload.requestId,
|
||||
kind: payload.kind,
|
||||
dialogId: payload.dialogId,
|
||||
appName: payload.appName,
|
||||
platform: payload.platform,
|
||||
risk: payload.risk,
|
||||
summary: payload.summary,
|
||||
recommendedAction: payload.recommendedAction,
|
||||
availableActions: payload.availableActions,
|
||||
dispatchExecutionId: payload.dispatchExecutionId,
|
||||
targetProjectId: payload.targetProjectId,
|
||||
targetThreadId: payload.targetThreadId,
|
||||
targetUrl: payload.targetUrl,
|
||||
targetApp: payload.targetApp,
|
||||
rawThreadReply: payload.rawThreadReply,
|
||||
executionProgress: payload.executionProgress,
|
||||
};
|
||||
}
|
||||
@@ -38,6 +38,11 @@ import {
|
||||
resolveMasterAgentTaskTimeoutMs,
|
||||
runWithTaskTimeout,
|
||||
} from "./master-task-timeout.mjs";
|
||||
import {
|
||||
buildComputerUseCompletionPayload,
|
||||
buildMasterAgentTaskCompletionRequestBody,
|
||||
buildRemoteExecutionCompletionPayload,
|
||||
} from "./master-task-completion.mjs";
|
||||
import { createSerializedRunner } from "./serialized-runner.mjs";
|
||||
|
||||
async function loadConfig(configPath) {
|
||||
@@ -346,20 +351,7 @@ async function completeMasterAgentTask(config, runtime, payload) {
|
||||
"Content-Type": "application/json",
|
||||
...deviceTokenHeaders(config, runtime),
|
||||
},
|
||||
body: JSON.stringify({
|
||||
deviceId: config.deviceId,
|
||||
status: payload.status,
|
||||
replyBody: payload.replyBody,
|
||||
errorMessage: payload.errorMessage,
|
||||
requestId: payload.requestId,
|
||||
dispatchExecutionId: payload.dispatchExecutionId,
|
||||
targetProjectId: payload.targetProjectId,
|
||||
targetThreadId: payload.targetThreadId,
|
||||
targetUrl: payload.targetUrl,
|
||||
targetApp: payload.targetApp,
|
||||
rawThreadReply: payload.rawThreadReply,
|
||||
executionProgress: payload.executionProgress,
|
||||
}),
|
||||
body: JSON.stringify(buildMasterAgentTaskCompletionRequestBody(config, payload)),
|
||||
},
|
||||
);
|
||||
|
||||
@@ -450,32 +442,6 @@ function parseDispatchExecutionCompletion(rawOutput) {
|
||||
};
|
||||
}
|
||||
|
||||
function buildRemoteExecutionCompletionPayload(task, payload) {
|
||||
return {
|
||||
taskId: task.taskId,
|
||||
status: payload.status === "failed" ? "failed" : "completed",
|
||||
requestId: payload.requestId,
|
||||
replyBody: typeof payload.replyBody === "string" ? payload.replyBody.trim() || undefined : undefined,
|
||||
errorMessage: typeof payload.errorMessage === "string" ? payload.errorMessage.trim() || undefined : undefined,
|
||||
dispatchExecutionId:
|
||||
typeof payload.dispatchExecutionId === "string" ? payload.dispatchExecutionId.trim() || undefined : undefined,
|
||||
targetProjectId:
|
||||
typeof payload.targetProjectId === "string" ? payload.targetProjectId.trim() || undefined : undefined,
|
||||
targetThreadId:
|
||||
typeof payload.targetThreadId === "string" ? payload.targetThreadId.trim() || undefined : undefined,
|
||||
targetUrl:
|
||||
typeof payload.targetUrl === "string" ? payload.targetUrl.trim() || undefined : undefined,
|
||||
targetApp:
|
||||
typeof payload.targetApp === "string" ? payload.targetApp.trim() || undefined : undefined,
|
||||
rawThreadReply:
|
||||
typeof payload.rawThreadReply === "string" ? payload.rawThreadReply.trim() || undefined : undefined,
|
||||
executionProgress:
|
||||
payload.executionProgress && typeof payload.executionProgress === "object"
|
||||
? payload.executionProgress
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
function runShortCommand(command, args, options = {}) {
|
||||
return new Promise((resolve) => {
|
||||
const child = spawn(command, args, {
|
||||
@@ -592,6 +558,11 @@ async function runMasterAgentTask(config, runtime, task) {
|
||||
if (computerUseResult.status === "failed") {
|
||||
throw new Error(computerUseResult.errorMessage || "COMPUTER_USE_FAILED");
|
||||
}
|
||||
if (computerUseResult.status === "needs_user_action") {
|
||||
return {
|
||||
waitingUserActionCompletion: buildComputerUseCompletionPayload(task, computerUseResult),
|
||||
};
|
||||
}
|
||||
return {
|
||||
replyBody: computerUseResult.replyBody,
|
||||
dispatchExecutionCompletion: {
|
||||
@@ -707,6 +678,31 @@ async function runMasterAgentTask(config, runtime, task) {
|
||||
: null,
|
||||
};
|
||||
})();
|
||||
if (executionResult.waitingUserActionCompletion) {
|
||||
const completion = await completeMasterAgentTask(
|
||||
config,
|
||||
runtime,
|
||||
executionResult.waitingUserActionCompletion,
|
||||
);
|
||||
if (!completion.ok) {
|
||||
throw new Error(`DIALOG_GUARD_COMPLETION_FAILED:${completion.status}:${completion.body}`);
|
||||
}
|
||||
runtime.activeMasterTask = {
|
||||
taskId: task.taskId,
|
||||
status: "needs_user_action",
|
||||
completedAt: new Date().toISOString(),
|
||||
detail: completion.body,
|
||||
};
|
||||
await postAppLog(config, runtime, {
|
||||
projectId: "master-agent",
|
||||
level: "info",
|
||||
category: "local_agent.desktop_dialog_guard_waiting_user_action",
|
||||
message: `Master Codex Node 等待用户处理桌面弹窗:${task.taskId}`,
|
||||
detail: executionResult.waitingUserActionCompletion.summary,
|
||||
mirrorToMaster: false,
|
||||
});
|
||||
return;
|
||||
}
|
||||
const { replyBody, dispatchExecutionCompletion, executionProgress } = executionResult;
|
||||
|
||||
const completion = await completeMasterAgentTask(
|
||||
|
||||
Reference in New Issue
Block a user