feat: add master-agent prompts and memory management

This commit is contained in:
kris
2026-04-01 04:10:11 +08:00
parent 9000a9f185
commit d316f0490e
31 changed files with 4398 additions and 32 deletions

View File

@@ -0,0 +1,128 @@
import { NextRequest, NextResponse } from "next/server";
import { requireRequestSession } from "@/lib/boss-auth";
import {
archiveUserMasterMemory,
updateUserMasterMemory,
type MasterMemoryScope,
type MasterMemoryType,
} from "@/lib/boss-data";
export const runtime = "nodejs";
const memoryScopes = new Set<MasterMemoryScope>(["global", "project"]);
const memoryTypes = new Set<MasterMemoryType>([
"user_preference",
"project_progress",
"decision",
"risk",
"blocking_issue",
"research_note",
"workflow_rule",
]);
export async function PATCH(
request: NextRequest,
context: { params: Promise<{ memoryId: string }> },
) {
const session = await requireRequestSession(request);
if (!session) {
return NextResponse.json({ ok: false, message: "UNAUTHORIZED" }, { status: 401 });
}
const { memoryId } = await context.params;
const rawBody = await request.text().catch(() => "");
let body: unknown;
try {
body = JSON.parse(rawBody);
} catch {
return NextResponse.json({ ok: false, message: "INVALID_JSON_PAYLOAD" }, { status: 400 });
}
if (!body || typeof body !== "object" || Array.isArray(body)) {
return NextResponse.json({ ok: false, message: "INVALID_MEMORY_PAYLOAD" }, { status: 400 });
}
const payload = body as {
scope?: unknown;
projectId?: unknown;
title?: unknown;
content?: unknown;
memoryType?: unknown;
tags?: unknown;
sourceMessageId?: unknown;
lastUsedAt?: unknown;
};
const allowedKeys = new Set([
"scope",
"projectId",
"title",
"content",
"memoryType",
"tags",
"sourceMessageId",
"lastUsedAt",
]);
if (Object.keys(payload).some((key) => !allowedKeys.has(key))) {
return NextResponse.json({ ok: false, message: "INVALID_MEMORY_PAYLOAD" }, { status: 400 });
}
if (payload.scope !== undefined && !memoryScopes.has(payload.scope as MasterMemoryScope)) {
return NextResponse.json({ ok: false, message: "INVALID_MEMORY_SCOPE" }, { status: 400 });
}
if (payload.memoryType !== undefined && !memoryTypes.has(payload.memoryType as MasterMemoryType)) {
return NextResponse.json({ ok: false, message: "INVALID_MEMORY_TYPE" }, { status: 400 });
}
if (payload.tags !== undefined && !Array.isArray(payload.tags)) {
return NextResponse.json({ ok: false, message: "INVALID_MEMORY_PAYLOAD" }, { status: 400 });
}
try {
const memory = await updateUserMasterMemory(memoryId, session.account, {
...(payload.scope !== undefined ? { scope: payload.scope as MasterMemoryScope } : {}),
...(payload.projectId !== undefined ? { projectId: typeof payload.projectId === "string" ? payload.projectId : "" } : {}),
...(payload.title !== undefined ? { title: typeof payload.title === "string" ? payload.title : "" } : {}),
...(payload.content !== undefined ? { content: typeof payload.content === "string" ? payload.content : "" } : {}),
...(payload.memoryType !== undefined ? { memoryType: payload.memoryType as MasterMemoryType } : {}),
...(payload.tags !== undefined ? { tags: payload.tags as string[] } : {}),
...(payload.sourceMessageId !== undefined
? { sourceMessageId: typeof payload.sourceMessageId === "string" ? payload.sourceMessageId : "" }
: {}),
...(payload.lastUsedAt !== undefined
? { lastUsedAt: typeof payload.lastUsedAt === "string" ? payload.lastUsedAt : "" }
: {}),
});
return NextResponse.json({ ok: true, memory });
} catch (error) {
const message = error instanceof Error ? error.message : "UNKNOWN_ERROR";
return NextResponse.json(
{
ok: false,
message,
},
{ status: message === "USER_MASTER_MEMORY_NOT_FOUND" ? 404 : 400 },
);
}
}
export async function DELETE(
request: NextRequest,
context: { params: Promise<{ memoryId: string }> },
) {
const session = await requireRequestSession(request);
if (!session) {
return NextResponse.json({ ok: false, message: "UNAUTHORIZED" }, { status: 401 });
}
const { memoryId } = await context.params;
try {
const memory = await archiveUserMasterMemory(memoryId, session.account);
return NextResponse.json({ ok: true, memory });
} catch (error) {
const message = error instanceof Error ? error.message : "UNKNOWN_ERROR";
return NextResponse.json(
{
ok: false,
message,
},
{ status: message === "USER_MASTER_MEMORY_NOT_FOUND" ? 404 : 400 },
);
}
}

View File

@@ -0,0 +1,133 @@
import { NextRequest, NextResponse } from "next/server";
import { requireRequestSession } from "@/lib/boss-auth";
import {
createUserMasterMemory,
listUserMasterMemories,
type MasterMemoryScope,
type MasterMemoryType,
} from "@/lib/boss-data";
export const runtime = "nodejs";
const memoryScopes = new Set<MasterMemoryScope>(["global", "project"]);
const memoryTypes = new Set<MasterMemoryType>([
"user_preference",
"project_progress",
"decision",
"risk",
"blocking_issue",
"research_note",
"workflow_rule",
]);
function parseBoolean(value: string | null) {
return value === "1" || value === "true";
}
export async function GET(request: NextRequest) {
const session = await requireRequestSession(request);
if (!session) {
return NextResponse.json({ ok: false, message: "UNAUTHORIZED" }, { status: 401 });
}
const { searchParams } = new URL(request.url);
const includeArchived = parseBoolean(searchParams.get("includeArchived"));
const scope = searchParams.get("scope") as MasterMemoryScope | null;
const projectId = searchParams.get("projectId")?.trim() || undefined;
if (scope && !memoryScopes.has(scope)) {
return NextResponse.json({ ok: false, message: "INVALID_MEMORY_SCOPE" }, { status: 400 });
}
const memories = await listUserMasterMemories(session.account, {
includeArchived,
...(scope ? { scope } : {}),
...(projectId ? { projectId } : {}),
});
return NextResponse.json({ ok: true, memories });
}
export async function POST(request: NextRequest) {
const session = await requireRequestSession(request);
if (!session) {
return NextResponse.json({ ok: false, message: "UNAUTHORIZED" }, { status: 401 });
}
const rawBody = await request.text().catch(() => "");
let body: unknown;
try {
body = JSON.parse(rawBody);
} catch {
return NextResponse.json({ ok: false, message: "INVALID_JSON_PAYLOAD" }, { status: 400 });
}
if (!body || typeof body !== "object" || Array.isArray(body)) {
return NextResponse.json({ ok: false, message: "INVALID_MEMORY_PAYLOAD" }, { status: 400 });
}
const payload = body as {
scope?: unknown;
projectId?: unknown;
title?: unknown;
content?: unknown;
memoryType?: unknown;
tags?: unknown;
sourceMessageId?: unknown;
};
const allowedKeys = new Set([
"scope",
"projectId",
"title",
"content",
"memoryType",
"tags",
"sourceMessageId",
]);
if (Object.keys(payload).some((key) => !allowedKeys.has(key))) {
return NextResponse.json({ ok: false, message: "INVALID_MEMORY_PAYLOAD" }, { status: 400 });
}
if (!memoryScopes.has(payload.scope as MasterMemoryScope)) {
return NextResponse.json({ ok: false, message: "INVALID_MEMORY_SCOPE" }, { status: 400 });
}
if (typeof payload.title !== "string" || typeof payload.content !== "string") {
return NextResponse.json({ ok: false, message: "INVALID_MEMORY_PAYLOAD" }, { status: 400 });
}
if (!memoryTypes.has(payload.memoryType as MasterMemoryType)) {
return NextResponse.json({ ok: false, message: "INVALID_MEMORY_TYPE" }, { status: 400 });
}
if (payload.scope === "project" && (typeof payload.projectId !== "string" || !payload.projectId.trim())) {
return NextResponse.json({ ok: false, message: "USER_MASTER_MEMORY_PROJECT_ID_REQUIRED" }, { status: 400 });
}
if (payload.tags !== undefined && !Array.isArray(payload.tags)) {
return NextResponse.json({ ok: false, message: "INVALID_MEMORY_PAYLOAD" }, { status: 400 });
}
if (
payload.sourceMessageId !== undefined &&
payload.sourceMessageId !== null &&
typeof payload.sourceMessageId !== "string"
) {
return NextResponse.json({ ok: false, message: "INVALID_MEMORY_PAYLOAD" }, { status: 400 });
}
try {
const memory = await createUserMasterMemory({
account: session.account,
scope: payload.scope as MasterMemoryScope,
projectId: typeof payload.projectId === "string" ? payload.projectId : undefined,
title: payload.title,
content: payload.content,
memoryType: payload.memoryType as MasterMemoryType,
tags: (payload.tags as string[] | undefined) ?? [],
sourceMessageId:
typeof payload.sourceMessageId === "string" ? payload.sourceMessageId : undefined,
});
return NextResponse.json({ ok: true, memory });
} catch (error) {
return NextResponse.json(
{
ok: false,
message: error instanceof Error ? error.message : "UNKNOWN_ERROR",
},
{ status: 400 },
);
}
}

View File

@@ -0,0 +1,67 @@
import { NextRequest, NextResponse } from "next/server";
import { requireRequestSession } from "@/lib/boss-auth";
import {
getMasterAgentPromptPolicy,
updateMasterAgentPromptPolicy,
} from "@/lib/boss-data";
export const runtime = "nodejs";
export async function GET(request: NextRequest) {
const session = await requireRequestSession(request);
if (!session) {
return NextResponse.json({ ok: false, message: "UNAUTHORIZED" }, { status: 401 });
}
const policy = await getMasterAgentPromptPolicy();
return NextResponse.json({ ok: true, policy });
}
export async function POST(request: NextRequest) {
const session = await requireRequestSession(request);
if (!session) {
return NextResponse.json({ ok: false, message: "UNAUTHORIZED" }, { status: 401 });
}
if (session.role !== "highest_admin") {
return NextResponse.json({ ok: false, message: "FORBIDDEN" }, { status: 403 });
}
const rawBody = await request.text().catch(() => "");
let body: unknown;
try {
body = JSON.parse(rawBody);
} catch {
return NextResponse.json({ ok: false, message: "INVALID_JSON_PAYLOAD" }, { status: 400 });
}
if (!body || typeof body !== "object" || Array.isArray(body)) {
return NextResponse.json({ ok: false, message: "INVALID_PROMPT_POLICY_PAYLOAD" }, { status: 400 });
}
const payload = body as {
globalPrompt?: unknown;
};
const allowedKeys = new Set(["globalPrompt"]);
if (Object.keys(payload).some((key) => !allowedKeys.has(key))) {
return NextResponse.json({ ok: false, message: "INVALID_PROMPT_POLICY_PAYLOAD" }, { status: 400 });
}
if (typeof payload.globalPrompt !== "string") {
return NextResponse.json({ ok: false, message: "INVALID_PROMPT_POLICY_PAYLOAD" }, { status: 400 });
}
try {
const policy = await updateMasterAgentPromptPolicy({
globalPrompt: payload.globalPrompt,
updatedBy: session.account,
});
return NextResponse.json({ ok: true, policy });
} catch (error) {
return NextResponse.json(
{
ok: false,
message: error instanceof Error ? error.message : "UNKNOWN_ERROR",
},
{ status: 400 },
);
}
}

View File

@@ -0,0 +1,77 @@
import { NextRequest, NextResponse } from "next/server";
import { requireRequestSession } from "@/lib/boss-auth";
import {
clearUserMasterPrompt,
getUserMasterPrompt,
updateUserMasterPrompt,
} from "@/lib/boss-data";
export const runtime = "nodejs";
export async function GET(request: NextRequest) {
const session = await requireRequestSession(request);
if (!session) {
return NextResponse.json({ ok: false, message: "UNAUTHORIZED" }, { status: 401 });
}
const prompt = await getUserMasterPrompt(session.account);
return NextResponse.json({ ok: true, prompt });
}
export async function POST(request: NextRequest) {
const session = await requireRequestSession(request);
if (!session) {
return NextResponse.json({ ok: false, message: "UNAUTHORIZED" }, { status: 401 });
}
const rawBody = await request.text().catch(() => "");
let body: unknown;
try {
body = JSON.parse(rawBody);
} catch {
return NextResponse.json({ ok: false, message: "INVALID_JSON_PAYLOAD" }, { status: 400 });
}
if (!body || typeof body !== "object" || Array.isArray(body)) {
return NextResponse.json({ ok: false, message: "INVALID_USER_PROMPT_PAYLOAD" }, { status: 400 });
}
const payload = body as {
content?: unknown;
};
const allowedKeys = new Set(["content"]);
if (Object.keys(payload).some((key) => !allowedKeys.has(key))) {
return NextResponse.json({ ok: false, message: "INVALID_USER_PROMPT_PAYLOAD" }, { status: 400 });
}
if (payload.content !== undefined && payload.content !== null && typeof payload.content !== "string") {
return NextResponse.json({ ok: false, message: "INVALID_USER_PROMPT_PAYLOAD" }, { status: 400 });
}
try {
const content = typeof payload.content === "string" ? payload.content : "";
if (!content.trim()) {
const result = await clearUserMasterPrompt(session.account);
return NextResponse.json({ ok: true, prompt: null, ...result });
}
const prompt = await updateUserMasterPrompt(session.account, content);
return NextResponse.json({ ok: true, prompt });
} catch (error) {
return NextResponse.json(
{
ok: false,
message: error instanceof Error ? error.message : "UNKNOWN_ERROR",
},
{ status: 400 },
);
}
}
export async function DELETE(request: NextRequest) {
const session = await requireRequestSession(request);
if (!session) {
return NextResponse.json({ ok: false, message: "UNAUTHORIZED" }, { status: 401 });
}
const result = await clearUserMasterPrompt(session.account);
return NextResponse.json({ ok: true, ...result });
}

View File

@@ -62,15 +62,17 @@ export async function POST(
const payload = body as {
modelOverride?: unknown;
reasoningEffortOverride?: unknown;
promptOverride?: unknown;
};
const hasModelOverride = Object.prototype.hasOwnProperty.call(payload, "modelOverride");
const hasReasoningEffortOverride = Object.prototype.hasOwnProperty.call(
payload,
"reasoningEffortOverride",
);
const allowedKeys = new Set(["modelOverride", "reasoningEffortOverride"]);
const hasPromptOverride = Object.prototype.hasOwnProperty.call(payload, "promptOverride");
const allowedKeys = new Set(["modelOverride", "reasoningEffortOverride", "promptOverride"]);
const hasUnsupportedKeys = Object.keys(payload).some((key) => !allowedKeys.has(key));
if ((!hasModelOverride && !hasReasoningEffortOverride) || hasUnsupportedKeys) {
if ((!hasModelOverride && !hasReasoningEffortOverride && !hasPromptOverride) || hasUnsupportedKeys) {
return NextResponse.json({ ok: false, message: "INVALID_AGENT_CONTROLS_PAYLOAD" }, { status: 400 });
}
@@ -89,6 +91,9 @@ export async function POST(
{ status: 400 },
);
}
if (hasPromptOverride && payload.promptOverride !== undefined && payload.promptOverride !== null && typeof payload.promptOverride !== "string") {
return NextResponse.json({ ok: false, message: "INVALID_PROMPT_OVERRIDE" }, { status: 400 });
}
try {
const controls = await updateProjectAgentControls(
@@ -96,6 +101,7 @@ export async function POST(
{
...(hasModelOverride ? { modelOverride: payload.modelOverride } : {}),
...(hasReasoningEffortOverride ? { reasoningEffortOverride: payload.reasoningEffortOverride } : {}),
...(hasPromptOverride ? { promptOverride: payload.promptOverride } : {}),
},
);
return NextResponse.json({ ok: true, controls: controls ?? null });

View File

@@ -0,0 +1,121 @@
import { NextRequest, NextResponse } from "next/server";
import { requireRequestSession } from "@/lib/boss-auth";
import { archiveUserMasterMemory, hasPersistedProject, updateUserMasterMemory } from "@/lib/boss-data";
const memoryTypeValues = new Set([
"user_preference",
"project_progress",
"decision",
"risk",
"blocking_issue",
"research_note",
"workflow_rule",
]);
const memoryScopeValues = new Set(["global", "project"]);
function normalizeTags(input: unknown) {
if (!Array.isArray(input)) return [] as string[];
return input.filter((item): item is string => typeof item === "string").map((item) => item.trim()).filter(Boolean);
}
export async function PATCH(
request: NextRequest,
context: { params: Promise<{ projectId: string; memoryId: string }> },
) {
const session = await requireRequestSession(request);
if (!session) {
return NextResponse.json({ ok: false, message: "UNAUTHORIZED" }, { status: 401 });
}
const { projectId, memoryId } = await context.params;
const projectExists = await hasPersistedProject(projectId);
if (!projectExists) {
return NextResponse.json({ ok: false, message: "PROJECT_NOT_FOUND" }, { status: 404 });
}
const body = (await request.json().catch(() => ({}))) as {
scope?: string;
projectId?: string;
title?: string;
content?: string;
memoryType?: string;
tags?: unknown;
sourceMessageId?: string;
lastUsedAt?: string;
};
try {
const patch: Parameters<typeof updateUserMasterMemory>[2] = {};
if (Object.prototype.hasOwnProperty.call(body, "scope")) {
if (!body.scope || !memoryScopeValues.has(body.scope)) {
return NextResponse.json({ ok: false, message: "INVALID_MEMORY_SCOPE" }, { status: 400 });
}
patch.scope = body.scope as "global" | "project";
}
if (Object.prototype.hasOwnProperty.call(body, "projectId")) {
patch.projectId = body.projectId;
}
if (Object.prototype.hasOwnProperty.call(body, "title")) {
patch.title = body.title ?? "";
}
if (Object.prototype.hasOwnProperty.call(body, "content")) {
patch.content = body.content ?? "";
}
if (Object.prototype.hasOwnProperty.call(body, "memoryType")) {
if (!body.memoryType || !memoryTypeValues.has(body.memoryType)) {
return NextResponse.json({ ok: false, message: "INVALID_MEMORY_TYPE" }, { status: 400 });
}
patch.memoryType = body.memoryType as
| "user_preference"
| "project_progress"
| "decision"
| "risk"
| "blocking_issue"
| "research_note"
| "workflow_rule";
}
if (Object.prototype.hasOwnProperty.call(body, "tags")) {
patch.tags = normalizeTags(body.tags);
}
if (Object.prototype.hasOwnProperty.call(body, "sourceMessageId")) {
patch.sourceMessageId = body.sourceMessageId;
}
if (Object.prototype.hasOwnProperty.call(body, "lastUsedAt")) {
patch.lastUsedAt = body.lastUsedAt;
}
const memory = await updateUserMasterMemory(memoryId, session.account, patch);
return NextResponse.json({ ok: true, memory });
} catch (error) {
return NextResponse.json(
{ ok: false, message: error instanceof Error ? error.message : "UNKNOWN_ERROR" },
{ status: error instanceof Error && error.message === "USER_MASTER_MEMORY_NOT_FOUND" ? 404 : 400 },
);
}
}
export async function DELETE(
request: NextRequest,
context: { params: Promise<{ projectId: string; memoryId: string }> },
) {
const session = await requireRequestSession(request);
if (!session) {
return NextResponse.json({ ok: false, message: "UNAUTHORIZED" }, { status: 401 });
}
const { projectId, memoryId } = await context.params;
const projectExists = await hasPersistedProject(projectId);
if (!projectExists) {
return NextResponse.json({ ok: false, message: "PROJECT_NOT_FOUND" }, { status: 404 });
}
try {
const memory = await archiveUserMasterMemory(memoryId, session.account);
return NextResponse.json({ ok: true, memory });
} catch (error) {
return NextResponse.json(
{ ok: false, message: error instanceof Error ? error.message : "UNKNOWN_ERROR" },
{ status: error instanceof Error && error.message === "USER_MASTER_MEMORY_NOT_FOUND" ? 404 : 400 },
);
}
}

View File

@@ -0,0 +1,123 @@
import { NextRequest, NextResponse } from "next/server";
import { requireRequestSession } from "@/lib/boss-auth";
import {
createUserMasterMemory,
hasPersistedProject,
listUserMasterMemories,
} from "@/lib/boss-data";
const memoryTypeValues = new Set([
"user_preference",
"project_progress",
"decision",
"risk",
"blocking_issue",
"research_note",
"workflow_rule",
]);
const memoryScopeValues = new Set(["global", "project"]);
function normalizeTags(input: unknown) {
if (!Array.isArray(input)) return [] as string[];
return input.filter((item): item is string => typeof item === "string").map((item) => item.trim()).filter(Boolean);
}
export async function GET(
request: NextRequest,
context: { params: Promise<{ projectId: string }> },
) {
const session = await requireRequestSession(request);
if (!session) {
return NextResponse.json({ ok: false, message: "UNAUTHORIZED" }, { status: 401 });
}
const { projectId } = await context.params;
const projectExists = await hasPersistedProject(projectId);
if (!projectExists) {
return NextResponse.json({ ok: false, message: "PROJECT_NOT_FOUND" }, { status: 404 });
}
const [globalMemories, projectMemories] = await Promise.all([
listUserMasterMemories(session.account, { scope: "global" }),
listUserMasterMemories(session.account, { scope: "project", projectId }),
]);
return NextResponse.json({
ok: true,
projectId,
memories: {
global: globalMemories,
project: projectMemories,
},
});
}
export async function POST(
request: NextRequest,
context: { params: Promise<{ projectId: string }> },
) {
const session = await requireRequestSession(request);
if (!session) {
return NextResponse.json({ ok: false, message: "UNAUTHORIZED" }, { status: 401 });
}
const { projectId } = await context.params;
const projectExists = await hasPersistedProject(projectId);
if (!projectExists) {
return NextResponse.json({ ok: false, message: "PROJECT_NOT_FOUND" }, { status: 404 });
}
const body = (await request.json().catch(() => ({}))) as {
scope?: string;
projectId?: string;
title?: string;
content?: string;
memoryType?: string;
tags?: unknown;
sourceMessageId?: string;
};
if (!body.scope || !memoryScopeValues.has(body.scope)) {
return NextResponse.json({ ok: false, message: "INVALID_MEMORY_SCOPE" }, { status: 400 });
}
if (!body.title?.trim()) {
return NextResponse.json({ ok: false, message: "USER_MASTER_MEMORY_TITLE_REQUIRED" }, { status: 400 });
}
if (!body.content?.trim()) {
return NextResponse.json({ ok: false, message: "USER_MASTER_MEMORY_CONTENT_REQUIRED" }, { status: 400 });
}
if (!body.memoryType || !memoryTypeValues.has(body.memoryType)) {
return NextResponse.json({ ok: false, message: "INVALID_MEMORY_TYPE" }, { status: 400 });
}
const targetProjectId = body.scope === "project" ? (body.projectId?.trim() || projectId) : undefined;
if (body.scope === "project" && !targetProjectId) {
return NextResponse.json({ ok: false, message: "USER_MASTER_MEMORY_PROJECT_ID_REQUIRED" }, { status: 400 });
}
try {
const memory = await createUserMasterMemory({
account: session.account,
scope: body.scope as "global" | "project",
projectId: targetProjectId,
title: body.title,
content: body.content,
memoryType: body.memoryType as
| "user_preference"
| "project_progress"
| "decision"
| "risk"
| "blocking_issue"
| "research_note"
| "workflow_rule",
tags: normalizeTags(body.tags),
sourceMessageId: typeof body.sourceMessageId === "string" && body.sourceMessageId.trim() ? body.sourceMessageId.trim() : undefined,
});
return NextResponse.json({ ok: true, memory });
} catch (error) {
return NextResponse.json(
{ ok: false, message: error instanceof Error ? error.message : "UNKNOWN_ERROR" },
{ status: 400 },
);
}
}

View File

@@ -0,0 +1,126 @@
import { NextRequest, NextResponse } from "next/server";
import { requireRequestSession } from "@/lib/boss-auth";
import {
clearUserMasterPrompt,
getMasterAgentPromptPolicy,
getProjectAgentControls,
getUserMasterPrompt,
hasPersistedProject,
updateProjectAgentControls,
updateUserMasterPrompt,
} from "@/lib/boss-data";
export async function GET(
request: NextRequest,
context: { params: Promise<{ projectId: string }> },
) {
const session = await requireRequestSession(request);
if (!session) {
return NextResponse.json({ ok: false, message: "UNAUTHORIZED" }, { status: 401 });
}
const { projectId } = await context.params;
const projectExists = await hasPersistedProject(projectId);
if (!projectExists) {
return NextResponse.json({ ok: false, message: "PROJECT_NOT_FOUND" }, { status: 404 });
}
const [promptPolicy, userPrompt, projectControls] = await Promise.all([
getMasterAgentPromptPolicy(),
getUserMasterPrompt(session.account),
getProjectAgentControls(projectId),
]);
return NextResponse.json({
ok: true,
projectId,
promptPolicy,
userPrompt,
projectControls,
projectPromptOverride: projectControls?.promptOverride ?? null,
account: session.account,
});
}
export async function POST(
request: NextRequest,
context: { params: Promise<{ projectId: string }> },
) {
const session = await requireRequestSession(request);
if (!session) {
return NextResponse.json({ ok: false, message: "UNAUTHORIZED" }, { status: 401 });
}
const { projectId } = await context.params;
const projectExists = await hasPersistedProject(projectId);
if (!projectExists) {
return NextResponse.json({ ok: false, message: "PROJECT_NOT_FOUND" }, { status: 404 });
}
const rawBody = await request.text().catch(() => "");
let body: unknown;
try {
body = JSON.parse(rawBody);
} catch {
return NextResponse.json({ ok: false, message: "INVALID_JSON_PAYLOAD" }, { status: 400 });
}
if (!body || typeof body !== "object" || Array.isArray(body)) {
return NextResponse.json({ ok: false, message: "INVALID_PROMPT_PROFILE_PAYLOAD" }, { status: 400 });
}
const payload = body as {
userPromptContent?: unknown;
promptOverride?: unknown;
};
const hasUserPromptContent = Object.prototype.hasOwnProperty.call(payload, "userPromptContent");
const hasPromptOverride = Object.prototype.hasOwnProperty.call(payload, "promptOverride");
const allowedKeys = new Set(["userPromptContent", "promptOverride"]);
const hasUnsupportedKeys = Object.keys(payload).some((key) => !allowedKeys.has(key));
if ((!hasUserPromptContent && !hasPromptOverride) || hasUnsupportedKeys) {
return NextResponse.json({ ok: false, message: "INVALID_PROMPT_PROFILE_PAYLOAD" }, { status: 400 });
}
if (hasUserPromptContent && payload.userPromptContent !== undefined && payload.userPromptContent !== null && typeof payload.userPromptContent !== "string") {
return NextResponse.json({ ok: false, message: "INVALID_USER_PROMPT_CONTENT" }, { status: 400 });
}
if (hasPromptOverride && payload.promptOverride !== undefined && payload.promptOverride !== null && typeof payload.promptOverride !== "string") {
return NextResponse.json({ ok: false, message: "INVALID_PROMPT_OVERRIDE" }, { status: 400 });
}
try {
if (hasUserPromptContent) {
const userPromptContent = typeof payload.userPromptContent === "string" ? payload.userPromptContent.trim() : "";
if (userPromptContent) {
await updateUserMasterPrompt(session.account, userPromptContent);
} else {
await clearUserMasterPrompt(session.account);
}
}
if (hasPromptOverride) {
await updateProjectAgentControls(projectId, {
promptOverride: payload.promptOverride,
});
}
const [promptPolicy, userPrompt, projectControls] = await Promise.all([
getMasterAgentPromptPolicy(),
getUserMasterPrompt(session.account),
getProjectAgentControls(projectId),
]);
return NextResponse.json({
ok: true,
projectId,
promptPolicy,
userPrompt,
projectControls,
projectPromptOverride: projectControls?.promptOverride ?? null,
account: session.account,
});
} catch (error) {
return NextResponse.json(
{ ok: false, message: error instanceof Error ? error.message : "UNKNOWN_ERROR" },
{ status: error instanceof Error && error.message === "PROJECT_NOT_FOUND" ? 404 : 400 },
);
}
}

View File

@@ -0,0 +1,53 @@
import { AppShell, PageNav, StatusBar } from "@/components/app-ui";
import { MasterAgentPromptMemoryClient } from "@/components/master-agent-prompt-memory-client";
import { requirePageSession } from "@/lib/boss-auth";
import {
getMasterAgentPromptPolicy,
getProjectAgentControls,
getUserMasterPrompt,
listUserMasterMemories,
} from "@/lib/boss-data";
export const dynamic = "force-dynamic";
export default async function MasterAgentPromptMemoryPage() {
const session = await requirePageSession();
const [promptPolicy, userPrompt, projectControls, globalMemories, projectMemories] =
await Promise.all([
getMasterAgentPromptPolicy(),
getUserMasterPrompt(session.account),
getProjectAgentControls("master-agent"),
listUserMasterMemories(session.account, { includeArchived: false, scope: "global" }),
listUserMasterMemories(session.account, {
includeArchived: false,
scope: "project",
projectId: "master-agent",
}),
]);
return (
<AppShell bottomNav={false}>
<StatusBar />
<PageNav title="主 Agent 提示词 / 记忆" backHref="/me" />
<div className="px-[18px] pb-3">
<div className="rounded-2xl border border-[#E5E5EA] bg-white px-4 py-4 text-[13px] leading-6 text-[#57606A]">
<span className="font-semibold text-[#111111]">{session.account}</span>
<br />
{session.role === "highest_admin"
? "你是管理员,可以编辑全局主提示词与当前对话附加提示词。"
: "你可以编辑自己的提示词与记忆;管理员全局主提示词只读。"}
</div>
</div>
<MasterAgentPromptMemoryClient
key={`${promptPolicy?.updatedAt ?? "none"}:${userPrompt?.updatedAt ?? "none"}:${projectControls?.updatedAt ?? "none"}:${globalMemories.length}:${projectMemories.length}`}
isAdmin={session.role === "highest_admin"}
promptPolicy={promptPolicy}
userPrompt={userPrompt}
projectControls={projectControls}
globalMemories={globalMemories}
projectMemories={projectMemories}
/>
</AppShell>
);
}

View File

@@ -21,6 +21,11 @@ export default async function MePage() {
<HeaderTitle title="我的" />
<div className="flex flex-col gap-3 px-[18px] pb-5">
<ProfileHero user={state.user} />
<MenuRow
href="/me/master-agent"
title="主 Agent 提示词 / 记忆"
description="配置全局主提示词、当前主提示词和用户记忆"
/>
<MenuRow
href="/me/storage"
title="附件与存储"