refactor: remove seeded boss console conversation
This commit is contained in:
@@ -203,7 +203,7 @@ public class MasterAgentMemoryActivity extends BossScreenActivity {
|
|||||||
|
|
||||||
final EditText titleInput = BossUi.buildInput(this, "记忆标题", false);
|
final EditText titleInput = BossUi.buildInput(this, "记忆标题", false);
|
||||||
final EditText contentInput = BossUi.buildInput(this, "记忆内容", true);
|
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);
|
final EditText tagsInput = BossUi.buildInput(this, "标签,逗号分隔", false);
|
||||||
contentInput.setMinLines(6);
|
contentInput.setMinLines(6);
|
||||||
|
|
||||||
|
|||||||
@@ -1880,7 +1880,7 @@ export function DeviceEnrollmentBuilder() {
|
|||||||
const [name, setName] = useState("Mac Mini");
|
const [name, setName] = useState("Mac Mini");
|
||||||
const [avatar, setAvatar] = useState("M");
|
const [avatar, setAvatar] = useState("M");
|
||||||
const [account, setAccount] = useState("17600003315");
|
const [account, setAccount] = useState("17600003315");
|
||||||
const [projects, setProjects] = useState("Boss 移动控制台");
|
const [projects, setProjects] = useState("");
|
||||||
const [endpoint, setEndpoint] = useState("mac://new-device.local");
|
const [endpoint, setEndpoint] = useState("mac://new-device.local");
|
||||||
const [note, setNote] = useState("新设备待绑定");
|
const [note, setNote] = useState("新设备待绑定");
|
||||||
const [result, setResult] = useState<{
|
const [result, setResult] = useState<{
|
||||||
|
|||||||
@@ -549,7 +549,7 @@ export function MasterAgentPromptMemoryClient({
|
|||||||
label="projectId"
|
label="projectId"
|
||||||
value={newMemory.projectId}
|
value={newMemory.projectId}
|
||||||
onChange={(value) => setNewMemory((current) => ({ ...current, projectId: value }))}
|
onChange={(value) => setNewMemory((current) => ({ ...current, projectId: value }))}
|
||||||
placeholder="例如 boss-console"
|
placeholder="例如 wenshenapp"
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
<Field
|
<Field
|
||||||
|
|||||||
@@ -1038,7 +1038,7 @@ const initialState: BossState = {
|
|||||||
account: PRIMARY_ADMIN_ACCOUNT,
|
account: PRIMARY_ADMIN_ACCOUNT,
|
||||||
source: "production",
|
source: "production",
|
||||||
status: "online",
|
status: "online",
|
||||||
projects: ["Boss 移动控制台", "硬件审计协作"],
|
projects: ["硬件审计协作"],
|
||||||
quota5h: 68,
|
quota5h: 68,
|
||||||
quota7d: 81,
|
quota7d: 81,
|
||||||
lastSeenAt: "2026-03-25T11:52:00+08:00",
|
lastSeenAt: "2026-03-25T11:52:00+08:00",
|
||||||
@@ -1053,7 +1053,7 @@ const initialState: BossState = {
|
|||||||
account: "kris.plus.gpu",
|
account: "kris.plus.gpu",
|
||||||
source: "demo",
|
source: "demo",
|
||||||
status: "abnormal",
|
status: "abnormal",
|
||||||
projects: ["Boss 移动控制台", "硬件审计协作"],
|
projects: ["硬件审计协作"],
|
||||||
quota5h: 31,
|
quota5h: 31,
|
||||||
quota7d: 46,
|
quota7d: 46,
|
||||||
lastSeenAt: "2026-03-25T11:40:00+08:00",
|
lastSeenAt: "2026-03-25T11:40:00+08:00",
|
||||||
@@ -1068,7 +1068,7 @@ const initialState: BossState = {
|
|||||||
account: "kris.plus.backup",
|
account: "kris.plus.backup",
|
||||||
source: "demo",
|
source: "demo",
|
||||||
status: "offline",
|
status: "offline",
|
||||||
projects: ["Boss 移动控制台"],
|
projects: [],
|
||||||
quota5h: 92,
|
quota5h: 92,
|
||||||
quota7d: 95,
|
quota7d: 95,
|
||||||
lastSeenAt: "2026-03-25T08:15:00+08:00",
|
lastSeenAt: "2026-03-25T08:15:00+08:00",
|
||||||
@@ -1084,7 +1084,7 @@ const initialState: BossState = {
|
|||||||
pinned: true,
|
pinned: true,
|
||||||
systemPinned: true,
|
systemPinned: true,
|
||||||
deviceIds: ["mac-studio"],
|
deviceIds: ["mac-studio"],
|
||||||
preview: "已汇总 3 个项目,优先收尾 Boss 移动控制台里 must_finish_before_compaction 的线程。",
|
preview: "已汇总当前活跃项目,优先收尾 must_finish_before_compaction 的线程并同步最新风险。",
|
||||||
updatedAt: "2026-03-25T12:06:00+08:00",
|
updatedAt: "2026-03-25T12:06:00+08:00",
|
||||||
lastMessageAt: "2026-03-25T12:06:00+08:00",
|
lastMessageAt: "2026-03-25T12:06:00+08:00",
|
||||||
isGroup: false,
|
isGroup: false,
|
||||||
@@ -1111,7 +1111,7 @@ const initialState: BossState = {
|
|||||||
id: "master-summary",
|
id: "master-summary",
|
||||||
sender: "master",
|
sender: "master",
|
||||||
senderLabel: "主 Agent",
|
senderLabel: "主 Agent",
|
||||||
body: "Boss 移动控制台存在 urgent 线程待交接,硬件审计协作还有 1 条摄像头证据待复核。",
|
body: "当前存在 urgent 线程待交接,硬件审计协作还有 1 条摄像头证据待复核。",
|
||||||
sentAt: "2026-03-25T12:06:00+08:00",
|
sentAt: "2026-03-25T12:06:00+08:00",
|
||||||
kind: "text",
|
kind: "text",
|
||||||
},
|
},
|
||||||
@@ -1119,91 +1119,6 @@ const initialState: BossState = {
|
|||||||
goals: [],
|
goals: [],
|
||||||
versions: [],
|
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",
|
id: "audit-collab",
|
||||||
name: "硬件审计协作",
|
name: "硬件审计协作",
|
||||||
@@ -1418,56 +1333,6 @@ const initialState: BossState = {
|
|||||||
checklist: baseThreadChecklist(["持续刷新阶段摘要", "复核风险排序", "回写交接文档"]),
|
checklist: baseThreadChecklist(["持续刷新阶段摘要", "复核风险排序", "回写交接文档"]),
|
||||||
capturedAt: "2026-03-25T12:06:00+08:00",
|
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",
|
snapshotId: "snapshot-audit-chief",
|
||||||
projectId: "audit-collab",
|
projectId: "audit-collab",
|
||||||
@@ -1519,36 +1384,8 @@ const initialState: BossState = {
|
|||||||
capturedAt: "2026-03-25T11:58:00+08:00",
|
capturedAt: "2026-03-25T11:58:00+08:00",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
threadHandoffPackages: [
|
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",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
threadContextAlerts: [
|
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",
|
alertId: "alert-audit-hw",
|
||||||
threadId: "thread-audit-hardware",
|
threadId: "thread-audit-hardware",
|
||||||
@@ -1591,23 +1428,6 @@ const initialState: BossState = {
|
|||||||
suggestedNextAction: "优先重试本地 agent 证据上传,再做审计复验。",
|
suggestedNextAction: "优先重试本地 agent 证据上传,再做审计复验。",
|
||||||
autoRepairable: true,
|
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: [
|
opsRepairTickets: [
|
||||||
{
|
{
|
||||||
@@ -7794,7 +7614,7 @@ function pruneStaleAutoImportedProjectsForDevice(
|
|||||||
selectedCandidates: DeviceImportCandidate[],
|
selectedCandidates: DeviceImportCandidate[],
|
||||||
) {
|
) {
|
||||||
const activeSignatures = new Set(selectedCandidates.map((candidate) => candidateThreadSignature(candidate)));
|
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) => {
|
state.projects = state.projects.filter((project) => {
|
||||||
if (reservedProjectIds.has(project.id)) return true;
|
if (reservedProjectIds.has(project.id)) return true;
|
||||||
|
|||||||
@@ -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?.latestReplyAt, "2026-04-04T11:48:00+08:00");
|
||||||
assert.equal(thread?.latestReplyLabel, formatTimestampLabel("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,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|||||||
@@ -66,6 +66,58 @@ test.beforeEach(async () => {
|
|||||||
await writeState(structuredClone(baseState));
|
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) {
|
async function createAuthedRequest(url: string, method: "GET" | "POST" | "PATCH", body?: unknown) {
|
||||||
const session = await createAuthSession({
|
const session = await createAuthSession({
|
||||||
account: "17600003315",
|
account: "17600003315",
|
||||||
@@ -91,44 +143,24 @@ async function ensureTwoSingleThreadProjects() {
|
|||||||
return singles;
|
return singles;
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.ok(singles[0], "expected at least one seeded single-thread project");
|
const freshProjects = [
|
||||||
const seed = singles[0];
|
buildDispatchableThreadProject({
|
||||||
const clonedProject = {
|
id: "dispatch-confirm-a",
|
||||||
...seed,
|
projectName: "北区试产线主线程",
|
||||||
id: "boss-console-clone",
|
threadDisplayName: "北区试产线回归",
|
||||||
name: "Boss 移动控制台副线程",
|
body: "这里还在等待主 Agent 汇总阻塞点。",
|
||||||
deviceIds: [...seed.deviceIds],
|
}),
|
||||||
updatedAt: "2026-03-30T10:00:00+08:00",
|
buildDispatchableThreadProject({
|
||||||
lastMessageAt: "2026-03-30T10:00:00+08:00",
|
id: "dispatch-confirm-b",
|
||||||
preview: "副线程等待主 Agent 汇总阻塞点。",
|
projectName: "南区试产线主线程",
|
||||||
threadMeta: {
|
|
||||||
...seed.threadMeta,
|
|
||||||
projectId: "boss-console-clone",
|
|
||||||
threadId: "thread-boss-ui-clone",
|
|
||||||
threadDisplayName: "南区试产线回归",
|
threadDisplayName: "南区试产线回归",
|
||||||
folderName: "阻塞梳理",
|
body: "这里还在等待视觉链路复核。",
|
||||||
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: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
await writeState({
|
await writeState({
|
||||||
...state,
|
...state,
|
||||||
projects: [...state.projects, clonedProject],
|
projects: state.projects.concat(freshProjects),
|
||||||
});
|
});
|
||||||
|
|
||||||
const nextState = await readState();
|
const nextState = await readState();
|
||||||
|
|||||||
@@ -49,6 +49,58 @@ test.beforeEach(async () => {
|
|||||||
await writeState(structuredClone(baseState));
|
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 }) {
|
async function createAuthedRequest(projectId: string, body: { body: string; kind?: string }) {
|
||||||
const session = await createAuthSession({
|
const session = await createAuthSession({
|
||||||
account: "17600003315",
|
account: "17600003315",
|
||||||
@@ -69,73 +121,22 @@ async function createAuthedRequest(projectId: string, body: { body: string; kind
|
|||||||
|
|
||||||
async function ensureTwoSingleThreadProjects() {
|
async function ensureTwoSingleThreadProjects() {
|
||||||
const state = await readState();
|
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 = {
|
const primaryProject = {
|
||||||
...seed,
|
...buildDispatchableThreadProject({
|
||||||
id: "dispatch-thread-a",
|
id: "dispatch-thread-a",
|
||||||
name: "Boss 移动控制台主线程",
|
projectName: "北区试产线主线程",
|
||||||
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",
|
|
||||||
threadDisplayName: "北区试产线回归",
|
threadDisplayName: "北区试产线回归",
|
||||||
folderName: "阻塞梳理",
|
body: "主线程还在等待主 Agent 汇总阻塞点。",
|
||||||
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: [],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const secondaryProject = {
|
const secondaryProject = {
|
||||||
...seed,
|
...buildDispatchableThreadProject({
|
||||||
id: "dispatch-thread-b",
|
id: "dispatch-thread-b",
|
||||||
name: "Boss 移动控制台副线程",
|
projectName: "南区试产线主线程",
|
||||||
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",
|
|
||||||
threadDisplayName: "南区试产线回归",
|
threadDisplayName: "南区试产线回归",
|
||||||
folderName: "阻塞梳理",
|
body: "副线程还在等待视觉链路复核。",
|
||||||
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: [],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
await writeState({
|
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 () => {
|
test("POST /api/v1/projects/[projectId]/messages keeps dispatchPlan null for single-thread projects", async () => {
|
||||||
await setup();
|
await setup();
|
||||||
const state = await readState();
|
const [singleProject] = await ensureTwoSingleThreadProjects();
|
||||||
const singleProject = state.projects.find(
|
assert.ok(singleProject, "expected a synthetic single-thread project");
|
||||||
(project) => project.id !== "master-agent" && !project.isGroup,
|
|
||||||
);
|
|
||||||
assert.ok(singleProject, "expected a seeded single-thread project");
|
|
||||||
|
|
||||||
const response = await POST(await createAuthedRequest(singleProject.id, { body: "单线程消息" }), {
|
const response = await POST(await createAuthedRequest(singleProject.id, { body: "单线程消息" }), {
|
||||||
params: Promise.resolve({ projectId: singleProject.id }),
|
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 () => {
|
test("createIndependentGroupChat rejects non-thread members like master-agent", async () => {
|
||||||
await setup();
|
await setup();
|
||||||
const state = await readState();
|
const [realThread] = await ensureTwoSingleThreadProjects();
|
||||||
const realThread = state.projects.find(
|
|
||||||
(project) => project.id !== "master-agent" && !project.isGroup && Boolean(project.threadMeta.codexThreadRef),
|
|
||||||
);
|
|
||||||
assert.ok(realThread, "expected a real thread-backed project");
|
assert.ok(realThread, "expected a real thread-backed project");
|
||||||
|
|
||||||
await assert.rejects(
|
await assert.rejects(
|
||||||
|
|||||||
@@ -63,6 +63,46 @@ test.beforeEach(async () => {
|
|||||||
await resetMasterAgentControls();
|
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 () => {
|
test.after(async () => {
|
||||||
if (runtimeRoot) {
|
if (runtimeRoot) {
|
||||||
await rm(runtimeRoot, { recursive: true, force: true });
|
await rm(runtimeRoot, { recursive: true, force: true });
|
||||||
@@ -368,6 +408,7 @@ test("master-agent 对话控制 POST 清空后仍稳定回传 controls null", as
|
|||||||
|
|
||||||
test("非 master-agent 项目详情不应回传 agentControls 字段", async () => {
|
test("非 master-agent 项目详情不应回传 agentControls 字段", async () => {
|
||||||
await setup();
|
await setup();
|
||||||
|
const ordinaryProjectId = await ensureOrdinaryProject();
|
||||||
|
|
||||||
const session = await createAuthSession({
|
const session = await createAuthSession({
|
||||||
account: "17600003315",
|
account: "17600003315",
|
||||||
@@ -377,13 +418,13 @@ test("非 master-agent 项目详情不应回传 agentControls 字段", async ()
|
|||||||
});
|
});
|
||||||
|
|
||||||
const response = await getProjectRoute(
|
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",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
cookie: `${AUTH_SESSION_COOKIE}=${session.sessionToken}`,
|
cookie: `${AUTH_SESSION_COOKIE}=${session.sessionToken}`,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
{ params: Promise.resolve({ projectId: "boss-console" }) },
|
{ params: Promise.resolve({ projectId: ordinaryProjectId }) },
|
||||||
);
|
);
|
||||||
|
|
||||||
assert.equal(response.status, 200);
|
assert.equal(response.status, 200);
|
||||||
@@ -841,6 +882,7 @@ test("GET /agent-controls 在未显式设置 BOSS_STATE_FILE 时仍可正常读
|
|||||||
|
|
||||||
test("GET /agent-controls rejects ordinary projects", async () => {
|
test("GET /agent-controls rejects ordinary projects", async () => {
|
||||||
await setup();
|
await setup();
|
||||||
|
const ordinaryProjectId = await ensureOrdinaryProject();
|
||||||
|
|
||||||
const session = await createAuthSession({
|
const session = await createAuthSession({
|
||||||
account: "17600003315",
|
account: "17600003315",
|
||||||
@@ -850,13 +892,13 @@ test("GET /agent-controls rejects ordinary projects", async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const response = await getAgentControlsRoute(
|
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",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
cookie: `${AUTH_SESSION_COOKIE}=${session.sessionToken}`,
|
cookie: `${AUTH_SESSION_COOKIE}=${session.sessionToken}`,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
{ params: Promise.resolve({ projectId: "boss-console" }) },
|
{ params: Promise.resolve({ projectId: ordinaryProjectId }) },
|
||||||
);
|
);
|
||||||
|
|
||||||
assert.equal(response.status, 404);
|
assert.equal(response.status, 404);
|
||||||
@@ -940,10 +982,11 @@ test("master-agent 对话控制 POST 会稳定拒绝非法 backendOverride", asy
|
|||||||
|
|
||||||
test("master-agent controls helper 不会写入普通项目", async () => {
|
test("master-agent controls helper 不会写入普通项目", async () => {
|
||||||
await setup();
|
await setup();
|
||||||
|
const ordinaryProjectId = await ensureOrdinaryProject();
|
||||||
|
|
||||||
await assert.rejects(
|
await assert.rejects(
|
||||||
() =>
|
() =>
|
||||||
updateProjectAgentControls("boss-console", {
|
updateProjectAgentControls(ordinaryProjectId, {
|
||||||
modelOverride: "gpt-5.4",
|
modelOverride: "gpt-5.4",
|
||||||
reasoningEffortOverride: "low",
|
reasoningEffortOverride: "low",
|
||||||
}),
|
}),
|
||||||
@@ -951,7 +994,7 @@ test("master-agent controls helper 不会写入普通项目", async () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const state = await readState();
|
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(project?.agentControls, undefined);
|
||||||
assert.equal(await getProjectAgentControls("boss-console"), null);
|
assert.equal(await getProjectAgentControls(ordinaryProjectId), null);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -44,6 +44,39 @@ test.beforeEach(async () => {
|
|||||||
await writeState(structuredClone(baseState));
|
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 }) {
|
async function createAuthedRequest(projectId: string, body: { body: string }) {
|
||||||
const session = await createAuthSession({
|
const session = await createAuthSession({
|
||||||
account: "17600003315",
|
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 () => {
|
test("single-thread message rejects projects without a real codex thread binding", async () => {
|
||||||
await setup();
|
await setup();
|
||||||
const state = await readState();
|
const state = await readState();
|
||||||
const singleProject = state.projects.find(
|
const singleProject = buildSingleThreadProject("preflight-thread");
|
||||||
(project) => project.id !== "master-agent" && !project.isGroup,
|
|
||||||
);
|
|
||||||
assert.ok(singleProject, "expected a seeded single-thread project");
|
|
||||||
|
|
||||||
await writeState({
|
await writeState({
|
||||||
...state,
|
...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.id === singleProject.id
|
||||||
? {
|
? {
|
||||||
...project,
|
...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.code, "THREAD_BINDING_REQUIRED");
|
||||||
assert.equal(payload.message, "当前线程还没有绑定真实 Codex 线程,请先重新导入该线程后再试。");
|
assert.equal(payload.message, "当前线程还没有绑定真实 Codex 线程,请先重新导入该线程后再试。");
|
||||||
|
|
||||||
const nextState = await readState();
|
const finalState = await readState();
|
||||||
const queuedTask = nextState.masterAgentTasks.find(
|
const queuedTask = finalState.masterAgentTasks.find(
|
||||||
(task) => task.projectId === singleProject.id && task.taskType === "conversation_reply",
|
(task) => task.projectId === singleProject.id && task.taskType === "conversation_reply",
|
||||||
);
|
);
|
||||||
assert.equal(queuedTask, undefined);
|
assert.equal(queuedTask, undefined);
|
||||||
|
|||||||
Reference in New Issue
Block a user