diff --git a/frontend-web/src/roundtable-planning/pages/RoundtablePlanningPage.tsx b/frontend-web/src/roundtable-planning/pages/RoundtablePlanningPage.tsx index 3fb86805..364d2253 100644 --- a/frontend-web/src/roundtable-planning/pages/RoundtablePlanningPage.tsx +++ b/frontend-web/src/roundtable-planning/pages/RoundtablePlanningPage.tsx @@ -169,6 +169,21 @@ interface RoundtablePrefill { autoStart?: boolean; } +/** + * 会商「聊天记录」存储键:草稿(useDraftPersistence / useRoundtableDrafts)与后台作业 + * (startJob / getJobByDraft / cancelJob)统一按此键隔离与回显。 + * + * - **xdfx-A(行动分析会商)**:同一任务下可派生多个「行动」,各行动的会商需互相独立, + * 故按「行动id」(`actionId`,即深链 URL `?taskId=` 原始传入值) 隔离,而非解析出的真实任务id。 + * - 其它业务:无 `actionId`,回退真实 `taskId`。 + * + * ⚠️ 仅用于聊天记录/作业存储。目的树、入库技能、任务详情、底部快捷跳转栏仍用真实 + * `prefill.taskId`(见各调用点),不受此键影响。 + */ +function recordTaskIdOf(prefill: RoundtablePrefill | null): string | null { + return prefill?.actionId ?? prefill?.taskId ?? null; +} + /** * 无界(wujie)抽屉场景:父页面通过 `window.$wujie.props` 注入「任务名称/描述/业务链条」。 * iframe 降级时 props 可能晚于首帧,故再读 hash 路由 query 作兜底(宿主 `appendEmbedQueryToUrl` 写入)。 @@ -715,7 +730,8 @@ export default function RoundtablePlanningPage() { currentStep, setCurrentStep, triggerToast, - taskId: prefillRef.current?.taskId ?? null, + // 聊天记录存储键:xdfx-A 按行动id 隔离,其它业务按真实 taskId(见 recordTaskIdOf)。 + taskId: recordTaskIdOf(prefillRef.current), intentSending: step1.isIntentSending, step2FinalizedCount, step3SavedTick, @@ -1045,7 +1061,8 @@ export default function RoundtablePlanningPage() { // C1.5(无界回显):带 taskId 时,挂载即按任务取最新草稿回显。命中 → 抑制 autoStart // (避免重复发首条消息);未命中 → 放行 autoStart 走全新任务。只跑一次。 useEffect(() => { - const tid = prefillRef.current?.taskId; + // 回显聊天记录按存储键(xdfx-A=行动id),与 useDraftPersistence 的 taskId 保持一致。 + const tid = recordTaskIdOf(prefillRef.current); if (!tid) return; let cancelled = false; void (async () => { @@ -1475,7 +1492,7 @@ export default function RoundtablePlanningPage() { step2.stopOrchestration(); // 1. 取消后端作业:避免前后端双跑/抢同一线程(409),线程随后由前端接管续跑。 // executor.cancel 会 task.cancel() 中断在跑的 run,DB 同步置 cancelled。 - await cancelJob(jobId, prefillRef.current?.taskId ?? null).catch(() => null); + await cancelJob(jobId, recordTaskIdOf(prefillRef.current)).catch(() => null); // 2. 加载作业最近写回草稿的快照:恢复 threadIds(作业真实线程)+ 协调名 + 完整对话。 // hydrateFromDraft 已同步写好 threadIds/coordinatorName 的 ref,供下一步接管即时读取。 await drafts.handleLoadDraft(draftId); @@ -1579,7 +1596,8 @@ export default function RoundtablePlanningPage() { if (agents.length === 0) return null; // 后台挂起发起前**立刻强制存一次**当前聊天(保住发起时刻的本地研讨记录,防丢)。 const draftId = await drafts.saveDraftNow(2); - const tid = prefillRef.current?.taskId ?? null; + // 后台作业按聊天记录存储键归属(xdfx-A=行动id):报告写回对应 task-draft 存储、去重一致。 + const tid = recordTaskIdOf(prefillRef.current); // 防重复:该草稿已有在跑作业就不再新建(避免重复作业 / 前后端双跑)。 const existing = draftId ? await getJobByDraft(draftId, tid).catch(() => null) : null; const alreadyRunning = @@ -1650,7 +1668,8 @@ export default function RoundtablePlanningPage() { const chainSnap = chainMeta; const planSnap = mode === "dag" ? orchestrationPlan : null; const draftId = drafts.currentDraftId; - const tid = prefillRef.current?.taskId ?? null; + // 后台作业按聊天记录存储键归属(xdfx-A=行动id),与草稿存储一致。 + const tid = recordTaskIdOf(prefillRef.current); // 立刻:标记「挂起中」+ 跳回第一步 + 提示(不等接口)。 if (draftId) markPendingSuspend(draftId); @@ -1745,7 +1764,7 @@ export default function RoundtablePlanningPage() { if (agents.length === 0) return; startJobKeepalive({ draftId: drafts.currentDraftId ?? undefined, - taskId: prefillRef.current?.taskId ?? undefined, + taskId: recordTaskIdOf(prefillRef.current) ?? undefined, intent: buildInitialIntent(), agents, model: selectedModel || undefined, @@ -2060,7 +2079,7 @@ export default function RoundtablePlanningPage() { onSelectChain={handleSelectChainFromSidebar} onOpenDraft={drafts.handleLoadDraft} activeDraftId={drafts.currentDraftId} - entryRailTaskId={prefillRef.current?.taskId ?? null} + entryRailTaskId={recordTaskIdOf(prefillRef.current)} entryRailBusinessCode={prefillRef.current?.businessCode ?? null} />