将岗位职责从智能体配置迁移到业务链席位 JSON,会商面板只读展示;WeKnora 嵌入 iframe 支持在授权可见范围内切换知识库并重新签发会话。 Co-authored-by: Cursor <cursoragent@cursor.com>
岗位多智能体会商页面开发说明
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 对接
- 进入页面时将业务侧任务的稳定主键原样放入 URL。工作区入口使用
/#/page/workspace/roundtable/position?taskId=<taskId>;策略页入口也兼容/#/page/strategy/qa/position-roundtable?taskId=<taskId>。不要使用任务标题、序号或每次请求都变化的临时 ID。 - 前端先调用
GET /api/position-roundtable/sessions?external_task_id=<taskId>;返回的是该任务下所有用户共享的会商记录,按最近更新时间排序。选中一条后再调用GET /sessions/{sessionId}恢复完整节点和聊天快照。 - 没有可恢复记录时,调用
POST /sessions,至少传入{ external_task_id: taskId, task_snapshot: { ...任务展示字段 } }。服务端将task_scoped设为true,并固定快照中的id/externalTaskId为传入的 taskId。 - 后续所有意图、链条、节点、收口智能体和历史删除 API 只使用返回的
sessionId;不要在每次消息请求中重新创建会话。一个 taskId 可以有多条 sessionId。 - 不传
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 启动前必须同时满足:
- 选择了业务链;
- 业务链至少有一个席位;
- 每个席位已配置智能体和岗位;
intent.intentReady存在结构化意图结果。
启动成功后会调用:
createPositionSession(首次启动时);savePositionIntent;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。
自动意图线程的时序约束:
- 外部页面只传
taskId时,先完成历史发现或创建 SQL 会话行; - 是否初始化智能体只看真实
intentThreadId,不能用 URL 是否已有sessionId代替; initIntent返回真实线程前,不保存“无线程欢迎语快照”;- 绑定线程后立即保存线程 ID,再合并数据库中的恢复消息和草稿。
这四步用于避免新建会话的恢复 effect 抢先覆盖并取消正在进行的线程初始化。
9. 开发与排查清单
- 新增岗位时:通过「设置和更多 → 岗位角色配置」(管理员)保存到
/api/position-roles;POSITION_OPTIONS仅作为接口暂不可用时的前端回退。业务链席位的position_id必须使用稳定 ID,不能使用显示名称。 - 新增收口智能体时:同时补充前端
PositionBuiltinAgentKind、后端 prepare/complete API、解锁条件和 UI 切换项。 - 变更节点完成条件时:前后端必须一致。当前普通节点的完成条件是 Markdown 交付物,而不是“有一条回答”。
- 改预览逻辑时:始终通过父页面的
onPreviewArtifact/closeSandbox入口;不要在子组件里新增沙箱状态。 - 变更输入框视觉时:只改对应的岗位专用输入组件,不要向全局
InputBox添加岗位样式。 - 验证最少覆盖:新建会话、刷新恢复、切岗位、单节点多智能体切换、生成 Markdown、关闭后再次对话、历史会话、方案总结、行动规划。
10. 构建验证
cd frontend-web
pnpm build
提交前还应执行:
git diff --check
11. 岗位角色配置
岗位角色配置只管理岗位会商页面的工作视角,不等同于组织岗位或权限。后端启动会幂等补齐
intelligence、planning、solution、evaluation 四个内置岗位;已保存的名称、说明、顺序、
启停和性质不会被种子覆盖。一个启用的“意图识别岗(特殊)”是必需的,负责第一阶段意图理解并
显示方案总结、行动规划两个收口智能体。内置岗位为兼容历史业务链而不可删除,但可以修改或停用;
自定义岗位可以新增、排序和删除。