diff --git a/android/app/src/main/java/com/hyzq/boss/MasterAgentMemoryActivity.java b/android/app/src/main/java/com/hyzq/boss/MasterAgentMemoryActivity.java index e8e1d31..a4f133a 100644 --- a/android/app/src/main/java/com/hyzq/boss/MasterAgentMemoryActivity.java +++ b/android/app/src/main/java/com/hyzq/boss/MasterAgentMemoryActivity.java @@ -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); diff --git a/src/components/app-ui.tsx b/src/components/app-ui.tsx index 4622caf..ac24854 100644 --- a/src/components/app-ui.tsx +++ b/src/components/app-ui.tsx @@ -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<{ diff --git a/src/components/master-agent-prompt-memory-client.tsx b/src/components/master-agent-prompt-memory-client.tsx index 25a3cdf..8ee1187 100644 --- a/src/components/master-agent-prompt-memory-client.tsx +++ b/src/components/master-agent-prompt-memory-client.tsx @@ -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} 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; diff --git a/tests/conversation-home-items.test.ts b/tests/conversation-home-items.test.ts index 3865ea7..8344378 100644 --- a/tests/conversation-home-items.test.ts +++ b/tests/conversation-home-items.test.ts @@ -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, + ); +}); diff --git a/tests/dispatch-plan-confirmation.test.ts b/tests/dispatch-plan-confirmation.test.ts index 45ee5b0..2e1c50a 100644 --- a/tests/dispatch-plan-confirmation.test.ts +++ b/tests/dispatch-plan-confirmation.test.ts @@ -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(); diff --git a/tests/group-message-dispatch-plan.test.ts b/tests/group-message-dispatch-plan.test.ts index ef7fbaa..1d26db9 100644 --- a/tests/group-message-dispatch-plan.test.ts +++ b/tests/group-message-dispatch-plan.test.ts @@ -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( diff --git a/tests/master-agent-chat-controls.test.ts b/tests/master-agent-chat-controls.test.ts index 93bf217..db36abb 100644 --- a/tests/master-agent-chat-controls.test.ts +++ b/tests/master-agent-chat-controls.test.ts @@ -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); }); diff --git a/tests/thread-message-preflight.test.ts b/tests/thread-message-preflight.test.ts index 4faa052..55b8660 100644 --- a/tests/thread-message-preflight.test.ts +++ b/tests/thread-message-preflight.test.ts @@ -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);