Herdr 源码深度解析¶
Herdr 0.8.0 Rust 固定提交 bbd7c2
Herdr 表面上是一个“给 AI 编码 Agent 用的终端工作区管理器”,源码里真正困难的问题却远不止终端分屏:它必须同时管理长寿命 PTY、复杂 VT 状态、Agent 生命周期、多个本地/远程客户端、Git worktree、断点恢复,以及更新时不杀掉正在工作的进程。
这套文档从这些难点反推架构,再沿真实调用链落回代码。目标不是记住 243 个 Rust 文件,而是建立一张可以用于阅读、调试和二次开发的心智地图。
审计快照
本站对应上游提交 bbd7c2094a44fcbcc4a3a3aedef236c4d697d793,Cargo 版本 0.8.0,审计日期 2026-08-10。所有源码链接都固定在该提交。
一句话架构¶
Herdr 是一个服务端拥有状态与 PTY、客户端只负责输入与呈现的终端工作区系统;它以 TerminalId 把可持久化状态和活跃运行时重新连接起来,以多来源证据推导 Agent 状态,并用序列号、代际和两阶段交接保护所有跨线程、跨进程的异步边界。
flowchart LR
User["键盘 / 鼠标 / IME"] --> Client["本地或 SSH 客户端"]
Client -->|"ClientMessage"| Server["Headless Server"]
Server --> AppState["AppState\n纯状态"]
Server --> Runtime["TerminalRuntimeRegistry\n活跃资源"]
Runtime --> PTY["PTY actor"]
PTY --> Child["Shell / AI Agent"]
PTY --> VT["Ghostty VT"]
VT --> Detect["Agent 检测与仲裁"]
VT --> Render["虚拟渲染 + diff"]
Render -->|"Semantic / ANSI frame"| Client
AppState --> Persist["session.json / history"]
Server --> API["本地 JSON API / EventHub"]
你会学到什么¶
-
从单体 TUI 到 Server/Client
为什么启动入口仍保留单进程模式,而默认路径已经是后台服务;服务器如何在一个循环里公平处理 PTY、API、客户端和定时任务。
-
终端不是字符串缓冲区
PTY 字节怎样进入 Ghostty VT,如何回送终端查询响应,为什么可见文本、检测文本、ANSI 快照和渲染脏行必须是不同读模型。
-
Agent 状态是推导结果
进程树、屏幕规则、OSC 和官方 Hook 如何竞争;为什么需要序列号、会话代际、权威源和
skip_state_update。 -
长寿命资源如何跨更新存活
Live Handoff 如何暂停 PTY reader、复制 master FD、交换快照、切换 socket,并在任何失败点回滚。
-
性能来自边界设计
256 容量事件队列、64 条公平 drain、合并渲染信号、脏行 patch、客户端背压和“发送成功后才推进基线”如何协作。
-
如何验证并发系统
3458 个 Rust 测试入口、维护脚本、架构守卫、跨平台 CI 和端到端 PTY 测试分别守住哪一层契约。
推荐路线¶
第一次阅读建议按以下顺序:
- 全景与阅读地图:先认识进程、状态和数据流边界。
- 启动与运行模式 → Server/Client:理解默认执行骨架。
- 状态与身份 → 工作区布局:建立领域模型。
- PTY/终端 → 输入/渲染:打通一帧和一次按键。
- Agent 检测 → 持久化与交接:攻克最具 Herdr 特色的部分。
- 再按兴趣选择 API、插件、worktree、远程、平台、性能和测试章节。
若你在定位具体文件,直接看源码地图;若你想核对数字或复跑统计,看证据、统计与复现。
本书如何拆代码¶
每一章尽量遵循同一条证据链:
问题是什么 → 完整旅程怎样走 → 核心类型保存什么 → 关键分支为什么存在 → 失败边界如何收口 → 测试证明了什么 → 设计可迁移到哪里
代码片段只截取理解所需的最小形状;精确实现以紧邻的固定提交源码链接为准。文中明确区分:
- 源码事实:可以从类型、函数、测试或配置直接验证;
- 设计解释:根据调用关系和约束推导出的意图;
- 评价与建议:本拆解对取舍、风险或演进方向的判断。
这一区分很重要。源码阅读最危险的不是漏掉一个函数,而是把“作者写了什么”和“我们认为为什么这样写”混成同一件事。