
人工智能AI Agent代码智能体自主智能体MCP ClientsCLI【免费下载链接】atomcodeClaude Code 的开源替代方案。连接任意大模型编辑代码运行命令自动验证 — 全自动执行。用 Rust 构建极致性能。 An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get Started项目地址https://gitcode.com/atomgit_atomcode/atomcode点击查看免费下载本文是 atomcode 仓库中 docs/adr/0026-pictures-in-blocks-not-yet.md 这条 ADR架构决策记录的完整解读与源码级展开。它回答一个看似简单的问题为什么一个号称支持多种图形协议的 TUI最终决定不在界面块里画真实图片读完本文你将理解 atuiatomcode-tui的渲染管线为什么从不让终端滚动、for_screen那道不变量门如何挡住所有图像转义序列、业界ratatui-image、ghostty 等对图跟着文本滚这个问题各自给出的契约边界以及将来要重开这件事时两条可执行的路径分别是什么。背景问题是怎么来的一切始于欢迎块里的吉祥物。atomcode-tuix每次开新 session 会在会话流的第一段画一块欢迎内容——品牌行、吉祥物、工作目录与模型、几条上手命令提示见 docs/plans/2026-09-15-session-welcome-block-design.md。这块内容里的猫现在是字符画用▀▄█这类块元素拼出来的。用户的诉求非常合理能不能直接控制像素而不是字符那样猫会精细得多。而 TUI 的渲染能力枚举看起来已经准备好了在 crates/atomcode-tui/src/caps.rs 中caps::Graphics定义了四个变体pub enum Graphics { /// Characters only. #[default] None, /// DEC sixel. Only ever set by a surface that probed for it. Sixel, /// iTerm2s inline images. ITerm2, /// kittys protocol: PNG, animation, GPU-accelerated. Kitty, }Caps::detect也确实能从环境变量里认出 Kitty包括 iTerm2 / ghostty / WezTerm见 caps.rs#L347-L356。但事实是整个crates/atomcode-tui/src/里对Graphics只有枚举定义与detect的赋值——零个绘制点。Caps.graphics自己的 doc 就写着caps.rs#L168-L172Pictures, if any. Nothing draws one yet — the field is here because it belongs to the shield, and the shield is the thing being built.所以这条 ADR 要回答的核心问题是要不要把它接上结论是先经过逐条核验三条图形协议、再对照自己的滚动模型与业界做法最终决定不做——不是难是没有契约。三条协议逐条核过iTerm2ESC ] 1337 ; File …规范原文的参数只有name / size / width / height / preserveAspectRatio / inline。关键发现没有 id没有放置位置没有源矩形裁剪。它在光标所在处画画的框是width/height单位可以是字符格N、像素Npx、百分比N%、或auto然后把光标推进到自己占的行列数。不可裁剪。要把一张图钉在某一行、随对话滚走就得让它随内容移动——而协议里没有移动一个已存在的图这个动作只能重画。重画要重新计算光标位置而光标一旦被推走就得在每个图之后重置一次光标多一次绝对定位。与text::for_screen冲突。for_screencrates/atomcode-tui/src/text.rs#L80-L95会通过eat_escapetext.rs#L595-L620吃掉 ESC 序列它的不变量是「我们发出的任何字节都不移动光标」。OSC 1337 是一条不移动光标的 OSC……除了它画完图会把光标推进。iTerm2 的文档没说可以禁止这个推进对比 Kitty 的C1所以这条协议的语义与那个不变量直接冲突。KittyESC _G … ESC \规范核到的关键点有 id 与 placement可以给同一个图多次放置有源矩形裁剪x, y, w, h指定要显示的原图区域有C1放置图像时不移动光标默认C0会移规范里有一条硬要求「When scrolling the screen (such as when using index cursor movement commands, or scrolling through the history buffer), images must be scrolled along with text.」这最后一条正是它与我们自己的模型协调不了的地方——详见下一节。Sixelcaps.rs 的注释写得很准「Sixel cannot be [detected from the environment]它需要一次 DA1 查询与回复那是 I/O」。它实现代价最高调色板量化 RLE而且Warp 不支持它Warp 的图形支持是 iTerm2 协议 2025-03 起、Kitty 协议 2025-05 起。Graphics::Sixel在Caps::detect里永远不被赋值——只有探测过终端的 surface才可能把它设上。症结我们的滚动不是终端的滚动这是本条的实质结论不是实现难度。atui 画的是 alt-screen 上的固定网格每帧绝对寻址重画。在 crates/atomcode-tui/src/ansi.rs 的encode_rows每一行以\x1b[{row};{col}H绝对定位先ERASE_LINE\x1b[Kansi.rs#L26再写。滚动是宿主自己算偏移然后把该显示的行重新画到同样的绝对坐标上。encode_rows的注释把不变量说得很清楚ansi.rs#L734-L736Erase first, draw second, one row at a time. Erasing a row cannot scroll the screen and cannot reach the scrollback.所以我们从不让终端滚动。一帧里那些行的屏幕坐标一个字都不变变的是画在那里的内容。而 Kitty 协议要求的恰恰是「终端滚动文本时带着图像一起滚」。两条模型对不上我们从不用终端的滚动 ⇒ 终端永远不会替我们移动图像我们按帧重画 ⇒ 图像若不重画就留在原处而原处的内容已经换了图像不是单元格 ⇒Rows/LastPainted那套行字节没变就跳过的 diff 对它不成立载荷几百字节到几 KB每帧重发不可接受。for_screen的定位text.rs#L70-L79也印证了这一点——The last fence before the terminal, and the only one that can be不变量我们发出的任何字节都不移动光标必须由它唯一地守住。任何会推光标的图像序列都过不了这道门。结论这不是实现难是没有定义好的语义。三步可以把它变得有定义给Frame加不透明载荷、按帧重建 placement/相对锚点、把载荷排除出 diff但那是渲染管线的第二条通路不是欢迎块的一个细节——而在动手之前没有一个人验证过「在 Warp 上这么画到底会怎么样」。Warp 的额外一层它的渲染模型是逐块的Warp 不是一个纯网格终端输出被切成块ScriptExecution / output grid / prompt-header grid它在自己的 issue 与 PR 里明确把图像当作「可见的网格内容」来撑块高、并把图像完成事件路由到输出块。而 atui 的 TUI 是在固定网格上每秒重画多次的 alt-screen 应用。在 Warp 的模型里这更像一个持续重绘的块——图像 placement 的滚动与裁剪语义在这个交叠处两边的规范都没有定义。Warp 的 Kitty 支持本身也有已知边界静态放置可以af/aa动画帧与动画控制明确不支持Unicode 占位符也不支持——维护者自己说「there isnt much support internally for pushing kitty image protocol forward」。atui 不需要动画或占位符但这是个信号这条路径在 Warp 上会长期处于「能用但边缘」的状态。业界怎么做的2026-09-15 查证结论先说主流 Rust TUI 画图的做法是「图像是一块 TUI 必须跳过的格子矩形」而且它不滚。这条查证把上面的结论说清了——契约不是普遍不存在而是只对 rect-anchored 的图存在对随内容滚的图不存在。ratatui-imageRust TUI 画图的事实标准它的结构是Picker探测 →ProtocolType { Halfblocks, Sixel, Kitty, Iterm2 }→ 每个协议一个编码器模块。它的 doc 里有一段正是 atui 这个问题Some protocols, like Sixels, are essentially immediate-mode, but we still need to avoid the TUI from overwriting the image area, even with blank characters. Other protocols, like Kitty, are essentially stateful, but at least provide a way to re-render an image that has been loaded, at a different or same position.Since we have the font-size in pixels, we can precisely map the characters/cells/rows-columns that will be covered by the image and skip drawing over the image.「跳过那块格子的绘制」是它的核心机制靠两样东西支撑字体像素尺寸[16t]查询出来→ 才能算出图覆盖哪些行列图像是 widget有自己的 rect不是滚动内容。所以它不需要回答「图怎么跟着滚」——它的图不滚位置是布局给的。能力探测的完整配方cap_parsercap_parser是 ratatui-image 的内部模块不是独立 crate。它的查询序列是一套可抄的配方问什么发什么收什么Kitty 图形APC_Gi31…\x1b_Gi31;OK\x1b\Sixel / 矩形操作DA1\x1b[c\x1b[?62;4c4sixel、\x1b[?62;28c28rect ops单元格像素尺寸\x1b[16t\x1b[4;H;Wt查询结束DSR\x1b[0n\x1b[0n两条实操约束必须在进 alt screen 之后、开始读终端事件之前发否则吞掉输入并内置一份已知有 bug 的协议黑名单——Konsole的 Sixel、WezTerm的 Kittyratatui-image 的兼容表明确写着 WezTerm「would support Sixel and Kitty, but only iTerm2 actually works bug-free」。能引用的编码器这一层是真能抄/能引的协议纯 Rust 件形态Sixelicy_sixel100% Rust无 C 依赖sixel_encode(rgba, w, h, opts)量化用 quantetteWu Floyd-Steinberg可配像素长宽比与透明Kittykitty-graphics-protocol、little_kitty、kittageAPC 命令构造kittage覆盖除动画帧外的全部客户端侧iTerm2无需依赖base64 PNG——本项目已有base64与png依赖粘贴图片在用最值得照搬的是形态。indexable-inc/index的packages/kitty把这件事说清了it turns image bytes into theAPC _G ... STescape sequences ...and nothing else. It does not open a terminal, decode images, or do I/O: callers own those concerns and decide where the returnedStringis written.编码器是纯函数I/O 与放置归调用方。这就是引用一部分能力该有的边界——也正是本条说可以引的那一层。终端侧那份图跟着滚的完整模型在哪qwertty_term_vt::kittyghostty 的 Zig 实现移植到 Rust里有storage::ImageStorage——per-screen image map、placement map、字节上限淘汰、delete 分发以及exec光标跟踪的放置、unicodeU1占位符。它证明「滚动 图像」在终端侧是有完整模型的pin-anchored placements正是 kitty 规范那句必须随文本一起滚的实现。但它是终端侧输入是客户端发来的字节输出是画什么。atui 是客户端引不到它也不需要——答案是不让它滚。由此修正结论有契约的图是不滚的要滚的图没有契约原稿说没有定义好的语义更准确的说法是分形态的图的形态有契约吗rect-anchored一块固定矩形TUI 跳过不画有而且业界标准做法明确ratatui-image。落地要三件Frame支持这块 rect 不画、编码器、探测随内容滚欢迎块要的没有便携契约。Kitty 可靠 id 重新放置 删除旧 placement 做到Sixel 无 id只能重发而清掉旧的在格子模型里不可靠于是有一条决策相关的事实半块▀bg是唯一保住随对话滚走的路——因为它就是字符不需要跳过矩形、不需要探测、不需要编码器、滚动免费。任何像素路都强制把图变成 rect-anchored 的一块而那与欢迎块选定的语义随对话滚动见 docs/plans/2026-09-15-session-welcome-block-design.md §一直接冲突。所以欢迎块的猫永远走半块。像素路只对固定位置的图成立——若哪天要画工具输出里的图表那是一个独立于欢迎块的问题且那时应当按 ratatui-image 的形态做图 rect-anchored widget 跳过矩形而不是把它做成流内容。决策不做欢迎块用字符精度靠一格多像素决策是不做。具体地caps::Graphics保持现状枚举与detect不动Caps.graphics继续报它测到的值iTerm2/Kitty但没有任何绘制点。它不是半成品——它是一块已经量好、还没接线的表。欢迎块用字符精度靠一格多像素而不是图形协议█→ 一格一像素9×4▀▄█→ 一格两像素9×8。更正本条早先说这些字形是 Unicode-Neutral 宽度没有终端会把它们拉伸——按 UAX #112580..258F是AAmbiguous不是 Neutral。它们与 UI 其余部分同一假设全部框线2500..254B也是 Ambiguous所以用它们并不比画一个面板边框更危险。BrailleU2800–28FF→ 一格2×4 个点9×16。2026-09-15 查证更正本条早先写「Braille 是 Ambiguous 宽度iTerm2 默认拉双宽所以必须是一个 caps 门」——那句是错的来源是crates/atomcode-tuix/src/render/qr.rs的一条错注释。按 UAX #11EastAsianWidth.txt2800..28FF是NNeutral反倒是2580..258F█▀▄与全部框线2500..254B是AAmbiguous。所以 Braille不需要为宽度加 caps 门——它是候选里最安全的一档而▀▄█与 UI 其余部分同一假设。详见 docs/adr/0027-raster-is-a-cell-grid.md 决策③。这三档全部落在 docs/adr/0025-blocks-may-shape-by-terminal-capability.md 那条缝里——不碰Frame、不碰for_screen、不碰 diff、不碰滚动。这与业界一致ratatui-image 自己的Halfblocks兜底也是这一档它注明 should work in all terminals, even if the font size could not be detected, with a 4:8 pixel ratioopencode 的 logo 也是半块 投影。真要更精细缺的是一位 caps bit 而不是新管线Style已支持逐 span 的bgansi::sgr会把它写成48;5;n/48;2;r;g;bansi.rs#L457-L468。半块加投影opencode 那种_ 空格 bg 投影、^▀fg 主体 bg 投影只多需要「这个终端能画格背景」一位。这比图形协议便宜两个数量级。重开条件与第一步见下。本条不是永远不做是没有验证前不做。重开条件两条门槛不同的路查证之后分岔成两条路它们的门槛不同所以分开写。路一便宜且业界已走通图是 rect-anchored 的一块TUI 跳过它先决条件不是技术是一个产品选择图能不能不随内容滚能这条路就是已知的三件工程没有未知Frame支持这块 rect 不画——encode_rowsansi.rs#L732-L739对图占用的格子连ERASE_LINE都不发发了就把图擦了这正是 ratatui-image 那段 doc 的原话所指编码器Sixel 引icy_sixelKitty 引kittage/little_kittyiTerm2 自己拼base64 png两个依赖都已在探测照cap_parser的配方自己写——surface 层已经有读终端应答的机制读背景色用了 libcpoll/read 超时同一套在进 alt screen 之后、读事件之前发。唯一还需要观察的这个只能靠看ERASE_LINE与图的关系、以及图会不会把光标推走。一个约 20 行的最小复现就够——依次发三个协议各一条最小放置序列每次之后自己重画固定网格观察图是移动、复制还是残留以及光标是否被推走。路二贵且有未知图要随内容滚先决条件有人能说清图跟着内容滚在至少两个终端上的实际行为。这条路的关键是 Kitty 能用 id 重新放置 删除旧 placement而Sixel 没有 id只能重发且清掉旧的在格子模型里不可靠——所以路上大概率会收敛成Kitty 才行那就等于放弃了 Sixel 与 iTerm2 终端。这是两条路里更贵的那条因为未知在清旧的而不在画新的。先做路一的复现再决定要不要走路二——路一的观察同时是路二的前置。放弃了什么先随便接一个能用的协议比如在 Warp 上接 Kitty因为 Warp 支持它。放弃是因为在我们的滚动模型下它没有定义好的行为——接上去大概率表现为「滚动时图撕裂/残留/复制」而那时最难的不是修是说不清哪里错了因为没有一条规范管这个交叠处。而且查证表明问题不是选哪个协议是图该不该滚——先定这个协议只是实现细节。把 ratatui-image 当依赖引进来。它是 ratatui 的 widgetImage/StatefulImage实现ratatui::Widget而 atui 的帧是自己的Frame/Line/Span中间隔着一整套 ratatui 的类型。能引的是它下面的编码器与探测配方不是它这一层。给Caps加一个图像协议已探到的字段并让欢迎块按它分支。现在Graphics已经承担这个角色加第二份是同一个事实的第二个来源——而且现在知道能探到的东西比Graphics一个枚举多单元格像素尺寸、矩形操作、文本缩放真要探测就该一次探清并一起上报而不是逐个加字段。把 9×4 的字符猫当成够用了不再想这件事。本条明确留了口子▀▄█9×8与 Braille9×16都是零风险的精度提升且不依赖任何本条的结论。▀▄█应当做Braille 做成 caps 门。而这两位之上还有一档半块 投影opencode 的做法。已落地2026-09-15这一档做了Caps::cell_background与ShapeCaps::cell_background已加见 docs/adr/0025-blocks-may-shape-by-terminal-capability.md 的字段说明。欢迎块的猫用它画一格两像素的半身缺这一位时退化成一格一像素的实心块。判断环境的方式照 tuix 原样搬WT_SESSION/TERM_PROGRAM/jediterm。在源码里可以看到这条决策的完整落地形态crates/atomcode-tui/src/block.rs#L97-L109 定义了ShapeCaps { unicode, basic_glyphs, colors, cell_background }——cell_background的 doc 明确写着它是shape bit 而不是 colour bitthe one thing a block does differently when it is false is drawlesscrates/atomcode-tui/src/caps.rs#L261-L275 的cell_background_for实现探测Windows / HarmonyOS 直接为真其余看WT_SESSION/TERM_PROGRAM/VTE_VERSION/ xterm 系TERM声明crates/atomcode-tui/src/content.rs#L544-L626 的mascot函数就是那只半块猫drawable门colors ! None (unicode || basic_glyphs) cell_background不满足就整块不画(top, bottom)两像素映射成▀上像素/▄下像素/▀bg两像素都有的格子风格相同的 run 合并成一个 span 以省字节。失效条件若某天 atui 改为使用终端自身的滚动进 scrollback、用IND/RI/SU随文本滚图那么 Kitty 协议那条「随文本滚」的要求第一次变得可满足本条的结论要重新论证。这是最可能改变结论的一条路。若 Warp 支持了 Sixel或把它的块模型换成纯网格则逐块渲染那一层约束消失。若出现必须在块里画真图的硬需求比如工具输出里的图表、图片 diff这条路线会从锦上添花变成必须那时先做第一步观察而不是先写编码器。若text::for_screen的不变量被改写不再吃掉所有 ESC第二条通路的一半障碍就没了。相关文档与代码导航ADR 链docs/adr/0025-blocks-may-shape-by-terminal-capability.md —— 字符多像素这一档走的正是它开的那条缝ShapeCaps的完整论证docs/adr/0027-raster-is-a-cell-grid.md —— 0026 路一的字符格版本Raster含 UAX #11 宽度判据与 braille 更正docs/adr/0004-tui-stream-is-an-irreversible-block-sequence.md ——Frame是「行」的序列本条讨论的正是一个不是行的东西docs/adr/0006-tui-is-full-screen-not-inline.md —— alt-screen 全屏我们从不让终端滚动的由来源码crates/atomcode-tui/src/caps.rs ——Graphics枚举与Caps.graphics的 doc、cell_background_for探测crates/atomcode-tui/src/ansi.rs ——encode_rows滚动是重画不是滚动sgr写出格背景色crates/atomcode-tui/src/text.rs ——for_screen/eat_escape唯一挡住 ESC 的那道门crates/atomcode-tui/src/block.rs ——ShapeCaps与渲染缓存按(width, ShapeCaps)换键crates/atomcode-tui/src/content.rs ——mascot欢迎块半块猫的实际绘制crates/atomcode-tuix/src/render/qr.rs —— 字符多像素的两个先例▀▄█与 Braille注意它的注释里有一条错的它说 Braille 是 Ambiguous 宽度按 UAX #11 是 NNeutral设计文档docs/plans/2026-09-15-session-welcome-block-design.md §四 —— 猫的形状。赞分享人工智能AI Agent代码智能体自主智能体MCP ClientsCLI【免费下载链接】atomcodeClaude Code 的开源替代方案。连接任意大模型编辑代码运行命令自动验证 — 全自动执行。用 Rust 构建极致性能。 An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get Started项目地址https://gitcode.com/atomgit_atomcode/atomcode点击查看免费下载相关推荐onefetch-image 终端图片显示库Sixel、Kitty 与 iTerm2 协议实现与集成指南onefetch image 终端图片显示库Sixel、Kitty 与 iTerm2 协议实现与集成指南 onefetch image 是 onefetch开发工具Valence性能优化秘籍如何支持数千玩家同时在线不卡顿Valence性能优化秘籍如何支持数千玩家同时在线不卡顿 Valence是一款基于Rust的Minecraft服务器框架以其卓越的性能表现著称能够轻松支持5个理由告诉你为什么Heroicons是最受欢迎的SVG图标库 5个理由告诉你为什么Heroicons是最受欢迎的SVG图标库 还在为网站设计寻找完美的图标吗Heroicons这个由Tailwind CSS团队精心打UI组件上一篇PaddleNLP 模型下载指南allenai/specter 科学文献文档级嵌入模型SPECTER获取与使用下一篇如何突破网盘限速终极网盘下载加速工具使用指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考