refactor: add remote runtime adapter

This commit is contained in:
kris
2026-04-03 00:24:11 +08:00
parent 8a62e72fd5
commit 70e8a13368
5 changed files with 144 additions and 25 deletions

View File

@@ -336,6 +336,24 @@ 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,
rawThreadReply:
typeof payload.rawThreadReply === "string" ? payload.rawThreadReply.trim() || undefined : undefined,
};
}
async function runMasterAgentTask(config, runtime, task) {
const outputFile = join(os.tmpdir(), `${task.taskId}.reply.txt`);
const stderrChunks = [];
@@ -372,15 +390,18 @@ async function runMasterAgentTask(config, runtime, task) {
task.taskType === "dispatch_execution"
? parseDispatchExecutionCompletion(replyBody)
: null;
const completion = await completeMasterAgentTask(config, runtime, {
taskId: task.taskId,
status: "completed",
replyBody: dispatchExecutionCompletion?.replyBody ?? replyBody,
dispatchExecutionId: task.dispatchExecutionId,
targetProjectId: task.targetProjectId,
targetThreadId: task.targetThreadId,
rawThreadReply: dispatchExecutionCompletion?.rawThreadReply,
});
const completion = await completeMasterAgentTask(
config,
runtime,
buildRemoteExecutionCompletionPayload(task, {
status: "completed",
replyBody: dispatchExecutionCompletion?.replyBody ?? replyBody,
dispatchExecutionId: task.dispatchExecutionId,
targetProjectId: task.targetProjectId,
targetThreadId: task.targetThreadId,
rawThreadReply: dispatchExecutionCompletion?.rawThreadReply,
}),
);
runtime.activeMasterTask = {
taskId: task.taskId,
status: completion.ok ? "completed" : "complete_failed",
@@ -403,14 +424,17 @@ async function runMasterAgentTask(config, runtime, task) {
completedAt: new Date().toISOString(),
detail,
};
await completeMasterAgentTask(config, runtime, {
taskId: task.taskId,
status: "failed",
errorMessage: detail,
dispatchExecutionId: task.dispatchExecutionId,
targetProjectId: task.targetProjectId,
targetThreadId: task.targetThreadId,
}).catch(() => null);
await completeMasterAgentTask(
config,
runtime,
buildRemoteExecutionCompletionPayload(task, {
status: "failed",
errorMessage: detail,
dispatchExecutionId: task.dispatchExecutionId,
targetProjectId: task.targetProjectId,
targetThreadId: task.targetThreadId,
}),
).catch(() => null);
await postAppLog(config, runtime, {
projectId: "master-agent",
level: "error",

View File

@@ -1,6 +1,7 @@
import { NextRequest, NextResponse } from "next/server";
import { authorizeDeviceWriteRequest } from "@/lib/boss-device-auth";
import { completeMasterAgentTask } from "@/lib/boss-data";
import { normalizeRemoteExecutionResult } from "@/lib/execution/remote-runtime-adapter";
export async function POST(
request: NextRequest,
@@ -30,17 +31,18 @@ export async function POST(
const { taskId } = await context.params;
try {
const normalized = normalizeRemoteExecutionResult(body);
const task = await completeMasterAgentTask({
taskId,
deviceId: body.deviceId.trim(),
status: body.status === "failed" ? "failed" : "completed",
replyBody: body.replyBody,
errorMessage: body.errorMessage,
requestId: body.requestId,
dispatchExecutionId: body.dispatchExecutionId,
targetProjectId: body.targetProjectId,
targetThreadId: body.targetThreadId,
rawThreadReply: body.rawThreadReply,
status: normalized.status,
replyBody: normalized.replyBody,
errorMessage: normalized.errorMessage,
requestId: normalized.requestId,
dispatchExecutionId: normalized.dispatchExecutionId,
targetProjectId: normalized.targetProjectId,
targetThreadId: normalized.targetThreadId,
rawThreadReply: normalized.rawThreadReply,
});
return NextResponse.json({ ok: true, task });
} catch (error) {

View File

@@ -0,0 +1,11 @@
import type { OrchestrationBackend } from "@/lib/execution/orchestration-backend";
export const BOSS_NATIVE_ORCHESTRATOR: OrchestrationBackend = {
backendId: "boss-native-orchestrator",
async describe() {
return {
backendId: "boss-native-orchestrator",
label: "Boss Native Orchestrator",
};
},
};

View File

@@ -0,0 +1,43 @@
export interface RemoteExecutionResultInput {
status: "completed" | "failed";
dispatchExecutionId?: string;
targetProjectId?: string;
targetThreadId?: string;
rawThreadReply?: string;
replyBody?: string;
errorMessage?: string;
requestId?: string;
}
export interface NormalizedRemoteExecutionResult {
status: "completed" | "failed";
dispatchExecutionId?: string;
targetProjectId?: string;
targetThreadId?: string;
rawThreadReply?: string;
replyBody?: string;
errorMessage?: string;
requestId?: string;
}
function trimToDefined(value: string | undefined) {
const trimmed = value?.trim();
return trimmed ? trimmed : undefined;
}
export function normalizeRemoteExecutionResult(
input: RemoteExecutionResultInput,
): NormalizedRemoteExecutionResult {
return {
status: input.status === "failed" ? "failed" : "completed",
dispatchExecutionId: trimToDefined(input.dispatchExecutionId),
targetProjectId: trimToDefined(input.targetProjectId),
targetThreadId: trimToDefined(input.targetThreadId),
rawThreadReply: trimToDefined(input.rawThreadReply),
replyBody: trimToDefined(input.replyBody),
errorMessage: trimToDefined(input.errorMessage),
requestId: trimToDefined(input.requestId),
};
}
export const normalizeRemoteExecutionResultForTesting = normalizeRemoteExecutionResult;

View File

@@ -0,0 +1,39 @@
import test from "node:test";
import assert from "node:assert/strict";
import { normalizeRemoteExecutionResultForTesting } from "../src/lib/execution/remote-runtime-adapter.ts";
test("RemoteRuntimeAdapter 会把 local-agent 回写标准化成统一结果", () => {
const normalized = normalizeRemoteExecutionResultForTesting({
status: "completed",
dispatchExecutionId: "dx-1",
targetProjectId: "project-1",
targetThreadId: "thread-1",
rawThreadReply: " 链路正常 ",
replyBody: " 主 Agent 汇总:链路正常 ",
});
assert.equal(normalized.status, "completed");
assert.equal(normalized.dispatchExecutionId, "dx-1");
assert.equal(normalized.targetProjectId, "project-1");
assert.equal(normalized.targetThreadId, "thread-1");
assert.equal(normalized.rawThreadReply, "链路正常");
assert.equal(normalized.replyBody, "主 Agent 汇总:链路正常");
});
test("RemoteRuntimeAdapter 会忽略空白字段并保留失败状态", () => {
const normalized = normalizeRemoteExecutionResultForTesting({
status: "failed",
dispatchExecutionId: " ",
targetProjectId: " project-2 ",
targetThreadId: "",
rawThreadReply: " ",
errorMessage: " MODEL_CALL_FAILED ",
});
assert.equal(normalized.status, "failed");
assert.equal(normalized.dispatchExecutionId, undefined);
assert.equal(normalized.targetProjectId, "project-2");
assert.equal(normalized.targetThreadId, undefined);
assert.equal(normalized.rawThreadReply, undefined);
assert.equal(normalized.errorMessage, "MODEL_CALL_FAILED");
});