refactor: remove seeded boss console conversation

This commit is contained in:
kris
2026-04-04 10:06:57 +08:00
parent 4e78fb0a34
commit 99acf26b1b
9 changed files with 247 additions and 308 deletions

View File

@@ -203,7 +203,7 @@ public class MasterAgentMemoryActivity extends BossScreenActivity {
final EditText titleInput = BossUi.buildInput(this, "记忆标题", false);
final EditText contentInput = BossUi.buildInput(this, "记忆内容", true);
final EditText projectIdInput = BossUi.buildInput(this, "例如:boss-console", false);
final EditText projectIdInput = BossUi.buildInput(this, "例如:wenshenapp", false);
final EditText tagsInput = BossUi.buildInput(this, "标签,逗号分隔", false);
contentInput.setMinLines(6);

View File

@@ -1880,7 +1880,7 @@ export function DeviceEnrollmentBuilder() {
const [name, setName] = useState("Mac Mini");
const [avatar, setAvatar] = useState("M");
const [account, setAccount] = useState("17600003315");
const [projects, setProjects] = useState("Boss 移动控制台");
const [projects, setProjects] = useState("");
const [endpoint, setEndpoint] = useState("mac://new-device.local");
const [note, setNote] = useState("新设备待绑定");
const [result, setResult] = useState<{

View File

@@ -549,7 +549,7 @@ export function MasterAgentPromptMemoryClient({
label="projectId"
value={newMemory.projectId}
onChange={(value) => setNewMemory((current) => ({ ...current, projectId: value }))}
placeholder="例如 boss-console"
placeholder="例如 wenshenapp"
/>
) : null}
<Field

View File

@@ -1038,7 +1038,7 @@ const initialState: BossState = {
account: PRIMARY_ADMIN_ACCOUNT,
source: "production",
status: "online",
projects: ["Boss 移动控制台", "硬件审计协作"],
projects: ["硬件审计协作"],
quota5h: 68,
quota7d: 81,
lastSeenAt: "2026-03-25T11:52:00+08:00",
@@ -1053,7 +1053,7 @@ const initialState: BossState = {
account: "kris.plus.gpu",
source: "demo",
status: "abnormal",
projects: ["Boss 移动控制台", "硬件审计协作"],
projects: ["硬件审计协作"],
quota5h: 31,
quota7d: 46,
lastSeenAt: "2026-03-25T11:40:00+08:00",
@@ -1068,7 +1068,7 @@ const initialState: BossState = {
account: "kris.plus.backup",
source: "demo",
status: "offline",
projects: ["Boss 移动控制台"],
projects: [],
quota5h: 92,
quota7d: 95,
lastSeenAt: "2026-03-25T08:15:00+08:00",
@@ -1084,7 +1084,7 @@ const initialState: BossState = {
pinned: true,
systemPinned: true,
deviceIds: ["mac-studio"],
preview: "已汇总 3 个项目,优先收尾 Boss 移动控制台里 must_finish_before_compaction 的线程。",
preview: "已汇总当前活跃项目,优先收尾 must_finish_before_compaction 的线程并同步最新风险。",
updatedAt: "2026-03-25T12:06:00+08:00",
lastMessageAt: "2026-03-25T12:06:00+08:00",
isGroup: false,
@@ -1111,7 +1111,7 @@ const initialState: BossState = {
id: "master-summary",
sender: "master",
senderLabel: "主 Agent",
body: "Boss 移动控制台存在 urgent 线程待交接,硬件审计协作还有 1 条摄像头证据待复核。",
body: "当前存在 urgent 线程待交接,硬件审计协作还有 1 条摄像头证据待复核。",
sentAt: "2026-03-25T12:06:00+08:00",
kind: "text",
},
@@ -1119,91 +1119,6 @@ const initialState: BossState = {
goals: [],
versions: [],
},
{
id: "boss-console",
name: "Boss 移动控制台",
pinned: false,
deviceIds: ["mac-studio"],
preview: "登录、设备页、线程预算与设备绑定链路正在收口到 v13。",
updatedAt: "2026-03-25T11:52:00+08:00",
lastMessageAt: "2026-03-25T11:52:00+08:00",
isGroup: false,
threadMeta: {
projectId: "boss-console",
threadId: "thread-boss-ui",
threadDisplayName: "北区试产线回归",
folderName: "归档确认",
activityIconCount: 1,
updatedAt: "2026-03-25T11:52:00+08:00",
codexThreadRef: "thread-boss-ui",
codexFolderRef: "boss-console",
},
groupMembers: [],
createdByAgent: true,
collaborationMode: "development",
approvalState: "not_required",
unreadCount: 2,
riskLevel: "medium",
contextBudgetPct: 62,
contextBudgetLabel: "62%",
messages: [
{
id: "p1",
sender: "master",
senderLabel: "主 Agent",
body: "项目目标页已切成可完成、可编辑、可记录完成时间的结构。",
sentAt: "2026-03-25T11:40:00+08:00",
kind: "text",
},
{
id: "p2",
sender: "device",
senderLabel: "Mac Studio / Codex",
body: "登录、注册、忘记密码页已经补齐,并带验证码发送状态回显。",
sentAt: "2026-03-25T11:48:00+08:00",
kind: "text",
},
],
goals: [
{
id: "goal-1",
text: "完成北区试产线全链路回归覆盖串口、视觉、OTA 和容灾切换。",
state: "completed",
note: "已完成 · 09:12 由主 Agent 复核",
completedAt: "2026-03-25T09:12:00+08:00",
completedBy: "主 Agent",
},
{
id: "goal-2",
text: "所有关键步骤必须留下可交接证据,禁止仅口头确认。",
state: "pending",
note: "进行中 · 允许用户编辑,主 Agent 会同步重排任务",
},
{
id: "goal-3",
text: "当线程上下文余量进入 urgent 前,必须完成阶段摘要与 handoff。",
state: "pending",
note: "待处理 · 主 Agent 会优先把压缩前必须收尾的任务推到前面",
},
],
versions: [
{
version: "v1.3.0",
summary: "登录页改为账号密码 / 验证码双模式,并新增 OTA 版本中心与本机最高管理员绑定。",
createdAt: "2026-03-26T09:20:00+08:00",
},
{
version: "v1.2.8",
summary: "补齐认证页、线程预算接口、设备绑定草稿与运维审计摘要。",
createdAt: "2026-03-25T11:30:00+08:00",
},
{
version: "v1.2.7",
summary: "会话页、设备页、我的页切到微信式一级导航。",
createdAt: "2026-03-25T09:15:00+08:00",
},
],
},
{
id: "audit-collab",
name: "硬件审计协作",
@@ -1418,56 +1333,6 @@ const initialState: BossState = {
checklist: baseThreadChecklist(["持续刷新阶段摘要", "复核风险排序", "回写交接文档"]),
capturedAt: "2026-03-25T12:06:00+08:00",
},
{
snapshotId: "snapshot-boss-ui",
projectId: "boss-console",
taskId: "task-ui-refine",
threadId: "thread-boss-ui",
title: "Boss UI 收口线程",
summary: "会话页、设备页和我的页已稳定,剩设备绑定与运维摘要联调。",
nodeId: "mac-studio",
workerId: "worker-mac-ui",
sourceKind: "worker_estimator",
status: "running",
contextBudgetRemainingPct: 62,
contextBudgetLevel: "watch",
compactionExpectedAt: "2026-03-25T14:05:00+08:00",
mustFinishBeforeCompaction: false,
estimatedRemainingTurns: 10,
estimatedRemainingLargeMessages: 4,
lastCompactionAt: "2026-03-25T10:28:00+08:00",
compactionCount: 1,
patchPending: true,
testsPending: true,
evidencePending: false,
checklist: baseThreadChecklist(["设备绑定页接 API", "回归 lint/build", "回写 README"]),
capturedAt: "2026-03-25T11:52:00+08:00",
},
{
snapshotId: "snapshot-boss-auth",
projectId: "boss-console",
taskId: "task-auth-delivery",
threadId: "thread-boss-auth",
title: "认证链路交接线程",
summary: "登录/注册/忘记密码已完成,但验证码回显和文档收口必须先固化。",
nodeId: "mac-studio",
workerId: "worker-mac-auth",
sourceKind: "worker_estimator",
status: "handoff_pending",
contextBudgetRemainingPct: 34,
contextBudgetLevel: "urgent",
compactionExpectedAt: "2026-03-25T12:24:00+08:00",
mustFinishBeforeCompaction: true,
estimatedRemainingTurns: 4,
estimatedRemainingLargeMessages: 1,
lastCompactionAt: "2026-03-25T11:14:00+08:00",
compactionCount: 2,
patchPending: true,
testsPending: true,
evidencePending: false,
checklist: baseThreadChecklist(["固化 patch", "记录测试结果", "生成 handoff package"]),
capturedAt: "2026-03-25T11:54:00+08:00",
},
{
snapshotId: "snapshot-audit-chief",
projectId: "audit-collab",
@@ -1519,36 +1384,8 @@ const initialState: BossState = {
capturedAt: "2026-03-25T11:58:00+08:00",
},
],
threadHandoffPackages: [
{
handoffPackageId: "handoff-boss-auth",
projectId: "boss-console",
taskId: "task-auth-delivery",
fromThreadId: "thread-boss-auth",
toThreadId: "thread-boss-auth-followup",
packageStatus: "ready",
summaryText: "认证页功能已齐,剩余文档、回归和部署验证待在新线程继续。",
openQuestions: ["验证码直回显是否继续保留在公网环境", "是否把帮助页挂到登录页右上角"],
criticalFiles: ["src/app/auth", "src/app/api/auth", "src/components/app-ui.tsx"],
criticalCommands: ["npm run lint", "npm run build", "curl -sS http://127.0.0.1:3000/api/health"],
criticalTests: ["登录验证码发送", "注册后登录", "忘记密码重置"],
criticalArtifacts: ["docs/architecture/api_and_service_inventory_cn.md"],
decisionLinks: ["boss-console:auth-mvp"],
createdAt: "2026-03-25T11:49:00+08:00",
readyAt: "2026-03-25T11:54:00+08:00",
},
],
threadHandoffPackages: [],
threadContextAlerts: [
{
alertId: "alert-boss-auth",
threadId: "thread-boss-auth",
projectId: "boss-console",
alertType: "context_urgent",
alertStatus: "opened",
openedAt: "2026-03-25T11:54:00+08:00",
summary: "认证链路线程已进入 urgent必须优先固化 patch、测试结论和 handoff。",
masterActions: ["prepare_handoff", "avoid_large_context_append", "finalize_artifacts"],
},
{
alertId: "alert-audit-hw",
threadId: "thread-audit-hardware",
@@ -1591,23 +1428,6 @@ const initialState: BossState = {
suggestedNextAction: "优先重试本地 agent 证据上传,再做审计复验。",
autoRepairable: true,
},
{
faultId: "fault-context-auth",
faultKey: "THREAD.CONTEXT.HANDOFF.REQUIRED",
severity: "critical",
status: "opened",
nodeId: "mac-studio",
serviceName: "boss-web",
projectId: "boss-console",
threadRef: "thread-boss-auth",
traceId: "trace-boss-auth-002",
runbookId: "runbook-thread-handoff",
firstSeenAt: "2026-03-25T11:50:00+08:00",
lastSeenAt: "2026-03-25T11:54:00+08:00",
summary: "认证线程预算降到 urgent未完成 handoff 前不应继续追加长上下文。",
suggestedNextAction: "执行压缩前收尾,写明关键文件、命令和测试结果。",
autoRepairable: false,
},
],
opsRepairTickets: [
{
@@ -7794,7 +7614,7 @@ function pruneStaleAutoImportedProjectsForDevice(
selectedCandidates: DeviceImportCandidate[],
) {
const activeSignatures = new Set(selectedCandidates.map((candidate) => candidateThreadSignature(candidate)));
const reservedProjectIds = new Set(["master-agent", "boss-console", "audit-collab"]);
const reservedProjectIds = new Set(["master-agent", "audit-collab"]);
state.projects = state.projects.filter((project) => {
if (reservedProjectIds.has(project.id)) return true;

View File

@@ -233,3 +233,16 @@ test("conversation items prefer latest observed codex activity over stale last m
assert.equal(thread?.latestReplyAt, "2026-04-04T11:48:00+08:00");
assert.equal(thread?.latestReplyLabel, formatTimestampLabel("2026-04-04T11:48:00+08:00"));
});
test("default seeded conversations no longer expose Boss 移动控制台", async () => {
await setup();
const state = await readState();
const items = getConversationHomeItems(state);
assert.ok(items.some((item) => item.projectId === "master-agent"), "expected master-agent to remain available");
assert.equal(
items.some((item) => item.projectId === "boss-console" || item.threadTitle === "Boss 移动控制台"),
false,
);
});

View File

@@ -66,6 +66,58 @@ test.beforeEach(async () => {
await writeState(structuredClone(baseState));
});
function buildDispatchableThreadProject({
id,
projectName,
threadDisplayName,
body,
}: {
id: string;
projectName: string;
threadDisplayName: string;
body: string;
}) {
return {
id,
name: projectName,
pinned: false,
systemPinned: false,
deviceIds: ["mac-studio"],
preview: body,
updatedAt: "2026-03-30T10:00:00+08:00",
lastMessageAt: "2026-03-30T10:00:00+08:00",
isGroup: false,
threadMeta: {
projectId: id,
threadId: `thread-${id}`,
threadDisplayName,
folderName: "阻塞梳理",
activityIconCount: 0,
updatedAt: "2026-03-30T10:00:00+08:00",
codexThreadRef: `thread-${id}`,
codexFolderRef: id,
},
groupMembers: [],
createdByAgent: true,
collaborationMode: "development" as const,
approvalState: "not_required" as const,
unreadCount: 0,
riskLevel: "low" as const,
messages: [
{
id: `msg-${id}`,
sender: "device" as const,
senderLabel: "Win GPU / Codex",
body,
sentAt: "2026-03-30T10:00:00+08:00",
kind: "text" as const,
},
],
goals: [],
versions: [],
};
}
async function createAuthedRequest(url: string, method: "GET" | "POST" | "PATCH", body?: unknown) {
const session = await createAuthSession({
account: "17600003315",
@@ -91,44 +143,24 @@ 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],
updatedAt: "2026-03-30T10:00:00+08:00",
lastMessageAt: "2026-03-30T10:00:00+08:00",
preview: "副线程等待主 Agent 汇总阻塞点。",
threadMeta: {
...seed.threadMeta,
projectId: "boss-console-clone",
threadId: "thread-boss-ui-clone",
const freshProjects = [
buildDispatchableThreadProject({
id: "dispatch-confirm-a",
projectName: "北区试产线主线程",
threadDisplayName: "北区试产线回归",
body: "这里还在等待主 Agent 汇总阻塞点。",
}),
buildDispatchableThreadProject({
id: "dispatch-confirm-b",
projectName: "南区试产线主线程",
threadDisplayName: "南区试产线回归",
folderName: "阻塞梳理",
updatedAt: "2026-03-30T10:00:00+08:00",
codexThreadRef: "thread-boss-ui-clone",
codexFolderRef: "boss-console-clone",
},
groupMembers: [],
messages: [
{
id: "msg-boss-console-clone",
sender: "device" as const,
senderLabel: "Win GPU / Codex",
body: "这里还在等待视觉链路复核。",
sentAt: "2026-03-30T10:00:00+08:00",
kind: "text" as const,
},
],
goals: [],
versions: [],
};
body: "这里还在等待视觉链路复核。",
}),
];
await writeState({
...state,
projects: [...state.projects, clonedProject],
projects: state.projects.concat(freshProjects),
});
const nextState = await readState();

View File

@@ -49,6 +49,58 @@ test.beforeEach(async () => {
await writeState(structuredClone(baseState));
});
function buildDispatchableThreadProject({
id,
projectName,
threadDisplayName,
body,
}: {
id: string;
projectName: string;
threadDisplayName: string;
body: string;
}) {
return {
id,
name: projectName,
pinned: false,
systemPinned: false,
deviceIds: ["mac-studio"],
preview: body,
updatedAt: "2026-03-30T10:00:00+08:00",
lastMessageAt: "2026-03-30T10:00:00+08:00",
isGroup: false,
threadMeta: {
projectId: id,
threadId: `thread-${id}`,
threadDisplayName,
folderName: "阻塞梳理",
activityIconCount: 0,
updatedAt: "2026-03-30T10:00:00+08:00",
codexThreadRef: `thread-${id}`,
codexFolderRef: `/Users/kris/code/${id}`,
},
groupMembers: [],
createdByAgent: true,
collaborationMode: "development" as const,
approvalState: "not_required" as const,
unreadCount: 0,
riskLevel: "low" as const,
messages: [
{
id: `msg-${id}`,
sender: "device" as const,
senderLabel: "Mac Studio / Codex",
body,
sentAt: "2026-03-30T10:00:00+08:00",
kind: "text" as const,
},
],
goals: [],
versions: [],
};
}
async function createAuthedRequest(projectId: string, body: { body: string; kind?: string }) {
const session = await createAuthSession({
account: "17600003315",
@@ -69,73 +121,22 @@ async function createAuthedRequest(projectId: string, body: { body: string; kind
async function ensureTwoSingleThreadProjects() {
const state = await readState();
const seed = state.projects.find((project) => project.id !== "master-agent" && !project.isGroup);
assert.ok(seed, "expected at least one seeded single-thread project");
const primaryProject = {
...seed,
id: "dispatch-thread-a",
name: "Boss 移动控制台主线程",
deviceIds: ["mac-studio"],
updatedAt: "2026-03-30T10:00:00+08:00",
lastMessageAt: "2026-03-30T10:00:00+08:00",
preview: "主线程正在等待汇总今天的联调阻塞点。",
threadMeta: {
...seed.threadMeta,
projectId: "dispatch-thread-a",
threadId: "thread-dispatch-a",
...buildDispatchableThreadProject({
id: "dispatch-thread-a",
projectName: "北区试产线主线程",
threadDisplayName: "北区试产线回归",
folderName: "阻塞梳理",
updatedAt: "2026-03-30T10:00:00+08:00",
codexThreadRef: "thread-dispatch-a",
codexFolderRef: "/Users/kris/code/boss",
},
groupMembers: [],
messages: [
{
id: "msg-dispatch-a",
sender: "device" as const,
senderLabel: "Mac Studio / Codex",
body: "主线程还在等待主 Agent 汇总阻塞点。",
sentAt: "2026-03-30T10:00:00+08:00",
kind: "text" as const,
},
],
goals: [],
versions: [],
body: "主线程还在等待主 Agent 汇总阻塞点。",
}),
};
const secondaryProject = {
...seed,
id: "dispatch-thread-b",
name: "Boss 移动控制台副线程",
deviceIds: ["mac-studio"],
updatedAt: "2026-03-30T10:00:00+08:00",
lastMessageAt: "2026-03-30T10:00:00+08:00",
preview: "副线程等待主 Agent 汇总阻塞点。",
threadMeta: {
...seed.threadMeta,
projectId: "dispatch-thread-b",
threadId: "thread-dispatch-b",
...buildDispatchableThreadProject({
id: "dispatch-thread-b",
projectName: "南区试产线主线程",
threadDisplayName: "南区试产线回归",
folderName: "阻塞梳理",
updatedAt: "2026-03-30T10:00:00+08:00",
codexThreadRef: "thread-dispatch-b",
codexFolderRef: "/Users/kris/code/boss",
},
groupMembers: [],
messages: [
{
id: "msg-dispatch-b",
sender: "device" as const,
senderLabel: "Mac Studio / Codex",
body: "副线程还在等待视觉链路复核。",
sentAt: "2026-03-30T10:00:00+08:00",
kind: "text" as const,
},
],
goals: [],
versions: [],
body: "副线程还在等待视觉链路复核。",
}),
};
await writeState({
@@ -202,11 +203,8 @@ test("POST /api/v1/projects/[projectId]/messages returns a dispatch plan for gro
test("POST /api/v1/projects/[projectId]/messages keeps dispatchPlan null for single-thread projects", async () => {
await setup();
const state = await readState();
const singleProject = state.projects.find(
(project) => project.id !== "master-agent" && !project.isGroup,
);
assert.ok(singleProject, "expected a seeded single-thread project");
const [singleProject] = await ensureTwoSingleThreadProjects();
assert.ok(singleProject, "expected a synthetic single-thread project");
const response = await POST(await createAuthedRequest(singleProject.id, { body: "单线程消息" }), {
params: Promise.resolve({ projectId: singleProject.id }),
@@ -547,10 +545,7 @@ test("POST /api/v1/projects/[projectId]/messages excludes master-agent from grou
test("createIndependentGroupChat rejects non-thread members like master-agent", async () => {
await setup();
const state = await readState();
const realThread = state.projects.find(
(project) => project.id !== "master-agent" && !project.isGroup && Boolean(project.threadMeta.codexThreadRef),
);
const [realThread] = await ensureTwoSingleThreadProjects();
assert.ok(realThread, "expected a real thread-backed project");
await assert.rejects(

View File

@@ -63,6 +63,46 @@ test.beforeEach(async () => {
await resetMasterAgentControls();
});
async function ensureOrdinaryProject(projectId = "ordinary-project") {
await setup();
const state = await readState();
if (state.projects.some((item) => item.id === projectId)) {
return projectId;
}
state.projects.push({
id: projectId,
name: "普通项目线程",
pinned: false,
systemPinned: false,
deviceIds: ["mac-studio"],
preview: "普通项目测试线程。",
updatedAt: "2026-04-04T12:00:00+08:00",
lastMessageAt: "2026-04-04T12:00:00+08:00",
isGroup: false,
threadMeta: {
projectId,
threadId: `thread-${projectId}`,
threadDisplayName: "普通项目线程",
folderName: "普通项目",
activityIconCount: 0,
updatedAt: "2026-04-04T12:00:00+08:00",
codexThreadRef: `thread-${projectId}`,
codexFolderRef: projectId,
},
groupMembers: [],
createdByAgent: true,
collaborationMode: "development",
approvalState: "not_required",
unreadCount: 0,
riskLevel: "low",
messages: [],
goals: [],
versions: [],
});
await writeState(state);
return projectId;
}
test.after(async () => {
if (runtimeRoot) {
await rm(runtimeRoot, { recursive: true, force: true });
@@ -368,6 +408,7 @@ test("master-agent 对话控制 POST 清空后仍稳定回传 controls null", as
test("非 master-agent 项目详情不应回传 agentControls 字段", async () => {
await setup();
const ordinaryProjectId = await ensureOrdinaryProject();
const session = await createAuthSession({
account: "17600003315",
@@ -377,13 +418,13 @@ test("非 master-agent 项目详情不应回传 agentControls 字段", async ()
});
const response = await getProjectRoute(
new NextRequest("http://127.0.0.1:3000/api/v1/projects/boss-console", {
new NextRequest(`http://127.0.0.1:3000/api/v1/projects/${ordinaryProjectId}`, {
method: "GET",
headers: {
cookie: `${AUTH_SESSION_COOKIE}=${session.sessionToken}`,
},
}),
{ params: Promise.resolve({ projectId: "boss-console" }) },
{ params: Promise.resolve({ projectId: ordinaryProjectId }) },
);
assert.equal(response.status, 200);
@@ -841,6 +882,7 @@ test("GET /agent-controls 在未显式设置 BOSS_STATE_FILE 时仍可正常读
test("GET /agent-controls rejects ordinary projects", async () => {
await setup();
const ordinaryProjectId = await ensureOrdinaryProject();
const session = await createAuthSession({
account: "17600003315",
@@ -850,13 +892,13 @@ test("GET /agent-controls rejects ordinary projects", async () => {
});
const response = await getAgentControlsRoute(
new NextRequest("http://127.0.0.1:3000/api/v1/projects/boss-console/agent-controls", {
new NextRequest(`http://127.0.0.1:3000/api/v1/projects/${ordinaryProjectId}/agent-controls`, {
method: "GET",
headers: {
cookie: `${AUTH_SESSION_COOKIE}=${session.sessionToken}`,
},
}),
{ params: Promise.resolve({ projectId: "boss-console" }) },
{ params: Promise.resolve({ projectId: ordinaryProjectId }) },
);
assert.equal(response.status, 404);
@@ -940,10 +982,11 @@ test("master-agent 对话控制 POST 会稳定拒绝非法 backendOverride", asy
test("master-agent controls helper 不会写入普通项目", async () => {
await setup();
const ordinaryProjectId = await ensureOrdinaryProject();
await assert.rejects(
() =>
updateProjectAgentControls("boss-console", {
updateProjectAgentControls(ordinaryProjectId, {
modelOverride: "gpt-5.4",
reasoningEffortOverride: "low",
}),
@@ -951,7 +994,7 @@ test("master-agent controls helper 不会写入普通项目", async () => {
);
const state = await readState();
const project = state.projects.find((item) => item.id === "boss-console");
const project = state.projects.find((item) => item.id === ordinaryProjectId);
assert.equal(project?.agentControls, undefined);
assert.equal(await getProjectAgentControls("boss-console"), null);
assert.equal(await getProjectAgentControls(ordinaryProjectId), null);
});

View File

@@ -44,6 +44,39 @@ test.beforeEach(async () => {
await writeState(structuredClone(baseState));
});
function buildSingleThreadProject(projectId: string) {
return {
id: projectId,
name: "测试线程",
pinned: false,
systemPinned: false,
deviceIds: ["mac-studio"],
preview: "测试线程等待继续处理。",
updatedAt: "2026-04-04T11:30:00+08:00",
lastMessageAt: "2026-04-04T11:30:00+08:00",
isGroup: false,
threadMeta: {
projectId,
threadId: "thread-preflight",
threadDisplayName: "测试线程",
folderName: "测试项目",
activityIconCount: 0,
updatedAt: "2026-04-04T11:30:00+08:00",
codexThreadRef: "thread-preflight",
codexFolderRef: "preflight-project",
},
groupMembers: [],
createdByAgent: true,
collaborationMode: "development" as const,
approvalState: "not_required" as const,
unreadCount: 0,
riskLevel: "low" as const,
messages: [],
goals: [],
versions: [],
};
}
async function createAuthedRequest(projectId: string, body: { body: string }) {
const session = await createAuthSession({
account: "17600003315",
@@ -65,14 +98,17 @@ async function createAuthedRequest(projectId: string, body: { body: string }) {
test("single-thread message rejects projects without a real codex thread binding", async () => {
await setup();
const state = await readState();
const singleProject = state.projects.find(
(project) => project.id !== "master-agent" && !project.isGroup,
);
assert.ok(singleProject, "expected a seeded single-thread project");
const singleProject = buildSingleThreadProject("preflight-thread");
await writeState({
...state,
projects: state.projects.map((project) =>
projects: state.projects.concat(singleProject),
});
const nextState = await readState();
await writeState({
...nextState,
projects: nextState.projects.map((project) =>
project.id === singleProject.id
? {
...project,
@@ -96,8 +132,8 @@ test("single-thread message rejects projects without a real codex thread binding
assert.equal(payload.code, "THREAD_BINDING_REQUIRED");
assert.equal(payload.message, "当前线程还没有绑定真实 Codex 线程,请先重新导入该线程后再试。");
const nextState = await readState();
const queuedTask = nextState.masterAgentTasks.find(
const finalState = await readState();
const queuedTask = finalState.masterAgentTasks.find(
(task) => task.projectId === singleProject.id && task.taskType === "conversation_reply",
);
assert.equal(queuedTask, undefined);