Claude Code 源码分析Claude Code 源码分析
首页
源码统计
系统架构
UML 图表
工具系统
CodeGraph
首页
源码统计
系统架构
UML 图表
工具系统
CodeGraph
  • 概览

    • Claude Code 源码分析
    • 源码统计
    • CodeGraph 图谱
  • 架构

    • 系统架构
    • UML 图表索引
    • 查询引擎
    • 核心流程
    • 消息系统
    • 状态管理
  • 功能模块

    • 工具系统
    • 斜杠命令
    • 服务层
    • MCP 协议
    • Skills 技能
    • 子代理系统
  • 分层深度

    • 入口层
    • UI / Ink 层
    • utils 基础设施
    • 桥接 / 远程
    • 上下文压缩
  • 原理与安全

    • 底层原理
    • 技术难点
    • 权限与安全
    • 内部机制
    • 遥测与分析
  • 深度专题

    • Hooks 系统
    • 插件系统
    • 记忆系统
    • API 通信层
    • Ink 终端 UI
    • 认证系统
    • 构建与发布
    • 术语表
  • 调用分析

    • 调用链分析
    • 核心文件索引
  • 模块详解

    • utils

      • 模块: utils
      • messages · 消息工厂与规范化
      • session-storage · JSONL 会话持久化
      • permissions · 工具权限决策
      • shell-hooks · 用户 Shell Hook 系统
    • components

      • 模块: components
      • REPL · 主屏编排
      • messages · 消息行渲染
      • PermissionRequest · 权限弹窗
      • PromptInput · 底部输入
    • services

      • 模块: services
      • api-claude · Anthropic API 流式与重试
      • mcp-client · MCP 连接与工具调用
      • compact · 上下文压缩与自动触发
      • analytics · GrowthBook、Datadog 与 1P 事件
    • tools

      • 模块: tools
      • tool-interface · Tool 契约与注册表
      • bash-tool · Shell 执行与权限
      • streaming-executor · 流式工具并发调度
      • agent-tool · 子 Agent 委派
    • commands

      • 模块: commands
      • command-registry · commands.ts 注册与分派
      • model-command · /model 模型选择
      • mcp-commands · /mcp 服务器管理
      • compact-memory-commands · /compact 与 /memory
    • ink

      • 模块: ink
      • Ink 渲染管线 · Screen 与终端输出
      • 终端事件 · resize、paste、stdin
      • Ink Hooks · 输入、搜索、终端状态
      • Ink 组件 · Box、Text、ScrollBox 原语
    • hooks

      • 模块: hooks
      • useCanUseTool · 权限 UI 接缝
      • 输入与快捷键 Hook
      • 合并态 Hook(MCP + 本地)
      • notifs 通知 Hook
    • bridge

      • 模块: bridge
      • repl-bridge · REPL 桥初始化与传输
      • bridge-messaging · 桥消息路由与入站处理
      • remote-bridge-core · env-less 核心与守护主循环
      • bridge-permissions-ui · 权限、API 与 TUI
    • cli

      • 模块: cli
      • Structured IO · NDJSON SDK 协议
      • CLI Transports · Session Ingress 传输层
      • CLI Handlers · 子命令懒加载实现
      • Update & Upload · 自更新与串行上传原语
    • screens

      • 模块: screens
      • REPL 屏 · Screen 类型与顶层路由
      • ResumeConversation · 会话恢复选择器
      • Doctor · 安装诊断全屏
    • entrypoints

      • 模块: entrypoints
      • cli-entrypoint · Bootstrap 与快路径
      • sdk-types · core / control / runtime 类型体系
      • mcp-entrypoint · MCP stdio 服务器
      • sandbox-types · 沙箱配置单一真相源
    • skills

      • 模块: skills
      • skills-loading · 磁盘加载与 bundled 注册表
      • bundled-skills · 内置 skill 与 initBundledSkills
      • mcp-skills · MCP prompt 转 skill
      • skill-tool-integration · SkillTool 与命令注册
    • types

      • 模块: types
      • message-types · Message 联合与 content blocks
      • tool-permission-types · Tool、Permission、Command 类型
      • api-sdk-types · API 与 Hooks 协议类型
      • misc-types · ids、plugin、generated 与其余类型
    • tasks

      • 模块: tasks
      • local-agent-task · 本地 Agent 与主会话后台化
      • remote-agent-task · 远程 CCR 与 In-Process Teammate
      • shell-workflow-tasks · Bash 后台、Workflow 与 stopTask
      • dream-monitor-tasks · Dream、Monitor MCP 与 pill 文案
    • keybindings

      • 模块: keybindings
      • keybinding-registry · 注册、Provider 与 useKeybinding
      • default-bindings · 默认键位表与平台差异
      • command-bindings · command:* 动态斜杠命令绑定
      • vim-bindings · Vim 模式与 keybindings 边界
    • memdir

      • 模块: memdir
      • memdir-core · 路径、加载与 MEMORY.md
      • memory-extraction · extractMemories 与 SessionMemory
      • memdir-commands · /memory、/remember 与命令集成
    • state

      • 模块: state
      • app-state-core · store、AppState 类型与 Provider
      • app-state-selectors · selectors 与 onChangeAppState
      • teammate-state · 队友视图与 swarm 状态
      • state-boundaries · bootstrap、sessionStorage、FileStateCache
    • query

      • 模块: query
      • query config 与 deps · 配置快照与依赖注入
      • query tokenBudget · +500k 自动续跑
      • query transitions · Continue / Terminal 状态机
      • query stopHooks · Stop 事件与 turn 结束编排
  • 模块详解(扩展)

    • messages · 消息工厂与规范化
    • session-storage · JSONL 会话持久化
    • permissions · 工具权限决策
    • shell-hooks · 用户 Shell Hook 系统
    • REPL · 主屏编排
    • messages · 消息行渲染
    • PermissionRequest · 权限弹窗
    • PromptInput · 底部输入
    • api-claude · Anthropic API 流式与重试
    • mcp-client · MCP 连接与工具调用
    • compact · 上下文压缩与自动触发
    • analytics · GrowthBook、Datadog 与 1P 事件
    • tool-interface · Tool 契约与注册表
    • bash-tool · Shell 执行与权限
    • streaming-executor · 流式工具并发调度
    • agent-tool · 子 Agent 委派
    • command-registry · commands.ts 注册与分派
    • model-command · /model 模型选择
    • mcp-commands · /mcp 服务器管理
    • compact-memory-commands · /compact 与 /memory
    • Ink 渲染管线 · Screen 与终端输出
    • 终端事件 · resize、paste、stdin
    • Ink Hooks · 输入、搜索、终端状态
    • Ink 组件 · Box、Text、ScrollBox 原语
    • useCanUseTool · 权限 UI 接缝
    • 输入与快捷键 Hook
    • 合并态 Hook(MCP + 本地)
    • notifs 通知 Hook
    • repl-bridge · REPL 桥初始化与传输
    • bridge-messaging · 桥消息路由与入站处理
    • remote-bridge-core · env-less 核心与守护主循环
    • bridge-permissions-ui · 权限、API 与 TUI
    • Structured IO · NDJSON SDK 协议
    • CLI Transports · Session Ingress 传输层
    • CLI Handlers · 子命令懒加载实现
    • Update & Upload · 自更新与串行上传原语
    • REPL 屏 · Screen 类型与顶层路由
    • ResumeConversation · 会话恢复选择器
    • Doctor · 安装诊断全屏
    • cli-entrypoint · Bootstrap 与快路径
    • sdk-types · core / control / runtime 类型体系
    • mcp-entrypoint · MCP stdio 服务器
    • sandbox-types · 沙箱配置单一真相源
    • skills-loading · 磁盘加载与 bundled 注册表
    • bundled-skills · 内置 skill 与 initBundledSkills
    • mcp-skills · MCP prompt 转 skill
    • skill-tool-integration · SkillTool 与命令注册
    • message-types · Message 联合与 content blocks
    • tool-permission-types · Tool、Permission、Command 类型
    • api-sdk-types · API 与 Hooks 协议类型
    • misc-types · ids、plugin、generated 与其余类型
    • local-agent-task · 本地 Agent 与主会话后台化
    • remote-agent-task · 远程 CCR 与 In-Process Teammate
    • shell-workflow-tasks · Bash 后台、Workflow 与 stopTask
    • dream-monitor-tasks · Dream、Monitor MCP 与 pill 文案
    • keybinding-registry · 注册、Provider 与 useKeybinding
    • default-bindings · 默认键位表与平台差异
    • command-bindings · command:* 动态斜杠命令绑定
    • vim-bindings · Vim 模式与 keybindings 边界
    • memdir-core · 路径、加载与 MEMORY.md
    • memory-extraction · extractMemories 与 SessionMemory
    • memdir-commands · /memory、/remember 与命令集成
    • app-state-core · store、AppState 类型与 Provider
    • app-state-selectors · selectors 与 onChangeAppState
    • teammate-state · 队友视图与 swarm 状态
    • state-boundaries · bootstrap、sessionStorage、FileStateCache
    • query config 与 deps · 配置快照与依赖注入
    • query tokenBudget · +500k 自动续跑
    • query transitions · Continue / Terminal 状态机
    • query stopHooks · Stop 事件与 turn 结束编排
  • 工具详解

    • tool-interface · Tool 契约与注册表
    • tool-permission-types · Tool、Permission、Command 类型
    • 工具: Bash
    • 工具: PowerShell
    • 工具: Agent
    • 工具: LSP
    • 工具: FileEdit
    • 工具: FileRead
    • 工具: Skill
    • 工具: WebFetch
    • 工具: MCP
    • 工具: SendMessage
    • 工具: FileWrite
    • 工具: Config
    • 工具: Grep
    • 工具: Brief
    • 工具: ExitPlanMode
    • 工具: ToolSearch
    • 工具: NotebookEdit
    • 工具: TaskOutput
    • 工具: WebSearch
    • 工具: ScheduleCron

本章总览

components/messages/ 目录(45+ 文件)实现 transcript 中每一类 Message 的 Ink 渲染。列表编排由 Messages.tsx + MessageRow.tsx 完成,叶子组件只关心单条数据的展示。本章要求你能从 API message type 追到对应 TSX 文件,并理解虚拟滚动与 brief 模式的性能权衡。

学完本章你应该能

  • 画出 Messages → MessageRow → messages/* 的分发链路
  • 区分 user / assistant / system / progress 消息组件职责
  • 说明 UserPromptMessage 截断策略与 brief layout 条件
  • 理解 AssistantToolUseMessage 如何关联 tool 定义与 inProgress 状态
  • 掌握 UserToolResultMessage 对 success/error/reject/cancel 的分流

核心概念(先读懂这些)

RenderableMessage 与 NormalizedMessage

引擎层 Message 进入 UI 前会 normalize、compact、分组(如 collapsed_read_search)。Messages.tsx 过滤 progress 类型、计算 lookups(toolUseByToolUseID),再交给 MessageRow。叶子组件收到的 param 多为 Anthropic SDK 的 block param(TextBlockParam、ToolUseBlockParam 等),而非原始 JSON。

为何 UserPromptMessage 要截断 1 万字符

全屏 Ink 每帧对挂载的 Text 节点做 wrap/output。管道输入超大文件会产生单行巨型 user message,导致 500ms+ 按键延迟。React.memo 挡不住 Ink 输出遍历。head+tail 截断保留用户落在末尾的真实问题。非全屏用 Static 打印到 scrollback 可规避此问题。

建议学习步骤

  1. 阅读 Messages.renderMessageRow(源码块 A)
  2. 打开 MessageRow 分发逻辑(源码块 B)
  3. 对照 UserPromptMessage 截断常量(源码块 C)
  4. 阅读 AssistantToolUseMessage 的 findToolByName(源码块 D)
  5. 跟踪 UserToolResultMessage 分支(源码块 E、F)

常见误区

注意

streamingText 在 Messages 末尾单独渲染,不在 renderableMessages 数组内

注意

isBriefOnly 时许多 assistant 块隐藏,勿在 brief 模式下调 UI 回归

注意

toolUseConfirmQueue 传给 Messages 是为行内指示,不等于 PermissionRequest 弹窗

消息 UI 在架构中的位置

Claude Code 的「聊天记录」并非 HTML DOM,而是 Ink 终端组件树:

REPL
  └─ Messages (props: messages, tools, streamingText, ...)
        ├─ VirtualMessageList (fullscreen + gate on)
        │     └─ renderItem = renderMessageRow
        └─ flatMap(renderMessageRow) (non-virtual)
              └─ MessageRow
                    └─ switch(message.type) → messages/* 或 tools 自带 UI

messages/ 目录命名与 message.type 或 content block 类型对应,例如:

类型 / 场景组件
用户文本UserTextMessage → UserPromptMessage
用户 tool_resultUserToolResultMessage/*
助手 tool_useAssistantToolUseMessage
助手 thinkingAssistantThinkingMessage
系统错误SystemAPIErrorMessage
Plan 审批PlanApprovalMessage

新增消息变体时,先改 types/message.ts 与 normalize 管道,再补 MessageRow 分支与叶子文件。

Messages.tsx:列表编排

Messages 组件负责:

  1. 截断与展开:hiddenMessageCount、showAllInTranscript、disableRenderCap([ 键 dump 全量 scrollback)
  2. 虚拟滚动门控:virtualScrollRuntimeGate 决定 VirtualMessageList vs flatMap
  3. 搜索索引:extractSearchText 优先调用 tool.extractSearchText,缓存 lowercase 字符串
  4. unseen divider:新消息分隔线插入 renderMessageRow

下列源码展示 renderMessageRow 如何包装 MessageActionsSelectedContext,并在 divider 位置插入 Divider。

源码引用: src/components/Messages.tsx · 第 614–637 行(共 1159 行)

 614|         briefToolNames.length > 0 && !isTranscriptMode
 615|           ? isBriefOnly
 616|             ? filterForBriefTool(messagesToShowNotTruncated, briefToolNames)
 617|             : dropTextToolNames.length > 0
 618|               ? dropTextInBriefTurns(
 619|                   messagesToShowNotTruncated,
 620|                   dropTextToolNames,
 621|                 )
 622|               : messagesToShowNotTruncated
 623|           : messagesToShowNotTruncated
 624| 
 625|       const messagesToShow = shouldTruncate
 626|         ? briefFiltered.slice(-MAX_MESSAGES_TO_SHOW_IN_TRANSCRIPT_MODE)
 627|         : briefFiltered
 628| 
 629|       const hasTruncatedMessages =
 630|         shouldTruncate &&
 631|         briefFiltered.length > MAX_MESSAGES_TO_SHOW_IN_TRANSCRIPT_MODE
 632| 
 633|       const { messages: groupedMessages } = applyGrouping(
 634|         messagesToShow,
 635|         tools,
 636|         verbose,
 637|       )

源码引用: src/components/Messages.tsx · 第 699–701 行(共 1159 行)

 699|     if (!unseenDivider) return -1
 700|     const prefix = unseenDivider.firstUnseenUuid.slice(0, 24)
 701|     return renderableMessages.findIndex(m => m.uuid.slice(0, 24) === prefix)

虚拟滚动与流式 sibling

VirtualMessageList 接收 itemKey=${uuid}-${conversationId},避免跨会话复用错误 fiber。

流式文本 不 进入 renderableMessages:Messages 在列表后单独渲染 streamingText 的 StreamingMarkdown,避免虚拟列表高度抖动。thinking 流同理用 AssistantThinkingMessage。

终端 progress bar(OSC)由 hasToolsInProgress 驱动,与消息行渲染解耦。

源码引用: src/components/Messages.tsx · 第 703–718 行(共 1159 行)

 703| 
 704|   const selectedIdx = useMemo(() => {
 705|     if (!cursor) return -1
 706|     return renderableMessages.findIndex(m => m.uuid === cursor.uuid)
 707|   }, [cursor, renderableMessages])
 708| 
 709|   // Fullscreen: click a message to toggle verbose rendering for it. Keyed by
 710|   // tool_use_id where available so a tool_use and its tool_result (separate
 711|   // rows) expand together; falls back to uuid for groups/thinking. Stale keys
 712|   // are harmless — they never match anything in renderableMessages.
 713|   const [expandedKeys, setExpandedKeys] = useState<ReadonlySet<string>>(
 714|     () => new Set(),
 715|   )
 716|   const onItemClick = useCallback((msg: RenderableMessage) => {
 717|     const k = expandKey(msg)
 718|     setExpandedKeys(prev => {

源码引用: src/components/Messages.tsx · 第 649–676 行(共 1159 行)

 649| 
 650|       const hiddenMessageCount =
 651|         messagesToShowNotTruncated.length -
 652|         MAX_MESSAGES_TO_SHOW_IN_TRANSCRIPT_MODE
 653| 
 654|       return {
 655|         collapsed,
 656|         lookups,
 657|         hasTruncatedMessages,
 658|         hiddenMessageCount,
 659|       }
 660|     }, [
 661|       verbose,
 662|       normalizedMessages,
 663|       isTranscriptMode,
 664|       syntheticStreamingToolUseMessages,
 665|       shouldTruncate,
 666|       tools,
 667|       isBriefOnly,
 668|     ])
 669| 
 670|   // Cheap slice — only runs when scroll range or slice config changes.
 671|   const renderableMessages = useMemo(() => {
 672|     // Safety cap for the non-virtualized render path. Applied here (not at
 673|     // the JSX site) so renderMessageRow's index-based lookups and
 674|     // dividerBeforeIndex compute on the same array. VirtualMessageList
 675|     // never sees this slice — virtualScrollRuntimeGate is constant for the
 676|     // component's lifetime (scrollRef is either always passed or never).

MessageRow:单行分发枢纽

MessageRow.tsx(约 380 行)是 messages/ 的「路由器」。它对每条 RenderableMessage:

  • 计算 isUserContinuation(连续 user 块合并边距)
  • 传递 lookups、inProgressToolUseIDs、streamingToolUseIDs
  • 调用各 render* 函数或工具自带 render

导出 areMessageRowPropsEqual 与 React.memo 严格控制重渲染——注释强调不要把整个 renderableMessages 数组塞进 props,否则 React Compiler memoCache 会累积历史版本(7 轮会话可达 1–2MB)。

阅读 MessageRow 时建议对照 types/message.ts 的 RenderableMessage 联合类型。

源码引用: src/components/MessageRow.tsx · 第 47–55 行(共 358 行)

  47|   isLoading: boolean
  48|   lookups: ReturnType<typeof buildMessageLookups>
  49| }
  50| 
  51| /**
  52|  * Scans forward from `index+1` to check if any "real" content follows. Used to
  53|  * decide whether a collapsed read/search group should stay in its active
  54|  * (grey dot, present-tense "Reading…") state while the query is still loading.
  55|  *

源码引用: src/components/MessageRow.tsx · 第 93–110 行(共 358 行)

  93|     if (msg?.type === 'system' || msg?.type === 'attachment') {
  94|       continue
  95|     }
  96|     // Tool results arrive while the collapsed group is still being built
  97|     if (msg?.type === 'user') {
  98|       const content = msg.message.content[0]
  99|       if (content?.type === 'tool_result') {
 100|         continue
 101|       }
 102|     }
 103|     // Collapsible grouped_tool_use messages arrive transiently before being
 104|     // merged into the current collapsed group on the next render cycle
 105|     if (msg?.type === 'grouped_tool_use') {
 106|       const firstInput = msg.messages[0]?.message.content[0]?.input
 107|       if (
 108|         getToolSearchOrReadInfo(msg.toolName, firstInput, tools).isCollapsible
 109|       ) {
 110|         continue

UserPromptMessage:用户输入展示

UserTextMessage 在需要时委托 UserPromptMessage 渲染 TextBlockParam。

核心工程决策:

  • MAX_DISPLAY_CHARS = 10000,head/tail 各 2500,中间显示隐藏行数
  • brief layout:KAIROS feature 下读取 isBriefOnly、viewingAgentTaskId,切换为 label 风格(无 userMessageBackground)
  • MessageActionsSelectedContext:选中时 backgroundColor 为 messageActionsBackground

下列源码含 feature() 门控的 useAppState,external 构建不会订阅 brief 相关 store。

源码引用: src/components/messages/UserPromptMessage.tsx · 第 20–78 行(共 120 行)

  20| 
  21| // Hard cap on displayed prompt text. Piping large files via stdin
  22| // (e.g. `cat 11k-line-file | claude`) creates a single user message whose
  23| // <Text> node the fullscreen Ink renderer must wrap/output on every frame,
  24| // causing 500ms+ keystroke latency. React.memo skips the React render but
  25| // the Ink output pass still iterates the full mounted text. Non-fullscreen
  26| // avoids this via <Static> (print-and-forget to terminal scrollback).
  27| // Head+tail because `{ cat file; echo prompt; } | claude` puts the user's
  28| // actual question at the end.
  29| const MAX_DISPLAY_CHARS = 10_000
  30| const TRUNCATE_HEAD_CHARS = 2_500
  31| const TRUNCATE_TAIL_CHARS = 2_500
  32| 
  33| export function UserPromptMessage({
  34|   addMargin,
  35|   param: { text },
  36|   isTranscriptMode,
  37|   timestamp,
  38| }: Props): React.ReactNode {
  39|   // REPL.tsx passes isBriefOnly={viewedTeammateTask ? false : isBriefOnly}
  40|   // but that prop isn't threaded this deep — replicate the override by
  41|   // reading viewingAgentTaskId directly. Computed here (not in the child)
  42|   // so the parent Box can drop its backgroundColor: in brief mode the
  43|   // child renders a label-style layout, and Box backgroundColor paints
  44|   // behind children unconditionally (they can't opt out).
  45|   //
  46|   // Hooks stay INSIDE feature() ternaries so external builds don't pay
  47|   // the per-scrollback-message store subscription (useSyncExternalStore
  48|   // bypasses React.memo). Runtime-gated like isBriefEnabled() but inlined
  49|   // to avoid pulling BriefTool.ts → prompt.ts tool-name strings into
  50|   // external builds.
  51|   const isBriefOnly =
  52|     feature('KAIROS') || feature('KAIROS_BRIEF')
  53|       ? // biome-ignore lint/correctness/useHookAtTopLevel: feature() is a compile-time constant
  54|         useAppState(s => s.isBriefOnly)
  55|       : false
  56|   const viewingAgentTaskId =
  57|     feature('KAIROS') || feature('KAIROS_BRIEF')
  58|       ? // biome-ignore lint/correctness/useHookAtTopLevel: feature() is a compile-time constant
  59|         useAppState(s => s.viewingAgentTaskId)
  60|       : null
  61|   // Hoisted to mount-time — per-message component, re-renders on every scroll.
  62|   const briefEnvEnabled =
  63|     feature('KAIROS') || feature('KAIROS_BRIEF')
  64|       ? // biome-ignore lint/correctness/useHookAtTopLevel: feature() is a compile-time constant
  65|         useMemo(() => isEnvTruthy(process.env.CLAUDE_CODE_BRIEF), [])
  66|       : false
  67|   const useBriefLayout =
  68|     feature('KAIROS') || feature('KAIROS_BRIEF')
  69|       ? (getKairosActive() ||
  70|           (getUserMsgOptIn() &&
  71|             (briefEnvEnabled ||
  72|               getFeatureValue_CACHED_MAY_BE_STALE(
  73|                 'tengu_kairos_brief',
  74|                 false,
  75|               )))) &&
  76|         isBriefOnly &&
  77|         !isTranscriptMode &&
  78|         !viewingAgentTaskId

源码引用: src/components/messages/UserTextMessage.tsx · 第 18–20 行(共 198 行)

  18| import { InterruptedByUser } from '../InterruptedByUser.js'
  19| import { MessageResponse } from '../MessageResponse.js'
  20| import { UserAgentNotificationMessage } from './UserAgentNotificationMessage.js'

AssistantToolUseMessage:工具调用行

助手发起 tool_use 时,transcript 显示 AssistantToolUseMessage:

  1. findToolByName(tools, param.name) 定位 Tool 定义
  2. inputSchema.safeParse 校验 input,失败仍展示但无 userFacing 配色
  3. 结合 inProgressToolUseIDs 显示 ToolUseLoader、HookProgressMessage
  4. classifier 检查状态影响「Attempting to auto-approve」类文案(与 permissions 联动)

该组件连接 Tool.js 与 messages/:工具可通过 isTransparentWrapper 改变行样式。verbose 模式展开更多调试字段。

源码引用: src/components/messages/AssistantToolUseMessage.tsx · 第 21–80 行(共 327 行)

  21| import { useSelectedMessageBg } from '../messageActions.js'
  22| import { SentryErrorBoundary } from '../SentryErrorBoundary.js'
  23| import { ToolUseLoader } from '../ToolUseLoader.js'
  24| import { HookProgressMessage } from './HookProgressMessage.js'
  25| 
  26| type Props = {
  27|   param: ToolUseBlockParam
  28|   addMargin: boolean
  29|   tools: Tools
  30|   commands: Command[]
  31|   verbose: boolean
  32|   inProgressToolUseIDs: Set<string>
  33|   progressMessagesForMessage: ProgressMessage[]
  34|   shouldAnimate: boolean
  35|   shouldShowDot: boolean
  36|   inProgressToolCallCount?: number
  37|   lookups: ReturnType<typeof buildMessageLookups>
  38|   isTranscriptMode?: boolean
  39| }
  40| 
  41| export function AssistantToolUseMessage({
  42|   param,
  43|   addMargin,
  44|   tools,
  45|   commands,
  46|   verbose,
  47|   inProgressToolUseIDs,
  48|   progressMessagesForMessage,
  49|   shouldAnimate,
  50|   shouldShowDot,
  51|   inProgressToolCallCount,
  52|   lookups,
  53|   isTranscriptMode,
  54| }: Props): React.ReactNode {
  55|   const terminalSize = useTerminalSize()
  56|   const [theme] = useTheme()
  57|   const bg = useSelectedMessageBg()
  58|   const pendingWorkerRequest = useAppStateMaybeOutsideOfProvider(
  59|     state => state.pendingWorkerRequest,
  60|   )
  61|   const isClassifierCheckingRaw = useIsClassifierChecking(param.id)
  62|   const permissionMode = useAppStateMaybeOutsideOfProvider(
  63|     state => state.toolPermissionContext.mode,
  64|   )
  65|   // strippedDangerousRules is set by stripDangerousPermissionsForAutoMode
  66|   // (even to {}) whenever auto is active, and cleared by restoreDangerousPermissions
  67|   // on deactivation — a reliable proxy for isAutoModeActive() during plan.
  68|   // prePlanMode would be stale after transitionPlanAutoMode deactivates mid-plan.
  69|   const hasStrippedRules = useAppStateMaybeOutsideOfProvider(
  70|     state => !!state.toolPermissionContext.strippedDangerousRules,
  71|   )
  72|   const isAutoClassifier =
  73|     permissionMode === 'auto' || (permissionMode === 'plan' && hasStrippedRules)
  74|   const isClassifierChecking =
  75|     "external" === 'ant' &&
  76|     isClassifierCheckingRaw &&
  77|     permissionMode !== 'auto'
  78| 
  79|   // Memoize on param identity (stable — from the persisted message object).
  80|   // Zod safeParse allocates per call, and some tools' userFacingName()

源码引用: src/components/messages/AssistantToolUseMessage.tsx · 第 35–60 行(共 327 行)

  35|   shouldShowDot: boolean
  36|   inProgressToolCallCount?: number
  37|   lookups: ReturnType<typeof buildMessageLookups>
  38|   isTranscriptMode?: boolean
  39| }
  40| 
  41| export function AssistantToolUseMessage({
  42|   param,
  43|   addMargin,
  44|   tools,
  45|   commands,
  46|   verbose,
  47|   inProgressToolUseIDs,
  48|   progressMessagesForMessage,
  49|   shouldAnimate,
  50|   shouldShowDot,
  51|   inProgressToolCallCount,
  52|   lookups,
  53|   isTranscriptMode,
  54| }: Props): React.ReactNode {
  55|   const terminalSize = useTerminalSize()
  56|   const [theme] = useTheme()
  57|   const bg = useSelectedMessageBg()
  58|   const pendingWorkerRequest = useAppStateMaybeOutsideOfProvider(
  59|     state => state.pendingWorkerRequest,
  60|   )

UserToolResultMessage:结果分流

tool 执行完成后,user 消息携带 tool_result block,由 UserToolResultMessage 分发:

条件子组件
content 以 CANCEL_MESSAGE 开头UserToolCanceledMessage
REJECT / INTERRUPTUserToolRejectMessage
is_errorUserToolErrorMessage
默认UserToolSuccessMessage

useGetToolFromMessages 通过 lookups 找回对应 tool_use 块与 Tool 实例。缺失 toolUse 时返回 null(静默跳过,避免崩溃)。

GroupedToolUseContent、CollapsedReadSearchContent 等处理聚合展示,减少连续 Read/Grep 的视觉噪音。

源码引用: src/components/messages/UserToolResultMessage/UserToolResultMessage.tsx · 第 12–80 行(共 102 行)

  12|   REJECT_MESSAGE,
  13| } from '../../../utils/messages.js'
  14| import { UserToolCanceledMessage } from './UserToolCanceledMessage.js'
  15| import { UserToolErrorMessage } from './UserToolErrorMessage.js'
  16| import { UserToolRejectMessage } from './UserToolRejectMessage.js'
  17| import { UserToolSuccessMessage } from './UserToolSuccessMessage.js'
  18| import { useGetToolFromMessages } from './utils.js'
  19| 
  20| type Props = {
  21|   param: ToolResultBlockParam
  22|   message: NormalizedUserMessage
  23|   lookups: ReturnType<typeof buildMessageLookups>
  24|   progressMessagesForMessage: ProgressMessage[]
  25|   style?: 'condensed'
  26|   tools: Tools
  27|   verbose: boolean
  28|   width: number | string
  29|   isTranscriptMode?: boolean
  30| }
  31| 
  32| export function UserToolResultMessage({
  33|   param,
  34|   message,
  35|   lookups,
  36|   progressMessagesForMessage,
  37|   style,
  38|   tools,
  39|   verbose,
  40|   width,
  41|   isTranscriptMode,
  42| }: Props): React.ReactNode {
  43|   const toolUse = useGetToolFromMessages(param.tool_use_id, tools, lookups)
  44|   if (!toolUse) {
  45|     return null
  46|   }
  47| 
  48|   if (
  49|     typeof param.content === 'string' &&
  50|     param.content.startsWith(CANCEL_MESSAGE)
  51|   ) {
  52|     return <UserToolCanceledMessage />
  53|   }
  54| 
  55|   if (
  56|     (typeof param.content === 'string' &&
  57|       param.content.startsWith(REJECT_MESSAGE)) ||
  58|     param.content === INTERRUPT_MESSAGE_FOR_TOOL_USE
  59|   ) {
  60|     return (
  61|       <UserToolRejectMessage
  62|         input={toolUse.toolUse.input as { [key: string]: unknown }}
  63|         progressMessagesForMessage={progressMessagesForMessage}
  64|         tool={toolUse.tool}
  65|         tools={tools}
  66|         lookups={lookups}
  67|         style={style}
  68|         verbose={verbose}
  69|         isTranscriptMode={isTranscriptMode}
  70|       />
  71|     )
  72|   }
  73| 
  74|   if (param.is_error) {
  75|     return (
  76|       <UserToolErrorMessage
  77|         progressMessagesForMessage={progressMessagesForMessage}
  78|         tool={toolUse.tool}
  79|         tools={tools}
  80|         param={param}

源码引用: src/components/messages/GroupedToolUseContent.tsx · 第 1–30 行(共 72 行)

   1| import type {
   2|   ToolResultBlockParam,
   3|   ToolUseBlockParam,
   4| } from '@anthropic-ai/sdk/resources/messages/messages.mjs'
   5| import * as React from 'react'
   6| import {
   7|   filterToolProgressMessages,
   8|   findToolByName,
   9|   type Tools,
  10| } from '../../Tool.js'
  11| import type { GroupedToolUseMessage } from '../../types/message.js'
  12| import type { buildMessageLookups } from '../../utils/messages.js'
  13| 
  14| type Props = {
  15|   message: GroupedToolUseMessage
  16|   tools: Tools
  17|   lookups: ReturnType<typeof buildMessageLookups>
  18|   inProgressToolUseIDs: Set<string>
  19|   shouldAnimate: boolean
  20| }
  21| 
  22| export function GroupedToolUseContent({
  23|   message,
  24|   tools,
  25|   lookups,
  26|   inProgressToolUseIDs,
  27|   shouldAnimate,
  28| }: Props): React.ReactNode {
  29|   const tool = findToolByName(tools, message.toolName)
  30|   if (!tool?.renderGroupedToolUse) {

其他高频消息组件

AssistantTextMessage:流式/完整 assistant 文本,配合 MessageResponse markdown 渲染。

AssistantThinkingMessage:extended thinking 块;transcript 模式可 hideInTranscript。

SystemTextMessage / SystemAPIErrorMessage:系统提示与 API 错误横幅色。

PlanApprovalMessage / TaskAssignmentMessage:多 agent 与 plan 流程专用 UI。

HookProgressMessage:Shell hook 执行进度,挂在 tool_use 行下方。

RateLimitMessage:触发限流时展示选项,REPL 传入 onOpenRateLimitOptions 回调。

目录 UserToolResultMessage/ 拆成多个文件是为减小 React Compiler 单元体积,修改 reject 样式时只 touch UserToolRejectMessage.tsx。

源码引用: src/components/messages/AssistantTextMessage.tsx · 第 1–25 行(共 223 行)

   1| import type { TextBlockParam } from '@anthropic-ai/sdk/resources/index.mjs'
   2| import React, { useContext } from 'react'
   3| import { ERROR_MESSAGE_USER_ABORT } from 'src/services/compact/compact.js'
   4| import { isRateLimitErrorMessage } from 'src/services/rateLimitMessages.js'
   5| import { BLACK_CIRCLE } from '../../constants/figures.js'
   6| import { Box, NoSelect, Text } from '../../ink.js'
   7| import {
   8|   API_ERROR_MESSAGE_PREFIX,
   9|   API_TIMEOUT_ERROR_MESSAGE,
  10|   CREDIT_BALANCE_TOO_LOW_ERROR_MESSAGE,
  11|   CUSTOM_OFF_SWITCH_MESSAGE,
  12|   INVALID_API_KEY_ERROR_MESSAGE,
  13|   INVALID_API_KEY_ERROR_MESSAGE_EXTERNAL,
  14|   ORG_DISABLED_ERROR_MESSAGE_ENV_KEY,
  15|   ORG_DISABLED_ERROR_MESSAGE_ENV_KEY_WITH_OAUTH,
  16|   PROMPT_TOO_LONG_ERROR_MESSAGE,
  17|   startsWithApiErrorPrefix,
  18|   TOKEN_REVOKED_ERROR_MESSAGE,
  19| } from '../../services/api/errors.js'
  20| import {
  21|   isEmptyMessageText,
  22|   NO_RESPONSE_REQUESTED,
  23| } from '../../utils/messages.js'
  24| import { getUpgradeMessage } from '../../utils/model/contextWindowUpgradeCheck.js'
  25| import {

源码引用: src/components/messages/HookProgressMessage.tsx · 第 1–25 行(共 68 行)

   1| import * as React from 'react'
   2| import type { HookEvent } from 'src/entrypoints/agentSdkTypes.js'
   3| import type { buildMessageLookups } from 'src/utils/messages.js'
   4| import { Box, Text } from '../../ink.js'
   5| import { MessageResponse } from '../MessageResponse.js'
   6| 
   7| type Props = {
   8|   hookEvent: HookEvent
   9|   lookups: ReturnType<typeof buildMessageLookups>
  10|   toolUseID: string
  11|   verbose: boolean
  12|   isTranscriptMode?: boolean
  13| }
  14| 
  15| export function HookProgressMessage({
  16|   hookEvent,
  17|   lookups,
  18|   toolUseID,
  19|   isTranscriptMode,
  20| }: Props): React.ReactNode {
  21|   const inProgressHookCount =
  22|     lookups.inProgressHookCounts.get(toolUseID)?.get(hookEvent) ?? 0
  23|   const resolvedHookCount =
  24|     lookups.resolvedHookCounts.get(toolUseID)?.get(hookEvent) ?? 0
  25|   if (inProgressHookCount === 0) {

messages 目录清单(学习路线)

建议按优先级阅读:

  1. UserPromptMessage / UserTextMessage — 理解用户侧性能与 brief
  2. AssistantToolUseMessage — 理解工具调用行
  3. UserToolResultMessage/* — 理解结果态
  4. AssistantThinkingMessage / AssistantTextMessage — 理解模型输出
  5. 边界类:CompactBoundaryMessage、SnipBoundaryMessage、ShutdownMessage

nullRenderingAttachments.ts 定义哪些 attachment 不渲染,避免空 Box 占位。

源码目录

点击 messages/ 下任意文件;若已有专题映射会跳转到本页对应小节。

动手练习

  1. 开启 verbose,观察 AssistantToolUseMessage 展开字段
  2. 管道输入超过 1 万字符,确认 UserPromptMessage 出现「+N lines」省略
  3. 拒绝一次 tool_use,在 transcript 找 UserToolRejectMessage 文案
  4. 对照 Messages.tsx 614 行 renderMessageRow 与界面中选区高亮是否一致

本章小结与延伸

messages/ = transcript 叶子渲染库。编排见 REPL + Messages;权限弹窗不在此目录。 继续学习:

  • components 总览
  • REPL 主屏
  • 权限 UI
Prev
REPL · 主屏编排
Next
PermissionRequest · 权限弹窗