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 completeMasterTaskRoute: (typeof import("../src/app/api/v1/master-agent/tasks/[taskId]/complete/route"))["POST"]; let createAuthSession: (typeof import("../src/lib/boss-data"))["createAuthSession"]; let readState: (typeof import("../src/lib/boss-data"))["readState"]; let AUTH_SESSION_COOKIE = ""; async function setup() { if (runtimeRoot) { return; } runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-single-thread-message-")); process.env.BOSS_RUNTIME_ROOT = runtimeRoot; process.env.BOSS_STATE_FILE = path.join(runtimeRoot, "boss-state.json"); const [messageModule, completeModule, data, auth] = await Promise.all([ import("../src/app/api/v1/projects/[projectId]/messages/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; completeMasterTaskRoute = completeModule.POST; createAuthSession = data.createAuthSession; readState = data.readState; 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), }); } function findSingleThreadProject( state: Awaited>, ) { return state.projects.find((project) => project.id !== "master-agent" && !project.isGroup); } test("POST /api/v1/projects/[projectId]/messages enqueues a conversation task for single-thread projects", async () => { await setup(); const state = await readState(); const singleProject = findSingleThreadProject(state); assert.ok(singleProject, "expected a seeded single-thread project"); const response = await postMessageRoute( await createAuthedRequest( `http://127.0.0.1:3000/api/v1/projects/${singleProject.id}/messages`, "POST", { body: "请同步一下当前阻塞情况" }, ), { params: Promise.resolve({ projectId: singleProject.id }) }, ); assert.equal(response.status, 200); const payload = (await response.json()) as { ok: boolean; task?: { taskId: string; taskType: string; status: string } | null; dispatchPlan: null; }; assert.equal(payload.ok, true); assert.equal(payload.dispatchPlan, null); assert.ok(payload.task, "expected single-thread message to return a queued task"); assert.equal(payload.task?.taskType, "conversation_reply"); assert.equal(payload.task?.status, "queued"); const nextState = await readState(); const task = nextState.masterAgentTasks.find( (item) => item.taskType === "conversation_reply" && item.projectId === singleProject.id && item.requestText === "请同步一下当前阻塞情况", ); assert.ok(task, "expected a queued conversation_reply task for the single-thread project"); assert.equal(task?.targetProjectId, singleProject.id); assert.equal(task?.targetThreadId, singleProject.threadMeta.threadId); assert.ok(task?.executionPrompt?.includes("请同步一下当前阻塞情况")); assert.ok(task?.executionPrompt?.includes(singleProject.threadMeta.threadDisplayName)); assert.ok(!task?.executionPrompt?.includes("threadProjectId:"), "thread prompt should not include project id labels"); assert.ok(!task?.executionPrompt?.includes("folderName:"), "thread prompt should not include folder labels"); assert.ok(!task?.executionPrompt?.includes("deviceIds:"), "thread prompt should not include device id labels"); }); test("POST /api/v1/master-agent/tasks/[taskId]/complete writes the raw thread reply back to the single-thread project", async () => { await setup(); const state = await readState(); const singleProject = findSingleThreadProject(state); assert.ok(singleProject, "expected a seeded single-thread project"); await postMessageRoute( await createAuthedRequest( `http://127.0.0.1:3000/api/v1/projects/${singleProject.id}/messages`, "POST", { body: "请同步一下当前阻塞情况" }, ), { params: Promise.resolve({ projectId: singleProject.id }) }, ); const queuedState = await readState(); const task = queuedState.masterAgentTasks.find( (item) => item.taskType === "conversation_reply" && item.projectId === singleProject.id && item.targetProjectId === singleProject.id, ); assert.ok(task, "expected a queued conversation_reply task"); const response = await completeMasterTaskRoute( await createAuthedRequest( `http://127.0.0.1:3000/api/v1/master-agent/tasks/${task.taskId}/complete`, "POST", { deviceId: task.deviceId, status: "completed", targetProjectId: singleProject.id, targetThreadId: singleProject.threadMeta.threadId, replyBody: "当前阻塞点已经同步:视觉验收待今晚回归。", }, ), { params: Promise.resolve({ taskId: task.taskId }) }, ); assert.equal(response.status, 200); const nextState = await readState(); const updatedProject = nextState.projects.find((project) => project.id === singleProject.id); const mirroredReply = updatedProject?.messages.find((message) => message.body.includes("当前阻塞点已经同步:视觉验收待今晚回归。"), ); assert.ok(mirroredReply, "expected single-thread reply to be written back to the project"); assert.equal(mirroredReply?.sender, "device"); });