第五部分 · 进程架构与界面
第 16 章:RLM 的终端界面
渲染一个新世界的三件事:Python cell、子代理树、多会话目录
渲染器没变,世界变了
第 2 章说过,packages/tui 的字符串差分渲染器原封不动——组件还是 render(width) => string[],逐行差分还是那套。但渲染器要呈现的世界完全变了:pi 的画面是「消息 + 工具面板」,Prime 的画面里有持续滚动的 Python cell、一棵会钝化会水合的子代理树、一个可以逛来逛去的多会话目录。这一章看 TUI 为 RLM 增加的三块新大陆——这也是全书最后一块技术拼图。
interactive-mode.ts 有 352KB、约一万行。它的 InteractiveModeOptions 里有一行注释可以当本章的宪法:
"InteractiveMode never talks directly to AgentSession for core execution."
一切经 AgentConnection(第 15 章)。TUI 甚至不知道自己对面是 daemon 还是 in-process——这个「不知道」是后面所有视图嵌套的前提。
ipython cell:顶行恒定
模型每写一段 Python,TUI 就渲染一个 IPythonCellComponent(components/ipython-cell.ts,约 26KB)。它不走通用工具面板——tool-execution 判定工具名为 ipython 且扩展没有自定义渲染器时,挂一个专用渲染容器。核心设计浓缩在一段注释里:
// The top line is identical whether collapsed or expanded — same marker, // counts, duration, and expand hint — so toggling never shifts the layout // or indentation; expanding only attaches code and output below it.
顶行 anatomy 大致如下:
◇ python df = clean(raw).query("score > 0.8") ↑3 ↓12 · 1.2s ENAME ctrl+o
└ 状态标记 语言 代码单行预览(打分选行+脱敏) 行数 耗时 错误名 展开提示
- 状态标记:✓ 完成(绿)、✗ 错误(红)/中断(黄)、动画菱形
◇◈◆◈运行中、◇ 排队。 - 代码预览来自
core/tools/code-preview.ts的打分选行引擎,自带脱敏——base64 变<blob>、疑似密钥变<redacted>。 - 展开后:代码区按行高亮(
%%bashcell 与!magic 行整行按 bash 着色);输出区优先用结构化details(stdout 正常色、stderr muted、result 单独成段),traceback 用 muted 色逐行——只有顶行错误名是红色;diff 区按文件渲染marker path +N -N+ 富高亮。 - 流式成本 O(1):kernel 每个 stream chunk 触发一次更新,partial result 只带最新 chunk(TUI 做 live tail),
tool_execution_end后才按结构化 details 一次性全量渲染。动画帧编进渲染缓存的版本号——差分渲染器对动画的适配,就是缓存键的一次数学。
图片的取舍值得单独一句:attachment 图片不进终端画面——pi-tui 明明支持 kitty/iterm2 图形协议,Prime 却用 fallbackOnly: true 只显示一行元数据(╰─ [image/png · 800×600]),base64 原样进模型上下文。在「模型看图」与「人看图」之间,Prime 选了前者。
slash 命令的分工:TUI 不实现会话治理
InteractiveMode 的命令分发有一条清晰的分界线:
export const SESSION_SLASH_COMMAND_NAMES = ["compact", "refine", "goal", "autonomous"] as const;
这四个命令标记 execution: "session"——TUI 不实现它们,而是作为动作交给 AgentSession,在回合边界执行(第 11 章的 session 命令队列)。TUI 自己处理的是纯呈现类命令(settings、model、effort、theme、export、tree、login……约三十个),另有几个别名映射(clear→new、usage→context、thinking→effort、side→btw)。分界线的标准与第 5 章一脉相承:会改变会话状态的归会话,只改变终端呈现的归客户端。
agents-view:多会话的目录学
prime-agent agents(或交互中左箭头)打开的不是一个列表,是一个永动循环:
exit | scope_back | open;聊天结束可以返回 agents-view,聊天里也能打开以某会话子树为 scope 的 agents-view。视图本身有三个 section:running / idle / inactive(saved 会话归 inactive)。heartbeat 不是 section 而是行内徽章(♥ N·倒计时),且有 heartbeat 的会话会把祖先链染成 running——时间唤醒在目录里是可见的。数据来自一次统一索引:reconcileUnifiedSessions() 合并 daemon 活会话与磁盘存档(daemon 权威),身份键有三别名(file: / session: / active:),身份翻转时持久集合原地改写。
RLM 家族在目录里是嵌套行:subagent-summary 行(同 spawn cell 派生的子代理归组,源码只渲染一次)、subagent 行、subagent-code 行。键位是一套完整的方向盘:Enter 打开、Space 设回复目标、Ctrl+X 两段式删除/停止(时间窗口确认)、Ctrl+O 显隐 spawn 程序、Ctrl+R 重命名。轮询 1 秒(列表)/15 秒(heartbeat),断线有 2 分钟重连窗口与 generation 计数防竞态——第 15 章的机制在这里再次上场。
agents-view 必须 daemon:in-process 路径会直说 "the agents view needs the daemon; opening a normal chat instead"。多会话目录天然是 daemon 的能力投影。
heartbeat 的界面
/heartbeat(单数)管理会话自己的那条心跳;/heartbeats(复数)打开 HeartbeatManagerComponent 全屏面板,列表 + 详情两级模式,Pause/Resume/Stop 操作。UI 区分 "Created by you" 与 "Created by agent"——用户心跳与模型心跳在第 13 章是存储隔离,在这里是标签区分。面板依赖 daemon(in-process 连接的 listHeartbeats() 返回空、manageHeartbeat() 直接抛错)——又一次,长任务能力与 daemon 绑定。
品牌、彩蛋与「安装器即产品」
终端体验的外圈还有几笔值得记录:
- 首启 splash:蝴蝶 logo(
themes/prime-logo.ts,注释里附再生成脚本)+ 程序化 lab-field 背景动画;按有无凭据分流到「选模型」或「登录 Prime Intellect」。 - 彩蛋三连:
/arminsayshi(31×36 XBM 位图,7 种动画随机)、切到某模型时自动触发的致敬头像动画、以及一条上游公告卡片——都不在帮助与补全里。严肃系统里留几个只有 Insider 知道的房间,是一种团队文化的地层。 - 安装器即产品:
install.sh是 1,600 行 POSIX sh——全屏动画安装界面(逐格程序化背景、同步刷新转义防撕裂)、Node 自举(standalone 下载 + 官方 SHASUMS 校验)、发布 tarball SHA-256 校验、询问式 IPython runtime 预热。一条curl | sh管道被做成了完整体验。
同一连接,四副面孔
收尾一张表:InteractiveMode 只是 AgentConnection 的消费者之一。同一个接口撑起了 Prime 的全部运行形态——
| 模式 | 触发 | 形态 |
|---|---|---|
| interactive | 默认(TTY) | 全屏 TUI,本章主角 |
| print(text/json) | -p / --mode json / 管道输入 | 单发即退;autonomous gate 失败/限额耗尽反映为退出码 |
| rpc | --mode rpc | 常驻 stdin/stdout JSONL;命令面最全(含 refine、schedules、heartbeats、observe subagent、send_message) |
| acp | --mode acp | Agent Client Protocol(JSON-RPC 2.0 over stdio)供编辑器驱动;源码注释强调 Prime 的差异性(IPython 单工具、子代理、autonomous gates)在此以一等事件直出,不做 RPC 转译 |
每种模式都有 runXxxMode(runtime) 与 runXxxModeWithConnection(connection) 双入口——前者进程内跑,后者挂 daemon 会话。模式判定则朴素得像一个函数签名:
function resolveAppMode(parsed: Args, stdinIsTTY: boolean): AppMode {
if (parsed.mode === "daemon") return "daemon";
if (parsed.mode === "rpc") return "rpc";
if (parsed.mode === "acp") return "acp";
if (parsed.mode === "json") return "json";
if (parsed.print || !stdinIsTTY) return "print";
return "interactive";
}
帮助横幅给整个产品的一句话自我介绍是:"prime-agent - AI coding assistant with an IPython tool"。十一个词,全是第 2–16 章讲过的东西。
实践应用
- 折叠/展开零布局位移。顶行恒定的 cell 设计让任何切换不产生重排——在差分渲染器上,「不移动」比「漂亮」更昂贵也更值得。
- 流式展示与结构化展示分两相。进行时只渲染最新 chunk(O(1) 成本),结束后按结构化 details 重排。live tail 与最终形态各取所需。
- 目录界面是运行时能力的投影。agents-view 的三个 section、heartbeat 徽章、子代理分组,全部是 daemon/RLM 状态的可视化——先有状态机,后有目录学。
- 呈现与治理的分界线写进命令注册表。哪些 slash 命令归 TUI、哪些归 session,一个常量数组就是全部争议的答案。
总结
Prime 的 TUI 站在 pi 差分渲染器的肩膀上,为 RLM 添加了三块新大陆:ipython-cell 用「顶行恒定」渲染模型的每个 Python 单元,流式 O(1)、结束重排;agents-view 把 daemon 的会话家族渲染成可导航的目录,与聊天互为循环;heartbeat 面板与 slash 分工表把长任务原语接进键位。界面只消费 AgentConnection——所以同一个世界还能以 print、json、rpc、acp 四副面孔存在。
到此,从 kernel 到终端的全链路走完了。最后一章,把这本书的赌注摆上桌面。