feat: add master-agent prompts and memory management
This commit is contained in:
128
src/app/api/v1/master-agent/memories/[memoryId]/route.ts
Normal file
128
src/app/api/v1/master-agent/memories/[memoryId]/route.ts
Normal 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 },
|
||||
);
|
||||
}
|
||||
}
|
||||
133
src/app/api/v1/master-agent/memories/route.ts
Normal file
133
src/app/api/v1/master-agent/memories/route.ts
Normal 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 },
|
||||
);
|
||||
}
|
||||
}
|
||||
67
src/app/api/v1/master-agent/prompt-policy/route.ts
Normal file
67
src/app/api/v1/master-agent/prompt-policy/route.ts
Normal 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 },
|
||||
);
|
||||
}
|
||||
}
|
||||
77
src/app/api/v1/master-agent/prompt/route.ts
Normal file
77
src/app/api/v1/master-agent/prompt/route.ts
Normal 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 });
|
||||
}
|
||||
Reference in New Issue
Block a user