03 - TUI 终端界面架构
学习目标
本章聚焦 Hermes Agent 的 TUI 终端界面架构。你将了解:
- TypeScript Ink 前端与 Python JSON-RPC 后端的进程分离
- 换行分隔的 JSON-RPC over stdio 传输协议
- Slash 命令的三级分发流
- Dashboard 的 PTY 桥接与 xterm.js 渲染
前置知识
无特定前置知识。
项目实践
进程分离架构
Hermes TUI 采用前后端进程分离架构:
职责划分:
- TypeScript(Ink):控制屏幕渲染、用户输入、动画效果、补全
- Python(tui_gateway):会话管理、工具调用、模型交互、审批处理
这种分离使得前端可以使用 React 生态(组件、状态管理),后端保持 Python 的 Agent 运行时。
传输层:换行分隔的 JSON-RPC over stdio
tui_gateway/transport.py 实现 stdio JSON-RPC 传输层:
- 每条 JSON-RPC 消息以换行符(
\n)分隔 - 双向通信:Node → Python(用户输入、命令)和 Python → Node(流式输出、工具活动)
- 消息格式:标准 JSON-RPC 2.0(method、params、id、result)
tui_gateway/ 目录(8 个文件):
| 文件 | 职责 |
|---|---|
server.py | TUI 网关服务器主入口 |
transport.py | stdio JSON-RPC 传输层 |
event_publisher.py | 事件发布到前端 |
render.py | 渲染数据格式化 |
slash_worker.py | Slash 命令子进程 |
ws.py | WebSocket 备用传输 |
entry.py | 进程入口点 |
Slash 命令三级分发流
TUI 中的 Slash 命令(如 /skin、/reset、/session)经过三级分发:
- 本地处理:简单命令(如清屏)由前端直接执行
slash.exec:大部分命令通过slash.exec分发到后端_SlashWorker子进程:重型命令在独立子进程中执行,不阻塞主进程command.dispatch回退:未匹配的命令回退到通用分发器
Dashboard:PTY 桥接 + xterm.js
hermes dashboard 命令启动 Dashboard 界面:
- PTY 桥接:后端创建伪终端,前端通过 JSON-RPC 获取终端输出
- xterm.js WebGL 渲染器:前端使用 xterm.js 的 WebGL 加速器渲染终端输出
- 支持实时日志流、Agent 活动监控
开发命令
| 命令 | 功能 |
|---|---|
npm run dev | watch 模式开发,自动重新构建 |
npm run build | 完整构建,生产打包 |
问题与规避
stdio JSON-RPC 消息边界
问题:如果 JSON 消息中包含换行符(如字符串值中的 \n),会导致消息解析错误。
对策:每条消息序列化为单行 JSON,字符串中的换行符被转义为 \n。接收端按行分割即可得到完整消息。
前后端版本不匹配
问题:前端期望的 JSON-RPC 格式与后端输出的格式不一致。
对策:JSON-RPC 2.0 是标准协议,前后端都遵循相同的 method/params/id/result 格式。新增方法时向后兼容。
设计取舍
React TUI + Python 后端 vs 纯 Python TUI
| 方案 | 优势 | 代价 |
|---|---|---|
| React TUI + Python 后端(Hermes) | 前端可用 React 生态、UI 更丰富、状态管理成熟 | 需要 Node.js 运行时、进程间通信开销 |
| 纯 Python TUI(Rich/Textual) | 单进程、部署简单 | UI 组件有限、动画能力弱 |
Hermes 选择 React TUI 是因为其目标用户需要丰富的交互体验(审批弹窗、会话选择器、实时流式输出),这在纯 Python TUI 中难以实现。
参考来源
- 源码验证:
ui-tui/package.json、tui_gateway/目录、AGENTS.mdTUI 章节