"use client"; import Link from "next/link"; import { usePathname, useRouter } from "next/navigation"; import { useState } from "react"; import clsx from "clsx"; import { sendAppLog } from "@/components/app-runtime"; import { DeviceImportDraftManager } from "@/components/device-import-draft-manager"; import { clearNativeSessionSnapshot, currentAppLocation, isNativeBossApp, persistNativeSessionSnapshot, popAppHistoryEntry, resolveAppBackAction, } from "@/lib/boss-app-client"; import { getMasterAgentChatMenuItems } from "@/lib/master-agent-chat-menu"; import { extractApprovedTargetProjectIds, summarizeDispatchPlan, summarizeDispatchPlanCompact, summarizeDispatchPlanLightTitle, } from "@/lib/dispatch-plan-ui"; import type { ThreadConversationExecutionConflict, ThreadConversationExecutionConflictAction, } from "@/lib/thread-execution-conflict"; import { parseChatMarkdown, type ChatMarkdownBlock } from "@/lib/chat-markdown"; import { describeThreadConversationExecutionConflict, labelForProjectConflictAllowPolicy, labelForProjectConflictState, labelForThreadConversationExecutionConflictDecision, summarizeThreadConversationExecutionDecisionResult, } from "@/lib/thread-execution-conflict-ui"; import type { Device, DeviceEnrollment, GoalItem, MasterIdentitySummary, Message, OtaUpdate, OtaUpdateLog, OpsRepairTicket, OpsRepairVerification, ProjectOrchestrationBackendState, OrchestrationBackendId, ThreadContextSnapshot, UserProfile, UserSettings, } from "@/lib/boss-data"; import type { ConversationItem, DeviceWorkspaceView } from "@/lib/boss-projections"; import { formatTimestampLabel } from "@/lib/boss-projections"; function formatClock(value: string) { return formatTimestampLabel(value); } function formatBytes(value?: number) { if (!value || value <= 0) return "未知大小"; if (value >= 1024 * 1024) return `${(value / 1024 / 1024).toFixed(2)} MB`; if (value >= 1024) return `${(value / 1024).toFixed(1)} KB`; return `${value} B`; } function boundDeviceIdFromDom() { return document.body.dataset.boundDeviceId || "mac-studio"; } function arrayLength(value: unknown) { return Array.isArray(value) ? value.length : 0; } function textFromMetadata(value: unknown) { return typeof value === "string" && value.trim() ? value.trim() : "未发现"; } function objectFromMetadata(value: unknown): Record { return value && typeof value === "object" && !Array.isArray(value) ? (value as Record) : {}; } function numberFromMetadata(value: unknown) { const numeric = Number(value); return Number.isFinite(numeric) ? Math.max(0, Math.round(numeric)) : 0; } function minuteTimestampFromMetadata(value: unknown) { const text = typeof value === "string" ? value.trim() : ""; if (!text) return "未发现"; const date = new Date(text); if (Number.isNaN(date.getTime())) return text; const parts = new Intl.DateTimeFormat("zh-CN", { timeZone: "Asia/Shanghai", year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", hour12: false, }).formatToParts(date); const valueOf = (type: string) => parts.find((part) => part.type === type)?.value ?? ""; return `${valueOf("year")}-${valueOf("month")}-${valueOf("day")} ${valueOf("hour")}:${valueOf("minute")}`; } export function buildDeviceWorkspaceDetailCards(workspace: DeviceWorkspaceView) { const selectedDevice = workspace.selectedDevice; const primaryPolicy = workspace.projectExecutionPolicies?.[0]; const codexAppServerMetadata = selectedDevice?.capabilities?.codexAppServer?.metadata ?? {}; const codexModelsCount = arrayLength(codexAppServerMetadata.models); const codexSkillCount = arrayLength(codexAppServerMetadata.skills); const codexPluginCount = arrayLength(codexAppServerMetadata.plugins); const codexAppCount = arrayLength(codexAppServerMetadata.apps); const codexExperimentalFeatureCount = arrayLength(codexAppServerMetadata.experimentalFeatures); const codexCollaborationModeCount = arrayLength(codexAppServerMetadata.collaborationModes); const codexMcpServerCount = arrayLength(codexAppServerMetadata.mcpServers); const codexPermissionProfileCount = arrayLength(codexAppServerMetadata.permissionProfiles); const codexAccountSummary = objectFromMetadata(codexAppServerMetadata.accountSummary); const codexRateLimitSummary = objectFromMetadata(codexAppServerMetadata.rateLimitSummary); const codexAppConfigSummary = objectFromMetadata(codexAppServerMetadata.appConfigSummary); const codexConfigRequirements = objectFromMetadata(codexAppServerMetadata.configRequirements); const codexExternalAgentMigration = objectFromMetadata(codexAppServerMetadata.externalAgentMigration); const codexSkillExtraRootsSummary = objectFromMetadata(codexAppServerMetadata.skillExtraRootsSummary); const codexHookSummary = objectFromMetadata(codexAppServerMetadata.hookSummary); const codexThreadSummary = objectFromMetadata(codexAppServerMetadata.threadSummary); const codexThreadTurnSummary = objectFromMetadata(codexAppServerMetadata.threadTurnSummary); const codexThreadActionSummary = objectFromMetadata(codexAppServerMetadata.threadActionSummary); const codexPluginGovernanceSummary = objectFromMetadata(codexAppServerMetadata.pluginGovernanceSummary); const codexAccountGovernanceSummary = objectFromMetadata(codexAppServerMetadata.accountGovernanceSummary); const codexConfigGovernanceSummary = objectFromMetadata(codexAppServerMetadata.configGovernanceSummary); const codexFileSystemGovernanceSummary = objectFromMetadata(codexAppServerMetadata.fileSystemGovernanceSummary); const codexCommandSessionSummary = objectFromMetadata(codexAppServerMetadata.commandSessionSummary); const codexExternalAgentGovernanceSummary = objectFromMetadata( codexAppServerMetadata.externalAgentGovernanceSummary, ); const codexMarketplaceGovernanceSummary = objectFromMetadata(codexAppServerMetadata.marketplaceGovernanceSummary); const codexExperimentalFeatureGovernanceSummary = objectFromMetadata( codexAppServerMetadata.experimentalFeatureGovernanceSummary, ); const codexReviewGovernanceSummary = objectFromMetadata(codexAppServerMetadata.reviewGovernanceSummary); const codexWindowsSandboxGovernanceSummary = objectFromMetadata( codexAppServerMetadata.windowsSandboxGovernanceSummary, ); const codexFuzzyFileSearchSummary = objectFromMetadata(codexAppServerMetadata.fuzzyFileSearchSummary); return { capabilities: { title: "执行能力", items: { gui: `GUI:${selectedDevice?.capabilities?.gui?.connected ? "已连接" : "未连接"}`, cli: `CLI:${selectedDevice?.capabilities?.cli?.connected ? "已连接" : "未连接"}`, browserAutomation: `浏览器自动化:${ selectedDevice?.capabilities?.browserAutomation?.connected ? "已连接" : "未连接" }`, computerUse: `桌面控制:${ selectedDevice?.capabilities?.computerUse?.connected ? "已连接" : "未连接" }`, codexAppServer: `Codex App Server:${ selectedDevice?.capabilities?.codexAppServer?.connected ? "已连接" : "未连接" }`, codexModels: codexModelsCount > 0 ? `模型:${codexModelsCount} 个 · 默认 ${textFromMetadata( codexAppServerMetadata.defaultModelId, )} · 快速 ${textFromMetadata(codexAppServerMetadata.fastModelId)} · 深度 ${textFromMetadata( codexAppServerMetadata.deepModelId, )}` : "模型:未发现", codexExtensions: `扩展:Skill ${codexSkillCount} 个 · Plugin ${codexPluginCount} 个 · App ${codexAppCount} 个`, codexGovernance: `治理:实验特性 ${codexExperimentalFeatureCount} 个 · 协作模式 ${codexCollaborationModeCount} 个 · MCP ${codexMcpServerCount} 个 · 权限 ${codexPermissionProfileCount} 个`, codexAccount: `账号:${textFromMetadata(codexAccountSummary.authMode)} · 套餐 ${textFromMetadata( codexAccountSummary.planType, )} · 额度 ${numberFromMetadata(codexRateLimitSummary.maxUsedPercent)}%`, codexConfig: `配置:App ${numberFromMetadata(codexAppConfigSummary.appCount)} 个 · 已启用 ${numberFromMetadata( codexAppConfigSummary.enabledAppCount, )} 个 · 托管要求 ${numberFromMetadata(codexConfigRequirements.requirementCount)} 个 · 外部迁移 ${numberFromMetadata( codexExternalAgentMigration.itemCount, )} 项`, codexSkillRoots: `共享 Skill 根:${numberFromMetadata(codexSkillExtraRootsSummary.rootCount)} 个 · ${ textFromMetadata(codexSkillExtraRootsSummary.status) === "applied" ? "已下发" : textFromMetadata(codexSkillExtraRootsSummary.status) === "failed" ? "下发失败" : "未配置" }`, codexHooks: `Hook:${numberFromMetadata(codexHookSummary.hookCount)} 个 · 启用 ${numberFromMetadata( codexHookSummary.enabledHookCount, )} 个 · 警告 ${numberFromMetadata(codexHookSummary.warningCount)} 个`, codexThreads: `线程:${numberFromMetadata(codexThreadSummary.threadCount)} 个 · 已加载 ${numberFromMetadata( codexThreadSummary.loadedThreadCount, )} 个 · 活跃 ${numberFromMetadata(codexThreadSummary.activeThreadCount)} 个 · 最新 ${minuteTimestampFromMetadata( codexThreadSummary.latestUpdatedAt, )}`, codexTurns: `轮次:${numberFromMetadata(codexThreadTurnSummary.totalTurnCount)} 个 · 运行中 ${numberFromMetadata( codexThreadTurnSummary.runningTurnCount, )} 个 · 完成 ${numberFromMetadata(codexThreadTurnSummary.completedTurnCount)} 个 · 最新 ${minuteTimestampFromMetadata( codexThreadTurnSummary.latestUpdatedAt, )}`, codexThreadActions: `线程操作:${numberFromMetadata( codexThreadActionSummary.actionCount, )} 项 · 生命周期 ${numberFromMetadata(codexThreadActionSummary.lifecycleActionCount)} 项 · 活跃干预 ${numberFromMetadata( codexThreadActionSummary.liveTurnActionCount, )} 项 · ${codexThreadActionSummary.shellActionAvailable === true ? "Shell 可用" : "Shell 不可用"}`, codexPluginGovernance: `插件治理:${numberFromMetadata( codexPluginGovernanceSummary.actionCount, )} 项 · 安装/卸载 ${numberFromMetadata(codexPluginGovernanceSummary.lifecycleActionCount)} 项 · 共享 ${numberFromMetadata( codexPluginGovernanceSummary.shareActionCount, )} 项 · ${codexPluginGovernanceSummary.skillReadAvailable === true ? "Skill 读取可用" : "Skill 读取不可用"}`, codexAccountGovernance: `账号治理:${numberFromMetadata( codexAccountGovernanceSummary.actionCount, )} 项 · 登录 ${numberFromMetadata(codexAccountGovernanceSummary.loginActionCount)} 项 · ${ codexAccountGovernanceSummary.tokenRefreshAvailable === true ? "令牌刷新可用" : "令牌刷新不可用" } · ${codexAccountGovernanceSummary.billingNudgeAvailable === true ? "额度提醒可用" : "额度提醒不可用"}`, codexConfigGovernance: `配置治理:${numberFromMetadata( codexConfigGovernanceSummary.actionCount, )} 项 · 写入 ${numberFromMetadata(codexConfigGovernanceSummary.writeActionCount)} 项 · 重载 ${numberFromMetadata( codexConfigGovernanceSummary.reloadActionCount, )} 项 · ${codexConfigGovernanceSummary.readActionAvailable === true ? "读取可用" : "读取不可用"}`, codexFileSystemGovernance: `文件治理:${numberFromMetadata( codexFileSystemGovernanceSummary.actionCount, )} 项 · 读 ${numberFromMetadata(codexFileSystemGovernanceSummary.readActionCount)} 项 · 写 ${numberFromMetadata( codexFileSystemGovernanceSummary.writeActionCount, )} 项 · 监听 ${numberFromMetadata(codexFileSystemGovernanceSummary.watchActionCount)} 项`, codexCommandSession: `命令会话:${numberFromMetadata( codexCommandSessionSummary.actionCount, )} 项 · 控制 ${numberFromMetadata(codexCommandSessionSummary.controlActionCount)} 项 · ${ codexCommandSessionSummary.streamAvailable === true ? "输出流可用" : "输出流不可用" } · ${codexCommandSessionSummary.terminationAvailable === true ? "可终止" : "不可终止"}`, codexExternalAgentGovernance: `迁移治理:${numberFromMetadata( codexExternalAgentGovernanceSummary.actionCount, )} 项 · 导入 ${numberFromMetadata(codexExternalAgentGovernanceSummary.importActionCount)} 项 · ${ codexExternalAgentGovernanceSummary.detectActionAvailable === true ? "检测可用" : "检测不可用" }`, codexMarketplaceGovernance: `市场治理:${numberFromMetadata( codexMarketplaceGovernanceSummary.actionCount, )} 项 · 写入 ${numberFromMetadata(codexMarketplaceGovernanceSummary.writeActionCount)} 项 · ${ codexMarketplaceGovernanceSummary.upgradeAvailable === true ? "升级可用" : "升级不可用" }`, codexExperimentalFeatureGovernance: `实验特性治理:${numberFromMetadata( codexExperimentalFeatureGovernanceSummary.actionCount, )} 项 · 写入 ${numberFromMetadata(codexExperimentalFeatureGovernanceSummary.writeActionCount)} 项 · ${ codexExperimentalFeatureGovernanceSummary.listAvailable === true ? "列表可用" : "列表不可用" }`, codexReviewGovernance: `审查治理:${numberFromMetadata( codexReviewGovernanceSummary.actionCount, )} 项 · ${codexReviewGovernanceSummary.reviewStartAvailable === true ? "审查可启动" : "审查不可启动"}`, codexWindowsSandboxGovernance: `Windows 沙箱:${numberFromMetadata( codexWindowsSandboxGovernanceSummary.actionCount, )} 项 · 设置 ${numberFromMetadata(codexWindowsSandboxGovernanceSummary.setupActionCount)} 项 · ${ codexWindowsSandboxGovernanceSummary.readinessAvailable === true ? "准备检查可用" : "准备检查不可用" }`, codexFuzzyFileSearch: `文件搜索事件:${numberFromMetadata( codexFuzzyFileSearchSummary.eventCount, )} 项 · ${codexFuzzyFileSearchSummary.completedEventAvailable === true ? "完成事件可用" : "完成事件不可用"}`, preferredExecutionMode: `默认执行模式:${ selectedDevice?.preferredExecutionMode === "gui" ? "GUI" : selectedDevice?.preferredExecutionMode === "cli" ? "CLI" : "未知" }`, }, }, conflicts: { title: "异常项目 / 文件夹冲突", headerHint: primaryPolicy ? "已接入,可直接调整" : "当前没有异常项目", scopeLabel: "仅作用于当前异常项目 / 文件夹", actions: ["禁止", "允许本次", "永久放行"], items: { device: `设备:${selectedDevice?.name ?? selectedDevice?.id ?? "未知设备"}`, folderKey: `文件夹:${primaryPolicy?.folderKey ?? "暂无"}`, projectId: `项目:${primaryPolicy?.projectId ?? "暂无"}`, allowPolicy: `当前策略:${labelForProjectConflictAllowPolicy(primaryPolicy?.allowPolicy ?? null)}`, conflictState: `冲突态:${labelForProjectConflictState(primaryPolicy?.conflictState ?? null)}`, }, }, }; } async function waitForLoginSessionReady(nativeClient: boolean) { for (let attempt = 0; attempt < 5; attempt += 1) { const response = await fetch("/api/auth/session", { cache: "no-store", headers: nativeClient ? { "x-boss-native-app": "1" } : undefined, }).catch(() => null); if (response?.ok) { return true; } await new Promise((resolve) => window.setTimeout(resolve, 120)); } return false; } function resolvePostLoginPath() { return window.location.hostname === "admin.boss.hyzq.net" ? "/" : "/conversations"; } function navigateAfterLogin(router: ReturnType) { const targetPath = resolvePostLoginPath(); router.replace(targetPath, { scroll: false }); router.refresh(); window.setTimeout(() => { if (window.location.pathname !== targetPath) { window.location.replace(targetPath); } }, 180); } export function AppShell({ children, bottomNav = true, }: { children: React.ReactNode; bottomNav?: boolean; }) { return (
{children}
{bottomNav ? : null}
); } export function StatusBar() { return
; } export function PageNav({ title, backHref, rightLabel, rightHref, rightNode, }: { title: string; backHref?: string; rightLabel?: string; rightHref?: string; rightNode?: React.ReactNode; }) { const router = useRouter(); async function handleBack() { const currentPath = currentAppLocation(); const action = resolveAppBackAction(currentPath, backHref); if (action.mode === "history") { popAppHistoryEntry(currentPath); router.back(); return; } if (action.mode === "replace") { popAppHistoryEntry(currentPath); router.replace(action.target, { scroll: false }); } } return (
{backHref ? ( ) : ( )}

{title}

{rightNode ? ( rightNode ) : rightLabel ? ( {rightLabel} ) : ( )}
); } export function BottomNav() { const pathname = usePathname(); const items = [ { href: "/conversations", label: "会话", icon: "◌" }, { href: "/devices", label: "设备", icon: "◫" }, { href: "/me", label: "我的", icon: "◍" }, ]; return (
{items.map((item) => { const active = pathname.startsWith(item.href); return ( {item.icon} {item.label} ); })}
); } export function HeaderTitle({ title, extra, }: { title: string; extra?: React.ReactNode; }) { return (

{title}

{extra}
); } export function LogoutButton({ label = "退出登录", compact = false, }: { label?: string; compact?: boolean; }) { const router = useRouter(); const [loading, setLoading] = useState(false); const [message, setMessage] = useState(""); async function handleLogout() { setLoading(true); const response = await fetch("/api/auth/logout", { method: "POST", }); const result = (await response.json()) as { ok: boolean; message?: string }; setLoading(false); setMessage(result.message ?? (result.ok ? "已退出登录。" : "退出失败。")); if (result.ok) { await clearNativeSessionSnapshot(); router.push("/auth/login"); router.refresh(); } } if (compact) { return ( ); } return (
{label}
清除当前会话,回到登录页重新选择账号或验证码方式。
{message ? (
{message}
) : null}
); } export function AvatarStack({ primary, secondary, overflowCount, }: { primary: string; secondary?: string; overflowCount?: number; }) { if (!secondary) { return (
{primary}
); } return (
{primary}
{overflowCount ? `+${overflowCount}` : secondary}
); } export function ContextRing({ value, label, level, }: { value: number; label: string; level?: "safe" | "watch" | "urgent" | "critical"; }) { const color = level === "critical" ? "#FF4D4F" : level === "urgent" ? "#FF8A00" : level === "watch" ? "#1C7ED6" : "#07C160"; const background = `conic-gradient(${color} ${value * 3.6}deg, #D9D9D9 0deg)`; return (
{label}
); } function riskBadgeColor(level: ConversationItem["riskLevel"]) { switch (level) { case "high": return "bg-[#FFF1F0] text-[#CF1322]"; case "medium": return "bg-[#FFF7E6] text-[#D46B08]"; default: return "bg-[#F6FFED] text-[#389E0D]"; } } export function getConversationListItemPresentation(conversation: ConversationItem) { const isFolderArchive = conversation.conversationType === "folder_archive"; return { href: isFolderArchive && conversation.folderKey ? `/conversations/folders/${encodeURIComponent(conversation.folderKey)}` : `/conversations/${conversation.projectId}`, title: conversation.projectTitle, subtitle: isFolderArchive ? conversation.folderLabel : conversation.deviceNamesPreview.join(" / "), }; } export function getConversationActionAvailability(conversation: ConversationItem) { return { canTogglePin: false, togglePinLabel: conversation.topPinnedLabel || conversation.manualPinned ? "取消置顶" : "置顶", }; } export function getConversationPinnedBadgeLabel(conversation: ConversationItem) { void conversation; return ""; } export function getConversationActionsPath(projectId: string) { return `/api/v1/conversations/${encodeURIComponent(projectId)}/actions`; } function ConversationActionButtons({ conversation, }: { conversation: ConversationItem; }) { const router = useRouter(); const [loading, setLoading] = useState<"toggle_pin" | "mark_read" | null>(null); const actionAvailability = getConversationActionAvailability(conversation); async function runAction(action: "toggle_pin" | "mark_read") { setLoading(action); await fetch(getConversationActionsPath(conversation.projectId), { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ action }), }); setLoading(null); router.refresh(); } return (
{actionAvailability.canTogglePin ? ( ) : null} {conversation.unreadCount > 0 ? ( ) : null}
); } export function ConversationList({ conversations, }: { conversations: ConversationItem[]; }) { return (
{conversations.map((conversation) => { const presentation = getConversationListItemPresentation(conversation); const isFolderArchive = conversation.conversationType === "folder_archive"; return (
{presentation.title}
{isFolderArchive ? ( {conversation.threadCount ?? 0} 个线程 ) : ( {conversation.riskLevel === "high" ? "高风险" : conversation.riskLevel === "medium" ? "关注" : "稳定"} )} {conversation.unreadCount > 0 ? ( {conversation.unreadCount} ) : null}
{presentation.subtitle}
{conversation.preview}
{getConversationPinnedBadgeLabel(conversation)}
{conversation.latestReplyLabel}
{conversation.contextBudgetIndicator.visible && conversation.contextBudgetIndicator.percent !== undefined ? ( ) : (
{conversation.activeDeviceCount > 1 ? `${conversation.activeDeviceCount} 台协作` : ""}
)}
); })}
); } export function DeviceList({ devices }: { devices: Device[] }) { const router = useRouter(); return (
{devices.map((device) => ( ))}
); } export function DeviceEditorCard({ device, relatedThreads, activeEnrollment, workspace, }: { device: Device; relatedThreads: ThreadContextSnapshot[]; activeEnrollment?: DeviceEnrollment; workspace: DeviceWorkspaceView; }) { const router = useRouter(); const detailCards = buildDeviceWorkspaceDetailCards(workspace); const primaryPolicy = workspace.projectExecutionPolicies?.[0]; const [name, setName] = useState(device.name); const [avatar, setAvatar] = useState(device.avatar); const [account, setAccount] = useState(device.account); const [status, setStatus] = useState(device.status); const [preferredExecutionMode, setPreferredExecutionMode] = useState< Device["preferredExecutionMode"] >(device.preferredExecutionMode ?? "cli"); const [endpoint, setEndpoint] = useState(device.endpoint ?? ""); const [note, setNote] = useState(device.note ?? ""); const [projects, setProjects] = useState(device.projects.join(", ")); const [message, setMessage] = useState(""); async function save() { const response = await fetch(`/api/v1/devices/${device.id}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name, avatar, account, status, endpoint, note, preferredExecutionMode, projects: projects .split(",") .map((item) => item.trim()) .filter(Boolean), }), }); const result = (await response.json()) as { ok: boolean; message?: string }; setMessage(result.ok ? "设备信息已更新。" : result.message ?? "更新失败"); if (result.ok) router.refresh(); } async function saveConflictDecision(decision: "forbid" | "allow_once" | "allow_always") { if (!primaryPolicy?.projectId) { setMessage("当前没有可操作的异常项目 / 文件夹。"); return; } const response = await fetch(`/api/v1/devices/${device.id}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ projectId: primaryPolicy.projectId, folderKey: primaryPolicy.folderKey, conflictDecision: decision, }), }); const result = (await response.json()) as { ok: boolean; message?: string }; setMessage(result.ok ? "冲突策略已更新。" : result.message ?? "更新失败"); if (result.ok) router.refresh(); } return (
设备详情
ID: {device.id}
{detailCards.capabilities.title}
{detailCards.capabilities.items.gui}
{detailCards.capabilities.items.cli}
{detailCards.capabilities.items.browserAutomation}
{detailCards.capabilities.items.computerUse}
{detailCards.capabilities.items.codexAppServer}
{detailCards.capabilities.items.codexModels}
{detailCards.capabilities.items.codexExtensions}
{detailCards.capabilities.items.codexGovernance}
{detailCards.capabilities.items.codexAccount}
{detailCards.capabilities.items.codexConfig}
{detailCards.capabilities.items.codexSkillRoots}
{detailCards.capabilities.items.codexHooks}
{detailCards.capabilities.items.codexThreads}
{detailCards.capabilities.items.codexTurns}
{detailCards.capabilities.items.codexThreadActions}
{detailCards.capabilities.items.codexPluginGovernance}
{detailCards.capabilities.items.codexAccountGovernance}
{detailCards.capabilities.items.codexConfigGovernance}
{detailCards.capabilities.items.codexFileSystemGovernance}
{detailCards.capabilities.items.codexCommandSession}
{detailCards.capabilities.items.codexExternalAgentGovernance}
{detailCards.capabilities.items.codexMarketplaceGovernance}
{detailCards.capabilities.items.codexExperimentalFeatureGovernance}
{detailCards.capabilities.items.codexReviewGovernance}
{detailCards.capabilities.items.codexWindowsSandboxGovernance}
{detailCards.capabilities.items.codexFuzzyFileSearch}
{detailCards.capabilities.items.preferredExecutionMode}
切换默认执行模式
{(["gui", "cli"] as const).map((mode) => ( ))}
{detailCards.conflicts.title}
{detailCards.conflicts.headerHint}
{detailCards.conflicts.items.device}
{detailCards.conflicts.items.folderKey}
{detailCards.conflicts.items.projectId}
{detailCards.conflicts.items.allowPolicy}
{detailCards.conflicts.items.conflictState}
{detailCards.conflicts.scopeLabel}
{primaryPolicy ? (
) : null}
当前状态
{(["online", "abnormal", "offline"] as const).map((item) => ( ))}
{message ? (
{message}
) : null} {activeEnrollment ? (
Pairing code: {activeEnrollment.pairingCode}
Token: {activeEnrollment.token}
过期时间:{formatClock(activeEnrollment.expiresAt)}
) : null}
活跃线程:{relatedThreads.length}
{relatedThreads.map((thread) => `${thread.title} (${thread.contextBudgetRemainingPct}%)`).join(";") || "当前无活跃线程。"}
); } export function ProfileHero({ user }: { user: UserProfile }) { return (
{user.avatar}
{user.name}
{user.accountType}
账号:{user.account}
绑定节点:{user.boundCodexNodeLabel ?? "未绑定"}
二维码:{user.qrCodeValue}
{user.roleLabel}
); } export function MenuRow({ href, title, description, badge, }: { href: string; title: string; description: string; badge?: React.ReactNode; }) { return (
{title}
{description}
{badge} >
); } function kindLabel(kind?: Message["kind"]) { switch (kind) { case "voice_intent": return "语音"; case "image_intent": return "图片"; case "video_intent": return "视频"; case "forward_notice": return "转发"; default: return null; } } export function ChatBubble({ message }: { message: Message }) { const mine = message.sender === "user"; const green = message.sender === "master"; const tag = kindLabel(message.kind); return (
{message.senderLabel} · {formatClock(message.sentAt)}
{tag ? (
{tag}
) : null}
); } function ChatBubbleMarkdown({ body, mine, green, }: { body: string; mine: boolean; green: boolean; }) { const blocks = parseChatMarkdown(body); return (
{blocks.map((block, index) => ( ))}
); } function ChatMarkdownBlockView({ block, mine, green, }: { block: ChatMarkdownBlock; mine: boolean; green: boolean; }) { const mutedClass = mine ? "text-white/82" : green ? "text-[#4E7A60]" : "text-[#57606A]"; const markerClass = mine ? "text-white/72" : green ? "text-[#44A064]" : "text-[#8C8C8C]"; switch (block.kind) { case "heading": return (
{block.text}
); case "label": return (
{block.label}
{block.text}
); case "bullet": return (
{block.text}
); case "ordered": return (
{block.order} {block.text}
); case "quote": return (
{block.text}
); case "code": return (
          {block.text}
        
); case "paragraph": default: return
{block.text}
; } } export function ProjectHeaderActions({ projectId }: { projectId: string }) { return (
项目目标 版本记录 转发 线程状态
); } function orchestrationBackendChoiceLabel(choice: ProjectOrchestrationBackendState["availableChoices"][number]) { return choice.backendId === "boss-native-orchestrator" ? "Boss Native Orchestrator" : "OMX Team Runtime"; } function normalizeOrchestrationReasonLabel(value: string) { const trimmed = value.trim(); if (trimmed.endsWith("。") || trimmed.endsWith(".")) { return trimmed.slice(0, -1); } return trimmed; } function orchestrationBackendAvailabilityCopy( state: ProjectOrchestrationBackendState, fallbackActive: boolean, ) { if (state.omxAvailability.selectable) { return { badge: "正常", summary: "OMX Team Runtime 当前可用,当前可切换到该后端。", }; } return { badge: fallbackActive ? "已回退" : "OMX 受限", summary: fallbackActive ? `${normalizeOrchestrationReasonLabel(state.omxAvailability.reasonLabel)},当前已自动回退到 Boss Native Orchestrator。` : `${normalizeOrchestrationReasonLabel(state.omxAvailability.reasonLabel)},切换后会自动回退到 Boss Native Orchestrator。`, }; } export function ProjectOrchestrationBackendCard({ projectId, initialState, }: { projectId: string; initialState: ProjectOrchestrationBackendState; }) { const router = useRouter(); const [state, setState] = useState(initialState); const [savingBackendId, setSavingBackendId] = useState(null); const [message, setMessage] = useState(""); const fallbackActive = state.requestedBackendId !== state.currentBackendId; const availabilityCopy = orchestrationBackendAvailabilityCopy(state, fallbackActive); async function saveBackend(requestedBackendId: OrchestrationBackendId) { setSavingBackendId(requestedBackendId); const response = await fetch(`/api/v1/projects/${projectId}/orchestration-backend`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ requestedBackendId }), }); const result = (await response.json()) as { ok: boolean; message?: string; currentBackendId?: OrchestrationBackendId; currentBackendLabel?: string; requestedBackendId?: OrchestrationBackendId; requestedBackendLabel?: string; availableChoices?: ProjectOrchestrationBackendState["availableChoices"]; omxAvailability?: ProjectOrchestrationBackendState["omxAvailability"]; }; setSavingBackendId(null); if ( !result.ok || !result.currentBackendId || !result.currentBackendLabel || !result.requestedBackendId || !result.requestedBackendLabel || !result.availableChoices || !result.omxAvailability ) { setMessage(result.message ?? "保存失败"); return; } setState({ projectId, currentBackendId: result.currentBackendId, currentBackendLabel: result.currentBackendLabel, requestedBackendId: result.requestedBackendId, requestedBackendLabel: result.requestedBackendLabel, availableChoices: result.availableChoices, omxAvailability: result.omxAvailability, }); setMessage( requestedBackendId === "omx-team" ? "已切换到 OMX Team Runtime。" : "已切换回 Boss Native Orchestrator。", ); router.refresh(); } return (
编排后端
当前生效:{state.currentBackendLabel}
当前请求:{state.requestedBackendLabel}
{availabilityCopy.badge}
{state.availableChoices.map((choice) => { const active = choice.current; const selectable = choice.selectable && savingBackendId !== choice.backendId; return ( ); })}
{availabilityCopy.summary}
{message ? (
{message}
) : null}
); } function masterIdentityPillClasses(role: MasterIdentitySummary["role"]) { switch (role) { case "primary": return "border-[#BBD6FF] bg-[#EEF5FF] text-[#2457C5]"; case "backup": return "border-[#FFD9B8] bg-[#FFF5E8] text-[#B54708]"; case "api_fallback": return "border-[#D8DDE6] bg-[#F3F4F6] text-[#4B5563]"; default: return "border-[#D8DDE6] bg-[#F3F4F6] text-[#4B5563]"; } } export function MasterIdentityPill({ identity }: { identity: MasterIdentitySummary }) { return (
{identity.roleLabel}
{(identity.nodeLabel || identity.displayName).slice(0, 18)}
); } export function MasterAgentChatMenu({ projectId }: { projectId: string }) { const router = useRouter(); const items = getMasterAgentChatMenuItems(projectId); if (items.length === 0) { return null; } return (
{items.map((item) => item.href ? ( {item.label} ) : ( ), )}
); } type PendingDispatchPlanState = { planId: string; summary?: string; targets: Array<{ projectId: string; threadDisplayName: string }>; }; export function ChatComposer({ projectId, initialPendingDispatchPlan, initialRejectedDispatchPlan, dispatchPlanRecoveryHint, initialLightDispatchReminderEnabled = false, }: { projectId: string; initialPendingDispatchPlan?: PendingDispatchPlanState | null; initialRejectedDispatchPlan?: PendingDispatchPlanState | null; dispatchPlanRecoveryHint?: string | null; initialLightDispatchReminderEnabled?: boolean; }) { const router = useRouter(); type ComposerMessageKind = "text" | "voice_intent" | "image_intent" | "video_intent"; const [value, setValue] = useState(""); const [message, setMessage] = useState(""); const [messageTone, setMessageTone] = useState<"success" | "error">("success"); const [loading, setLoading] = useState(false); const [localPendingDispatchPlan, setLocalPendingDispatchPlan] = useState(null); const [localRejectedDispatchPlan, setLocalRejectedDispatchPlan] = useState(null); const [dismissedPendingPlanId, setDismissedPendingPlanId] = useState(null); const [lightDispatchReminderEnabled, setLightDispatchReminderEnabled] = useState( initialLightDispatchReminderEnabled, ); const [threadExecutionConflict, setThreadExecutionConflict] = useState<{ conflict: ThreadConversationExecutionConflict; draftBody: string; kind: ComposerMessageKind; } | null>(null); const pendingDispatchPlan = localPendingDispatchPlan ?? (initialPendingDispatchPlan && initialPendingDispatchPlan.planId !== dismissedPendingPlanId ? initialPendingDispatchPlan : null); const rejectedDispatchPlan = pendingDispatchPlan ? null : localRejectedDispatchPlan ?? initialRejectedDispatchPlan ?? null; const threadExecutionConflictDescription = threadExecutionConflict ? describeThreadConversationExecutionConflict(threadExecutionConflict.conflict) : null; async function confirmDispatchPlan(rememberLightReminder = false) { if (!pendingDispatchPlan) return; setLoading(true); const response = await fetch( `/api/v1/projects/${projectId}/dispatch-plans/${pendingDispatchPlan.planId}/confirm`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ approvedTargetProjectIds: extractApprovedTargetProjectIds(pendingDispatchPlan), rememberLightReminder, }), }, ); const result = (await response.json()) as { ok: boolean; executions?: Array; collaborationGate?: { lightDispatchReminderEnabled?: boolean; }; message?: string; }; setLoading(false); if (!result.ok) { setMessageTone("error"); setMessage(result.message ?? "确认下发失败,请重试。"); return; } const executionCount = result.executions?.length ?? extractApprovedTargetProjectIds(pendingDispatchPlan).length; setLightDispatchReminderEnabled( result.collaborationGate?.lightDispatchReminderEnabled ?? lightDispatchReminderEnabled, ); setLocalPendingDispatchPlan(null); setLocalRejectedDispatchPlan(null); setDismissedPendingPlanId(pendingDispatchPlan.planId); setMessageTone("success"); setMessage( rememberLightReminder ? `已确认下发到 ${executionCount} 个线程,并记住这个群使用轻提醒。` : `已确认下发到 ${executionCount} 个线程。`, ); router.refresh(); } async function retryDispatchPlan() { if (!rejectedDispatchPlan) return; setLoading(true); const response = await fetch( `/api/v1/projects/${projectId}/dispatch-plans/${rejectedDispatchPlan.planId}/retry`, { method: "POST", headers: { "Content-Type": "application/json" }, }, ); const result = (await response.json()) as { ok: boolean; dispatchPlan?: { planId: string; summary?: string; targets?: Array<{ projectId: string; threadDisplayName: string }>; } | null; collaborationGate?: { requiresMasterAgentApproval?: boolean; lightDispatchReminderEnabled?: boolean; }; message?: string; }; setLoading(false); if (!result.ok) { setMessageTone("error"); setMessage(result.message ?? "重新生成推荐失败,请稍后重试。"); return; } setLocalRejectedDispatchPlan(null); setLocalPendingDispatchPlan( result.dispatchPlan ? { planId: result.dispatchPlan.planId, summary: result.dispatchPlan.summary, targets: result.dispatchPlan.targets ?? [], } : null, ); setDismissedPendingPlanId(null); setLightDispatchReminderEnabled( result.collaborationGate?.lightDispatchReminderEnabled ?? lightDispatchReminderEnabled, ); setMessageTone("success"); setMessage( result.collaborationGate?.requiresMasterAgentApproval ? "主 Agent 已重新生成推荐,等待你确认下发。" : "主 Agent 已重新生成推荐。", ); router.refresh(); } async function rejectDispatchPlan() { if (!pendingDispatchPlan) return; setLoading(true); const response = await fetch( `/api/v1/projects/${projectId}/dispatch-plans/${pendingDispatchPlan.planId}/reject`, { method: "POST", headers: { "Content-Type": "application/json" }, }, ); const result = (await response.json()) as { ok: boolean; plan?: { planId: string; summary?: string; targets?: Array<{ projectId: string; threadDisplayName: string }>; } | null; message?: string; }; setLoading(false); if (!result.ok) { setMessageTone("error"); setMessage(result.message ?? "拒绝失败,请稍后重试。"); return; } setLocalPendingDispatchPlan(null); setLocalRejectedDispatchPlan( result.plan ? { planId: result.plan.planId, summary: result.plan.summary, targets: result.plan.targets ?? pendingDispatchPlan.targets, } : pendingDispatchPlan, ); setDismissedPendingPlanId(pendingDispatchPlan.planId); setMessageTone("success"); setMessage("已拒绝主 Agent 推荐。"); router.refresh(); } async function send( kind: ComposerMessageKind, options?: { draftBody?: string; }, ) { const draftBody = kind === "text" ? (options?.draftBody ?? value).trim() : ""; if (kind === "text" && !draftBody) { return; } setLoading(true); const response = await fetch(`/api/v1/projects/${projectId}/messages`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ body: kind === "text" ? draftBody : undefined, kind }), }); const result = (await response.json()) as { ok: boolean; message?: { body: string } | string; dispatchPlan?: { planId: string; summary?: string; targets?: Array<{ projectId: string; threadDisplayName: string }>; } | null; collaborationGate?: { requiresMasterAgentApproval?: boolean; lightDispatchReminderEnabled?: boolean; }; code?: string; executionConflict?: ThreadConversationExecutionConflict; messageText?: string; }; setLoading(false); if (!result.ok && response.status === 409 && result.code === "THREAD_EXECUTION_CONFLICT" && result.executionConflict) { setThreadExecutionConflict({ conflict: result.executionConflict, draftBody, kind, }); setMessageTone("error"); setMessage(typeof result.message === "string" ? result.message : "当前线程命中了 GUI / CLI 冲突保护。"); return; } if (result.ok) { setThreadExecutionConflict(null); void sendAppLog({ deviceId: boundDeviceIdFromDom(), projectId, level: "info", category: "chat.message_sent", message: kind === "text" ? `已发送文本消息:${draftBody || "空文本"}` : `已发送 ${kind} 意图消息。`, mirrorToMaster: false, }); setValue(""); if (result.dispatchPlan) { setLocalPendingDispatchPlan({ planId: result.dispatchPlan.planId, summary: result.dispatchPlan.summary, targets: result.dispatchPlan.targets ?? [], }); setLocalRejectedDispatchPlan(null); setDismissedPendingPlanId(null); setLightDispatchReminderEnabled( result.collaborationGate?.lightDispatchReminderEnabled ?? lightDispatchReminderEnabled, ); setMessage( result.collaborationGate?.requiresMasterAgentApproval ? "消息已发送,等待你批准主 Agent 下发。" : "消息已发送,主 Agent 已给出推荐线程。", ); setMessageTone("success"); } else { setLocalPendingDispatchPlan(null); setMessage(""); } router.refresh(); return; } void sendAppLog({ deviceId: boundDeviceIdFromDom(), projectId, level: "error", category: "chat.message_failed", message: `发送 ${kind} 消息失败。`, detail: "Boss 会话消息接口返回失败。", mirrorToMaster: true, }); setMessageTone("error"); setMessage(typeof result.message === "string" ? result.message : "消息发送失败,请重试。"); } async function handleThreadExecutionConflictDecision( decision: ThreadConversationExecutionConflictAction, ) { if (!threadExecutionConflict) { return; } setLoading(true); const response = await fetch(`/api/v1/devices/${threadExecutionConflict.conflict.deviceId}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ projectId: threadExecutionConflict.conflict.projectId, folderKey: threadExecutionConflict.conflict.folderKey ?? null, conflictDecision: decision, }), }); const result = (await response.json()) as { ok: boolean; message?: string; }; setLoading(false); if (!result.ok) { setMessageTone("error"); setMessage(result.message ?? "冲突放行设置失败,请重试。"); return; } const pendingDraft = threadExecutionConflict; setThreadExecutionConflict(null); setMessageTone(decision === "forbid" ? "error" : "success"); setMessage(summarizeThreadConversationExecutionDecisionResult(decision)); if (decision === "forbid") { return; } await send(pendingDraft.kind, { draftBody: pendingDraft.draftBody }); } return (
setValue(event.target.value)} placeholder="向主 Agent 或项目发送消息" className="flex-1 rounded-full bg-[#F5F5F7] px-4 py-3 text-[14px] text-[#111111] outline-none" />
转发
{message ? (
{message}
) : null} {dispatchPlanRecoveryHint ? (
{dispatchPlanRecoveryHint}
) : null} {threadExecutionConflict && threadExecutionConflictDescription ? (
{threadExecutionConflictDescription.title}
{threadExecutionConflictDescription.summary}
设备:{threadExecutionConflict.conflict.deviceName} {" · "} 默认模式:{threadExecutionConflict.conflict.preferredExecutionMode === "gui" ? "GUI" : "CLI"} {threadExecutionConflict.conflict.folderKey ? ` · ${threadExecutionConflict.conflict.folderKey}` : ""}
{threadExecutionConflict.conflict.actions.map((action) => ( ))}
) : null} {pendingDispatchPlan ? (
{lightDispatchReminderEnabled ? summarizeDispatchPlanLightTitle(pendingDispatchPlan) : "主 Agent 推荐下发"}
{summarizeDispatchPlanCompact(pendingDispatchPlan)}
{lightDispatchReminderEnabled ? "轻提醒已开启" : "当前仍会显式提醒你确认"}
{!lightDispatchReminderEnabled ? ( ) : null}
) : null} {rejectedDispatchPlan ? (
上次推荐已拒绝
{summarizeDispatchPlan(rejectedDispatchPlan)}
{dispatchPlanRecoveryHint ?? "如果还想继续当前协作,可以直接重新生成新的推荐,不用把整条需求重新打一遍。"}
) : null}
); } export function GoalChecklist({ projectId, goals, }: { projectId: string; goals: GoalItem[]; }) { const router = useRouter(); const [workingId, setWorkingId] = useState(null); const [editingId, setEditingId] = useState(null); const [draft, setDraft] = useState(""); const [newGoal, setNewGoal] = useState(""); async function handleToggle(goalId: string) { setWorkingId(goalId); await fetch(`/api/projects/${projectId}/goals/${goalId}/toggle`, { method: "POST" }); setWorkingId(null); router.refresh(); } async function handleSave(goalId: string) { await fetch(`/api/projects/${projectId}/goals/update`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ goalId, text: draft, action: "update" }), }); setEditingId(null); setDraft(""); router.refresh(); } async function handleCreate() { if (!newGoal.trim()) return; await fetch(`/api/projects/${projectId}/goals/update`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ text: newGoal, action: "create" }), }); setNewGoal(""); router.refresh(); } return (
新增项目目标