feat: complete chat routing and openai onboarding

This commit is contained in:
kris
2026-03-31 03:31:22 +08:00
parent 5b590f7cc1
commit 9c02ebb574
25 changed files with 2241 additions and 133 deletions

View File

@@ -327,6 +327,15 @@ export interface DeviceImportCandidate {
suggestedImport: boolean;
}
export function isDispatchableThreadProject(project: Project) {
return (
project.id !== "master-agent" &&
!project.isGroup &&
Boolean(project.threadMeta.codexThreadRef?.trim()) &&
project.deviceIds.length > 0
);
}
export interface DeviceImportDraft {
draftId: string;
deviceId: string;
@@ -521,6 +530,8 @@ export interface MasterAgentTask {
targetProjectId?: string;
targetThreadId?: string;
targetThreadDisplayName?: string;
targetCodexThreadRef?: string;
targetCodexFolderRef?: string;
deviceImportDraftId?: string;
status: MasterAgentTaskStatus;
requestedAt: string;
@@ -3812,7 +3823,10 @@ export async function saveAiAccount(payload: {
const existing = payload.accountId
? state.aiAccounts.find((item) => item.accountId === payload.accountId)
: null;
const accountId = existing?.accountId ?? `ai-${slugify(`${payload.label}-${payload.displayName}`)}`;
const accountId =
existing?.accountId ??
payload.accountId?.trim() ??
`ai-${slugify(`${payload.label}-${payload.displayName}`)}`;
const next: AiAccount = normalizeAiAccount({
accountId,
label: payload.label.trim() || aiRoleLabel(payload.role),
@@ -3989,6 +4003,8 @@ export async function queueMasterAgentTask(payload: {
targetProjectId?: string;
targetThreadId?: string;
targetThreadDisplayName?: string;
targetCodexThreadRef?: string;
targetCodexFolderRef?: string;
}) {
const task = await mutateState((state) => {
const task: MasterAgentTask = {
@@ -4014,6 +4030,8 @@ export async function queueMasterAgentTask(payload: {
targetProjectId: payload.targetProjectId,
targetThreadId: payload.targetThreadId,
targetThreadDisplayName: payload.targetThreadDisplayName,
targetCodexThreadRef: payload.targetCodexThreadRef,
targetCodexFolderRef: payload.targetCodexFolderRef,
status: "queued",
requestedAt: nowIso(),
};
@@ -4281,6 +4299,8 @@ function ensureDispatchExecutionTaskInState(
existing.targetProjectId = existing.targetProjectId ?? execution.targetProjectId;
existing.targetThreadId = existing.targetThreadId ?? execution.targetThreadId;
existing.targetThreadDisplayName = existing.targetThreadDisplayName ?? target.threadDisplayName;
existing.targetCodexThreadRef = existing.targetCodexThreadRef ?? target.codexThreadRef;
existing.targetCodexFolderRef = existing.targetCodexFolderRef ?? target.codexFolderRef;
existing.executionPrompt =
existing.executionPrompt ||
buildDispatchExecutionPrompt({
@@ -4311,6 +4331,8 @@ function ensureDispatchExecutionTaskInState(
targetProjectId: execution.targetProjectId,
targetThreadId: execution.targetThreadId,
targetThreadDisplayName: target.threadDisplayName,
targetCodexThreadRef: target.codexThreadRef,
targetCodexFolderRef: target.codexFolderRef,
status: "queued",
requestedAt: nowIso(),
};
@@ -4790,17 +4812,48 @@ export async function completeMasterAgentTask(payload: {
masterSummary: payload.replyBody?.trim(),
});
} else if (!attachmentProjectId && payload.status === "completed" && task.replyBody) {
pushProjectLedgerMessage(state, task.projectId, {
sender: "master",
senderLabel: task.accountLabel ? `主 Agent · ${task.accountLabel}` : "主 Agent",
body: task.replyBody,
kind: "text",
});
const isThreadConversationReply =
task.taskType === "conversation_reply" &&
task.projectId !== "master-agent" &&
Boolean(task.targetProjectId && task.targetThreadId);
if (isThreadConversationReply) {
const threadProject = state.projects.find(
(item) => item.id === (task.targetProjectId ?? task.projectId),
);
const device = state.devices.find((item) => item.id === payload.deviceId);
pushProjectLedgerMessage(state, threadProject?.id ?? task.projectId, {
sender: "device",
senderLabel:
task.targetThreadDisplayName?.trim() ||
threadProject?.threadMeta.threadDisplayName ||
device?.name ||
"线程",
body: task.replyBody,
kind: "text",
});
} else {
pushProjectLedgerMessage(state, task.projectId, {
sender: "master",
senderLabel: task.accountLabel ? `主 Agent · ${task.accountLabel}` : "主 Agent",
body: task.replyBody,
kind: "text",
});
}
} else if (!attachmentProjectId && payload.status === "failed") {
const isThreadConversationReply =
task.taskType === "conversation_reply" &&
task.projectId !== "master-agent" &&
Boolean(task.targetProjectId && task.targetThreadId);
pushProjectLedgerMessage(state, task.projectId, {
sender: "ops",
senderLabel: task.accountLabel ? `主 Agent Relay · ${task.accountLabel}` : "主 Agent Relay",
body: `Master Codex Node 执行失败:${task.errorMessage ?? "UNKNOWN_ERROR"}`,
senderLabel: isThreadConversationReply
? "线程执行失败"
: task.accountLabel
? `主 Agent Relay · ${task.accountLabel}`
: "主 Agent Relay",
body: isThreadConversationReply
? `${task.targetThreadDisplayName ?? "当前线程"} 执行失败:${task.errorMessage ?? "UNKNOWN_ERROR"}`
: `Master Codex Node 执行失败:${task.errorMessage ?? "UNKNOWN_ERROR"}`,
kind: "text",
});
}
@@ -6233,6 +6286,9 @@ function createGroupChatFromProjectIds(
if (!memberProject) {
throw new Error("GROUP_CHAT_MEMBER_NOT_FOUND");
}
if (!isDispatchableThreadProject(memberProject)) {
throw new Error("GROUP_CHAT_MEMBER_NOT_THREAD");
}
memberProjects.push(memberProject);
}
if (memberProjects.length < 2) {