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 会话中的大部分产品状态,包括:
- settings、verbose、main model、status line;
- permission mode 与完整
ToolPermissionContext; - tasks、foregrounded task、viewing agent task;
- agent definitions、agent name registry、team context;
- MCP clients/tools/commands/resources;
- plugins enabled/disabled/errors/installation status/needsRefresh;
- file history、commit attribution、todos;
- notifications、elicitation queue;
- prompt suggestion、speculation、skill improvement;
- bridge 状态、remote session 状态、assistant/Kairos 状态;
- buddy/companion、tmux/tungsten、web browser/bagel、computer use MCP;
- worker sandbox permissions、inbox、session hooks、active overlays;
- fast mode、effort、advisor、ultraplan 等实验/产品状态。
这说明 UI 层不是模型输出的被动展示,而是整个代理运行时的控制平面。
3. Store 与订阅方式#
src/state/AppState.tsx 使用 createStore() + React context + useSyncExternalStore:
- Provider 创建一次 store,context value 稳定,不靠 Provider 更新触发整树重渲染。
- 组件用
useAppState(selector)订阅 state 切片。 useSetAppState()返回稳定 setter,不订阅状态。useAppStateStore()允许非 React 逻辑拿到getState/setState。- selector 不能返回整个 state;内部构建会对这种用法直接抛错,强制细粒度订阅。
这种设计说明项目明确遇到过“大状态树导致重渲染”的问题,因此把状态选择器稳定性做成运行时约束。
AppStateProvider 还会处理一个重要竞态:remote managed settings 可能在 React mount 前就加载完成,因此 mount 后会再次检查 bypass permissions 是否已被远端禁用,并同步到 AppState。
4. AppState 默认值#
getDefaultAppState() 中能看到很多产品默认假设:
- teammate 且 plan required 时,初始 permission mode 是
plan,否则default; mcp.clients/tools/commands/resources初始为空;- plugin 状态分 enabled/disabled/errors/installing/needsRefresh;
thinkingEnabled由shouldEnableThinkingByDefault()决定;promptSuggestionEnabled由shouldEnablePromptSuggestion()决定;speculation初始为 idle;remoteConnectionStatus初始为connecting;- bridge 多个状态位拆开保存:enabled、explicit、outboundOnly、connected、sessionActive、reconnecting、URL、IDs、error 等。
默认值本身就是一份产品功能地图。
5. 自研 Ink 渲染层#
src/ink 有 96 个文件,不是第三方 Ink 的薄封装,而是一套深度接管终端渲染的基础设施。目录职责大致包括:
layout/*:布局计算;events/*:输入、焦点、鼠标等事件;termio/*:ANSI/CSI/OSC/DEC 等终端协议;- renderer/screen/focus/selection/string width/search highlight/render border 等终端 UI 底层能力。
为什么需要自研/深度改造?从组件规模看,这个 CLI 要支持复杂输入、多面板、可选择/可高亮 transcript、权限弹窗、任务视图、MCP 面板、Vim 模式、buddy sprite、图片引用等。社区默认抽象很难同时满足性能、选择、高亮和复杂交互。
6. 组件层组织#
src/components 是体量第二大的目录。重要子系统包括:
messages:主 transcript 展示。PromptInput:复杂输入框,承载 history、suggestion、mode、快捷键、上下文行为。permissions:权限请求、审批、拒绝、规则建议、auto mode 反馈。mcp:MCP server/tool/resource 管理与状态展示。tasks/agents:后台任务、多代理、teammate/foreground 切换。CustomSelect、wizard、TrustDialog、ManagedSettingsSecurityDialog:交互选择、onboarding、托管策略等产品界面。LogoV2、FeedbackSurvey、ClaudeCodeHint:品牌、反馈、推荐提示。
组件层的特点是“运行时状态可视化”:权限、任务、MCP、bridge、agent、todo、remote session 都会映射成 UI 控件。
7. hooks 的角色#
src/hooks 有一百多个文件。它们不是简单封装生命周期,而是把“会话中持续存在的行为”拆成独立 hook。几条主线:
| 主线 | 代表 hook | 作用 |
|---|---|---|
| 输入 | useTextInput、useArrowKeyHistory、useTypeahead、useVimInput | 文本输入、历史、补全、Vim 模式 |
| 快捷键 | useGlobalKeybindings 等 | 全局键位和组件级键位协调 |
| 权限 | useCanUseTool、toolPermission/* | 权限决策、弹窗队列、bridge/channel 回调 |
| 任务 | useTasksV2、useTaskListWatcher、useScheduledTasks | 任务轮询和状态同步 |
| MCP/插件 | useManageMCPConnections、useManagePlugins | MCP 连接和插件状态管理 |
| bridge/remote | useReplBridge、useMailboxBridge、useInboxPoller | 远程控制、消息收件箱、跨端事件 |
| IDE/语音 | useIDEIntegration、useVoiceIntegration | 外部 IDE 和语音能力 |
| 推荐 | usePromptSuggestion、useClaudeCodeHintRecommendation、useLspPluginRecommendation | prompt/插件/技能推荐 |
这说明 React hook 层承担的是“持续行为编排”,不只是 UI 局部状态。
8. 权限 UI 与权限逻辑的分层#
权限系统分两层:
utils/permissions/*:规则、匹配、解释、classifier、auto mode、settings 同步、deny tracking。components/permissions/*与hooks/toolPermission/*:用户可见审批、队列、bridge/channel/coordinator/swarm worker handler。
这样拆是必要的,因为权限不是一个弹窗,而是贯穿所有高风险能力的运行时治理:Bash、PowerShell、文件写入、Notebook、WebFetch、AskUserQuestion、Plan Mode、Computer Use、MCP、Agent、Sandbox 都可能触发不同决策路径。auto 下 AskUserQuestion 默认走分类器,不是一律弹窗。
useCanUseTool.tsx 体现了 UI 与权限逻辑的连接:
- 先调用
hasPermissionsToUseTool()得到 allow/deny/ask。 - allow 直接 resolve,并记录 classifier approval 等 UI 状态。
- deny 可发通知,例如 auto mode denied。
- 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。
两者拆开后,项目可以同时支持:
- 全局一致的快捷键;
- 可切换输入模式;
- PromptInput、selector、overlay、footer pill 等不同焦点区域的键位协调。
10. 任务、多代理与前台视图#
AppState 中的这些字段显示任务/多代理已经是正式交互模式:
tasks、foregroundedTaskId;viewingAgentTaskId;selectedIPAgentIndex、coordinatorTaskIndex;viewSelectionMode;agentNameRegistry;teamContext;remoteBackgroundTaskCount。
这意味着 UI 需要在“主会话 transcript”“后台 task 输出”“某个 teammate/agent transcript”“远端 assistant viewer”之间切换。任务面板不是调试工具,而是主产品的一部分。
11. Bridge、remote 与 UI 状态#
AppState 为 always-on bridge 和 remote viewer 保留了大量状态:
- REPL bridge enabled/explicit/outboundOnly;
- env registered 与 session active;
- reconnecting/error;
- connect URL / session URL;
- environment id / session id;
- permission callbacks;
- remote connection status 与 remote background task count。
这说明本地终端并不是孤立前端,它可以成为 claude.ai/mobile/remote harness 的一个 session endpoint。UI 必须同时展示本地执行和远程连接状态。
12. companion、bagel、tungsten、computer use#
一些看似“产品味”的状态也深度接入 AppState:
companionReaction、companionPetAt:buddy/sprite 反馈。bagelActive、bagelUrl、bagelPanelVisible:Chrome / Computer Use 面板状态。2.1.233 工具表没有WebBrowser。tungstenActiveSession、tungstenPanelVisible、tungstenLastCommand:tmux/terminal capture 面板状态。2.1.233 工具表没有Tungsten。computerUseMcpState:Computer Use MCP 的 allowlist、clipboard/system-key grants、screenshot dims、display 选择、隐藏 app 等。
这些字段说明 CLI UI 已经承载多种“外部执行环境”的可视化控制,而不只是文本对话。
13. 这一层的结论#
这一层把运行时能力做成看得见、点得着的界面:
- 自研 Ink 层负责复杂终端渲染;
- AppState store 负责统一运行时状态;
- components 把权限、任务、MCP、多代理、bridge、输入体验做成可见产品;
- hooks 把持续行为、外部连接、权限 race、插件/MCP 状态同步编排起来。