feat: add thread status documents and safe thread reply handling

This commit is contained in:
kris
2026-04-04 11:50:46 +08:00
parent 010d8eda2d
commit 4d9b8e2976
10 changed files with 487 additions and 57 deletions

View File

@@ -75,30 +75,30 @@ async function ensureTwoSingleThreadProjects() {
return singles;
}
assert.ok(singles[0], "expected at least one seeded single-thread project");
const seed = singles[0];
const clonedProject = {
...seed,
id: "boss-console-clone",
name: "Boss 移动控制台副线程",
deviceIds: [...seed.deviceIds],
const buildSingleThreadProject = (projectId: string, threadDisplayName: string) => ({
id: projectId,
name: threadDisplayName,
pinned: false,
systemPinned: false,
deviceIds: ["mac-studio"],
preview: `${threadDisplayName} 等待主 Agent 汇总阻塞点。`,
updatedAt: "2026-03-30T10:00:00+08:00",
lastMessageAt: "2026-03-30T10:00:00+08:00",
preview: "副线程等待主 Agent 汇总阻塞点。",
isGroup: false,
threadMeta: {
...seed.threadMeta,
projectId: "boss-console-clone",
threadId: "thread-boss-ui-clone",
threadDisplayName: "南区试产线回归",
projectId,
threadId: `${projectId}-thread`,
threadDisplayName,
folderName: "阻塞梳理",
activityIconCount: 0,
updatedAt: "2026-03-30T10:00:00+08:00",
codexThreadRef: "thread-boss-ui-clone",
codexFolderRef: "boss-console-clone",
codexThreadRef: `${projectId}-thread`,
codexFolderRef: `/Users/kris/code/${projectId}`,
},
groupMembers: [],
messages: [
{
id: "msg-boss-console-clone",
id: `msg-${projectId}`,
sender: "device" as const,
senderLabel: "Win GPU / Codex",
body: "这里还在等待视觉链路复核。",
@@ -108,11 +108,21 @@ async function ensureTwoSingleThreadProjects() {
],
goals: [],
versions: [],
};
createdByAgent: true,
collaborationMode: "development" as const,
approvalState: "not_required" as const,
unreadCount: 0,
riskLevel: "low" as const,
});
const missingProjects = [
!singles[0] ? buildSingleThreadProject("dispatch-thread-a", "北区试产线回归") : null,
!singles[1] ? buildSingleThreadProject("dispatch-thread-b", "南区试产线回归") : null,
].filter(Boolean);
await writeState({
...state,
projects: [...state.projects, clonedProject],
projects: [...state.projects, ...missingProjects],
});
const nextState = await readState();
@@ -288,3 +298,37 @@ test("POST /api/v1/master-agent/tasks/[taskId]/complete is idempotent for repeat
assert.equal(mirroredReplies.length, 1);
assert.equal(masterSummaries.length, 1);
});
test("POST /api/v1/master-agent/tasks/[taskId]/complete blocks leaked thread environment diagnostics from group dispatch results", async () => {
const { groupProject, execution, executionTask } = await createConfirmedDispatchExecution();
const response = await completeMasterTaskRoute(
await createAuthedRequest(
`http://127.0.0.1:3000/api/v1/master-agent/tasks/${executionTask.taskId}/complete`,
"POST",
{
deviceId: execution.deviceId,
status: "completed",
dispatchExecutionId: execution.executionId,
targetProjectId: execution.targetProjectId,
targetThreadId: execution.targetThreadId,
rawThreadReply:
"我不能直接把当前会话环境从只读改回可写。cwd 我可以在命令里指向 /Users/kris/code/gptpluscontrol但现在真正卡住的是只读权限。",
},
),
{ params: Promise.resolve({ taskId: executionTask.taskId }) },
);
assert.equal(response.status, 200);
const nextState = await readState();
const groupMessages = nextState.projects.find((project) => project.id === groupProject.id)?.messages ?? [];
const leakedReply = groupMessages.find((message) =>
message.body.includes("当前会话环境从只读改回可写"),
);
assert.equal(leakedReply, undefined);
const opsNotice = groupMessages.find((message) =>
message.body.includes("线程返回了内部环境提示,已拦截"),
);
assert.ok(opsNotice, "expected a system notice instead of raw leaked diagnostics");
});