PTY 包装与内联媒体 · 让复制穿透 SSH、让图片进终端
Part 5 收官,两个「终端里居然能做到」的把戏:grok wrap 用 PTY 拦截 OSC 52 让复制穿透 SSH/容器;内联图片用 Kitty 图形协议在滚动区里显示、还能随滚动裁切滑入滑出。这一章拆这两套机制。
PTY 包装与内联媒体 · 让复制穿透 SSH、让图片进终端
Part 5 收官,看两个「终端里居然能做到」的把戏。
grok wrap:让复制穿透 SSH
先纠正一个容易望文生义的点:pty_wrap.rs 不是内嵌终端视图,而是 grok wrap 命令的剪贴板/SSH 桥。
问题背景:你在远程机器上、或容器里跑 grok,想把它输出的东西复制到本地剪贴板——终端本身不支持 OSC 52 时(比如 Apple Terminal),这条路就断了。grok wrap <命令> 把命令包在一个本地 PTY 里跑(portable-pty),在输出流上装一个 Osc52Filter:拦截 OSC 52 剪贴板写序列,把内容复制进本地剪贴板。它还设 GROK_OSC52_SINK + LC_GROK_OSC52_SINK 环境变量(后者能搭 SSH 的 SendEnv/AcceptEnv LC_* 顺风车),好让远端里层的 grok 信任 OSC 52。于是「远端 agent 输出 → 本地剪贴板」就通了。顺带,它还把本地的系统明暗写进 GROK_APPEARANCE / LC_GROK_APPEARANCE,远端设成 auto 的主题也能跟着本地变。
退出安全做得很足:TerminalRestoreGuard(Drop)、信号循环(SIGHUP/INT/TERM 转发给子进程、恢复终端模式)、绕过 Rust stdout 锁的写(免得信号路径上死锁)。包一个 PTY 看似简单,把终端状态在各种退出路径上都恢复干净才是真功夫。
内联图片:Kitty 图形协议
在终端里显示图片靠图形协议。GraphicsProtocol 三选一:Kitty / ITerm2 / None,但实际策略很保守:
- Windows → None(ConPTY 会吞掉 APC 转义);
- iTerm2 → 滚动区里关掉(它的 OSC-1337 缺放置与清除原语,图跟不住网格)→ 退回文字占位;只有 prompt 输入框里的图片预览单独放行 iTerm2;
- 真正启用滚动区内联叠加的,只有 Kitty 协议 + Kitty / Ghostty / WezTerm 这几个终端(Warp 虽然也走 Kitty 协议,却被排除在外);在 tmux 里则一律不开。
滚动区的图分两步:每个文件只传输一次(transmit_inline_image,各有独立的 image id,从 2 起分配),之后每帧只发放置转义;图片被部分滚出屏幕时,place_kitty_image_cropped 在放置转义里带上源裁切(x/y/w/h)——这样一张图能随滚动裁切着滑入滑出。数据按 base64 切成 4096 字节一块发送。render_kitty_image 那种「传输 + 显示」一次到位的转义,则只给弹窗、预览这类叠加用:它们共享放置 id 1,由 overlay.rs 按渲染方记录所有权——同一渲染方在同一位置重复请求就不重发,所有权变更只在字节真正写出(PostFlush)后才提交,防止滚动时残留像素花屏。新拆出的 xai-grok-image crate 只管图片的校验与转码,终端显示逻辑仍在 terminal/image.rs。
视频海报:可选的 ffmpeg
视频显示不了,但能抽一帧当海报:extract_poster_frame 调 ffmpeg -ss 1 -frames:v 1 ... 抽首秒一帧(失败就退回取第一帧;按协议选 png/jpg)。ffmpeg 是可选的——没装就显示一行「装 ffmpeg」的横幅(还给出按系统的安装命令),装上后中途也能恢复(re-probe)。高度预留和实际放置共用同一个 inline_media_reserved_rows,保证两者不会算不一致。
超链接路由
顺带一提 OSC 8 超链接:hyperlink_route() 按终端能力决定发不发(Apple Terminal、Warp、老 VTE(低于 0.50.4)、GNU screen、老 tmux(低于 3.4)以及认不出的终端都不发,skip_reason 记着原因)。链接参与 cell diff(在 draw_frame 里 set_frame_links),不是带外发的。
和《从 LLM 到 Coding Agent》对照
这一章几乎全是「终端里怎么可能做到」的产品魔法——教学 agent 根本不碰。让复制穿透 SSH、让图片在滚动区里裁切滑动、让视频有海报,每一个都是深挖终端协议 + 兜住各种退化的硬活。它们不改变 agent 的智能,却实实在在决定了「用起来爽不爽」。
Part 5 终端 UI 深潜到此为止。下一部分运行形态与工程,看 grok 的会话持久化、plan/headless、ACP、后台任务与 Rust 工程底座。
上一篇:主题与终端适配