本章把分析粒度继续下压到“函数级”。这里不再只说某个文件负责什么,而是明确到:
- 哪个函数负责状态建立
- 哪个函数负责过滤、折叠、分发
- 哪个函数负责性能优化
- 哪个函数负责输入/渲染之间的衔接
核心范围包括:
AppStateProvider与状态切片 hooksMessages里的 transcript 预处理函数VirtualMessageList里的虚拟滚动/搜索函数MessageRow与Message的分发函数PromptInput里的输入编排辅助函数
位置:
实现职责:
- 先检查
HasAppStateContext,禁止嵌套 provider - 通过
createStore(initialState ?? getDefaultAppState(), onChangeAppState)懒初始化 store - 在
useEffect中检查toolPermissionContext.isBypassPermissionsModeAvailable - 若远端设置要求禁用 bypass,则调用内部
_temp(prev)把toolPermissionContext替换成禁用版 - 用
useSettingsChange(onSettingsChange)把 settings 变更同步回 store - 最终把
MailboxProvider、VoiceProvider、AppStoreContext.Provider一起挂上去
这说明 AppStateProvider 不是纯容器,而是“状态源 + 配置变更同步 + 权限模式修正”的复合入口。
实现职责:
- 通过
useAppStore()取到 store - 构造
get(),每次从store.getState()取新状态后执行 selector - 再用
useSyncExternalStore(store.subscribe, get, get)订阅切片
关键点:
- 它不是返回整棵状态树,而是强制调用者只取切片
- 配合
Object.is语义,避免无关组件重渲染
这也是为什么上层组件里大量出现 useAppState(s => s.xxx) 而不是把一堆状态对象打包传下去。
实现职责:
useSetAppState()直接返回store.setStateuseAppStateStore()直接返回 store 本体
它们的意义是把“订阅”和“写入”拆开。只写不读的组件不会因为状态变化而重渲染。
实现职责:
- 当外层没有
AppStateProvider时返回undefined - 仍然走
useSyncExternalStore
这让少数可脱离主工作台复用的组件,能在测试或工具场景中安全运行。
位置:
实现职责:
- 建立
nameSet和briefToolUseIDs - 第一遍通过 assistant
tool_use找到 Brief 工具调用,并记录其id - 保留:
- 非
api_metrics的 system message - API error assistant message
- Brief tool_use message
- 对应的 user tool_result
- 非 meta 的真实 user 输入
queued_command且commandMode === 'prompt'的 attachment
- 非
- 丢弃其它普通 assistant text
这个函数的本质是把 Brief 模式重新定义成“只显示 Brief 相关消息及真实用户输入”的专用视图。
实现职责:
- 先按“非 meta user message”划分 turn
- 给每个 assistant text block 标记其所属 turn
- 若某个 turn 内出现了 Brief
tool_use,则把同 turn 的 assistant text 删除
与 filterForBriefTool 的区别是:
- 前者是严格过滤视图
- 后者是 transcript 模式下的“去重清洗”
实现职责:
- 根据
anchorRef.current.uuid在collapsed中查找当前锚点 - 如果 uuid 丢失,则退回到历史 index
- 当
collapsed.length - start > cap + step时推进窗口 - 再用当前
start对应的 message 反向刷新 anchor
这个函数专门为“非虚拟化路径”的消息截断服务,其核心不是简单 slice(-N),而是用 uuid+idx 组合锚点避免:
- 消息分组重排时窗口抖动
- compaction 后突然回到 0
- 终端 scrollback 因前部裁切不断重置
3.4 shouldRenderStatically(message, streamingToolUseIDs, inProgressToolUseIDs, siblingToolUseIDs, screen, lookups)
实现职责:
- transcript 模式下一律返回
true - 普通 user/assistant/attachment message:
- 没有
toolUseID就可静态 - 在
streamingToolUseIDs或inProgressToolUseIDs中则保持动态 - 有未解决
PostToolUsehook 时保持动态 - 否则要求 sibling tool use 全部 resolved
- 没有
- system
api_error保持动态 grouped_tool_use需要组内全部 resolvedcollapsed_read_search在 prompt 模式永远动态
这个函数是消息稳定渲染策略的核心。它决定哪些行可以冻结,哪些行必须继续随工具状态更新。
位置:
实现职责:
- 通过
WeakMap缓存renderableSearchText(msg)的 lower 结果
这是 transcript 搜索的兜底文本提取器。没有它,输入搜索词时每次都要重新做文本下沉与 lower。
实现职责:
stickyPromptText用WeakMap做缓存computeStickyPromptText只识别两类“真实用户输入”:usermessage 中的 text blockattachment.type === 'queued_command'的 mid-turn 用户输入
- 会先调用
stripSystemReminders(raw) - 若文本以
<开头或为空,则认为不是用户真实输入
这两个函数直接支撑 sticky prompt header。它们的重点不在格式化,而在“过滤掉系统 reminder、XML 包装内容和伪输入”。
实现职责:
- 维护
keysRef,对 append-only 消息流做增量 key 追加 - 调用
useVirtualScroll(scrollRef, keys, columns)取得:rangemeasureRefoffsetsgetItemTopgetItemElementscrollToIndex
- 通过
useImperativeHandle(cursorNavRef, ...)暴露光标导航接口:enterCursornavigatePrevnavigateNextnavigatePrevUsernavigateNextUsernavigateTopnavigateBottom
- 通过
jumpState和scanRequestRef组织跳转、搜索和高亮
这不是单纯的列表组件,而是“虚拟滚动 + 导航控制器 + 搜索高亮控制器”的复合体。
实现职责:
- 为每条消息包一层稳定事件包装
- 降低 per-item 闭包分配
- 把
measureRef(k)、hover/click 这些与虚拟列表有关的行为绑定到单项上
这个函数存在的主要理由是性能,而不是业务语义。
实现职责:
- 用
useSyncExternalStore(subscribe, snapshot)订阅滚动状态 - 根据
scrollTop + pendingDelta算出当前可见窗口顶部 - 从 mounted range 里逆向找到
firstVisible - 再向前查找最近一个可作为 sticky prompt 的真实用户输入
- 过滤“提示文本其实仍在屏幕顶部可见”的重复情况
它把“顶部应该显示哪条历史 prompt”从滚动行为中实时推导出来,是 transcript 可读性设计的一部分。
位置:
实现职责:
- 向后扫描消息数组
- 跳过:
- assistant thinking / redacted thinking
- 可折叠的 read/search tool_use
- streaming 中的非折叠 tool_use
- system / attachment
- user tool_result
- 临时 grouped collapsible tool_use
- 一旦遇到真正内容则返回
true
它的目标是判断 collapsed read/search 组后面是否已经出现真实内容,以便决定该组还要不要保持“进行中”状态。
实现职责:
- 判断当前消息是否是 grouped / collapsed 类型
- 计算
isActiveCollapsedGroup - 抽取
displayMsg - 取
progressMessagesForMessage - 调用
shouldRenderStatically(...) - 根据
inProgressToolUseIDs计算shouldAnimate
因此 MessageRowImpl 更像“单条消息渲染前的状态预计算层”。
这两个函数是行级状态判断辅助:
isMessageStreaming判断某条消息是否仍在 streaming 集合中allToolsResolved判断相关 tool use 是否已经全部 resolved
它们服务于 areMessageRowPropsEqual(...) 和渲染冻结策略。
实现职责:
- 只在真正影响当前行显示时返回
false - 避免每次全局消息变化都导致整个 transcript 行级重渲染
这是 MessageRow 性能设计的重要一环。
位置:
实现职责:
- 根据
message.type分发给不同渲染支路 - attachment 走
AttachmentMessage - assistant 走
AssistantMessageBlock逐 block 渲染 - user 走
UserMessage(...) - system / grouped / collapsed 走对应专用组件
它是消息类型分发器,不负责复杂业务判断,只负责把“标准消息结构”转给正确叶子组件。
实现职责:
- 根据用户消息中的 block/附件类型决定是文本、图片还是工具结果消息
- 对 bash output、memory input、teammate/channel message 等走不同 UI
这一层把所有“用户侧产物”统一成一套可见语义。
实现职责:
CONNECTOR_TEXT打开时,可把 connector_text 伪装成普通 text blocktool_use走AssistantToolUseMessagetext走AssistantTextMessageredacted_thinking和thinking在非 transcript 且非 verbose 时直接隐藏thinking还会比较thinkingBlockId === lastThinkingBlockId,在 transcript 中隐藏旧 thinkingserver_tool_use/advisor_tool_result则转给AdvisorMessage- 未知 block 直接记错误日志
它是 assistant content block 的正式分发器,也是 thinking 可见性策略的落点。
实现职责:
- 仅对 assistant message 检查其 content 中是否含
thinking或redacted_thinking
此函数虽然很短,但直接被 areMessagePropsEqual(...) 用来避免“lastThinkingBlockId 变化时全量重渲染所有无 thinking 的消息”。
实现职责:
- 比较
message.uuid - 只有当前消息真有 thinking 内容时,才关心
lastThinkingBlockId变化 - 仅当该消息是否为
latestBashOutputUUID发生变化时才重渲染 - 对 transcript mode、containerWidth、verbose 等关键显示项做细粒度比较
这个函数体现了该项目对终端长会话的性能敏感度。
位置:
实现职责:
- 建立输入相关状态:
isAutoUpdatingexitMessagecursorOffset
- 用
lastInternalInputRef区分“外部注入输入”和“内部编辑输入” - 暴露
insertTextRef.current,提供:insert(text)setInputWithCursor(value, cursor)
- 从
AppState读取大量工作台状态:- tasks
- bridge 状态
- team context
- prompt suggestion / speculation
- teammate view / expandedView
- 继续联动 queued commands、history、typeahead、overlay、voice、dialogs 等行为
这个函数的本质是输入行为协调器。它不只是画输入框,而是统一协调“文本、队列、建议、弹层、bridge、team、history”。
实现职责:
- 遍历历史 user messages
- 从
imagePasteIds和 text block 中的parseReferences(block.text)找最大 paste id - 返回
maxId + 1
它解决的是“新粘贴内容的引用编号不能与历史冲突”的问题。
实现职责:
- 若不显示 fast icon,返回
undefined - 否则构造顶部边框提示内容:
- 仅 icon
- 或
icon + /fast提示
这是纯展示函数,但把 fast mode 的视觉提示规范成统一边框文本结构。
函数级拆解后,可以更清楚地看到:
AppStateProvider与useAppState负责建立“切片订阅式”全局状态基座。Messages的关键函数负责 brief 过滤、窗口锚定与静态/动态渲染判定。VirtualMessageList的关键函数负责虚拟滚动、搜索、高亮与 sticky prompt。MessageRow、MessageImpl、AssistantMessageBlock构成消息语义到显示语义的三级函数链。PromptInput则是输入协调器,其辅助函数负责光标插入、粘贴编号和边框提示。
也就是说,这套核心交互组件真正的复杂度,已经明确沉到了函数级策略上,而不是仅仅体现在目录结构上。