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 rejectDispatchPlanRoute: (typeof import("../src/app/api/v1/projects/[projectId]/dispatch-plans/[planId]/reject/route"))["POST"]; let retryDispatchPlanRoute: (typeof import("../src/app/api/v1/projects/[projectId]/dispatch-plans/[planId]/retry/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-task4-")); process.env.BOSS_RUNTIME_ROOT = runtimeRoot; process.env.BOSS_STATE_FILE = path.join(runtimeRoot, "boss-state.json"); const [messageModule, plansModule, confirmModule, rejectModule, retryModule, 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/app/api/v1/projects/[projectId]/dispatch-plans/[planId]/reject/route.ts"), import("../src/app/api/v1/projects/[projectId]/dispatch-plans/[planId]/retry/route.ts"), import("../src/lib/boss-data.ts"), import("../src/lib/boss-auth.ts"), ]); postMessageRoute = messageModule.POST; getDispatchPlansRoute = plansModule.GET; confirmDispatchPlanRoute = confirmModule.POST; rejectDispatchPlanRoute = rejectModule.POST; retryDispatchPlanRoute = retryModule.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: "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) => 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 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 }>; notice: { kind: string; body: string } | null; collaborationGate: { isGroup: boolean; collaborationMode: string; requiresMasterAgentApproval: boolean; approvalState: 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"); assert.ok(payload.notice, "expected a confirmation notice in the response"); assert.equal(payload.notice?.kind, "system_notice"); assert.equal(payload.notice?.body, "已确认下发到 1 个线程:《北区试产线回归》。"); assert.equal(payload.collaborationGate.isGroup, true); assert.equal(payload.collaborationGate.collaborationMode, "development"); assert.equal(payload.collaborationGate.requiresMasterAgentApproval, false); assert.equal(payload.collaborationGate.approvalState, "not_required"); 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"); }); test("confirming a dispatch plan marks approval_required groups as approved", async () => { const { groupProject, dispatchPlan } = await createDispatchPlanForTest(); const approvedTargetProjectId = dispatchPlan.targets[0]?.projectId; assert.ok(approvedTargetProjectId, "expected a recommended target project"); const state = await readState(); await writeState({ ...state, projects: state.projects.map((project) => project.id === groupProject.id ? { ...project, collaborationMode: "approval_required" as const, approvalState: "pending_user" as const, } : 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 nextState = await readState(); const nextGroupProject = nextState.projects.find((project) => project.id === groupProject.id); assert.ok(nextGroupProject, "expected group project to remain present"); assert.equal(nextGroupProject?.approvalState, "approved"); }); test("rejecting a dispatch plan marks approval_required groups as rejected and writes a system notice", async () => { const { groupProject, dispatchPlan } = await createDispatchPlanForTest(); const state = await readState(); await writeState({ ...state, projects: state.projects.map((project) => project.id === groupProject.id ? { ...project, collaborationMode: "approval_required" as const, approvalState: "pending_user" as const, } : project, ), }); const response = await rejectDispatchPlanRoute( await createAuthedRequest( `http://127.0.0.1:3000/api/v1/projects/${groupProject.id}/dispatch-plans/${dispatchPlan.planId}/reject`, "POST", {}, ), { 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 }; notice: { kind: string; body: string }; collaborationGate: { isGroup: boolean; collaborationMode: string; requiresMasterAgentApproval: boolean; approvalState: string; }; }; assert.equal(payload.ok, true); assert.equal(payload.plan.planId, dispatchPlan.planId); assert.equal(payload.plan.status, "rejected"); assert.equal(payload.notice.kind, "system_notice"); assert.equal(payload.notice.body, "已拒绝主 Agent 推荐,本次不会下发到任何线程。"); assert.equal(payload.collaborationGate.isGroup, true); assert.equal(payload.collaborationGate.collaborationMode, "approval_required"); assert.equal(payload.collaborationGate.requiresMasterAgentApproval, true); assert.equal(payload.collaborationGate.approvalState, "rejected"); const nextState = await readState(); const nextGroupProject = nextState.projects.find((project) => project.id === groupProject.id); assert.ok(nextGroupProject, "expected group project to remain present"); assert.equal(nextGroupProject?.approvalState, "rejected"); const notice = nextGroupProject?.messages.find( (message) => message.kind === "system_notice" && message.body.includes("已拒绝主 Agent 推荐"), ); assert.ok(notice, "expected rejection notice in group chat"); }); test("retrying a rejected dispatch plan creates a fresh pending recommendation and resets approval gate", async () => { const { groupProject, dispatchPlan } = await createDispatchPlanForTest(); const state = await readState(); await writeState({ ...state, projects: state.projects.map((project) => project.id === groupProject.id ? { ...project, collaborationMode: "approval_required" as const, approvalState: "pending_user" as const, } : project, ), }); const rejectResponse = await rejectDispatchPlanRoute( await createAuthedRequest( `http://127.0.0.1:3000/api/v1/projects/${groupProject.id}/dispatch-plans/${dispatchPlan.planId}/reject`, "POST", {}, ), { params: Promise.resolve({ projectId: groupProject.id, planId: dispatchPlan.planId }) }, ); assert.equal(rejectResponse.status, 200); const retryResponse = await retryDispatchPlanRoute( await createAuthedRequest( `http://127.0.0.1:3000/api/v1/projects/${groupProject.id}/dispatch-plans/${dispatchPlan.planId}/retry`, "POST", {}, ), { params: Promise.resolve({ projectId: groupProject.id, planId: dispatchPlan.planId }) }, ); assert.equal(retryResponse.status, 200); const retryPayload = (await retryResponse.json()) as { ok: boolean; dispatchPlan: { planId: string; status: string; requestMessageId: string } | null; collaborationGate: { approvalState: string; requiresMasterAgentApproval: boolean; collaborationMode: string; }; }; assert.equal(retryPayload.ok, true); assert.ok(retryPayload.dispatchPlan, "expected a fresh dispatch recommendation"); assert.notEqual(retryPayload.dispatchPlan?.planId, dispatchPlan.planId); assert.equal(retryPayload.dispatchPlan?.status, "pending_user_confirmation"); assert.match(retryPayload.dispatchPlan?.requestMessageId ?? "", /:retry:/); assert.equal(retryPayload.collaborationGate.collaborationMode, "approval_required"); assert.equal(retryPayload.collaborationGate.requiresMasterAgentApproval, true); assert.equal(retryPayload.collaborationGate.approvalState, "pending_user"); const nextState = await readState(); const refreshedPlan = nextState.dispatchPlans.find((plan) => plan.planId === retryPayload.dispatchPlan?.planId); assert.ok(refreshedPlan, "expected retried dispatch plan in state"); const nextGroupProject = nextState.projects.find((project) => project.id === groupProject.id); assert.equal(nextGroupProject?.approvalState, "pending_user"); });