feat: 新增 AssistantRender hook 事件——LLM 输出显示层重渲染

Author: viggo-podCreated Sep 6, 2026Updated Sep 6, 2026

发帖前必读

  • 我已经搜索过 现有 Issues,没有找到重复。
  • 这是功能建议,不是 Bug 报告或使用问题。
  • 使用问题请前往 Discussions

要解决的问题

LLM 的文本输出与终端显示强耦合:模型输出的原文直接进渲染层,无法在「显示层」做重渲染。典型场景:

  • <mermaid> 标签 / mermaid ``` 代码块在回合结束后渲染成 ASCII 图就地展示
  • 表格美化、长输出折叠、脱敏展示等显示层变换

现有 hook 事件都无法安全地只改「显示」:在 Stop / PostToolUse 等钩子里改写消息会污染 transcript 与模型上下文,破坏 prompt cache 与对话一致性。

建议方案

新增第 28 个 hook 事件 AssistantRender

  • 触发时机:回合末(Stop hooks 之后)逐条 assistant 消息触发,输入 message_id + text_blocks(非空 text 块原文与 block_index
  • 输出协议hookSpecificOutput.updatedBlocks = [{blockIndex, text}],未列出的块显示原文
  • 仅替换显示层:结果写入内容寻址缓存(sha1(原文)[:16]),transcript 与模型上下文始终保留原文
  • 单脚本语义:仅支持注册一个有效 hook(去重后计数 >1 时发可见警告并整体不渲染)——渲染不是管道,各 hook 拿到的都是原始 text_blocks,多脚本只会重复渲染并互相覆盖
  • 零开销短路:无 hook 注册时不构造输入、不 spawn
  • 安全边界:30s 超时(显示路径钩子收紧);/clear--resume/--continue 时清空渲染缓存

使用示例(settings.json):

json
{
  "hooks": {
    "AssistantRender": [
      { "hooks": [{ "type": "command", "command": "node ~/plugins/mermaid-inline.mjs" }] }
    ]
  }
}

hook 从 stdin 收到:

json
{ "hook_event_name": "AssistantRender", "message_id": "msg_...", "text_blocks": [{ "block_index": 0, "text": "<mermaid>...</mermaid>" }] }

stdout 返回:

json
{ "continue": true, "hookSpecificOutput": { "hookEventName": "AssistantRender", "updatedBlocks": [{ "blockIndex": 0, "text": "ASCII 图" }] } }

即可把该 text 块的终端显示替换为渲染结果。

配套实现:renderCache 模块、MessageRow renderCacheVersion / 重绘纪元重绘机制(解决 OffscreenFreeze 静态定格行的重印)、/clear 缓存清理接线、全类型层注册(HOOK_EVENTS ×4、zod Schema、SDK 生成类型)。

考虑过的替代方案

  • 在 Stop / PostToolUse 钩子里改写消息:污染 transcript 与模型上下文,破坏 prompt cache 与对话一致性,放弃。
  • 把渲染器硬编码进核心:不可扩展,社区无法挂自己的渲染管道,放弃。
  • 多 hook 管道式串联:渲染不是管道(各 hook 只见原始输入,看不到上一个 hook 的输出),串联语义不成立,故采用单脚本语义。

补充信息

  • 测试:新增 3 个测试文件共 13 用例(执行器 0 / 1 / >1 注册三等价类、内容寻址缓存行为、memo 比较器版本号机制),bun test 全过;tsc --noEmit 零错误;biome 通过
  • 真实场景端到端验证过:mermaid 源码块在回合末被渲染类 hook 就地替换为 ASCII 图,transcript 中始终为源码,/clear 后缓存正确失效
  • 实现见配套 PR

Source: claude-code-best/claude-code