feat: prioritize thread status in master agent prompt
This commit is contained in:
@@ -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<ReturnType<typeof readState>>,
|
||||
document: Awaited<ReturnType<typeof readState>>["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<ReturnType<typeof readState>>,
|
||||
event: Awaited<ReturnType<typeof readState>>["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 "";
|
||||
|
||||
197
tests/master-agent-thread-status-prompt.test.ts
Normal file
197
tests/master-agent-thread-status-prompt.test.ts
Normal file
@@ -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("关键时刻深拉线程兜底:"),
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user