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 data: typeof import("../src/lib/boss-data.ts"); let postProgress: (typeof import("../src/app/api/v1/master-agent/tasks/[taskId]/progress/route.ts"))["POST"]; async function setup() { if (runtimeRoot) return; runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-master-task-progress-route-")); process.env.BOSS_RUNTIME_ROOT = runtimeRoot; process.env.BOSS_STATE_FILE = path.join(runtimeRoot, "boss-state.json"); const [dataModule, routeModule] = await Promise.all([ import("../src/lib/boss-data.ts"), import("../src/app/api/v1/master-agent/tasks/[taskId]/progress/route.ts"), ]); data = dataModule; postProgress = routeModule.POST; } test.beforeEach(async () => { await setup(); await rm(runtimeRoot, { recursive: true, force: true }); }); test.after(async () => { if (runtimeRoot) await rm(runtimeRoot, { recursive: true, force: true }); }); test("POST task progress accepts device-token updates and keeps task running", async () => { const task = await data.queueMasterAgentTask({ taskId: "route-progress-task", projectId: "group-progress-test", taskType: "dispatch_execution", requestMessageId: "msg-route-progress", requestText: "让目标线程继续开发", executionPrompt: "让目标线程继续开发", requestedBy: "krisolo", requestedByAccount: "krisolo", deviceId: "mac-studio", targetProjectId: "master-agent", targetThreadId: "master-agent-thread", }); await data.claimNextMasterAgentTask("mac-studio"); const response = await postProgress( new NextRequest(`http://127.0.0.1:3000/api/v1/master-agent/tasks/${task.taskId}/progress`, { method: "POST", headers: { "content-type": "application/json", "x-boss-device-token": "boss-mac-studio-token", }, body: JSON.stringify({ deviceId: "mac-studio", status: "running", executionProgress: { steps: [ { text: "连接 Codex App Server", status: "done" }, { text: "启动目标线程 turn", status: "running" }, ], branch: { additions: 12, deletions: 1 }, }, }), }), { params: Promise.resolve({ taskId: task.taskId }) }, ); assert.equal(response.status, 200); const payload = await response.json(); assert.equal(payload.ok, true); assert.equal(payload.task.status, "running"); assert.equal(payload.task.completedAt, undefined); const state = await data.readState(); const progressMessage = state.projects .find((project) => project.id === "master-agent") ?.messages.find((message) => message.executionProgress?.taskId === task.taskId); assert.equal(progressMessage?.executionProgress?.status, "running"); assert.equal(progressMessage?.executionProgress?.steps[0]?.text, "连接 Codex App Server"); assert.equal(progressMessage?.executionProgress?.branch?.additions, 12); }); test("POST task progress preserves Codex approval, warning, and file-change summaries", async () => { const task = await data.queueMasterAgentTask({ taskId: "route-progress-approval-task", projectId: "group-progress-test", taskType: "dispatch_execution", requestMessageId: "msg-route-progress-approval", requestText: "让目标线程继续开发并回写审批状态", executionPrompt: "让目标线程继续开发并回写审批状态", requestedBy: "krisolo", requestedByAccount: "krisolo", deviceId: "mac-studio", targetProjectId: "master-agent", targetThreadId: "master-agent-thread", }); await data.claimNextMasterAgentTask("mac-studio"); const response = await postProgress( new NextRequest(`http://127.0.0.1:3000/api/v1/master-agent/tasks/${task.taskId}/progress`, { method: "POST", headers: { "content-type": "application/json", "x-boss-device-token": "boss-mac-studio-token", }, body: JSON.stringify({ deviceId: "mac-studio", status: "running", executionProgress: { steps: [{ text: "等待 Codex 审批事件", status: "running" }], approvals: [ { id: "cmd-approval-1", kind: "command", label: "命令执行审批", status: "resolved", detail: "需要确认命令执行", }, ], warnings: [ { id: "guardian-warning-1", message: "检测到需要用户确认的命令执行。", severity: "warning", }, ], fileChanges: [ { id: "file-change-1", path: "src/app/page.tsx", kind: "update", status: "updated", }, ], }, }), }), { params: Promise.resolve({ taskId: task.taskId }) }, ); assert.equal(response.status, 200); const state = await data.readState(); const progress = state.projects .find((project) => project.id === "master-agent") ?.messages.find((message) => message.executionProgress?.taskId === task.taskId) ?.executionProgress; assert.equal(progress?.approvals?.[0]?.label, "命令执行审批"); assert.equal(progress?.warnings?.[0]?.message, "检测到需要用户确认的命令执行。"); assert.equal(progress?.fileChanges?.[0]?.path, "src/app/page.tsx"); }); test("POST task progress preserves Codex thread status and realtime summaries", async () => { const task = await data.queueMasterAgentTask({ taskId: "route-progress-realtime-task", projectId: "group-progress-test", taskType: "dispatch_execution", requestMessageId: "msg-route-progress-realtime", requestText: "让目标线程继续开发并回写实时状态", executionPrompt: "让目标线程继续开发并回写实时状态", requestedBy: "krisolo", requestedByAccount: "krisolo", deviceId: "mac-studio", targetProjectId: "master-agent", targetThreadId: "master-agent-thread", }); await data.claimNextMasterAgentTask("mac-studio"); const response = await postProgress( new NextRequest(`http://127.0.0.1:3000/api/v1/master-agent/tasks/${task.taskId}/progress`, { method: "POST", headers: { "content-type": "application/json", "x-boss-device-token": "boss-mac-studio-token", }, body: JSON.stringify({ deviceId: "mac-studio", status: "running", executionProgress: { steps: [{ text: "监听 Codex realtime 事件", status: "running" }], threadStatus: { type: "active", activeFlags: ["waitingOnApproval", "waitingOnUserInput"], waitingOnApproval: true, waitingOnUserInput: true, }, realtime: { status: "streaming", sessionId: "rt-session-1", version: "v2", transcriptRole: "assistant", transcriptPreview: "正在分析 Codex App Server 实时事件。", audioChunkCount: 1, itemCount: 1, }, }, }), }), { params: Promise.resolve({ taskId: task.taskId }) }, ); assert.equal(response.status, 200); const state = await data.readState(); const progress = state.projects .find((project) => project.id === "master-agent") ?.messages.find((message) => message.executionProgress?.taskId === task.taskId) ?.executionProgress; assert.equal(progress?.threadStatus?.type, "active"); assert.equal(progress?.threadStatus?.waitingOnApproval, true); assert.equal(progress?.threadStatus?.waitingOnUserInput, true); assert.equal(progress?.realtime?.status, "streaming"); assert.equal(progress?.realtime?.transcriptPreview, "正在分析 Codex App Server 实时事件。"); assert.equal(progress?.realtime?.audioChunkCount, 1); });