import test from "node:test"; import assert from "node:assert/strict"; import os from "node:os"; import path from "node:path"; import { mkdtemp, rm, writeFile } 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"); let getProjectMemoriesRoute: typeof import("../src/app/api/v1/projects/[projectId]/memories/route"); let promptProfileRoute: typeof import("../src/app/api/v1/projects/[projectId]/prompt-profile/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, projectMemoriesRoute, loadedPromptProfileRoute] = 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"), import("../src/app/api/v1/projects/[projectId]/memories/route.ts"), import("../src/app/api/v1/projects/[projectId]/prompt-profile/route.ts"), ]); createAuthSession = data.createAuthSession; AUTH_SESSION_COOKIE = auth.AUTH_SESSION_COOKIE; getMasterAgentPromptPolicyRoute = promptPolicyRoute; getUserMasterPromptRoute = userPromptRoute; getUserMasterMemoriesRoute = memoriesRoute; patchUserMasterMemoryRoute = memoryRoute; getProjectMemoriesRoute = projectMemoriesRoute.GET; promptProfileRoute = loadedPromptProfileRoute; } 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); }); test("master-agent 记忆页会返回当前用户所有项目记忆", async () => { await setup(); const adminRequest = await createAuthedRequest(); 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: "boss-console", title: "Boss 进度", content: "Boss 项目聊天主链已接通。", memoryType: "project_progress", }), }), ); 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: "boss-console", title: "Boss 进度", content: "Boss 项目聊天主链已接通。", memoryType: "project_progress", }), }), ); 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: "wenshenapp", title: "纹身项目进度", content: "wenshenapp 当前只保留一个主线程。", memoryType: "project_progress", }), }), ); const response = await getProjectMemoriesRoute( new NextRequest("http://127.0.0.1:3000/api/v1/projects/master-agent/memories", { method: "GET", headers: adminRequest.headers, }), { params: Promise.resolve({ projectId: "master-agent" }) }, ); assert.equal(response.status, 200); const payload = (await response.json()) as { ok: boolean; memories: { project: Array<{ projectId?: string }> }; }; assert.equal(payload.ok, true); assert.deepEqual( payload.memories.project.map((memory) => memory.projectId).sort(), ["master-agent", "wenshenapp"].sort(), ); }); test("prompt-profile 写入当前对话提示词时按当前账号隔离", async () => { await setup(); const tempDir = await mkdtemp(path.join(os.tmpdir(), "boss-claw-prompt-profile-")); const scriptPath = path.join(tempDir, "claw-runtime.mjs"); await writeFile(scriptPath, "console.log('ok');\n", "utf8"); const previousEnv = { BOSS_CLAW_ENABLED: process.env.BOSS_CLAW_ENABLED, BOSS_CLAW_COMMAND: process.env.BOSS_CLAW_COMMAND, BOSS_CLAW_ARGS: process.env.BOSS_CLAW_ARGS, BOSS_CLAW_WORKDIR: process.env.BOSS_CLAW_WORKDIR, }; process.env.BOSS_CLAW_ENABLED = "true"; process.env.BOSS_CLAW_COMMAND = process.execPath; process.env.BOSS_CLAW_ARGS = scriptPath; process.env.BOSS_CLAW_WORKDIR = tempDir; try { const memberRequest = await createAuthedRequest("18800001111", "member"); const response = await promptProfileRoute.POST( new NextRequest("http://127.0.0.1:3000/api/v1/projects/master-agent/prompt-profile", { method: "POST", headers: memberRequest.headers, body: JSON.stringify({ promptOverride: "成员自己的当前对话提示词", backendOverride: "claw-runtime", }), }), { params: Promise.resolve({ projectId: "master-agent" }) }, ); assert.equal(response.status, 200); const payload = (await response.json()) as { ok: boolean; projectPromptOverride: string | null; account: string; projectControls: { backendOverride?: string | null; } | null; }; assert.equal(payload.ok, true); assert.equal(payload.account, "18800001111"); assert.equal(payload.projectPromptOverride, "成员自己的当前对话提示词"); assert.equal(payload.projectControls?.backendOverride, "claw-runtime"); } finally { if (previousEnv.BOSS_CLAW_ENABLED === undefined) delete process.env.BOSS_CLAW_ENABLED; else process.env.BOSS_CLAW_ENABLED = previousEnv.BOSS_CLAW_ENABLED; if (previousEnv.BOSS_CLAW_COMMAND === undefined) delete process.env.BOSS_CLAW_COMMAND; else process.env.BOSS_CLAW_COMMAND = previousEnv.BOSS_CLAW_COMMAND; if (previousEnv.BOSS_CLAW_ARGS === undefined) delete process.env.BOSS_CLAW_ARGS; else process.env.BOSS_CLAW_ARGS = previousEnv.BOSS_CLAW_ARGS; if (previousEnv.BOSS_CLAW_WORKDIR === undefined) delete process.env.BOSS_CLAW_WORKDIR; else process.env.BOSS_CLAW_WORKDIR = previousEnv.BOSS_CLAW_WORKDIR; await rm(tempDir, { recursive: true, force: true }); } }); test("prompt-profile 会返回当前 Claw Runtime 的可用性状态", async () => { await setup(); const memberRequest = await createAuthedRequest("18800001111", "member"); const response = await promptProfileRoute.GET( new NextRequest("http://127.0.0.1:3000/api/v1/projects/master-agent/prompt-profile", { method: "GET", headers: memberRequest.headers, }), { params: Promise.resolve({ projectId: "master-agent" }) }, ); assert.equal(response.status, 200); const payload = (await response.json()) as { ok: boolean; clawAvailability?: { configured: boolean; status: string; selectable: boolean; reason: string; reasonLabel: string; }; }; assert.equal(payload.ok, true); assert.deepEqual(payload.clawAvailability, { configured: false, status: "disabled", selectable: false, reason: "disabled", reasonLabel: "Claw Runtime 当前未启用。", }); }); test("prompt-profile 会返回当前 Hermes Runtime 的可用性状态", async () => { await setup(); const memberRequest = await createAuthedRequest("18800001111", "member"); const response = await promptProfileRoute.GET( new NextRequest("http://127.0.0.1:3000/api/v1/projects/master-agent/prompt-profile", { method: "GET", headers: memberRequest.headers, }), { params: Promise.resolve({ projectId: "master-agent" }) }, ); assert.equal(response.status, 200); const payload = (await response.json()) as { ok: boolean; hermesAvailability?: { configured: boolean; status: string; selectable: boolean; reason: string; reasonLabel: string; }; }; assert.equal(payload.ok, true); assert.deepEqual(payload.hermesAvailability, { command: "hermes", configured: false, status: "disabled", selectable: false, reason: "disabled", reasonLabel: "Hermes Runtime 当前未启用。", }); });