docs/architecture/team-mode-performance.md
排查日期:2026-04-09 分支:
fix/team-solo-perf排查人:老锤(主进程)、小快(前端)、郭总(根因定位) 经三轮深度代码核实,原始 20 个问题中 14 个证实为合理设计或不存在,以下为确认的 7 个真实问题(含新发现的根因)。
| # | 优先级 | 问题 | 代码证据 | 原理 | 合理性 | 复现方式 | 如何观测 | 用户感知 |
|---|---|---|---|---|---|---|---|---|
| 0 | P0 | 侧边栏 responseStream 监听导致无限 SQLite 刷新(根因) | useConversationListSync.ts:96-99 过滤掉 team 对话后构建 conversationIdsState;:178-199 全局 responseStream 监听每个 chunk 检查 conversationIdsState.has(id),未命中则调用 refreshConversations()(全量 SQLite 查询 pageSize=10000) | team 对话 ID 因 extra.teamId 被过滤出 filteredData,导致 conversationIdsState 永远不包含 team 对话 ID。ipcBridge.ts:437 中 acpConversation.responseStream === conversation.responseStream(同引用),agent streaming 的每个 chunk 都命中 !has() 分支,触发全量 DB 查询→IPC 往返→React re-render | 不合理,team 对话 ID 应纳入已知 ID 集合 | 开启 team 模式,leader 执行任务,同时观察侧边栏 | 在 refreshConversations 入口加 console.count 或 Network tab 观察 IPC 调用频率;弱性能机器直接白屏 | 全应用冻结,侧边栏无法点击,白屏 |
| 1 | P1 | useTeamSession 订阅 messageStream 但上层未消费 | useTeamSession.ts:77-109 订阅 team.messageStream 维护 messages state;TeamPage.tsx:445 解构时丢弃 messages | 主进程 TeammateManager.ts:267 每个 streaming text chunk emit 一次 team.messageStream,前端收到后 setMessages(new Map(...)) 触发 React re-render,但 TeamPage JSX 完全不使用这个 state | 不合理,订阅存在但无消费方 | 开启 team 模式,让任意 agent 执行任务 | React DevTools Profiler 录制,观察 TeamPage 在 streaming 期间每个 chunk 都 re-render;或在 setMessages 前加 console.count('messageStream setState') 观察调用频率 | 界面整体卡顿,滚动不流畅 |
| 2 | P1 | AgentChatSlot 无 React.memo | TeamPage.tsx:56-181 AgentChatSlot 是普通函数组件,无 memo 包裹 | 任意 agent 状态变化(active/idle 切换)→ 父组件 re-render → 所有 AgentChatSlot 跟着 re-render,即使该 slot 对应的 agent 状态没变 | 不合理,N 个 slot 应独立更新 | 开启 team 模式,3+ 个 agent,其中一个执行任务 | React DevTools Profiler 高亮更新:观察不相关的 slot 是否跟着闪烁;或在 AgentChatSlot 函数体首行加 console.count('slot-' + agent.slotId + ' render') | 不相关聊天窗口也卡 |
| 3 | P1 | useAutoScroll 每 chunk 触发 DOM forced layout | useAutoScroll.ts:259 useEffect 依赖 messages,每条 chunk 触发 el.scrollHeight 读(forced layout)+ el.scrollTop 写 | streaming 期间 messages 引用每条 chunk 都变,effect 每次都跑。N 个 agent 并发时每秒数十次 forced layout | 不合理,依赖应为 messages.length 或用 RAF 合并 | 多 agent 同时流式输出 | Chrome DevTools Performance 录制,查看 Layout 事件频率;筛选 "Forced reflow" 警告 | 页面滚动掉帧 |
| 4 | P2 | TeamTabsContext Provider value 无 useMemo | TeamTabsContext.tsx:82-88 Provider value 是裸对象字面量 { agents, statusMap, ... } | 父组件每次 render 产生新对象引用 → 所有 useTeamTabs() 消费者触发 re-render,即使内容没变 | 不合理,Provider value 应 memo | 任何导致 TeamTabsContext 父组件 re-render 的操作 | React DevTools Profiler 观察 useTeamTabs 消费者的 render 原因是否为 "context changed" | 整体渲染性能下降 |
| 5 | P2 | getOrStartSession 无并发锁 | TeamSessionService.ts:506-533:sessions.get 到 sessions.set 之间有 1 个 await(repo.findById,SQLite <1ms) | 窗口极窄但理论上存在:两个并发调用各创建一个 TeamSession + MCP server,第二个覆盖第一个,第一个的 TCP server 引用丢失 | 可接受但不完美,加 Promise 锁是防御性编程 | 极难复现(窗口 <1ms),可通过在 repo.findById 前加 await new Promise(r => setTimeout(r, 100)) 人为拉宽窗口验证 | 加日志:在 sessions.get miss 时打印 console.log('[getOrStartSession] cache miss', teamId, Date.now()),短时间内出现两次 miss 即为复现 | 偶发性 agent MCP 工具调用失败 |
| # | 优先级 | 问题 | 代码证据 | 原理 | 合理性 | 复现方式 | 如何观测 | 用户感知 |
|---|---|---|---|---|---|---|---|---|
| 6 | P2 | mailbox 已读消息永不清理 | 全局搜索无 DELETE FROM mailbox WHERE read=1 逻辑;writeMessage 只 INSERT,readUnreadAndMark 标 read=1 不删,deleteMailboxByTeam 只在团队删除时触发 | 已读消息持久留存,表无限增长。活跃 team 运行几天可积累数千条 | 不合理,应有 TTL 清理 | 使用 team 模式数天后检查 DB | sqlite3 <db_path> "SELECT count(*) FROM mailbox WHERE read=1" 观察已读消息数量 | 长期后应用体积增大 |
以下 14 个问题在首轮排查中被列出,经第二轮逐行代码核实后确认不构成实际问题:
| 原编号 | 问题 | 排除原因 |
|---|---|---|
| 原2 | wake 跳过不排队 | 设计决策:activeWakes 锁窗口仅 "wake 开始→消息发出" 几毫秒,消息持久化在 mailbox,不丢失 |
| 原5 | N 个 agent 各挂 3+ listener | listener 数量准确但各走独立路径,不存在重复接收 |
| 原7 | setTimeout 自循环永不停 | 经核实 pending 为空时自然停止,不是无限循环 |
| 原8 | agent 冷启动 skipCache | skipCache 只在 session 初始化调一次,wake 路径走正常缓存 |
| 原9 | PetIdleTicker 50ms | 故意设计:working 状态需感知鼠标移动以唤醒;native API <0.01ms/次 |
| 原10 | 每 chunk 双重 IPC emit | teamEventBus 是主进程内部 EventEmitter,不路由到渲染进程,双路各有用途 |
| 原11 | allConversationIds memo 失效 | 只在 agent 增删时发生,非高频操作 |
| 原14 | SWR revalidateOnFocus | 全局 key 去重,N 个组件共享 1 个请求 |
| 原15 | ThoughtDisplay 1s timer | 合理 UX 设计,每秒 1 次不算高频 |
| 原16 | handleResponseStream O(N) 查找 | N = team agent 数(2-10),纳秒级,无可测量影响 |
| 原17 | taskManager.list 全量查询 | 业务必要(agent prompt 需完整任务上下文),有 team_id 条件 |
| 原18 | SqliteTeamRepository read-then-write | JSON blob 存储必要代价,两次操作合计 <0.2ms |
| 原19 | 每 agent 加载 10000 消息 | 现阶段对话短,影响可忽略 |
| 原20 | finalizeTurn 每轮写 idle_notification | 协议必要,有互斥保护防双写 |
| # | 状态 | 修复分支/PR | 备注 |
|---|---|---|---|
| 0 | ✅ 已修复 | fix/team-solo-perf PR #2265 | useConversationListSync.ts:101 改用 data.map 构建 conversationIdsState,包含 team 对话 ID,阻断无限刷新 |
| 1 | ✅ 已修复 | fix/team-solo-perf PR #2265 | 删除 useTeamSession 的 messages 订阅 + 主进程 TeammateManager 的 messageStream emit + 完整 IPC 链路清理 |
| 4 | ✅ 已修复 | fix/team-solo-perf PR #2265 | TeamTabsContext.tsx Provider value 加 useMemo |
| 2 | 待修复 | - | AgentChatSlot 加 React.memo |
| 3 | 待修复 | - | autoScroll 依赖改为 messages.length 或 RAF |
| 5 | 待修复 | - | 加 inflight Promise Map |
| 6 | 待修复 | - | 加 mailbox 已读消息定期清理 |