deerflow-code/frontend-web/docs/roundtable-step2-sandbox-progress.md
2026-09-07 18:24:55 +08:00

12 KiB
Raw Permalink Blame History

圆桌 Step 2 沙箱预览 · 开发进度

最后更新:2026-05-27 关联文档:multi-agent-frontend-dev.md(总览)、multi-agent-backend-dev.md(后端)、multi-agent-api.md(接口) 对照参考:主聊天 /page/workspace/chats/:threadId(ChatBox + ArtifactsProvider + message-group.tsx)


1. 目标

在 Step 2「多智能体圆桌研讨」中,当子智能体调用 write_file / str_replace / present_files 时,行为与主聊天一致:

能力 主聊天 Step 2 目标
write_file 流式期间自动打开右侧沙箱 ✅ ✅ 虚拟 URL
步骤卡里点击文件路径打开沙箱 ✅ ✅
消息下方文件卡片(present_files)点击打开 ✅ ✅ PresentFilesRow
沙箱打开时收起 workspace 左侧栏 ✅(ArtifactsProvider.select) ✅ Step 2 左栏联动
60/40 对话 | 产物预览分栏 ✅ ChatBox ✅ Step2SandboxLayout
流式 MD 从 tool_call.args.content 预览 ✅ 虚拟 URL + stub messages ✅ seatStubMessages(双通道:onPhaseChange + onUpstreamFrame)
落盘后 HTTP 拉取真实文件 ✅ artifacts API ✅ present_files 卡片走 HTTP
草稿 reload 后历史文件可预览 ✅ ✅ seatStubMessages 一并入草稿

2. 已完成

2.1 Step 2 布局调整

项 状态 说明
推理模型下拉 ✅ 从右侧栏迁至 Step 2 顶栏「多智能体圆桌会商中心」右侧
意图理解 ✅ 移至左侧栏顶部,支持展开/收起;收起时仅显示「目标」
任务概览 ✅ 放在「人工参与」下方
右侧栏 Step 2/3 隐藏 ✅ RightSidebar 仅在 currentStep === 1 渲染
左侧栏整体收起 ✅ 宽度 56px;展开按钮在顶部;研讨中角色 ring + ping 动画
沙箱打开 → 左栏收起 ✅ Step2SandboxSidebarSync 监听 useArtifacts().open

主要文件:RoundtablePlanningPage.tsx(step2SidebarExpanded、LeftSidebar、Step2SandboxSidebarSync)

2.2 沙箱基础设施(Phase 1)

项 状态 说明
路由包 ChatRuntime ✅ WorkspaceRoutes.tsx → roundtable/planning 外包 ArtifactsProvider
60/40 分栏壳 ✅ Step2SandboxLayout.tsx,对齐 chat-box.tsx
ArtifactFileDetail / ArtifactFileList ✅ 嵌在右栏 ResizablePanel
ThreadContext stub ✅ 最小 BaseStream stub,供 useArtifactContent 工作

2.3 沙箱打开逻辑(Phase 2)

项 状态 说明
write_file 自动打开 ✅ handleArtifactToolPhase 在 tool_calling + 完整 args.path 时触发
步骤卡点击打开 ✅ MessageStepsCard → openArtifactFromStep
虚拟 URL 机制 ✅ write-file:<path>?message_id=...&tool_call_id=...(对齐主聊天)
Stub messages 注入 ✅ seatStubMessages + upsertSeatToolCall → loadArtifactContentFromToolCall
重置清理 ✅ resetStep2State 清空 artifacts / stub / deselect
present_files 步骤点击 ✅ 走真实路径 openArtifactPreview(HTTP API)

2.4 上游帧兜底注入(Phase 3)

项 状态 说明
onPhaseChange 主通道 ✅ tool_calling 阶段每帧把完整 args 喂进 stub;首次出现 path 即触发自动打开
onUpstreamFrame 兜底通道 ✅ handleUpstreamFrame 扫描 messages-tuple 帧的 tool_calls[],对 write_file/str_replace 持续 upsert args.content;覆盖 phase 已切走但 tool_calls 又回包的边界
双通道幂等 ✅ upsertSeatToolCall 按 (toolName, path) 去重,两个通道并发写入安全

2.5 present_files 文件卡(Phase 4)

项 状态 说明
气泡下方文件卡行 ✅ Step2Message → collectPresentedFiles → PresentFilesRow
卡片样式 ✅ 文件名 + 扩展名徽章(FILE_EXT_LABELS),最小宽度 30/最大 50(hover 边框亮色)
点击 → HTTP 预览 ✅ openArtifactPreview(agentThreadId, path),与步骤卡 present_files 走同一通道
席位 thread 缺失保护 ✅ 没有 agentThreadId 时 disabled + tooltip
同气泡多次 present_files ✅ filepaths 顺序去重,全部聚合到一行卡片

2.6 草稿持久化(Phase 5)

项 状态 说明
seatStubMessages 进 Step2Snapshot ✅ useStep2Orchestration.ts Step2Snapshot 新增字段
seatStubMessages 进 DraftStep2Snapshot ✅ drafts.ts 加 optional 字段,老草稿 reload 时回退到 {}
hydrateFromDraft 还原 stub ✅ 同步写 seatStubMessagesRef + state,加载草稿后点击历史 write_file 步骤可直接预览
getStep2Snapshot 透出 stub ✅ RoundtablePlanningPage.tsx 给 useDraftPersistence 的 getter 加上该字段

主要文件:

frontend-web/src/pages/WorkspaceRoutes.tsx
frontend-web/src/roundtable-planning/hooks/useStep2Orchestration.ts
frontend-web/src/roundtable-planning/components/Step2SandboxLayout.tsx
frontend-web/src/roundtable-planning/components/Step2Panel.tsx
frontend-web/src/roundtable-planning/components/MessageStepsCard.tsx
frontend-web/src/roundtable-planning/pages/RoundtablePlanningPage.tsx
frontend-web/src/roundtable-planning/utils/drafts.ts

2.7 与主聊天对齐的核心数据流

special seat SSE (streamMultiAgent)
  ├── onPhaseChange("tool_calling", "write_file", args)
  │     ↓
  │   upsertSeatToolCall(seatThreadId, "write_file", args)
  │     → seatStubMessages[seatThreadId] = [{ type:"ai", tool_calls:[{ id, name, args }] }]
  │     ↓
  │   buildWriteFileVirtualUrl(path, messageId, toolCallId)
  │     → "write-file:/mnt/user-data/outputs/foo.md?message_id=...&tool_call_id=..."
  │     ↓
  │   openSeatVirtualArtifact(seatThreadId, virtualUrl, auto=true)
  │     → selectArtifact(virtualUrl) + setArtifactsOpen(true)
  │
  └── onUpstreamFrame(frame)         ← 兜底通道
        ↓
      handleUpstreamFrame —— 持续 upsert tool_calls 里完整的 args(不打开沙箱)

Step2SandboxLayout
  ThreadContext.thread.messages = seatStubMessages[activeThreadId]
  ArtifactFileDetail(filepath=virtualUrl, threadId=seatThreadId)
    ↓
  useArtifactContent → loadArtifactContentFromToolCall
    → 从 thread.messages 读 tool_call.args.content(不发 HTTP)

为何不用真实 path 直接预览:流式期间文件可能尚未落盘,直接调 /api/threads/:id/artifacts/... 会返回 404 {"detail":"Not Found"}。主聊天同样用虚拟 URL 绕过此问题。


3. 未完成 / 待验证

3.1 功能缺口(按优先级)

优先级 项 状态 说明
P0 端到端回归 ⚠️ 待验证 虚拟 URL + 双通道 + 文件卡 + 草稿持久化全链路尚未在浏览器里跑一遍
P2 str_replace 流式编辑预览 ❌ 主聊天 applyPendingTextEdit 依赖完整 message 链;stub 未模拟 tool result / 多轮 str_replace 累加
P2 沙箱 artifacts[] 列表与虚拟 URL 混排 ❌ 虚拟 URL 不写入 setArtifacts([]);多文件切换、文件名下拉体验未完全对齐主聊天
P3 Step 3 与沙箱衔接 ❌ Step 3 仍用 HighFidelityReport + listSessionArtifacts;与 Step 2 沙箱选中态无联动
P3 文档同步 ❌ multi-agent-frontend-dev.md 仍描述旧布局(右侧栏模型选择器等),需更新 § 布局 + 沙箱章节

3.2 已知风险 / 边界

  1. 打开时机:仅在 SSE 进入 tool_calling 且 tool_calls[0].args 为完整对象时触发自动打开(multi-agent.ts 刻意忽略 tool_call_chunks 的拼接 args)。若模型只发 chunks、迟迟不发完整 tool_calls,自动打开会延迟到 args 完整那一帧——与主聊天一致。
  2. 多 write_file 同气泡:upsertSeatToolCall 按 (toolName, path) 幂等;同 path 多次 write 会更新 args,不会新建多条 stub tool_call。
  3. leader 席位:当前仅 special 流绑定 agentThreadId 并接 onUpstreamFrame;总控若直接 write_file,需单独接 thread id 与帧回调。
  4. present_files 文件大小:流式期间 backend 尚未给出真实大小;卡片只显示扩展名徽章,不显示 size。落盘后用户点开走 HTTP API 自然能看到完整内容。
  5. 调试日志:控制台 [roundtable-sandbox] 前缀;生产环境可考虑加 window.__ROUNDTABLE_SANDBOX_DEBUG__ 门控。

4. 分阶段计划(原始规划 vs 现状)

阶段 内容 状态
Phase 1 ChatRuntime + 分栏壳 + 侧栏联动 ✅ 完成
Phase 2 write_file 自动/点击打开 + 虚拟 URL 预览 ✅ 完成
Phase 3 onUpstreamFrame 注入完整 LangGraph messages ✅ 完成(双通道兜底)
Phase 4 present_files 卡片 + Step 3 文件区衔接 ⚠️ 卡片完成;Step 3 衔接遗留
Phase 5 草稿 stub 持久化 + 文档更新 ✅ stub 完成;本文档同步更新

5. 测试清单

在 /page/workspace/roundtable/planning Step 2 下逐项勾选:

  • 子智能体 write_file 进入 tool_calling 后,右侧沙箱 自动 打开(约 100ms 延迟,对齐主聊天)
  • 沙箱标题为文件名(如 sample-paper.md),不是 {"detail":"Not Found"}
  • 流式期间 MD 内容随 args.content 更新(双通道:phase 切换帧 + 兜底帧)
  • 点击 ChainOfThought 步骤「写入文件」/ 路径 chip → 手动打开同一文件
  • present_files 后气泡下方出现文件卡(带扩展名徽章),点击 → HTTP 预览
  • 沙箱打开时左侧「意图理解」栏 自动收起
  • 关闭沙箱(X)后对话区恢复全宽
  • 「新建任务」后 artifacts / 沙箱 / seatStubMessages 全部清空
  • 「保存草稿」→「加载草稿」后,点击历史 write_file 步骤可立即预览(stub 已还原)
  • 与主聊天 /page/workspace/chats/:threadId 对比:同一 write_file 行为一致

调试:DevTools Console 过滤 [roundtable-sandbox] 或 [stream:special:...]。


6. 关键代码索引

用途 路径
主聊天 write_file 虚拟 URL + 自动打开 src/components/workspace/messages/message-group.tsx
主聊天 RichFileCard(present_files) src/components/workspace/messages/message-list-item.tsx
主聊天 60/40 分栏 src/components/workspace/chats/chat-box.tsx
虚拟 URL 内容加载 src/core/artifacts/loader.ts → loadArtifactContentFromToolCall
Artifacts 上下文 src/components/workspace/artifacts/context.tsx
Step 2 编排 + 沙箱状态 + 草稿快照 src/roundtable-planning/hooks/useStep2Orchestration.ts
Step 2 沙箱布局 src/roundtable-planning/components/Step2SandboxLayout.tsx
Step 2 文件卡 + 步骤渲染 src/roundtable-planning/components/Step2Panel.tsx
SSE phase 推断 + onUpstreamFrame src/roundtable-planning/api/multi-agent.ts → setPhase
主页面侧栏 + 联动 src/roundtable-planning/pages/RoundtablePlanningPage.tsx
草稿持久化 src/roundtable-planning/utils/drafts.ts、hooks/useDraftPersistence.ts

7. 下一步建议(给接续开发者)

  1. 先跑通 P0 测试清单,确认 Phase 3/4/5 改造后无回归;重点观察双通道并发写入 stub 时 args 是否被旧帧覆盖(理论上 LangGraph 完整 tool_calls 是单调累积的,应该没问题)。
  2. Phase 4 收尾:把 Step 3 的 listSessionArtifacts / HighFidelityReport 接进沙箱选中态——Step 3 进入时复用 Step 2 的 ArtifactsProvider,让用户在 Step 3 也能预览 Step 2 留下的产物。
  3. Phase 2 边界:str_replace 多轮编辑的 stub 模拟(追加 tool result 消息让 applyPendingTextEdit 能 replay edit 序列),优先级低。
  4. 更新 multi-agent-frontend-dev.md:Step 2 布局、沙箱架构、seatStubMessages 数据流,避免与本文档重复——可在总览文档加一节「见 roundtable-step2-sandbox-progress.md」。