h脚本想法
你这段经历本质上是一次**“前端降级、核心不变”的入口迁移**:Desktop 的上下文组装瓶颈来自它那个厚重的 Electron 渲染层,而不是 Hermes 的 Agent 核心。把入口换到 TUI + Telegram 之后,上下文组装的开销立刻回到可接受的水平,但 TUI 在会话检索上的短板又催生了 h 脚本的设想。
🖥️ Hermes 的前端入口:一个核心,五张脸
Hermes 的所有入口共享同一个 AIAgent 核心,入口只负责“输入输出”,对话逻辑、上下文组装、工具调度全都走同一套代码。这正是你能在不同入口之间迁移而不丢失会话的根本原因——Desktop 上开始的会话,可以在 TUI 或 Telegram 里用同一个 ID 恢复。
| 入口 | 启动方式 | 定位 | 上下文组装特征 |
|---|---|---|---|
| CLI | hermes | 脚本、一次性提示、SSH | 最轻,纯文本流 |
| TUI | hermes --tui | 终端里的日常交互主力 | 轻,React/Ink 渲染,开销远低于 Desktop |
| Desktop | 下载安装,或 hermes desktop | 图形化管理工作台 | 重,Electron 渲染全量 DOM,长会话下组装成本急剧上升 |
| Dashboard | hermes dashboard | 浏览器里的配置/日志/会话管理面板 | 中等,Web 嵌入真实 TUI |
| Gateway | hermes gateway | Telegram/Slack/Discord 等消息平台接入 | 轻,消息平台适配层,上下文按消息窗口组织 |
| OpenAI 兼容 API | API server 端口 8642 | 接入 LibreChat、Open WebUI 等第三方前端 | 取决于前端,Hermes 侧只暴露标准端点 |
Desktop 的“重”不是偶然,而是架构选择的代价。 一个被反复确认的根因是:Desktop 的 Electron 渲染器没有对消息列表做虚拟化,整个对话历史被挂载为真实的 DOM 节点,系统消息、记忆块、已加载技能全部渲染在 DOM 里。每次按键都会触发整棵 React 树的遍历和更新。macOS 上 30 轮以上的对话,输入延迟可达 200–500ms,最终“几乎不可用,被迫 /new 丢掉上下文”。相比之下,Remote 模式下的 Desktop 反而流畅,因为它是一个薄前端,不加载 Python 运行时,也不在 DOM 里渲染系统消息。
TUI 和 Telegram 没有这一层 Electron DOM 负担。TUI 是 React/Ink 的终端渲染,Telegram 是 Gateway 适配层按消息窗口组织上下文,两者的“组装开销”都远低于 Desktop 的全量 DOM 重渲染。
🧭 从 Desktop 退到 TUI + Telegram 的决策路径
你的迁移不是“Desktop 不好用”,而是Desktop 的优势和你的工作流错位。Desktop 真正擅长的是本地单机、图形化配置、预览拖拽、技能管理——对不想碰 YAML 的用户,它是降低门槛的正确方向。但一旦你的工作流涉及大量并行会话和远程访问,Desktop 的 Electron 渲染瓶颈和远程链路复杂度就会压过它的便利性。
有一个社区作者的体会和你几乎一样:他装了 Desktop,用了一周,最后还是回到 Telegram——因为他的 Hermes 跑在远程服务器上,Desktop 连的是 Dashboard backend 而不是 Gateway,中间多出内网穿透、代理、WebSocket 校验等故障点,“每一层都可能单独出问题”。Desktop 的界面再好看,也改变不了工作流入口。
你的组合 TUI + Telegram 恰好避开了这两类问题:
- TUI 承担桌面端的深度操作,没有 Electron DOM 的全量重渲染,上下文组装开销低,
Ctrl+X//sessions可以在已打开的会话间快速切换。 - Telegram 承担移动端和异步调度,
/topic模式让每个话题成为一个完全独立的 Hermes 会话,不需要在群里手动维护固定话题列表。
代价是 TUI 的会话检索体验不够顺滑:/sessions 切换器主要面向“活跃会话”,而不是全部历史;真正的全文搜索藏在 hermes sessions browse 或 session_search 工具里,需要跳出当前交互去用命令。这就是 h 脚本要解决的问题。
⌨️ h 脚本:用 sessions list --json 替代 browse | grep
你原先设想的 h xxxx = hermes sessions browse | grep xxxx 在技术上走不通,因为 sessions browse 是一个 curses 交互式程序,它接管终端屏幕、等待键盘实时过滤,不接受管道输入做预过滤。但它完全可以换一种实现方式达到同样的目标。
可行的架构是:在 shell 层面用 sessions list --json 做程序化判断,再决定进入 TUI 的方式。 hermes sessions list 支持 --source、--limit 等参数,会话数据存于 SQLite 并带 FTS5 全文索引,list 的默认 limit 是 20,可以用 --limit 拉大。结合 jq 按 id、title、preview 做关键词匹配,就能在不启动交互式 browse 的情况下知道“有没有匹配”。
一个可用的 h 函数骨架如下:
h() {
local kw="$*"
[ -z "$kw" ] && { hermes sessions browse; return; }
local ids
ids=$(hermes sessions list --json --limit 200 2>/dev/null \
| jq -r --arg k "$kw" \
'.sessions[] | select(
((.id // "") | test($k; "i"))
or ((.title // "") | test($k; "i"))
or ((.preview // "") | test($k; "i"))
) | .id')
local n
n=$(printf "%s\n" "$ids" | grep -c . || true)
if [ "$n" -eq 0 ]; then
echo "无匹配「$kw」,进入 TUI 后输入:/new $kw"
hermes --tui
elif [ "$n" -eq 1 ]; then
hermes --tui --resume "$ids"
else
local pick
pick=$(printf "%s\n" "$ids" | fzf --prompt="选择会话: ")
[ -n "$pick" ] && hermes --tui --resume "$pick"
fi
}
这个脚本的分支逻辑完全对应你最初的设想,只是把“预过滤”从不可行的 browse | grep 换成了 list --json + jq:
h无参数 → 直接进sessions browse交互式浏览器。h xxxx有匹配 → 单匹配直接--resume,多匹配用fzf收窄后--resume。h xxxx无匹配 → 启动 TUI 并提示/new xxxx。/new的[name]参数会设置新会话的初始标题,之后h xxxx就能直接命中它。
唯一的妥协点是“自动创建”。目前没有看到从 shell 启动 TUI 并自动传入 /new <name> 的官方参数,最稳妥的做法是“启动 + 提示”,而不是赌 --resume 在无匹配时会自动创建。这样你得到的不是 100% 无缝的自动化,而是一个可预测、可调试、日常足够顺手的包装脚本。
💎 一句话总结
Desktop 的上下文组装瓶颈是 Electron 渲染层的架构债,不是 Hermes 核心的问题;退到 TUI + Telegram 是用入口的“轻”换回了可用的组装效率,代价是 TUI 的原生检索不够顺手——h 脚本用 sessions list --json 把“查找、选择、进入/创建”收敛成一条命令,正是在“轻入口”的前提下把检索体验补回来的务实方案。