279 lines
10 KiB
TypeScript
279 lines
10 KiB
TypeScript
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 = "krisolo", 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",
|
|
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(),
|
|
["boss", "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 当前未启用。",
|
|
});
|
|
});
|