fix(roundtable): xdfx-A 深链会商聊天记录按行动id隔离存储

xdfx 深链 URL ?taskId= 原始值即 actionId(登录时解析成真实任务id)。
一个任务可派生多个行动,各行动会商需互相独立,故聊天记录/后台作业
按 actionId 存取,而非解析出的真实 taskId。

新增 recordTaskIdOf(prefill)=actionId??taskId,应用到草稿 CRUD/回显
(useDraftPersistence/useRoundtableDrafts/loadLatestByTask) 与后台作业
(startJob/getJobByDraft/cancelJob/startJobKeepalive)。目的树/入库/任务
详情/底部快捷跳转栏仍用真实 taskId。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
fengxiangdebaoziandClaude Opus 4.8 committed 2026-06-22 21:01:34 +08:00
1 parent fbec222eb4
commit f144a59c97
1 file changed
+26 -7
@@ -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}
/>