import { NextRequest } from "next/server"; import { jsonNoStore } from "@/lib/api-response"; import { requireRequestSession } from "@/lib/boss-auth"; import { BOSS_PERMISSION_TEMPLATES } from "@/lib/boss-access-templates"; import { buildAdminOverview } from "@/lib/boss-admin-overview"; import { readState, type BossState } from "@/lib/boss-data"; import { getBossStateBackupStatus, type BossStateBackupStatus } from "@/lib/boss-state-backups"; import { buildMasterAgentTaskSlaRows, type MasterAgentTaskSlaLevel } from "@/lib/master-agent-task-sla"; const PLATFORM_MENU_TREE = [ { key: "platform-overview", label: "平台总览" }, { key: "platform-provisioning", label: "企业开通" }, { key: "platform-customer-plans", label: "客户与套餐" }, { key: "platform-devices", label: "全局设备" }, { key: "platform-risk", label: "全局风险" }, { key: "platform-customer-success", label: "客户成功" }, { key: "platform-audit", label: "系统审计" }, { key: "platform-billing", label: "计费与授权" }, { key: "platform-settings", label: "平台设置" }, ] as const; const ENTERPRISE_MENU_TREE = [ { key: "enterprise-overview", label: "企业总览" }, { key: "enterprise-members", label: "组织与成员" }, { key: "enterprise-devices-agents", label: "设备与项目" }, { key: "enterprise-agent-flows", label: "Agent 与流程" }, { key: "enterprise-skill", label: "Skill 中心" }, { key: "enterprise-risk-backup", label: "风险与审计" }, { key: "enterprise-backup", label: "备份与回退" }, { key: "enterprise-settings", label: "企业设置" }, ] as const; type BackofficeSurface = "platform" | "enterprise"; function companyNameMap(state: BossState) { return new Map(state.adminCompanies.map((company) => [company.companyId, company.name])); } function companyNameFor(state: BossState, companyId?: string) { if (!companyId || companyId === "default") return "默认公司"; return companyNameMap(state).get(companyId) ?? companyId; } function accountCompanyId(state: BossState, account?: string) { if (!account) return undefined; return state.authAccounts.find((item) => item.account === account)?.companyId ?? "default"; } function resolvedDeviceCompanyId(state: BossState, device: { account?: string; companyId?: string }) { return device.companyId ?? accountCompanyId(state, device.account) ?? "default"; } function filteredStateForCompany(state: BossState, companyId: string): BossState { const companyAccounts = new Set( state.authAccounts.filter((account) => (account.companyId ?? "default") === companyId).map((account) => account.account), ); const devices = state.devices.filter((device) => resolvedDeviceCompanyId(state, device) === companyId); const deviceIds = new Set(devices.map((device) => device.id)); const projects = state.projects.filter( (project) => project.deviceIds.some((deviceId) => deviceIds.has(deviceId)) || project.groupMembers.some((member) => deviceIds.has(member.deviceId)), ); const projectIds = new Set(projects.map((project) => project.id)); return { ...state, adminCompanies: state.adminCompanies.filter((company) => company.companyId === companyId), authAccounts: state.authAccounts.filter((account) => companyAccounts.has(account.account)), authSessions: state.authSessions.filter((session) => companyAccounts.has(session.account)), devices, projects, deviceSkills: state.deviceSkills.filter((skill) => deviceIds.has(skill.deviceId)), accountDeviceGrants: state.accountDeviceGrants.filter((grant) => companyAccounts.has(grant.account) && deviceIds.has(grant.deviceId)), accountProjectGrants: state.accountProjectGrants.filter((grant) => companyAccounts.has(grant.account) && projectIds.has(grant.projectId)), accountSkillGrants: state.accountSkillGrants.filter((grant) => companyAccounts.has(grant.account)), opsFaults: state.opsFaults.filter( (fault) => (fault.nodeId && deviceIds.has(fault.nodeId)) || (fault.projectId && projectIds.has(fault.projectId)), ), threadContextAlerts: state.threadContextAlerts.filter((alert) => projectIds.has(alert.projectId)), masterAgentTasks: state.masterAgentTasks.filter( (task) => (task.deviceId && deviceIds.has(task.deviceId)) || (task.projectId && projectIds.has(task.projectId)), ), permissionAuditLogs: state.permissionAuditLogs.filter( (log) => companyAccounts.has(log.actorAccount) || Boolean(log.targetAccount && companyAccounts.has(log.targetAccount)) || Boolean(log.deviceId && deviceIds.has(log.deviceId)) || Boolean(log.projectId && projectIds.has(log.projectId)), ), adminRiskTimeline: state.adminRiskTimeline.filter((event) => event.companyId === companyId), adminNotifications: state.adminNotifications.filter((notification) => { const companyScoped = notification as { companyId?: string; deviceId?: string; projectId?: string }; return ( companyScoped.companyId === companyId || Boolean(companyScoped.deviceId && deviceIds.has(companyScoped.deviceId)) || Boolean(companyScoped.projectId && projectIds.has(companyScoped.projectId)) ); }), }; } function safeUsers(state: BossState) { return state.authAccounts.map((account) => ({ id: account.id, account: account.account, displayName: account.displayName, role: account.role, status: account.status ?? "active", companyId: account.companyId ?? "default", companyName: companyNameFor(state, account.companyId), primaryDeviceId: account.primaryDeviceId, codexNodeId: account.codexNodeId, codexNodeLabel: account.codexNodeLabel, lastLoginAt: account.lastLoginAt, lastLoginMethod: account.lastLoginMethod, mfaRequired: Boolean(account.mfaRequired), createdAt: account.createdAt, updatedAt: account.updatedAt, })); } function skillResources(state: BossState) { const byName = new Map< string, { skillId: string; name: string; description: string; category?: string; invocation?: string; sourceType: "device" | "catalog"; deviceCount: number; devices: Array<{ deviceId: string; updatedAt: string }>; updatedAt: string; } >(); for (const skill of state.deviceSkills) { const existing = byName.get(skill.name); if (existing) { existing.deviceCount += existing.devices.some((device) => device.deviceId === skill.deviceId) ? 0 : 1; existing.devices.push({ deviceId: skill.deviceId, updatedAt: skill.updatedAt }); if (skill.updatedAt.localeCompare(existing.updatedAt) > 0) { existing.updatedAt = skill.updatedAt; } continue; } byName.set(skill.name, { skillId: skill.skillId, name: skill.name, description: skill.description, category: skill.category, invocation: skill.invocation, sourceType: "device", deviceCount: 1, devices: [{ deviceId: skill.deviceId, updatedAt: skill.updatedAt }], updatedAt: skill.updatedAt, }); } for (const catalogItem of state.skillCatalog) { if (byName.has(catalogItem.name)) continue; byName.set(catalogItem.name, { skillId: catalogItem.skillId, name: catalogItem.name, description: catalogItem.description, category: catalogItem.category, sourceType: "catalog", deviceCount: 0, devices: [], updatedAt: catalogItem.updatedAt, }); } return [...byName.values()].sort( (left, right) => right.deviceCount - left.deviceCount || left.name.localeCompare(right.name, "zh-CN"), ); } function projectResources(state: BossState) { return state.projects.map((project) => ({ id: project.id, name: project.name, deviceIds: project.deviceIds, deviceCount: project.deviceIds.length, folderName: project.threadMeta.folderName, threadId: project.threadMeta.threadId, threadDisplayName: project.threadMeta.threadDisplayName, isGroup: project.isGroup, collaborationMode: project.collaborationMode, unreadCount: project.unreadCount, riskLevel: project.riskLevel, updatedAt: project.updatedAt, lastMessageAt: project.lastMessageAt, })); } function rolesContract() { return { builtInRoles: [ { role: "highest_admin", label: "超级管理员", description: "平台侧最高权限,可管理全部公司、账号、设备、项目、Skill、风险和审计。", }, { role: "admin", label: "企业管理员", description: "企业内管理员,按授权范围管理本公司资源。", }, { role: "member", label: "成员账号", description: "企业子账号,只能访问已分配的电脑、项目和 Skill。", }, ], permissionTemplates: BOSS_PERMISSION_TEMPLATES, }; } function safePercent(value: number, total: number) { if (total <= 0) return 0; return Math.max(0, Math.min(100, Math.round((value / total) * 100))); } function healthScore(company: { accountCount: number; deviceCount: number; onlineDeviceCount: number; openRiskCount: number }) { const onlineScore = company.deviceCount > 0 ? safePercent(company.onlineDeviceCount, company.deviceCount) : 80; const riskPenalty = Math.min(45, company.openRiskCount * 12); const activityBonus = Math.min(10, company.accountCount); return Math.max(0, Math.min(100, onlineScore - riskPenalty + activityBonus)); } function riskAggregateValue(risks: Array<{ kind: string; title: string }>, matcher: (risk: { kind: string; title: string }) => boolean) { return risks.filter(matcher).length; } function minutesSince(value?: string): number | null { if (!value) return null; const timestamp = Date.parse(value); if (!Number.isFinite(timestamp)) return null; return Math.max(0, Math.floor((Date.now() - timestamp) / 60_000)); } function buildDataSafetySummary(backupStatus: BossStateBackupStatus) { const ageMinutes = minutesSince(backupStatus.lastBackupAt); const healthLabel = backupStatus.status === "error" ? "备份异常" : backupStatus.status === "empty" || !backupStatus.lastBackupAt ? "暂无备份" : ageMinutes !== null && ageMinutes > 24 * 60 ? "备份过期" : "备份正常"; const nextAction = healthLabel === "备份异常" ? "检查备份目录与状态文件写入权限" : healthLabel === "暂无备份" ? "立即创建首个状态快照" : healthLabel === "备份过期" ? "补创建状态快照并检查自动备份任务" : "保持自动快照并定期演练恢复"; return { mode: backupStatus.mode, status: backupStatus.status, restorePointCount: backupStatus.restorePointCount, lastBackupAt: backupStatus.lastBackupAt ?? "", ageMinutes, healthLabel, rpoLabel: "文件 MVP:以最近成功快照为准", rtoLabel: "文件 MVP:人工恢复目标 30-60 分钟", nextAction, }; } function canRecoverActiveTask(task: BossState["masterAgentTasks"][number]) { if (task.recoverable) return true; if (task.phase === "recoverable_failed") return true; if (task.phase === "turn_started" || task.phase === "awaiting_reply" || task.phase === "completing") { return false; } const maxAttempts = task.maxAttempts ?? 1; return (task.attemptCount ?? 0) < maxAttempts; } function buildTaskRiskSummary(state: BossState) { const activeStatuses = new Set(["queued", "running", "needs_user_action"]); const activeTasks = state.masterAgentTasks.filter((task) => activeStatuses.has(task.status)); const rows = buildMasterAgentTaskSlaRows(state) .filter((row) => activeStatuses.has(row.status)) .map((row) => ({ taskId: row.taskId, projectId: row.projectId, deviceId: row.deviceId, status: row.status, phase: row.phase, stale: row.stale, recoverable: row.recoverable || canRecoverActiveTask(state.masterAgentTasks.find((task) => task.taskId === row.taskId)!), lastProgressAt: row.lastProgressAt, summary: row.summary, slaLevel: row.slaLevel, slaDueAt: row.slaDueAt, recommendedAction: row.recommendedAction, })) .slice(0, 20); return { counts: { active: activeTasks.length, stale: rows.filter((row) => row.stale).length, recoverable: activeTasks.filter(canRecoverActiveTask).length, needsUserAction: activeTasks.filter((task) => task.status === "needs_user_action" || task.phase === "needs_user_action").length, }, rows, }; } function buildTaskSlaPanel(state: BossState) { const rows = buildMasterAgentTaskSlaRows(state).slice(0, 50); const countByLevel = (level: MasterAgentTaskSlaLevel) => rows.filter((row) => row.slaLevel === level).length; return { generatedAt: new Date().toISOString(), summary: { total: rows.length, active: rows.filter((row) => row.status === "queued" || row.status === "running" || row.status === "needs_user_action").length, ok: countByLevel("ok"), watch: countByLevel("watch"), breached: countByLevel("breached"), recoverable: countByLevel("recoverable"), terminal: countByLevel("terminal"), autoRecoverable: rows.filter((row) => row.autoRecoverable).length, }, rows, }; } function buildBackofficeInsights(state: BossState, options: { surface: BackofficeSurface; backupStatus: BossStateBackupStatus }) { const overview = buildAdminOverview(state); const devices = state.devices; const onlineDevices = devices.filter((device) => device.status === "online").length; const guiReady = devices.filter((device) => Boolean(device.capabilities?.gui.connected)).length; const cliReady = devices.filter((device) => Boolean(device.capabilities?.cli.connected)).length; const computerUseReady = devices.filter((device) => Boolean(device.capabilities?.computerUse.connected)).length; const browserReady = devices.filter((device) => Boolean(device.capabilities?.browserAutomation.connected)).length; const codexHealthy = devices.length === 0 || (guiReady + cliReady) >= devices.length; const computerUseHealthy = devices.length === 0 || computerUseReady >= Math.ceil(devices.length / 2); return { onboardingSteps: ["企业信息", "老板账号", "套餐授权", "设备与交付"], serviceStatuses: [ { label: "Boss API", value: "正常", tone: "green" }, { label: "OTA", value: "正常", tone: "green" }, { label: "Codex Provider", value: codexHealthy ? "正常" : "降级", tone: codexHealthy ? "green" : "orange" }, { label: "Computer Use", value: computerUseHealthy ? "正常" : "降级", tone: computerUseHealthy ? "green" : "orange" }, { label: "Skill Hub", value: state.deviceSkills.length + state.skillCatalog.length > 0 ? "正常" : "待配置", tone: "green" }, ], openingPreview: [ { label: "默认套餐", value: "企业专业版" }, { label: "推荐设备", value: `${Math.max(1, devices.length || 1)} 台起` }, { label: "Codex Provider", value: "App Server 优先,CLI 兜底" }, { label: "Computer Use", value: "macOS 桌面控制" }, ], deliveryChecklist: [ { label: "API 可用", done: true }, { label: "OTA 可用", done: true }, { label: "boss-agent 安装包已生成", done: true }, { label: "初始密码策略已设置", done: true }, { label: "客户成功负责人已分配", done: overview.companies.some((company) => Boolean(company.successOwnerAccount)) }, ], recentCompanies: overview.companies.slice(0, 5).map((company) => ({ companyId: company.companyId, label: company.name, note: `${company.planTier ?? "enterprise"} · ${company.deviceCount} 台设备 · ${company.openRiskCount} 个风险`, })), customerHealthRows: overview.companies.map((company) => ({ companyId: company.companyId, name: company.name, healthScore: healthScore(company), planTier: company.planTier ?? "enterprise", onlineDevices: `${company.onlineDeviceCount}/${company.deviceCount}`, openRiskCount: company.openRiskCount, ownerAccount: company.successOwnerAccount || company.ownerAccount || "未指派", })), riskAggregates: [ { label: "设备离线", value: riskAggregateValue(overview.risks, (risk) => risk.kind === "device_offline"), }, { label: "主 Agent 执行失败", value: riskAggregateValue(overview.risks, (risk) => risk.kind === "master_agent_task_failed"), }, { label: "任务 SLA 告警", value: riskAggregateValue(overview.risks, (risk) => risk.kind === "master_agent_task_sla"), }, { label: "Computer Use 权限缺失", value: riskAggregateValue(overview.risks, (risk) => /Computer Use|权限/.test(risk.title)), }, { label: "Skill 升级失败", value: riskAggregateValue(overview.risks, (risk) => /Skill/.test(risk.title)), }, { label: "备份异常", value: riskAggregateValue(overview.risks, (risk) => /备份|backup/i.test(risk.title)), }, ], customerFollowups: overview.riskTimeline.slice(0, 5).map((event) => ({ eventId: event.eventId, label: event.note || event.action, meta: `${event.actorAccount} · ${event.createdAt}`, })), enterpriseGoals: state.projects.slice(0, 3).map((project) => ({ projectId: project.id, label: project.name, progress: Math.max(30, Math.min(96, 82 - (project.unreadCount ?? 0) * 3 - (project.riskLevel === "high" ? 18 : 0))), })), organizationUnits: ["销售部", "客服部", "研发部", "财务部", "行政部"], departmentProgress: [ { label: "销售部", note: "线索跟进正常", tone: "green" }, { label: "客服部", note: `${overview.summary.openNotifications} 个通知待处理`, tone: overview.summary.openNotifications > 0 ? "orange" : "green" }, { label: "研发部", note: `${state.projects.length} 个项目运行中`, tone: "green" }, { label: "财务部", note: "账单与授权正常", tone: "green" }, ], masterAgentSummary: [ `当前企业有 ${state.projects.length} 个项目、${onlineDevices}/${devices.length} 台电脑在线。`, overview.summary.openRisks > 0 ? `建议优先处理 ${overview.summary.openRisks} 个开放风险。` : "当前没有开放风险。", ], permissionHighlights: ["device.view", "thread.chat", "master_agent.takeover", "computer.control", "skill.use"], agentFlowSteps: ["主 Agent", "项目 Agent", "本地 Agent", "Codex / Computer Use / Skill"], skillUsageAudit: state.permissionAuditLogs.slice(0, 5).map((log) => ({ auditId: log.auditId, label: log.detail || log.action, meta: `${log.actorAccount} · ${log.createdAt}`, })), recoveryActions: ["消息恢复", "项目目标恢复", "权限撤销", "Skill 回滚", "Codex checkpoint"], backupStatus: { lastBackupAt: options.backupStatus.lastBackupAt ?? "", status: options.backupStatus.status === "ready" ? "校验通过" : options.backupStatus.status === "empty" ? "暂无快照" : "备份异常", restorePointCount: options.backupStatus.restorePointCount, backupDir: options.backupStatus.backupDir, detail: options.backupStatus.detail, }, dataSafetySummary: buildDataSafetySummary(options.backupStatus), taskRiskSummary: buildTaskRiskSummary(state), taskSlaPanel: buildTaskSlaPanel(state), capabilitySummary: { guiReady, cliReady, computerUseReady, browserReady, }, surface: options.surface, }; } function buildBackofficePayload( state: BossState, options: { surface: BackofficeSurface; currentCompanyId?: string; backupStatus: BossStateBackupStatus }, ) { const overview = buildAdminOverview(state); const skills = skillResources(state); const currentCompany = options.currentCompanyId ? state.adminCompanies.find((company) => company.companyId === options.currentCompanyId) ?? null : null; return { ok: true, surface: options.surface, currentCompany, menuTree: options.surface === "platform" ? PLATFORM_MENU_TREE : ENTERPRISE_MENU_TREE, insights: buildBackofficeInsights(state, { surface: options.surface, backupStatus: options.backupStatus }), workbench: { summary: overview.summary, companies: overview.companies.slice(0, 10), devices: overview.devices.slice(0, 20), risks: overview.risks.slice(0, 20), notifications: overview.notifications, grantsSummary: overview.grantsSummary, }, tenants: overview.companies.map((company) => ({ ...company, lifecycleStatus: company.status ?? "active", })), users: safeUsers(state), roles: rolesContract(), resourceGroups: { devices: overview.devices, projects: projectResources(state), skills, grants: { devices: state.accountDeviceGrants, projects: state.accountProjectGrants, skills: state.accountSkillGrants, }, }, audit: { risks: overview.risks, notifications: overview.notifications, riskTimeline: overview.riskTimeline, permissionLogs: state.permissionAuditLogs .slice() .sort((left, right) => right.createdAt.localeCompare(left.createdAt)) .slice(0, 100), }, yudaoMapping: { tenant: "adminCompanies", user: "authAccounts", role: "BOSS_PERMISSION_TEMPLATES", menu: "menuTree", operateLog: "permissionAuditLogs", resource: "devices/projects/deviceSkills", risk: "opsFaults/threadContextAlerts/masterAgentTasks/adminNotifications", }, }; } export async function GET(request: NextRequest) { const session = await requireRequestSession(request); if (!session) { return jsonNoStore({ ok: false, message: "UNAUTHORIZED" }, { status: 401 }); } const state = await readState(); const backupStatus = await getBossStateBackupStatus(); const url = new URL(request.url); const scope = url.searchParams.get("scope") === "enterprise" ? "enterprise" : "platform"; if (scope === "platform") { if (session.role !== "highest_admin") { return jsonNoStore({ ok: false, message: "FORBIDDEN" }, { status: 403 }); } return jsonNoStore(buildBackofficePayload(state, { surface: "platform", backupStatus })); } if (session.role !== "admin" && session.role !== "highest_admin") { return jsonNoStore({ ok: false, message: "FORBIDDEN" }, { status: 403 }); } const requestedCompanyId = url.searchParams.get("companyId")?.trim(); const companyId = session.role === "highest_admin" ? requestedCompanyId || state.adminCompanies[0]?.companyId || "default" : accountCompanyId(state, session.account); if (!companyId) { return jsonNoStore({ ok: false, message: "COMPANY_NOT_FOUND" }, { status: 404 }); } const scopedState = filteredStateForCompany(state, companyId); return jsonNoStore(buildBackofficePayload(scopedState, { surface: "enterprise", currentCompanyId: companyId, backupStatus })); }