feat: add realtime sync and import project understanding
This commit is contained in:
@@ -482,6 +482,19 @@ export interface DeviceImportResolution {
|
||||
appliedBy?: string;
|
||||
}
|
||||
|
||||
export interface DeviceImportProjectUnderstanding {
|
||||
candidateId: string;
|
||||
threadDisplayName: string;
|
||||
folderName: string;
|
||||
projectGoal: string;
|
||||
currentProgress: string;
|
||||
technicalArchitecture: string;
|
||||
currentBlockers: string;
|
||||
recommendedNextStep: string;
|
||||
sourceTaskId: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface VerificationCode {
|
||||
id: string;
|
||||
account: string;
|
||||
@@ -646,6 +659,8 @@ export interface MasterAgentTask {
|
||||
orchestrationBackendId?: OrchestrationBackendId;
|
||||
orchestrationBackendLabel?: string;
|
||||
deviceImportDraftId?: string;
|
||||
deviceImportCandidateId?: string;
|
||||
deviceImportCandidateFolderName?: string;
|
||||
status: MasterAgentTaskStatus;
|
||||
requestedAt: string;
|
||||
claimedAt?: string;
|
||||
@@ -2926,6 +2941,8 @@ function normalizeState(raw: Partial<BossState> | undefined): BossState {
|
||||
: undefined,
|
||||
orchestrationBackendLabel: task.orchestrationBackendLabel,
|
||||
deviceImportDraftId: task.deviceImportDraftId,
|
||||
deviceImportCandidateId: task.deviceImportCandidateId,
|
||||
deviceImportCandidateFolderName: task.deviceImportCandidateFolderName,
|
||||
status: task.status ?? "queued",
|
||||
requestedAt: task.requestedAt ?? nowIso(),
|
||||
claimedAt: task.claimedAt,
|
||||
@@ -5130,6 +5147,8 @@ export async function queueMasterAgentTask(payload: {
|
||||
targetCodexFolderRef?: string;
|
||||
orchestrationBackendId?: OrchestrationBackendId;
|
||||
orchestrationBackendLabel?: string;
|
||||
deviceImportCandidateId?: string;
|
||||
deviceImportCandidateFolderName?: string;
|
||||
}) {
|
||||
const task = await mutateState((state) => {
|
||||
const task: MasterAgentTask = {
|
||||
@@ -5159,6 +5178,8 @@ export async function queueMasterAgentTask(payload: {
|
||||
targetCodexFolderRef: payload.targetCodexFolderRef,
|
||||
orchestrationBackendId: payload.orchestrationBackendId,
|
||||
orchestrationBackendLabel: payload.orchestrationBackendLabel,
|
||||
deviceImportCandidateId: payload.deviceImportCandidateId,
|
||||
deviceImportCandidateFolderName: payload.deviceImportCandidateFolderName,
|
||||
status: "queued",
|
||||
requestedAt: nowIso(),
|
||||
};
|
||||
@@ -6114,11 +6135,20 @@ export async function completeMasterAgentTask(payload: {
|
||||
masterSummary: payload.replyBody?.trim(),
|
||||
});
|
||||
} else if (!attachmentProjectId && payload.status === "completed" && task.replyBody) {
|
||||
const isDeviceImportUnderstanding =
|
||||
task.taskType === "conversation_reply" &&
|
||||
task.projectId === "master-agent" &&
|
||||
Boolean(task.deviceImportDraftId && task.deviceImportCandidateId);
|
||||
const isThreadConversationReply =
|
||||
task.taskType === "conversation_reply" &&
|
||||
task.projectId !== "master-agent" &&
|
||||
Boolean(task.targetProjectId && task.targetThreadId);
|
||||
if (isThreadConversationReply) {
|
||||
if (isDeviceImportUnderstanding) {
|
||||
const draft = state.deviceImportDrafts.find((item) => item.draftId === task.deviceImportDraftId);
|
||||
if (draft) {
|
||||
publishBossEvent("devices.updated", { deviceId: draft.deviceId });
|
||||
}
|
||||
} else if (isThreadConversationReply) {
|
||||
const threadProject = state.projects.find(
|
||||
(item) => item.id === (task.targetProjectId ?? task.projectId),
|
||||
);
|
||||
@@ -7009,7 +7039,110 @@ export async function getLatestDeviceImportDraft(deviceId: string) {
|
||||
item.deviceImportDraftId === draft.draftId,
|
||||
) ?? null
|
||||
: null;
|
||||
return { draft, resolution, reviewTask };
|
||||
const understandingTasks = draft
|
||||
? listDeviceImportUnderstandingTasks(state, draft.draftId)
|
||||
: [];
|
||||
const projectUnderstandings = draft
|
||||
? deriveDeviceImportProjectUnderstandings(state, draft.draftId)
|
||||
: [];
|
||||
return { draft, resolution, reviewTask, understandingTasks, projectUnderstandings };
|
||||
}
|
||||
|
||||
function listDeviceImportUnderstandingTasks(state: BossState, draftId: string) {
|
||||
const latestByCandidate = new Map<string, MasterAgentTask>();
|
||||
for (const task of state.masterAgentTasks) {
|
||||
if (
|
||||
task.taskType !== "conversation_reply" ||
|
||||
task.deviceImportDraftId !== draftId ||
|
||||
!task.deviceImportCandidateId
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
const existing = latestByCandidate.get(task.deviceImportCandidateId);
|
||||
if (!existing || existing.requestedAt < task.requestedAt) {
|
||||
latestByCandidate.set(task.deviceImportCandidateId, task);
|
||||
}
|
||||
}
|
||||
return [...latestByCandidate.values()].map((task) => ({
|
||||
taskId: task.taskId,
|
||||
candidateId: task.deviceImportCandidateId ?? "",
|
||||
threadDisplayName: task.targetThreadDisplayName ?? "",
|
||||
folderName: task.deviceImportCandidateFolderName ?? "",
|
||||
status: task.status,
|
||||
updatedAt: task.completedAt ?? task.claimedAt ?? task.requestedAt,
|
||||
}));
|
||||
}
|
||||
|
||||
function parseDeviceImportUnderstandingReply(
|
||||
task: Pick<MasterAgentTask, "replyBody" | "deviceImportCandidateId" | "targetThreadDisplayName" | "deviceImportCandidateFolderName" | "taskId" | "completedAt" | "requestedAt">,
|
||||
): DeviceImportProjectUnderstanding | null {
|
||||
const candidateId = task.deviceImportCandidateId?.trim();
|
||||
const replyBody = task.replyBody?.trim();
|
||||
if (!candidateId || !replyBody) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const fencedMatch = replyBody.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
||||
const jsonCandidate = fencedMatch?.[1]?.trim() ?? replyBody;
|
||||
let parsed:
|
||||
| {
|
||||
projectGoal?: string;
|
||||
currentProgress?: string;
|
||||
technicalArchitecture?: string;
|
||||
currentBlockers?: string;
|
||||
recommendedNextStep?: string;
|
||||
}
|
||||
| null = null;
|
||||
try {
|
||||
parsed = JSON.parse(jsonCandidate);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
|
||||
const projectGoal = parsed?.projectGoal?.trim() ?? "";
|
||||
const currentProgress = parsed?.currentProgress?.trim() ?? "";
|
||||
const technicalArchitecture = parsed?.technicalArchitecture?.trim() ?? "";
|
||||
const currentBlockers = parsed?.currentBlockers?.trim() ?? "";
|
||||
const recommendedNextStep = parsed?.recommendedNextStep?.trim() ?? "";
|
||||
if (!projectGoal && !currentProgress && !technicalArchitecture && !currentBlockers && !recommendedNextStep) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
candidateId,
|
||||
threadDisplayName: task.targetThreadDisplayName?.trim() || "未命名线程",
|
||||
folderName: task.deviceImportCandidateFolderName?.trim() || "",
|
||||
projectGoal,
|
||||
currentProgress,
|
||||
technicalArchitecture,
|
||||
currentBlockers,
|
||||
recommendedNextStep,
|
||||
sourceTaskId: task.taskId,
|
||||
updatedAt: task.completedAt ?? task.requestedAt,
|
||||
};
|
||||
}
|
||||
|
||||
function deriveDeviceImportProjectUnderstandings(state: BossState, draftId: string) {
|
||||
const latestByCandidate = new Map<string, DeviceImportProjectUnderstanding>();
|
||||
for (const task of state.masterAgentTasks) {
|
||||
if (
|
||||
task.taskType !== "conversation_reply" ||
|
||||
task.deviceImportDraftId !== draftId ||
|
||||
task.status !== "completed" ||
|
||||
!task.deviceImportCandidateId
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
const understanding = parseDeviceImportUnderstandingReply(task);
|
||||
if (!understanding) {
|
||||
continue;
|
||||
}
|
||||
const existing = latestByCandidate.get(understanding.candidateId);
|
||||
if (!existing || existing.updatedAt < understanding.updatedAt) {
|
||||
latestByCandidate.set(understanding.candidateId, understanding);
|
||||
}
|
||||
}
|
||||
return [...latestByCandidate.values()];
|
||||
}
|
||||
|
||||
export async function previewDeviceImportResolution(input: { deviceId: string }) {
|
||||
|
||||
@@ -1579,6 +1579,35 @@ function buildDeviceImportResolutionPrompt(params: {
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
function buildDeviceImportUnderstandingPrompt(params: {
|
||||
deviceName: string;
|
||||
draftId: string;
|
||||
candidateId: string;
|
||||
threadDisplayName: string;
|
||||
folderName: string;
|
||||
lastActiveAt: string;
|
||||
}) {
|
||||
return [
|
||||
"你正在协助 Boss 控制台完成新设备导入前的项目理解。",
|
||||
"请以当前活跃线程的视角,直接总结这个项目当前最关键的信息。",
|
||||
"输出必须是 JSON,对象结构如下:",
|
||||
'{ "projectGoal": "一句中文目标", "currentProgress": "一句中文进度", "technicalArchitecture": "一句中文架构说明", "currentBlockers": "一句中文阻塞说明", "recommendedNextStep": "一句中文建议动作" }',
|
||||
"要求:",
|
||||
"1. 只输出 JSON,不要输出额外解释。",
|
||||
"2. 用中文,句子短而明确。",
|
||||
"3. 如果某项信息不确定,也要明确写出当前已知范围,不要留空。",
|
||||
"",
|
||||
`deviceName: ${params.deviceName}`,
|
||||
`draftId: ${params.draftId}`,
|
||||
`candidateId: ${params.candidateId}`,
|
||||
`threadDisplayName: ${params.threadDisplayName}`,
|
||||
`folderName: ${params.folderName}`,
|
||||
`lastActiveAt: ${params.lastActiveAt}`,
|
||||
"",
|
||||
"请回答当前项目:目标、进度、技术架构、阻塞点、下一步建议。",
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
export async function queueDeviceImportResolutionTask(params: {
|
||||
deviceId: string;
|
||||
reviewedBy: string;
|
||||
@@ -1599,6 +1628,35 @@ export async function queueDeviceImportResolutionTask(params: {
|
||||
const selectedCandidates = draft.candidates.filter((candidate) =>
|
||||
draft.selectedCandidateIds.includes(candidate.candidateId),
|
||||
);
|
||||
const understandingTasks = await Promise.all(
|
||||
selectedCandidates
|
||||
.filter((candidate) => candidate.codexThreadRef?.trim())
|
||||
.map((candidate) =>
|
||||
queueMasterAgentTask({
|
||||
projectId: "master-agent",
|
||||
taskType: "conversation_reply",
|
||||
requestMessageId: draft.draftId,
|
||||
requestText: `请总结线程 ${candidate.threadDisplayName} 当前项目目标与进度`,
|
||||
executionPrompt: buildDeviceImportUnderstandingPrompt({
|
||||
deviceName: device.name,
|
||||
draftId: draft.draftId,
|
||||
candidateId: candidate.candidateId,
|
||||
threadDisplayName: candidate.threadDisplayName,
|
||||
folderName: candidate.folderName,
|
||||
lastActiveAt: candidate.lastActiveAt,
|
||||
}),
|
||||
requestedBy: params.reviewedBy,
|
||||
requestedByAccount: params.reviewedBy,
|
||||
deviceId: device.id,
|
||||
deviceImportDraftId: draft.draftId,
|
||||
deviceImportCandidateId: candidate.candidateId,
|
||||
deviceImportCandidateFolderName: candidate.folderName,
|
||||
targetThreadDisplayName: candidate.threadDisplayName,
|
||||
targetCodexThreadRef: candidate.codexThreadRef,
|
||||
targetCodexFolderRef: candidate.codexFolderRef ?? candidate.folderRef,
|
||||
}),
|
||||
),
|
||||
);
|
||||
const task = await queueMasterAgentTask({
|
||||
projectId: "master-agent",
|
||||
taskType: "device_import_resolution",
|
||||
@@ -1639,6 +1697,13 @@ export async function queueDeviceImportResolutionTask(params: {
|
||||
deviceImportDraftId: task.deviceImportDraftId,
|
||||
},
|
||||
draft: latest.draft ?? undefined,
|
||||
understandingTasks: understandingTasks.map((item) => ({
|
||||
taskId: item.taskId,
|
||||
taskType: item.taskType,
|
||||
status: item.status,
|
||||
candidateId: item.deviceImportCandidateId,
|
||||
threadDisplayName: item.targetThreadDisplayName,
|
||||
})),
|
||||
...(latest.resolution ? { resolution: latest.resolution } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user