第八章 TUI 终端界面 — 双进程架构
Hermes 提供了一个基于 Ink(React for CLI)的富终端界面,通过 hermes --tui 启动。它采用双进程架构:Node.js 负责渲染 UI,Python 负责 Agent 逻辑。
8.1 架构概览
为什么用两个进程?
| 职责 | Node/Ink 擅长 | Python 擅长 |
|---|---|---|
| 终端渲染 | ✅ Ink 组件化 UI | ❌ |
| 用户输入 | ✅ prompt_toolkit 级体验 | ❌ |
| Agent 逻辑 | ❌ | ✅ AIAgent + 工具系统 |
| 会话管理 | ❌ | ✅ SQLite + FTS5 |
| 模型调用 | ❌ | ✅ OpenAI/Anthropic SDK |
8.2 JSON-RPC 通信协议
两个进程通过 stdio 传输的 JSON-RPC 通信:
请求方法(Ink → Python)
| 方法 | 说明 |
|---|---|
prompt.submit | 提交用户消息 |
session.list | 列出历史会话 |
session.resume | 恢复会话 |
slash.exec | 执行斜杠命令 |
approval.respond | 响应审批请求 |
clarify.respond | 响应澄清问题 |
complete.slash | 斜杠命令自动补全 |
complete.path | 路径自动补全 |
事件推送(Python → Ink)
| 事件 | 说明 |
|---|---|
message.delta | 流式文本增量 |
message.complete | 消息完成 |
tool.start | 工具开始执行 |
tool.progress | 工具执行进度 |
tool.complete | 工具执行完成 |
approval.request | 请求用户审批 |
clarify.request | 请求澄清 |
gateway.ready | 后端就绪(附带皮肤、模型列表等) |
8.3 Ink 前端关键组件
组件结构
ui-tui/src/
├── entry.tsx # TTY 检测 + render()
├── app.tsx # 主状态机和 UI
├── gatewayClient.ts # JSON-RPC 客户端
├── app/
│ ├── eventHandler.ts # 事件处理
│ ├── slashHandler.ts # 斜杠命令
│ ├── stores/ # 状态管理
│ └── hooks/ # 自定义 Hooks
├── components/
│ ├── messageLine.tsx # 消息行
│ ├── thinking.tsx # 工具活动指示
│ ├── prompts.tsx # 审批/澄清提示
│ └── branding.tsx # 品牌元素
└── hooks/
├── useCompletion.ts # 自动补全
├── useInputHistory.ts # 输入历史
└── useQueue.ts # 消息队列App 状态机
app.tsx 是 TUI 的核心,管理着整个应用状态:
消息渲染管线
消息从 Python 后端到终端屏幕的过程:
message.delta (增量文本)
→ useMainApp hook 接收
→ 追加到 currentResponse 状态
→ messageLine.tsx 渲染
→ Markdown 解析(代码块高亮、链接、列表)
→ Ink <Text> 组件输出到终端核心 Hook 详解
| Hook | 作用 | 实现原理 |
|---|---|---|
| useCompletion | Tab 自动补全 | 监听 Tab 键,发送 complete.slash 或 complete.path RPC,渲染补全列表 |
| useInputHistory | 上下箭头历史 | 维护 history[] 数组和 historyIndex,↑↓ 切换时替换输入框内容 |
| useQueue | 消息发送队列 | 保证同一时间只有一个 prompt.submit 在处理,后续消息排队等待 |
| useMainApp | 主状态管理 | 合并所有 RPC 事件到统一的 React state,驱动 UI 更新 |
主题与皮肤集成
TUI 通过 gateway.ready 事件获取皮肤数据,同步 CLI 的视觉风格:
typescript
// gatewayClient.ts
socket.on('gateway.ready', (data) => {
const { skin, models, sessions } = data
setTheme(skin) // 应用颜色、品牌名称等
setModels(models)
setSessions(sessions)
})8.4 Python 后端 RPC 服务
8.4 Python 后端 RPC 服务
tui_gateway/server.py 负责处理所有 RPC 请求:
线程模型
- 主线程:JSON-RPC 请求解析和分发(快速,不阻塞)
- 线程池:处理 Agent 调用、会话操作等阻塞任务
- SlashWorker:独立的 Python 子进程,运行斜杠命令(不阻塞聊天)
8.5 斜杠命令工作流
为什么用独立子进程?
- 隔离:斜杠命令可能很耗时(如
/skills browse),不阻塞主聊天 - 持久:SlashWorker 在会话期间持续运行,避免每次启动的开销
- 状态:维护独立的命令状态(如搜索上下文)
8.6 如何调试 TUI
开发模式
bash
# 前端热重载开发
cd ui-tui
npm run dev # TypeScript watch + Ink 渲染
# 后端调试
HERMES_LOG_LEVEL=DEBUG hermes --tui查看 RPC 通信
bash
# 开启 debug 后,RPC 消息会输出到 stderr
HERMES_DEBUG=1 hermes --tui 2>tui-debug.log日志中可以看到完整的 JSON-RPC 请求和响应,方便排查前后端通信问题。
常见问题
| 问题 | 原因 | 解决 |
|---|---|---|
| TUI 不渲染 | Node 版本过低 | 需要 Node.js 18+ |
| 中文乱码 | 终端编码 | 设置 LANG=zh_CN.UTF-8 |
| 卡在 "Connecting..." | Python 后端崩溃 | 检查 HERMES_LOG_LEVEL=DEBUG 输出 |
源码导航:
- TUI 入口 →
ui-tui/src/entry.tsx - 主应用 →
ui-tui/src/app.tsx - JSON-RPC 客户端 →
ui-tui/src/gatewayClient.ts - Python RPC 服务 →
tui_gateway/server.py - 斜杠命令工作进程 →
tui_gateway/slash_worker.py