import { randomBytes } from "node:crypto"; import { AUTH_SESSION_TTL_MS, aiRoleLabel, aiProviderLabel, appendProjectMessage, completeMasterAgentTask, getProjectAttachment, getAttachmentStorageConfig, getProjectAgentControls, getLatestDeviceImportDraft, getRuntimeAiAccountById, getMasterAgentRuntimeAccount, getMasterAgentTask, previewDeviceImportResolution, queueMasterAgentTask, readState, isDispatchableThreadProject, updateAttachmentAnalysisResult, updateAiAccountHealth, } from "@/lib/boss-data"; import type { DispatchPlanTarget, Project, ProjectAgentControls, ReasoningEffort } from "@/lib/boss-data"; import { canInlineAttachmentText, extractAttachmentTextExcerpt } from "@/lib/boss-attachments"; import { readAliyunOssObjectBuffer } from "@/lib/boss-storage-aliyun-oss"; import { readServerFileAttachmentBuffer } from "@/lib/boss-storage-server-file"; type MasterAgentReplyState = "queued" | "running" | "completed"; const OPENAI_MASTER_AGENT_DEVICE_ID = "master-agent-openai"; type QueuedMasterAgentReplyEnvelope = { ok: true; accountId: string; taskId: string; masterReplyState: MasterAgentReplyState; task: { taskId: string; taskType: "conversation_reply"; status: MasterAgentReplyState; }; }; function buildAgentControlsDigest(agentControls?: ProjectAgentControls | null) { if (!agentControls) { return "当前对话覆盖:无"; } return [ "当前对话覆盖:", `model=${agentControls.modelOverride ?? "默认"}`, `reasoning=${agentControls.reasoningEffortOverride ?? "默认"}`, ].join(" "); } function buildMasterAgentInstructions() { return [ "你是 Boss 控制台的主 Agent。", "你要基于当前运行时状态给出中文回复,要求直接、可执行、便于继续联调。", "优先关注线程上下文预算、must_finish_before_compaction、最新 APP 日志、设备在线状态和 OTA 状态。", "如果信息不足,就明确说缺什么;不要编造设备状态或执行结果。", "如果用户要继续开发,默认给出下一步实现/验证动作,而不是泛泛安慰。", "保持回答简洁,通常 3-6 句即可。", ].join("\n"); } function buildThreadConversationReplyPrompt(project: Project, requestText: string) { return [ "你正在代表某个 Codex 线程回复 Boss 控制台里的单线程会话。", "你不是主 Agent,不要使用“主 Agent”口吻,不要写总结,不要解释调度过程。", "请直接像该线程本人一样,用中文回复用户当前这条消息。", "如果信息不足,要明确说缺什么;不要假装已经执行过设备操作。", "输出要求:只输出线程要回给用户的正文,不要输出 JSON、代码块或额外前缀。", `threadProjectId: ${project.id}`, `threadTitle: ${project.threadMeta.threadDisplayName}`, `folderName: ${project.threadMeta.folderName}`, `deviceIds: ${project.deviceIds.join(",")}`, `requestText: ${requestText}`, ].join("\n"); } function buildRuntimeDigest( state: Awaited>, requestText: string, currentSessionExpiresAt?: string, agentControls?: ProjectAgentControls | null, ) { const recentMessages = state.projects .find((project) => project.id === "master-agent") ?.messages.slice(-6) .map((message) => `${message.senderLabel}:${message.body}`) .join("\n"); const recentLogs = state.appLogs .slice(0, 5) .map((log) => `${log.createdAt} ${log.deviceId} ${log.category} ${log.message}`) .join("\n"); const riskyThreads = state.threadContextSnapshots .slice() .sort((a, b) => a.contextBudgetRemainingPct - b.contextBudgetRemainingPct) .slice(0, 4) .map( (snapshot) => `${snapshot.projectId} / ${snapshot.title} / ${snapshot.contextBudgetLevel} / ${snapshot.contextBudgetRemainingPct}% / must_finish=${snapshot.mustFinishBeforeCompaction ? "yes" : "no"} / ${snapshot.summary}`, ) .join("\n"); const devices = state.devices .map( (device) => `${device.name}(${device.id}) 状态=${device.status} 账号=${device.account} 5h=${device.quota5h} 7d=${device.quota7d}`, ) .join("\n"); const ota = state.otaUpdates .filter((update) => update.status === "available") .map((update) => `${update.version} -> ${update.targetScope}`) .join("\n"); const authSummary = [ `登录会话策略:成功登录后默认保持 ${Math.round(AUTH_SESSION_TTL_MS / 24 / 60 / 60_000)} 天。`, "Cookie Max-Age:2592000 秒。", currentSessionExpiresAt ? `当前请求会话到期时间:${currentSessionExpiresAt}` : undefined, buildAgentControlsDigest(agentControls), ] .filter(Boolean) .join("\n"); return [ `当前时间:${new Date().toISOString()}`, `用户消息:${requestText}`, "", "最近主 Agent 对话:", recentMessages || "无", "", "最新 APP 日志:", recentLogs || "无", "", "高风险线程:", riskyThreads || "无", "", "在线设备:", devices || "无", "", "认证状态:", authSummary, "", "可用 OTA:", ota || "无", ].join("\n"); } function extractResponseText(payload: unknown): string { if (!payload || typeof payload !== "object") { return ""; } const response = payload as { output_text?: string; output?: Array<{ content?: Array<{ type?: string; text?: string; content?: string }>; }>; }; if (typeof response.output_text === "string" && response.output_text.trim()) { return response.output_text.trim(); } const chunks = response.output ?.flatMap((item) => item.content ?? []) .map((item) => { if (typeof item.text === "string") return item.text; if (typeof item.content === "string") return item.content; return ""; }) .filter(Boolean) ?? []; return chunks.join("\n").trim(); } function normalizeOpenAiError(message: string) { const trimmed = message.trim(); const lowered = trimmed.toLowerCase(); if (lowered.includes("network is unreachable") || lowered.includes("enetunreach")) { return "服务器当前无法访问 api.openai.com,请先恢复服务器出网,或先切回 Master Codex Node。"; } if (lowered.includes("fetch failed") || lowered.includes("connect timeout") || lowered.includes("timed out")) { return "服务器当前无法连接 OpenAI API,请检查出网、代理或防火墙配置。"; } if (!trimmed) return "主 Agent 当前调用模型失败。"; if (trimmed.length <= 240) return trimmed; return `${trimmed.slice(0, 237)}...`; } function normalizeOpenAiFetchFailure(error: unknown) { if (error instanceof Error) { const causeCode = typeof (error as Error & { cause?: { code?: string } }).cause?.code === "string" ? (error as Error & { cause?: { code?: string } }).cause?.code : ""; const causeMessage = (error as Error & { cause?: { message?: string } }).cause?.message?.trim() || ""; return normalizeOpenAiError([error.message, causeCode, causeMessage].filter(Boolean).join(" ")); } return normalizeOpenAiError(String(error)); } function fallbackAiRolePriority(role: "primary" | "backup" | "api_fallback") { switch (role) { case "primary": return 0; case "backup": return 1; case "api_fallback": return 2; default: return 9; } } async function findFallbackOpenAiAccount(excludedAccountId?: string) { const state = await readState(); return [...state.aiAccounts] .filter( (account) => account.accountId !== excludedAccountId && account.enabled && account.provider === "openai_api" && Boolean(account.apiKey?.trim()), ) .sort((left, right) => { const roleDelta = fallbackAiRolePriority(left.role) - fallbackAiRolePriority(right.role); if (roleDelta !== 0) return roleDelta; return (right.updatedAt ?? "").localeCompare(left.updatedAt ?? ""); })[0]; } async function replyViaOpenAiAccount(params: { account: Awaited>; requestText: string; currentSessionExpiresAt?: string; senderLabel: string; agentControls?: ProjectAgentControls | null; }) { if (!params.account?.apiKey?.trim()) { throw new Error("OPENAI_ACCOUNT_NOT_CONFIGURED"); } const generated = await generateOpenAiReply({ apiKey: params.account.apiKey, model: params.agentControls?.modelOverride || params.account.model || "gpt-5.4", reasoningEffort: params.agentControls?.reasoningEffortOverride || "medium", requestText: params.requestText, currentSessionExpiresAt: params.currentSessionExpiresAt, agentControls: params.agentControls, }); await appendMasterAgentSystemReply(generated.content, params.senderLabel); await updateAiAccountHealth({ accountId: params.account.accountId, status: "ready", lastValidatedAt: new Date().toISOString(), lastUsedAt: new Date().toISOString(), }); return { ok: true as const, accountId: params.account.accountId, requestId: generated.requestId, }; } async function generateOpenAiReply(params: { apiKey: string; model: string; reasoningEffort: ReasoningEffort; requestText: string; currentSessionExpiresAt?: string; agentControls?: ProjectAgentControls | null; }) { const state = await readState(); let response: Response; try { response = await fetch("https://api.openai.com/v1/responses", { method: "POST", headers: { Authorization: `Bearer ${params.apiKey}`, "Content-Type": "application/json", }, body: JSON.stringify({ model: params.model, reasoning: { effort: params.reasoningEffort }, instructions: buildMasterAgentInstructions(), input: buildRuntimeDigest( state, params.requestText, params.currentSessionExpiresAt, params.agentControls, ), }), signal: AbortSignal.timeout(45_000), }); } catch (error) { throw new Error(normalizeOpenAiFetchFailure(error)); } const requestId = response.headers.get("x-request-id") ?? undefined; const payload = (await response.json().catch(() => null)) as | { error?: { message?: string } } | null; if (!response.ok) { const apiError = payload && typeof payload === "object" && "error" in payload ? payload.error?.message : undefined; throw new Error( normalizeOpenAiError( `${apiError ?? `OpenAI API ${response.status}`}${requestId ? ` (request_id=${requestId})` : ""}`, ), ); } const content = extractResponseText(payload); if (!content) { throw new Error( normalizeOpenAiError( `模型已返回成功状态,但没有可用文本输出${requestId ? ` (request_id=${requestId})` : ""}`, ), ); } return { content, requestId, }; } function buildMasterOpenAiReplyPrompt( state: Awaited>, requestText: string, currentSessionExpiresAt?: string, agentControls?: ProjectAgentControls | null, ) { return [ buildMasterAgentInstructions(), "", buildRuntimeDigest(state, requestText, currentSessionExpiresAt, agentControls), ].join("\n"); } async function queueAndStartOpenAiMasterAgentReply(params: { taskId: string; deviceId: string; requestText: string; currentSessionExpiresAt?: string; apiKey: string; model: string; reasoningEffort: ReasoningEffort; agentControls?: ProjectAgentControls | null; }) { const timer = setTimeout(() => { void (async () => { const task = await getMasterAgentTask(params.taskId); if (!task || task.status !== "queued") { return; } try { const generated = await generateOpenAiReply({ apiKey: params.apiKey, model: params.model, reasoningEffort: params.reasoningEffort, requestText: params.requestText, currentSessionExpiresAt: params.currentSessionExpiresAt, agentControls: params.agentControls, }); await completeMasterAgentTask({ taskId: params.taskId, deviceId: params.deviceId, status: "completed", replyBody: generated.content, requestId: generated.requestId, }); } catch (error) { await completeMasterAgentTask({ taskId: params.taskId, deviceId: params.deviceId, status: "failed", errorMessage: error instanceof Error ? error.message : "主 Agent 当前调用模型失败。", }); } })(); }, 0); timer.unref?.(); } async function enqueueOpenAiMasterAgentReply(params: { accountId: string; accountLabel: string; requestMessageId?: string; requestText: string; requestedBy: string; requestedByAccount: string; currentSessionExpiresAt?: string; apiKey: string; model: string; reasoningEffort: ReasoningEffort; agentControls?: ProjectAgentControls | null; }) { const state = await readState(); const task = await queueMasterAgentTask({ requestMessageId: params.requestMessageId ?? "master-agent-manual", requestText: params.requestText, executionPrompt: buildMasterOpenAiReplyPrompt( state, params.requestText, params.currentSessionExpiresAt, params.agentControls, ), requestedBy: params.requestedBy, requestedByAccount: params.requestedByAccount, deviceId: OPENAI_MASTER_AGENT_DEVICE_ID, accountId: params.accountId, accountLabel: params.accountLabel, }); void queueAndStartOpenAiMasterAgentReply({ taskId: task.taskId, deviceId: OPENAI_MASTER_AGENT_DEVICE_ID, requestText: params.requestText, currentSessionExpiresAt: params.currentSessionExpiresAt, apiKey: params.apiKey, model: params.model, reasoningEffort: params.reasoningEffort, agentControls: params.agentControls, }); const queuedReply: QueuedMasterAgentReplyEnvelope = { ok: true as const, accountId: params.accountId, taskId: task.taskId, masterReplyState: "queued" as const, task: { taskId: task.taskId, taskType: "conversation_reply" as const, status: "queued" as const, }, }; return queuedReply; } export async function probeOpenAiApiAccount(params: { apiKey: string; model?: string; }) { const apiKey = params.apiKey.trim(); if (!apiKey) { throw new Error("当前账号还没有可用的 OpenAI API Key。"); } const model = params.model?.trim() || "gpt-5.4"; let response: Response; try { response = await fetch("https://api.openai.com/v1/responses", { method: "POST", headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json", }, body: JSON.stringify({ model, reasoning: { effort: "low" }, instructions: "你正在执行 OpenAI API 连接自检。请只回复“连接正常”。", input: "请只回复“连接正常”。", }), signal: AbortSignal.timeout(15_000), }); } catch (error) { throw new Error(normalizeOpenAiFetchFailure(error)); } const requestId = response.headers.get("x-request-id") ?? undefined; const payload = (await response.json().catch(() => null)) as | { error?: { message?: string } } | null; if (!response.ok) { const apiError = payload && typeof payload === "object" && "error" in payload ? payload.error?.message : undefined; throw new Error( normalizeOpenAiError( `${apiError ?? `OpenAI API ${response.status}`}${requestId ? ` (request_id=${requestId})` : ""}`, ), ); } const content = extractResponseText(payload) || "连接正常。"; return { ok: true as const, message: content, requestId, model, }; } async function appendMasterAgentSystemReply(body: string, senderLabel = "主 Agent") { return appendProjectMessage({ projectId: "master-agent", sender: "master", senderLabel, body, kind: "text", }); } function buildMasterCodexNodePrompt( state: Awaited>, requestText: string, currentSessionExpiresAt?: string, agentControls?: ProjectAgentControls | null, ) { return [ "你是 Boss 控制台的主 Agent,运行在用户自己的 Master Codex Node 上。", "请结合下面的运行时状态和用户消息,直接给出中文回复。", "如果你认为需要继续在当前仓库里推进实现、排障或验证,可以直接说明你下一步会做什么;如果必须先做交接或收尾,也要明确说出原因。", "保持简洁,优先给出结论、动作、验证点。", buildAgentControlsDigest(agentControls), "", buildRuntimeDigest(state, requestText, currentSessionExpiresAt, agentControls), ].join("\n"); } function summarizeDispatchRequest(requestText: string) { const compact = requestText.trim().replace(/\s+/g, " "); if (!compact) { return "用户发来新的群聊协作请求"; } if (compact.length <= 36) { return compact; } return `${compact.slice(0, 33)}...`; } function collectGroupDispatchTargets( state: Awaited>, project: Project, requestText: string, ): DispatchPlanTarget[] { const members = project.groupMembers.length > 0 ? project.groupMembers : project.deviceIds.map((deviceId) => ({ projectId: project.id, deviceId, threadId: project.threadMeta.threadId, threadDisplayName: project.threadMeta.threadDisplayName, folderName: project.threadMeta.folderName, })); return members .map((member) => { const candidate = state.projects.find((projectCandidate) => projectCandidate.id === member.projectId); if (!candidate) { throw new Error("DISPATCH_TARGET_PROJECT_NOT_FOUND"); } return candidate; }) .filter((candidate) => isDispatchableThreadProject(candidate)) .map((candidate) => ({ deviceId: candidate.deviceIds[0] ?? candidate.id, projectId: candidate.id, threadId: candidate.threadMeta.threadId, threadDisplayName: candidate.threadMeta.threadDisplayName, folderName: candidate.threadMeta.folderName, codexFolderRef: candidate.threadMeta.codexFolderRef, codexThreadRef: candidate.threadMeta.codexThreadRef, reason: `群聊消息“${summarizeDispatchRequest(requestText)}”需要该线程补充状态或执行建议。`, })) .filter((target, index, array) => { const signature = `${target.projectId}::${target.deviceId}::${target.threadId}`; return array.findIndex((item) => `${item.projectId}::${item.deviceId}::${item.threadId}` === signature) === index; }); } function summarizeGroupDispatchPlan(requestText: string, targets: DispatchPlanTarget[]) { const targetLabels = targets.map((target) => target.threadDisplayName).filter(Boolean); return `主 Agent 建议先按线程分发这条群聊消息:${summarizeDispatchRequest(requestText)}${targetLabels.length > 0 ? `。建议目标:${targetLabels.join("、")}` : ""}`; } function buildGroupDispatchPlanPrompt(project: Project, requestText: string) { const memberDigest = (project.groupMembers.length > 0 ? project.groupMembers : project.deviceIds.map((deviceId) => ({ projectId: project.id, deviceId, threadId: project.threadMeta.threadId, threadDisplayName: project.threadMeta.threadDisplayName, folderName: project.threadMeta.folderName, })) ) .map( (member) => `${member.projectId} / ${member.threadDisplayName} / ${member.folderName} / device=${member.deviceId}`, ) .join("\n"); return [ "你正在处理 Boss 控制台的群聊分发建议任务。", "目标不是直接回复用户,而是为这条群聊消息推荐后续需要分发到哪些线程。", "当前服务端会优先使用已有群成员和线程映射做 recommendation workflow。", `groupProjectId: ${project.id}`, `groupProjectName: ${project.name}`, `requestText: ${requestText}`, "groupMembers:", memberDigest || "无", ].join("\n"); } type GroupDispatchRecommendationResult = | { ok: true; taskId: string; status: "completed"; dispatchPlan: NonNullable< Awaited> >["dispatchPlan"] | null; } | { ok: false; taskId: string; status: "failed"; dispatchPlan: null; error: string; }; async function resolveGroupDispatchPlanTask(taskId: string): Promise { const task = await getMasterAgentTask(taskId); if (!task) { throw new Error("MASTER_AGENT_TASK_NOT_FOUND"); } if (task.taskType !== "group_dispatch_plan") { throw new Error("MASTER_AGENT_TASK_TYPE_INVALID"); } try { const state = await readState(); const project = state.projects.find((item) => item.id === task.projectId); if (!project) { throw new Error("PROJECT_NOT_FOUND"); } if (!project.isGroup) { throw new Error("PROJECT_NOT_GROUP_CHAT"); } const targets = collectGroupDispatchTargets(state, project, task.requestText); if (targets.length === 0) { throw new Error("GROUP_DISPATCH_TARGETS_REQUIRED"); } const completedTask = await completeMasterAgentTask({ taskId: task.taskId, deviceId: task.deviceId, status: "completed", dispatchPlan: { summary: summarizeGroupDispatchPlan(task.requestText, targets), targets, }, }); return { ok: true as const, taskId: task.taskId, status: "completed" as const, dispatchPlan: completedTask.dispatchPlan ?? null, }; } catch (error) { const message = error instanceof Error ? error.message : "GROUP_DISPATCH_PLAN_FAILED"; await completeMasterAgentTask({ taskId: task.taskId, deviceId: task.deviceId, status: "failed", errorMessage: message, }); return { ok: false as const, taskId: task.taskId, status: "failed" as const, dispatchPlan: null, error: message, }; } } export async function queueGroupDispatchPlan(params: { groupProjectId: string; requestMessageId: string; requestText: string; requestedBy: string; }): Promise { const state = await readState(); const project = state.projects.find((item) => item.id === params.groupProjectId); if (!project) { throw new Error("PROJECT_NOT_FOUND"); } if (!project.isGroup) { throw new Error("PROJECT_NOT_GROUP_CHAT"); } const task = await queueMasterAgentTask({ projectId: project.id, taskType: "group_dispatch_plan", requestMessageId: params.requestMessageId, requestText: params.requestText, executionPrompt: buildGroupDispatchPlanPrompt(project, params.requestText), requestedBy: params.requestedBy, requestedByAccount: params.requestedBy, deviceId: state.user.boundDeviceId || "mac-studio", }); return resolveGroupDispatchPlanTask(task.taskId); } export async function queueThreadConversationReplyTask(params: { projectId: string; requestMessageId: string; requestText: string; requestedBy: string; requestedByAccount: string; }) { const state = await readState(); const project = state.projects.find((item) => item.id === params.projectId); if (!project) { throw new Error("PROJECT_NOT_FOUND"); } if (project.isGroup) { throw new Error("PROJECT_NOT_SINGLE_THREAD"); } if (project.id === "master-agent") { throw new Error("PROJECT_NOT_THREAD_CONVERSATION"); } const deviceId = project.deviceIds[0] || state.user.boundDeviceId || "mac-studio"; return queueMasterAgentTask({ projectId: project.id, taskType: "conversation_reply", requestMessageId: params.requestMessageId, requestText: params.requestText, executionPrompt: buildThreadConversationReplyPrompt(project, params.requestText), requestedBy: params.requestedBy, requestedByAccount: params.requestedByAccount, deviceId, targetProjectId: project.id, targetThreadId: project.threadMeta.threadId, targetThreadDisplayName: project.threadMeta.threadDisplayName, targetCodexThreadRef: project.threadMeta.codexThreadRef, targetCodexFolderRef: project.threadMeta.codexFolderRef, }); } function buildDeviceImportResolutionPrompt(params: { deviceName: string; deviceId: string; draftId: string; selectedCandidates: Array<{ candidateId: string; threadDisplayName: string; folderName: string; lastActiveAt: string; }>; existingProjects: string[]; }) { return [ "你正在处理 Boss 控制台的设备导入决议任务。", "请根据候选线程和现有会话,给出导入建议。", "输出必须是 JSON,对象结构如下:", '{ "summary": "一句中文摘要", "items": [{ "candidateId": "...", "action": "create_thread_conversation|attach_existing|skip", "targetProjectId": "可选", "reason": "中文原因" }] }', "要求:", "1. 每个 candidateId 最多出现一次。", "2. 如果 action=attach_existing,尽量给出 targetProjectId。", "3. 如果信息不足,也必须给出 reason,不要输出额外解释文本。", "", `deviceName: ${params.deviceName}`, `deviceId: ${params.deviceId}`, `draftId: ${params.draftId}`, "selectedCandidates:", params.selectedCandidates .map( (candidate) => `${candidate.candidateId} / ${candidate.threadDisplayName} / ${candidate.folderName} / ${candidate.lastActiveAt}`, ) .join("\n") || "无", "", "existingProjects:", params.existingProjects.join("\n") || "无", ].join("\n"); } type DeviceImportResolutionTaskResult = | { ok: true; taskId: string; status: "completed"; draft: NonNullable>["draft"]>; resolution: NonNullable>["resolution"]>; } | { ok: false; taskId: string; status: "failed"; draft: Awaited>["draft"]; resolution: Awaited>["resolution"]; error: string; }; async function resolveDeviceImportResolutionTask(taskId: string): Promise { const task = await getMasterAgentTask(taskId); if (!task) { throw new Error("MASTER_AGENT_TASK_NOT_FOUND"); } if (task.taskType !== "device_import_resolution" || !task.deviceImportDraftId) { throw new Error("MASTER_AGENT_TASK_TYPE_INVALID"); } const draftRecord = await readState(); const draft = draftRecord.deviceImportDrafts.find((item) => item.draftId === task.deviceImportDraftId); if (!draft) { throw new Error("DEVICE_IMPORT_DRAFT_NOT_FOUND"); } try { const proposal = await previewDeviceImportResolution({ deviceId: draft.deviceId }); await completeMasterAgentTask({ taskId: task.taskId, deviceId: task.deviceId, status: "completed", replyBody: JSON.stringify( { summary: proposal.summary, items: proposal.items.map((item) => ({ candidateId: item.candidateId, action: item.action, targetProjectId: item.targetProjectId, reason: item.reason, })), }, null, 2, ), }); const latest = await getLatestDeviceImportDraft(draft.deviceId); return { ok: true as const, taskId: task.taskId, status: "completed" as const, draft: latest.draft!, resolution: latest.resolution!, }; } catch (error) { const message = error instanceof Error ? error.message : "DEVICE_IMPORT_RESOLUTION_FAILED"; await completeMasterAgentTask({ taskId: task.taskId, deviceId: task.deviceId, status: "failed", errorMessage: message, }); const latest = await getLatestDeviceImportDraft(draft.deviceId); return { ok: false as const, taskId: task.taskId, status: "failed" as const, draft: latest.draft, resolution: latest.resolution, error: message, }; } } export async function queueDeviceImportResolutionTask(params: { deviceId: string; reviewedBy: string; }) { const state = await readState(); const draft = state.deviceImportDrafts.find((item) => item.deviceId === params.deviceId); if (!draft) { throw new Error("DEVICE_IMPORT_DRAFT_NOT_FOUND"); } if (draft.selectedCandidateIds.length === 0) { throw new Error("DEVICE_IMPORT_SELECTION_REQUIRED"); } const device = state.devices.find((item) => item.id === params.deviceId); if (!device) { throw new Error("DEVICE_NOT_FOUND"); } const selectedCandidates = draft.candidates.filter((candidate) => draft.selectedCandidateIds.includes(candidate.candidateId), ); const task = await queueMasterAgentTask({ projectId: "master-agent", taskType: "device_import_resolution", requestMessageId: draft.draftId, requestText: `请审核设备 ${device.name} 的线程导入建议`, executionPrompt: buildDeviceImportResolutionPrompt({ deviceName: device.name, deviceId: device.id, draftId: draft.draftId, selectedCandidates: selectedCandidates.map((candidate) => ({ candidateId: candidate.candidateId, threadDisplayName: candidate.threadDisplayName, folderName: candidate.folderName, lastActiveAt: candidate.lastActiveAt, })), existingProjects: state.projects .filter((project) => !project.isGroup) .map( (project) => `${project.id} / ${project.threadMeta.threadDisplayName} / ${project.threadMeta.folderName} / devices=${project.deviceIds.join(",")}`, ), }), requestedBy: params.reviewedBy, requestedByAccount: params.reviewedBy, deviceId: state.user.boundDeviceId || "mac-studio", deviceImportDraftId: draft.draftId, }); return resolveDeviceImportResolutionTask(task.taskId); } async function waitForMasterAgentTaskCompletion(taskId: string, timeoutMs = 55_000) { const startedAt = Date.now(); while (Date.now() - startedAt < timeoutMs) { const task = await getMasterAgentTask(taskId); if (task?.status === "completed" || task?.status === "failed") { return task; } await new Promise((resolve) => setTimeout(resolve, 1_500)); } return getMasterAgentTask(taskId); } function resolveBossPublicBaseUrl() { const configured = process.env.BOSS_PUBLIC_BASE_URL?.trim(); return configured && /^https?:\/\//i.test(configured) ? configured.replace(/\/+$/, "") : "https://boss.hyzq.net"; } async function buildAttachmentAnalysisContext(params: { attachment: NonNullable>>["attachment"]; }) { const attachment = params.attachment; let excerpt = ""; try { if (canInlineAttachmentText(attachment)) { let buffer: Buffer | Uint8Array = Buffer.alloc(0); if (attachment.storageBackend === "server_file") { buffer = await readServerFileAttachmentBuffer(attachment.storagePath); } else if (attachment.storageBackend === "aliyun_oss") { if (attachment.storageSnapshot?.provider === "aliyun_oss") { buffer = await readAliyunOssObjectBuffer( { enabled: true, accessKeyId: attachment.storageSnapshot.accessKeyId, accessKeySecretEncrypted: attachment.storageSnapshot.accessKeySecretEncrypted, bucket: attachment.storageSnapshot.bucket, endpoint: attachment.storageSnapshot.endpoint, region: attachment.storageSnapshot.region, prefix: attachment.storageSnapshot.prefix, }, attachment.storagePath, ); } else { const currentConfig = await getAttachmentStorageConfig(attachment.uploadedBy); if ( currentConfig.mode === "oss" && currentConfig.ossProvider === "aliyun_oss" && currentConfig.aliyunOss ) { buffer = await readAliyunOssObjectBuffer(currentConfig.aliyunOss, attachment.storagePath); } } } excerpt = extractAttachmentTextExcerpt(buffer); } } catch { excerpt = ""; } return { textExcerpt: excerpt, }; } function buildAttachmentAnalysisPrompt(params: { projectId: string; projectName: string; attachment: NonNullable>>["attachment"]; messageBody: string; requestedBy: string; requestedByAccount: string; attachmentDownloadUrl: string; attachmentTextExcerpt?: string; }) { const attachment = params.attachment; return [ "你是 Boss 控制台的附件分析主 Agent。", "请根据下面的附件元数据、可下载地址,以及你能实际读取到的附件内容进行分析。", "如果需要读取原始文件,请优先使用 curl、python 或系统工具下载并检查该附件。", "如果你无法直接读取原始内容,不要假装已经看过内容,必须明确说明限制,并只基于你实际拿到的内容给出判断。", "输出要求:", "1. 一句话结论", "2. 内容摘要或可见特征", "3. 风险或异常", "4. 建议动作", "", `projectId: ${params.projectId}`, `projectName: ${params.projectName}`, `requestedBy: ${params.requestedBy}`, `requestedByAccount: ${params.requestedByAccount}`, `attachmentId: ${attachment.attachmentId}`, `fileName: ${attachment.fileName}`, `mimeType: ${attachment.mimeType}`, `fileSizeBytes: ${attachment.fileSizeBytes}`, `attachmentKind: ${attachment.attachmentKind}`, `storageBackend: ${attachment.storageBackend}`, `storagePath: ${attachment.storagePath}`, `previewAvailable: ${attachment.previewAvailable ? "yes" : "no"}`, `uploadedAt: ${attachment.uploadedAt}`, `uploadedBy: ${attachment.uploadedBy}`, `analysisState: ${attachment.analysisState}`, `downloadUrl: ${params.attachmentDownloadUrl}`, "", "原始消息:", params.messageBody || "无", "", "如果附件可以直接解析文本,请优先基于文本内容进行判断。", "文本摘录:", params.attachmentTextExcerpt || "无可直接内嵌的文本摘录,请按需下载原文件后自行读取。", ].join("\n"); } export async function queueAttachmentAnalysisTask(params: { projectId: string; attachmentId: string; requestMessageId: string; requestedBy: string; requestedByAccount: string; markProcessing?: boolean; publicBaseUrl?: string; }) { const record = await getProjectAttachment(params.projectId, params.attachmentId); if (!record) { throw new Error("ATTACHMENT_NOT_FOUND"); } const state = await readState(); const taskId = `mastertask-${randomBytes(4).toString("hex")}`; const attachmentDownloadToken = randomBytes(12).toString("hex"); const attachmentDownloadExpiresAt = new Date(Date.now() + 30 * 60_000).toISOString(); const attachmentDownloadUrl = `${params.publicBaseUrl?.trim() || resolveBossPublicBaseUrl()}/api/v1/attachments/${record.attachment.attachmentId}/download` + `?taskId=${taskId}&token=${attachmentDownloadToken}`; const attachmentContext = await buildAttachmentAnalysisContext({ attachment: record.attachment, }); const task = await queueMasterAgentTask({ taskId, projectId: record.project.id, taskType: "attachment_analysis", requestMessageId: params.requestMessageId, requestText: `分析附件《${record.attachment.fileName}》`, executionPrompt: buildAttachmentAnalysisPrompt({ projectId: record.project.id, projectName: record.project.name, attachment: record.attachment, messageBody: record.message.body, requestedBy: params.requestedBy, requestedByAccount: params.requestedByAccount, attachmentDownloadUrl, attachmentTextExcerpt: attachmentContext.textExcerpt, }), requestedBy: params.requestedBy, requestedByAccount: params.requestedByAccount, deviceId: state.user.boundDeviceId || "mac-studio", attachmentId: record.attachment.attachmentId, attachmentFileName: record.attachment.fileName, attachmentDownloadToken, attachmentDownloadExpiresAt, attachmentDownloadUrl, attachmentTextExcerpt: attachmentContext.textExcerpt, }); if (params.markProcessing) { await updateAttachmentAnalysisResult({ projectId: params.projectId, attachmentId: params.attachmentId, status: "processing", }); } return task; } export async function validateAiAccountConnection(accountId: string) { const account = await getRuntimeAiAccountById(accountId); if (!account) { throw new Error("AI_ACCOUNT_NOT_FOUND"); } if (account.provider === "master_codex_node") { const state = await readState(); const nodeId = account.nodeId?.trim() || state.user.boundDeviceId || ""; const boundDevice = state.devices.find((device) => device.id === nodeId); const boundNodeLabel = account.nodeLabel?.trim() || boundDevice?.name || state.user.boundCodexNodeLabel || state.user.boundDeviceId || "绑定设备"; if (!nodeId) { await updateAiAccountHealth({ accountId: account.accountId, status: "needs_login", lastError: "MASTER_CODEX_NODE_NOT_CONFIGURED", lastValidatedAt: new Date().toISOString(), }); return { ok: false as const, status: "needs_login" as const, message: `主 GPT 不在手机里直接登录。请先在绑定设备(例如 ${boundNodeLabel})上的 Codex / ChatGPT Plus 会话里登录,并填写正确的节点 ID,再回来校验连接。`, }; } if (!boundDevice || boundDevice.status !== "online") { await updateAiAccountHealth({ accountId: account.accountId, status: "degraded", lastError: !boundDevice ? "MASTER_CODEX_NODE_DEVICE_NOT_FOUND" : "MASTER_CODEX_NODE_DEVICE_OFFLINE", lastValidatedAt: new Date().toISOString(), }); return { ok: false as const, status: "degraded" as const, message: `主 GPT 不在手机里直接登录。当前绑定设备 ${boundNodeLabel}${boundDevice ? " 不在线" : " 未找到"},主 Agent 暂时无法通过该节点对话。请先在这台设备上登录 Codex / ChatGPT Plus,并确保 local-agent 在线。`, }; } await updateAiAccountHealth({ accountId: account.accountId, status: "ready", lastError: undefined, lastValidatedAt: new Date().toISOString(), lastUsedAt: boundDevice.lastSeenAt || new Date().toISOString(), }); return { ok: true as const, status: "ready" as const, message: `主 GPT 不在手机里直接登录。当前已通过绑定设备 ${boundNodeLabel} 接好 Master Codex Node,主 Agent 会把任务转交给这台设备上的 Codex / ChatGPT Plus 会话。`, }; } if (account.provider !== "openai_api" || !account.apiKey?.trim()) { return { ok: false as const, status: "needs_api_key", message: "当前账号还没有可用的 OpenAI API Key。", }; } const generated = await probeOpenAiApiAccount({ apiKey: account.apiKey, model: account.model || "gpt-5.4", }); await updateAiAccountHealth({ accountId: account.accountId, status: "ready", lastValidatedAt: new Date().toISOString(), lastUsedAt: new Date().toISOString(), }); return { ok: true as const, status: "ready", message: generated.message, requestId: generated.requestId, }; } export async function replyToMasterAgentUserMessage(params: { requestMessageId?: string; requestText: string; requestedBy: string; requestedByAccount: string; currentSessionExpiresAt?: string; mode?: "wait" | "enqueue"; }) { const runtime = await getMasterAgentRuntimeAccount(); const agentControls = await getProjectAgentControls("master-agent"); if (!runtime?.account) { await appendMasterAgentSystemReply( "我已经收到你的消息,但当前没有可用的主控 AI 账号。请到“我的 > AI 账号”至少配置一个可用的 OpenAI API 账号,再继续对话。", ); return { ok: false as const, reason: "NO_AI_ACCOUNT" }; } if (params.mode === "enqueue") { if (runtime.account.provider === "master_codex_node") { const state = await readState(); const deviceId = runtime.account.nodeId || state.user.boundDeviceId || "mac-studio"; const boundDevice = state.devices.find((device) => device.id === deviceId); const boundNodeLabel = runtime.account.nodeLabel?.trim() || boundDevice?.name || state.user.boundCodexNodeLabel || deviceId; if (!boundDevice || boundDevice.status !== "online") { await updateAiAccountHealth({ accountId: runtime.account.accountId, status: "degraded", lastError: !boundDevice ? "MASTER_CODEX_NODE_DEVICE_NOT_FOUND" : "MASTER_CODEX_NODE_DEVICE_OFFLINE", lastValidatedAt: new Date().toISOString(), }); const fallbackAccount = await findFallbackOpenAiAccount(runtime.account.accountId); if (fallbackAccount?.apiKey?.trim()) { return enqueueOpenAiMasterAgentReply({ accountId: fallbackAccount.accountId, accountLabel: fallbackAccount.label || aiRoleLabel(fallbackAccount.role), requestMessageId: params.requestMessageId, requestText: params.requestText, requestedBy: params.requestedBy, requestedByAccount: params.requestedByAccount, currentSessionExpiresAt: params.currentSessionExpiresAt, apiKey: fallbackAccount.apiKey, model: agentControls?.modelOverride || fallbackAccount.model || "gpt-5.4", reasoningEffort: agentControls?.reasoningEffortOverride || "medium", agentControls, }); } await appendMasterAgentSystemReply( `主 GPT 不在手机里直接登录。当前绑定设备 ${boundNodeLabel}${boundDevice ? " 不在线" : " 未找到"},主 Agent 暂时无法通过这台设备对话。请先在该设备上登录 Codex / ChatGPT Plus,并确保 local-agent 在线后再重试。`, `主 Agent · ${runtime.summary.roleLabel}`, ); return { ok: false as const, reason: "MASTER_NODE_OFFLINE" }; } const task = await queueMasterAgentTask({ requestMessageId: params.requestMessageId ?? "master-agent-manual", requestText: params.requestText, executionPrompt: buildMasterCodexNodePrompt( state, params.requestText, params.currentSessionExpiresAt, agentControls, ), requestedBy: params.requestedBy, requestedByAccount: params.requestedByAccount, deviceId, accountId: runtime.account.accountId, accountLabel: runtime.account.label || runtime.summary.roleLabel, }); const queuedReply: QueuedMasterAgentReplyEnvelope = { ok: true as const, accountId: runtime.account.accountId, taskId: task.taskId, masterReplyState: "queued" as const, task: { taskId: task.taskId, taskType: "conversation_reply" as const, status: "queued" as const, }, }; return queuedReply; } if (runtime.account.provider === "openai_api" && runtime.account.apiKey?.trim()) { return enqueueOpenAiMasterAgentReply({ accountId: runtime.account.accountId, accountLabel: runtime.account.label || runtime.summary.roleLabel, requestMessageId: params.requestMessageId, requestText: params.requestText, requestedBy: params.requestedBy, requestedByAccount: params.requestedByAccount, currentSessionExpiresAt: params.currentSessionExpiresAt, apiKey: runtime.account.apiKey, model: agentControls?.modelOverride || runtime.account.model || "gpt-5.4", reasoningEffort: agentControls?.reasoningEffortOverride || "medium", agentControls, }); } } if (runtime.account.provider === "master_codex_node") { const state = await readState(); const deviceId = runtime.account.nodeId || state.user.boundDeviceId || "mac-studio"; const boundDevice = state.devices.find((device) => device.id === deviceId); const boundNodeLabel = runtime.account.nodeLabel?.trim() || boundDevice?.name || state.user.boundCodexNodeLabel || deviceId; if (!boundDevice || boundDevice.status !== "online") { await updateAiAccountHealth({ accountId: runtime.account.accountId, status: "degraded", lastError: !boundDevice ? "MASTER_CODEX_NODE_DEVICE_NOT_FOUND" : "MASTER_CODEX_NODE_DEVICE_OFFLINE", lastValidatedAt: new Date().toISOString(), }); const fallbackAccount = await findFallbackOpenAiAccount(runtime.account.accountId); if (fallbackAccount) { try { return await replyViaOpenAiAccount({ account: fallbackAccount, requestText: params.requestText, currentSessionExpiresAt: params.currentSessionExpiresAt, senderLabel: `主 Agent · ${fallbackAccount.label || aiRoleLabel(fallbackAccount.role)}`, agentControls, }); } catch { // Fall through to the original offline guidance when the fallback API account cannot respond. } } await appendMasterAgentSystemReply( `主 GPT 不在手机里直接登录。当前绑定设备 ${boundNodeLabel}${boundDevice ? " 不在线" : " 未找到"},主 Agent 暂时无法通过这台设备对话。请先在该设备上登录 Codex / ChatGPT Plus,并确保 local-agent 在线后再重试。`, `主 Agent · ${runtime.summary.roleLabel}`, ); return { ok: false as const, reason: "MASTER_NODE_OFFLINE" }; } const task = await queueMasterAgentTask({ requestMessageId: params.requestMessageId ?? "master-agent-manual", requestText: params.requestText, executionPrompt: buildMasterCodexNodePrompt( state, params.requestText, params.currentSessionExpiresAt, agentControls, ), requestedBy: params.requestedBy, requestedByAccount: params.requestedByAccount, deviceId, accountId: runtime.account.accountId, accountLabel: runtime.summary.roleLabel, }); const completedTask = await waitForMasterAgentTaskCompletion(task.taskId); if (completedTask?.status === "completed") { return { ok: true as const, accountId: runtime.account.accountId, taskId: task.taskId, requestId: completedTask.requestId, }; } if (completedTask?.status === "failed") { const fallbackAccount = await findFallbackOpenAiAccount(runtime.account.accountId); if (fallbackAccount) { try { return await replyViaOpenAiAccount({ account: fallbackAccount, requestText: params.requestText, currentSessionExpiresAt: params.currentSessionExpiresAt, senderLabel: `主 Agent · ${fallbackAccount.label || aiRoleLabel(fallbackAccount.role)}`, agentControls, }); } catch { // Preserve the original execution failure below if the fallback account also fails. } } return { ok: false as const, reason: "MASTER_NODE_EXEC_FAILED", taskId: task.taskId, message: completedTask.errorMessage, }; } await appendMasterAgentSystemReply( [ `当前主控身份是 ${runtime.summary.roleLabel},任务已经转交到 ${boundNodeLabel} 的 Master Codex Node。`, "如果本机 Codex 节点在线,回复会在稍后自动回写到当前会话。", ].join(""), `主 Agent · ${runtime.summary.roleLabel}`, ); return { ok: true as const, accountId: runtime.account.accountId, taskId: task.taskId }; } if (runtime.account.provider !== "openai_api" || !runtime.account.apiKey?.trim()) { await appendMasterAgentSystemReply( [ `当前主控身份是 ${runtime.summary.roleLabel},来源 ${aiProviderLabel(runtime.account.provider)}。`, "当前账号既没有接入 Master Codex Node 执行器,也没有可用的 OpenAI API Key。", "请到“我的 > AI 账号”补一个可用的 OpenAI API 账号,或者把当前节点接回 Master Codex Node relay。", ].join(""), `主 Agent · ${runtime.summary.roleLabel}`, ); return { ok: false as const, reason: "MASTER_NODE_NOT_CONNECTED" }; } try { const generated = await generateOpenAiReply({ apiKey: runtime.account.apiKey, model: agentControls?.modelOverride || runtime.account.model || "gpt-5.4", reasoningEffort: agentControls?.reasoningEffortOverride || "medium", requestText: params.requestText, currentSessionExpiresAt: params.currentSessionExpiresAt, agentControls, }); await appendMasterAgentSystemReply( generated.content, `主 Agent · ${runtime.summary.roleLabel}`, ); if (!runtime.isEnvironmentFallback) { await updateAiAccountHealth({ accountId: runtime.account.accountId, status: "ready", lastValidatedAt: new Date().toISOString(), lastUsedAt: new Date().toISOString(), activate: !runtime.account.isActive, switchReason: runtime.account.isActive ? runtime.account.switchReason : `主 Agent 回复时自动切换到 ${runtime.account.label}`, }); } return { ok: true as const, accountId: runtime.account.accountId, requestId: generated.requestId, }; } catch (error) { const message = error instanceof Error ? error.message : "主 Agent 当前调用模型失败。"; if (!runtime.isEnvironmentFallback) { await updateAiAccountHealth({ accountId: runtime.account.accountId, status: "degraded", lastError: message, lastValidatedAt: new Date().toISOString(), }); } await appendMasterAgentSystemReply( [ `我已经收到你的消息,但当前 AI 账号调用失败:${message}。`, "请到“我的 > AI 账号”检查 API Key、模型名或切换到其他 AI 账号后重试。", ].join(""), `主 Agent · ${runtime.summary.roleLabel}`, ); return { ok: false as const, reason: "MODEL_CALL_FAILED", message }; } }