本章总览
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 可规避此问题。
建议学习步骤
- 阅读 Messages.renderMessageRow(源码块 A)
- 打开 MessageRow 分发逻辑(源码块 B)
- 对照 UserPromptMessage 截断常量(源码块 C)
- 阅读 AssistantToolUseMessage 的 findToolByName(源码块 D)
- 跟踪 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_result | UserToolResultMessage/* |
| 助手 tool_use | AssistantToolUseMessage |
| 助手 thinking | AssistantThinkingMessage |
| 系统错误 | SystemAPIErrorMessage |
| Plan 审批 | PlanApprovalMessage |
新增消息变体时,先改 types/message.ts 与 normalize 管道,再补 MessageRow 分支与叶子文件。
Messages.tsx:列表编排
Messages 组件负责:
- 截断与展开:hiddenMessageCount、showAllInTranscript、disableRenderCap([ 键 dump 全量 scrollback)
- 虚拟滚动门控:virtualScrollRuntimeGate 决定 VirtualMessageList vs flatMap
- 搜索索引:extractSearchText 优先调用 tool.extractSearchText,缓存 lowercase 字符串
- 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:
findToolByName(tools, param.name)定位 Tool 定义inputSchema.safeParse校验 input,失败仍展示但无 userFacing 配色- 结合 inProgressToolUseIDs 显示 ToolUseLoader、HookProgressMessage
- 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 / INTERRUPT | UserToolRejectMessage |
| is_error | UserToolErrorMessage |
| 默认 | 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 目录清单(学习路线)
建议按优先级阅读:
- UserPromptMessage / UserTextMessage — 理解用户侧性能与 brief
- AssistantToolUseMessage — 理解工具调用行
- UserToolResultMessage/* — 理解结果态
- AssistantThinkingMessage / AssistantTextMessage — 理解模型输出
- 边界类:CompactBoundaryMessage、SnipBoundaryMessage、ShutdownMessage
nullRenderingAttachments.ts 定义哪些 attachment 不渲染,避免空 Box 占位。
源码目录
点击 messages/ 下任意文件;若已有专题映射会跳转到本页对应小节。
动手练习
- 开启 verbose,观察 AssistantToolUseMessage 展开字段
- 管道输入超过 1 万字符,确认 UserPromptMessage 出现「+N lines」省略
- 拒绝一次 tool_use,在 transcript 找 UserToolRejectMessage 文案
- 对照 Messages.tsx 614 行 renderMessageRow 与界面中选区高亮是否一致
本章小结与延伸
messages/ = transcript 叶子渲染库。编排见 REPL + Messages;权限弹窗不在此目录。 继续学习: