第五部分 · 进程架构与界面

第 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 就渲染一个 IPythonCellComponentcomponents/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>
  • 展开后:代码区按行高亮(%%bash cell 与 ! 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(或交互中左箭头)打开的不是一个列表,是一个永动循环

图 1:聊天 ⇄ agents-view 的视图循环。run() 的返回类型就是循环的边: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 acpAgent 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 章讲过的东西。

实践应用

  1. 折叠/展开零布局位移。顶行恒定的 cell 设计让任何切换不产生重排——在差分渲染器上,「不移动」比「漂亮」更昂贵也更值得。
  2. 流式展示与结构化展示分两相。进行时只渲染最新 chunk(O(1) 成本),结束后按结构化 details 重排。live tail 与最终形态各取所需。
  3. 目录界面是运行时能力的投影。agents-view 的三个 section、heartbeat 徽章、子代理分组,全部是 daemon/RLM 状态的可视化——先有状态机,后有目录学。
  4. 呈现与治理的分界线写进命令注册表。哪些 slash 命令归 TUI、哪些归 session,一个常量数组就是全部争议的答案。

总结

Prime 的 TUI 站在 pi 差分渲染器的肩膀上,为 RLM 添加了三块新大陆:ipython-cell 用「顶行恒定」渲染模型的每个 Python 单元,流式 O(1)、结束重排;agents-view 把 daemon 的会话家族渲染成可导航的目录,与聊天互为循环;heartbeat 面板与 slash 分工表把长任务原语接进键位。界面只消费 AgentConnection——所以同一个世界还能以 print、json、rpc、acp 四副面孔存在。

到此,从 kernel 到终端的全链路走完了。最后一章,把这本书的赌注摆上桌面。