x

Ink TUI 全新终端界面详解

概述

Ink TUI 是 Hermes Agent 的全新现代化终端界面,用 Node.js 构建,运行在 Python CLI 的子进程里。与传统 CLI 共用同一套运行时、会话、slash 命令和配置,但提供了更流畅、更丰富的交互体验。

一句话总结:hermes --tui 启动的那个漂亮的终端界面,就是 Ink TUI。


1. 架构全貌

进程关系

用户终端(Terminal
      
      
┌─────────────────────────────────────┐
  Python CLIhermes --tui 进程)     
  - 参数解析、配置加载、凭证管理       
  - 启动 Node.js TUI 子进程           
  - 启动 tui_gateway 子进程(JSON-RPC)│
└──────────┬──────────────────────────┘
            stdioJSON-RPC
           
┌─────────────────────────────────────┐
  tui_gatewayPython 子进程)         
  - JSON-RPC 命令分发器                
  - 处理 tool_call / reasoning / 状态  
  - 渲染器桥接(agent.rich_output   
└──────────┬──────────────────────────┘
            JSON-RPC
           
┌─────────────────────────────────────┐
  Ink TUINode.js 进程)             
  - 终端渲染(ANSI 输出)             
  - 用户输入捕获                      
  - 模态浮层、鼠标选择、主题切换      
└─────────────────────────────────────┘

关键文件:

  • tui_gateway/entry.py — gateway 入口,从 stdin 读取 JSON-RPC 命令,写入 stdout
  • tui_gateway/server.py — 分发器(161KB),处理所有 JSON-RPC 请求
  • tui_gateway/transport.py — 传输层抽象(StdioTransport / TeeTransport)
  • tui_gateway/event_publisher.py — WebSocket 事件发布(dashboard sidebar 用)
  • tui_gateway/render.py — 渲染器桥接(调用 agent.rich_output

2. 启动方式

# 标准启动
hermes --tui

# 恢复上次会话
hermes --tui -c
hermes --tui --continue

# 恢复指定会话
hermes --tui -r 20260409_000000_aa11bb
hermes --tui --resume "my session name"

# 开发模式(跳过预构建)
hermes --tui --dev

# 环境变量方式(全局启用)
export HERMES_TUI=1
hermes          # 自动走 TUI
hermes chat     # 同上

3. 核心特性

3.1 即时首帧渲染

TUI 的 banner(启动画面)在应用完全加载前就开始绘制,终端永远不会感觉"冻住"了。

3.2 非阻塞输入

可以在会话就绪前就开始打字和排队消息。消息会在 agent 上线的瞬间立即发送,不会丢失。

3.3 丰富的模态浮层

以下 slash 命令在 TUI 中以模态浮层(modal overlay)呈现,而不是内联流程:

命令 TUI 行为
/help 分类命令列表,方向键导航
/sessions 模态会话选择器 — 预览、标题、token 统计、在线恢复
/model 按 Provider 分组的模型选择器,含费用提示
/skin 实时预览 — 浏览时主题实时切换
/details 切换详细的 tool-call 详情(全局或按分区)
/usage 丰富的 token / 费用 / 上下文面板

3.4 鼠标友好选择

拖拽高亮文本使用统一的背景色,而不是 SGR 反转。与终端的正常复制手势一致,体验接近 GUI 应用。

3.5 无闪烁流式渲染

差分更新意味着流式输出时不会闪烁,退出后也不会有滚动条残留(alternate-screen 渲染模式)。

3.6 粘贴与附件

  • 内联粘贴折叠长代码片段
  • Cmd+V / Ctrl+V 优先普通文本粘贴,回退到 OSC52/剪贴板读取
  • 剪贴板图像自动识别并作为附件附加
  • 文件路径自动规范化

3.7 实时状态行

TUI 的状态行实时跟踪 agent 状态:

状态 含义
starting agent… 会话已建立,工具和技能还在初始化。可以打字,消息会排队等待
ready Agent 空闲,接受输入
thinking… / running… Agent 正在推理或运行工具
interrupted 当前轮次已取消,按 Enter 重新发送
forging session… / resuming… 首次连接或 --resume 握手中

4. 分区展示(Details Mode)

TUI 将对话流渲染为分区分段(sections),默认展开 thinking 和 tools:

分区 默认可见性 说明
thinking 展开 推理过程实时流式展示
tools 展开 工具调用和结果实时展示
subagents 折叠(chevron) 仅在发生实际委托时显示
activity 隐藏 背景元数据(gateway 提示、通知),大多数日常使用中是噪音

可通过配置或 /details 命令动态调整:

display:
  details_mode: collapsed    # hidden | collapsed | expanded
  sections:
    thinking: expanded       # 强制展开
    tools: expanded         # 强制展开
    activity: collapsed      # 开启活动面板

运行时命令:

/details [hidden|collapsed|expanded|cycle]       # 全局
/details <section> [hidden|collapsed|expanded]   # 单区

5. 主题(Skins)

TUI 支持完整的皮肤/主题系统,与经典 CLI 共享同一套配置:

display:
  skin: default   # 内置或自定义皮肤

运行时切换:

/skin ares   # 实时预览,浏览时主题即时切换

支持自定义的元素:banner 色调、UI 颜色、提示符 glyph/颜色、会话显示、命令补全菜单、选择背景、tool_prefixhelp_header


6. JSON-RPC 通信协议

tui_gateway 通过 JSON-RPC 2.0 与 TUI 通信。

Gateway 入口(entry.py)

# 读取 stdin 的每一行 JSON-RPC 命令
for raw in sys.stdin:
    req = json.loads(raw.strip())
    resp = dispatch(req)   # 分发到对应 handler
    write_json(resp)      # 写入 stdout

事件类型

Gateway 主动推送的事件(method: "event"):

type 说明
gateway.ready 启动完成,带 skin 信息
gateway.stderr Gateway 端的错误输出
agent.reasoning 推理内容片段
agent.tool_call 工具调用请求
agent.tool_result 工具执行结果
agent.message 最终回复文本
agent.error Agent 层错误

渲染器桥接

TUI 本身不直接做 markdown 渲染,而是通过 render.py 调用 Python 端的 agent.rich_output

# render.py
def render_message(text: str, cols: int = 80) -> str | None:
    from agent.rich_output import format_response
    return format_response(text, cols=cols)

如果 agent.rich_output 不存在,TUI 回退到自带的 markdown.tsx 渲染。


7. 传输层抽象(Transport Layer)

tui_gateway/transport.py 定义了传输层接口,让同一套分发逻辑可以跑在不同的 I/O 介质上:

@runtime_checkable
class Transport(Protocol):
    def write(self, obj: dict) -> bool: ...
    def close(self) -> None: ...

三种具体实现:

StdioTransport

写入流(通常是 sys.stdout),用于本地 stdio 模式。加锁保证线程安全,支持 BrokenPipeError 静默处理。

TeeTransport

一主多从的复制模式。主传输的返回值决定结果;从传输静默吞掉异常,永不阻塞主路径。用于 PTY 子进程:每一帧同时发给 stdio(Ink)和 WebSocket(dashboard sidebar)。

server._stdio_transport = TeeTransport(
    server._stdio_transport, WsPublisherTransport(url)
)

WsPublisherTransport

通过 WebSocket 将事件镜像到 dashboard 的 /api/pub 端点。后台 daemon 线程异步发送,队列满时直接丢弃(best-effort)。


8. 信号处理(entry.py)

Gateway 专门处理三类信号,防止"静默消失":

信号 处理方式
SIGPIPE 忽略 — 防止后台线程(TTS、voice)写已关闭的 stdout 导致进程崩溃
SIGTERM / SIGHUP 记录完整栈追踪到 ~/.hermes/logs/tui_gateway_crash.log
SIGINT 忽略 — Ctrl+C 留给 TUI 处理

崩溃日志同时输出到 stderr,让 TUI 能在 Activity 面板展示。


9. 与经典 CLI 的关系

维度 经典 CLI Ink TUI
界面 prompt_toolkit Node.js 渲染
交互 内联流程 模态浮层
鼠标 不支持 拖拽选择
首帧 有延迟 即时渲染
输入阻塞 阻塞等待 非阻塞排队

共用~/.hermes/state.db(同一会话)、slash 命令、配置、~/.hermes/config.yaml、凭证、会话压缩逻辑。

可以混用:在 TUI 里开的会话,关掉后用 hermes(经典 CLI)也能恢复。


10. Web Dashboard 集成

TUI 还可以在浏览器里运行(Web Dashboard 的 Chat 标签页):

Dashboard Server(Node.js)
    │
    │ POSIX pseudo-terminal
    ▼
hermes --tui 子进程
    │
    │ ANSI 输出流
    ▼
xterm.js(WebGL 渲染)

文档原文:

"The dashboard is running the real TUI binary and rendering its ANSI output through xterm.js with its WebGL renderer for pixel-perfect cell layout."

TUI 的所有功能(slash 命令、模型选择、工具卡片、markdown 流式渲染、skin 主题)在浏览器里完全一致。


11. 适用场景建议

场景 推荐
日常交互开发 ✅ Ink TUI(非阻塞输入、实时状态)
远程服务器(SSH) ✅ Ink TUI(xterm.js 远程渲染)
无头/批处理 ❌ 用 hermes chat 或脚本模式
极端低带宽 ❌ 经典 CLI(更少的渲染开销)
需要 GUI 拖拽 ✅ Ink TUI

相关源文件

  • tui_gateway/entry.py — Gateway 入口、信号处理、退出日志
  • tui_gateway/server.py — JSON-RPC 分发器(161KB,最核心)
  • tui_gateway/transport.py — Transport ABC(Stdio / Tee / WsPublisher)
  • tui_gateway/event_publisher.py — WebSocket 镜像发布
  • tui_gateway/render.py — Python 渲染器桥接
  • tui_gateway/ws.py — WebSocket 服务器
  • tui_gateway/slash_worker.py — slash 命令后台工作器
  • website/docs/user-guide/tui.md — 官方文档
Left-click: follow link, Right-click: select node, Scroll: zoom
x