Skip to content

feat(perf): item 5 normalizeMessages 二次规范化缓存 (CC 2.1.216/203) - #112

Merged
dahai80 merged 1 commit into
mainfrom
feat/item5-normalize-cache
Aug 21, 2026
Merged

feat(perf): item 5 normalizeMessages 二次规范化缓存 (CC 2.1.216/203)#112
dahai80 merged 1 commit into
mainfrom
feat/item5-normalize-cache

Conversation

@dahai80

@dahai80 dahai80 commented Aug 21, 2026

Copy link
Copy Markdown
Owner

item 5 — 二次规范化缓存 (CC 2.1.216/203, §187)

两层 O(n) per-render 中的 normalize 层: Messages.tsx:464 useMemo deps [messages], 每 append (流末原子 setMessages((prev)=>[...prev,newMsg])) 换引用 → 重 flatMap 全 27k 消息。本 PR 缓存 normalizeMessages 输出, append 复用前缀仅尾部续算, O(n)→O(tail)。

审计前提 (实读确认)

  • normalizeMessages isNewChain = 单调 false→true, 取决于全部前序消息; deriveUUID 确定性 → 前缀引用增量正确 (存段长 + flag 终值, 尾部续算)。
  • messages 严格 append-only, 已存对象引用稳定 (流式 content-block 进独立 streamingToolUses state, 不入 messages; 完整 assistant 仅流末原子 append)。
  • compact/rewind/clear 整体替换 → 前缀引用不匹配 → 全量重算 (自动失效, 无 stale 输出)。

改动 (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)", 含 :598 useMemo (reorder+applyGrouping+collapse+buildMessageLookups) 增量 + 删 REPL 全屏 cap (item 17)。实施中发现 :598非 append-only 安全缓存, 三处跨边界 hazard 使前缀不可定稿 (见 issue #111):

  1. reorderMessagesInUI 签名含 syntheticStreamingToolUseMessages (尾部 append) → 前缀 reorder 随 delta stale。
  2. applyGrouping 全数组 resultsByToolUseId (groupToolUses.ts:106) → tail result 改变 prefix 分组。
  3. buildMessageLookups orphan-marker messages.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 errors
  • bun test src/__tests__/ — 235 pass, 0 fail (无回归)
  • bun test src/__tests__/utils/normalizeMessagesCache.test.ts — 7 pass, 26 expect
  • bun run build:dev — OK
  • biome check 新文件 — 无 error (仅 test noNonNullAssertion warning, 与现有 test 风格一致)
  • 真 MLX 手工验 — normalize 层为纯函数 + 单测 byte-identical 锁, 默认行为 cache=null 首调 === 全量, 风险低; 流式/compact 场景由 235 全过覆盖

default 行为不变守

  • cache=null 首调 === 全量 (测试 case 1 锁)
  • compact → 引用全换 → 全量重算 (测试 case 4 锁)
  • 非全屏 REPL cap 不动 (scope 外)

Closes #110.

Co-Authored-By: Claude Fable 5 noreply@anthropic.com

二次规范化缓存: 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
dahai80 merged commit a64a914 into main Aug 21, 2026
1 check passed
@dahai80
dahai80 deleted the feat/item5-normalize-cache branch August 21, 2026 14:19
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant