feat(perf): item 5 normalizeMessages 二次规范化缓存 (CC 2.1.216/203) - #112
Merged
Conversation
二次规范化缓存: messages 严格 append-only, 已存对象引用稳定 → 按前缀引用
增量复用 normalized 输出, 仅尾部续算, 避免 O(n) 每 render/append 重 flatMap
27k 消息。compact/rewind 整体替换 → 前缀引用不匹配 → 全量重算 (自动失效,
无 stale)。
实现:
- messages.ts: 抽 normalizeMessagesCore(messages, seedIsNewChain) 返回
{normalized, isNewChain}, 行为与原内联 loop 完全一致 (isNewChain 单调
false→true + deriveUUID 确定性)。normalizeMessages 变薄 wrapper, 9 其它
调用方零变化。
- normalizeMessagesCache.ts (新): 前缀引用增量缓存 NormalizedCacheState
{sourceRef, segmentCounts, normalized, isNewChain}。逐 source message 记
段长 (1 msg flatMap → N normalized), 按 segmentCounts 对齐复用前缀,
seed isNewChain 续算尾部, UUID 与全量重算 byte-identical。
- Messages.tsx:464: useRef 持缓存, useMemo [messages] 内增量调用后写回,
输出再 filter(isNotEmptyMessage)。
- 7 case 单测: baseline 等价 / 增量前缀复用 / isNewChain 跨边界续传 /
compact 替换失效 / 部分前缀 / 空数组 / 段长正确。26 expect。
验证: typecheck 0 err; 235 test 全过无回归; build:dev OK; biome 无 err
(仅 test noNonNullAssertion warning, 与现有 test 风格一致)。
:598 层增量 + item 17 全屏完整历史推迟 (issue #111): reorderMessagesInUI
签名含 synthetic (尾部 append) + applyGrouping 全数组 resultsByToolUseId
+ buildMessageLookups orphan-marker messages.at(-1) 三处跨边界使前缀不可
定稿, 非 append-only 安全缓存。本 PR 仅 normalize 层。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
dahai80
added a commit
that referenced
this pull request
Aug 22, 2026
Patch release bundling 17 commits since v0.4.23: - item 15(a) MLX OOM auto-recovery gc+retry (#129) - #7 one-click diagnostic fixAction framework (#131) - 维度5 background-default spawn FUSION_SUBAGENT_DEFAULT_BACKGROUND (#127) - #4 proactive MLX OOM detection via /v1/health (#125) - item 17 fullscreen pre-compact history + :598 synthetic decouple (#123) - item 25B workflow stagger fan-out (#122) - item 21 auto-mode local rule classifier test coverage (#120) - item 23 archive plugin source + SHA-256 lock (#118) - item 24 marketplace command source (#116) - item 6 transcript disk trimming + crash-safe checkpoint (#114) - item 5 normalizeMessages secondary normalize cache (#112) - item 19 screen-reader plaintext render (#110) - item 16 compact retry countdown + stall hint (#108) - item 25A workflow execution runtime docs (#106) - item 25A minimal workflow execution runtime (#105) - item 12 subagent auto-background threshold (#103) - item 20 vimInsertModeRemaps dual-key INSERT sequence (#101) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
item 5 — 二次规范化缓存 (CC 2.1.216/203, §187)
两层 O(n) per-render 中的 normalize 层:
Messages.tsx:464useMemo deps[messages], 每 append (流末原子setMessages((prev)=>[...prev,newMsg])) 换引用 → 重 flatMap 全 27k 消息。本 PR 缓存 normalizeMessages 输出, append 复用前缀仅尾部续算, O(n)→O(tail)。审计前提 (实读确认)
normalizeMessagesisNewChain= 单调 false→true, 取决于全部前序消息;deriveUUID确定性 → 前缀引用增量正确 (存段长 + flag 终值, 尾部续算)。messages严格 append-only, 已存对象引用稳定 (流式 content-block 进独立streamingToolUsesstate, 不入 messages; 完整 assistant 仅流末原子 append)。改动 (4 文件)
messages.ts: 抽normalizeMessagesCore(messages, seedIsNewChain)→{normalized, isNewChain}, 行为与原内联 loop 完全一致。normalizeMessages变薄 wrapper, 9 其它调用方零变化。normalizeMessagesCache.ts(新): 前缀引用增量缓存NormalizedCacheState {sourceRef, segmentCounts, normalized, isNewChain}。逐 source message 记段长 (1 msg flatMap → N normalized), 按 segmentCounts 对齐复用前缀, seed isNewChain 续算尾部 → UUID 与全量重算 byte-identical。Messages.tsx:464:useRef持缓存,useMemo [messages]内增量调用后写回。normalizeMessagesCache.test.ts(新, 7 case): baseline 等价 / 增量前缀复用 / isNewChain 跨边界续传 / compact 替换失效 / 部分前缀 / 空数组 / 段长正确。scope 收缩说明
原计划为 "item 5 + :598 一体 (解锁 item 17)", 含
:598useMemo (reorder+applyGrouping+collapse+buildMessageLookups) 增量 + 删 REPL 全屏 cap (item 17)。实施中发现:598层 非 append-only 安全缓存, 三处跨边界 hazard 使前缀不可定稿 (见 issue #111):reorderMessagesInUI签名含syntheticStreamingToolUseMessages(尾部 append) → 前缀 reorder 随 delta stale。applyGrouping全数组resultsByToolUseId(groupToolUses.ts:106) → tail result 改变 prefix 分组。buildMessageLookupsorphan-markermessages.at(-1)(messages.ts:1335) → resolved/errored Set 每次 messagesToShow 变化重算。按计划预授权退路 ("实施中遇 correctness 挣扎即降 scope, Rule 12 fail-visible") → 本 PR 仅 normalize 层;
:598+ item 17 推迟至 issue #111 独立解决。验证
bun run typecheck— 0 errorsbun test src/__tests__/— 235 pass, 0 fail (无回归)bun test src/__tests__/utils/normalizeMessagesCache.test.ts— 7 pass, 26 expectbun run build:dev— OKbiome check新文件 — 无 error (仅 test noNonNullAssertion warning, 与现有 test 风格一致)default 行为不变守
Closes #110.
Co-Authored-By: Claude Fable 5 noreply@anthropic.com