import test from "node:test"; import assert from "node:assert/strict"; import os from "node:os"; import path from "node:path"; import { mkdtemp, rm } from "node:fs/promises"; import { NextRequest } from "next/server"; let runtimeRoot = ""; let getRoute: (typeof import("../src/app/api/v1/projects/[projectId]/orchestration-backend/route"))["GET"]; let patchRoute: (typeof import("../src/app/api/v1/projects/[projectId]/orchestration-backend/route"))["PATCH"]; let postMessageRoute: (typeof import("../src/app/api/v1/projects/[projectId]/messages/route"))["POST"]; let confirmDispatchPlanRoute: (typeof import("../src/app/api/v1/projects/[projectId]/dispatch-plans/[planId]/confirm/route"))["POST"]; let createAuthSession: (typeof import("../src/lib/boss-data"))["createAuthSession"]; let createProjectGroupChat: (typeof import("../src/lib/boss-data"))["createProjectGroupChat"]; let isDispatchableThreadProject: (typeof import("../src/lib/boss-data"))["isDispatchableThreadProject"]; let readState: (typeof import("../src/lib/boss-data"))["readState"]; let writeState: (typeof import("../src/lib/boss-data"))["writeState"]; let AUTH_SESSION_COOKIE = ""; let baseState: Awaited>; const originalEnv = { BOSS_OMX_ENABLED: process.env.BOSS_OMX_ENABLED, BOSS_OMX_COMMAND: process.env.BOSS_OMX_COMMAND, BOSS_OMX_ARGS: process.env.BOSS_OMX_ARGS, BOSS_OMX_WORKDIR: process.env.BOSS_OMX_WORKDIR, BOSS_OMX_TIMEOUT_MS: process.env.BOSS_OMX_TIMEOUT_MS, }; function buildDispatchableProject(id: string, name: string, threadId: string) { return { id, name, pinned: false, systemPinned: false, deviceIds: ["mac-studio"], preview: "等待群聊下发。", updatedAt: "2026-04-03T10:00:00+08:00", lastMessageAt: "2026-04-03T10:00:00+08:00", isGroup: false, unreadCount: 0, riskLevel: "low" as const, contextBudgetPct: 80, contextBudgetLabel: "80%", threadMeta: { projectId: id, threadId, threadDisplayName: name, folderName: "boss", activityIconCount: 0, updatedAt: "2026-04-03T10:00:00+08:00", codexFolderRef: "/Users/kris/code/boss", codexThreadRef: threadId, }, groupMembers: [], messages: [ { id: `msg-${id}`, sender: "device" as const, senderLabel: "Mac Studio / Codex", body: "等待群聊下发。", sentAt: "2026-04-03T10:00:00+08:00", kind: "text" as const, }, ], goals: [], versions: [], createdByAgent: true, collaborationMode: "development" as const, approvalState: "not_required" as const, lightDispatchReminderEnabled: false, }; } async function setup() { if (runtimeRoot) return; runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-omx-route-")); process.env.BOSS_RUNTIME_ROOT = runtimeRoot; process.env.BOSS_STATE_FILE = path.join(runtimeRoot, "boss-state.json"); const [orchestrationRoute, messageRoute, confirmRoute, data, auth] = await Promise.all([ import("../src/app/api/v1/projects/[projectId]/orchestration-backend/route.ts"), import("../src/app/api/v1/projects/[projectId]/messages/route.ts"), import("../src/app/api/v1/projects/[projectId]/dispatch-plans/[planId]/confirm/route.ts"), import("../src/lib/boss-data.ts"), import("../src/lib/boss-auth.ts"), ]); getRoute = orchestrationRoute.GET; patchRoute = orchestrationRoute.PATCH; postMessageRoute = messageRoute.POST; confirmDispatchPlanRoute = confirmRoute.POST; createAuthSession = data.createAuthSession; createProjectGroupChat = data.createProjectGroupChat; isDispatchableThreadProject = data.isDispatchableThreadProject; readState = data.readState; writeState = data.writeState; baseState = structuredClone(await readState()); AUTH_SESSION_COOKIE = auth.AUTH_SESSION_COOKIE; } async function authedRequest(url: string, method: "GET" | "PATCH" | "POST", body?: unknown) { const session = await createAuthSession({ account: "krisolo", role: "highest_admin", displayName: "Boss 超级管理员", loginMethod: "password", }); return new NextRequest(url, { method, headers: { "content-type": "application/json", cookie: `${AUTH_SESSION_COOKIE}=${session.sessionToken}`, }, body: body ? JSON.stringify(body) : undefined, }); } async function ensureTwoSingleThreadProjects() { const state = await readState(); const singles = state.projects.filter((project) => isDispatchableThreadProject(project)); if (singles.length >= 2) { return singles; } const seeded = [ buildDispatchableProject("omx-thread-a", "Boss OMX 主线程", "thread-omx-a"), buildDispatchableProject("omx-thread-b", "Boss OMX 副线程", "thread-omx-b"), ].slice(singles.length); await writeState({ ...state, projects: [...state.projects, ...seeded], }); const nextState = await readState(); return nextState.projects.filter((project) => isDispatchableThreadProject(project)); } function configureOmxAvailable() { process.env.BOSS_OMX_ENABLED = "true"; process.env.BOSS_OMX_COMMAND = process.execPath; process.env.BOSS_OMX_ARGS = "/Users/kris/code/boss/scripts/omx-team-smoke.mjs"; process.env.BOSS_OMX_WORKDIR = "/Users/kris/code/boss"; process.env.BOSS_OMX_TIMEOUT_MS = "45000"; } function restoreOmxEnv() { process.env.BOSS_OMX_ENABLED = originalEnv.BOSS_OMX_ENABLED; process.env.BOSS_OMX_COMMAND = originalEnv.BOSS_OMX_COMMAND; process.env.BOSS_OMX_ARGS = originalEnv.BOSS_OMX_ARGS; process.env.BOSS_OMX_WORKDIR = originalEnv.BOSS_OMX_WORKDIR; process.env.BOSS_OMX_TIMEOUT_MS = originalEnv.BOSS_OMX_TIMEOUT_MS; } test.beforeEach(async () => { await setup(); restoreOmxEnv(); await writeState(structuredClone(baseState)); }); test.after(async () => { restoreOmxEnv(); if (runtimeRoot) { await rm(runtimeRoot, { recursive: true, force: true }); } }); test("GET orchestration backend returns null requested backend for default group chats", async () => { const singles = await ensureTwoSingleThreadProjects(); const group = await createProjectGroupChat({ sourceProjectId: singles[0].id, memberProjectIds: [singles[1].id], createdBy: "krisolo", }); const response = await getRoute( await authedRequest(`http://127.0.0.1:3000/api/v1/projects/${group.id}/orchestration-backend`, "GET"), { params: Promise.resolve({ projectId: group.id }) }, ); assert.equal(response.status, 200); const payload = (await response.json()) as { ok: boolean; requestedBackendId: string | null; currentBackendId: string; availableChoices: Array<{ backendId: string; current: boolean }>; }; assert.equal(payload.ok, true); assert.equal(payload.requestedBackendId, null); assert.equal(payload.currentBackendId, "boss-native-orchestrator"); assert.equal(payload.availableChoices[0]?.backendId, "boss-native-orchestrator"); assert.equal(payload.availableChoices[0]?.current, true); }); test("PATCH orchestration backend rejects omx when runtime is unavailable", async () => { const singles = await ensureTwoSingleThreadProjects(); const group = await createProjectGroupChat({ sourceProjectId: singles[0].id, memberProjectIds: [singles[1].id], createdBy: "krisolo", }); const response = await patchRoute( await authedRequest( `http://127.0.0.1:3000/api/v1/projects/${group.id}/orchestration-backend`, "PATCH", { requestedBackendId: "omx-team" }, ), { params: Promise.resolve({ projectId: group.id }) }, ); assert.equal(response.status, 200); const payload = (await response.json()) as { ok: boolean; requestedBackendId: string; currentBackendId: string; omxAvailability: { selectable: boolean; reasonLabel: string }; }; assert.equal(payload.ok, true); assert.equal(payload.requestedBackendId, "omx-team"); assert.equal(payload.currentBackendId, "boss-native-orchestrator"); assert.equal(payload.omxAvailability.selectable, false); assert.equal(payload.omxAvailability.reasonLabel, "OMX Team Runtime 当前未启用。"); }); test("group dispatch plans and executions carry omx backend when selected and available", async () => { configureOmxAvailable(); const singles = await ensureTwoSingleThreadProjects(); const group = await createProjectGroupChat({ sourceProjectId: singles[0].id, memberProjectIds: [singles[1].id], createdBy: "krisolo", }); const saveResponse = await patchRoute( await authedRequest( `http://127.0.0.1:3000/api/v1/projects/${group.id}/orchestration-backend`, "PATCH", { requestedBackendId: "omx-team" }, ), { params: Promise.resolve({ projectId: group.id }) }, ); assert.equal(saveResponse.status, 200); const postResponse = await postMessageRoute( await authedRequest( `http://127.0.0.1:3000/api/v1/projects/${group.id}/messages`, "POST", { body: "请大家汇总一下今天的 OMX 联调阻塞点" }, ), { params: Promise.resolve({ projectId: group.id }) }, ); assert.equal(postResponse.status, 200); const postPayload = (await postResponse.json()) as { ok: boolean; dispatchPlan: null | { planId: string; orchestrationBackendId?: string; orchestrationBackendLabel?: string; }; }; assert.equal(postPayload.ok, true); assert.ok(postPayload.dispatchPlan, "expected dispatch plan"); assert.equal(postPayload.dispatchPlan?.orchestrationBackendId, "omx-team"); assert.equal(postPayload.dispatchPlan?.orchestrationBackendLabel, "OMX Team Runtime"); const confirmResponse = await confirmDispatchPlanRoute( await authedRequest( `http://127.0.0.1:3000/api/v1/projects/${group.id}/dispatch-plans/${postPayload.dispatchPlan?.planId}/confirm`, "POST", { approvedTargetProjectIds: [singles[0].id, singles[1].id] }, ), { params: Promise.resolve({ projectId: group.id, planId: postPayload.dispatchPlan?.planId ?? "" }) }, ); assert.equal(confirmResponse.status, 200); const confirmPayload = (await confirmResponse.json()) as { ok: boolean; plan: { orchestrationBackendId?: string }; executions: Array<{ orchestrationBackendId?: string; orchestrationBackendLabel?: string }>; }; assert.equal(confirmPayload.ok, true); assert.equal(confirmPayload.plan.orchestrationBackendId, "omx-team"); assert.ok(confirmPayload.executions.length > 0); assert.ok(confirmPayload.executions.every((item) => item.orchestrationBackendId === "omx-team")); assert.ok(confirmPayload.executions.every((item) => item.orchestrationBackendLabel === "OMX Team Runtime")); const nextState = await readState(); const queuedTasks = nextState.masterAgentTasks.filter( (task) => task.taskType === "dispatch_execution" && task.projectId === group.id, ); assert.ok( queuedTasks.some((task) => task.orchestrationBackendId === "omx-team"), "expected dispatch execution tasks to carry omx metadata", ); });