Files
boss/src/lib/boss-master-agent.ts
2026-03-31 19:59:08 +08:00

1460 lines
50 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<ReturnType<typeof readState>>,
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-Age2592000 秒。",
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<ReturnType<typeof findFallbackOpenAiAccount>>;
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<ReturnType<typeof readState>>,
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<ReturnType<typeof readState>>,
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<ReturnType<typeof readState>>,
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<ReturnType<typeof completeMasterAgentTask>>
>["dispatchPlan"] | null;
}
| {
ok: false;
taskId: string;
status: "failed";
dispatchPlan: null;
error: string;
};
async function resolveGroupDispatchPlanTask(taskId: string): Promise<GroupDispatchRecommendationResult> {
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<GroupDispatchRecommendationResult> {
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<Awaited<ReturnType<typeof getLatestDeviceImportDraft>>["draft"]>;
resolution: NonNullable<Awaited<ReturnType<typeof getLatestDeviceImportDraft>>["resolution"]>;
}
| {
ok: false;
taskId: string;
status: "failed";
draft: Awaited<ReturnType<typeof getLatestDeviceImportDraft>>["draft"];
resolution: Awaited<ReturnType<typeof getLatestDeviceImportDraft>>["resolution"];
error: string;
};
async function resolveDeviceImportResolutionTask(taskId: string): Promise<DeviceImportResolutionTaskResult> {
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<Awaited<ReturnType<typeof getProjectAttachment>>>["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<Awaited<ReturnType<typeof getProjectAttachment>>>["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 };
}
}