Files
boss/tests/master-agent-task-progress-route.test.ts
2026-06-01 18:20:04 +08:00

671 lines
25 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);
});
test("POST task progress preserves Codex thread collaboration summaries without leaking thread internals", async () => {
const task = await data.queueMasterAgentTask({
taskId: "route-progress-thread-collab-task",
projectId: "group-progress-test",
taskType: "dispatch_execution",
requestMessageId: "msg-route-progress-thread-collab",
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" }],
threadCollaboration: {
tool: "send_input",
status: "completed",
target: "已有线程",
agentStatus: "completed",
senderThreadId: "thread-source-secret-should-not-persist",
receiverThreadId: "thread-target-secret-should-not-persist",
prompt: "internal prompt token=sk-secret-should-not-persist",
},
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.deepEqual(progress?.threadCollaboration, {
tool: "send_input",
status: "completed",
target: "已有线程",
agentStatus: "completed",
});
assert.equal(progress?.compaction?.status, "completed");
const serialized = JSON.stringify(progress);
assert.equal(serialized.includes("thread-source-secret-should-not-persist"), false);
assert.equal(serialized.includes("thread-target-secret-should-not-persist"), false);
assert.equal(serialized.includes("internal prompt"), false);
assert.equal(serialized.includes("sk-secret-should-not-persist"), false);
assert.equal(serialized.includes("turn-secret-should-not-persist"), false);
});
test("POST task progress preserves Codex tool activity summaries without leaking raw payloads", async () => {
const task = await data.queueMasterAgentTask({
taskId: "route-progress-tool-activity-task",
projectId: "group-progress-test",
taskType: "dispatch_execution",
requestMessageId: "msg-route-progress-tool-activity",
requestText: "检查 Codex 工具活动",
executionPrompt: "检查 Codex 工具活动",
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" }],
toolActivities: [
{
kind: "mcp",
name: "github/pull_request/list",
status: "failed",
detail: "token=sk-secret-should-not-persist failed",
arguments: { token: "sk-secret-should-not-persist" },
result: "internal tool result",
},
{
kind: "web_search",
name: "openPage",
status: "running",
detail: "Codex App Server ThreadItem",
url: "https://example.com/private?token=sk-secret-should-not-persist",
},
{
kind: "command",
name: "commandExecution",
status: "completed",
detail: "exit 0 · 2345ms",
command: "cat /Users/kris/.ssh/id_rsa",
cwd: "/Users/kris/code/boss",
},
],
},
}),
}),
{ 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.deepEqual(progress?.toolActivities, [
{
kind: "mcp",
name: "github/pull_request/list",
status: "failed",
detail: "token=[redacted] failed",
},
{
kind: "web_search",
name: "openPage",
status: "running",
detail: "Codex App Server ThreadItem",
},
{
kind: "command",
name: "commandExecution",
status: "completed",
detail: "exit 0 · 2345ms",
},
]);
const serialized = JSON.stringify(progress);
assert.equal(serialized.includes("sk-secret-should-not-persist"), false);
assert.equal(serialized.includes("internal tool result"), false);
assert.equal(serialized.includes("https://example.com/private"), false);
assert.equal(serialized.includes("/Users/kris"), false);
assert.equal(serialized.includes("id_rsa"), false);
});
test("POST task progress preserves Codex reasoning summaries without leaking raw content", async () => {
const task = await data.queueMasterAgentTask({
taskId: "route-progress-reasoning-summary-task",
projectId: "group-progress-test",
taskType: "dispatch_execution",
requestMessageId: "msg-route-progress-reasoning-summary",
requestText: "检查 Codex 思考摘要",
executionPrompt: "检查 Codex 思考摘要",
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" }],
reasoningSummary: {
status: "completed",
summary: "确认只展示官方 summary不展示 raw content token=sk-secret-should-not-persist。",
content: [{ text: "raw hidden chain of thought token=sk-secret-should-not-persist" }],
itemId: "reasoning-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.deepEqual(progress?.reasoningSummary, {
status: "completed",
summary: "确认只展示官方 summary不展示 raw content token=[redacted]",
});
const serialized = JSON.stringify(progress);
assert.equal(serialized.includes("raw hidden chain of thought"), false);
assert.equal(serialized.includes("sk-secret-should-not-persist"), false);
assert.equal(serialized.includes("reasoning-secret-should-not-persist"), false);
});