Files
deerflow-code/frontend-web/src/position-roundtable

岗位多智能体会商页面开发说明

1. 页面定位

岗位多智能体会商将一个业务链条拆成多个“岗位视角”的单智能体问答:每个席位仍使用既有的 LangGraph 对话线程完成流式生成,岗位会商服务只负责保存任务快照、冻结后的链条、节点状态与线程索引。

因此它不是新的多智能体调度图,也不创建后台会商任务。页面的核心约束是:

  • 一条岗位会商会话对应一个任务和一份冻结的业务链配置;
  • 一个业务链席位对应一个 PositionRoundtableNode 和一条直接对话线程;
  • 节点写出 Markdown 交付物后才能被视为完成并解锁后序;
  • 所有岗位交付完成后,测试分析岗可以依次进入“方案总结”和“行动规划”两个收口智能体;
  • 页面中的文件预览沙箱只有一个实例,不会为多个节点重复打开或同时流式渲染。

前端入口位于 src/pages/StrategyRoutes.tsx:qa/position-roundtable。

2. URL 与会话恢复

推荐访问地址:

/#/page/strategy/qa/position-roundtable?taskId=<任务ID>&sessionId=<会商会话ID>&positionId=<岗位ID>
参数 是否必填 作用
taskId 否 外部任务 ID。传入后它是岗位会商的共享存储作用域;缺失时保持原有的个人会话逻辑。mock 仅提供任务卡片的显示兜底,绝不替换路由传入的 ID。
sessionId 否 直接恢复指定岗位会商会话;缺失时页面会按 taskId 查找最近一条未归档会话。
positionId 否 初始打开的岗位,同时同步顶部岗位选择器。无效值回退到 intelligence。
new=1 否 强制不恢复历史会话,以新会话开始。

传入 taskId 时,同一个任务下可以有多条会商会话,所有用户共享同一份历史、节点快照与 LangGraph 线程;当前 user_id 仅保留创建审计,不参与这些任务会话的读写过滤。页面恢复 sessionId 时会核验其 externalTaskId 与路由 taskId 一致,不一致会丢弃该会话并按当前任务重新发现历史。未传 taskId 时,会继续只读取当前用户的原有个人会话。

外部系统按 taskId 对接

  1. 进入页面时将业务侧任务的稳定主键原样放入 URL。工作区入口使用 /#/page/workspace/roundtable/position?taskId=<taskId>;策略页入口也兼容 /#/page/strategy/qa/position-roundtable?taskId=<taskId>。不要使用任务标题、序号或每次请求都变化的临时 ID。
  2. 前端先调用 GET /api/position-roundtable/sessions?external_task_id=<taskId>;返回的是该任务下所有用户共享的会商记录,按最近更新时间排序。选中一条后再调用 GET /sessions/{sessionId} 恢复完整节点和聊天快照。
  3. 没有可恢复记录时,调用 POST /sessions,至少传入 { external_task_id: taskId, task_snapshot: { ...任务展示字段 } }。服务端将 task_scoped 设为 true,并固定快照中的 id / externalTaskId 为传入的 taskId。
  4. 后续所有意图、链条、节点、收口智能体和历史删除 API 只使用返回的 sessionId;不要在每次消息请求中重新创建会话。一个 taskId 可以有多条 sessionId。
  5. 不传 taskId(同时省略 external_task_id)就是兼容模式:后端只返回和操作当前用户自己的个人会话,绝不会把历史记录升级为共享。响应中的 task_scoped 可用来区分两种记录。

服务端从请求中的非空 external_task_id 判断是否开启共享,调用方无需也不应自行传 task_scoped。

当前岗位 ID 定义在 lib/positions.ts:

ID 页面岗位 职责
intelligence 测试分析岗 完成任务意图识别、业务链确认,以及收口智能体入口。
planning 组织测试岗 承接上游信息并完成组织侧分析。
solution 方案设计岗 产出方案侧 Markdown 交付物。
evaluation 综合测试岗 汇总评估并给出综合建议。

切换岗位时,PositionRoundtablePage 会关闭当前沙箱、清空当前节点/收口智能体选择,并写回 positionId。不要在子组件中直接修改 URL,否则容易留下上一岗位的预览状态。

3. 页面结构与职责

PositionRoundtablePage
├─ 左侧:任务分析
│  ├─ 任务卡片(名称、简介、来源、下发时间)
│  ├─ 意图分析
│  └─ 内嵌业务链流程图
├─ 中间:当前岗位的对话区
│  ├─ 测试分析岗:Step1Panel + PositionIntentInput
│  ├─ 普通席位:PositionNodeChat + PositionAgentInput
│  └─ 收口阶段:PositionBuiltinAgentChat + PositionAgentInput
├─ 右侧:智能体配置 / 交付产物抽屉
└─ 文件预览:唯一的 ArtifactFileDetail 沙箱实例

主要文件:

文件 责任
pages/PositionRoundtablePage.tsx 页面状态机、URL 同步、任务与会话恢复、岗位/节点切换、唯一沙箱预览。
api/sessions.ts 岗位会商 API 的前端类型、snake_case/camelCase 映射和请求函数。
components/PositionNodeChat.tsx 一个普通业务链节点的一对一对话、线程绑定、上游上下文注入与交付完成回写。
components/PositionBuiltinAgentChat.tsx 方案总结、行动规划的直接对话和“准备 → 线程就绪 → 发送 → 归档”流程。
components/PositionIntentInput.tsx 仅测试分析岗使用的意图输入框。样式、工具栏和按钮均与其他页面隔离。
components/PositionAgentInput.tsx 普通节点与收口智能体共用的岗位专用输入框边界。
components/PositionAgentSwitcher.tsx 测试分析岗中两个收口智能体的全名切换按钮。
lib/positions.ts 前端岗位目录和稳定岗位 ID。

4. 状态流转

意图识别
  └─ 确认意图并启动业务链
       ├─ 冻结 taskSnapshot / intentSnapshot / chainSnapshot
       ├─ 按业务链席位创建节点
       └─ intelligence 岗节点自动标记完成

普通业务链节点
  └─ 直接问答并生成 Markdown
       ├─ 写入 LangGraph 线程
       ├─ 回写 node.threadId
       ├─ 保存 latestAnswer 与 artifactManifest
       └─ Markdown 交付成功后解锁后续阶段

全部普通节点已交付
  └─ 方案总结(测试分析岗)
       └─ 行动规划(测试分析岗)

4.1 意图识别与业务链启动

测试分析岗复用 Step1Panel 的消息和状态逻辑;页面通过 inputArea 插槽替换为 PositionIntentInput,因此不会改动普通会商 Step 1 的输入样式。

handleActivateChain 启动前必须同时满足:

  1. 选择了业务链;
  2. 业务链至少有一个席位;
  3. 每个席位已配置智能体和岗位;
  4. intent.intentReady 存在结构化意图结果。

启动成功后会调用:

  1. createPositionSession(首次启动时);
  2. savePositionIntent;
  3. activatePositionSession。

后端会冻结链条快照并根据链条阶段创建节点。已启动后意图不可再修改;需要重新识别时必须新建会话。

The position workspace selects the dedicated built-in position-roundtable-intent agent by sending intent_agent_id through the shared Step-1 init and stream requests. The thread lifecycle, file upload, and SSE transport stay identical to the generic multi-agent flow. The dedicated agent permits at most five user-assistance rounds; one round may contain multiple independent ask_clarification cards. Step1Panel renders every card separately and submits the selected answers together, so cards must never be merged into one numbered multi-question card. Only its final [INTENT_READY] schema changes to the following four string arrays:

{
  "coreGoals": ["..."],
  "riskWarnings": ["..."],
  "keyPoints": ["..."],
  "strategicSignificance": ["..."]
}

The position sidebar and edit dialog render these four fields. Business-chain activation requires all four arrays and at least one core goal or strategic significance. Historic objective / constraints / assumptions snapshots are read through a compatibility mapping only; ordinary multi-agent Step 1 is not changed.

4.2 普通节点问答

PositionNodeChat 在发送前调用 getPositionNodeContext 获取受长度控制的任务、意图和已交付上游内容,再以 task_external_context 作为隐藏上下文发送给当前智能体。

首次创建线程时,onStart 会调用 bindPositionNodeThread。组件还监听 SDK 实际线程 ID,处理“线程创建后、绑定写入前刷新页面”的竞态。

流结束后,组件会读取该线程的产物列表并调用 completePositionNodeTurn。普通节点只有交付 Markdown 文件才会变为 done;单纯继续追问只会保存回答,不会误解锁下游节点。

4.3 收口智能体

收口阶段只有测试分析岗可见:

  • 方案总结:全部岗位交付完成后可开始;
  • 行动规划:方案总结完成后可开始。

PositionBuiltinAgentChat 不使用后台任务。每次发送先调用 preparePositionSummary 或 preparePositionActionPlan,从服务端得到冻结的输入提示词、来源版本和可复用线程 ID;待 useThreadStream 真正绑定到该线程后才发送首条消息。这个两步提交避免了界面先出现用户气泡、实际却没有启动流的竞态。

完成回调会调用 completePositionSummary 或 completePositionActionPlan。服务端使用 source_revisions 判断上游是否被重做,以避免将过期总结当作当前交付物。方案总结(以及行动规划的 Markdown 部分)若模型只返回了对话正文、遗漏 write_file,服务端会将该正文自动写入对应线程的 outputs/ 目录并登记为 Markdown 产物,因此用户始终同时获得聊天正文与可预览、下载的报告文件;行动规划仍必须另外生成 action-plan-subtasks.json。

5. 文件预览沙箱

页面只维护一个 sandboxArtifact。PositionNodeChat 和 PositionBuiltinAgentChat 通过 onPreviewArtifact 上报预览目标;父页面统一渲染 ArtifactFileDetail。

对接 / 重做页面 / 排障指南(打开关闭时机、步骤条、接口约定): ../../docs/position-roundtable-sandbox/沙箱打开关闭与步骤条.md (目录说明见 ../../docs/position-roundtable-sandbox/README.md)

设计要点:

  • 文件正在由 write_file 流式生成时,优先显示源码;完成后交由预览组件切换 Markdown 预览;
  • write_file 的参数是一整个 JSON 流,键顺序由模型决定。若 content 先于 path 到达,会先用临时文件名(未命名文件.md/.html,按正文首部嗅探)开沙箱——流式内容只按 message_id + tool_call_id 定位,与路径无关;真实 path 到达后沿用同一个 previewKey 改名,属于「更新」而非第二次交付;
  • 流式快照不能进页面 state。本页很大(任务面板、意图结果、流程图、节点对话),按流式频率重渲染整页会把主线程占满,连消息列表都会停止绘制直到本轮结束。快照走 createSandboxThreadStore() + SandboxThreadProvider(useSyncExternalStore),只有沙箱子树重渲染;
  • 快照的合并节流是前沿节流:距上次推送超过 160ms 就在流回调里同步推送,未满则挂一个补尾定时器。不能只靠尾随定时器——主线程繁忙时回调会被饿死,面板会一直冻在打开时的内容;
  • 用户手动关闭后,closedSandboxKeyRef 与 suppressedAutoPreviewThreadIdsRef 阻止同一线程立即把沙箱重新打开;
  • 切岗位、切节点、切收口阶段、新建会话和切换历史会话都会先调用 closeSandbox()。

不要在节点组件中直接操作全局 ArtifactsContext 或创建第二个 ArtifactFileDetail,否则会出现预览重复、关闭失效和更新深度异常。

6. 输入框隔离约定

岗位会商有两类输入框,均应只在 src/position-roundtable/components/ 内调整:

组件 使用范围 调整原则
PositionIntentInput 测试分析岗意图识别 完整自有布局;发送和阶段按钮均在同一输入框内。
PositionAgentInput 普通岗位节点、方案总结、行动规划 目前包装全局 InputBox,保留全部对话能力,同时提供 position-roundtable-agent-input 样式边界。

禁止直接修改 components/workspace/input-box 或默认 Step1Panel 的输入样式来满足岗位会商需求。这会影响普通工作区、其他业务和原始多智能体会商。

7. API 契约

前端基地址:/api/position-roundtable。LangGraph 负责实时执行;SQL 同时保存岗位会商索引、结构化状态、完整对话恢复快照和产物正文副本,避免应用或 checkpoint 卷重启后页面变空。

方法 路径 用途
GET /sessions?external_task_id=<taskId> 传入时按任务作用域查询共享会话;不传参数时查询当前用户的个人历史。
POST /sessions 创建会话并保存任务快照;传入非空 external_task_id 时服务端会将快照 ID 固定为该值并启用共享;缺失或空值时创建原有个人会话。
GET /sessions/{id} 恢复会话、节点和冻结快照。
DELETE /sessions/{id} 删除一条岗位会商历史及其节点;删除当前记录时前端会先屏蔽并发恢复请求。
PUT /sessions/{id}/task 更新未冻结的任务快照。
PUT /sessions/{id}/intent 保存意图线程 ID、结构化意图和完整意图对话快照。
POST /sessions/{id}/activate 冻结业务链并创建节点。
POST /sessions/{id}/archive 归档会话,后续仅允许查看。
POST /sessions/{id}/nodes/{nodeKey}/thread 绑定普通节点的 LangGraph 线程。
POST /sessions/{id}/nodes/{nodeKey}/context 取得当前轮隐藏交接上下文。
POST /sessions/{id}/nodes/{nodeKey}/complete-turn 保存回答、产物清单、上游版本并推进状态。
PUT /sessions/{id}/nodes/{nodeKey}/conversation 保存普通岗位节点的消息与未发送草稿。
POST /sessions/{id}/summary/* 准备、发送和完成方案总结。
POST /sessions/{id}/action-plan/* 准备、发送和完成行动规划。
PUT `/sessions/{id}/{summary action-plan}/conversation`
POST /demo/seed 按 demoKey 幂等初始化完整演示会话。

后端实现在 offline-backend-20260512/backend/app/gateway/routers/position_roundtable.py。

8. 演示数据与免登录独立模式

完整演示数据只有一份事实源:

src/position-roundtable/demo/position-roundtable-full.json

它包含任务、意图识别、业务链、全部岗位节点对话、岗位产物正文、方案总结和行动规划。修改数据结构时需要同步更新 demoKey,避免已初始化的旧 SQL 演示记录与新结构混用。

两个运行时开关用途不同:

配置 默认值 行为
VITE_POSITION_ROUNDTABLE_DEMO_MODE false 正常登录和正常后台请求;进入演示任务时先调用 /demo/seed 幂等写入数据库,失败则读取前端 JSON 只读兜底。
VITE_POSITION_ROUNDTABLE_STANDALONE_MODE false 纯前端展台模式;跳过登录并直达岗位分析页,不加载全局后台配置,不请求模型、业务链、智能体、会话或 LangGraph 接口,只读渲染同一份 JSON。

部署后可直接修改 dist/runtime-config.js(容器部署通常挂载外部 runtime-config.js),无需重新构建。独立模式会绕过鉴权,只允许在隔离的演示环境开启,生产环境必须保持 false。

自动意图线程的时序约束:

  1. 外部页面只传 taskId 时,先完成历史发现或创建 SQL 会话行;
  2. 是否初始化智能体只看真实 intentThreadId,不能用 URL 是否已有 sessionId 代替;
  3. initIntent 返回真实线程前,不保存“无线程欢迎语快照”;
  4. 绑定线程后立即保存线程 ID,再合并数据库中的恢复消息和草稿。

这四步用于避免新建会话的恢复 effect 抢先覆盖并取消正在进行的线程初始化。

9. 开发与排查清单

  1. 新增岗位时:通过「设置和更多 → 岗位角色配置」(管理员)保存到 /api/position-roles;POSITION_OPTIONS 仅作为接口暂不可用时的前端回退。业务链席位的 position_id 必须使用稳定 ID,不能使用显示名称。
  2. 新增收口智能体时:同时补充前端 PositionBuiltinAgentKind、后端 prepare/complete API、解锁条件和 UI 切换项。
  3. 变更节点完成条件时:前后端必须一致。当前普通节点的完成条件是 Markdown 交付物,而不是“有一条回答”。
  4. 改预览逻辑时:始终通过父页面的 onPreviewArtifact / closeSandbox 入口;不要在子组件里新增沙箱状态。
  5. 变更输入框视觉时:只改对应的岗位专用输入组件,不要向全局 InputBox 添加岗位样式。
  6. 验证最少覆盖:新建会话、刷新恢复、切岗位、单节点多智能体切换、生成 Markdown、关闭后再次对话、历史会话、方案总结、行动规划。

10. 构建验证

cd frontend-web
pnpm build

提交前还应执行:

git diff --check

11. 岗位角色配置

岗位角色配置只管理岗位会商页面的工作视角,不等同于组织岗位或权限。后端启动会幂等补齐 intelligence、planning、solution、evaluation 四个内置岗位;已保存的名称、说明、顺序、 启停和性质不会被种子覆盖。一个启用的“意图识别岗(特殊)”是必需的,负责第一阶段意图理解并 显示方案总结、行动规划两个收口智能体。内置岗位为兼容历史业务链而不可删除,但可以修改或停用; 自定义岗位可以新增、排序和删除。