feat: ship native boss android console

This commit is contained in:
kris
2026-03-26 23:16:56 +08:00
parent 90e904814d
commit 90cb6b7ff1
261 changed files with 40051 additions and 135 deletions

View File

@@ -0,0 +1,29 @@
import { NextRequest, NextResponse } from "next/server";
import { requireRequestSession } from "@/lib/boss-auth";
import { activateAiAccount } from "@/lib/boss-data";
export async function POST(
request: NextRequest,
context: { params: Promise<{ accountId: string }> },
) {
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 body = (await request.json().catch(() => ({}))) as { reason?: string };
const { accountId } = await context.params;
try {
const result = await activateAiAccount(accountId, body.reason?.trim() || "手动切换主控身份");
return NextResponse.json({ ok: true, ...result });
} catch (error) {
return NextResponse.json(
{ ok: false, message: error instanceof Error ? error.message : "UNKNOWN_ERROR" },
{ status: 400 },
);
}
}

View File

@@ -0,0 +1,116 @@
import { NextRequest, NextResponse } from "next/server";
import { requireRequestSession } from "@/lib/boss-auth";
import { deleteAiAccount, getAiAccount, saveAiAccount } from "@/lib/boss-data";
function isValidRole(value: string): value is "primary" | "backup" | "api_fallback" {
return value === "primary" || value === "backup" || value === "api_fallback";
}
function isValidProvider(value: string): value is "master_codex_node" | "openai_api" {
return value === "master_codex_node" || value === "openai_api";
}
export async function GET(
request: NextRequest,
context: { params: Promise<{ accountId: string }> },
) {
const session = await requireRequestSession(request);
if (!session) {
return NextResponse.json({ ok: false, message: "UNAUTHORIZED" }, { status: 401 });
}
const { accountId } = await context.params;
const account = await getAiAccount(accountId);
if (!account) {
return NextResponse.json({ ok: false, message: "AI_ACCOUNT_NOT_FOUND" }, { status: 404 });
}
return NextResponse.json({ ok: true, account });
}
export async function PATCH(
request: NextRequest,
context: { params: Promise<{ accountId: string }> },
) {
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 { accountId } = await context.params;
const body = (await request.json()) as {
label?: string;
role?: string;
provider?: string;
displayName?: string;
accountIdentifier?: string;
nodeId?: string;
nodeLabel?: string;
model?: string;
apiKey?: string;
enabled?: boolean;
setActive?: boolean;
loginStatusNote?: string;
};
if (!body.label?.trim() || !body.displayName?.trim()) {
return NextResponse.json(
{ ok: false, message: "AI 账号至少需要填写显示名称和账号名称。" },
{ status: 400 },
);
}
if (!body.role || !isValidRole(body.role)) {
return NextResponse.json({ ok: false, message: "AI 账号角色不合法。" }, { status: 400 });
}
if (!body.provider || !isValidProvider(body.provider)) {
return NextResponse.json({ ok: false, message: "AI 账号类型不合法。" }, { status: 400 });
}
try {
const account = await saveAiAccount({
accountId,
label: body.label,
role: body.role,
provider: body.provider,
displayName: body.displayName,
accountIdentifier: body.accountIdentifier,
nodeId: body.nodeId,
nodeLabel: body.nodeLabel,
model: body.model,
apiKey: body.apiKey,
enabled: body.enabled,
setActive: body.setActive,
loginStatusNote: body.loginStatusNote,
});
return NextResponse.json({ ok: true, account });
} catch (error) {
return NextResponse.json(
{ ok: false, message: error instanceof Error ? error.message : "UNKNOWN_ERROR" },
{ status: 400 },
);
}
}
export async function DELETE(
request: NextRequest,
context: { params: Promise<{ accountId: string }> },
) {
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 { accountId } = await context.params;
try {
await deleteAiAccount(accountId);
return NextResponse.json({ ok: true });
} catch (error) {
return NextResponse.json(
{ ok: false, message: error instanceof Error ? error.message : "UNKNOWN_ERROR" },
{ status: 400 },
);
}
}

View File

@@ -0,0 +1,27 @@
import { NextRequest, NextResponse } from "next/server";
import { requireRequestSession } from "@/lib/boss-auth";
import { validateAiAccountConnection } from "@/lib/boss-master-agent";
export async function POST(
request: NextRequest,
context: { params: Promise<{ accountId: string }> },
) {
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 });
}
try {
const { accountId } = await context.params;
const result = await validateAiAccountConnection(accountId);
return NextResponse.json(result, { status: result.ok ? 200 : 400 });
} catch (error) {
return NextResponse.json(
{ ok: false, message: error instanceof Error ? error.message : "UNKNOWN_ERROR" },
{ status: 400 },
);
}
}

View File

@@ -0,0 +1,74 @@
import { NextRequest, NextResponse } from "next/server";
import { requireRequestSession } from "@/lib/boss-auth";
import { listAiAccounts, saveAiAccount } from "@/lib/boss-data";
function isValidRole(value: string): value is "primary" | "backup" | "api_fallback" {
return value === "primary" || value === "backup" || value === "api_fallback";
}
function isValidProvider(value: string): value is "master_codex_node" | "openai_api" {
return value === "master_codex_node" || value === "openai_api";
}
export async function GET(request: NextRequest) {
const session = await requireRequestSession(request);
if (!session) {
return NextResponse.json({ ok: false, message: "UNAUTHORIZED" }, { status: 401 });
}
const result = await listAiAccounts();
return NextResponse.json({ ok: true, ...result });
}
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 body = (await request.json()) as {
label?: string;
role?: string;
provider?: string;
displayName?: string;
accountIdentifier?: string;
nodeId?: string;
nodeLabel?: string;
model?: string;
apiKey?: string;
enabled?: boolean;
setActive?: boolean;
loginStatusNote?: string;
};
if (!body.label?.trim() || !body.displayName?.trim()) {
return NextResponse.json(
{ ok: false, message: "AI 账号至少需要填写显示名称和账号名称。" },
{ status: 400 },
);
}
if (!body.role || !isValidRole(body.role)) {
return NextResponse.json({ ok: false, message: "AI 账号角色不合法。" }, { status: 400 });
}
if (!body.provider || !isValidProvider(body.provider)) {
return NextResponse.json({ ok: false, message: "AI 账号类型不合法。" }, { status: 400 });
}
const account = await saveAiAccount({
label: body.label,
role: body.role,
provider: body.provider,
displayName: body.displayName,
accountIdentifier: body.accountIdentifier,
nodeId: body.nodeId,
nodeLabel: body.nodeLabel,
model: body.model,
apiKey: body.apiKey,
enabled: body.enabled,
setActive: body.setActive,
loginStatusNote: body.loginStatusNote,
});
return NextResponse.json({ ok: true, account });
}

View File

@@ -0,0 +1,93 @@
import { NextRequest, NextResponse } from "next/server";
import { requireRequestSession } from "@/lib/boss-auth";
import { authorizeDeviceWriteRequest } from "@/lib/boss-device-auth";
import { appendAppLog, readState } from "@/lib/boss-data";
export async function GET(request: NextRequest) {
const session = await requireRequestSession(request);
if (!session) {
return NextResponse.json({ ok: false, message: "UNAUTHORIZED" }, { status: 401 });
}
const limit = Math.min(Math.max(Number(request.nextUrl.searchParams.get("limit") ?? "20"), 1), 100);
const cursor = request.nextUrl.searchParams.get("cursor");
const deviceId = request.nextUrl.searchParams.get("deviceId") ?? undefined;
const projectId = request.nextUrl.searchParams.get("projectId") ?? undefined;
const level = request.nextUrl.searchParams.get("level") ?? undefined;
const category = request.nextUrl.searchParams.get("category") ?? undefined;
const source = request.nextUrl.searchParams.get("source") ?? undefined;
const state = await readState();
const filtered = state.appLogs
.filter((entry) => {
const device = state.devices.find((item) => item.id === entry.deviceId);
if (
session.role !== "highest_admin" &&
device?.account !== session.account
) {
return false;
}
if (deviceId && entry.deviceId !== deviceId) return false;
if (projectId && entry.projectId !== projectId) return false;
if (level && entry.level !== level) return false;
if (category && entry.category !== category) return false;
if (source && entry.source !== source) return false;
if (cursor && entry.createdAt >= cursor) return false;
return true;
})
.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
const entries = filtered.slice(0, limit);
const nextCursor = filtered.length > limit ? entries.at(-1)?.createdAt : null;
return NextResponse.json({
ok: true,
entries,
nextCursor,
hasMore: Boolean(nextCursor),
});
}
export async function POST(request: NextRequest) {
const body = (await request.json()) as {
deviceId?: string;
projectId?: string;
level?: "info" | "warn" | "error";
source?: "app_client" | "local_agent";
category?: string;
message?: string;
detail?: string;
mirrorToMaster?: boolean;
};
if (!body.deviceId || !body.level || !body.category || !body.message) {
return NextResponse.json({ ok: false, message: "APP 日志字段不完整。" }, { status: 400 });
}
const authorization = await authorizeDeviceWriteRequest(request, body.deviceId);
if (!authorization.ok) {
return NextResponse.json({ ok: false, message: "UNAUTHORIZED_DEVICE_WRITE" }, { status: 401 });
}
if (!authorization.device) {
return NextResponse.json({ ok: false, message: "DEVICE_NOT_FOUND" }, { status: 404 });
}
try {
const entry = await appendAppLog({
deviceId: body.deviceId,
projectId: body.projectId,
level: body.level,
source: body.source ?? "app_client",
category: body.category,
message: body.message,
detail: body.detail,
mirrorToMaster: body.mirrorToMaster,
});
return NextResponse.json({ ok: true, entry });
} catch (error) {
return NextResponse.json(
{ ok: false, message: error instanceof Error ? error.message : "UNKNOWN_ERROR" },
{ status: 400 },
);
}
}

View File

@@ -0,0 +1,13 @@
import { NextRequest, NextResponse } from "next/server";
import { requireRequestSession } from "@/lib/boss-auth";
import { getAuditSummaryView } from "@/lib/boss-projections";
import { readState } from "@/lib/boss-data";
export async function GET(request: NextRequest) {
const session = await requireRequestSession(request);
if (!session) {
return NextResponse.json({ ok: false, message: "UNAUTHORIZED" }, { status: 401 });
}
const state = await readState();
return NextResponse.json({ ok: true, ...getAuditSummaryView(state) });
}

View File

@@ -0,0 +1,29 @@
import { NextRequest, NextResponse } from "next/server";
import { requireRequestSession } from "@/lib/boss-auth";
import { updateConversationAction } from "@/lib/boss-data";
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 body = (await request.json()) as { action?: "toggle_pin" | "mark_read" };
if (!body.action) {
return NextResponse.json({ ok: false, message: "缺少 action" }, { status: 400 });
}
try {
const project = await updateConversationAction(projectId, body.action);
return NextResponse.json({ ok: true, project });
} catch (error) {
return NextResponse.json(
{ ok: false, message: error instanceof Error ? error.message : "UNKNOWN_ERROR" },
{ status: 400 },
);
}
}

View File

@@ -0,0 +1,16 @@
import { NextRequest, NextResponse } from "next/server";
import { requireRequestSession } from "@/lib/boss-auth";
import { getConversationItems } from "@/lib/boss-projections";
import { readState } from "@/lib/boss-data";
export async function GET(request: NextRequest) {
const session = await requireRequestSession(request);
if (!session) {
return NextResponse.json({ ok: false, message: "UNAUTHORIZED" }, { status: 401 });
}
const state = await readState();
return NextResponse.json({
ok: true,
conversations: getConversationItems(state),
});
}

View File

@@ -0,0 +1,33 @@
import { NextRequest, NextResponse } from "next/server";
import { requireRequestSession } from "@/lib/boss-auth";
import { updateDevice } from "@/lib/boss-data";
export async function PATCH(
request: NextRequest,
context: { params: Promise<{ deviceId: string }> },
) {
const session = await requireRequestSession(request);
if (!session) {
return NextResponse.json({ ok: false, message: "UNAUTHORIZED" }, { status: 401 });
}
const { deviceId } = await context.params;
const body = (await request.json()) as {
name?: string;
avatar?: string;
account?: string;
status?: "online" | "abnormal" | "offline";
endpoint?: string;
note?: string;
projects?: string[];
};
try {
const device = await updateDevice(deviceId, body);
return NextResponse.json({ ok: true, device });
} 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 { authorizeDeviceWriteRequest } from "@/lib/boss-device-auth";
import { readState, upsertDeviceSkills } from "@/lib/boss-data";
export async function GET(
request: NextRequest,
context: { params: Promise<{ deviceId: string }> },
) {
const session = await requireRequestSession(request);
if (!session) {
return NextResponse.json({ ok: false, message: "UNAUTHORIZED" }, { status: 401 });
}
const { deviceId } = await context.params;
const state = await readState();
const device = state.devices.find((item) => item.id === deviceId);
if (!device) {
return NextResponse.json({ ok: false, message: "DEVICE_NOT_FOUND" }, { status: 404 });
}
return NextResponse.json({
ok: true,
device,
skills: state.deviceSkills
.filter((item) => item.deviceId === deviceId)
.sort((a, b) => a.name.localeCompare(b.name, "zh-CN")),
});
}
export async function POST(
request: NextRequest,
context: { params: Promise<{ deviceId: string }> },
) {
const { deviceId } = await context.params;
const body = (await request.json()) as {
skills?: Array<{
name?: string;
description?: string;
path?: string;
invocation?: string;
category?: string;
}>;
};
if (!Array.isArray(body.skills)) {
return NextResponse.json({ ok: false, message: "缺少技能列表。" }, { status: 400 });
}
const authorization = await authorizeDeviceWriteRequest(request, deviceId);
if (!authorization.ok) {
return NextResponse.json({ ok: false, message: "UNAUTHORIZED_DEVICE_WRITE" }, { status: 401 });
}
if (!authorization.device) {
return NextResponse.json({ ok: false, message: "DEVICE_NOT_FOUND" }, { status: 404 });
}
try {
const skills = await upsertDeviceSkills({
deviceId,
skills: body.skills
.filter((skill) => skill.name && skill.path)
.map((skill) => ({
name: skill.name as string,
description: skill.description,
path: skill.path as string,
invocation: skill.invocation,
category: skill.category,
})),
});
return NextResponse.json({ ok: true, skills, count: skills.length });
} catch (error) {
return NextResponse.json(
{ ok: false, message: error instanceof Error ? error.message : "UNKNOWN_ERROR" },
{ status: 400 },
);
}
}

View File

@@ -0,0 +1,51 @@
import { NextRequest, NextResponse } from "next/server";
import { requireRequestSession } from "@/lib/boss-auth";
import { createDeviceEnrollment, readState } from "@/lib/boss-data";
export async function GET(request: NextRequest) {
const session = await requireRequestSession(request);
if (!session) {
return NextResponse.json({ ok: false, message: "UNAUTHORIZED" }, { status: 401 });
}
const state = await readState();
return NextResponse.json({ ok: true, enrollments: state.deviceEnrollments });
}
export async function POST(request: NextRequest) {
const session = await requireRequestSession(request);
if (!session) {
return NextResponse.json({ ok: false, message: "UNAUTHORIZED" }, { status: 401 });
}
const body = (await request.json()) as {
name?: string;
avatar?: string;
account?: string;
endpoint?: string;
note?: string;
projects?: string[];
};
if (!body.name || !body.avatar || !body.account) {
return NextResponse.json(
{ ok: false, message: "缺少设备名、头像或账号" },
{ status: 400 },
);
}
try {
const result = await createDeviceEnrollment({
name: body.name,
avatar: body.avatar,
account: body.account,
endpoint: body.endpoint,
note: body.note,
projects: body.projects ?? [],
});
return NextResponse.json({ ok: true, ...result });
} catch (error) {
return NextResponse.json(
{ ok: false, message: error instanceof Error ? error.message : "UNKNOWN_ERROR" },
{ status: 400 },
);
}
}

View File

@@ -0,0 +1,21 @@
import { NextRequest, NextResponse } from "next/server";
import { requireRequestSession } from "@/lib/boss-auth";
import { getDeviceWorkspaceView } from "@/lib/boss-projections";
import { readState } from "@/lib/boss-data";
export async function GET(request: NextRequest) {
const session = await requireRequestSession(request);
if (!session) {
return NextResponse.json({ ok: false, message: "UNAUTHORIZED" }, { status: 401 });
}
const url = new URL(request.url);
const deviceId = url.searchParams.get("device");
const state = await readState();
return NextResponse.json({
ok: true,
devices: state.devices,
enrollments: state.deviceEnrollments,
workspace: getDeviceWorkspaceView(state, deviceId ?? undefined),
});
}

View File

@@ -0,0 +1,80 @@
import { NextRequest } from "next/server";
import { requireRequestSession } from "@/lib/boss-auth";
import { subscribeBossEvents } from "@/lib/boss-events";
import { getAuditSummaryView, getConversationItems, getOpsSummaryView } from "@/lib/boss-projections";
import { readState } from "@/lib/boss-data";
export const dynamic = "force-dynamic";
function sseEvent(event: string, data: unknown) {
return `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`;
}
export async function GET(request: NextRequest) {
const session = await requireRequestSession(request);
if (!session) {
return new Response(JSON.stringify({ ok: false, message: "UNAUTHORIZED" }), {
status: 401,
headers: { "Content-Type": "application/json; charset=utf-8" },
});
}
const encoder = new TextEncoder();
let heartbeatTimer: ReturnType<typeof setInterval> | undefined;
let unsubscribe: (() => void) | undefined;
const stream = new ReadableStream({
async start(controller) {
const publishSnapshots = async () => {
const state = await readState();
controller.enqueue(
encoder.encode(
sseEvent("conversation.context_indicator.updated", {
at: new Date().toISOString(),
conversations: getConversationItems(state),
}),
),
);
controller.enqueue(
encoder.encode(
sseEvent("project.context_risk.updated", {
at: new Date().toISOString(),
ops: getOpsSummaryView(state),
audits: getAuditSummaryView(state),
}),
),
);
};
await publishSnapshots();
unsubscribe = subscribeBossEvents((event, payload) => {
try {
controller.enqueue(encoder.encode(sseEvent(event, payload)));
} catch {
unsubscribe?.();
}
});
heartbeatTimer = setInterval(() => {
try {
controller.enqueue(encoder.encode(": keepalive\n\n"));
} catch {
if (heartbeatTimer) clearInterval(heartbeatTimer);
unsubscribe?.();
}
}, 20_000);
},
cancel() {
if (heartbeatTimer) clearInterval(heartbeatTimer);
unsubscribe?.();
},
});
return new Response(stream, {
headers: {
"Content-Type": "text/event-stream; charset=utf-8",
"Cache-Control": "no-cache, no-transform",
Connection: "keep-alive",
},
});
}

View File

@@ -0,0 +1,44 @@
import { NextRequest, NextResponse } from "next/server";
import { authorizeDeviceWriteRequest } from "@/lib/boss-device-auth";
import { completeMasterAgentTask } from "@/lib/boss-data";
export async function POST(
request: NextRequest,
context: { params: Promise<{ taskId: string }> },
) {
const body = (await request.json().catch(() => ({}))) as {
deviceId?: string;
status?: "completed" | "failed";
replyBody?: string;
errorMessage?: string;
requestId?: string;
};
if (!body.deviceId?.trim()) {
return NextResponse.json({ ok: false, message: "DEVICE_ID_REQUIRED" }, { status: 400 });
}
const auth = await authorizeDeviceWriteRequest(request, body.deviceId.trim());
if (!auth.ok) {
return NextResponse.json({ ok: false, message: "UNAUTHORIZED" }, { status: 401 });
}
const { taskId } = await context.params;
try {
const task = await completeMasterAgentTask({
taskId,
deviceId: body.deviceId.trim(),
status: body.status === "failed" ? "failed" : "completed",
replyBody: body.replyBody,
errorMessage: body.errorMessage,
requestId: body.requestId,
});
return NextResponse.json({ ok: true, task });
} catch (error) {
return NextResponse.json(
{ ok: false, message: error instanceof Error ? error.message : "UNKNOWN_ERROR" },
{ status: 400 },
);
}
}

View File

@@ -0,0 +1,19 @@
import { NextRequest, NextResponse } from "next/server";
import { authorizeDeviceWriteRequest } from "@/lib/boss-device-auth";
import { claimNextMasterAgentTask } from "@/lib/boss-data";
export async function POST(request: NextRequest) {
const body = (await request.json().catch(() => ({}))) as { deviceId?: string };
const deviceId = body.deviceId?.trim();
if (!deviceId) {
return NextResponse.json({ ok: false, message: "DEVICE_ID_REQUIRED" }, { status: 400 });
}
const auth = await authorizeDeviceWriteRequest(request, deviceId);
if (!auth.ok) {
return NextResponse.json({ ok: false, message: "UNAUTHORIZED" }, { status: 401 });
}
const task = await claimNextMasterAgentTask(deviceId);
return NextResponse.json({ ok: true, task });
}

View File

@@ -0,0 +1,23 @@
import { NextRequest, NextResponse } from "next/server";
import { requireRequestSession } from "@/lib/boss-auth";
import { approveRepairTicket } from "@/lib/boss-data";
export async function POST(
request: NextRequest,
context: { params: Promise<{ ticketId: string }> },
) {
const session = await requireRequestSession(request);
if (!session) {
return NextResponse.json({ ok: false, message: "UNAUTHORIZED" }, { status: 401 });
}
const { ticketId } = await context.params;
try {
const ticket = await approveRepairTicket(ticketId);
return NextResponse.json({ ok: true, ticket });
} catch (error) {
return NextResponse.json(
{ ok: false, message: error instanceof Error ? error.message : "UNKNOWN_ERROR" },
{ status: 400 },
);
}
}

View File

@@ -0,0 +1,23 @@
import { NextRequest, NextResponse } from "next/server";
import { requireRequestSession } from "@/lib/boss-auth";
import { verifyRepairTicket } from "@/lib/boss-data";
export async function POST(
request: NextRequest,
context: { params: Promise<{ ticketId: string }> },
) {
const session = await requireRequestSession(request);
if (!session) {
return NextResponse.json({ ok: false, message: "UNAUTHORIZED" }, { status: 401 });
}
const { ticketId } = await context.params;
try {
const ticket = await verifyRepairTicket(ticketId);
return NextResponse.json({ ok: true, ticket });
} catch (error) {
return NextResponse.json(
{ ok: false, message: error instanceof Error ? error.message : "UNKNOWN_ERROR" },
{ status: 400 },
);
}
}

View File

@@ -0,0 +1,13 @@
import { NextRequest, NextResponse } from "next/server";
import { requireRequestSession } from "@/lib/boss-auth";
import { getOpsSummaryView } from "@/lib/boss-projections";
import { readState } from "@/lib/boss-data";
export async function GET(request: NextRequest) {
const session = await requireRequestSession(request);
if (!session) {
return NextResponse.json({ ok: false, message: "UNAUTHORIZED" }, { status: 401 });
}
const state = await readState();
return NextResponse.json({ ok: true, ...getOpsSummaryView(state) });
}

View File

@@ -0,0 +1,39 @@
import { NextRequest, NextResponse } from "next/server";
import { requireRequestSession } from "@/lib/boss-auth";
import { forwardProjectMessage } from "@/lib/boss-data";
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 body = (await request.json()) as {
targetProjectId?: string;
note?: string;
};
if (!body.targetProjectId || !body.note) {
return NextResponse.json(
{ ok: false, message: "缺少 targetProjectId 或 note" },
{ status: 400 },
);
}
try {
const message = await forwardProjectMessage({
sourceProjectId: projectId,
targetProjectId: body.targetProjectId,
note: body.note,
});
return NextResponse.json({ ok: true, message });
} catch (error) {
return NextResponse.json(
{ ok: false, message: error instanceof Error ? error.message : "UNKNOWN_ERROR" },
{ status: 400 },
);
}
}

View File

@@ -0,0 +1,29 @@
import { NextRequest, NextResponse } from "next/server";
import { requireRequestSession } from "@/lib/boss-auth";
import { createGoal } from "@/lib/boss-data";
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 body = (await request.json()) as { text?: string };
if (!body.text) {
return NextResponse.json({ ok: false, message: "缺少 text" }, { status: 400 });
}
try {
const goal = await createGoal(projectId, body.text);
return NextResponse.json({ ok: true, goal });
} catch (error) {
return NextResponse.json(
{ ok: false, message: error instanceof Error ? error.message : "UNKNOWN_ERROR" },
{ status: 400 },
);
}
}

View File

@@ -0,0 +1,48 @@
import { NextRequest, NextResponse } from "next/server";
import { requireRequestSession } from "@/lib/boss-auth";
import { appendProjectMessage } from "@/lib/boss-data";
import { replyToMasterAgentUserMessage } from "@/lib/boss-master-agent";
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 body = (await request.json()) as {
body?: string;
kind?: "text" | "voice_intent" | "image_intent" | "video_intent";
};
try {
const message = await appendProjectMessage({
projectId,
senderLabel: session.displayName || "你",
body: body.body,
kind: body.kind ?? "text",
});
let masterReply:
| { ok: boolean; reason?: string; message?: string; accountId?: string; requestId?: string }
| undefined;
if (projectId === "master-agent" && (body.kind ?? "text") === "text" && message.body.trim()) {
masterReply = await replyToMasterAgentUserMessage({
requestMessageId: message.id,
requestText: message.body,
requestedBy: session.displayName,
requestedByAccount: session.account,
currentSessionExpiresAt: session.expiresAt,
});
}
return NextResponse.json({ ok: true, message, masterReply });
} catch (error) {
return NextResponse.json(
{ ok: false, message: error instanceof Error ? error.message : "UNKNOWN_ERROR" },
{ status: 400 },
);
}
}

View File

@@ -0,0 +1,23 @@
import { NextRequest, NextResponse } from "next/server";
import { requireRequestSession } from "@/lib/boss-auth";
import { getProjectDetailView } from "@/lib/boss-projections";
import { readState } 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 state = await readState();
const detail = getProjectDetailView(state, projectId);
if (!detail) {
return NextResponse.json({ ok: false, message: "PROJECT_NOT_FOUND" }, { status: 404 });
}
return NextResponse.json({ ok: true, ...detail });
}

View File

@@ -0,0 +1,39 @@
import { NextRequest, NextResponse } from "next/server";
import { requireRequestSession } from "@/lib/boss-auth";
import { readState, updateUserSettings } from "@/lib/boss-data";
export async function GET(request: NextRequest) {
const session = await requireRequestSession(request);
if (!session) {
return NextResponse.json({ ok: false, message: "UNAUTHORIZED" }, { status: 401 });
}
const state = await readState();
return NextResponse.json({
ok: true,
settings: state.user.settings,
user: state.user,
});
}
export async function POST(request: NextRequest) {
const session = await requireRequestSession(request);
if (!session) {
return NextResponse.json({ ok: false, message: "UNAUTHORIZED" }, { status: 401 });
}
const body = (await request.json()) as {
liveUpdates?: boolean;
showRiskBadges?: boolean;
confirmDangerousActions?: boolean;
preferredEntryPoint?: "conversations" | "devices" | "me";
};
try {
const settings = await updateUserSettings(body);
return NextResponse.json({ ok: true, settings });
} catch (error) {
return NextResponse.json(
{ ok: false, message: error instanceof Error ? error.message : "UNKNOWN_ERROR" },
{ status: 400 },
);
}
}

View File

@@ -0,0 +1,23 @@
import { NextRequest, NextResponse } from "next/server";
import { requireRequestSession } from "@/lib/boss-auth";
import { getThreadContextDetailView } from "@/lib/boss-projections";
import { readState } from "@/lib/boss-data";
export async function GET(
request: NextRequest,
context: { params: Promise<{ threadId: string }> },
) {
const session = await requireRequestSession(request);
if (!session) {
return NextResponse.json({ ok: false, message: "UNAUTHORIZED" }, { status: 401 });
}
const { threadId } = await context.params;
const state = await readState();
const detail = getThreadContextDetailView(state, threadId);
if (!detail) {
return NextResponse.json({ ok: false, message: "THREAD_NOT_FOUND" }, { status: 404 });
}
return NextResponse.json({ ok: true, ...detail });
}

View File

@@ -0,0 +1,35 @@
import { promises as fs } from "node:fs";
import { NextRequest } from "next/server";
import { requireRequestSession } from "@/lib/boss-auth";
import { getPublishedOtaAsset } from "@/lib/boss-ota";
export async function GET(request: NextRequest) {
const session = await requireRequestSession(request);
if (!session) {
return new Response(JSON.stringify({ ok: false, message: "UNAUTHORIZED" }), {
status: 401,
headers: { "Content-Type": "application/json; charset=utf-8" },
});
}
const asset = await getPublishedOtaAsset();
if (!asset) {
return new Response(JSON.stringify({ ok: false, message: "OTA_PACKAGE_NOT_FOUND" }), {
status: 404,
headers: { "Content-Type": "application/json; charset=utf-8" },
});
}
const content = await fs.readFile(asset.absolutePath);
return new Response(content, {
status: 200,
headers: {
"Content-Type": "application/vnd.android.package-archive",
"Content-Length": String(asset.sizeBytes),
"Content-Disposition": `attachment; filename=\"${asset.fileName}\"`,
ETag: asset.sha256,
"X-Boss-Ota-Sha256": asset.sha256,
"X-Boss-Ota-Updated-At": asset.updatedAt,
},
});
}

View File

@@ -0,0 +1,45 @@
import { NextRequest, NextResponse } from "next/server";
import { requireRequestSession } from "@/lib/boss-auth";
import { checkForOta, getOtaStatus, performOta } from "@/lib/boss-data";
export async function GET(request: NextRequest) {
const session = await requireRequestSession(request);
if (!session) {
return NextResponse.json({ ok: false, message: "UNAUTHORIZED" }, { status: 401 });
}
const status = await getOtaStatus();
return NextResponse.json({ ok: true, ...status });
}
export async function POST(request: NextRequest) {
const session = await requireRequestSession(request);
if (!session) {
return NextResponse.json({ ok: false, message: "UNAUTHORIZED" }, { status: 401 });
}
const raw = await request.text();
const body = raw.trim() ? (JSON.parse(raw) as { action?: "check" | "apply" }) : {};
const action = body.action ?? "apply";
try {
if (action === "check") {
const result = await checkForOta();
return NextResponse.json({ ok: true, ...result, message: result.hasOta ? "发现新的 OTA 版本。" : "当前已经是最新版本。" });
}
const result = await performOta();
return NextResponse.json({
ok: true,
version: result.version,
summary: result.summary,
downloadUrl: result.downloadUrl,
packageFileName: result.packageFileName,
packageSizeBytes: result.packageSizeBytes,
packageSha256: result.packageSha256,
});
} catch (error) {
return NextResponse.json(
{ ok: false, message: error instanceof Error ? error.message : "UNKNOWN_ERROR" },
{ status: 400 },
);
}
}

View File

@@ -0,0 +1,98 @@
import { NextRequest, NextResponse } from "next/server";
import { authorizeDeviceWriteRequest } from "@/lib/boss-device-auth";
import { upsertThreadContextSnapshot } from "@/lib/boss-data";
export async function POST(
request: NextRequest,
context: { params: Promise<{ workerId: string }> },
) {
const { workerId } = await context.params;
const body = (await request.json()) as {
nodeId?: string;
threadId?: string;
projectId?: string;
taskId?: string;
title?: string;
summary?: string;
sourceKind?: "codex_app_server" | "codex_sdk" | "worker_estimator";
status?: string;
contextBudgetRemainingPct?: number;
contextBudgetLevel?: "safe" | "watch" | "urgent" | "critical";
compactionExpectedAt?: string;
mustFinishBeforeCompaction?: boolean;
estimatedRemainingTurns?: number;
estimatedRemainingLargeMessages?: number;
lastCompactionAt?: string;
compactionCount?: number;
patchPending?: boolean;
testsPending?: boolean;
evidencePending?: boolean;
checklist?: string[];
capturedAt?: string;
};
if (
!body.nodeId ||
!body.threadId ||
!body.projectId ||
!body.taskId ||
!body.title ||
!body.summary ||
typeof body.contextBudgetRemainingPct !== "number"
) {
return NextResponse.json(
{ ok: false, message: "thread-context 字段不完整" },
{ status: 400 },
);
}
const authorization = await authorizeDeviceWriteRequest(request, body.nodeId);
if (!authorization.ok) {
return NextResponse.json({ ok: false, message: "UNAUTHORIZED_DEVICE_WRITE" }, { status: 401 });
}
if (!authorization.device) {
return NextResponse.json({ ok: false, message: "DEVICE_NOT_FOUND" }, { status: 404 });
}
try {
const result = await upsertThreadContextSnapshot(workerId, {
nodeId: body.nodeId,
threadId: body.threadId,
projectId: body.projectId,
taskId: body.taskId,
title: body.title,
summary: body.summary,
sourceKind: body.sourceKind ?? "worker_estimator",
status: (body.status as
| "idle"
| "running"
| "waiting_input"
| "waiting_approval"
| "context_watch"
| "context_urgent"
| "compacted"
| "handoff_pending"
| "completed"
| "failed") ?? "running",
contextBudgetRemainingPct: body.contextBudgetRemainingPct,
contextBudgetLevel: body.contextBudgetLevel,
compactionExpectedAt: body.compactionExpectedAt,
mustFinishBeforeCompaction: Boolean(body.mustFinishBeforeCompaction),
estimatedRemainingTurns: body.estimatedRemainingTurns ?? 0,
estimatedRemainingLargeMessages: body.estimatedRemainingLargeMessages ?? 0,
lastCompactionAt: body.lastCompactionAt,
compactionCount: body.compactionCount ?? 0,
patchPending: Boolean(body.patchPending),
testsPending: Boolean(body.testsPending),
evidencePending: Boolean(body.evidencePending),
checklist: body.checklist ?? [],
capturedAt: body.capturedAt ?? new Date().toISOString(),
});
return NextResponse.json(result);
} catch (error) {
return NextResponse.json(
{ ok: false, message: error instanceof Error ? error.message : "UNKNOWN_ERROR" },
{ status: 400 },
);
}
}