deerflow-code/frontend-web/docs/multi-agent-business-chain-dev.md
2026-09-07 18:24:55 +08:00

31 KiB
Raw Blame History

业务链条模式 · 需求与分步实现文档

面向「智能体推荐弹窗」的扩展:在现有智能推荐之外,新增业务链条选择与业务链条配置两块能力;选定业务链条后,Step 2 圆桌研讨按链条内的固定顺序逐个发言(而非现在的总控派活)。

配套阅读:

  • 前端总览:multi-agent-frontend-dev.md(推荐弹窗 §6.2、Step 2 编排 §5.4、草稿持久化 §5.7)
  • 后端总览:multi-agent-backend-dev.md(Step 2 §6、草稿/历史持久化 §7A)

本文档约定:**「席位 / 智能体」指一个 agent;「业务链条 / chain」**指一组有序排列的 agent 配置,是可命名、可复用、可持久化的实体。


0. 需求原文拆解

用户原始诉求逐条拆成可实现条目:

# 原始诉求 拆解
1 推荐弹窗要有「模式选择」 弹窗顶部加模式切换(智能推荐 / 业务链条),两种模式互斥,确认后都产出有序的 SelectedAgent[]
2 「智能推荐」就是现在这个 现有 RecommendAgentsDialog 整体收进「智能推荐」模式,行为不变
3 「业务链条选择」是卡片列表 卡片 = 标题 + 一排智能体名 tag;tag 不同颜色;tag 之间有向左箭头连接,表达链条次序
4 业务链条需要一个配置页面 左侧智能体列表(复用 GET /api/agents),右侧有序长条卡片(名称 + 描述);支持右侧内部拖拽排序、左→右拖入指定位置
5 配置页用弹窗还是新页面? 本文档决策:独立路由页面(理由见 §2.1)
6 选定链条后,Step 2 用链条内智能体研讨 复用既有 Step 2 init/stream;总控(leader)按链条顺序逐个派活(§6)
7 规划成分步可实现的需求文档 见 §7 分阶段实现

1. 目标与非目标

1.1 目标

  • 推荐弹窗成为「席位来源选择器」:无论智能推荐还是业务链条,最终都向 onConfirm 交付一个有序的 { agent_id, name }[] + 一个 orchestrationMode。
  • 业务链条是用户可创建、命名、保存、复用、删除的有序智能体编排模板。
  • Step 2 支持两种编排:
    • recommend 模式 → 现有总控自由派活循环(不动)。
    • chain 模式 → 总控仍是派活方,但被约束按链条顺序逐个派活(新增)。

1.2 非目标(本期不做,留扩展点)

  • 链条内每个席位的 per-seat prompt/temperature 真实覆盖(仍仅前端记录,沿用现状)。
  • 链条的分支 / 条件流转(只做线性顺序链)。
  • 链条跨用户共享 / 市场(先做「本人私有链条」)。
  • 链条版本管理 / 协作编辑。

2. 关键设计决策

2.1 决策 A:配置页面用「独立路由页面」而非弹窗 ✅

结论:业务链条的「编辑/管理」用独立路由页面 /page/workspace/roundtable/chains;业务链条的「选择」仍内嵌在推荐弹窗里(卡片列表)。

理由:

维度 弹窗 独立页面 ✅
双栏 + 拖拽 + 长条卡片 弹窗宽度(sm:max-w-3xl)+ max-h-[85vh] 拖拽空间局促,左右栏挤 整屏宽度,拖拽手感好
复用入口 只能从推荐弹窗进 可从侧栏、推荐弹窗「管理链条」按钮、深链接进入
可深链接/刷新 不可 /roundtable/chains 可直达、可分享
推荐弹窗内嵌编辑器 弹窗套弹窗 / 模式套编辑,状态复杂 关注点分离:弹窗只「选」,页面只「编」

分工:

  • 推荐弹窗:模式选择 + 业务链条选择(只读卡片列表,可点「去配置」跳到页面)。
  • 配置页面:业务链条的增删改(双栏拖拽编辑器 + 链条列表管理)。

备选:若产品坚持不加路由,可改为**全屏 Dialog(max-w-[90vw] h-[90vh])**承载编辑器,从推荐弹窗内打开。结构与页面版一致,仅外壳不同。本文档按「独立页面」实现,§7 标注了切换成本。

2.2 决策 B:链条持久化 —— 后端 MySQL(确定) ✅

结论:业务链条存后端 MySQL,仿 roundtable_drafts(§7A)新建 roundtable_chains 表 + /api/roundtable-chains 路由,按登录用户隔离、跨设备保留。 不走 localStorage。

理由:链条是可复用、需跨设备/会话保留的模板,与草稿/推荐历史性质一致;草稿当年正是因 localStorage 换设备即丢才迁后端(§7A),链条不重复踩坑。

落地:

  • 表/路由设计见 §5.2,是「复制 §7A 草稿持久化、把字段换成 chain」的机械工作。
  • 前端 api/chains.ts 直接打后端接口(snake_case↔camelCase 在这层收口,仿 api/drafts.ts),不提供 localStorage 实现。
  • 后端表必须先于前端 UI 落地(§7 阶段 0 即包含后端表/路由)。

2.3 决策 C:拖拽库选型 —— @dnd-kit

仓库当前无任何拖拽库(package.json 无 @dnd-kit / react-dnd / sortablejs;@xyflow/react 是节点图,不适合列表排序)。

方案 取舍
@dnd-kit/core + @dnd-kit/sortable(推荐) React 19 友好、可访问性好、跨容器拖入 + 列表排序 + 插入指示器开箱即用;新增 ~30KB gzip 依赖
原生 HTML5 DnD(零依赖兜底) 不加依赖,但跨容器插入位置指示、键盘可达性都要手写,易出 bug

建议:新增 @dnd-kit/core、@dnd-kit/sortable、@dnd-kit/utilities。若产品方禁止新增依赖,退回原生 HTML5 DnD(§7 阶段 2 标注差异)。

2.4 决策 D:Step 2「总控按链条顺序派活」,后端零改动 ✅

形态:总控(leader)仍是唯一派活方,但每一轮被前端约束「只能派给链条里的下一个席位」,逐个走完整条链,最后由总控收口。 区别于 recommend 模式的「总控自由决定派给谁」。

为什么后端无需改动:

  • Step 2 后端 streamMultiAgent({ agentType: "leader" }) 每轮返回 dispatched = [[agent, task], ...];agentType: "special" 单独驱动某席位跑一轮。这两个原语已足够。
  • 每个 special 跑完,后端 _broadcast 会把「子智能体 X 完成…交付内容为:…」append 进其它所有席位 + 总控 thread 的历史(后端 §6.4),所以链路上下文天然累积。
  • chain 模式只是改变前端怎么提示 leader、以及怎么校验它的派活——把每轮 leader 的提示词约束成「请向链条第 i 位【X】派活」,并对返回的 dispatched 做「必须命中链条下一席位」的校验/纠偏。后端路由、SOUL、表结构都不动。

→ chain 模式 = 在 useStep2Orchestration 里新增 runChainOrchestration:按链条游标逐位调 leader(约束派给该位)→ 拿到 task → 调 special → 下一位 → 链尾后调一次 leader 收口。详见 §6。

与「前端绕过 leader 直接顺序 special」的区别:本方案保留总控在每一步的协调/串接职责(leader 结合前序进展为下一席位编写针对性任务、并在派活说明里承上启下),更符合「总控按链条顺序派活」的语义,圆桌可视化里总控气泡也照常出现。


3. 数据模型与类型

新增前端类型,建议放 frontend-web/src/roundtable-planning/lib/business-chain.ts:

/** 业务链条里的一个有序席位。 */
export interface ChainSeat {
  agent_id: string;
  /** 落库时的快照名/描述,避免 agent 改名后链条显示错乱;展示优先用实时 agent 数据,回退到快照。 */
  name: string;
  description?: string;
}

/** 一条可复用的业务链条。 */
export interface BusinessChain {
  id: string;
  title: string;
  description?: string;
  /** 有序席位列表;seats[0] 是第一个发言者。 */
  seats: ChainSeat[];
  createdAt?: string;
  updatedAt?: string;
}

/** 推荐弹窗确认后回传给主页面的统一结果。 */
export interface SeatSelectionResult {
  /** 有序席位(与现有 handleConfirmAgents 入参兼容)。 */
  agents: Array<{ agent_id: string; name: string }>;
  /** 决定 Step 2 走哪种编排。 */
  mode: "recommend" | "chain";
  /** chain 模式下带上来源链条(用于草稿快照 / Step 2 展示链名)。 */
  chain?: Pick<BusinessChain, "id" | "title">;
}

约束:链条席位数沿用圆桌的 2 ≤ N ≤ 8(MIN_SELECTED/MAX_SELECTED),配置页保存时校验。

tag 配色:复用 roundtable-constants.ts::getAvatarTypeFor(agentId, index),得到 blue|purple|emerald|amber|pink|human,再映射到已有的 avatar 配色 token(与圆桌可视化、Step 2 气泡同一套色板,视觉统一)。


4. 现有代码改动点速查

位置 改动 说明
components/RecommendAgentsDialog.tsx 包一层「模式选择」外壳,现有内容收进「智能推荐」Tab onConfirm 签名从 agents[] 升级为 SeatSelectionResult(或加可选第二参 mode,见 §7 阶段 3 的兼容策略)
pages/RoundtablePlanningPage.tsx handleConfirmAgents 接收 mode,存到新状态 orchestrationMode 透传给 useStep2Orchestration
hooks/useStep2Orchestration.ts 新增 runChainOrchestration + orchestrationMode 分支 init effect 里按 mode 选择 runOrchestration 或 runChainOrchestration
lib/roundtable-constants.ts 复用 getAvatarTypeFor / SelectedAgent 不改,仅引用
hooks/useDraftPersistence.ts + api/drafts.ts Step2 快照新增 orchestrationMode + chain 字段 草稿能记住「这次是链条模式」
pages/WorkspaceRoutes.tsx 注册 roundtable/chains 路由 仿 roundtable/planning
components/page-sidebar.tsx(如有侧栏入口) 加「业务链条」入口(可选) 也可只从推荐弹窗进
api/chains.ts(新增) 链条 CRUD 客户端 直接打后端 /api/roundtable-chains
后端 roundtable_chains 表 + 路由(MySQL) 仿 roundtable_drafts,必做

5. 链条持久化 API 设计

5.1 前端客户端 api/chains.ts(直接打后端)

listChains(): Promise<BusinessChain[]>          // 当前用户全部链条
getChain(id: string): Promise<BusinessChain>
createChain(input: Omit<BusinessChain,"id"|"createdAt"|"updatedAt">): Promise<BusinessChain>
updateChain(id: string, patch: Partial<BusinessChain>): Promise<BusinessChain>
deleteChain(id: string): Promise<void>
  • 实现用 apiFetch 打 /api/roundtable-chains(§5.2),snake_case↔camelCase 在这层收口(仿 api/drafts.ts)。
  • 不提供 localStorage 实现;后端表是前端 UI 的硬依赖。

5.2 后端表 + 路由(仿 §7A roundtable_drafts)

  • 表 roundtable_chains:id(PK) / user_id(idx) / title(512) / description(text) / seats(PortableLongText,存 ChainSeat[] JSON) / created_at / updated_at(BeijingDateTime)。
  • 路由 app/gateway/routers/roundtable_chains.py,前缀 /api/roundtable-chains:GET ``(列表)/ POST ``(建)/ GET /{id} / PUT /{id}(partial,exclude_unset)/ DELETE /{id}。
  • 鉴权/属主:_current_user_id(request),跨用户访问一律 404,与 roundtable_drafts 完全一致。
  • 启动 Base.metadata.create_all 自动建表(MySQL 生产 / sqlite 本地走同一套 SQLAlchemy 代码,PortableLongText 各自降级),登记进 persistence/models/__init__.py,store 接到 app.state 并 include_router。

整体是「复制 §7A 的草稿持久化、把字段换成 chain」的机械工作,风险低。


6. Step 2 链条模式编排设计(runChainOrchestration)

6.1 与现有 runOrchestration 的差异

维度 runOrchestration(recommend) runChainOrchestration(chain,新增)
谁派活 总控 leader 总控 leader(不变)
派给谁由谁定 leader 自由决定 前端按链条游标约束 leader 只能派给下一席位
发言顺序 leader 每轮自由派 链条数组的固定顺序
循环结构 while 直到 leader 返回 [] 共识 / MAX_CYCLES 按链条游标走一趟(每位:leader 派活 → special 交付)
收口 leader 返回 [] 即共识 链尾后再调一次 leader 做总结收口,产出 lastLeaderContent + hasConsensus

核心:leader 始终在场,只是每轮的派活目标被前端钉死成链条的下一席位——前端在 leader 的 newMessage 里写明「本轮请通过 agent_orchestration 仅向【X】派活」,并对 leaderRes.dispatched 做命中校验/纠偏。

6.2 伪代码

runChainOrchestration(seedMessage, ids, coord, chainSeats):
  isOrchestrating = true
  cancel = false
  N = chainSeats.length

  for (let i = 0; i < N; i++):           // 链条游标,逐位推进
    if cancel: break
    seat = chainSeats[i]
    seatMeta = agentNameToRole[seat.agent_id]

    // ── (a) 总控派活给「链条下一席位」──────────────────────────
    highlightSpeaker(null)               // leader 阶段不高亮任何 sub
    leaderBubble = appendStreamingDialogue("总控协调", confidence:"链条派活中")
    leaderTask = buildChainLeaderTask({
      seedMessage,                       // 任务目标/约束(首轮)
      position: i, total: N,
      nextSeat: seat,                    // 「本轮请仅向【X(seat.agent_id)】派活」
      isFirst: i === 0,
    })
    leaderRes = await streamMultiAgent(leader, ids, coord, leaderTask, model=globalModel)

    // 约束/纠偏:取 dispatched 中命中 seat.agent_id 的那条 task;
    // 若 leader 没派或派错(派给非 seat、或派给多个),前端兜底:
    //   - 命中就用它给出的 task 文本(保留总控的承上启下措辞);
    //   - 没命中则用 leader 的正文 content 作为兜底 task,或回退到模板 task。
    task = pickChainTask(leaderRes, seat) 
    // leader 若返回 clarification → 暂停链条,走与现状一致的澄清条(§6.3)
    finalizeDialogue(leaderBubble, leaderRes.content, "派活说明")

    // ── (b) 被派席位交付(special,顺序、不并行)──────────────────
    markSpeakerStreaming(seatMeta.speakerId)
    seatBubble = appendStreamingDialogue(seatMeta.displayName, confidence:"调度中")
    perSeatModel = agentConfigs[seatMeta.speakerId]?.model || globalModel
    specRes = await streamMultiAgent(special, ids, seat.agent_id, task, model=perSeatModel)
    finalizeDialogue(seatBubble, specRes.content, "子智能体交付")
    markSpeakerIdle(seatMeta.speakerId)
    // 后端 _broadcast 已把本席位交付注入其余 thread 历史 → 下一席位天然可见

  if cancel: return

  // ── (c) 总控收口:综合全链交付,产出最终结论 ──────────────────
  leaderBubble = appendStreamingDialogue("总控协调", confidence:"汇总链条结论")
  leaderRes = await streamMultiAgent(leader, ids, coord,
                "各链条席位已按顺序完成发言,请综合所有交付,输出最终方案,不要再派活。")
  finalizeDialogue(leaderBubble, leaderRes.content, "全案盖印")
  setLastLeaderContent(leaderRes.content)
  setHasConsensus(true); setConsensusPercentage(100)
  isOrchestrating = false

要点:

  • 派活方仍是总控,符合「总控按链条顺序派活」的语义;圆桌可视化里每位席位前都先出现一个总控派活气泡,再出现该席位交付气泡。
  • 约束手段是 prompt + 前端校验,不动后端:buildChainLeaderTask 把「本轮只能派给【X】」写进 leader 的 newMessage;pickChainTask 对 leaderRes.dispatched 做命中校验,派错/未派时兜底,保证链条游标严格推进。
  • 复用现有三段式气泡 / 高亮 / abort 基建(appendStreamingDialogue / finalizeDialogue / markSpeakerStreaming / activeAbortersRef / orchestrationCancelRef),只是驱动顺序不同。
  • special 仍是顺序(不并行),与现状不变量一致(文件顶部注释 §1)。
  • 收口 leader 调用默认开(产出 Step 3 结论);若链条末位本身是「总结陈述」类,可关掉收口,直接拿末位交付当 lastLeaderContent。配置位 chainNeedsLeaderSummary(默认 true)。

6.3 澄清 / 挂起 / 恢复 / 干预

  • 澄清:链条中途某轮 leader 返回 clarification → 与现状一致,暂停链条、底部弹澄清条;用户回答后从当前游标继续(需记录 chainCursor)。
  • 挂起/恢复:isRoundTableRunning toggle 复用现有机制;恢复时从 chainCursor 续跑。MVP 若不做断点续跑,可先只支持「整体停止 / 从头重跑」,UI 文案说明(扩展点,§7 阶段 4 标注)。
  • 干预:可在两席位之间插入一条用户指令,由下一轮 leader 派活前消化。MVP 同上可后置。

6.4 多轮链条(扩展点,非 MVP)

单趟遍历是 MVP。若要「链条跑完后总控判断是否再来一轮」,可在收口 leader 处复用 dispatched===[]? 判定:收口 leader 若仍派活则回到链条头再跑一轮,直到共识或 MAX_CYCLES。MVP 不做,避免复杂度。

6.5 init 复用

chain 模式的 Step 2 init 完全复用 initMultiAgent:agentNames = chainSeats.map(s => s.agent_id)(按链条顺序传入,使 buildRoleTemplates 的 seat id 顺序 = 链条顺序,且后端注入 coordinator 的「可调度席位清单」也按此序)。init effect 里按 orchestrationMode 决定 init 完成后调 runOrchestration 还是 runChainOrchestration。


7. 分阶段实现计划

每阶段都可独立交付、独立验收。建议顺序:数据层 → 配置页 → 弹窗模式 → 链条选择 → Step2 编排 → 持久化兼容与收尾。

阶段 0 · 后端链条表 + 脚手架(1.5 天) ✅ 已完成

目标:打地基(含后端持久化),不含前端 UI。

  • 后端:仿 §7A roundtable_drafts 新建 roundtable_chains 表 + /api/roundtable-chains 路由(§5.2):
    • persistence/roundtable_chains/(model + sql repo + make_roundtable_chain_store),登记进 persistence/models/__init__.py。
    • app/gateway/routers/roundtable_chains.py(GET 列表 / POST / GET{id} / PUT{id} / DELETE),鉴权属主同 roundtable_drafts;席位 2–8 + 去重校验。
    • deps.py 接 app.state.roundtable_chain_store,app.py include_router。
    • 后端测试 tests/test_roundtable_chains.py:CRUD + 部分更新 + 跨用户隔离(5 用例全过)。
  • 前端:新增 lib/business-chain.ts(ChainSeat / BusinessChain / SeatSelectionResult,§3)+ api/chains.ts(直接打后端,§5.1)。
  • (决策 C)安装 @dnd-kit/core @dnd-kit/sortable @dnd-kit/utilities。
  • 验收:✅ 后端 5 用例通过;✅ 表注册进 Base.metadata、路由 /api/roundtable-chains 加载正常;✅ ruff 0 错;✅ 前端 typecheck 新增文件 0 错。

阶段 1 · 业务链条配置页面(2–3 天) ✅ 已完成

目标:能创建/编辑/保存/删除链条。

  • 路由:WorkspaceRoutes.tsx 注册 roundtable/chains(套 WorkspaceLayout;本页不需要 ChatRuntime,无沙箱依赖)。
  • 页面 roundtable-planning/pages/BusinessChainEditorPage.tsx:
    • 顶部:链条列表下拉(<select> 列出已存链条 + 新建项)/ 新建按钮 / 标题+描述输入 / 保存 / 删除(window.confirm)。
    • 左栏:候选智能体(listAgents() + filterRecommendCandidates),竖排卡片,可拖、可点「+」快捷加入;已加入的置灰「已加入」。
    • 右栏:有序长条卡片(序号 tag + 名称 + 描述 + 移除),SortableContext 内拖拽排序;空态有占位提示。
    • 拖拽用 @dnd-kit:左栏 useDraggable,右栏 useDroppable + SortableContext/useSortable,onDragEnd 计算插入 index(pool→右栏按落点插入;seat→seat arrayMove);DragOverlay 跟手卡片。
    • 校验:2 ≤ seats ≤ 8(前端禁用保存 + toast,后端 _validate_seats 兜底),重复 agent 去重提示。
  • 保存走 api/chains.ts(create 用 nanoid() 客户端 id,update 走同一 id)。
  • 验收:✅ typecheck 0 新增错误(76 个预存量错误均在无关模块)。人工验收待跑:新建「情报→方案→风险→总结」链保存 → 刷新后下拉重载 → 右栏拖拽排序 → 左栏拖入指定位 → 2–8 边界禁用保存。
  • 切换成本备注:若改全屏 Dialog 方案,编辑器组件原样复用,仅去掉路由、外面套 Dialog。
  • 入口说明:圆桌本身未在侧栏(sidebar-menu.ts 入口被注释),本页暂经直链 /page/workspace/roundtable/chains 或顶栏「返回圆桌」往返;阶段 3 推荐弹窗「去配置」按钮会导航到此。
  • 视觉(后续重画):本页不套圆桌 roundtable-planning scope,直接遵循《浅红色/深蓝色 UI 规范》——用 app 语义 token(光 --primary 红 hue27 / 暗 蓝 hue259,已内置自适应)+ 规范小圆角(输入/按钮/tag/卡片内项 2px = R_CTRL,分栏/面板 4px = R_CARD)+ 克制阴影(PANEL_SHADOW)。筛选框复用通用 TagSearchBar,配色随 app 中性 token,不再被红 scope 染色。

阶段 2 · 推荐弹窗「模式选择」外壳(1 天) ✅ 已完成

目标:弹窗顶部模式切换,智能推荐行为不变。

  • RecommendAgentsDialog 顶部加分段按钮模式切换:智能推荐 | 业务链条(active 态品牌色 + 卡底;DialogDescription 随模式切换文案)。
  • 现有全部内容原样收进「智能推荐」面板({mode === "recommend" && (<>…</>)} 包裹,零行为变更)。
  • 「业务链条」面板放占位:居中提示 + 「去配置业务链条」按钮(handleGoConfig → 关弹窗 + 跳 roundtable/chains)。
  • onConfirm 统一升级为回传 SeatSelectionResult(含 mode):智能推荐确认回传 { agents, mode: "recommend" };主页面 handleConfirmAgents 同步改签名取 result.agents(result.mode 预留给阶段 4)。
  • 验收:✅ typecheck 0 新增错误(总数仍 76);智能推荐分支 JSX/逻辑原样保留(流式/勾选/进入研讨未改);模式切换只切显示、state/selectedIds 不卸载 → 候选池不丢。人工验收待跑:切到业务链条→再切回,推荐结果与勾选仍在。

阶段 3 · 业务链条选择卡片列表(1.5 天) ✅ 已完成

目标:弹窗「业务链条」面板里选链条。新增 components/BusinessChainPicker.tsx,替换阶段 2 的占位。

  • 进入该 Tab 时 listChains() 拉本人链条 + listAgents() 取存活 id/名称,渲染卡片列表:
    • 卡片标题(链条 title)+ 「N 席」徽章 + 选中 ✓;描述行。
    • 标题下一排智能体名 tag:按链条顺序从左到右、不同颜色(getAvatarTypeFor→chainTagClasses),tag 之间插 →(ArrowRightIcon)箭头连接(seats[0] 最左、先发言)。
    • 底部「配置链条」按钮(跳 roundtable/chains)。
  • 空态:无链条时引导「去配置页面创建第一条业务链条」+ 按钮。
  • 选中一条 → 高亮(ring-primary);底部「进入研讨」onConfirm({ agents: 存活席位有序, mode:"chain", chain:{id,title} })。
  • 链条里有 agent 已不存在(被删)→ 该 tag 置灰删除线 + title 提示 + 卡片下方 amber 警告;confirm 按存活席位过滤;存活 < CHAIN_MIN_SEATS 则禁用「进入研讨」并提示。agent 列表加载失败时 existingIds=null → 不误判删除。
  • 验收:✅ typecheck 0 新增错误(总数仍 76)。卡片渲染标题 + 彩色 tag + ← 箭头;选链 → onConfirm(mode:"chain") → handleConfirmAgents 按链条顺序 setSelectedAgents → Step 2 左侧「参与角色」顺序 = 链条顺序(按顺序派活在阶段 4)。人工 UI 验收待跑。
  • 箭头方向(已确认):tag 左→右排列、→(ArrowRightIcon)连接,seats[0] 最左、先发言,箭头顺读即链条研讨顺序。

阶段 4 · Step 2 链条顺序编排(2–3 天) ✅ 已完成

目标:chain 模式按链顺序逐席发言。后端零改动。

  • 主页面新增 orchestrationMode 状态,handleConfirmAgents 从 SeatSelectionResult.mode 写入(chain 模式 toast 带链名);handleStartNewTask 复位为 recommend;透传给 useStep2Orchestration。chain 顺序 = selectedAgents(确认时已按链条顺序写入),无需单独 chainSeats。
  • useStep2Orchestration 新增 runChainOrchestration(§6.2)+ orchestrationModeRef/selectedAgentsRef;复用现有三段式气泡/高亮/abort 基建。
  • 新增 runActiveOrchestration 分发器:init / 恢复 / 干预 / 澄清回复 4 处调用点统一改走它,按 orchestrationModeRef 选 runOrchestration(recommend)或 runChainOrchestration(chain)。
  • runChainOrchestration:按 selectedAgents 游标,每位先 leader 约束派活(buildChainLeaderTask 钉死目标席位 + pickChainTask 命中校验/纠偏,派错/未派都兜底推进)→ 再对该席位跑 special;中途 leader 若 clarification 则暂停(MVP 回复后整链重跑)。
  • initMultiAgent 的 agentNames 即 roundtableAgentIds(由 selectedAgents 派生,已是链条顺序,§6.5)。
  • 收口 leader(finishedAllSeats 时触发),产出 lastLeaderContent + hasConsensus=true + consensusPercentage=100 解锁 Step 3。
  • 验收:✅ typecheck 0 新增错误(总数仍 76);✅ 唯一直接引用 runOrchestration 的是 dispatcher。逻辑层:每席「总控派活气泡 → 席位交付气泡」、严格顺序、单 special 在跑、_broadcast 注入前序上下文、leader 纠偏推进、跑完解锁 Step 3、abort 可中断。人工 UI 验收待跑。
  • MVP 边界:chain 模式的「挂起→断点续跑」「逐席间干预」未做断点续跑,clarification/恢复/干预当前为整链重跑(chainCursor 列为扩展点)。

阶段 5 · 草稿持久化兼容 + 收尾(1 天) ✅ 已完成

目标:草稿记住模式,回归不破。

  • Step2Snapshot(useStep2Orchestration.ts)+ DraftStep2Snapshot(api/drafts.ts)新增 orchestrationMode?: "recommend"|"chain" 与 chain?: {id,title}|null。
  • 恢复路径:orchestrationMode/chainMeta 是主页面 state,getStep2Snapshot 写入;包裹版 hydrateStep2 在加载时 setOrchestrationMode(snapshot.orchestrationMode ?? "recommend") + setChainMeta(snapshot.chain ?? null) 再调 step2.hydrateFromDraft;老草稿无字段 → 默认 recommend(向后兼容)。handleStartNewTask 复位两者。
  • 文档回写:multi-agent-frontend-dev.md §5.7(快照形状 + orchestrationMode/chain 说明)、§6.2(席位来源选择器导语);multi-agent-backend-dev.md 新增 §7B(roundtable_chains 表/路由)。
  • 验收:✅ pnpm typecheck 0 新增错误(总数仍 76)。逻辑:chain 会话保存草稿 → 重载 orchestrationMode="chain" + 链信息恢复;老草稿(无字段)默认 recommend 照常加载。人工 UI 验收 + pnpm build 待跑。

工期粗估

阶段 估时
0 后端链条表 + 脚手架 1.5d
1 配置页 2–3d
2 弹窗模式壳 1d
3 链条选择卡片 1.5d
4 Step2 链编排 2–3d
5 持久化兼容收尾 1d
合计 约 9–11 人日(含后端 roundtable_chains 表/路由)

8. 验收清单(端到端)

  1. 推荐弹窗顶部有「智能推荐 / 业务链条」切换;智能推荐与现状逐像素一致。
  2. 配置页 /page/workspace/roundtable/chains 左栏列出智能体、右栏有序长条卡片;左→右拖入指定位、右栏内拖拽排序均生效;2–8 校验生效;保存/重载/删除正常。
  3. 业务链条卡片:标题 + 彩色智能体 tag + ← 箭头连接,渲染正确。
  4. 选定链条 → Step 2 席位顺序 = 链条顺序,并按顺序逐个发言,后序能看到前序交付。
  5. chain 跑完解锁 Step 3;草稿记住 chain 模式并可重载。
  6. 删除链条中引用的 agent 后,选择端有降级提示,不崩。

9. 风险与开放问题

# 项 说明 / 建议
1 配置页:弹窗 vs 页面 本文档定为独立页面(§2.1)。若产品要弹窗,编辑器组件可复用,仅换外壳。请确认。
2 链条持久化 已定:后端 MySQL roundtable_chains(§2.2 / §5.2),仿草稿持久化。
3 Step 2 链编排 已定:总控按链条顺序派活(§2.4 / §6),后端零改动。
4 新增 @dnd-kit 依赖 若禁止新增依赖,退原生 HTML5 DnD(手感与可达性下降)。请确认。
5 ← 箭头方向 / tag 阅读顺序 默认 seats[0] 在最左、先发言;箭头仅作视觉连接。请确认是否要「从右往左读」。
6 chain 模式是否需要收口 leader 总结 默认需要(产出 Step 3 结论)。若末位席位自带总结可关掉。
7 leader 派活纠偏策略 leader 派错/未派时前端兜底推进游标(§6.2 pickChainTask);可加 toast 提示「已按链条强制推进」。
8 chain 模式断点续跑 / 逐席干预 MVP 不做,列为扩展点(§6.3 / §7 阶段 4)。
9 链条是否私有 本期仅本人私有(仿草稿按 user_id 隔离)。共享/市场留扩展。

10. 路径速查(新增/改动)

资源 路径 状态
链条类型 frontend-web/src/roundtable-planning/lib/business-chain.ts 新增
链条 API 客户端 frontend-web/src/roundtable-planning/api/chains.ts 新增
配置页 frontend-web/src/roundtable-planning/pages/BusinessChainEditorPage.tsx 新增
链条选择面板 frontend-web/src/roundtable-planning/components/BusinessChainPicker.tsx 新增
链条卡片 frontend-web/src/roundtable-planning/components/BusinessChainCard.tsx 新增
推荐弹窗(加模式壳) frontend-web/src/roundtable-planning/components/RecommendAgentsDialog.tsx 改
主页面(mode 透传) frontend-web/src/roundtable-planning/pages/RoundtablePlanningPage.tsx 改
Step2 编排(加 chain) frontend-web/src/roundtable-planning/hooks/useStep2Orchestration.ts 改
草稿快照(加 mode) hooks/useDraftPersistence.ts + api/drafts.ts 改
路由注册 frontend-web/src/pages/WorkspaceRoutes.tsx 改
后端链条持久化 offline-backend-20260512/backend/packages/harness/deerflow/persistence/roundtable_chains/ 新增
后端链条路由 offline-backend-20260512/backend/app/gateway/routers/roundtable_chains.py 新增
后端 store 接线 offline-backend-20260512/backend/app/gateway/deps.py(app.state.roundtable_chain_store)+ app.py 改