753 lines
28 KiB
TypeScript
753 lines
28 KiB
TypeScript
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",
|
||
},
|
||
windowsSandbox: {
|
||
status: "failed",
|
||
setupMode: "elevated",
|
||
error: "token=sk-secret-should-not-persist failed",
|
||
sourcePath: "C:\\Users\\kris\\secret\\sandbox.log",
|
||
},
|
||
},
|
||
}),
|
||
}),
|
||
{ 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(progress?.windowsSandbox?.status, "failed");
|
||
assert.equal(progress?.windowsSandbox?.setupMode, "elevated");
|
||
assert.equal(progress?.windowsSandbox?.error, "token=[redacted] failed");
|
||
assert.equal(JSON.stringify(progress).includes("install-secret-should-not-persist"), false);
|
||
assert.equal(JSON.stringify(progress).includes("C:\\Users\\kris\\secret"), 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 stream event counters without leaking raw deltas", async () => {
|
||
const task = await data.queueMasterAgentTask({
|
||
taskId: "route-progress-stream-events-task",
|
||
projectId: "group-progress-test",
|
||
taskType: "dispatch_execution",
|
||
requestMessageId: "msg-route-progress-stream-events",
|
||
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" }],
|
||
streamEvents: {
|
||
status: "streaming",
|
||
agentDeltaCount: 2,
|
||
planDeltaCount: 1,
|
||
reasoningDeltaCount: 3,
|
||
toolProgressCount: 1,
|
||
commandOutputChunkCount: 4,
|
||
terminalInteractionCount: 1,
|
||
fileOutputChunkCount: 1,
|
||
delta: "secret delta should-not-persist",
|
||
text: "secret text should-not-persist",
|
||
output: "secret output should-not-persist",
|
||
content: "secret content should-not-persist",
|
||
command: "cat secret should-not-persist",
|
||
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?.streamEvents, {
|
||
status: "streaming",
|
||
agentDeltaCount: 2,
|
||
planDeltaCount: 1,
|
||
reasoningDeltaCount: 3,
|
||
toolProgressCount: 1,
|
||
commandOutputChunkCount: 4,
|
||
terminalInteractionCount: 1,
|
||
fileOutputChunkCount: 1,
|
||
});
|
||
const serialized = JSON.stringify(progress);
|
||
assert.equal(serialized.includes("should-not-persist"), false);
|
||
assert.equal(serialized.includes("turn-secret"), 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);
|
||
});
|