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 confirmDispatchPlanRoute: (typeof import("../src/app/api/v1/projects/[projectId]/dispatch-plans/[planId]/confirm/route"))["POST"]; let completeMasterTaskRoute: (typeof import("../src/app/api/v1/master-agent/tasks/[taskId]/complete/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 = ""; async function setup() { if (runtimeRoot) { return; } runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-task5-")); process.env.BOSS_RUNTIME_ROOT = runtimeRoot; process.env.BOSS_STATE_FILE = path.join(runtimeRoot, "boss-state.json"); const [messageModule, confirmModule, completeModule, data, auth] = await Promise.all([ 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/app/api/v1/master-agent/tasks/[taskId]/complete/route.ts"), import("../src/lib/boss-data.ts"), import("../src/lib/boss-auth.ts"), ]); postMessageRoute = messageModule.POST; confirmDispatchPlanRoute = confirmModule.POST; completeMasterTaskRoute = completeModule.POST; createAuthSession = data.createAuthSession; createProjectGroupChat = data.createProjectGroupChat; isDispatchableThreadProject = data.isDispatchableThreadProject; 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: "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: JSON.stringify(body), }); } async function ensureTwoSingleThreadProjects() { const state = await readState(); const singles = state.projects.filter((project) => isDispatchableThreadProject(project)); 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: [...seed.deviceIds], 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) => isDispatchableThreadProject(project)); } async function createConfirmedDispatchExecution() { await setup(); const memberProjects = await ensureTwoSingleThreadProjects(); const groupProject = await createProjectGroupChat({ sourceProjectId: memberProjects[0].id, memberProjectIds: [memberProjects[1].id], createdBy: "17600003315", }); const messageResponse = 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(messageResponse.status, 200); const messagePayload = (await messageResponse.json()) as { dispatchPlan: { planId: string; targets: Array<{ projectId: string }> } | null; }; assert.ok(messagePayload.dispatchPlan, "expected seeded dispatch plan"); const approvedTargetProjectId = messagePayload.dispatchPlan.targets[0]?.projectId; assert.ok(approvedTargetProjectId, "expected approved target"); const confirmResponse = await confirmDispatchPlanRoute( await createAuthedRequest( `http://127.0.0.1:3000/api/v1/projects/${groupProject.id}/dispatch-plans/${messagePayload.dispatchPlan.planId}/confirm`, "POST", { approvedTargetProjectIds: [approvedTargetProjectId] }, ), { params: Promise.resolve({ projectId: groupProject.id, planId: messagePayload.dispatchPlan.planId, }), }, ); assert.equal(confirmResponse.status, 200); const state = await readState(); const execution = state.dispatchExecutions.find( (item) => item.planId === messagePayload.dispatchPlan?.planId && item.targetProjectId === approvedTargetProjectId, ); assert.ok(execution, "expected queued dispatch execution"); const executionTask = state.masterAgentTasks.find( (task) => task.taskType === "dispatch_execution" && task.projectId === groupProject.id && task.requestMessageId === messagePayload.dispatchPlan?.planId, ); assert.ok(executionTask, "expected a queued dispatch execution master-agent task"); return { groupProject, execution, executionTask }; } test("POST /api/v1/master-agent/tasks/[taskId]/complete mirrors raw thread replies to the group chat and appends a master-agent summary", async () => { const { groupProject, execution, executionTask } = await createConfirmedDispatchExecution(); const response = await completeMasterTaskRoute( await createAuthedRequest( `http://127.0.0.1:3000/api/v1/master-agent/tasks/${executionTask.taskId}/complete`, "POST", { deviceId: execution.deviceId, status: "completed", dispatchExecutionId: execution.executionId, targetProjectId: execution.targetProjectId, targetThreadId: execution.targetThreadId, rawThreadReply: "线程A已经完成阻塞点整理,待你确认最终回滚窗口。", replyBody: "主 Agent 汇总:线程A已返回阻塞点整理,下一步建议安排回滚窗口确认。", }, ), { params: Promise.resolve({ taskId: executionTask.taskId }) }, ); assert.equal(response.status, 200); const nextState = await readState(); const completedExecution = nextState.dispatchExecutions.find( (item) => item.executionId === execution.executionId, ); assert.equal(completedExecution?.status, "completed"); assert.ok(completedExecution?.resultMessageId, "expected raw result message id to be recorded"); const groupMessages = nextState.projects.find((project) => project.id === groupProject.id)?.messages ?? []; const mirroredDeviceReply = groupMessages.find( (message) => message.sender === "device" && message.body.includes("线程A已经完成阻塞点整理"), ); assert.ok(mirroredDeviceReply, "expected raw thread reply to be mirrored back to the group chat"); const masterSummary = groupMessages.find( (message) => message.sender === "master" && message.body.includes("主 Agent 汇总:线程A已返回阻塞点整理"), ); assert.ok(masterSummary, "expected master-agent summary to be appended after the raw thread reply"); }); test("POST /api/v1/master-agent/tasks/[taskId]/complete is idempotent for repeated dispatch execution completions", async () => { const { groupProject, execution, executionTask } = await createConfirmedDispatchExecution(); const completionBody = { deviceId: execution.deviceId, status: "completed" as const, dispatchExecutionId: execution.executionId, targetProjectId: execution.targetProjectId, targetThreadId: execution.targetThreadId, rawThreadReply: "线程A已经完成阻塞点整理,待你确认最终回滚窗口。", replyBody: "主 Agent 汇总:线程A已返回阻塞点整理,下一步建议安排回滚窗口确认。", }; const firstResponse = await completeMasterTaskRoute( await createAuthedRequest( `http://127.0.0.1:3000/api/v1/master-agent/tasks/${executionTask.taskId}/complete`, "POST", completionBody, ), { params: Promise.resolve({ taskId: executionTask.taskId }) }, ); assert.equal(firstResponse.status, 200); const secondResponse = await completeMasterTaskRoute( await createAuthedRequest( `http://127.0.0.1:3000/api/v1/master-agent/tasks/${executionTask.taskId}/complete`, "POST", completionBody, ), { params: Promise.resolve({ taskId: executionTask.taskId }) }, ); assert.equal(secondResponse.status, 200); const nextState = await readState(); const groupMessages = nextState.projects.find((project) => project.id === groupProject.id)?.messages ?? []; const mirroredReplies = groupMessages.filter( (message) => message.sender === "device" && message.body.includes("线程A已经完成阻塞点整理"), ); const masterSummaries = groupMessages.filter( (message) => message.sender === "master" && message.body.includes("主 Agent 汇总:线程A已返回阻塞点整理"), ); assert.equal(mirroredReplies.length, 1); assert.equal(masterSummaries.length, 1); });