308 lines
12 KiB
TypeScript
308 lines
12 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";
|
||
|
||
let runtimeRoot = "";
|
||
let readState: (typeof import("../src/lib/boss-data"))["readState"];
|
||
let writeState: (typeof import("../src/lib/boss-data"))["writeState"];
|
||
let toggleGoal: (typeof import("../src/lib/boss-data"))["toggleGoal"];
|
||
let updateGoalText: (typeof import("../src/lib/boss-data"))["updateGoalText"];
|
||
let createGoal: (typeof import("../src/lib/boss-data"))["createGoal"];
|
||
let updateProjectAgentControls: (typeof import("../src/lib/boss-data"))["updateProjectAgentControls"];
|
||
let queueMasterAgentTask: (typeof import("../src/lib/boss-data"))["queueMasterAgentTask"];
|
||
let completeMasterAgentTask: (typeof import("../src/lib/boss-data"))["completeMasterAgentTask"];
|
||
let forceProjectUnderstandingSyncTask: (typeof import("../src/lib/boss-data"))["forceProjectUnderstandingSyncTask"];
|
||
let subscribeBossEvents: (typeof import("../src/lib/boss-events"))["subscribeBossEvents"];
|
||
|
||
async function setup() {
|
||
if (runtimeRoot) return;
|
||
|
||
runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-goal-events-"));
|
||
process.env.BOSS_RUNTIME_ROOT = runtimeRoot;
|
||
process.env.BOSS_STATE_FILE = path.join(runtimeRoot, "boss-state.json");
|
||
|
||
const [data, events] = await Promise.all([
|
||
import("../src/lib/boss-data.ts"),
|
||
import("../src/lib/boss-events.ts"),
|
||
]);
|
||
readState = data.readState;
|
||
writeState = data.writeState;
|
||
toggleGoal = data.toggleGoal;
|
||
updateGoalText = data.updateGoalText;
|
||
createGoal = data.createGoal;
|
||
updateProjectAgentControls = data.updateProjectAgentControls;
|
||
queueMasterAgentTask = data.queueMasterAgentTask;
|
||
completeMasterAgentTask = data.completeMasterAgentTask;
|
||
forceProjectUnderstandingSyncTask = data.forceProjectUnderstandingSyncTask;
|
||
subscribeBossEvents = events.subscribeBossEvents;
|
||
}
|
||
|
||
async function resetGoalState() {
|
||
const state = await readState();
|
||
const existingProject = state.projects.find((project) => project.id !== "master-agent") ?? state.projects[0];
|
||
const project = structuredClone(existingProject);
|
||
project.id = "project-goal-events";
|
||
project.name = "项目目标事件测试";
|
||
project.goals = [
|
||
{
|
||
id: "goal-1",
|
||
text: "完成接入验证",
|
||
state: "pending",
|
||
note: "等待执行",
|
||
},
|
||
];
|
||
project.versions = [];
|
||
project.projectUnderstanding = undefined;
|
||
project.messages = [];
|
||
project.lastMessageAt = "2026-04-07T10:00:00.000Z";
|
||
state.projects = state.projects.filter((item) => item.id !== "project-goal-events");
|
||
state.projects.unshift(project);
|
||
await writeState(state);
|
||
}
|
||
|
||
test.beforeEach(async () => {
|
||
await setup();
|
||
await resetGoalState();
|
||
});
|
||
|
||
test.after(async () => {
|
||
if (runtimeRoot) {
|
||
await rm(runtimeRoot, { recursive: true, force: true });
|
||
}
|
||
});
|
||
|
||
test("toggleGoal publishes project goal refresh marker for the project", async () => {
|
||
const events: Array<{ event: string; payload: { projectId?: string; note?: string } }> = [];
|
||
const unsubscribe = subscribeBossEvents((event, payload) => {
|
||
events.push({ event, payload });
|
||
});
|
||
|
||
await toggleGoal("project-goal-events", "goal-1");
|
||
unsubscribe();
|
||
|
||
const latest = events.at(-1);
|
||
assert.ok(latest);
|
||
assert.equal(latest.event, "conversation.updated");
|
||
assert.equal(latest.payload.projectId, "project-goal-events");
|
||
assert.equal(latest.payload.note, "project_goals.updated");
|
||
});
|
||
|
||
test("updateGoalText publishes project goal refresh marker for the project", async () => {
|
||
const events: Array<{ event: string; payload: { projectId?: string; note?: string } }> = [];
|
||
const unsubscribe = subscribeBossEvents((event, payload) => {
|
||
events.push({ event, payload });
|
||
});
|
||
|
||
await updateGoalText("project-goal-events", "goal-1", "完成接入验证并复盘");
|
||
unsubscribe();
|
||
|
||
const latest = events.at(-1);
|
||
assert.ok(latest);
|
||
assert.equal(latest.event, "conversation.updated");
|
||
assert.equal(latest.payload.projectId, "project-goal-events");
|
||
assert.equal(latest.payload.note, "project_goals.updated");
|
||
});
|
||
|
||
test("createGoal publishes project goal refresh marker for the project", async () => {
|
||
const events: Array<{ event: string; payload: { projectId?: string; note?: string } }> = [];
|
||
const unsubscribe = subscribeBossEvents((event, payload) => {
|
||
events.push({ event, payload });
|
||
});
|
||
|
||
await createGoal("project-goal-events", "补一条回归目标");
|
||
unsubscribe();
|
||
|
||
const latest = events.at(-1);
|
||
assert.ok(latest);
|
||
assert.equal(latest.event, "conversation.updated");
|
||
assert.equal(latest.payload.projectId, "project-goal-events");
|
||
assert.equal(latest.payload.note, "project_goals.updated");
|
||
});
|
||
|
||
test("project understanding sync completion also publishes project goal refresh marker", async () => {
|
||
const events: Array<{ event: string; payload: { projectId?: string; note?: string } }> = [];
|
||
const unsubscribe = subscribeBossEvents((event, payload) => {
|
||
events.push({ event, payload });
|
||
});
|
||
|
||
const state = await readState();
|
||
const existingProject = state.projects.find((project) => project.id !== "master-agent");
|
||
assert.ok(existingProject);
|
||
const project = structuredClone(existingProject);
|
||
project.id = "project-goal-understanding-sync";
|
||
project.name = "项目目标理解同步测试";
|
||
project.goals = [];
|
||
project.versions = [];
|
||
project.messages = [];
|
||
project.lastMessageAt = "2026-04-07T10:00:00.000Z";
|
||
project.threadMeta.lastObservedCodexActivityAt = "2026-04-07T10:00:00.000Z";
|
||
state.projects = state.projects.filter((item) => item.id !== project.id);
|
||
state.projects.unshift(project);
|
||
await writeState(state);
|
||
await updateProjectAgentControls(project.id, { takeoverEnabled: true });
|
||
|
||
const queuedTask = await forceProjectUnderstandingSyncTask({
|
||
projectId: project.id,
|
||
observedActivityAt: "2026-04-07T10:00:00.000Z",
|
||
reason: "thread_reply",
|
||
});
|
||
assert.ok(queuedTask);
|
||
|
||
await completeMasterAgentTask({
|
||
taskId: queuedTask!.taskId,
|
||
deviceId: "mac-studio",
|
||
status: "completed",
|
||
replyBody: JSON.stringify({
|
||
projectGoal: "完成项目目标和版本记录自动同步",
|
||
currentProgress: "项目目标页需要展示主 Agent 最新核对结果",
|
||
technicalArchitecture: "Boss Web 与 Android 共用文件账本状态",
|
||
currentBlockers: "",
|
||
recommendedNextStep: "继续优化聊天阅读排版",
|
||
versionRecord: "已补项目目标/版本记录的自动同步链路。",
|
||
}),
|
||
});
|
||
unsubscribe();
|
||
|
||
const goalRefreshEvent = events.find(
|
||
(item) =>
|
||
item.event === "conversation.updated" &&
|
||
item.payload.projectId === project.id &&
|
||
item.payload.note === "project_goals.updated",
|
||
);
|
||
assert.ok(goalRefreshEvent, "expected project understanding sync to publish a goal refresh marker");
|
||
});
|
||
|
||
test("takeover conversation summary reply writes project goal and version record back to the current conversation", async () => {
|
||
const events: Array<{ event: string; payload: { projectId?: string; note?: string } }> = [];
|
||
const unsubscribe = subscribeBossEvents((event, payload) => {
|
||
events.push({ event, payload });
|
||
});
|
||
|
||
const task = await queueMasterAgentTask({
|
||
projectId: "project-goal-events",
|
||
requestMessageId: "message-summary-request",
|
||
requestText: "请汇总当前项目目标和版本记录,并同步到当前对话顶部入口",
|
||
executionPrompt: "请汇总当前项目目标和版本记录",
|
||
requestedBy: "Boss 超级管理员",
|
||
requestedByAccount: "krisolo",
|
||
deviceId: "mac-studio",
|
||
accountLabel: "主 GPT",
|
||
relayViaMasterAgent: true,
|
||
});
|
||
|
||
await completeMasterAgentTask({
|
||
taskId: task.taskId,
|
||
deviceId: "mac-studio",
|
||
status: "completed",
|
||
replyBody: [
|
||
"项目目标:完成主 Agent 汇总内容自动回写到当前对话。",
|
||
"当前进度:已确认普通接管回复需要同步项目摘要。",
|
||
"技术架构:Boss 文件账本保存项目理解,Android 对话页通过实时事件刷新。",
|
||
"当前阻塞:无。",
|
||
"建议下一步:继续做真机回归。",
|
||
"版本记录:新增主 Agent 接管汇总回复自动写入项目目标和版本记录。",
|
||
].join("\n"),
|
||
});
|
||
unsubscribe();
|
||
|
||
const refreshedProject = (await readState()).projects.find((project) => project.id === "project-goal-events");
|
||
assert.ok(refreshedProject, "expected project to exist");
|
||
assert.equal(
|
||
refreshedProject!.projectUnderstanding?.projectGoal,
|
||
"完成主 Agent 汇总内容自动回写到当前对话。",
|
||
);
|
||
assert.equal(
|
||
refreshedProject!.projectUnderstanding?.currentProgress,
|
||
"已确认普通接管回复需要同步项目摘要。",
|
||
);
|
||
assert.equal(
|
||
refreshedProject!.versions[0]?.summary,
|
||
"新增主 Agent 接管汇总回复自动写入项目目标和版本记录。",
|
||
);
|
||
|
||
assert.ok(
|
||
events.some(
|
||
(item) =>
|
||
item.event === "conversation.updated" &&
|
||
item.payload.projectId === "project-goal-events" &&
|
||
item.payload.note === "project_goals.updated",
|
||
),
|
||
"expected project goal refresh marker",
|
||
);
|
||
assert.ok(
|
||
events.some(
|
||
(item) =>
|
||
item.event === "conversation.updated" &&
|
||
item.payload.projectId === "project-goal-events" &&
|
||
item.payload.note === "project_versions.updated",
|
||
),
|
||
"expected project version refresh marker",
|
||
);
|
||
});
|
||
|
||
test("takeover conversation summary wording writes project goal and version record back to the current conversation", async () => {
|
||
const events: Array<{ event: string; payload: { projectId?: string; note?: string } }> = [];
|
||
const unsubscribe = subscribeBossEvents((event, payload) => {
|
||
events.push({ event, payload });
|
||
});
|
||
|
||
const state = await readState();
|
||
const project = state.projects.find((item) => item.id === "project-goal-events");
|
||
assert.ok(project, "expected project to exist");
|
||
project!.projectUnderstanding = undefined;
|
||
project!.versions = [];
|
||
await writeState(state);
|
||
|
||
const task = await queueMasterAgentTask({
|
||
projectId: "project-goal-events",
|
||
requestMessageId: "message-summary-wording-request",
|
||
requestText: "请总结当前项目目标和版本记录",
|
||
executionPrompt: "请总结当前项目目标和版本记录",
|
||
requestedBy: "Boss 超级管理员",
|
||
requestedByAccount: "krisolo",
|
||
deviceId: "mac-studio",
|
||
accountLabel: "主 GPT",
|
||
relayViaMasterAgent: true,
|
||
});
|
||
|
||
await completeMasterAgentTask({
|
||
taskId: task.taskId,
|
||
deviceId: "mac-studio",
|
||
status: "completed",
|
||
replyBody: [
|
||
"项目目标:稳定总结类请求的项目目标回写。",
|
||
"当前进度:已进入总结语义回归。",
|
||
"技术架构:Boss 文件账本保存项目理解。",
|
||
"当前阻塞:无。",
|
||
"建议下一步:继续跑真机回归。",
|
||
"版本记录:总结类请求也能写入版本记录。",
|
||
].join("\n"),
|
||
});
|
||
unsubscribe();
|
||
|
||
const refreshedProject = (await readState()).projects.find((item) => item.id === "project-goal-events");
|
||
assert.equal(refreshedProject!.projectUnderstanding?.projectGoal, "稳定总结类请求的项目目标回写。");
|
||
assert.equal(refreshedProject!.versions[0]?.summary, "总结类请求也能写入版本记录。");
|
||
|
||
assert.ok(
|
||
events.some(
|
||
(item) =>
|
||
item.event === "conversation.updated" &&
|
||
item.payload.projectId === "project-goal-events" &&
|
||
item.payload.note === "project_goals.updated",
|
||
),
|
||
"expected project goal refresh marker for summary wording",
|
||
);
|
||
assert.ok(
|
||
events.some(
|
||
(item) =>
|
||
item.event === "conversation.updated" &&
|
||
item.payload.projectId === "project-goal-events" &&
|
||
item.payload.note === "project_versions.updated",
|
||
),
|
||
"expected project version refresh marker for summary wording",
|
||
);
|
||
});
|