Files
boss/tests/master-agent-message-queue.test.ts

1139 lines
39 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import test from "node:test";
import assert from "node:assert/strict";
import os from "node:os";
import path from "node:path";
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
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 updateDevice: (typeof import("../src/lib/boss-data"))["updateDevice"];
let readState: (typeof import("../src/lib/boss-data"))["readState"];
let createAuthSession: (typeof import("../src/lib/boss-data"))["createAuthSession"];
let AUTH_SESSION_COOKIE = "";
async function setup() {
if (runtimeRoot) {
return;
}
runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-master-agent-message-queue-"));
process.env.BOSS_RUNTIME_ROOT = runtimeRoot;
process.env.BOSS_STATE_FILE = path.join(runtimeRoot, "boss-state.json");
const [messageRoute, data, auth] = await Promise.all([
import("../src/app/api/v1/projects/[projectId]/messages/route.ts"),
import("../src/lib/boss-data.ts"),
import("../src/lib/boss-auth.ts"),
]);
POST = messageRoute.POST;
saveAiAccount = data.saveAiAccount;
getProjectAgentControls = data.getProjectAgentControls;
updateProjectAgentControls = data.updateProjectAgentControls;
updateDevice = data.updateDevice;
readState = data.readState;
createAuthSession = data.createAuthSession;
AUTH_SESSION_COOKIE = auth.AUTH_SESSION_COOKIE;
}
async function createAuthedRequest(projectId: string, body: unknown) {
const session = await createAuthSession({
account: "17600003315",
role: "highest_admin",
displayName: "Boss 超级管理员",
loginMethod: "password",
});
return new NextRequest(`http://127.0.0.1:3000/api/v1/projects/${projectId}/messages`, {
method: "POST",
headers: {
"content-type": "application/json",
cookie: `${AUTH_SESSION_COOKIE}=${session.sessionToken}`,
},
body: JSON.stringify(body),
});
}
async function waitFor(predicate: () => Promise<boolean>, timeoutMs = 5_000) {
const startedAt = Date.now();
while (Date.now() - startedAt < timeoutMs) {
if (await predicate()) {
return;
}
await new Promise((resolve) => setTimeout(resolve, 50));
}
throw new Error("waitFor timed out");
}
test.after(async () => {
if (runtimeRoot) {
await rm(runtimeRoot, { recursive: true, force: true });
}
});
test.beforeEach(async () => {
await setup();
await rm(runtimeRoot, { recursive: true, force: true });
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: "master-codex-offline-switch",
label: "主 GPT",
role: "primary",
provider: "master_codex_node",
displayName: "Mac 上的 Master Codex Node",
nodeId: "offline-master-node",
nodeLabel: "离线 Codex",
model: "gpt-5.4",
enabled: true,
setActive: true,
loginStatusNote: "用于离线主节点模型切换文案测试。",
});
const response = await POST(
await createAuthedRequest("master-agent", {
body: "把主agent模型换成gpt5.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?.modelOverride ?? null, "gpt-5.4-mini");
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 offline switch reply to be persisted");
assert.match(reply?.body ?? "", /已把主 Agent 的当前主模型切到 gpt-5\.4-mini/);
assert.doesNotMatch(reply?.body ?? "", /当前可用模型:暂无/);
assert.match(reply?.body ?? "", /已登记\/可选模型:/);
});
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("master-agent 重复追问状态类问题时会记录 repeated_question 进化信号", async () => {
await saveAiAccount({
accountId: "openai-repeat-status",
label: "OpenAI 主模型",
role: "primary",
provider: "openai_api",
displayName: "OpenAI 主模型",
model: "gpt-5.4",
apiKey: "sk-openai-repeat-status",
enabled: true,
setActive: true,
loginStatusNote: "用于重复状态问题测试。",
});
const firstResponse = await POST(
await createAuthedRequest("master-agent", {
body: "当前主节点在线吗",
}),
{ params: Promise.resolve({ projectId: "master-agent" }) },
);
assert.equal(firstResponse.status, 200);
const secondResponse = await POST(
await createAuthedRequest("master-agent", {
body: "现在主节点在线吗",
}),
{ params: Promise.resolve({ projectId: "master-agent" }) },
);
assert.equal(secondResponse.status, 200);
const state = await readState();
assert.ok(
state.masterAgentEvolutionSignals.some((signal) => signal.kind === "repeated_question"),
"expected at least one repeated_question signal",
);
});
test("master-agent 查询全局接管状态时直接走 fast path 返回当前状态", async () => {
await saveAiAccount({
accountId: "openai-takeover-status",
label: "OpenAI 主模型",
role: "primary",
provider: "openai_api",
displayName: "OpenAI 主模型",
model: "gpt-5.4-mini",
apiKey: "sk-openai-takeover-status",
enabled: true,
setActive: true,
loginStatusNote: "用于全局接管状态查询测试。",
});
await updateProjectAgentControls(
"master-agent",
{
globalTakeoverEnabled: true,
},
"17600003315",
);
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);
assert.match(reply?.body ?? "", /全局接管:开启/);
assert.equal(reply?.senderLabel ?? "", "主Agent·gpt-5.4-mini");
});
test("master-agent 可以直接通过 fast path 开启全局接管", async () => {
await saveAiAccount({
accountId: "openai-takeover-switch",
label: "OpenAI 主模型",
role: "primary",
provider: "openai_api",
displayName: "OpenAI 主模型",
model: "gpt-5.4-mini",
apiKey: "sk-openai-takeover-switch",
enabled: true,
setActive: true,
loginStatusNote: "用于全局接管切换测试。",
});
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 controls = await getProjectAgentControls("master-agent", "17600003315");
assert.equal(controls?.globalTakeoverEnabled ?? null, true);
const state = await readState();
const masterProject = state.projects.find((project) => project.id === "master-agent");
const reply = masterProject?.messages.at(-1);
assert.ok(reply);
assert.match(reply?.body ?? "", /已开启全局接管/);
});
test("master-agent 可以直接通过 fast path 切换默认后端到 Hermes", async () => {
const previousEnv = {
BOSS_HERMES_ENABLED: process.env.BOSS_HERMES_ENABLED,
BOSS_HERMES_COMMAND: process.env.BOSS_HERMES_COMMAND,
};
process.env.BOSS_HERMES_ENABLED = "true";
process.env.BOSS_HERMES_COMMAND = process.execPath;
await saveAiAccount({
accountId: "openai-backend-switch",
label: "OpenAI 主模型",
role: "primary",
provider: "openai_api",
displayName: "OpenAI 主模型",
model: "gpt-5.4",
apiKey: "sk-openai-backend-switch",
enabled: true,
setActive: true,
loginStatusNote: "用于后端切换测试。",
});
try {
const response = await POST(
await createAuthedRequest("master-agent", {
body: "把默认后端切到 Hermes",
}),
{ 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?.backendOverride ?? null, "hermes-runtime");
const state = await readState();
const masterProject = state.projects.find((project) => project.id === "master-agent");
const reply = masterProject?.messages.at(-1);
assert.ok(reply);
assert.match(reply?.body ?? "", /已把默认后端切到 hermes-runtime/i);
} finally {
process.env.BOSS_HERMES_ENABLED = previousEnv.BOSS_HERMES_ENABLED;
process.env.BOSS_HERMES_COMMAND = previousEnv.BOSS_HERMES_COMMAND;
}
});
test("master-agent 查询默认执行模式时直接返回 GUI CLI 状态", async () => {
await saveAiAccount({
accountId: "master-codex-execution-mode",
label: "主 GPT",
role: "primary",
provider: "master_codex_node",
displayName: "Mac 上的 Master Codex Node",
nodeId: "mac-studio",
nodeLabel: "Mac Studio",
model: "gpt-5.4",
enabled: true,
setActive: true,
loginStatusNote: "用于执行模式查询测试。",
});
await updateDevice("mac-studio", {
status: "online",
preferredExecutionMode: "gui",
capabilities: {
gui: {
connected: true,
},
cli: {
connected: true,
},
},
});
const response = await POST(
await createAuthedRequest("master-agent", {
body: "现在默认走 GUI 还是 CLI",
}),
{ 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);
assert.match(reply?.body ?? "", /默认执行模式gui/i);
assert.match(reply?.body ?? "", /GUI在线/);
assert.match(reply?.body ?? "", /CLI在线/);
});
test("master-agent 查询当前主节点设备状态时直接返回绑定设备在线信息", async () => {
await saveAiAccount({
accountId: "master-codex-device-status",
label: "主 GPT",
role: "primary",
provider: "master_codex_node",
displayName: "Mac 上的 Master Codex Node",
nodeId: "mac-studio",
nodeLabel: "Mac Studio",
model: "gpt-5.4",
enabled: true,
setActive: true,
loginStatusNote: "用于绑定设备状态查询测试。",
});
await updateDevice("mac-studio", {
status: "online",
capabilities: {
gui: {
connected: true,
},
cli: {
connected: false,
},
},
});
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);
assert.match(reply?.body ?? "", /当前主节点设备:/);
assert.match(reply?.body ?? "", /设备状态online/);
assert.match(reply?.body ?? "", /GUI在线/);
assert.match(reply?.body ?? "", /CLI离线/);
});
test("POST /api/v1/projects/master-agent/messages 快速返回队列态并在异步实际回复时继承当前会话覆盖", async () => {
await saveAiAccount({
accountId: "openai-master-agent-queue",
label: "API 容灾",
role: "api_fallback",
provider: "openai_api",
displayName: "OpenAI API 队列测试",
model: "gpt-5.4",
apiKey: "sk-test-openai-queue",
enabled: true,
setActive: true,
loginStatusNote: "用于 master-agent 队列测试。",
});
await updateProjectAgentControls("master-agent", {
modelOverride: "gpt-4.1-mini",
reasoningEffortOverride: "high",
});
const fetchCalls: Array<{ url: string; body: unknown }> = [];
const originalFetch = globalThis.fetch;
globalThis.fetch = (async (input, init) => {
const body = typeof init?.body === "string" ? JSON.parse(init.body) : init?.body ?? null;
fetchCalls.push({ url: String(input), body });
return new Response(JSON.stringify({ output_text: "已切到异步队列回复。" }), {
status: 200,
headers: {
"content-type": "application/json",
"x-request-id": "req-master-agent-queue",
},
});
}) as typeof fetch;
try {
const response = await POST(
await createAuthedRequest("master-agent", {
body: "请同步 master-agent 当前阻塞点",
}),
{ params: Promise.resolve({ projectId: "master-agent" }) },
);
assert.equal(response.status, 200);
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;
};
assert.equal(payload.ok, true);
assert.equal(payload.masterReply?.accountId, "openai-master-agent-queue");
assert.equal(payload.masterReplyState, "queued");
assert.ok(payload.task, "expected master-agent message to return a task envelope");
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();
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.ok(task, "expected the queued task to remain in state");
assert.equal(task?.status, "completed");
assert.equal(task?.replyBody, "已切到异步队列回复。");
const masterProject = nextState.projects.find((project) => project.id === "master-agent");
const mirroredReply = masterProject?.messages.at(-1);
assert.ok(mirroredReply, "expected the async reply to be written back to the master-agent ledger");
assert.match(mirroredReply?.body ?? "", /已切到异步队列回复/);
assert.equal(fetchCalls.length, 1);
assert.equal(fetchCalls[0]?.url, "https://api.openai.com/v1/responses");
const requestBody = fetchCalls[0]?.body as {
model?: string;
reasoning?: { effort?: string };
};
assert.equal(requestBody?.model, "gpt-4.1-mini");
assert.equal(requestBody?.reasoning?.effort, "high");
} finally {
globalThis.fetch = originalFetch;
}
});
test("master-agent enqueue 在主节点离线时会自动切到 OpenAI 后台队列而不是挂到本机设备队列", async () => {
await saveAiAccount({
accountId: "master-codex-primary-offline",
label: "主 GPT",
role: "primary",
provider: "master_codex_node",
displayName: "离线 Master Codex Node",
nodeId: "offline-node",
nodeLabel: "离线节点",
model: "gpt-5.4",
enabled: true,
setActive: true,
loginStatusNote: "离线主节点",
});
await saveAiAccount({
accountId: "openai-backup-queue",
label: "备用 GPT",
role: "backup",
provider: "openai_api",
displayName: "OpenAI 备用账号",
accountIdentifier: "sk-queue-demo",
model: "gpt-5.4",
apiKey: "sk-queue-demo",
enabled: true,
setActive: false,
loginStatusNote: "备用 API 账号",
});
const originalFetch = globalThis.fetch;
globalThis.fetch = (async () =>
new Response(JSON.stringify({ output_text: "离线主节点已切到 API 后台队列。" }), {
status: 200,
headers: {
"content-type": "application/json",
"x-request-id": "req-master-agent-offline-fallback-queue",
},
})) as typeof fetch;
try {
const response = await POST(
await createAuthedRequest("master-agent", {
body: "请走备用 API 队列",
}),
{ params: Promise.resolve({ projectId: "master-agent" }) },
);
assert.equal(response.status, 200);
const payload = (await response.json()) as {
ok: boolean;
task?: { taskId: string; taskType: string; status: string } | null;
masterReplyState?: "queued" | "running" | "completed";
masterReply?: { accountId?: string } | null;
};
assert.equal(payload.ok, true);
assert.equal(payload.masterReply?.accountId, "openai-backup-queue");
assert.equal(payload.masterReplyState, "queued");
assert.equal(payload.task?.taskType, "conversation_reply");
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?.deviceId, "master-agent-openai");
assert.equal(task?.status, "completed");
assert.equal(task?.accountId, "openai-backup-queue");
} finally {
globalThis.fetch = originalFetch;
}
});
test("master-agent enqueue 在显式选择 claw-runtime 时会通过 Claw 异步回写回复", async () => {
const clawDir = await mkdtemp(path.join(os.tmpdir(), "boss-claw-queue-"));
const clawScriptPath = path.join(clawDir, "claw-runtime.mjs");
await writeFile(
clawScriptPath,
`
let stdin = "";
process.stdin.setEncoding("utf8");
for await (const chunk of process.stdin) {
stdin += chunk;
}
const payload = JSON.parse(stdin);
process.stdout.write(JSON.stringify({
status: "completed",
output: "Claw 已接管当前主 Agent 会话:" + payload.body
}));
`,
"utf8",
);
const previousEnv = {
BOSS_CLAW_ENABLED: process.env.BOSS_CLAW_ENABLED,
BOSS_CLAW_COMMAND: process.env.BOSS_CLAW_COMMAND,
BOSS_CLAW_ARGS: process.env.BOSS_CLAW_ARGS,
BOSS_CLAW_TIMEOUT_MS: process.env.BOSS_CLAW_TIMEOUT_MS,
};
process.env.BOSS_CLAW_ENABLED = "true";
process.env.BOSS_CLAW_COMMAND = process.execPath;
process.env.BOSS_CLAW_ARGS = clawScriptPath;
process.env.BOSS_CLAW_TIMEOUT_MS = "1000";
await saveAiAccount({
accountId: "master-codex-primary-claw",
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: "用于 Claw backend 队列测试。",
});
await updateProjectAgentControls("master-agent", {
backendOverride: "claw-runtime",
});
try {
const response = await POST(
await createAuthedRequest("master-agent", {
body: "请走 Claw 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, "claw-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.equal(task?.replyBody, "Claw 已接管当前主 Agent 会话:请走 Claw runtime");
const masterProject = nextState.projects.find((project) => project.id === "master-agent");
const mirroredReply = masterProject?.messages.at(-1);
assert.match(mirroredReply?.body ?? "", /Claw 已接管当前主 Agent 会话/);
} finally {
process.env.BOSS_CLAW_ENABLED = previousEnv.BOSS_CLAW_ENABLED;
process.env.BOSS_CLAW_COMMAND = previousEnv.BOSS_CLAW_COMMAND;
process.env.BOSS_CLAW_ARGS = previousEnv.BOSS_CLAW_ARGS;
process.env.BOSS_CLAW_TIMEOUT_MS = previousEnv.BOSS_CLAW_TIMEOUT_MS;
await rm(clawDir, { recursive: true, force: true });
}
});
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",
label: "主 GPT",
role: "primary",
provider: "master_codex_node",
displayName: "离线 Master Codex Node",
nodeId: "offline-node",
nodeLabel: "离线节点",
model: "gpt-5.4",
enabled: true,
setActive: true,
loginStatusNote: "离线主节点",
});
await saveAiAccount({
accountId: "master-codex-backup-online",
label: "备用主节点",
role: "backup",
provider: "master_codex_node",
displayName: "在线备用 Master Codex Node",
nodeId: "mac-studio",
nodeLabel: "Mac Studio",
model: "gpt-5.4",
enabled: true,
setActive: false,
loginStatusNote: "在线备用主节点",
});
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; taskType: string; status: string } | null;
masterReplyState?: "queued" | "running" | "completed";
masterReply?: { accountId?: string } | null;
};
assert.equal(payload.ok, true);
assert.equal(payload.masterReply?.accountId, "master-codex-backup-online");
assert.equal(payload.masterReplyState, "queued");
assert.equal(payload.task?.taskType, "conversation_reply");
assert.equal(payload.task?.status, "queued");
const state = await readState();
const task = state.masterAgentTasks.find((item) => item.taskId === payload.task?.taskId);
assert.ok(task, "expected queued master-agent task");
assert.equal(task?.accountId, "master-codex-backup-online");
assert.equal(task?.deviceId, "mac-studio");
});
test("master-agent enqueue 会在首个 API 候选失败后切到下一条备用链并重写任务账号", async () => {
await saveAiAccount({
accountId: "openai-primary-queue",
label: "OpenAI 主控",
role: "primary",
provider: "openai_api",
displayName: "OpenAI 主账号",
model: "gpt-5.4",
apiKey: "sk-openai-primary-queue",
enabled: true,
setActive: true,
loginStatusNote: "OpenAI 主控",
});
await saveAiAccount({
accountId: "aliyun-qwen-backup-queue",
label: "阿里备用",
role: "backup",
provider: "aliyun_qwen_api",
displayName: "阿里百炼备用账号",
model: "qwen3.5-plus",
apiKey: "sk-aliyun-backup-queue",
enabled: true,
setActive: false,
loginStatusNote: "阿里备用账号",
});
const fetchCalls: string[] = [];
const originalFetch = globalThis.fetch;
globalThis.fetch = (async (input) => {
fetchCalls.push(String(input));
if (typeof input === "string" && input === "https://api.openai.com/v1/responses") {
return new Response(JSON.stringify({ error: { message: "openai queue failure" } }), {
status: 500,
headers: { "content-type": "application/json" },
});
}
if (typeof input === "string" && input === "https://dashscope.aliyuncs.com/compatible-mode/v1/responses") {
return new Response(JSON.stringify({ output_text: "后台队列已切到阿里备用。" }), {
status: 200,
headers: {
"content-type": "application/json",
"x-request-id": "req-master-agent-queue-fallback-chain",
},
});
}
throw new Error(`unexpected fetch: ${String(input)}`);
}) as typeof fetch;
try {
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; taskType: string; status: string } | null;
masterReplyState?: "queued" | "running" | "completed";
masterReply?: { accountId?: string } | null;
};
assert.equal(payload.ok, true);
assert.equal(payload.masterReply?.accountId, "openai-primary-queue");
assert.equal(payload.masterReplyState, "queued");
await waitFor(async () => {
const state = await readState();
const task = state.masterAgentTasks.find((item) => item.taskId === payload.task?.taskId);
return task?.status === "completed";
});
const state = await readState();
const task = state.masterAgentTasks.find((item) => item.taskId === payload.task?.taskId);
assert.ok(task, "expected queued task to remain in state");
assert.equal(task?.status, "completed");
assert.equal(task?.accountId, "aliyun-qwen-backup-queue");
assert.equal(task?.deviceId, "master-agent-aliyun-qwen");
const aliyunAccount = state.aiAccounts.find((item) => item.accountId === "aliyun-qwen-backup-queue");
assert.equal(aliyunAccount?.isActive, true);
assert.equal(fetchCalls[0], "https://api.openai.com/v1/responses");
assert.equal(fetchCalls[1], "https://dashscope.aliyuncs.com/compatible-mode/v1/responses");
} finally {
globalThis.fetch = originalFetch;
}
});