模块二:启动与状态 | 前置依赖:第 04 课 | 预计学习时间:60 分钟
学习目标
完成本课后,你将能够:
- 列出 AppState 的核心字段分组及其职责
- 解释
Store<T>模式的实现原理与不可变更新策略 - 说明状态变更如何从 Store 传播到 React 组件
- 理解 DeepImmutable 类型约束为何重要
5.1 AppState 的全貌
AppState 是 Claude Code 的全局状态容器。系统在任意时刻"知道"的一切信息都在这里。
字段总览(按职责分组)
核心会话状态
| 字段 | 类型 | 职责 |
|---|---|---|
settings | Settings | 用户设置(模型、权限模式等) |
mainLoopModel | string | 当前使用的模型 |
thinkingEnabled | boolean | 是否启用扩展思考 |
fastMode | boolean | 快速模式(Penguin Mode) |
effortValue | number | 推理努力等级 |
verbose | boolean | 详细输出模式 |
Agent 与工具
| 字段 | 类型 | 职责 |
|---|---|---|
agent | AgentDefinition | 当前 Agent 定义 |
agentDefinitions | AgentDefinitionsResult | 所有可用 Agent 定义 |
agentNameRegistry | Map | Agent 名称注册表 |
toolPermissionContext | ToolPermissionContext | 工具权限上下文 |
tasks | TaskState[] | 后台任务列表 |
foregroundedTaskId | string? | 当前前台任务 |
MCP 集成
| 字段 | 类型 | 职责 |
|---|---|---|
mcp.clients | MCPClient[] | MCP 客户端连接 |
mcp.tools | MCPTool[] | MCP 提供的工具 |
mcp.commands | MCPCommand[] | MCP 提供的命令 |
mcp.resources | MCPResource[] | MCP 提供的资源 |
插件与技能
| 字段 | 类型 | 职责 |
|---|---|---|
plugins | PluginState[] | 已安装插件状态 |
skillImprovement | SkillImprovementState | 技能改进追踪 |
UI 与交互
| 字段 | 类型 | 职责 |
|---|---|---|
statusLineText | string | 状态栏文本 |
expandedView | boolean | 展开视图模式 |
viewSelectionMode | ViewMode | 视图选择模式 |
footerSelection | FooterItem | 底栏选中项 |
notifications | Notification[] | 通知队列 |
activeOverlays | Overlay[] | 活跃覆盖层 |
Bridge 与远程
| 字段 | 类型 | 职责 |
|---|---|---|
replBridgeEnabled | boolean | Bridge 模式是否启用 |
replBridgeConnected | boolean | Bridge 连接状态 |
replBridgeReconnecting | boolean | Bridge 重连中 |
remoteSessionUrl | string? | 远程会话 URL |
remoteConnectionStatus | Status | 远程连接状态 |
推测执行与编辑追踪
| 字段 | 类型 | 职责 |
|---|---|---|
speculation | SpeculationState | 乐观更新/推测执行状态 |
fileHistory | FileHistory | 文件编辑历史 |
attribution | Attribution | 修改归属 |
KAIROS/Buddy 专用(Feature-gated)
| 字段 | 类型 | 职责 |
|---|---|---|
kairosEnabled | boolean | KAIROS 模式 |
isBriefOnly | boolean | Brief 输出模式 |
companionReaction | string? | Buddy 伴侣反应 |
权限与安全
| 字段 | 类型 | 职责 |
|---|---|---|
denialTracking | DenialTrackingState | 用户拒绝操作追踪 |
workerSandboxPermissions | Permissions | Worker 沙箱权限 |
内部工具(ant only)
| 字段 | 类型 | 职责 |
|---|---|---|
tungstenActiveSession | Session? | Tungsten 活跃会话 |
tungstenPanelVisible | boolean | Tungsten 面板可见性 |
bagelActive | boolean | Bagel 工具状态 |
computerUseMcpState | State | Computer 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 是一个全局的状态变更拦截器,当前主要负责:
- 检测权限模式转换:当
toolPermissionContext.mode发生变化时 - 通知 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 Store | Redux | Zustand | React useState |
|---|---|---|---|---|
| 更新方式 | setState(updater) | dispatch(action) | set(updater) | setState(value) |
| 同步/异步 | 同步 | 同步 dispatch | 同步 | 可能批量异步 |
| 不可变性 | DeepImmutable 类型 | 约定(immutablejs 可选) | 约定 | 无保证 |
| 中间件 | onChange 回调 | middleware 链 | middleware | 无 |
| DevTools | 无 | Redux 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() 如何被调用、权限弹窗如何弹出、以及流式结果如何被渲染到终端。