Step 3: 模块通信机制分析
分析日期: 2026-04-16 核心文件: AppStateStore.ts, store.ts, AppState.tsx, context/*, signal.ts, bridgeMessaging.ts, toolHooks.ts
1. 通信机制清单
1.1 AppState Store(中央状态总线)
文件: src/state/AppStateStore.ts(类型定义), src/state/store.ts(store 原语), src/state/AppState.tsx(React 绑定)
AppState Store 是最重要的通信机制。基于 createStore<T> 构建,提供 getState(), setState(updater), subscribe(listener) —— 类似 Redux 但更简洁:无 reducer、无 action、无中间件,只有不可变状态更新函数。
Store 原语 (store.ts):
1
2
3
4
5
export type Store<T> = {
getState: () => T
setState: (updater: (prev: T) => T) => void
subscribe: (listener: Listener) => () => void
}
状态更新使用 Object.is 引用比较 —— 如果更新器返回相同引用,订阅者不会被通知。可选的 onChange 回调接收 {newState, oldState} 用于副作用桥接(Bridge 用此将状态推送到 CCR)。
AppState 字段分类(~70+ 顶层键, ~360+ 叶子字段):
| 域 | 字段 | 用途 |
|---|---|---|
| 设置/配置 | settings, verbose, mainLoopModel, mainLoopModelForSession, isBriefOnly, fastMode, effortValue, advisorModel, thinkingEnabled |
配置状态 |
| UI/视图 | expandedView, selectedIPAgentIndex, coordinatorTaskIndex, viewSelectionMode, footerSelection, statusLineText, showTeammateMessagePreview, spinnerTip, activeOverlays |
视图路由和显示状态 |
| 任务 | tasks, foregroundedTaskId, viewingAgentTaskId, agentNameRegistry, remoteAgentTaskSuggestions |
任务生命周期和路由 |
| MCP | mcp.clients, mcp.tools, mcp.commands, mcp.resources, mcp.pluginReconnectKey |
MCP 服务器连接及其工具 |
| 插件 | plugins.enabled, plugins.disabled, plugins.commands, plugins.errors, plugins.installationStatus, plugins.needsRefresh |
插件系统状态 |
| Bridge | replBridgeEnabled, replBridgeExplicit, replBridgeOutboundOnly, replBridgeConnected, replBridgeSessionActive, replBridgeReconnecting, replBridgeConnectUrl, replBridgeSessionUrl, replBridgeEnvironmentId, replBridgeSessionId, replBridgeError, replBridgeInitialName, replBridgePermissionCallbacks, showRemoteCallout, channelPermissionCallbacks |
常驻 Bridge(claude.ai 远程控制) |
| 远程 | remoteSessionUrl, remoteConnectionStatus, remoteBackgroundTaskCount |
远程/助手模式 WebSocket 状态 |
| 团队/Swarm | teamContext, standaloneAgentContext, inbox, workerSandboxPermissions, pendingWorkerRequest, pendingSandboxRequest |
多 Agent swarm 通信 |
| Tmux/Tungsten | tungstenActiveSession, tungstenLastCapturedTime, tungstenLastCommand, tungstenPanelVisible, tungstenPanelAutoHidden |
终端复用器面板 |
| Web 浏览器 (Bagel) | bagelActive, bagelUrl, bagelPanelVisible |
嵌入式浏览器工具 |
| 计算机使用 | computerUseMcpState(含 allowedApps, grantFlags, lastScreenshotDims, hiddenDuringTurn, selectedDisplayId, displayPinnedByModel, displayResolvedForApps) |
屏幕自动化 |
| 推测执行 | speculation, speculationSessionTimeSavedMs |
推测执行状态 |
| 通知 | notifications.current, notifications.queue |
Toast 通知队列 |
| 需求 | elicitation.queue |
MCP 需求请求队列 |
| 权限 | toolPermissionContext, denialTracking |
权限模式和拒绝追踪 |
| 伴游/Buddy | companionReaction, companionPetAt |
伴游精灵反应 |
| 提示/输入 | promptSuggestion, promptSuggestionEnabled, initialMessage |
输入路由和建议 |
| Agent/模式 | agent, kairosEnabled |
Agent 身份和助手模式 |
| 文件历史 | fileHistory |
文件快照追踪 |
| 归属 | attribution |
Git 提交归属状态 |
| 待办 | todos |
每 Agent 待办列表 |
| Hooks | sessionHooks |
会话级 hook 状态 |
| 认证 | authVersion |
认证失效计数器 |
| REPL 上下文 | replContext(含 vmContext, registeredTools, console) |
REPL 工具 VM 状态 |
| 计划/模式 | pendingPlanVerification, isUltraplanMode, ultraplanLaunching, ultraplanSessionUrl, ultraplanPendingChoice, ultraplanLaunchPending |
计划模式和 ultraplan 状态 |
| 技能改进 | skillImprovement.suggestion |
技能增强建议 |
React 集成: useAppState(selector) 使用 useSyncExternalStore 实现无撕裂读取。useSetAppState() 返回稳定的 setState 引用而不订阅。useAppStateMaybeOutsideOfProvider() 为可能在 provider 树外渲染的组件提供安全回退。
耦合级别: 松散 —— 消费者通过 selector 订阅特定切片;只有选中的切片触发重渲染。但生产者与类型定义紧密耦合(任何调用 setAppState 的模块必须了解其形状)。
1.2 Bootstrap State(模块级单例)
文件: src/bootstrap/state.ts
这是一个独立于 AppState 的全局可变状态对象。存储会话级不可变或极少变更的值:sessionId, projectRoot, originalCwd, isInteractive, 成本追踪(totalCostUSD, 模型使用),注册的 hooks,设置引用,遥测等。使用 createSignal() 进行变更通知。
与 AppState 的关键区别:
- 模块级单例 —— 无 React context,任何代码无需 provider 即可访问
- 命令式读/写 ——
getSessionId(),getCwd(),getMainThreadAgentType()等 - 基于 Signal 的通知 ——
createSignal()用于特定状态变更(如设置变更)
耦合级别: 紧密 —— 整个代码库直接从 bootstrap/state.js 导入。无抽象层;每个消费者直接调用 getter 函数。
1.3 React Context 系统
9 个 React Context provider,各自服务特定领域:
| Context | 文件 | 提供 | 消费者 | 耦合 |
|---|---|---|---|---|
| AppStoreContext | state/AppState.tsx |
AppState store 引用 | useAppState(), useSetAppState(), useAppStateStore() |
松散 —— 基于 selector 的订阅 |
| MailboxContext | context/mailbox.tsx |
Mailbox 实例(Agent 间消息队列) |
useMailbox() |
中等 —— 单对象,但窄 API |
| VoiceContext | context/voice.tsx |
Store<VoiceState>(语音录制状态) |
useVoiceState(), useSetVoiceState(), useGetVoiceState() |
松散 —— 基于 selector 的 useSyncExternalStore |
| StatsContext | context/stats.tsx |
StatsStore(性能指标) |
useStats(), useCounter(), useGauge(), useTimer(), useSet() |
中等 —— 函数式 API,无订阅 |
| PromptOverlayContext | context/promptOverlayContext.tsx |
4 个 context(data, setData, dialog, setDialog)用于浮动覆盖层 | usePromptOverlay(), useSetPromptOverlay(), usePromptOverlayDialog(), useSetPromptOverlayDialog() |
松散 —— 分离 data/setter context 防止重渲染 |
| ModalContext | context/modalContext.tsx |
Modal 槽位大小 + 滚动引用 | useIsInsideModal(), useModalOrTerminalSize(), useModalScrollRef() |
中等 —— 直接值读取 |
| OverlayContext | context/overlayContext.tsx |
委托到 AppState.activeOverlays |
useRegisterOverlay(), useIsOverlayActive(), useIsModalOverlayActive() |
松散 —— 回退到 AppState |
| QueuedMessageContext | context/QueuedMessageContext.tsx |
isQueued, isFirst, paddingWidth 用于消息布局 |
useQueuedMessage() |
紧密 —— 简单值传播 |
| FpsMetricsContext | context/fpsMetrics.tsx |
FPS 指标获取函数 | useFpsMetrics() |
松散 —— 稳定 getter 引用 |
Provider 嵌套(来自 AppState.tsx):
1
2
3
4
5
AppStateProvider
-> HasAppStateContext
-> AppStoreContext
-> MailboxProvider
-> VoiceProvider
1.4 Signal 系统(轻量级发布/订阅)
文件: src/utils/signal.ts
用于纯事件信号的最小监听器集合原语,无存储状态。设计用于”某事发生了”的通知,而非”当前值是什么”的查询。
1
2
3
4
5
export type Signal<Args extends unknown[] = []> = {
subscribe: (listener: (...args: Args) => void) => () => void
emit: (...args: Args) => void
clear: () => void
}
在 18 个文件中使用,用于各种事件通道:
utils/settings/changeDetector.ts—— 设置文件变更通知utils/skills/skillChangeDetector.ts—— 技能目录变更通知utils/messageQueueManager.ts—— 命令队列变更通知utils/mailbox.ts—— mailbox 修订变更通知utils/awsAuthStatusManager.ts—— AWS 认证状态变更utils/classifierApprovals.ts—— 分类器审批事件utils/fastMode.ts—— 快速模式切换事件utils/QueryGuard.ts—— 查询守卫状态变更hooks/fileSuggestions.ts—— 文件建议更新hooks/useTasksV2.ts—— 任务状态变更通知keybindings/loadUserBindings.ts—— 键绑定重载通知services/analytics/growthbook.ts—— 特性开关更新skills/loadSkillsDir.ts—— 技能加载事件utils/suggestions/slackChannelSuggestions.ts—— Slack 频道建议utils/tasks.ts—— 任务状态事件utils/claudeCodeHints.ts—— 提示状态变更bootstrap/state.ts—— 各种 bootstrap 状态变更 signal
耦合级别: 松散 —— 生产者和消费者完全解耦。生产者发射;订阅者被调用。无共享状态引用。
1.5 EventEmitter(Ink 终端 UI)
文件: src/ink/events/emitter.ts, src/ink/events/event.ts
自定义 EventEmitter,扩展 Node 的 EventEmitter,支持通过自定义 Event 类实现 stopImmediatePropagation()。专用于 Ink 终端 UI 层的键盘输入路由、焦点管理和终端事件传播。
Event 类提供 DOM 风格的 stopImmediatePropagation() 语义,用于优先级事件处理(如 modal 可以在主输入处理器之前停止按键事件传播)。
耦合级别: 松散 —— 基于事件;生产者发射命名事件,消费者注册监听器。但 Ink 特定的 EventEmitter 在 Ink 渲染树内全局共享。
1.6 Hook 系统(Shell 命令 Hooks)
文件: src/utils/hooks.ts(执行引擎), src/services/tools/toolHooks.ts(工具集成层)
Hook 系统是用户定义的 shell 命令执行管线,在特定生命周期点运行。它不是事件系统 —— 而是同步请求/响应管线,具有明确定义的拦截点:
Hook 事件(生命周期拦截点):
PreToolUse—— 工具执行前;可允许/拒绝/修改输入PostToolUse—— 工具成功后;可修改输出、阻止继续PostToolUseFailure—— 工具失败后Notification—— 会话通知时Stop—— 会话结束前SubagentStop—— 子 Agent 结束前
数据流:
executePreToolHooks()/executePostToolHooks()生成子进程,通过 stdin 传入 JSON 输入- Hook 进程在 stdout 返回 JSON,包含
permissionBehavior(allow/deny/ask)、updatedInput、blockingError、additionalContexts、preventContinuation runPreToolUseHooks()和runPostToolUseHooks()在toolHooks.ts中是异步生成器,将类型化结果回传给工具执行循环resolveHookPermissionDecision()合并 hook 决策与基于规则的权限(hook 不能覆盖 settings.json 中的deny规则)
耦合级别: 中等 —— hook 执行引擎与工具执行管线(ToolUseContext)紧密耦合,但实际 hook 定义是外部的(用户 shell 命令),因此与代码库最大程度解耦。
1.7 MCP 协议(Model Context Protocol)
文件: src/services/mcp/types.ts
MCP 定义与外部工具服务器通信的协议。使用 @modelcontextprotocol/sdk 客户端库,支持多种传输类型:
传输类型: stdio, sse, sse-ide, http, ws, ws-ide, sdk, claudeai-proxy
连接状态(可辨识联合):
ConnectedMCPServer—— 活跃连接,含客户端、能力、清理函数FailedMCPServer—— 连接失败,含错误NeedsAuthMCPServer—— 需要 OAuth 认证PendingMCPServer—— 正在重连DisabledMCPServer—— 用户禁用
数据流:
- MCP 服务器配置从设置加载(settings.json 中的
mcpServers) useManageMCPConnections建立连接并填充AppState.mcp- 已连接的 MCP 服务器暴露工具、命令和资源
- MCP 工具集成到标准
Tool抽象和ToolUseContext管线 - 来自 MCP 服务器的需求请求流入
AppState.elicitation.queue channelPermissionCallbacks支持通过消息通道的权限提示
耦合级别: 松散 —— MCP 服务器是通过明确定义协议通信的外部进程。SDK 处理序列化/反序列化。内部代码只需了解 MCPServerConnection 类型。
1.8 Bridge 消息传递(claude.ai 远程控制)
文件: src/bridge/bridgeMessaging.ts
Bridge 支持本地 CLI 会话与 claude.ai(或 SDK 消费者)之间的双向通信。使用 WebSocket/SSE 传输进行实时消息流。
通过 Bridge 流动的消息类型:
- 出站:
user和assistant消息,system/local_command事件(转发到 claude.ai 显示) - 入站: 来自 claude.ai 的
user消息(发送到 CLI 的提示),control_request和control_response消息
控制请求子类型(服务器 -> CLI):
initialize—— 会话能力握手set_model—— 从 Web UI 切换模型set_max_thinking_tokens—— 思考预算控制set_permission_mode—— 权限模式变更interrupt—— 取消当前操作
回声去重: BoundedUUIDSet —— 基于循环缓冲区的 Set,当容量达到上限时驱逐最旧条目,防止服务器重放消息历史时的回声循环。
耦合级别: 松散 —— bridge 消息层是纯函数(不对 bridge 状态闭包)。所有协作者(传输、sessionId、回调)作为参数传入。handleIngressMessage() 和 handleServerControlRequest() 函数是纯路由器。
1.9 Mailbox(Agent 间消息队列)
文件: src/utils/mailbox.ts
React 绑定: src/context/mailbox.tsx
用于 Agent 间通信(队友/swarm 场景)的消息队列。支持:
send(msg)—— 入队消息或分派给等待的接收者poll(fn)—— 同步非阻塞出队,带谓词匹配receive(fn)—— 异步阻塞出队,带谓词匹配(返回 Promise)subscribe—— 通过createSignal()变更通知
消息类型: user, teammate, system, tick, task —— 各含 id, source, content, from, color, timestamp。
耦合级别: 中等 —— Mailbox 实例通过 React Context 提供,但直接访问。生产者和消费者共享相同的 Mailbox 实例引用,但通过队列解耦。
1.10 命令队列(MessageQueueManager)
文件: src/utils/messageQueueManager.ts
模块级优先队列,管理所有用户输入、任务通知和孤立权限。这是馈送 REPL 主循环的统一命令队列。
优先级级别: now (0) > next (1) > later (2)。同级内 FIFO 排序。
关键操作: enqueue(), enqueuePendingNotification(), dequeue(filter?), dequeueAll(), peek(), popAllEditable(), clearCommandQueue().
React 集成: subscribeToCommandQueue / getCommandQueueSnapshot 兼容 useSyncExternalStore,允许 React 组件订阅队列变更而无需直接耦合。
耦合级别: 松散 —— 模块级单例,带 createSignal() 通知。非 React 代码直接读取;React 代码通过 useSyncExternalStore 订阅。
1.11 ToolUseContext(上下文对象模式)
文件: src/Tool.ts
ToolUseContext 是传递给每个工具执行的参数对象。它不是全局通信机制,但它是工具与系统其余部分通信的主要通道。
ToolUseContext 中的关键通道:
getAppState()/setAppState()—— 读/写中央 storeabortController—— 取消信号options—— 模型、工具列表、命令、MCP 客户端addNotification()—— 推送 UI 通知appendSystemMessage()—— 向对话添加系统消息setToolJSX()—— 设置工具特定的 UI 渲染handleElicitation()—— MCP 需求处理器sendOSNotification()—— OS 级通知requireCanUseTool—— 权限门控setInProgressToolUseIDs()—— 追踪活跃工具setStreamMode()—— 控制旋转器模式
耦合级别: 紧密 —— 每个工具直接依赖此类型。但作为参数传入(依赖注入)而非全局访问,使其可测试且可组合。
2. 数据流图
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
┌──────────────────────────────────────────────┐
│ 外部边界 │
│ │
│ ┌────────┐ ┌──────────┐ ┌────────────┐ │
│ │ 用户 │ │ claude.ai│ │ MCP 服务器 │ │
│ │ (CLI) │ │ (Bridge)│ │ (stdio/WS) │ │
│ └───┬────┘ └────┬─────┘ └─────┬──────┘ │
│ │ │ │ │
└──────┼────────────┼──────────────┼──────────┘
│ │ │
┌───────────────▼──┐ ┌───▼────────┐ ┌──▼──────────┐
│ 命令队列 │ │ Bridge │ │ MCP 客户端 │
│ (优先级 FIFO) │ │ 消息传递 │ │ (SDK) │
│ [messageQueue │ │ [bridge │ │ [mcp/ │
│ Manager.ts] │ │ Messaging]│ │ client.ts]│
└───────┬──────────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
▼ ▼ ▼
┌───────────────────────────────────────────────────────┐
│ APP STATE STORE │
│ [AppStateStore.ts / store.ts] │
│ │
│ ┌──────────┐ ┌─────────┐ ┌────────┐ ┌─────────────┐│
│ │ tasks │ │ mcp.* │ │ bridge │ │ notifications││
│ │ todos │ │ plugins │ │ state │ │ queue ││
│ │ inbox │ │ permCtx │ │ remote │ │ elicitation ││
│ │ agent* │ │ history │ │ team │ │ speculation ││
│ │ UI state │ │ settings│ │ CU mcp │ │ ... ││
│ └──────────┘ └─────────┘ └────────┘ └─────────────┘│
└───────────┬──────────────────────┬─────────────────┘
│ │
┌─────────────▼──────┐ ┌─────────▼──────────┐
│ React 组件 │ │ 非 React 服务 │
│ (useAppState) │ │ (store.getState) │
│ │ │ │
│ REPL.tsx │ │ Bridge core │
│ PromptInput │ │ Hook executor │
│ ToolPanel │ │ Tool executor │
│ ScrollBox │ │ MCP manager │
│ Footer │ │ Task runner │
└───────────────────┘ └─────────────────────┘
┌────────────────────────────────────────────────────┐
│ 侧通道 │
│ │
│ Signals (createSignal): │
│ settings-change, skill-change, queue-mutate, │
│ mailbox-change, auth-status, fast-mode-toggle │
│ │
│ EventEmitter (Ink): │
│ keydown, keypress, focus-change, input │
│ │
│ Bootstrap State (模块单例): │
│ sessionId, cwd, cost, hooks, telemetry │
│ │
│ ToolUseContext (每工具参数对象): │
│ getAppState, setAppState, abort, permissions │
│ │
│ Hook 系统 (子进程 IPC): │
│ PreToolUse, PostToolUse, Stop, Notification │
│ │
│ Mailbox (Agent 间队列): │
│ send, poll, receive, subscribe │
│ │
│ React Contexts: │
│ Voice, Stats, Modal, Overlay, PromptOverlay │
└────────────────────────────────────────────────────┘
3. 耦合分析
紧耦合(直接导入 / 共享类型)
| 机制 | 描述 | 风险 |
|---|---|---|
| Bootstrap State | 模块级单例,直接 getter 导入。~200+ 文件从 bootstrap/state.ts 导入 |
高:getter 签名变更会波及到处。无测试隔离,需 monkey-patch |
| ToolUseContext | 每个工具直接依赖此类型 | 中:参数注入缓解部分风险,但类型本身很宽(30+ 字段) |
| QueuedMessageContext | 简单值传播,无抽象 | 低:窄 API 面 |
| AppState 类型定义 | 所有生产者和消费者必须了解其形状 | 高:新增字段涉及中央类型定义 |
中耦合(共享实例 / Context)
| 机制 | 描述 | 风险 |
|---|---|---|
| Mailbox | 通过 React Context 共享队列实例 | 中:生产者和消费者必须就消息格式达成一致 |
| StatsStore | 通过 React Context 的函数式 API | 低:窄接口,无状态形状耦合 |
| VoiceStore | 通过 React Context 的 Store |
低:隔离域,selector 读取 |
| Bridge 消息 | 带协作者参数的纯函数 | 低:bridge core 和调用者间无共享可变状态 |
松耦合(事件 / Signal / 协议)
| 机制 | 描述 | 风险 |
|---|---|---|
| Signal 系统 | 生产者发射;订阅者被通知。无共享状态 | 最低:生产者和消费者完全独立 |
| EventEmitter (Ink) | 命名事件发布/订阅,带传播控制 | 低:按事件名解耦,但共享 emitter 引用 |
| MCP 协议 | 通过 stdio/WS/SSE 的标准 JSON-RPC。外部服务器 | 最低:按协议规范完全解耦 |
| AppState Store(消费端) | 基于 selector 的订阅,消费者只依赖读取的切片 | 消费端低;生产端高(必须了解完整形状) |
| 命令队列 | 模块级,带基于 signal 的通知 | 低:明确定义的优先队列 API,useSyncExternalStore 集成 |
| Hook 系统 | 通过 stdin/stdout JSON 通信的外部进程 | 最低:hook 完全在进程外部 |
4. 关键架构观察
-
双状态系统: AppState(React 优化,基于 selector)和 Bootstrap State(模块级命令式)。Bootstrap State 先于 AppState,处理 React 挂载前或非 React 代码必须访问的值。
bootstrap/state.ts中的注释 “DO NOT ADD MORE STATE HERE” 表明有意向 AppState 迁移。 -
Signal 作为通用语:
createSignal()原语已取代约 15 处重复的监听器集合样板代码。它是”某事变更了”通知的首选机制,无需完整 store。它在 React(通过useSyncExternalStore)和非 React 代码间无缝桥接。 -
AppState 作为通信总线: 尽管是”状态 store”,AppState 实际上是模块间的主要通信总线。Bridge 写入
replBridge*字段;UI 读取它们。MCP 连接填充mcp.*;工具读取它们。任务更新tasks和foregroundedTaskId;视图层响应。这实际上是通过不可变状态更新中介的发布/订阅模型。 -
权限流是多通道的: 权限决策在 4 个通道间竞争:本地 UI、bridge(claude.ai)、通道回调(Telegram/iMessage)和 hooks。AppState 中的
channelPermissionCallbacks和replBridgePermissionCallbacks字段是回调对象,支持这些竞争 —— 它们不是纯数据,使得 AppState 成为数据和行为的混合体。 -
Hook 系统是最解耦的机制: 它通过 JSON stdin/stdout 与外部进程通信。这是唯一跨越进程边界的通信机制,完全独立于内部模块结构。
-
Bridge 消息传递有意设计为纯函数:
handleIngressMessage()和handleServerControlRequest()函数将所有协作者作为参数而非闭包引用 bridge 特定状态。此设计允许基于环境和无环境的 bridge core 共享相同的消息处理逻辑。 -
命令队列解耦输入与处理: 统一命令队列(
messageQueueManager.ts)将输入源(用户输入、bridge 消息、任务通知)与处理它们的 REPL 主循环解耦。优先级排序确保用户输入永远不会被系统消息饥饿。