feat: add master-agent prompts and memory management
This commit is contained in:
@@ -7,6 +7,8 @@ import { mkdtemp, rm } from "node:fs/promises";
|
||||
let runtimeRoot = "";
|
||||
let saveAiAccount: (typeof import("../src/lib/boss-data"))["saveAiAccount"];
|
||||
let updateProjectAgentControls: (typeof import("../src/lib/boss-data"))["updateProjectAgentControls"];
|
||||
let updateMasterAgentPromptPolicy: (typeof import("../src/lib/boss-data"))["updateMasterAgentPromptPolicy"];
|
||||
let updateUserMasterPrompt: (typeof import("../src/lib/boss-data"))["updateUserMasterPrompt"];
|
||||
let resolveMasterAgentExecutionConfig: (typeof import("../src/lib/boss-master-agent"))["resolveMasterAgentExecutionConfig"];
|
||||
|
||||
async function setup() {
|
||||
@@ -23,6 +25,8 @@ async function setup() {
|
||||
|
||||
saveAiAccount = data.saveAiAccount;
|
||||
updateProjectAgentControls = data.updateProjectAgentControls;
|
||||
updateMasterAgentPromptPolicy = data.updateMasterAgentPromptPolicy;
|
||||
updateUserMasterPrompt = data.updateUserMasterPrompt;
|
||||
resolveMasterAgentExecutionConfig = masterAgent.resolveMasterAgentExecutionConfig;
|
||||
}
|
||||
|
||||
@@ -63,3 +67,39 @@ test("当前对话 override 优先于主控账号默认值", async () => {
|
||||
assert.equal(resolved.account.accountId, "master-codex-primary");
|
||||
assert.equal(resolved.account.model, "gpt-4.1-mini");
|
||||
});
|
||||
|
||||
test("主 Agent 执行配置会合成管理员提示词、用户提示词和当前对话提示词", async () => {
|
||||
await setup();
|
||||
|
||||
await saveAiAccount({
|
||||
accountId: "master-codex-primary",
|
||||
label: "主 GPT",
|
||||
role: "primary",
|
||||
provider: "master_codex_node",
|
||||
displayName: "Mac 上的 Master Codex Node",
|
||||
nodeId: "local-codex-node",
|
||||
nodeLabel: "本机 Codex",
|
||||
model: "gpt-4.1-mini",
|
||||
enabled: true,
|
||||
setActive: true,
|
||||
loginStatusNote: "通过绑定的 Master Codex Node 对话。",
|
||||
});
|
||||
|
||||
await updateMasterAgentPromptPolicy({
|
||||
globalPrompt: "全局主提示词",
|
||||
updatedBy: "17600003315",
|
||||
});
|
||||
await updateUserMasterPrompt("17600003315", "用户私有主提示词");
|
||||
await updateProjectAgentControls("master-agent", {
|
||||
modelOverride: "gpt-5.4",
|
||||
reasoningEffortOverride: "high",
|
||||
promptOverride: "当前对话提示词",
|
||||
});
|
||||
|
||||
const resolved = await resolveMasterAgentExecutionConfig("master-agent");
|
||||
|
||||
assert.equal(resolved.promptPolicy?.globalPrompt, "全局主提示词");
|
||||
assert.equal(resolved.userPrompt?.content, "用户私有主提示词");
|
||||
assert.equal(resolved.projectPromptOverride, "当前对话提示词");
|
||||
assert.equal(resolved.promptPolicy?.updatedBy, "17600003315");
|
||||
});
|
||||
|
||||
59
tests/master-agent-memory-ingestion.test.ts
Normal file
59
tests/master-agent-memory-ingestion.test.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
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 queueMasterAgentTask: (typeof import("../src/lib/boss-data"))["queueMasterAgentTask"];
|
||||
let completeMasterAgentTask: (typeof import("../src/lib/boss-data"))["completeMasterAgentTask"];
|
||||
let listUserMasterMemories: (typeof import("../src/lib/boss-data"))["listUserMasterMemories"];
|
||||
|
||||
async function setup() {
|
||||
if (runtimeRoot) return;
|
||||
|
||||
runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-master-agent-memory-ingestion-"));
|
||||
process.env.BOSS_RUNTIME_ROOT = runtimeRoot;
|
||||
process.env.BOSS_STATE_FILE = path.join(runtimeRoot, "boss-state.json");
|
||||
|
||||
const data = await import("../src/lib/boss-data.ts");
|
||||
queueMasterAgentTask = data.queueMasterAgentTask;
|
||||
completeMasterAgentTask = data.completeMasterAgentTask;
|
||||
listUserMasterMemories = data.listUserMasterMemories;
|
||||
}
|
||||
|
||||
test.after(async () => {
|
||||
if (runtimeRoot) {
|
||||
await rm(runtimeRoot, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("主 Agent 完成对话后会自动沉淀用户偏好和项目记忆", async () => {
|
||||
await setup();
|
||||
|
||||
const task = await queueMasterAgentTask({
|
||||
projectId: "master-agent",
|
||||
requestMessageId: "msg-user-1",
|
||||
requestText: "boss 项目后续都按微信式交互来做,并且默认中文回复。",
|
||||
executionPrompt: "prompt",
|
||||
requestedBy: "17600003315",
|
||||
requestedByAccount: "17600003315",
|
||||
deviceId: "master-agent-openai",
|
||||
});
|
||||
|
||||
await completeMasterAgentTask({
|
||||
taskId: task.taskId,
|
||||
deviceId: "master-agent-openai",
|
||||
status: "completed",
|
||||
replyBody: "boss 项目当前进度已更新:会话页会继续按微信式交互推进。",
|
||||
});
|
||||
|
||||
const memories = await listUserMasterMemories("17600003315", { includeArchived: false });
|
||||
const globalMemory = memories.find((memory) => memory.scope === "global");
|
||||
const projectMemory = memories.find((memory) => memory.scope === "project" && memory.projectId === "boss-console");
|
||||
|
||||
assert.ok(globalMemory, "expected a global user memory");
|
||||
assert.ok(projectMemory, "expected a project-scoped memory");
|
||||
assert.match(globalMemory?.content ?? "", /微信式交互|中文回复/);
|
||||
assert.match(projectMemory?.content ?? "", /boss 项目当前进度已更新/);
|
||||
});
|
||||
131
tests/master-agent-prompts-memory-routes.test.ts
Normal file
131
tests/master-agent-prompts-memory-routes.test.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
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";
|
||||
import { NextRequest } from "next/server";
|
||||
|
||||
let runtimeRoot = "";
|
||||
let AUTH_SESSION_COOKIE = "";
|
||||
let createAuthSession: (typeof import("../src/lib/boss-data"))["createAuthSession"];
|
||||
let getMasterAgentPromptPolicyRoute: typeof import("../src/app/api/v1/master-agent/prompt-policy/route");
|
||||
let getUserMasterPromptRoute: typeof import("../src/app/api/v1/master-agent/prompt/route");
|
||||
let getUserMasterMemoriesRoute: typeof import("../src/app/api/v1/master-agent/memories/route");
|
||||
let patchUserMasterMemoryRoute: typeof import("../src/app/api/v1/master-agent/memories/[memoryId]/route");
|
||||
|
||||
async function setup() {
|
||||
if (runtimeRoot) return;
|
||||
|
||||
runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-master-agent-prompts-memory-routes-"));
|
||||
process.env.BOSS_RUNTIME_ROOT = runtimeRoot;
|
||||
process.env.BOSS_STATE_FILE = path.join(runtimeRoot, "boss-state.json");
|
||||
|
||||
const [data, auth, promptPolicyRoute, userPromptRoute, memoriesRoute, memoryRoute] = await Promise.all([
|
||||
import("../src/lib/boss-data.ts"),
|
||||
import("../src/lib/boss-auth.ts"),
|
||||
import("../src/app/api/v1/master-agent/prompt-policy/route.ts"),
|
||||
import("../src/app/api/v1/master-agent/prompt/route.ts"),
|
||||
import("../src/app/api/v1/master-agent/memories/route.ts"),
|
||||
import("../src/app/api/v1/master-agent/memories/[memoryId]/route.ts"),
|
||||
]);
|
||||
|
||||
createAuthSession = data.createAuthSession;
|
||||
AUTH_SESSION_COOKIE = auth.AUTH_SESSION_COOKIE;
|
||||
getMasterAgentPromptPolicyRoute = promptPolicyRoute;
|
||||
getUserMasterPromptRoute = userPromptRoute;
|
||||
getUserMasterMemoriesRoute = memoriesRoute;
|
||||
patchUserMasterMemoryRoute = memoryRoute;
|
||||
}
|
||||
|
||||
async function createAuthedRequest(account = "17600003315", role: "member" | "admin" | "highest_admin" = "highest_admin") {
|
||||
await setup();
|
||||
const session = await createAuthSession({
|
||||
account,
|
||||
role,
|
||||
displayName: "Boss 超级管理员",
|
||||
loginMethod: "password",
|
||||
});
|
||||
|
||||
return {
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
cookie: `${AUTH_SESSION_COOKIE}=${session.sessionToken}`,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
test.after(async () => {
|
||||
if (runtimeRoot) {
|
||||
await rm(runtimeRoot, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("master-agent prompt and memory routes support admin prompt, user prompt, and memory CRUD", async () => {
|
||||
await setup();
|
||||
|
||||
const adminRequest = await createAuthedRequest();
|
||||
const promptGet = await getMasterAgentPromptPolicyRoute.GET(
|
||||
new NextRequest("http://127.0.0.1:3000/api/v1/master-agent/prompt-policy", {
|
||||
method: "GET",
|
||||
headers: adminRequest.headers,
|
||||
}),
|
||||
);
|
||||
assert.equal(promptGet.status, 200);
|
||||
|
||||
const promptPost = await getMasterAgentPromptPolicyRoute.POST(
|
||||
new NextRequest("http://127.0.0.1:3000/api/v1/master-agent/prompt-policy", {
|
||||
method: "POST",
|
||||
headers: adminRequest.headers,
|
||||
body: JSON.stringify({ globalPrompt: "管理员全局主提示词" }),
|
||||
}),
|
||||
);
|
||||
assert.equal(promptPost.status, 200);
|
||||
|
||||
const userPromptPost = await getUserMasterPromptRoute.POST(
|
||||
new NextRequest("http://127.0.0.1:3000/api/v1/master-agent/prompt", {
|
||||
method: "POST",
|
||||
headers: adminRequest.headers,
|
||||
body: JSON.stringify({ content: "用户私有主提示词" }),
|
||||
}),
|
||||
);
|
||||
assert.equal(userPromptPost.status, 200);
|
||||
|
||||
const memoriesPost = await getUserMasterMemoriesRoute.POST(
|
||||
new NextRequest("http://127.0.0.1:3000/api/v1/master-agent/memories", {
|
||||
method: "POST",
|
||||
headers: adminRequest.headers,
|
||||
body: JSON.stringify({
|
||||
scope: "project",
|
||||
projectId: "master-agent",
|
||||
title: "项目进度",
|
||||
content: "主 Agent 提示词与记忆链路完成。",
|
||||
memoryType: "project_progress",
|
||||
tags: ["主Agent", "记忆"],
|
||||
}),
|
||||
}),
|
||||
);
|
||||
assert.equal(memoriesPost.status, 200);
|
||||
|
||||
const memoriesPayload = (await memoriesPost.json()) as {
|
||||
ok: boolean;
|
||||
memory?: { memoryId: string };
|
||||
};
|
||||
assert.equal(memoriesPayload.ok, true);
|
||||
assert.ok(memoriesPayload.memory?.memoryId);
|
||||
|
||||
const patchResponse = await patchUserMasterMemoryRoute.PATCH(
|
||||
new NextRequest(
|
||||
`http://127.0.0.1:3000/api/v1/master-agent/memories/${memoriesPayload.memory?.memoryId}`,
|
||||
{
|
||||
method: "PATCH",
|
||||
headers: adminRequest.headers,
|
||||
body: JSON.stringify({
|
||||
content: "主 Agent 提示词与记忆链路已完成。",
|
||||
tags: ["提示词", "记忆"],
|
||||
}),
|
||||
},
|
||||
),
|
||||
{ params: Promise.resolve({ memoryId: memoriesPayload.memory?.memoryId ?? "" }) },
|
||||
);
|
||||
assert.equal(patchResponse.status, 200);
|
||||
});
|
||||
87
tests/master-agent-prompts-memory-state.test.ts
Normal file
87
tests/master-agent-prompts-memory-state.test.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
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 getMasterAgentPromptPolicy: (typeof import("../src/lib/boss-data"))["getMasterAgentPromptPolicy"];
|
||||
let updateMasterAgentPromptPolicy: (typeof import("../src/lib/boss-data"))["updateMasterAgentPromptPolicy"];
|
||||
let getUserMasterPrompt: (typeof import("../src/lib/boss-data"))["getUserMasterPrompt"];
|
||||
let updateUserMasterPrompt: (typeof import("../src/lib/boss-data"))["updateUserMasterPrompt"];
|
||||
let listUserMasterMemories: (typeof import("../src/lib/boss-data"))["listUserMasterMemories"];
|
||||
let createUserMasterMemory: (typeof import("../src/lib/boss-data"))["createUserMasterMemory"];
|
||||
let updateUserMasterMemory: (typeof import("../src/lib/boss-data"))["updateUserMasterMemory"];
|
||||
let archiveUserMasterMemory: (typeof import("../src/lib/boss-data"))["archiveUserMasterMemory"];
|
||||
|
||||
async function setup() {
|
||||
if (runtimeRoot) return;
|
||||
|
||||
runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-master-agent-prompts-memory-"));
|
||||
process.env.BOSS_RUNTIME_ROOT = runtimeRoot;
|
||||
process.env.BOSS_STATE_FILE = path.join(runtimeRoot, "boss-state.json");
|
||||
|
||||
const data = await import("../src/lib/boss-data.ts");
|
||||
readState = data.readState;
|
||||
getMasterAgentPromptPolicy = data.getMasterAgentPromptPolicy;
|
||||
updateMasterAgentPromptPolicy = data.updateMasterAgentPromptPolicy;
|
||||
getUserMasterPrompt = data.getUserMasterPrompt;
|
||||
updateUserMasterPrompt = data.updateUserMasterPrompt;
|
||||
listUserMasterMemories = data.listUserMasterMemories;
|
||||
createUserMasterMemory = data.createUserMasterMemory;
|
||||
updateUserMasterMemory = data.updateUserMasterMemory;
|
||||
archiveUserMasterMemory = data.archiveUserMasterMemory;
|
||||
}
|
||||
|
||||
test.after(async () => {
|
||||
if (runtimeRoot) {
|
||||
await rm(runtimeRoot, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("主 Agent 提示词与用户记忆可读写", async () => {
|
||||
await setup();
|
||||
|
||||
await updateMasterAgentPromptPolicy({
|
||||
globalPrompt: "全局主提示词",
|
||||
updatedBy: "17600003315",
|
||||
});
|
||||
await updateUserMasterPrompt("17600003315", "用户私有主提示词");
|
||||
|
||||
const created = await createUserMasterMemory({
|
||||
account: "17600003315",
|
||||
scope: "project",
|
||||
projectId: "master-agent",
|
||||
title: "项目进度",
|
||||
content: "当前主链优先打通聊天闭环。",
|
||||
memoryType: "project_progress",
|
||||
tags: ["聊天", "主链"],
|
||||
});
|
||||
|
||||
await updateUserMasterMemory(created.memoryId, "17600003315", {
|
||||
content: "当前主链优先打通主 Agent 聊天闭环。",
|
||||
tags: ["聊天", "主Agent"],
|
||||
});
|
||||
|
||||
const policy = await getMasterAgentPromptPolicy();
|
||||
const userPrompt = await getUserMasterPrompt("17600003315");
|
||||
const memories = await listUserMasterMemories("17600003315", {
|
||||
includeArchived: false,
|
||||
});
|
||||
|
||||
assert.equal(policy?.globalPrompt, "全局主提示词");
|
||||
assert.equal(userPrompt?.content, "用户私有主提示词");
|
||||
assert.equal(memories.length, 1);
|
||||
assert.equal(memories[0]?.content, "当前主链优先打通主 Agent 聊天闭环。");
|
||||
assert.deepEqual(memories[0]?.tags, ["聊天", "主Agent"]);
|
||||
|
||||
await archiveUserMasterMemory(created.memoryId, "17600003315");
|
||||
const visible = await listUserMasterMemories("17600003315", { includeArchived: false });
|
||||
const all = await listUserMasterMemories("17600003315", { includeArchived: true });
|
||||
const archived = all.find((item) => item.memoryId === created.memoryId);
|
||||
|
||||
assert.equal(visible.length, 0);
|
||||
assert.equal(archived?.archived, true);
|
||||
assert.equal((await readState()).masterAgentMemories.length, 1);
|
||||
});
|
||||
Reference in New Issue
Block a user