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 });
|
||||
}
|
||||
@@ -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 });
|
||||
|
||||
121
src/app/api/v1/projects/[projectId]/memories/[memoryId]/route.ts
Normal file
121
src/app/api/v1/projects/[projectId]/memories/[memoryId]/route.ts
Normal 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 },
|
||||
);
|
||||
}
|
||||
}
|
||||
123
src/app/api/v1/projects/[projectId]/memories/route.ts
Normal file
123
src/app/api/v1/projects/[projectId]/memories/route.ts
Normal 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 },
|
||||
);
|
||||
}
|
||||
}
|
||||
126
src/app/api/v1/projects/[projectId]/prompt-profile/route.ts
Normal file
126
src/app/api/v1/projects/[projectId]/prompt-profile/route.ts
Normal 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 },
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user