From 7d578aa12f887e1416bd7e38b1b267232e02d40c Mon Sep 17 00:00:00 2001 From: kris Date: Sat, 4 Apr 2026 11:37:41 +0800 Subject: [PATCH] feat: prioritize thread status in master agent prompt --- src/lib/boss-master-agent.ts | 130 ++++++++++-- .../master-agent-thread-status-prompt.test.ts | 197 ++++++++++++++++++ 2 files changed, 305 insertions(+), 22 deletions(-) create mode 100644 tests/master-agent-thread-status-prompt.test.ts diff --git a/src/lib/boss-master-agent.ts b/src/lib/boss-master-agent.ts index 7e58d5a..fdc7224 100644 --- a/src/lib/boss-master-agent.ts +++ b/src/lib/boss-master-agent.ts @@ -110,13 +110,28 @@ export async function resolveMasterAgentExecutionConfig( "medium"; const promptPolicy = getMasterAgentPromptPolicyView(state); const userPrompt = getUserMasterPromptView(state, resolvedAccountId); - const memoryScope = listUserMasterMemoriesView(state, resolvedAccountId, { includeArchived: false }); + const memoryAccountIds = [...new Set([resolvedAccountId, state.user.account, runtime.account.accountId].filter( + (value): value is string => Boolean(value?.trim()), + ))]; + const memoryScope = [...new Map( + memoryAccountIds.flatMap((memoryAccountId) => + listUserMasterMemoriesView(state, memoryAccountId, { includeArchived: false }), + ).map((memory) => [memory.memoryId, memory] as const), + ).values()]; const { projectMemories, userMemories } = resolveRuntimeRelevantMemories({ projectId, requestText, memories: memoryScope, }); - const touchedMemoryIds = [...projectMemories, ...userMemories].map((memory) => memory.memoryId); + const resolvedProjectMemories = [ + ...new Map( + [ + ...projectMemories, + ...state.masterAgentMemories.filter((memory) => memory.scope === "project"), + ].map((memory) => [memory.memoryId, memory] as const), + ).values(), + ].slice(0, 6); + const touchedMemoryIds = [...resolvedProjectMemories, ...userMemories].map((memory) => memory.memoryId); if (touchedMemoryIds.length > 0) { await touchUserMasterMemories(touchedMemoryIds, resolvedAccountId); } @@ -131,13 +146,13 @@ export async function resolveMasterAgentExecutionConfig( reasoningEffort, promptPolicy, userPrompt, - projectMemories, + projectMemories: resolvedProjectMemories, userMemories, executionPrompt: buildExecutionPrompt({ globalPrompt: promptPolicy?.globalPrompt ?? null, userPrompt: userPrompt?.content ?? null, conversationPrompt: scopedAgentControls?.promptOverride ?? null, - projectMemories, + projectMemories: resolvedProjectMemories, userMemories, requestText: requestText ?? "", }), @@ -246,7 +261,27 @@ function buildRuntimeDigest( .filter((update) => update.status === "available") .map((update) => `${update.version} -> ${update.targetScope}`) .join("\n"); - const activeProjectUnderstandings = state.projects + const threadStatusDocuments = [...state.threadStatusDocuments] + .sort((left, right) => { + const updatedDelta = Date.parse(right.updatedAt) - Date.parse(left.updatedAt); + if (updatedDelta !== 0) { + return updatedDelta; + } + return right.documentId.localeCompare(left.documentId); + }) + .slice(0, 6) + .map((document) => buildThreadStatusDocumentDigest(state, document)); + const recentProgressEvents = [...state.threadProgressEvents] + .sort((left, right) => { + const createdDelta = Date.parse(right.createdAt) - Date.parse(left.createdAt); + if (createdDelta !== 0) { + return createdDelta; + } + return right.eventId.localeCompare(left.eventId); + }) + .slice(0, 8) + .map((event) => buildThreadProgressEventDigest(state, event)); + const deepPullThreadUnderstandings = state.projects .filter((project) => project.id !== "master-agent" && project.projectUnderstanding) .sort((left, right) => String(right.projectUnderstanding?.updatedAt ?? right.lastMessageAt).localeCompare( @@ -254,20 +289,7 @@ function buildRuntimeDigest( ), ) .slice(0, 3) - .map((project) => { - const understanding = project.projectUnderstanding!; - return [ - `${project.name}:`, - understanding.projectGoal ? `目标=${understanding.projectGoal}` : undefined, - understanding.currentProgress ? `进度=${understanding.currentProgress}` : undefined, - understanding.technicalArchitecture ? `架构=${understanding.technicalArchitecture}` : undefined, - understanding.currentBlockers ? `阻塞=${understanding.currentBlockers}` : undefined, - understanding.recommendedNextStep ? `下一步=${understanding.recommendedNextStep}` : undefined, - ] - .filter(Boolean) - .join(" / "); - }) - .join("\n"); + .map((project) => buildDeepPullThreadUnderstandingDigest(project)); const authSummary = [ `登录会话策略:成功登录后默认保持 ${Math.round(AUTH_SESSION_TTL_MS / 24 / 60 / 60_000)} 天。`, @@ -281,15 +303,21 @@ function buildRuntimeDigest( `当前时间:${new Date().toISOString()}`, `用户消息:${requestText}`, "", + "线程状态文档:", + threadStatusDocuments.length > 0 ? threadStatusDocuments.join("\n") : "无", + "", + "最近进展事件:", + recentProgressEvents.length > 0 ? recentProgressEvents.join("\n") : "无", + "", + "关键时刻深拉线程兜底:", + deepPullThreadUnderstandings.length > 0 ? deepPullThreadUnderstandings.join("\n") : "无", + "", "最近主 Agent 对话:", recentMessages || "无", "", "最新 APP 日志:", recentLogs || "无", "", - "活跃项目理解:", - activeProjectUnderstandings || "无", - "", "高风险线程:", riskyThreads || "无", "", @@ -304,6 +332,64 @@ function buildRuntimeDigest( ].join("\n"); } +function buildThreadStatusDocumentDigest( + state: Awaited>, + document: Awaited>["threadStatusDocuments"][number], +) { + const projectName = state.projects.find((project) => project.id === document.projectId)?.name ?? document.projectId; + return [ + `${projectName} / ${document.threadDisplayName}:`, + document.folderName ? `文件夹=${document.folderName}` : undefined, + document.projectGoal ? `目标=${document.projectGoal}` : undefined, + document.currentPhase ? `阶段=${document.currentPhase}` : undefined, + document.currentProgress ? `进度=${document.currentProgress}` : undefined, + document.technicalArchitecture ? `架构=${document.technicalArchitecture}` : undefined, + document.currentBlockers ? `阻塞=${document.currentBlockers}` : undefined, + document.recommendedNextStep ? `下一步=${document.recommendedNextStep}` : undefined, + document.keyFiles.length > 0 ? `关键文件=${document.keyFiles.slice(0, 3).join(", ")}` : undefined, + document.keyCommands.length > 0 ? `关键命令=${document.keyCommands.slice(0, 2).join(", ")}` : undefined, + `更新时间=${document.updatedAt}`, + ] + .filter(Boolean) + .join(" / "); +} + +function buildThreadProgressEventDigest( + state: Awaited>, + event: Awaited>["threadProgressEvents"][number], +) { + const projectName = state.projects.find((project) => project.id === event.projectId)?.name ?? event.projectId; + return [ + `${projectName} / ${event.threadDisplayName}:`, + `时间=${event.createdAt}`, + `类型=${event.eventType}`, + `摘要=${event.summary}`, + event.phase ? `阶段=${event.phase}` : undefined, + event.blockerDelta ? `阻塞变化=${event.blockerDelta}` : undefined, + event.nextStepDelta ? `下一步变化=${event.nextStepDelta}` : undefined, + ] + .filter(Boolean) + .join(" / "); +} + +function buildDeepPullThreadUnderstandingDigest(project: Project) { + const understanding = project.projectUnderstanding; + if (!understanding) { + return ""; + } + + return [ + `${project.name}:`, + understanding.projectGoal ? `目标=${understanding.projectGoal}` : undefined, + understanding.currentProgress ? `进度=${understanding.currentProgress}` : undefined, + understanding.technicalArchitecture ? `架构=${understanding.technicalArchitecture}` : undefined, + understanding.currentBlockers ? `阻塞=${understanding.currentBlockers}` : undefined, + understanding.recommendedNextStep ? `下一步=${understanding.recommendedNextStep}` : undefined, + ] + .filter(Boolean) + .join(" / "); +} + function extractResponseText(payload: unknown): string { if (!payload || typeof payload !== "object") { return ""; diff --git a/tests/master-agent-thread-status-prompt.test.ts b/tests/master-agent-thread-status-prompt.test.ts new file mode 100644 index 0000000..ba12bd2 --- /dev/null +++ b/tests/master-agent-thread-status-prompt.test.ts @@ -0,0 +1,197 @@ +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 saveAiAccount: (typeof import("../src/lib/boss-data"))["saveAiAccount"]; +let updateMasterAgentPromptPolicy: (typeof import("../src/lib/boss-data"))["updateMasterAgentPromptPolicy"]; +let updateUserMasterPrompt: (typeof import("../src/lib/boss-data"))["updateUserMasterPrompt"]; +let createUserMasterMemory: (typeof import("../src/lib/boss-data"))["createUserMasterMemory"]; +let updateProjectAgentControls: (typeof import("../src/lib/boss-data"))["updateProjectAgentControls"]; +let resolveMasterAgentExecutionConfig: (typeof import("../src/lib/boss-master-agent"))["resolveMasterAgentExecutionConfig"]; +let replyToMasterAgentUserMessage: (typeof import("../src/lib/boss-master-agent"))["replyToMasterAgentUserMessage"]; + +async function setup() { + if (runtimeRoot) return; + + runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-master-agent-thread-status-")); + process.env.BOSS_RUNTIME_ROOT = runtimeRoot; + process.env.BOSS_STATE_FILE = path.join(runtimeRoot, "boss-state.json"); + + const [data, masterAgent] = await Promise.all([ + import("../src/lib/boss-data.ts"), + import("../src/lib/boss-master-agent.ts"), + ]); + + readState = data.readState; + writeState = data.writeState; + saveAiAccount = data.saveAiAccount; + updateMasterAgentPromptPolicy = data.updateMasterAgentPromptPolicy; + updateUserMasterPrompt = data.updateUserMasterPrompt; + createUserMasterMemory = data.createUserMasterMemory; + updateProjectAgentControls = data.updateProjectAgentControls; + resolveMasterAgentExecutionConfig = masterAgent.resolveMasterAgentExecutionConfig; + replyToMasterAgentUserMessage = masterAgent.replyToMasterAgentUserMessage; +} + +test.after(async () => { + if (runtimeRoot) { + await rm(runtimeRoot, { recursive: true, force: true }); + } +}); + +test("主 Agent 执行 prompt 默认读取线程状态文档、最近进展事件和项目记忆,并保留深拉兜底", async () => { + await setup(); + + await saveAiAccount({ + accountId: "master-codex-primary", + label: "主 GPT", + role: "primary", + provider: "master_codex_node", + displayName: "17600003315 · Master Codex Node", + nodeId: "mac-studio", + nodeLabel: "Mac Studio", + enabled: true, + setActive: true, + status: "ready", + loginStatusNote: "主节点可用。", + }); + await updateMasterAgentPromptPolicy({ + globalPrompt: "管理员全局主提示词", + updatedBy: "17600003315", + }); + await updateUserMasterPrompt("17600003315", "用户私有主提示词"); + await updateProjectAgentControls("master-agent", { + promptOverride: "当前对话提示词", + }); + await createUserMasterMemory({ + account: "17600003315", + scope: "project", + projectId: "master-agent", + title: "项目记忆", + content: "项目记忆正文", + memoryType: "project_progress", + tags: ["线程状态"], + }); + await createUserMasterMemory({ + account: "master-codex-primary", + scope: "project", + projectId: "master-agent", + title: "项目记忆", + content: "项目记忆正文", + memoryType: "project_progress", + tags: ["线程状态"], + }); + + const state = await readState(); + const auditProject = state.projects.find((project) => project.id === "audit-collab"); + assert.ok(auditProject, "expected seeded audit-collab project"); + auditProject!.projectUnderstanding = { + projectGoal: "深拉兜底目标", + currentProgress: "深拉兜底进度", + technicalArchitecture: "深拉兜底架构", + currentBlockers: "深拉兜底阻塞", + recommendedNextStep: "深拉兜底下一步", + sourceTaskId: "task-deep-pull", + updatedAt: "2026-04-04T18:00:00+08:00", + sourceKind: "thread_sync", + }; + state.threadStatusDocuments = [ + { + documentId: "thread-status-doc-1", + projectId: "audit-collab", + threadId: "thread-audit-chief", + threadDisplayName: "审计对话", + folderName: "审计群聊", + deviceId: "mac-studio", + projectGoal: "线程状态目标", + currentPhase: "线程状态阶段", + currentProgress: "线程状态进度", + technicalArchitecture: "线程状态架构", + currentBlockers: "线程状态阻塞", + recommendedNextStep: "线程状态下一步", + keyFiles: ["src/lib/boss-master-agent.ts"], + keyCommands: ["npm run build"], + updatedAt: "2026-04-04T18:01:00+08:00", + sourceTaskId: "task-thread-status", + sourceKind: "incremental_sync", + }, + ]; + state.threadProgressEvents = [ + { + eventId: "thread-progress-event-1", + projectId: "audit-collab", + threadId: "thread-audit-chief", + threadDisplayName: "审计对话", + deviceId: "mac-studio", + eventType: "progress_updated", + summary: "最近进展事件摘要", + phase: "线程状态阶段", + blockerDelta: "线程状态阻塞", + nextStepDelta: "线程状态下一步", + createdAt: "2026-04-04T18:02:00+08:00", + sourceTaskId: "task-thread-progress", + }, + ]; + await writeState(state); + + const resolved = await resolveMasterAgentExecutionConfig( + "master-agent", + "17600003315", + "继续推进线程状态同步", + ); + assert.ok(resolved.projectMemories.length > 0); + assert.equal(resolved.projectMemories[0]?.content, "项目记忆正文"); + assert.ok(resolved.executionPrompt.includes("当前对话提示词")); + assert.ok( + resolved.executionPrompt.indexOf("管理员全局主提示词:") < + resolved.executionPrompt.indexOf("用户私有主提示词:") && + resolved.executionPrompt.indexOf("用户私有主提示词:") < + resolved.executionPrompt.indexOf("当前对话附加提示词:") && + resolved.executionPrompt.indexOf("当前对话附加提示词:") < + resolved.executionPrompt.indexOf("当前消息:"), + ); + + const reply = await replyToMasterAgentUserMessage({ + requestText: "继续推进线程状态同步", + requestedBy: "Boss 超级管理员", + requestedByAccount: "17600003315", + mode: "enqueue", + }); + assert.equal(reply.ok, true); + assert.equal(reply.masterReplyState, "queued"); + + const queuedTask = (await readState()).masterAgentTasks.find( + (task) => task.projectId === "master-agent" && task.requestText === "继续推进线程状态同步", + ); + assert.ok(queuedTask, "expected master-agent task to be queued"); + assert.ok(queuedTask?.executionPrompt.includes("线程状态文档:")); + assert.ok(queuedTask?.executionPrompt.includes("线程状态目标")); + assert.ok(queuedTask?.executionPrompt.includes("最近进展事件:")); + assert.ok(queuedTask?.executionPrompt.includes("最近进展事件摘要")); + assert.ok(queuedTask?.executionPrompt.includes("关键时刻深拉线程兜底:")); + assert.ok(queuedTask?.executionPrompt.includes("深拉兜底目标")); + + assert.ok( + queuedTask?.executionPrompt.indexOf("管理员全局主提示词:") < + queuedTask.executionPrompt.indexOf("用户私有主提示词:") && + queuedTask.executionPrompt.indexOf("用户私有主提示词:") < + queuedTask.executionPrompt.indexOf("当前对话附加提示词:") && + queuedTask.executionPrompt.indexOf("当前对话附加提示词:") < + queuedTask.executionPrompt.indexOf("项目记忆:") && + queuedTask.executionPrompt.indexOf("项目记忆:") < + queuedTask.executionPrompt.indexOf("当前消息:") && + queuedTask.executionPrompt.indexOf("当前消息:") < + queuedTask.executionPrompt.indexOf("当前对话覆盖:") && + queuedTask.executionPrompt.indexOf("当前对话覆盖:") < + queuedTask.executionPrompt.indexOf("线程状态文档:") && + queuedTask.executionPrompt.indexOf("线程状态文档:") < + queuedTask.executionPrompt.indexOf("最近进展事件:") && + queuedTask.executionPrompt.indexOf("最近进展事件:") < + queuedTask.executionPrompt.indexOf("关键时刻深拉线程兜底:"), + ); +});