31 KiB
业务链条模式 · 需求与分步实现文档
面向「智能体推荐弹窗」的扩展:在现有智能推荐之外,新增业务链条选择与业务链条配置两块能力;选定业务链条后,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)。 - 挂起/恢复:
isRoundTableRunningtoggle 复用现有机制;恢复时从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.pyinclude_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→seatarrayMove);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-planningscope,直接遵循《浅红色/深蓝色 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 typecheck0 新增错误(总数仍 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. 验收清单(端到端)
- 推荐弹窗顶部有「智能推荐 / 业务链条」切换;智能推荐与现状逐像素一致。
- 配置页
/page/workspace/roundtable/chains左栏列出智能体、右栏有序长条卡片;左→右拖入指定位、右栏内拖拽排序均生效;2–8 校验生效;保存/重载/删除正常。 - 业务链条卡片:标题 + 彩色智能体 tag + ← 箭头连接,渲染正确。
- 选定链条 → Step 2 席位顺序 = 链条顺序,并按顺序逐个发言,后序能看到前序交付。
- chain 跑完解锁 Step 3;草稿记住 chain 模式并可重载。
- 删除链条中引用的 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 |
改 |