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 postMessageRoute: (typeof import("../src/app/api/v1/projects/[projectId]/messages/route"))["POST"]; let getDispatchPlansRoute: (typeof import("../src/app/api/v1/projects/[projectId]/dispatch-plans/route"))["GET"]; 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 readState: (typeof import("../src/lib/boss-data"))["readState"]; let writeState: (typeof import("../src/lib/boss-data"))["writeState"]; let AUTH_SESSION_COOKIE = ""; async function setup() { if (runtimeRoot) { return; } runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-task4-")); process.env.BOSS_RUNTIME_ROOT = runtimeRoot; process.env.BOSS_STATE_FILE = path.join(runtimeRoot, "boss-state.json"); const [messageModule, plansModule, confirmModule, data, auth] = await Promise.all([ import("../src/app/api/v1/projects/[projectId]/messages/route.ts"), import("../src/app/api/v1/projects/[projectId]/dispatch-plans/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"), ]); postMessageRoute = messageModule.POST; getDispatchPlansRoute = plansModule.GET; confirmDispatchPlanRoute = confirmModule.POST; createAuthSession = data.createAuthSession; createProjectGroupChat = data.createProjectGroupChat; readState = data.readState; writeState = data.writeState; AUTH_SESSION_COOKIE = auth.AUTH_SESSION_COOKIE; } test.after(async () => { if (runtimeRoot) { await rm(runtimeRoot, { recursive: true, force: true }); } }); async function createAuthedRequest(url: string, method: "GET" | "POST", body?: unknown) { const session = await createAuthSession({ account: "17600003315", 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) => project.id !== "master-agent" && !project.isGroup); if (singles.length >= 2) { return singles; } assert.ok(singles[0], "expected at least one seeded single-thread project"); const seed = singles[0]; const clonedProject = { ...seed, id: "boss-console-clone", name: "Boss 移动控制台副线程", deviceIds: ["win-gpu-01"], updatedAt: "2026-03-30T10:00:00+08:00", lastMessageAt: "2026-03-30T10:00:00+08:00", preview: "副线程等待主 Agent 汇总阻塞点。", threadMeta: { ...seed.threadMeta, projectId: "boss-console-clone", threadId: "thread-boss-ui-clone", threadDisplayName: "南区试产线回归", folderName: "阻塞梳理", updatedAt: "2026-03-30T10:00:00+08:00", codexThreadRef: "thread-boss-ui-clone", codexFolderRef: "boss-console-clone", }, groupMembers: [], messages: [ { id: "msg-boss-console-clone", sender: "device" as const, senderLabel: "Win GPU / Codex", body: "这里还在等待视觉链路复核。", sentAt: "2026-03-30T10:00:00+08:00", kind: "text" as const, }, ], goals: [], versions: [], }; await writeState({ ...state, projects: [...state.projects, clonedProject], }); const nextState = await readState(); return nextState.projects.filter((project) => project.id !== "master-agent" && !project.isGroup); } async function createDispatchPlanForTest() { await setup(); const memberProjects = await ensureTwoSingleThreadProjects(); const groupProject = await createProjectGroupChat({ sourceProjectId: memberProjects[0].id, memberProjectIds: [memberProjects[1].id], createdBy: "17600003315", }); const response = await postMessageRoute( await createAuthedRequest( `http://127.0.0.1:3000/api/v1/projects/${groupProject.id}/messages`, "POST", { body: "请主 Agent 推荐要先同步的线程" }, ), { params: Promise.resolve({ projectId: groupProject.id }) }, ); assert.equal(response.status, 200); const payload = (await response.json()) as { dispatchPlan: { planId: string; targets: Array<{ projectId: string }> } | null; }; assert.ok(payload.dispatchPlan, "expected seeded dispatch plan"); return { groupProject, dispatchPlan: payload.dispatchPlan }; } test("GET /api/v1/projects/[projectId]/dispatch-plans lists the latest group dispatch plans", async () => { const { groupProject, dispatchPlan } = await createDispatchPlanForTest(); const response = await getDispatchPlansRoute( await createAuthedRequest( `http://127.0.0.1:3000/api/v1/projects/${groupProject.id}/dispatch-plans`, "GET", ), { params: Promise.resolve({ projectId: groupProject.id }) }, ); assert.equal(response.status, 200); const payload = (await response.json()) as { ok: boolean; plans: Array<{ planId: string; requestMessageId: string; status: string }>; }; assert.equal(payload.ok, true); assert.equal(payload.plans[0]?.planId, dispatchPlan.planId); assert.equal(payload.plans[0]?.status, "pending_user_confirmation"); }); test("POST /api/v1/projects/[projectId]/dispatch-plans/[planId]/confirm confirms targets, creates executions, and writes a master-agent notice", async () => { const { groupProject, dispatchPlan } = await createDispatchPlanForTest(); const approvedTargetProjectId = dispatchPlan.targets[0]?.projectId; assert.ok(approvedTargetProjectId, "expected a recommended target project"); const response = await confirmDispatchPlanRoute( await createAuthedRequest( `http://127.0.0.1:3000/api/v1/projects/${groupProject.id}/dispatch-plans/${dispatchPlan.planId}/confirm`, "POST", { approvedTargetProjectIds: [approvedTargetProjectId] }, ), { params: Promise.resolve({ projectId: groupProject.id, planId: dispatchPlan.planId }) }, ); assert.equal(response.status, 200); const payload = (await response.json()) as { ok: boolean; plan: { planId: string; status: string; confirmedTargetProjectIds: string[] }; executions: Array<{ planId: string; targetProjectId: string; status: string }>; }; assert.equal(payload.ok, true); assert.equal(payload.plan.planId, dispatchPlan.planId); assert.equal(payload.plan.status, "dispatched"); assert.deepEqual(payload.plan.confirmedTargetProjectIds, [approvedTargetProjectId]); assert.equal(payload.executions.length, 1); assert.equal(payload.executions[0]?.planId, dispatchPlan.planId); assert.equal(payload.executions[0]?.targetProjectId, approvedTargetProjectId); assert.equal(payload.executions[0]?.status, "queued"); const nextState = await readState(); const notice = nextState.projects .find((project) => project.id === groupProject.id) ?.messages.find( (message) => message.sender === "master" && message.kind === "system_notice" && message.body.includes("已确认下发到 1 个线程"), ); assert.ok(notice, "expected a master-agent notice in the group chat after confirmation"); });