Pi Agent 101|11|Terminal UI Runtime

Pi 的 TUI 把终端界面做成组件、diff rendering、overlay、真实光标和输入协议归一化。

Pi Agent 101|11|Terminal UI Runtime

先说人话:终端 UI 不是把文字一行行打印出来那么简单。

当模型正在流式输出、工具还在跑、用户还要继续输入、中文输入法还要显示候选框时,终端就变成了一个真正的界面系统。Pi 为此单独做了 TUI runtime。

如果你在读 OpenClaw 的 TUI 代码,先理解 Pi TUI 会轻松很多:组件、overlay、diff rendering、真实光标,这些都是上层产品界面的基础零件。

Agent 的终端 UI 如果只会不断 print 文本,很快就会失控。流式 token、工具输出、输入框、快捷键、overlay、图片、IME、窗口 resize、tmux/SSH/Windows 差异,都会让终端交互变复杂。Pi 把终端 UI 当成一个 runtime,而不是一组零散输出语句。

Terminal UI Runtime

这张图的重点是:TUI 不是 println,而是一个小型界面 runtime。模型流、工具执行、队列状态、输入框、overlay、footer、主题和光标都要在同一个终端窗口里协调。

如果没有组件化和 diff rendering,终端界面很容易变成刷屏日志。用户能不能理解 agent 正在做什么,很大程度上取决于 TUI runtime 如何消费 session events。

给基础读者的慢速版地图

终端 UI 不是简单打印文本。真正的 Agent 终端需要显示流式回答、工具进度、选择器、编辑器、状态栏、弹窗、图片和快捷键。Terminal UI Runtime 就是把终端当成一个可刷新的应用界面,而不是普通日志窗口。

Terminal UI Runtime|组件树

读图说明:这张图把“组件树”拆成五个连续位置。先不要记术语,先看每一格负责什么,以及上一格的结果怎样交给下一格。

Terminal UI Runtime|输入管线

读图说明:这张图把“输入管线”拆成五个连续位置。先不要记术语,先看每一格负责什么,以及上一格的结果怎样交给下一格。

Terminal UI Runtime|渲染管线

读图说明:这张图把“渲染管线”拆成五个连续位置。先不要记术语,先看每一格负责什么,以及上一格的结果怎样交给下一格。

Terminal UI Runtime|焦点系统

读图说明:这张图把“焦点系统”拆成五个连续位置。先不要记术语,先看每一格负责什么,以及上一格的结果怎样交给下一格。

Terminal UI Runtime|失败防护

读图说明:这张图把“失败防护”拆成五个连续位置。先不要记术语,先看每一格负责什么,以及上一格的结果怎样交给下一格。

这组图的目的不是替代正文,而是给读者一个低门槛入口:先形成整体画面,再回到正文理解为什么这些边界必须存在。

读完这一篇,你应该能看懂什么

  • 理解为什么 agent TUI 需要组件模型和 diff rendering
  • 看清硬件光标、IME、keyboard protocol 对中文输入的重要性
  • 学会把 UI 可观测性做在渲染层,而不是只在业务层埋点

和主流产品怎么对应

Claude Code 和 Codex CLI 这类终端 agent,表面看是命令行,实际上都需要一个小型 UI runtime。模型在流式输出,工具在后台跑,用户还可能继续输入,底部还要显示模型、成本、上下文压力和状态。

Cursor 把这些问题放进 IDE,所以你不太会意识到终端工程的复杂度。Pi 和 OpenClaw 这类 TUI 产品必须自己处理:怎么少刷新,怎么不打断输入,怎么让中文输入法候选框出现在正确位置,怎么让工具输出可以折叠和展开。

Component 模型

Pi 的 TUI component 只需要实现 render(width)、handleInput、invalidate 等接口。TUI container 负责组合 components、处理 overlay、分发输入、比较前后帧并写入终端。

一个最小 component 至少要能完成三件事:根据当前宽度渲染内容,处理键盘输入,在状态变化时请求重绘。这样 assistant message、tool execution、footer、editor、selector、extension overlay 都能作为组件存在,而不是散落成一堆临时 printf。

Diff rendering

流式 agent UI 如果每个 token 都全屏重绘,会闪烁、慢,而且容易破坏用户输入。Pi 维护 previousLines,每次 render 后找出变化区域,只重绘改变的行,并使用 synchronized output 批量写入。

这类机制看似底层,但直接决定 agent 的“手感”。

真实光标与 IME

中文输入法需要硬件光标位置正确,否则候选框会跑偏。Pi 让 focusable component 在输出中插入 cursor marker,TUI 渲染时扫描 marker,计算可见列,剥离 marker,再移动真实光标。

这说明 coding agent 的终端 UI 不是英文 token printer;它必须尊重真实输入环境。

Pi footer 展示 cwd、git branch、session name、token/cache/cost、context pressure、auto compact 状态、model/provider/thinking level、extension status 等。它把 agent runtime 的关键状态常驻显示。

对长任务来说,footer 不是装饰,而是低干扰可观测性界面。

放到修测试这个例子里

修复失败测试时,TUI 要同时展示很多东西:模型正在想什么,刚才跑了哪条命令,测试日志是否还在输出,文件 diff 有没有生成,用户现在能不能中断或补一句话。

如果只是 printf,这些内容会互相冲掉。好的 TUI 会把 assistant message、tool execution、footer、输入框和 overlay 分成组件,再用 diff rendering 更新。用户看到的是一个稳定界面,不是一条不断滚动的日志瀑布。

可迁移的边界

如果自己做 harness,TUI runtime 不要直接等同于 stdout。它需要组件树、输入分发、增量渲染、overlay、主题和事件订阅。只有这样,模型流、工具执行、队列变化和 session 状态才能稳定地映射到界面。

为什么 TUI 是 observability 的第一层

用户和 agent 的关系不是“等最终答案”。在长任务里,用户会不断观察:模型在读什么,工具执行到哪一步,测试为什么失败,是否该中断,是否该补充约束。TUI 是这些判断的第一入口。

所以 footer、tool block、streaming assistant message、error panel、selector、overlay 都不是装饰。它们是把 runtime event 翻译成人能理解的运行状态。

好的终端界面应该少打扰但可介入

太少信息会让用户焦虑,不知道 agent 是否卡住;太多日志会让用户看不清主线。好的 TUI 应该把默认视图做得稳定,把细节折叠起来,把关键状态放在 footer 或显眼位置,并允许用户在安全点 steering、follow-up 或 abort。

这也是为什么 Terminal UI Runtime 不能只是“把事件打印出来”。它必须理解事件层级、组件生命周期和用户输入。

这里的取舍

  • 取舍:UI 结构
    • Pi 的倾向:Component + Container
    • 可迁移原则:不要把终端当纯 stdout
  • 取舍:Streaming
    • Pi 的倾向:diff rendering
    • 可迁移原则:流式 UI 要减少重绘和闪烁
  • 取舍:输入
    • Pi 的倾向:keyboard protocol 归一化
    • 可迁移原则:快捷键和 paste 要跨终端稳定
  • 取舍:中文输入
    • Pi 的倾向:硬件光标 marker
    • 可迁移原则IME 是一等交互需求
  • 取舍:Debug
    • Pi 的倾向:write log / redraw reason
    • 可迁移原则:UI runtime 自己也要可观测

如果你在读 OpenClaw

如果你在读 OpenClaw,这一篇对应 TUI components、overlay、input、theme 和运行态状态栏。OpenClaw 上层可能还有 channel、media、voice,但只要它提供终端交互,就绕不开同一件事:把 agent 的事件流稳定投影成用户能操作的界面。

源码里真正能看到的终端 UI 机制

Pi 的终端 UI 是组件树加差分渲染。每个组件负责根据宽度渲染自己、处理输入、在状态变化时请求重绘。整个界面由组件组合而成,再叠加 overlay、焦点、真实光标、图片和 footer 状态。

输入也不是简单读一行文本。终端 raw input 会先经过缓冲和 escape sequence 解析,再经过全局监听器、协议响应过滤、焦点判断,最后交给当前组件处理。这样才能支持快捷键、粘贴、选择器、overlay、IME、图片尺寸响应等复杂行为。

渲染时,Pi 不会每次全屏重画。它维护上一帧内容,比较哪些行变化,只重绘变化区域;遇到宽度变化、viewport 不安全、图片残留等情况,再回退全量重绘。

为什么 TUI 是 agent 的第一层可观测界面

长任务里,用户不是只等最终答案。用户要看模型是否还在生成,工具是否还在跑,测试输出是否异常,队列里是否有 follow-up,压缩是否发生,是否该中断。

这些状态都来自 runtime event。TUI 的工作,是把事件翻译成用户能理解、能介入的界面。

所以 Terminal UI Runtime 不是“把文本打印漂亮一点”,而是 agent 产品的实时控制台。没有这层,复杂 agent 很容易退化成滚动日志,用户既看不懂,也无法及时干预。

阅读时可以用的三问

第一,界面有没有表达 runtime 状态。用户要能区分模型生成、工具执行、等待输入、压缩、错误和完成。

第二,重绘是否稳定。终端环境复杂,盲目刷屏会破坏输入体验,也会让工具日志和模型输出互相覆盖。

第三,用户介入是否自然。好的 TUI 不只是显示过程,还要让用户能在合适时机补充约束、取消任务或切换模式。

如果只记住一句话

流式 agent 需要的是界面系统,不是 printf。

小结

Pi 的 Terminal UI Runtime 说明:Agent 产品体验很大一部分来自底层终端工程。一个好的 TUI 不是把模型输出打印出来,而是把 agent 的实时状态组织成稳定、可输入、可观察、可扩展的界面。