架构主线 · 03

UI、状态与交互

终端 UI 不只是画对话。自研 Ink 负责渲染,AppState 集中管会话状态;权限、任务、MCP、多代理和远程桥接都有可见面板,hooks 负责会话里持续跑着的行为。

1. UI 根骨架#

交互层最外壳是 src/components/App.tsx。它本身很薄,只负责包三层 Provider:

<FpsMetricsProvider>
  <StatsProvider>
    <AppStateProvider>{children}</AppStateProvider>
  </StatsProvider>
</FpsMetricsProvider>

这说明 UI 根节点的定位是“运行时状态控制面”,而不是普通页面容器。真正的状态定义在 src/state/AppStateStore.ts,React 订阅接口在 src/state/AppState.tsx

2. AppState 的规模#

AppState 远超普通 UI state。它集中管理 REPL 会话中的大部分产品状态,包括:

这说明 UI 层不是模型输出的被动展示,而是整个代理运行时的控制平面。

3. Store 与订阅方式#

src/state/AppState.tsx 使用 createStore() + React context + useSyncExternalStore

这种设计说明项目明确遇到过“大状态树导致重渲染”的问题,因此把状态选择器稳定性做成运行时约束。

AppStateProvider 还会处理一个重要竞态:remote managed settings 可能在 React mount 前就加载完成,因此 mount 后会再次检查 bypass permissions 是否已被远端禁用,并同步到 AppState。

4. AppState 默认值#

getDefaultAppState() 中能看到很多产品默认假设:

默认值本身就是一份产品功能地图。

5. 自研 Ink 渲染层#

src/ink 有 96 个文件,不是第三方 Ink 的薄封装,而是一套深度接管终端渲染的基础设施。目录职责大致包括:

为什么需要自研/深度改造?从组件规模看,这个 CLI 要支持复杂输入、多面板、可选择/可高亮 transcript、权限弹窗、任务视图、MCP 面板、Vim 模式、buddy sprite、图片引用等。社区默认抽象很难同时满足性能、选择、高亮和复杂交互。

6. 组件层组织#

src/components 是体量第二大的目录。重要子系统包括:

组件层的特点是“运行时状态可视化”:权限、任务、MCP、bridge、agent、todo、remote session 都会映射成 UI 控件。

7. hooks 的角色#

src/hooks 有一百多个文件。它们不是简单封装生命周期,而是把“会话中持续存在的行为”拆成独立 hook。几条主线:

主线代表 hook作用
输入useTextInputuseArrowKeyHistoryuseTypeaheaduseVimInput文本输入、历史、补全、Vim 模式
快捷键useGlobalKeybindings全局键位和组件级键位协调
权限useCanUseTooltoolPermission/*权限决策、弹窗队列、bridge/channel 回调
任务useTasksV2useTaskListWatcheruseScheduledTasks任务轮询和状态同步
MCP/插件useManageMCPConnectionsuseManagePluginsMCP 连接和插件状态管理
bridge/remoteuseReplBridgeuseMailboxBridgeuseInboxPoller远程控制、消息收件箱、跨端事件
IDE/语音useIDEIntegrationuseVoiceIntegration外部 IDE 和语音能力
推荐usePromptSuggestionuseClaudeCodeHintRecommendationuseLspPluginRecommendationprompt/插件/技能推荐

这说明 React hook 层承担的是“持续行为编排”,不只是 UI 局部状态。

8. 权限 UI 与权限逻辑的分层#

权限系统分两层:

这样拆是必要的,因为权限不是一个弹窗,而是贯穿所有高风险能力的运行时治理:Bash、PowerShell、文件写入、Notebook、WebFetch、AskUserQuestion、Plan Mode、Computer Use、MCP、Agent、Sandbox 都可能触发不同决策路径。auto 下 AskUserQuestion 默认走分类器,不是一律弹窗。

useCanUseTool.tsx 体现了 UI 与权限逻辑的连接:

  1. 先调用 hasPermissionsToUseTool() 得到 allow/deny/ask。
  2. allow 直接 resolve,并记录 classifier approval 等 UI 状态。
  3. deny 可发通知,例如 auto mode denied。
  4. ask 时根据场景分流:coordinator 自动检查、swarm worker 转发给 leader、Bash speculative classifier grace period、interactive permission dialog、bridge/channel callbacks。

9. 输入、快捷键与 Vim#

src/keybindings 独立处理快捷键 schema、解析、匹配、冲突检查、用户自定义绑定加载和展示格式。它作为一级目录存在,说明快捷键是全局协议,而不是某个输入框内部逻辑。

src/vim 独立实现 motion、operator、transition 和 text object。Vim 模式不是简单按键映射,而是一个独立输入子系统。2.1.233 没有 /vim slash,改走 /config → Editor mode。

两者拆开后,项目可以同时支持:

10. 任务、多代理与前台视图#

AppState 中的这些字段显示任务/多代理已经是正式交互模式:

这意味着 UI 需要在“主会话 transcript”“后台 task 输出”“某个 teammate/agent transcript”“远端 assistant viewer”之间切换。任务面板不是调试工具,而是主产品的一部分。

11. Bridge、remote 与 UI 状态#

AppState 为 always-on bridge 和 remote viewer 保留了大量状态:

这说明本地终端并不是孤立前端,它可以成为 claude.ai/mobile/remote harness 的一个 session endpoint。UI 必须同时展示本地执行和远程连接状态。

12. companion、bagel、tungsten、computer use#

一些看似“产品味”的状态也深度接入 AppState:

这些字段说明 CLI UI 已经承载多种“外部执行环境”的可视化控制,而不只是文本对话。

13. 这一层的结论#

这一层把运行时能力做成看得见、点得着的界面: