Integrate master agent runtime orchestration updates

This commit is contained in:
kris
2026-04-16 04:41:46 +08:00
parent e0c0ea1814
commit 39be49630f
81 changed files with 9283 additions and 448 deletions

View File

@@ -8,6 +8,7 @@ import { NextRequest } from "next/server";
let runtimeRoot = "";
let POST: (typeof import("../src/app/api/v1/projects/[projectId]/messages/route"))["POST"];
let saveAiAccount: (typeof import("../src/lib/boss-data"))["saveAiAccount"];
let getProjectAgentControls: (typeof import("../src/lib/boss-data"))["getProjectAgentControls"];
let updateProjectAgentControls: (typeof import("../src/lib/boss-data"))["updateProjectAgentControls"];
let readState: (typeof import("../src/lib/boss-data"))["readState"];
let createAuthSession: (typeof import("../src/lib/boss-data"))["createAuthSession"];
@@ -30,6 +31,7 @@ async function setup() {
POST = messageRoute.POST;
saveAiAccount = data.saveAiAccount;
getProjectAgentControls = data.getProjectAgentControls;
updateProjectAgentControls = data.updateProjectAgentControls;
readState = data.readState;
createAuthSession = data.createAuthSession;
@@ -77,6 +79,240 @@ test.beforeEach(async () => {
await mkdir(runtimeRoot, { recursive: true });
});
test("master-agent 明确查询可用模型时直接本地返回模型清单而不进入异步队列", async () => {
await saveAiAccount({
accountId: "openai-model-list",
label: "OpenAI 主账号",
role: "primary",
provider: "openai_api",
displayName: "OpenAI 主账号",
model: "gpt-5.4",
apiKey: "sk-openai-model-list",
enabled: true,
setActive: true,
loginStatusNote: "用于模型清单测试。",
});
await saveAiAccount({
accountId: "qwen-model-list",
label: "Qwen 备用",
role: "backup",
provider: "aliyun_qwen_api",
displayName: "阿里百炼",
model: "qwen3.5-plus",
apiKey: "sk-qwen-model-list",
enabled: true,
setActive: false,
loginStatusNote: "用于模型清单测试。",
});
const response = await POST(
await createAuthedRequest("master-agent", {
body: "主 Agent现在有哪些模型可以用",
}),
{ params: Promise.resolve({ projectId: "master-agent" }) },
);
assert.equal(response.status, 200);
const payload = (await response.json()) as {
ok: boolean;
task?: { taskId: string } | null;
masterReplyState?: "queued" | "running" | "completed" | null;
};
assert.equal(payload.ok, true);
assert.equal(payload.task ?? null, null);
assert.equal(payload.masterReplyState, "completed");
const state = await readState();
const masterProject = state.projects.find((project) => project.id === "master-agent");
const reply = masterProject?.messages.at(-1);
assert.ok(reply, "expected the master-agent model list reply to be persisted");
assert.match(reply?.body ?? "", /当前可用模型/);
assert.match(reply?.body ?? "", /gpt-5\.4/);
assert.match(reply?.body ?? "", /qwen3\.5-plus/);
});
test("master-agent 明确要求切快模型时直接更新 controls 并返回完成态", async () => {
await saveAiAccount({
accountId: "openai-fast-switch",
label: "OpenAI 快模型",
role: "primary",
provider: "openai_api",
displayName: "OpenAI 快模型",
model: "gpt-5.4-mini",
apiKey: "sk-openai-fast-switch",
enabled: true,
setActive: true,
loginStatusNote: "用于快模型切换测试。",
});
const response = await POST(
await createAuthedRequest("master-agent", {
body: "帮我把快模型切到 gpt-5.4-mini",
}),
{ params: Promise.resolve({ projectId: "master-agent" }) },
);
assert.equal(response.status, 200);
const payload = (await response.json()) as {
ok: boolean;
task?: { taskId: string } | null;
masterReplyState?: "queued" | "running" | "completed" | null;
};
assert.equal(payload.ok, true);
assert.equal(payload.task ?? null, null);
assert.equal(payload.masterReplyState, "completed");
const controls = await getProjectAgentControls("master-agent", "17600003315");
assert.equal(controls?.fastModelOverride ?? null, "gpt-5.4-mini");
const state = await readState();
assert.equal(state.masterAgentTasks.length, 0);
const masterProject = state.projects.find((project) => project.id === "master-agent");
const reply = masterProject?.messages.at(-1);
assert.ok(reply, "expected the master-agent model switch reply to be persisted");
assert.match(reply?.body ?? "", /快模型/);
assert.match(reply?.body ?? "", /gpt-5\.4-mini/);
assert.equal(reply?.senderLabel ?? "", "主Agent·gpt-5.4-mini");
});
test("master-agent 识别自然写法的模型名并切当前主模型", async () => {
await saveAiAccount({
accountId: "openai-main-switch",
label: "OpenAI 主模型",
role: "primary",
provider: "openai_api",
displayName: "OpenAI 主模型",
model: "gpt-5.4",
apiKey: "sk-openai-main-switch",
enabled: true,
setActive: true,
loginStatusNote: "用于主模型自然写法切换测试。",
});
const response = await POST(
await createAuthedRequest("master-agent", {
body: "把主agent模型换成gpt5.4",
}),
{ params: Promise.resolve({ projectId: "master-agent" }) },
);
assert.equal(response.status, 200);
const payload = (await response.json()) as {
ok: boolean;
task?: { taskId: string } | null;
masterReplyState?: "queued" | "running" | "completed" | null;
};
assert.equal(payload.ok, true);
assert.equal(payload.task ?? null, null);
assert.equal(payload.masterReplyState, "completed");
const controls = await getProjectAgentControls("master-agent", "17600003315");
assert.equal(controls?.modelOverride ?? null, "gpt-5.4");
const state = await readState();
const masterProject = state.projects.find((project) => project.id === "master-agent");
const reply = masterProject?.messages.at(-1);
assert.ok(reply, "expected the master-agent natural model switch reply to be persisted");
assert.match(reply?.body ?? "", /当前主模型/);
assert.match(reply?.body ?? "", /gpt-5\.4/);
assert.equal(reply?.senderLabel ?? "", "主Agent·gpt-5.4");
});
test("master-agent 查询当前是什么大模型时直接走 fast path 返回当前模型摘要", async () => {
await saveAiAccount({
accountId: "openai-fast-query",
label: "OpenAI 主模型",
role: "primary",
provider: "openai_api",
displayName: "OpenAI 主模型",
model: "gpt-5.4",
apiKey: "sk-openai-fast-query",
enabled: true,
setActive: true,
loginStatusNote: "用于当前模型查询测试。",
});
await updateProjectAgentControls(
"master-agent",
{
fastModelOverride: "gpt-5.4-mini",
smartModelOverride: "gpt-5.4",
},
"17600003315",
);
const response = await POST(
await createAuthedRequest("master-agent", {
body: "你现在是什么大模型",
}),
{ params: Promise.resolve({ projectId: "master-agent" }) },
);
assert.equal(response.status, 200);
const payload = (await response.json()) as {
ok: boolean;
task?: { taskId: string } | null;
masterReplyState?: "queued" | "running" | "completed" | null;
};
assert.equal(payload.ok, true);
assert.equal(payload.task ?? null, null);
assert.equal(payload.masterReplyState, "completed");
const state = await readState();
const masterProject = state.projects.find((project) => project.id === "master-agent");
const reply = masterProject?.messages.at(-1);
assert.ok(reply, "expected the master-agent fast model summary reply to be persisted");
assert.match(reply?.body ?? "", /当前聊天模型gpt-5\.4-mini/);
assert.match(reply?.body ?? "", /强模型gpt-5\.4/);
assert.equal(reply?.senderLabel ?? "", "主Agent·gpt-5.4-mini");
});
test("master-agent 查询当前后端时直接走 fast path 返回后端摘要", async () => {
await saveAiAccount({
accountId: "openai-backend-query",
label: "OpenAI 主模型",
role: "primary",
provider: "openai_api",
displayName: "OpenAI 主模型",
model: "gpt-5.4",
apiKey: "sk-openai-backend-query",
enabled: true,
setActive: true,
loginStatusNote: "用于后端查询测试。",
});
await updateProjectAgentControls(
"master-agent",
{
backendOverride: "hermes-runtime",
},
"17600003315",
);
const response = await POST(
await createAuthedRequest("master-agent", {
body: "当前后端是什么",
}),
{ params: Promise.resolve({ projectId: "master-agent" }) },
);
assert.equal(response.status, 200);
const payload = (await response.json()) as {
ok: boolean;
task?: { taskId: string } | null;
masterReplyState?: "queued" | "running" | "completed" | null;
};
assert.equal(payload.ok, true);
assert.equal(payload.task ?? null, null);
assert.equal(payload.masterReplyState, "completed");
const state = await readState();
const masterProject = state.projects.find((project) => project.id === "master-agent");
const reply = masterProject?.messages.at(-1);
assert.ok(reply, "expected the master-agent backend summary reply to be persisted");
assert.match(reply?.body ?? "", /当前后端hermes-runtime/);
assert.equal(reply?.senderLabel ?? "", "主Agent·gpt-5.4");
});
test("POST /api/v1/projects/master-agent/messages 快速返回队列态并在异步实际回复时继承当前会话覆盖", async () => {
await saveAiAccount({
accountId: "openai-master-agent-queue",
@@ -122,6 +358,7 @@ test("POST /api/v1/projects/master-agent/messages 快速返回队列态并在异
const payload = (await response.json()) as {
ok: boolean;
message: { id: string };
task?: { taskId: string; taskType: string; status: string } | null;
masterReplyState?: "queued" | "running" | "completed";
masterReply?: { accountId?: string } | null;
@@ -134,6 +371,7 @@ test("POST /api/v1/projects/master-agent/messages 快速返回队列态并在异
assert.equal(payload.task?.taskType, "conversation_reply");
assert.equal(payload.task?.status, "queued");
assert.ok(payload.task?.taskId, "expected a stable taskId in the response");
assert.equal((payload.task as { requestMessageId?: string } | null)?.requestMessageId, payload.message.id);
await waitFor(async () => {
const state = await readState();
@@ -333,6 +571,96 @@ test("master-agent enqueue 在显式选择 claw-runtime 时会通过 Claw 异步
}
});
test("master-agent enqueue 在显式选择 hermes-runtime 时会通过 Hermes 异步回写回复", async () => {
const hermesDir = await mkdtemp(path.join(os.tmpdir(), "boss-hermes-queue-"));
const hermesScriptPath = path.join(hermesDir, "hermes-runtime.mjs");
await writeFile(
hermesScriptPath,
`
const args = process.argv.slice(2);
const queryIndex = args.findIndex((item) => item === "-q" || item === "--query");
const query = queryIndex >= 0 ? args[queryIndex + 1] ?? "" : "";
process.stdout.write("Hermes 已接管当前主 Agent 会话:" + query + "\\n\\n");
process.stdout.write("session_id: hermes-session-123\\n");
`,
"utf8",
);
const previousEnv = {
BOSS_HERMES_ENABLED: process.env.BOSS_HERMES_ENABLED,
BOSS_HERMES_COMMAND: process.env.BOSS_HERMES_COMMAND,
BOSS_HERMES_ARGS: process.env.BOSS_HERMES_ARGS,
BOSS_HERMES_TIMEOUT_MS: process.env.BOSS_HERMES_TIMEOUT_MS,
};
process.env.BOSS_HERMES_ENABLED = "true";
process.env.BOSS_HERMES_COMMAND = process.execPath;
process.env.BOSS_HERMES_ARGS = hermesScriptPath;
process.env.BOSS_HERMES_TIMEOUT_MS = "1000";
await saveAiAccount({
accountId: "master-codex-primary-hermes",
label: "主 GPT",
role: "primary",
provider: "master_codex_node",
displayName: "Mac 上的 Master Codex Node",
nodeId: "local-codex-node",
nodeLabel: "本机 Codex",
model: "gpt-5.4",
enabled: true,
setActive: true,
loginStatusNote: "用于 Hermes backend 队列测试。",
});
await updateProjectAgentControls("master-agent", {
backendOverride: "hermes-runtime",
});
try {
const response = await POST(
await createAuthedRequest("master-agent", {
body: "请走 Hermes runtime",
}),
{ params: Promise.resolve({ projectId: "master-agent" }) },
);
assert.equal(response.status, 200);
const payload = (await response.json()) as {
ok: boolean;
task?: { taskId: string; status: string } | null;
masterReply?: { accountId?: string } | null;
masterReplyState?: string | null;
};
assert.equal(payload.ok, true);
assert.equal(payload.masterReply?.accountId, "hermes-runtime");
assert.equal(payload.masterReplyState, "queued");
assert.ok(payload.task?.taskId);
await waitFor(async () => {
const state = await readState();
const task = state.masterAgentTasks.find((item) => item.taskId === payload.task?.taskId);
return task?.status === "completed";
});
const nextState = await readState();
const task = nextState.masterAgentTasks.find((item) => item.taskId === payload.task?.taskId);
assert.equal(task?.status, "completed");
assert.match(task?.replyBody ?? "", /Hermes 已接管当前主 Agent 会话:/);
assert.match(task?.replyBody ?? "", /请走 Hermes runtime/);
assert.equal(task?.sessionId, "hermes-session-123");
const masterProject = nextState.projects.find((project) => project.id === "master-agent");
const mirroredReply = masterProject?.messages.at(-1);
assert.match(mirroredReply?.body ?? "", /Hermes 已接管当前主 Agent 会话/);
} finally {
process.env.BOSS_HERMES_ENABLED = previousEnv.BOSS_HERMES_ENABLED;
process.env.BOSS_HERMES_COMMAND = previousEnv.BOSS_HERMES_COMMAND;
process.env.BOSS_HERMES_ARGS = previousEnv.BOSS_HERMES_ARGS;
process.env.BOSS_HERMES_TIMEOUT_MS = previousEnv.BOSS_HERMES_TIMEOUT_MS;
await rm(hermesDir, { recursive: true, force: true });
}
});
test("master-agent enqueue 在首选主节点离线时会回退到可用的备用主节点并返回实际账号", async () => {
await saveAiAccount({
accountId: "master-codex-primary-offline",