第 05 课:状态管理架构

4 阅读6分钟

模块二:启动与状态 | 前置依赖:第 04 课 | 预计学习时间:60 分钟


学习目标

完成本课后,你将能够:

  1. 列出 AppState 的核心字段分组及其职责
  2. 解释 Store<T> 模式的实现原理与不可变更新策略
  3. 说明状态变更如何从 Store 传播到 React 组件
  4. 理解 DeepImmutable 类型约束为何重要

5.1 AppState 的全貌

AppState 是 Claude Code 的全局状态容器。系统在任意时刻"知道"的一切信息都在这里。

字段总览(按职责分组)

核心会话状态

字段类型职责
settingsSettings用户设置(模型、权限模式等)
mainLoopModelstring当前使用的模型
thinkingEnabledboolean是否启用扩展思考
fastModeboolean快速模式(Penguin Mode)
effortValuenumber推理努力等级
verboseboolean详细输出模式

Agent 与工具

字段类型职责
agentAgentDefinition当前 Agent 定义
agentDefinitionsAgentDefinitionsResult所有可用 Agent 定义
agentNameRegistryMapAgent 名称注册表
toolPermissionContextToolPermissionContext工具权限上下文
tasksTaskState[]后台任务列表
foregroundedTaskIdstring?当前前台任务

MCP 集成

字段类型职责
mcp.clientsMCPClient[]MCP 客户端连接
mcp.toolsMCPTool[]MCP 提供的工具
mcp.commandsMCPCommand[]MCP 提供的命令
mcp.resourcesMCPResource[]MCP 提供的资源

插件与技能

字段类型职责
pluginsPluginState[]已安装插件状态
skillImprovementSkillImprovementState技能改进追踪

UI 与交互

字段类型职责
statusLineTextstring状态栏文本
expandedViewboolean展开视图模式
viewSelectionModeViewMode视图选择模式
footerSelectionFooterItem底栏选中项
notificationsNotification[]通知队列
activeOverlaysOverlay[]活跃覆盖层

Bridge 与远程

字段类型职责
replBridgeEnabledbooleanBridge 模式是否启用
replBridgeConnectedbooleanBridge 连接状态
replBridgeReconnectingbooleanBridge 重连中
remoteSessionUrlstring?远程会话 URL
remoteConnectionStatusStatus远程连接状态

推测执行与编辑追踪

字段类型职责
speculationSpeculationState乐观更新/推测执行状态
fileHistoryFileHistory文件编辑历史
attributionAttribution修改归属

KAIROS/Buddy 专用(Feature-gated)

字段类型职责
kairosEnabledbooleanKAIROS 模式
isBriefOnlybooleanBrief 输出模式
companionReactionstring?Buddy 伴侣反应

权限与安全

字段类型职责
denialTrackingDenialTrackingState用户拒绝操作追踪
workerSandboxPermissionsPermissionsWorker 沙箱权限

内部工具(ant only)

字段类型职责
tungstenActiveSessionSession?Tungsten 活跃会话
tungstenPanelVisiblebooleanTungsten 面板可见性
bagelActivebooleanBagel 工具状态
computerUseMcpStateStateComputer Use 状态

5.2 Store 模式

实现

state/store.ts 实现了一个极简的泛型状态容器:

type Store<T> = {
  getState(): T
  setState(updater: (prev: T) => T): void
  subscribe(listener: () => void): () => void  // 返回取消订阅函数
}

function createStore<T>(initialState: T, onChange?: (prev: T, next: T) => void): Store<T> {
  let state = initialState
  const listeners = new Set<() => void>()

  return {
    getState() {
      return state
    },

    setState(updater) {
      const prev = state
      const next = updater(prev)
      if (Object.is(prev, next)) return  // 引用相同则跳过
      state = next
      onChange?.(prev, next)            // 通知变更回调
      listeners.forEach(l => l())       // 通知所有订阅者
    },

    subscribe(listener) {
      listeners.add(listener)
      return () => listeners.delete(listener)  // 返回取消函数
    }
  }
}

核心设计决策

1. 不可变更新

// 正确:创建新对象
setState(prev => ({
  ...prev,
  mainLoopModel: 'opus-4.6'
}))

// 错误:直接修改(TypeScript 会阻止,因为 DeepImmutable)
state.mainLoopModel = 'opus-4.6'  // 编译错误!

2. Object.is 相等性检查

// 如果 updater 返回相同的引用,不触发更新
setState(prev => prev)  // 不会通知任何 listener

这避免了不必要的重渲染 — 如果状态实际上没变,就不通知。

3. 同步更新

与 React 的 useState(可能批量异步更新)不同,Store 的 setState 是同步的 — 调用后状态立即生效。这对 Agent 循环中的状态一致性很重要。


5.3 React Provider 集成

AppStateProvider(state/AppState.tsx

function AppStateProvider({ children, initialState, onChangeAppState }) {
  // 防止嵌套
  if (useContext(HasAppStateContext)) {
    throw new Error('AppStateProvider cannot be nested')
  }

  // 创建 Store
  const store = useMemo(
    () => createStore(initialState, onChangeAppState),
    []
  )

  // 设置变更的响应式同步
  useSettingsChange(store)

  return (
    <HasAppStateContext.Provider value={true}>
      <AppStoreContext.Provider value={store}>
        <VoiceProvider>
          <MailboxProvider>
            {children}
          </MailboxProvider>
        </VoiceProvider>
      </AppStoreContext.Provider>
    </HasAppStateContext.Provider>
  )
}

Context 嵌套结构

<AppStateProvider>
  <AppStoreContext.Provider>        ← Store 实例
    <VoiceProvider>                 ← 语音状态
      <MailboxProvider>             ← Agent 间通信
        <App>
          <REPL />                  ← 主交互界面
        </App>
      </MailboxProvider>
    </VoiceProvider>
  </AppStoreContext.Provider>
</AppStateProvider>

5.4 状态变更传播

完整传播链路

工具执行 / 用户操作
  │
  ▼
store.setState(prev => newState)
  │
  ├── Object.is 检查 → 相同引用?→ 跳过
  │
  ├── onChange(prev, next)
  │   └── onChangeAppState.ts
  │       └── 检测权限模式变化
  │       └── 通知 CCR 会话元数据变更
  │
  └── listeners.forEach(l => l())
      └── React 组件重渲染
          └── Ink 重新计算布局
              └── 终端输出更新

onChangeAppState:全局变更拦截

state/onChangeAppState.ts 是一个全局的状态变更拦截器,当前主要负责:

  1. 检测权限模式转换:当 toolPermissionContext.mode 发生变化时
  2. 通知 CCR:通过 notifySessionMetadataChanged 告知云容器运行时

这是一个扩展点 — 未来可以在这里添加更多全局级别的状态变更副作用。

Selectors:纯函数状态查询

// state/selectors.ts — 两个纯函数选择器

// 获取当前正在查看的队友任务
function getViewedTeammateTask(appState: AppState)
  : InProcessTeammateTaskState | undefined

// 获取当前输入应路由到哪个 Agent
function getActiveAgentForInput(appState: AppState)
  : { type: 'leader' | 'viewed' | 'named_agent', task? }

Selector 是纯函数 — 给定相同的 AppState 总是返回相同的结果。它们不触发副作用,只从状态中提取派生数据。


5.5 DeepImmutable:类型级不可变保证

AppState 被定义为 DeepImmutable 类型,这是 TypeScript 级别的不可变性保证:

type DeepImmutable<T> = {
  readonly [K in keyof T]: T[K] extends object ? DeepImmutable<T[K]> : T[K]
}

效果:

const state: DeepImmutable<AppState> = store.getState()

state.mainLoopModel = 'new'          // TS 错误: readonly
state.mcp.tools.push(newTool)        // TS 错误: readonly array
state.notifications[0].text = 'new'  // TS 错误: readonly

为什么需要类型级别的保证?

在一个有 70+ 个 Hook 和 146 个组件的系统中,任何一个地方不小心直接修改状态都可能导致难以调试的 bug(UI 不更新、状态不一致、竞态条件)。DeepImmutable 让 TypeScript 编译器在编译时捕获所有修改尝试。


5.6 与常见状态管理方案的对比

维度Claude Code StoreReduxZustandReact useState
更新方式setState(updater)dispatch(action)set(updater)setState(value)
同步/异步同步同步 dispatch同步可能批量异步
不可变性DeepImmutable 类型约定(immutablejs 可选)约定无保证
中间件onChange 回调middleware 链middleware
DevToolsRedux DevTools可选React DevTools
代码量~34 行数千行~100 行React 内置

Claude Code 选择了最简方案:34 行代码实现核心 Store,依赖 TypeScript 类型系统保证不可变性,不引入额外依赖。这很适合 CLI 应用 — 不需要 DevTools,不需要中间件链,状态结构相对稳定。


课后练习

练习 1:字段分类

不看本课内容,阅读 state/AppStateStore.ts 源码,将所有字段分为"核心必须"和"可选扩展"两类。对比你的分类和本课的分组。

练习 2:Store 实现

用 30 行以内的代码实现一个简化版 Store,包含 getState、setState、subscribe 三个方法。测试不可变更新和 Object.is 跳过逻辑。

练习 3:状态流追踪

追踪以下场景的状态变更路径:

  • 用户切换模型(从 Sonnet 到 Opus)
  • 一个新的 MCP 工具被注册
  • 用户拒绝一个权限请求

练习 4:DeepImmutable 探索

写出 3 个会被 DeepImmutable 阻止的代码示例,以及它们对应的正确(不可变)写法。


本课小结

要点内容
AppState 规模50+ 字段,涵盖会话、工具、MCP、UI、远程、安全等
Store 实现34 行泛型 Store,同步更新,Object.is 跳过
不可变保证DeepImmutable 类型 + setState(updater) 模式
变更传播setState → onChange → listeners → React 重渲染
设计哲学最简方案,类型系统保证安全,不引入额外依赖

下一课预告

第 06 课:REPL 交互循环 — 深入 895KB 的 REPL.tsx,理解用户输入如何被接收和路由、query() 如何被调用、权限弹窗如何弹出、以及流式结果如何被渲染到终端。