zero TUI 处理详细序列图

Bubble Tea 事件循环:从按键提交到 Agent 流式回传、权限/追问阻塞、回合结束与 flush 落盘

User / TTY 终端 (os.Stdin) Bubble Tea tea.Program model.Update model.go handleSubmit parseCommand runAgent goroutine (tea.Cmd) Agent Loop zeroruntime + LLM 阶段 0 · 启动 · Run() → program.Run() run.go: term.IsTerminal(stdin) — 非 TTY 直接退出码 2 RuntimeMessageSink 包装 program.Send;newModel(ctx, options) program.Run() 进入事件循环 Init() → tea.Batch(cmds…) Init 发起的异步命令: • tea.RequestWindowSize → WindowSizeMsg • tea.RequestBackgroundColor(auto 主题) • gitSweepCmd(FILES 侧栏基线)+ 模型发现 + PR watcher WindowSizeMsg{W,H} → 设置宽高/composer 宽度 View() → 渲染首帧 (alt-screen) 阶段 1 · 用户提交 · KeyPress → handleSubmit → beginRun 输入 prompt + Enter tea.KeyPressMsg (Enter) handleSubmit() parseCommand: commandEmpty → no-op /clear /model /mcp… → 本地处理无 LLM commandPrompt → launchPrompt 若 m.pending == true: queueMessage(text) 排队, 回合结束后 launchQueuedMessage beginRun(cancel): runID++, pending=true, turnStartedAt 返回 tea.Batch( runAgent(runID, runCtx, prompt, images), spinner.Tick ) spinner.TickMsg 自调度:working 状态行动画 阶段 2 · Agent 执行与流式回传 · runAgent goroutine ↔ 回调 → sink → program.Send agentOptions(含 OnText / OnToolCall* / OnPermissionRequest / OnAskUser 回调) SelfCorrect(默认开): LSP 诊断 + 按模式自动修复 /selfcorrect on 才跑测试 循环: LLM 流式 token / 工具调用 OnText(delta) sink(agentTextMsg{runID, delta}) → program.Send streamingText += delta; recordStreamingDelta; fade tick OnToolCallStart / Delta toolCallStreamStartMsg / toolCallStreamDeltaMsg streamCallDecoder.feed():实时显示 write_file/edit 正在写入的内容 agentRowMsg / agentReasoningMsg / agentUsageMsg 追加 transcriptRow;specialistStart/Progress/Complete、planUpdateMsg 同理经 sink 阻塞式交互 · 权限请求(channel 同步,goroutine 挂起等待主循环) OnPermissionRequest(req) sendPermissionRequest → permissionRequestMsg{req, decide} m.pendingPermission = {req, decide};goroutine 在 <-decisionCh 阻塞 渲染权限卡片(Allow / Deny) 按键选择 → decide(decision) decisionCh <- decision:goroutine 恢复,工具执行/拒绝 追问 ask_user(同样 channel 阻塞) OnAskUser(req) → askUserRequestMsg{req, answer};pendingAskUser 记录多问题状态 submitAskUser() → answer([]string):answerCh 解阻塞(无问题时立即以空答复自动解决) spec review 走 pendingSpecReview,同为 noBlockingModal() 判定的模态之一 阶段 3 · 回合结束 · agentResponseMsg → pending=false → 排队消息启动 循环结束 (err 或完成) return agentResponseMsg{runID, rows, usageEvents, sessionEvents, err…} agentResponseMsg 处理(runID == activeRunID): • clearStreamingToolCall(); pending=false; resetStreamingFade() • runCancel(); activeRunID=0; plan.completeRemaining()(成功时) • recordUsageEvent + appendSessionEvents(持久化会话/checkpoint) • 追加 msg.rows 到 transcript • runID != activeRunID(已取消):仅 flush session events 保 /rewind • launchQueuedMessageIfReady():有排队 prompt 则开启下一回合 阶段 4 · 每次 Update 收尾 · syncChatScroll → syncMouseCapture → settleTranscript → View Update() 尾部(每条消息后都执行): 1. updateModel(msg) 得到新 model 2. syncChatScroll() — 跟随底部/保持滚动位 3. syncMouseCapture() — 依据模态开关鼠标捕获 4. settleTranscript() — 推进 flush 前沿 5. flushedMsg ack → drainFlushQueue()(串行落盘) alt-screen: 保留在托管视图;inline: tea.Println 落入 scrollback tea.Sequence(Println(out), flushedMsg) flushedMsg → printInFlight=false,再次 drain View() → tea.View(AltScreen / BackgroundColor / MouseMode) 重绘终端帧 图例 Runtime↔UI 同步消息 同步调用 sink → program.Send 异步消息 阻塞式权限/追问 (channel) 返回 / 渲染

Bubble Tea 事件循环

  • • 单一 Update goroutine 串行处理所有 tea.Msg
  • Update 只返回新 model + tea.Cmd,永不阻塞
  • • 尾部固定链:syncScroll → syncMouse → settleTranscript
  • View() 返回 tea.View,alt-screen 全屏托管

流式回传通道

  • runAgent 在 goroutine 内跑 Agent Loop
  • • 回调 OnText/OnToolCall*RuntimeMessageSink
  • • sink → program.Send 把消息投回 Update
  • runID != activeRunID 的迟到消息被丢弃

阻塞式交互

  • • 权限/追问用 chan 让 goroutine 挂起
  • • 主循环渲染卡片,用户按键调用 decide()/answer()
  • • channel 收到值后 goroutine 恢复执行
  • ctx.Done()(取消)也会解阻塞并拒绝

回合生命周期

  • beginRun:runID++、pending=true、清理上回合状态
  • • pending 期间新 prompt 进入 queueMessage
  • agentResponseMsg 收尾并 launch 排队消息
  • • Ctrl+C 取消:延迟 quit 直到 checkpoint flush 完成

持久化与自纠错

  • • sessionEvents 落盘保 /resume/rewind
  • • 每个 mutating 工具前写 checkpoint blob
  • • SelfCorrect 默认开:LSP 诊断,按权限模式自动修
  • /selfcorrect on 才追加项目测试

启动关键点

  • Run():非 TTY 直接退出码 2(防止挂起)
  • Init() 主动请求 WindowSize,不依赖终端推送
  • • auto 主题请求背景色决定明/暗
  • • 模型发现预热 context 用量计(token 窗口)