feat: sync thread status events

This commit is contained in:
kris
2026-04-04 11:06:00 +08:00
parent 8e12fa1e8c
commit f69eebd82d
3 changed files with 585 additions and 61 deletions

View File

@@ -342,6 +342,55 @@ test("device import draft review queues a master-agent task, then completion wri
const device = nextState.devices.find((item) => item.id === enrollmentPayload.device.id);
assert.deepEqual(device?.projects, ["北区试产线"]);
const progressEventCountBefore = nextState.threadProgressEvents.filter(
(event) => event.projectId === importedProject?.id,
).length;
const followupHeartbeatResponse = await deviceHeartbeatRoute(
new NextRequest("http://127.0.0.1:3000/api/device-heartbeat", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
deviceId: enrollmentPayload.device.id,
pairingCode: enrollmentPayload.enrollment.pairingCode,
name: "Mac mini",
avatar: "M",
account: "17600003315",
status: "online",
quota5h: 73,
quota7d: 84,
projects: ["北区试产线"],
endpoint: "mac://mini.local",
projectCandidates: [
{
folderName: "北区试产线",
folderRef: "north-line",
threadId: "thread-north-regression",
threadDisplayName: "北区试产线回归",
codexFolderRef: "north-line",
codexThreadRef: "thread-north-regression",
lastActiveAt: "2026-03-30T12:00:00+08:00",
suggestedImport: true,
},
],
}),
}),
);
assert.equal(followupHeartbeatResponse.status, 200);
const afterHeartbeatState = await readState();
const progressEvents = afterHeartbeatState.threadProgressEvents.filter(
(event) => event.projectId === importedProject?.id,
);
assert.equal(progressEvents.length, progressEventCountBefore + 1);
assert.equal(progressEvents[0]?.eventType, "progress_updated");
assert.match(progressEvents[0]?.summary ?? "", /北区试产线回归|新活动/);
assert.equal(
afterHeartbeatState.masterAgentTasks.some(
(task) => task.projectUnderstandingTargetProjectId === importedProject?.id && task.status === "queued",
),
false,
);
const appliedDraft = nextState.deviceImportDrafts.find(
(draft) => draft.deviceId === enrollmentPayload.device.id,
);
@@ -527,6 +576,9 @@ test("imported thread projects queue hidden understanding sync tasks on newer ac
);
assert.ok(importedProject);
assert.equal(importedProject?.projectUnderstanding?.currentProgress, "已经完成导入前梳理,准备开始界面和设备联调。");
const progressEventsBefore = currentState.threadProgressEvents.filter(
(event) => event.projectId === importedProject?.id,
).length;
const secondHeartbeatResponse = await deviceHeartbeatRoute(
new NextRequest("http://127.0.0.1:3000/api/device-heartbeat", {
@@ -555,42 +607,19 @@ test("imported thread projects queue hidden understanding sync tasks on newer ac
task.projectUnderstandingReason === "heartbeat_activity" &&
task.status === "queued",
);
assert.ok(hiddenSyncTask, "expected a hidden follow-up sync task for newer thread activity");
assert.equal(hiddenSyncTask, undefined);
assert.equal(
(
await completeMasterTaskRoute(
await createAuthedRequest(
`http://127.0.0.1:3000/api/v1/master-agent/tasks/${hiddenSyncTask.taskId}/complete`,
"POST",
{
deviceId: enrollmentPayload.device.id,
status: "completed",
replyBody: JSON.stringify(
{
projectGoal: "让智能看板项目能够稳定接入主控面板。",
currentProgress: "用户已经继续推进到实时状态同步和 UI 联调阶段。",
technicalArchitecture: "Android 原生端通过 SSE 接收 Boss 更新local-agent 负责把线程状态回流到控制台。",
currentBlockers: "高刷设备上的 UI 更新仍需继续优化。",
recommendedNextStep: "优先压平实时状态刷新抖动,再验证群聊调度链路。",
},
null,
2,
),
},
),
{ params: Promise.resolve({ taskId: hiddenSyncTask.taskId }) },
)
).status,
200,
const progressEventsAfter = currentState.threadProgressEvents.filter(
(event) => event.projectId === importedProject?.id,
);
assert.equal(progressEventsAfter.length, progressEventsBefore + 1);
assert.equal(progressEventsAfter[0]?.eventType, "progress_updated");
assert.match(progressEventsAfter[0]?.summary ?? "", /北区试产线回归|新活动/);
currentState = await readState();
const refreshedProject = currentState.projects.find((project) => project.id === importedProject?.id);
assert.equal(refreshedProject?.projectUnderstanding?.currentProgress, "用户已经继续推进到实时状态同步和 UI 联调阶段。");
assert.match(refreshedProject?.projectUnderstanding?.technicalArchitecture ?? "", /SSE 接收 Boss 更新/);
assert.equal(refreshedProject?.projectUnderstanding?.sourceKind, "thread_sync");
assert.ok(refreshedProject?.threadMeta.lastProjectUnderstandingRequestedAt);
assert.equal(refreshedProject?.projectUnderstanding?.currentProgress, "已经完成导入前梳理,准备开始界面和设备联调。");
assert.match(refreshedProject?.projectUnderstanding?.technicalArchitecture ?? "", /Android 原生端连接 Boss Web/);
assert.equal(refreshedProject?.projectUnderstanding?.sourceKind, "device_import");
assert.ok(refreshedProject?.threadMeta.lastProjectUnderstandingSyncedAt);
assert.equal(
@@ -599,7 +628,7 @@ test("imported thread projects queue hidden understanding sync tasks on newer ac
memory.projectId === refreshedProject?.id &&
memory.title === "项目进度 · 智能看板主线程",
)?.content,
"用户已经继续推进到实时状态同步和 UI 联调阶段。",
"已经完成导入前梳理,准备开始界面和设备联调。",
);
assert.equal(
currentState.masterAgentMemories.find(
@@ -607,32 +636,8 @@ test("imported thread projects queue hidden understanding sync tasks on newer ac
memory.projectId === refreshedProject?.id &&
memory.title === "下一步建议 · 智能看板主线程",
)?.content,
"优先压平实时状态刷新抖动,再验证群聊调度链路。",
"先对齐状态推送协议,再做前后端联调。",
);
const masterAgentProject = currentState.projects.find((project) => project.id === "master-agent");
const syncNotice = masterAgentProject?.messages.findLast(
(message) =>
message.kind === "system_notice" &&
/已同步项目理解:智能看板主线程/.test(message.body) &&
/实时状态同步和 UI 联调阶段/.test(message.body),
);
assert.ok(syncNotice, "expected master-agent conversation to receive a lightweight sync digest");
const nextStepNotice = masterAgentProject?.messages.findLast(
(message) =>
message.kind === "system_notice" &&
/建议下一步推进:智能看板主线程/.test(message.body) &&
/优先压平实时状态刷新抖动,再验证群聊调度链路。/.test(message.body),
);
assert.ok(nextStepNotice, "expected master-agent conversation to receive a lightweight next-step suggestion");
const takeoverNotice = masterAgentProject?.messages.findLast(
(message) =>
message.kind === "system_notice" &&
/主 Agent 可接手:智能看板主线程/.test(message.body) &&
/已掌握当前目标、进度、架构与阻塞,可继续推进:优先压平实时状态刷新抖动,再验证群聊调度链路。/.test(
message.body,
),
);
assert.ok(takeoverNotice, "expected master-agent conversation to receive a lightweight takeover suggestion");
});
test("heartbeat candidates no longer auto-create chat windows from legacy projects when import draft is present", async () => {