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 } from "node:fs/promises"; let runtimeRoot = ""; let replyToMasterAgentUserMessage: (typeof import("../src/lib/boss-master-agent"))["replyToMasterAgentUserMessage"]; let saveAiAccount: (typeof import("../src/lib/boss-data"))["saveAiAccount"]; let readState: (typeof import("../src/lib/boss-data"))["readState"]; let updateAiAccountHealth: (typeof import("../src/lib/boss-data"))["updateAiAccountHealth"]; async function setup() { if (runtimeRoot) return; runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-master-agent-fallback-")); process.env.BOSS_RUNTIME_ROOT = runtimeRoot; process.env.BOSS_STATE_FILE = path.join(runtimeRoot, "boss-state.json"); const [masterAgent, data] = await Promise.all([ import("../src/lib/boss-master-agent.ts"), import("../src/lib/boss-data.ts"), ]); replyToMasterAgentUserMessage = masterAgent.replyToMasterAgentUserMessage; saveAiAccount = data.saveAiAccount; readState = data.readState; updateAiAccountHealth = data.updateAiAccountHealth; } 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("replyToMasterAgentUserMessage falls back to a runnable OpenAI API account when the master node is offline", async () => { await saveAiAccount({ accountId: "master-codex-primary", label: "主 GPT", role: "primary", provider: "master_codex_node", displayName: "Mac 上的 Master Codex Node", nodeId: "offline-node", nodeLabel: "离线节点", model: "gpt-5.4", enabled: true, setActive: true, loginStatusNote: "通过绑定的 Master Codex Node 对话。", }); await saveAiAccount({ accountId: "openai-backup", label: "备用 GPT", role: "backup", provider: "openai_api", displayName: "OpenAI API 备用账号", accountIdentifier: "sk-demo", model: "gpt-5.4", apiKey: "sk-live-demo-123456", enabled: true, setActive: false, loginStatusNote: "备用 API 账号。", }); const originalFetch = globalThis.fetch; globalThis.fetch = (async (input) => { if (typeof input === "string" && input === "https://api.openai.com/v1/responses") { return new Response(JSON.stringify({ output_text: "主Agent链路正常。" }), { status: 200, headers: { "content-type": "application/json", "x-request-id": "req-master-fallback", }, }); } throw new Error(`unexpected fetch: ${String(input)}`); }) as typeof fetch; try { const result = await replyToMasterAgentUserMessage({ requestMessageId: "msg-master-fallback", requestText: "请只回复:主Agent链路正常。", requestedBy: "Boss 超级管理员", requestedByAccount: "17600003315", }); assert.equal(result.ok, true); assert.equal(result.accountId, "openai-backup"); assert.equal(result.requestId, "req-master-fallback"); const state = await readState(); const masterProject = state.projects.find((project) => project.id === "master-agent"); const reply = masterProject?.messages.at(-1); assert.ok(reply, "expected a master-agent reply to be appended"); assert.equal(reply?.sender, "master"); assert.equal(reply?.senderLabel, "主 Agent · 备用 GPT"); assert.match(reply?.body ?? "", /主Agent链路正常/); } finally { globalThis.fetch = originalFetch; } }); test("replyToMasterAgentUserMessage can retry the same degraded API account when it is the only available backend", async () => { await saveAiAccount({ accountId: "master-codex-primary", label: "主 GPT", role: "primary", provider: "master_codex_node", displayName: "Mac 上的 Master Codex Node", nodeId: "offline-node", nodeLabel: "离线节点", model: "gpt-5.4", enabled: true, setActive: false, loginStatusNote: "测试中显式模拟默认主节点离线。", }); await updateAiAccountHealth({ accountId: "master-codex-primary", status: "degraded", lastError: "MASTER_CODEX_NODE_DEVICE_OFFLINE", lastValidatedAt: new Date().toISOString(), }); await saveAiAccount({ accountId: "openai-primary-degraded", label: "OpenAI 主控", role: "primary", provider: "openai_api", displayName: "OpenAI 主账号", model: "gpt-5.4", apiKey: "sk-openai-only", enabled: true, setActive: true, loginStatusNote: "唯一可用的 OpenAI 账号。", }); await updateAiAccountHealth({ accountId: "openai-primary-degraded", status: "degraded", lastError: "temporary failure", lastValidatedAt: new Date().toISOString(), }); const originalFetch = globalThis.fetch; globalThis.fetch = (async (input) => { if (typeof input === "string" && input === "https://api.openai.com/v1/responses") { return new Response(JSON.stringify({ output_text: "仍然可以重试同一个 API 账号。" }), { status: 200, headers: { "content-type": "application/json", "x-request-id": "req-openai-degraded-retry", }, }); } throw new Error(`unexpected fetch: ${String(input)}`); }) as typeof fetch; try { const result = await replyToMasterAgentUserMessage({ requestMessageId: "msg-openai-degraded-retry", requestText: "请只回复:仍然可以重试同一个 API 账号。", requestedBy: "Boss 超级管理员", requestedByAccount: "17600003315", }); assert.equal(result.ok, true); assert.equal(result.accountId, "openai-primary-degraded"); const state = await readState(); const account = state.aiAccounts.find((item) => item.accountId === "openai-primary-degraded"); assert.equal(account?.status, "ready"); assert.equal(account?.isActive, true); } finally { globalThis.fetch = originalFetch; } }); test("replyToMasterAgentUserMessage falls back to a runnable aliyun qwen backup account when the master node is offline", async () => { await saveAiAccount({ accountId: "master-codex-primary", label: "主 GPT", role: "primary", provider: "master_codex_node", displayName: "Mac 上的 Master Codex Node", nodeId: "offline-node", nodeLabel: "离线节点", model: "gpt-5.4", enabled: true, setActive: true, loginStatusNote: "通过绑定的 Master Codex Node 对话。", }); await saveAiAccount({ accountId: "aliyun-qwen-backup", label: "阿里备用", role: "backup", provider: "aliyun_qwen_api", displayName: "阿里百炼备用账号", accountIdentifier: "dashscope-demo", model: "qwen3.5-plus", apiKey: "sk-aliyun-demo-123456", enabled: true, setActive: false, loginStatusNote: "阿里百炼 Qwen 备用账号。", }); const originalFetch = globalThis.fetch; globalThis.fetch = (async (input) => { 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-aliyun-fallback", }, }); } throw new Error(`unexpected fetch: ${String(input)}`); }) as typeof fetch; try { const result = await replyToMasterAgentUserMessage({ requestMessageId: "msg-master-aliyun-fallback", requestText: "请只回复:阿里备用链路正常。", requestedBy: "Boss 超级管理员", requestedByAccount: "17600003315", }); assert.equal(result.ok, true); assert.equal(result.accountId, "aliyun-qwen-backup"); assert.equal(result.requestId, "req-master-aliyun-fallback"); const state = await readState(); const masterProject = state.projects.find((project) => project.id === "master-agent"); const reply = masterProject?.messages.at(-1); assert.ok(reply, "expected a master-agent reply to be appended"); assert.equal(reply?.sender, "master"); assert.equal(reply?.senderLabel, "主 Agent · 阿里备用"); assert.match(reply?.body ?? "", /阿里备用链路正常/); } finally { globalThis.fetch = originalFetch; } }); test("replyToMasterAgentUserMessage retries the next ready API backup when the first API backend call fails", async () => { await saveAiAccount({ accountId: "openai-primary-ready", label: "OpenAI 主控", role: "primary", provider: "openai_api", displayName: "OpenAI 主账号", model: "gpt-5.4", apiKey: "sk-openai-primary", enabled: true, setActive: true, loginStatusNote: "主 OpenAI 账号。", }); await saveAiAccount({ accountId: "aliyun-qwen-backup", label: "阿里备用", role: "backup", provider: "aliyun_qwen_api", displayName: "阿里百炼备用账号", accountIdentifier: "dashscope-demo", model: "qwen3.5-plus", apiKey: "sk-aliyun-demo-123456", enabled: true, setActive: false, loginStatusNote: "阿里百炼 Qwen 备用账号。", }); const originalFetch = globalThis.fetch; globalThis.fetch = (async (input) => { if (typeof input === "string" && input === "https://api.openai.com/v1/responses") { return new Response(JSON.stringify({ error: { message: "openai temporary 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-api-chain", }, }); } throw new Error(`unexpected fetch: ${String(input)}`); }) as typeof fetch; try { const result = await replyToMasterAgentUserMessage({ requestMessageId: "msg-master-api-chain", requestText: "请只回复:阿里备用接管成功。", requestedBy: "Boss 超级管理员", requestedByAccount: "17600003315", }); assert.equal(result.ok, true); assert.equal(result.accountId, "aliyun-qwen-backup"); assert.equal(result.requestId, "req-master-api-chain"); const state = await readState(); const openaiAccount = state.aiAccounts.find((item) => item.accountId === "openai-primary-ready"); assert.equal(openaiAccount?.status, "degraded"); const aliyunAccount = state.aiAccounts.find((item) => item.accountId === "aliyun-qwen-backup"); assert.equal(aliyunAccount?.isActive, true); const masterProject = state.projects.find((project) => project.id === "master-agent"); const reply = masterProject?.messages.at(-1); assert.ok(reply, "expected a fallback reply to be appended"); assert.match(reply?.body ?? "", /阿里备用接管成功/); } finally { globalThis.fetch = originalFetch; } }); test("replyToMasterAgentUserMessage falls back to a ready backup master node account when API backends are unavailable", async () => { await saveAiAccount({ accountId: "master-codex-primary-offline", label: "主 GPT", role: "primary", provider: "master_codex_node", displayName: "离线主节点", nodeId: "offline-node", nodeLabel: "离线节点", model: "gpt-5.4", enabled: true, setActive: true, loginStatusNote: "离线主节点。", }); await saveAiAccount({ accountId: "master-codex-backup-ready", 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 result = await replyToMasterAgentUserMessage({ requestMessageId: "msg-master-node-backup-fallback", requestText: "请切到备用主节点。", requestedBy: "Boss 超级管理员", requestedByAccount: "17600003315", mode: "enqueue", }); assert.equal(result.ok, true); assert.equal(result.accountId, "master-codex-backup-ready"); assert.ok(result.taskId, "expected a queued master-agent task"); const state = await readState(); const task = state.masterAgentTasks.find((item) => item.taskId === result.taskId); assert.ok(task, "expected queued task to be written into state"); assert.equal(task?.accountId, "master-codex-backup-ready"); assert.equal(task?.deviceId, "mac-studio"); });