Files
boss/tests/project-goal-events.test.ts

308 lines
12 KiB
TypeScript
Raw Permalink 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";
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",
);
});