Files
boss/tests/master-agent-task-progress-route.test.ts
2026-06-01 17:40:03 +08:00

450 lines
16 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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);
});
test("POST task progress preserves Codex runtime status summaries", async () => {
const task = await data.queueMasterAgentTask({
taskId: "route-progress-runtime-task",
projectId: "group-progress-test",
taskType: "dispatch_execution",
requestMessageId: "msg-route-progress-runtime",
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" }],
modelRoute: {
fromModel: "gpt-5.4-mini",
toModel: "gpt-5.4",
reason: "highRiskCyberActivity",
},
tokenUsage: {
totalTokens: 3000,
inputTokens: 2200,
cachedInputTokens: 300,
outputTokens: 650,
reasoningOutputTokens: 150,
modelContextWindow: 200000,
contextPercent: 2,
},
mcpServers: [
{
name: "github",
status: "failed",
error: "token=[redacted] failed to start",
},
],
remoteControl: {
status: "connected",
serverName: "Mac Studio",
environmentId: "env-prod",
installationId: "install-secret-should-not-persist",
},
},
}),
}),
{ 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?.modelRoute?.toModel, "gpt-5.4");
assert.equal(progress?.tokenUsage?.contextPercent, 2);
assert.equal(progress?.mcpServers?.[0]?.name, "github");
assert.equal(progress?.mcpServers?.[0]?.error, "token=[redacted] failed to start");
assert.equal(progress?.remoteControl?.status, "connected");
assert.equal(JSON.stringify(progress).includes("install-secret-should-not-persist"), false);
});
test("POST task progress preserves Codex thread goal, settings, and compaction summaries", async () => {
const task = await data.queueMasterAgentTask({
taskId: "route-progress-thread-config-task",
projectId: "group-progress-test",
taskType: "dispatch_execution",
requestMessageId: "msg-route-progress-thread-config",
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" }],
threadGoal: {
objective: "完成 App Server 线程目标同步",
status: "active",
tokenBudget: 120000,
tokensUsed: 4800,
timeUsedSeconds: 600,
},
threadSettings: {
model: "gpt-5.5",
modelProvider: "openai",
approvalPolicy: "on-request",
approvalsReviewer: "user",
sandboxPolicy: "workspaceWrite",
permissionProfile: ":workspace",
serviceTier: "fast",
effort: "low",
summary: "concise",
collaborationMode: "plan",
personality: "pragmatic",
cwd: "/Users/kris/code/boss/secret-project",
},
compaction: {
status: "completed",
message: "上下文已压缩",
turnId: "turn-secret-should-not-persist",
},
},
}),
}),
{ 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?.threadGoal?.objective, "完成 App Server 线程目标同步");
assert.equal(progress?.threadSettings?.model, "gpt-5.5");
assert.equal(progress?.threadSettings?.sandboxPolicy, "workspaceWrite");
assert.equal(progress?.compaction?.status, "completed");
const serialized = JSON.stringify(progress);
assert.equal(serialized.includes("/Users/kris"), false);
assert.equal(serialized.includes("turn-secret-should-not-persist"), false);
});
test("POST task progress preserves Codex account, quota, verification, and notice summaries", async () => {
const task = await data.queueMasterAgentTask({
taskId: "route-progress-account-notices-task",
projectId: "group-progress-test",
taskType: "dispatch_execution",
requestMessageId: "msg-route-progress-account-notices",
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" }],
accountStatus: {
authMode: "chatgpt",
planType: "team",
limitId: "codex",
limitName: "Codex",
usedPercent: 88,
windowDurationMins: 180,
resetsAt: 1770003600,
creditsBalance: "120.5",
hasCredits: true,
unlimitedCredits: false,
accessToken: "sk-secret-should-not-persist",
},
modelVerification: {
verifications: ["trustedAccessForCyber"],
turnId: "turn-secret-should-not-persist",
},
warnings: [
{
id: "config-warning-1",
message: "项目配置已忽略openai_base_url 不能放在项目配置里",
severity: "warning",
path: "/Users/kris/code/boss/.codex/config.toml",
},
],
},
}),
}),
{ 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?.accountStatus?.authMode, "chatgpt");
assert.equal(progress?.accountStatus?.planType, "team");
assert.equal(progress?.accountStatus?.usedPercent, 88);
assert.equal(progress?.modelVerification?.verifications?.[0], "trustedAccessForCyber");
assert.equal(progress?.warnings?.[0]?.message, "项目配置已忽略openai_base_url 不能放在项目配置里");
const serialized = JSON.stringify(progress);
assert.equal(serialized.includes("/Users/kris"), false);
assert.equal(serialized.includes("sk-secret-should-not-persist"), false);
assert.equal(serialized.includes("turn-secret-should-not-persist"), false);
});