Skip to content

feat(herdr): 切换原生终端流#484

Open
luw2007 wants to merge 5 commits into
deepcoldy:masterfrom
luw2007:feat/herdr-native-terminal-stream
Open

feat(herdr): 切换原生终端流#484
luw2007 wants to merge 5 commits into
deepcoldy:masterfrom
luw2007:feat/herdr-native-terminal-stream

Conversation

@luw2007

@luw2007 luw2007 commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

改了什么

  • HerdrBackend 的实时输出从 500ms agent list + agent read --lines 10000 + 字符串 diff 迁移到 Herdr terminal session observe 原生 NDJSON 终端流。
  • 使用 Zod 校验 terminal.frame / terminal.closed,支持 stdout 拆包/粘包、base64 ANSI 字节和跨 frame UTF-8 解码。
  • seq 去重并过滤乱序帧;observer 异常退出时单次探测 agent,存活则重连,消失则触发 backend exit。
  • resize 时重建 observer,并用新的 full frame 重新建立终端基线;kill/destroy 会清理 observer 和重连 timer。
  • 保留 reattach/adopt 的 snapshot seed;observer 的首个 full frame不再被丢弃,因为它可能包含 seed 之后产生的新输出。
  • worker 当前在 spawn() 后注册 onData,backend 会暂存此前到达的最早帧并在首个 listener 注册时交付,避免启动竞态丢失完整画面。
  • Herdr 最低版本提升到 0.7.2,旧版本明确提示执行 herdr update,不保留轮询 fallback。

为什么

旧实时路径每个会话持续执行轮询和固定 10,000 行扫描,并在 Node.js 中做全量字符串差分:

  • 空闲会话仍产生周期性 Herdr CLI / IPC 开销;
  • 长终端历史会重复传输和扫描;
  • 固定滚屏窗口跨边界时可能无法可靠恢复增量。

Herdr 0.7.2+ 已提供单个长连接的 ANSI 终端流,可直接传递首帧和后续绘制增量。

TDD 补充验证

新增并执行了以下回归契约:

  • 一个 UTF-8 字符被拆到连续两个 terminal.frame 时仍完整解码,不产生
  • observer 断开重连后转发新的 full rebaseline,再继续接收增量;旧 observer 的迟到帧被忽略。
  • backend kill 会取消等待中的 observer 重连,不会后台复活子进程。
  • spawn() 后、首个 onData listener 注册前到达的 full frame 不会丢失。
  • 真实 Herdr reattach:snapshot seed 恢复已有画面,现存 shell 后续自主输出通过 observer 到达并由 TerminalRenderer 正确渲染。

RED 证据:旧实现 unit 只得到 delta done、丢失 reattach full frame;真实 Herdr 0.7.3 reattach 新输出超时。GREEN 后上述路径全部通过。

影响面

  • Herdr backend:实时输出读取、observer 生命周期、resize/reconnect 基线、最低版本检查。
  • 保持不变:Herdr 输入仍走现有 pane send-text / pane send-keys;显式 screen capture 仍使用 read API。
  • 其它 backend:PTY、Tmux、Zellij、Riff 未修改。
  • 会话类型:fresh、持久会话 reattach、external adopt 均有定向覆盖。
  • 平台:通过 spawn() 参数数组调用 Herdr CLI,不新增 shell 或平台专用路径。

验证

使用 Node 22:

  • pnpm build:通过。
  • test/herdr-backend.test.ts:32/32。
  • Herdr discovery + worker initial-screen 契约:67/67。
  • 真实 Herdr 0.7.3 test/herdr-backend.e2e.ts:8/8。
  • 全量 unit:8356 通过、6 跳过、1 个既有失败。
    • 既有失败:test/dashboard-monitoring-ui.test.ts:326 期望 .ui-info-pop-floatingpointer-events: none;本 PR 未修改 dashboard CSS 或该测试。
  • git diff --check origin/master...HEAD:通过。

关联

@luw2007
luw2007 requested a review from deepcoldy as a code owner July 16, 2026 02:04
luw2007 added 5 commits July 16, 2026 10:52
覆盖 NDJSON 拆包、ANSI 字节解码、首帧语义、序列去重与 observer 清理。

Constraint: Herdr 原生流要求 0.7.2+

Confidence: high

Scope-risk: narrow

Not-tested: Node 26 下 Vitest 启动挂起;RED 使用 Node 22 验证
使用 Herdr 0.7.2+ terminal session observe NDJSON 流替代 500ms 轮询、固定 10000 行扫描和状态 watcher。保留 fresh/reattach/adopt 首帧语义,并覆盖断流判活重连、resize rebaseline 与进程退出。

Constraint: Herdr >= 0.7.2

Rejected: 保留旧轮询 fallback | 会继续产生固定窗口丢流和高 IPC 成本

Confidence: high

Scope-risk: narrow

Not-tested: 全项目 build 被 src/cli.ts 既有 npmGlobalUpdateCwd 缺失导出阻断;全单测唯一既有 CSS 断言失败
覆盖跨帧 UTF-8、断流重连基线、关闭期间取消重连,以及真实 reattach 后新增量输出。

Constraint: RED 在真实 Herdr 0.7.3 reattach 增量路径稳定超时

Confidence: high

Scope-risk: narrow
将 reattach 契约收敛为 observer full frame 单一基线,并复现 spawn 后注册 onData 导致首帧丢失。

Constraint: unit RED 收到空数组而非 early baseline;真实 E2E 无 reattach full baseline

Confidence: high

Scope-risk: narrow
不再丢弃 reattach/adopt observer 的首个 full frame,并在 worker 尚未注册 onData 时缓存早到帧,避免 snapshot seed 之后的新输出被吞。

Constraint: Herdr 静止 reattach 依赖 snapshot seed,屏幕变化后 observer 才发 ANSI full/增量

Rejected: 固定等待首帧后再允许输入 | 真实时序不稳定且无法证明无丢流

Confidence: high

Scope-risk: narrow
@luw2007
luw2007 force-pushed the feat/herdr-native-terminal-stream branch from ab7941e to 02b58ab Compare July 16, 2026 03:25
@deepcoldy

Copy link
Copy Markdown
Owner

这就一起review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants