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; } const buildSingleThreadProject = (projectId: string, threadDisplayName: string) => ({ id: projectId, name: threadDisplayName, pinned: false, systemPinned: false, deviceIds: ["mac-studio"], preview: `${threadDisplayName} 等待主 Agent 汇总阻塞点。`, updatedAt: "2026-03-30T10:00:00+08:00", lastMessageAt: "2026-03-30T10:00:00+08:00", isGroup: false, threadMeta: { projectId, threadId: `${projectId}-thread`, threadDisplayName, folderName: "阻塞梳理", activityIconCount: 0, updatedAt: "2026-03-30T10:00:00+08:00", codexThreadRef: `${projectId}-thread`, codexFolderRef: `/Users/kris/code/${projectId}`, }, groupMembers: [], messages: [ { id: `msg-${projectId}`, sender: "device" as const, senderLabel: "Win GPU / Codex", body: "这里还在等待视觉链路复核。", sentAt: "2026-03-30T10:00:00+08:00", kind: "text" as const, }, ], goals: [], versions: [], createdByAgent: true, collaborationMode: "development" as const, approvalState: "not_required" as const, unreadCount: 0, riskLevel: "low" as const, }); const missingProjects = [ !singles[0] ? buildSingleThreadProject("dispatch-thread-a", "北区试产线回归") : null, !singles[1] ? buildSingleThreadProject("dispatch-thread-b", "南区试产线回归") : null, ].filter(Boolean); await writeState({ ...state, projects: [...state.projects, ...missingProjects], }); 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"); assert.ok(executionTask?.executionPrompt?.includes("请主 Agent 推荐要先同步的线程")); assert.ok(executionTask?.executionPrompt?.includes(executionTask?.targetThreadDisplayName ?? "")); assert.ok(!executionTask?.executionPrompt?.includes("groupProjectId:"), "dispatch prompt should not include group project id labels"); assert.ok(!executionTask?.executionPrompt?.includes("threadProjectId:"), "dispatch prompt should not include thread project id labels"); assert.ok(!executionTask?.executionPrompt?.includes("threadId:"), "dispatch prompt should not include raw thread id labels"); assert.ok(!executionTask?.executionPrompt?.includes("folderName:"), "dispatch prompt should not include folder labels"); 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"); const targetThreadMessages = nextState.projects.find((project) => project.id === execution.targetProjectId)?.messages ?? []; const mirroredThreadReply = targetThreadMessages.find( (message) => message.sender === "device" && message.body.includes("线程A已经完成阻塞点整理"), ); assert.ok( mirroredThreadReply, "expected raw thread reply to also be mirrored back to the target single-thread conversation", ); }); 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); }); test("POST /api/v1/master-agent/tasks/[taskId]/complete blocks leaked thread environment diagnostics from group dispatch results", 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: "我不能直接把当前会话环境从只读改回可写。cwd 我可以在命令里指向 /Users/kris/code/gptpluscontrol,但现在真正卡住的是只读权限。", }, ), { params: Promise.resolve({ taskId: executionTask.taskId }) }, ); assert.equal(response.status, 200); const nextState = await readState(); const groupMessages = nextState.projects.find((project) => project.id === groupProject.id)?.messages ?? []; const leakedReply = groupMessages.find((message) => message.body.includes("当前会话环境从只读改回可写"), ); assert.equal(leakedReply, undefined); const opsNotice = groupMessages.find((message) => message.body.includes("线程环境异常,请重新绑定到正确项目或工作目录后再试。"), ); assert.ok(opsNotice, "expected a system notice instead of raw leaked diagnostics"); });