REPL.tsx 源码逐行分析

文件路径 : src/screens/REPL.tsx

总行数 : 5005 行

核心职责: Claude Code 交互式模式的根组件,管理用户输入、消息展示、API 查询、权限审批等全流程


一、导入阶段(第 1-294 行)

1.1 编译时依赖(第 1 行)

tsx 复制代码
import { c as _c } from "react/compiler-runtime";

React 编译器(React Forget)的运行时。因为文件经过编译,所有组件函数体内都有 _c() 调用用于 memoization。

1.2 构建特性标记(第 3 行)

tsx 复制代码
import { feature } from 'bun:bundle';

Bun 的编译时特性标记系统。feature('VOICE_MODE') 在构建期求值,如果特性关闭,相关代码块在打包时被彻底移除(dead code elimination)。

1.3 条件导入模式(第 96-120 行, 192-199 行, 220-223 行, 271-272 行)

文件中大量使用条件 require() 来实现特性门控:

模式一:基于 feature() 标记

tsx 复制代码
const useVoiceIntegration = feature('VOICE_MODE')
  ? require('../hooks/useVoiceIntegration.js').useVoiceIntegration
  : () => ({ stripTrailing: () => 0, handleKeyEvent: () => {}, resetAnchor: () => {} });

VOICE_MODE 编译为 false 时,打包器直接替换为空函数实现,整个 useVoiceIntegration 模块不会出现在最终产物中。

模式二:基于构建目标

tsx 复制代码
const useFrustrationDetection = "external" === 'ant'
  ? require('...').useFrustrationDetection
  : () => ({ state: 'closed', handleTranscriptSelect: () => {} });

"external" === 'ant' 在外部构建中编译为 false,Ant 内部构建编译为 true,实现内部功能与开源构建的代码隔离。

条件导入的模块包括:

  • useVoiceIntegration(第 98 行)--- 语音模式
  • VoiceKeybindingHandler(第 103 行)
  • useFrustrationDetection(第 107 行)--- 用户挫败感检测
  • useAntOrgWarningNotification(第 113 行)--- 组织警告
  • getCoordinatorUserContext(第 115 行)--- 协调器模式
  • proactiveModule(第 194 行)--- AI 主动发起模式
  • useScheduledTasks(第 199 行)
  • AntModelSwitchCallout(第 221 行)--- 模型切换提示
  • WebBrowserPanelModule(第 272 行)

1.4 普通导入(第 4-290 行)

按功能分类:

类别 模块 用途
React react, react/compiler-runtime 核心
Ink 终端渲染 ../ink.js (Box, Text, useInput, useStdin...) 终端 UI 组件
状态管理 ../state/AppState.js (useAppState, useSetAppState, useAppStateStore) 全局应用状态
消息处理 ../utils/messages.js (handleMessageFromStream, createUserMessage...) 消息类型工具
查询 ../query.js (query) 核心 API 查询
提交流程 ../utils/handlePromptSubmit.js 用户输入处理
命令 ../commands.js 斜杠命令系统
权限 ../utils/permissions/, ../permission/ 工具权限审批
远程模式 useRemoteSession, useDirectConnect, useSSHSession 三种远程模式
通知 ../context/notifications.js 通知系统
分析 src/services/analytics/index.js 遥测事件
键盘 keybindings/ 快捷键系统

1.5 模块级常量(第 294-305 行)

tsx 复制代码
const EMPTY_MCP_CLIENTS: MCPServerConnection[] = [];       // 稳定空数组,避免无限重渲染
const HISTORY_STUB = { maybeLoadOlder: (_: ScrollBoxHandle) => {} };  // KAIROS 空操作桩
const RECENT_SCROLL_REPIN_WINDOW_MS = 3000;                // 滚动后 3 秒内不自动回底

二、辅助函数与小组件(第 311-524 行)

2.1 median()(第 311-315 行)

tsx 复制代码
function median(values: number[]): number {
  const sorted = [...values].sort((a, b) => a - b);
  const mid = Math.floor(sorted.length / 2);
  return sorted.length % 2 === 0
    ? Math.round((sorted[mid - 1]! + sorted[mid]!) / 2)
    : sorted[mid]!;
}

计算中位数。用于计算多轮 API 请求的 P50 TTFT(首 token 时间)和 OTPS(每秒输出 token)。

2.2 TranscriptModeFooter(第 321-362 行)

Transcript(详细聊天记录)模式底栏组件。语音模式。使用 React Compiler _c() 手动 memo。

arduino 复制代码
显示内容: "Showing detailed transcript · ctrl+o to toggle · ctrl+e to show all"
右侧: 搜索状态 "current/count" 或自定义状态文本

关键特性:

  • 使用 useShortcutDisplay 获取当前平台的快捷键显示(cmd 或 ctrl)
  • 全静态内容,通过 _c 手动缓存避免重渲染

2.3 TranscriptSearchBar(第 368-472 行)

/ 触发的搜索栏,模仿 less 风格:

tsx 复制代码
function TranscriptSearchBar({ jumpRef, count, current, onClose, onCancel, setHighlight, initialQuery })

实现细节:

  • 使用 useSearchInput hook 处理 readline 风格编辑
  • 搜索索引预热 (第 413-437 行):在首次打开时调用 jumpRef.current.warmSearchIndex(),如果超过 20ms 则显示 "indexed in Xms"
  • warmDone 状态守卫(第 440 行):搜索高亮只在索引预热完成后生效
  • 渲染为带 / 前缀的一行编辑框 + 右侧计数

2.4 AnimatedTerminalTitle(第 484-524 行)

tsx 复制代码
const TITLE_ANIMATION_FRAMES = ['⠂', '⠐'];
const TITLE_STATIC_PREFIX = '✳';
const TITLE_ANIMATION_INTERVAL_MS = 960;

终端标签页标题动画组件:

  • 查询运行时:✳ ⠂ Claude Code✳ ⠐ Claude Code 960ms 切换
  • 空闲时:✳ Claude Code
  • 独立组件:960ms tick 只重渲染这个叶子组件,不触发 REPL 主体

三、Props 定义(第 526-570 行)

tsx 复制代码
export type Props = {
  commands: Command[];
  debug: boolean;
  initialTools: Tool[];
  initialMessages?: MessageType[];
  pendingHookMessages?: Promise<HookResultMessage[]>;
  initialFileHistorySnapshots?: FileHistorySnapshot[];
  initialContentReplacements?: ContentReplacementRecord[];
  initialAgentName?: string;
  initialAgentColor?: AgentColorName;
  mcpClients?: MCPServerConnection[];
  dynamicMcpConfig?: Record<string, ScopedMcpServerConfig>;
  autoConnectIdeFlag?: boolean;
  strictMcpConfig?: boolean;
  systemPrompt?: string;
  appendSystemPrompt?: string;
  onBeforeQuery?: (input: string, newMessages: MessageType[]) => Promise<boolean>;
  onTurnComplete?: (messages: MessageType[]) => void | Promise<void>;
  disabled?: boolean;
  mainThreadAgentDefinition?: AgentDefinition;
  disableSlashCommands?: boolean;
  taskListId?: string;
  remoteSessionConfig?: RemoteSessionConfig;
  directConnectConfig?: DirectConnectConfig;
  sshSession?: SSHSession;
  thinkingConfig: ThinkingConfig;
};
export type Screen = 'prompt' | 'transcript';

关键设计:

  • 三模合一remoteSessionConfig / directConnectConfig / sshSession 三者互斥,分别对应 --remote / claude connect / claude ssh 三种模式
  • 惰性初始化initialMessages 用于会话恢复,pendingHookMessages 是 Promise,在首次 API 调用前 await
  • 会话生命周期回调onBeforeQuery(查询前拦截)、onTurnComplete(每次轮次结束通知)

四、REPL 组件主体(第 572-5004 行)

4.1 组件签名与环境门控(第 572-608 行)

tsx 复制代码
export function REPL({ commands: initialCommands, debug, initialTools, ... }: Props): React.ReactNode {
  const isRemoteSession = !!remoteSessionConfig;
  const titleDisabled = useMemo(() => isEnvTruthy(process.env.CLAUDE_CODE_DISABLE_TERMINAL_TITLE), []);
  const moreRightEnabled = useMemo(() => "external" === 'ant' && isEnvTruthy(process.env.CLAUDE_MORERIGHT), []);
  const disableVirtualScroll = useMemo(() => isEnvTruthy(process.env.CLAUDE_CODE_DISABLE_VIRTUAL_SCROLL), []);

将环境变量检查提升到 useMemo 中。这些检查原本在渲染路径上每次执行,useMemo 确保只执行一次。

4.2 生命周期日志(第 610-614 行)

tsx 复制代码
useEffect(() => {
  logForDebugging(`[REPL:mount] REPL mounted, disabled=${disabled}`);
  return () => logForDebugging(`[REPL:unmount] REPL unmounting`);
}, [disabled]);

4.3 AppState 读取(第 616-640 行)

tsx 复制代码
const mainThreadAgentDefinition = useState(initialMainThreadAgentDefinition);
const toolPermissionContext = useAppState(s => s.toolPermissionContext);
const verbose = useAppState(s => s.verbose);
const queuedCommands = useCommandQueue();
// ... 约 20 个 useAppState 选择器
const setAppState = useSetAppState();

所有全局状态通过 useAppState 的 selector 模式读取,确保只有相关子状态变化时组件才重渲染。

4.4 Agent 会话引导(第 646-671 行)

本地 agent(local_agent)保留会话的反序列化引导逻辑:

tsx 复制代码
const viewedLocalAgent = viewingAgentTaskId ? tasks[viewingAgentTaskId] : undefined;
const needsBootstrap = isLocalAgentTask(viewedLocalAgent) && viewedLocalAgent.retain && !viewedLocalAgent.diskLoaded;

从 JSONL 文件中读取磁盘持久化的消息,与流式输出的消息做 UUID 去重合并(第 656-657 行),确保消息不重复。

4.5 命令热重载(第 680-684 行)

tsx 复制代码
const [localCommands, setLocalCommands] = useState(initialCommands);
useSkillsChange(isRemoteSession ? undefined : getProjectRoot(), setLocalCommands);

监听 skill 文件变化,重新加载所有命令(/help 等斜杠命令支持热更新)。

4.6 主动模式订阅(第 687 行)

tsx 复制代码
const proactiveActive = React.useSyncExternalStore(
  proactiveModule?.subscribeToProactiveChanges ?? PROACTIVE_NO_OP_SUBSCRIBE,
  proactiveModule?.isProactiveActive ?? PROACTIVE_FALSE
);

useSyncExternalStore 是 React 18 的 API,用于订阅外部 store。这里订阅 proactive 模块的状态变化。

4.7 工具与命令合并(第 696-835 行)

工具合并管线:

scss 复制代码
localTools(useMemo) → getTools(toolPermissionContext)
combinedInitialTools = [...localTools, ...initialTools]
mergedTools = useMergedTools(combinedInitialTools, mcp.tools, toolPermissionContext)
tools = mainThreadAgentDefinition ? resolveAgentTools(...) : mergedTools

命令合并管线:

scss 复制代码
localCommands(state) → useMergedCommands(plugins) → useMergedCommands(mcp)
commands = disableSlashCommands ? [] : mergedCommands

4.8 查询状态管理(第 838-953 行)

核心状态

tsx 复制代码
const queryGuard = React.useRef(new QueryGuard()).current;   // 同步状态机
const isQueryActive = React.useSyncExternalStore(            // 派生状态
  queryGuard.subscribe, queryGuard.getSnapshot
);
const [isExternalLoading, setIsExternalLoadingRaw] = useState(remoteSessionConfig?.hasInitialPrompt ?? false);
const isLoading = isQueryActive || isExternalLoading;        // 最终 loading 标志

QueryGuard 状态机 (来自 ../utils/QueryGuard.js):

css 复制代码
状态: idle → dispatching → running → idle
方法: reserve() / tryStart() / end() / forceEnd() / cancelReservation()

REPL 查询管线深度分析

时间跟踪系统(第 931-967 行):

tsx 复制代码
const loadingStartTimeRef = useRef<number>(0);
const totalPausedMsRef = useRef(0);
const pauseStartTimeRef = useRef<number | null>(null);

三个 ref 配合实现暂停感知的耗时计算。当权限审批对话框弹出时,暂停计时器停止累加,对话框关闭后恢复。

4.9 流式状态(第 849-867 行)

tsx 复制代码
const [streamingToolUses, setStreamingToolUses] = useState<StreamingToolUse[]>([]);
const [streamingThinking, setStreamingThinking] = useState<StreamingThinking | null>(null);
const streamingText  // 流式文本(字符级增量)
const visibleStreamingText  // 只保留完整行(lastIndexOf('\n') + 1),实现逐行输出

visibleStreamingText 的设计(第 1473 行):截取到最后一个换行符,保证文本逐行显示而不是逐字符跳跃。

4.10 消息状态管理(第 1182-1322 行)

Ref 镜像模式(第 1182-1222 行):

tsx 复制代码
const [messages, rawSetMessages] = useState<MessageType[]>(initialMessages ?? []);
const messagesRef = useRef(messages);

const setMessages = useCallback((action) => {
  const prev = messagesRef.current;
  const next = typeof action === 'function' ? action(messagesRef.current) : action;
  messagesRef.current = next;    // 同步写入 ref
  // ...placeholder 逻辑
  rawSetMessages(next);          // 触发 React 重渲染
}, []);

这是一个关键设计模式:

  • Ref 作为真实数据源,React state 作为渲染投影
  • setter 中同步更新 ref,保证所有闭包读到最新值
  • 避免 onQuery/onSubmit 等回调因 messages 变化而频繁重建

useDeferredValue(第 1318 行):

tsx 复制代码
const deferredMessages = useDeferredValue(messages);

React 18 并发特性。messages 在流式更新时频繁变化,deferredMessages 以过渡优先级更新,保证输入框保持响应。

4.11 输入状态管理(第 1329-1371 行)

tsx 复制代码
const [inputValue, setInputValueRaw] = useState(() => consumeEarlyInput());

consumeEarlyInput() 消费 REPL 挂载前捕获的早期输入(用户在启动过程中就打字的内容)。

tsx 复制代码
const setInputValue = useCallback((value: string) => {
  if (trySuggestBgPRIntercept(inputValueRef.current, value)) return;
  if (inputValueRef.current === '' && value !== '' && /* 最近3秒内没有滚动 */) {
    repinScroll();
  }
  inputValueRef.current = value;
  setInputValueRaw(value);
  setIsPromptInputActive(value.trim().length > 0);
}, []);

类型空 → 非空时自动回到底部,但保留 3 秒窗口防止用户在阅读时被拉走。

4.12 提示抑制系统(第 1365-1371 行)

tsx 复制代码
useEffect(() => {
  if (inputValue.trim().length === 0) return;
  const timer = setTimeout(setIsPromptInputActive, PROMPT_SUPPRESSION_MS, false);
  return () => clearTimeout(timer);
}, [inputValue]);

PROMPT_SUPPRESSION_MS = 1500。用户停止打字 1.5 秒后才弹出打断式对话框,防止意外确认权限。

4.13 远程模式初始化(第 1380-1422 行)

三种远程模式互斥:

tsx 复制代码
const remoteSession = useRemoteSession({ config: remoteSessionConfig, ... });
const directConnect = useDirectConnect({ config: directConnectConfig, ... });
const sshRemote = useSSHSession({ session: sshSession, ... });
const activeRemote = sshRemote.isRemoteMode ? sshRemote
  : directConnect.isRemoteMode ? directConnect
  : remoteSession;

useRemoteSession --- WebSocket 连接远程 CCR 实例

useDirectConnect --- WebSocket 连接本地 claude server

useSSHSession --- SSH 子进程通信

4.14 防休眠与标签页状态(第 1149-1175 行)

tsx 复制代码
useEffect(() => {
  if (isLoading && !isWaitingForApproval && !isShowingLocalJSXCommand) {
    startPreventSleep();
    return () => stopPreventSleep();
  }
}, [isLoading, isWaitingForApproval, isShowingLocalJSXCommand]);

Claude 工作时阻止 macOS 进入睡眠。

tsx 复制代码
const sessionStatus: TabStatusKind = isWaitingForApproval || isShowingLocalJSXCommand
  ? 'waiting'
  : isLoading ? 'busy' : 'idle';

状态通过 useTabStatus 推送到终端标签页侧边栏(OSC 21337 协议)。

4.15 通知系统(第 745-773 行)

一系列 useEffect 风格的自定义 hooks,在特定条件下触发通知:

Hook 条件
useModelMigrationNotifications 模型版本迁移
useIDEStatusIndicator IDE 连接状态
useMcpConnectivityStatus MCP 服务器连接
useRateLimitWarningNotification 速率限制警告
useFastModeNotification 快速模式
useDeprecationWarningNotification 弃用警告
useNpmDeprecationNotification npm 弃用
等等...

4.16 成本阈值对话框(第 2203-2215 行)

tsx 复制代码
useEffect(() => {
  const totalCost = getTotalCost();
  if (totalCost >= 5 && !showCostDialog && !haveShownCostDialog) {
    // 显示 $5 成本阈值警告
  }
}, [messages, showCostDialog, haveShownCostDialog]);

当会话成本达到 $5 时弹出确认对话框。使用 haveShownCostDialog 标记确保只弹一次(即使没有控制台计费权限也标记为已显示,防止事件风暴)。

4.17 沙箱网络权限(第 2216-2310 行)

sandboxAskCallback --- 网络访问权限请求的回调,支持三种模式:

  1. Worker 模式(第 2218-2249 行):通过 mailbox 转发给 leader
  2. 本地模式(第 2253-2265 行):显示本地权限对话框
  3. Bridge 模式(第 2270-2308 行):同时转发到远程控制端(claude.ai

支持竞速解决:本地和远程谁先响应就用谁的结果。

4.18 getFocusedInputDialog()(第 2017-2065 行)

对话框优先级系统,返回值决定当前显示哪个对话框:

markdown 复制代码
优先级从高到低:
1. isExiting / exitFlow         → 不显示任何对话框
2. isMessageSelectorVisible     → 消息选择器
3. sandboxPermissionRequestQueue → 网络权限(不因打字抑制)
4. toolUseConfirmQueue          → 工具使用审批
5. promptQueue                  → 通用提示框
6. workerSandboxPermissions     → Worker 沙箱权限
7. elicitation.queue            → MCP 提示
8. showingCostDialog            → 成本对话框
9. idleReturnPending            → 空闲返回对话框
...(依次降低优先级)

第 4-8 项受 isPromptInputActive 抑制:用户正在打字时跳过。

4.19 getToolUseContext()(第 2392-2523 行)

构建 ProcessUserInputContext,这是整个查询管线的核心上下文对象,包含:

tsx 复制代码
return {
  abortController,
  options: {
    commands, tools, debug, verbose, mainLoopModel,
    thinkingConfig, mcpClients, mcpResources,
    dynamicMcpConfig, theme,
    customSystemPrompt, appendSystemPrompt,
    refreshTools  // 闭包内从 store 重新读取工具列表
  },
  getAppState: () => store.getState(),
  setAppState,
  messages, setMessages,
  updateFileHistoryState,
  updateAttributionState,
  openMessageSelector,
  onChangeAPIKey: reverify,
  readFileState,
  setToolJSX,
  addNotification,
  appendSystemMessage,
  sendOSNotification,
  onChangeDynamicMcpConfig,
  // ...更多工具函数
};

关键设计 --- computeTools()(第 2404-2410 行):每次调用时从 store.getState() 重新读取 MCP 工具列表,确保异步连接的 MCP 工具不会因为闭包陈旧而丢失。

4.20 onCancel()(第 2106-2163 行)

ctrl+c / Escape 中断处理器:

tsx 复制代码
function onCancel() {
  queryGuard.forceEnd();        // 强制结束 query
  // 保留已流式文本(第 2125-2129 行)
  if (streamingText?.trim()) {
    setMessages(prev => [...prev, createAssistantMessage({ content: streamingText })]);
  }
  resetLoadingState();
  // 根据当前对话框类型处理:
  if (focusedInputDialog === 'tool-permission') {
    toolUseConfirmQueue[0]?.onAbort();
    setToolUseConfirmQueue([]);
  } else if (focusedInputDialog === 'prompt') {
    // 拒绝所有 pending prompt
  } else if (activeRemote.isRemoteMode) {
    activeRemote.cancelRequest();
  } else {
    abortController?.abort('user-cancel');
  }
}

4.21 onQueryEvent()(第 2584-2659 行)

消息流事件处理器,是 query() 函数输出端到 setMessages() 的桥梁:

三种消息类型的分发逻辑:

  1. CompactBoundary(第 2586-2607 行) --- 上下文压缩边界

    • 全屏模式:保留压缩前的消息作为滚动缓存
    • 滚动模式:清空(只保留边界后的消息)
    • 生成新的 conversationId,重置 Messages 组件的所有 memo 缓存
  2. Ephemeral Progress(第 2608-2627 行) --- 短暂进度消息

    • 只替换同一 tool call 的最后一条进度消息,避免数组无限膨胀(观察过 13k+ 条)
    • 只对 isEphemeralToolProgress 类型生效(sleep 进度、bash 进度),不替换 agent/hook/skill 进度
  3. 普通消息(第 2628-2630 行) --- 直接追加

4.22 onQueryImpl()(第 2661-2853 行)

查询执行的核心实现:

执行流程:

scss 复制代码
1. 诊断跟踪 → IDE 关闭打开的 diff(第 2665-2672 行)
2. 项目引导标记完成(第 2675 行)
3. 会话标题提取(第 2684-2699 行)
   └─ 首次用户消息 → generateSessionTitle() 调用 Haiku API
4. 斜杠命令工具权限注入(第 2701-2726 行)
5. 非查询命令短路(第 2730-2744 行)
   └─ shouldQuery=false → resetLoadingState,返回
6. 构建 toolUseContext + 计算 tools/mcpClients(第 2746-2755 行)
7. 并加载系统提示 + 用户/系统上下文(第 2768-2772 行)
   └─ checkAndDisableBypassPermissionsIfNeeded()
   └─ getSystemPrompt() + getUserContext() + getSystemContext()
8. 构建最终 systemPrompt(第 2781-2788 行)
9. for await...of query({...})(第 2793-2803 行)
   └─ 逐事件调用 onQueryEvent()
10. Companion 反应触发(第 2804-2809 行)
11. API 指标计算(第 2814-2845 行)
    └─ TTFT / OTPS / Hook 耗时 / Tool 耗时 / Classifier 耗时
12. resetLoadingState()(第 2847 行)

关键:for await...of (第 2793 行)。query() 是一个异步生成器函数 ,每次 yield 一个事件,onQueryEvent 处理每个事件(增量渲染流式消息、工具使用、进度更新)。这是整个交互模式的核心事件循环

4.23 onQuery()(第 2855-3000 行)

onQueryonQueryImpl 的安全包装:

tsx 复制代码
const onQuery = useCallback(async (...): Promise<void> => {
  // Swarm 活跃状态标记
  const thisGeneration = queryGuard.tryStart();  // 并发守卫
  if (thisGeneration === null) {                 // 已有查询运行中
    // 将输入入队等待
    newMessages.filter(...).forEach(msg => enqueue({ value: msg, mode: 'prompt' }));
    return;
  }
  try {
    // 初始化:重置计时器、追加消息、清空流式状态...
    await onQueryImpl(...);
  } finally {
    if (queryGuard.end(thisGeneration)) {        // 只有本轮结束才执行
      resetLoadingState();
      await mrOnTurnComplete(messagesRef.current, abortController.signal.aborted);
      // 通知 bridge 客户端
      sendBridgeResultRef.current();
      // 添加 turn 耗时消息(>30s 或 token budget 启用时)
      if (turnDurationMs > 30000 || budgetInfo !== undefined) {
        setMessages(prev => [...prev, createTurnDurationMessage(...)]);
      }
    }
  }
}, [...]);

并发入队(第 2876-2884 行):如果用户连发两条消息,第二条不会丢失,而是进队列等待。

4.24 onSubmit()(第 3142-3545 行)

用户按回车后的完整处理流程:

scss 复制代码
第一步:repinScroll() --- 回到底部
第二步:resumeProactive() --- 恢复主动模式
第三步:检查 immediate 命令(第 3161-3281 行)
  ├─ /config, /btw, /sandbox 等
  └─ 如果 query 正在运行且命令标记为 immediate → 直接执行,返回
第四步:远程模式空输入检查(第 3285-3287 行)
第五步:idle-return 检查(第 3292-3310 行)
  └─ 空闲超过阈值 + token 超过阈值 → 弹出空闲返回对话框
第六步:添加到历史记录(第 3316-3326 行)
第七步:输入清除 + placeholder 显示(第 3339-3389 行)
  └─ setUserInputOnProcessing(input) --- 显示"❯ <输入>"占位
第八步:speculation 接受处理(第 3392-3406 行)
第九步:远程模式发送(第 3416-3486 行)
  ├─ 构建消息内容 + 图片附件
  ├─ createUserMessage + setMessages
  └─ activeRemote.sendMessage()
第十步:本地查询(第 3488-3519 行)
  ├─ await awaitPendingHooks()    --- 等待 hook 消息
  └─ await handlePromptSubmit()  --- 核心提交流程

Immediate 命令执行(第 3208-3280 行):

tsx 复制代码
const executeImmediateCommand = async (): Promise<void> => {
  const mod = await matchingCommand.load();
  const jsx = await mod.call(onDone, context, commandArgs);
  if (jsx && !doneWasCalled) {
    setToolJSX({ jsx, shouldHidePromptInput: false, isLocalJSXCommand: true });
  }
};

通过 isLocalJSXCommand 机制叠加 UI,不影响正在进行的对话。

4.25 onAgentSubmit()(第 3548-3578 行)

在查看 teammate/agent 视角时提交输入:

  • 本地 agent:appendMessageToLocalAgent + queuePendingMessage(运行时)或 resumeAgentBackground(空闲时)
  • 进程内 teammate:injectUserMessageToTeammate

4.26 resume()(第 1735-1948 行)

会话恢复/分支的完整流程:

scss 复制代码
1. deserializeMessages()         → 清理未完成的 tool use
2. 协调器模式匹配                 → 切换 Agent 定义
3. executeSessionEndHooks()      → 触发当前会话的结束 hooks
4. processSessionStartHooks()    → 触发新会话的启动 hooks
5. copyPlanForFork/Resume()      → 复制计划文件
6. restoreSessionStateFromLog()  → 恢复文件历史、归属状态
7. restoreAgentFromSession()     → 恢复 Agent 设置
8. computeStandaloneAgentContext → 恢复 agent 名称/颜色
9. 切换会话 ID + 目录            → switchSession()
10. 退出旧 worktree + 进入新 worktree
11. reconstructContentReplacementState → 重建内容替换状态
12. setMessages()                → 设置恢复后的消息

五、对话框系统(第 4060-4893 行)

渲染到 FullscreenLayoutbottom 插槽,由 getFocusedInputDialog() 决定当前显示哪个。

组件 对话框类型 触发条件
PermissionRequest 'tool-permission' Claude 请求使用工具
SandboxPermissionRequest 'sandbox-permission' 网络访问请求
PromptDialog 'prompt' 通用提示
ElicitationDialog 'elicitation' MCP 服务器询问
CostThresholdDialog 'cost' 成本超过 $5
IdleReturnDialog 'idle-return' 长时间空闲后返回
WorkerPendingPermission --- Worker 等待 leader 审批
IdeOnboardingDialog 'ide-onboarding' IDE 首次设置引导
AntModelSwitchCallout 'model-switch' 模型版本迁移提示
UndercoverAutoCallout 'undercover-callout' 自动模式切换
EffortCallout 'effort-callout' Effort 模式介绍
RemoteCallout 'remote-callout' 远程模式提示
PluginHintMenu 'plugin-hint' 插件推荐
LspRecommendationMenu 'lsp-recommendation' LSP 插件推荐
DesktopUpsellStartup 'desktop-upsell' 桌面应用推广
UltraplanChoiceDialog 'ultraplan-choice' 超计划选择
UltraplanLaunchDialog 'ultraplan-launch' 超计划启动

六、渲染层(第 4485-5004 行)

6.1 结构树

tsx 复制代码
<KeybindingSetup>
  <AnimatedTerminalTitle />
  <GlobalKeybindingHandlers />
  <VoiceKeybindingHandler />        {/* 条件编译 */}
  <CommandKeybindingHandlers />
  <ScrollKeybindingHandler />
  <CancelRequestHandler />
  <MCPConnectionManager>            {/* MCP 连接生命周期管理 */}
    <FullscreenLayout>              {/* 全屏布局:scrollable + bottom */}
      {/* scrollable 区域 */}
      <TeammateViewHeader />
      <Messages messages={displayedMessages} ... />
      <UserTextMessage />           {/* 处理中占位符 */}
      <SpinnerWithVerb />           {/* 加载动画 */}
      <BriefIdleStatus />           {/* 空闲状态 */}

      {/* bottom 区域 --- 所有对话框和输入 */}
      <PermissionRequest />
      <SandboxPermissionRequest />
      <PromptDialog />
      <ElicitationDialog />
      <CostThresholdDialog />
      <IdleReturnDialog />
      {/* ...约 20 种对话框... */}
      <PromptInput />               {/* 底部输入框 */}
      <SessionBackgroundHint />
    </FullscreenLayout>
  </MCPConnectionManager>
</KeybindingSetup>

6.2 全屏模式守卫(第 4499-5004 行)

tsx 复制代码
const mainReturn = <KeybindingSetup>...</KeybindingSetup>;

if (isFullscreenEnvEnabled()) {
  return <AlternateScreen mouseTracking={isMouseTrackingEnabled()}>
    {mainReturn}
  </AlternateScreen>;
}
return mainReturn;
  • 全屏环境 :包裹 <AlternateScreen> 使用备用屏幕缓冲 + 鼠标跟踪
  • 滚动模式:直接使用终端原生滚动缓冲

6.3 placeholder 文本(第 4518 行)

tsx 复制代码
const placeholderText = userInputOnProcessing && !viewedAgentTask
  && displayedMessages.length <= userInputBaselineRef.current
  ? userInputOnProcessing : undefined;

在用户消息真正渲染到消息列表之前,显示 "❯ <用户输入>" 作为占位符。一旦 displayedMessages 增长超过提交时的基准长度,占位符自动消失。

6.4 Companion 精灵(第 4524-4531 行)

tsx 复制代码
const companionNarrow = transcriptCols < MIN_COLS_FOR_FULL_SPRITE;
const companionVisible = !toolJSX?.shouldHidePromptInput && !focusedInputDialog && !showBashesDialog;

窄终端中 companion 精灵变为单行模式(堆叠在输入上方),宽终端中并排显示。


七、关键设计模式总结

7.1 Ref 镜像模式

scss 复制代码
React State (渲染触发)  ←──  setMessages()   ──→  messagesRef (同步真实数据源)
                                                    ↑
                                              所有闭包都读 ref
好处:onQuery/onSubmit 等回调不因 messages 变化而重建,避免下游组件大规模重渲染

7.2 QueryGuard 状态机

scss 复制代码
idle → tryStart() → running → end() → idle
                  ↘  (并发时返回 null,输入入队)
forceEnd() 强制回到 idle(用户取消)

7.3 死代码消除策略

arduino 复制代码
feature('FLAG')       → 编译时替换为 true/false,false 分支被 tree-shaking 移除
"external" === 'ant'  → 外部构建为 false,内部构建为 true

7.4 对话框优先级 + 打字抑制

ini 复制代码
getFocusedInputDialog() 输出当前最高优先级的对话框类型
isPromptInputActive = true → 跳过 mid-priority 对话框
用户停止打字 1.5s 后 → isPromptInputActive = false → 对话框弹出

7.5 精确耗时计算

ini 复制代码
loadingStartTimeRef = Date.now()
  pauseStartTimeRef = Date.now()  ← 权限审批弹窗
  totalPausedMsRef += pause 持续时间
turnDurationMs = Date.now() - loadingStartTimeRef - totalPausedMsRef

八、执行顺序总结

scss 复制代码
1. 模块加载(import + 条件 require)
2. REPL() 第一次渲染:
   a. useState 初始化所有状态
   b. useMemo 计算派生值
   c. useCallback 创建回调
   d. useEffect 注册副作用(通知、定时器、MCP 连接等)
   e. 渲染 JSX 结构
3. 用户输入 → onSubmit():
   f. 清除输入、显示 placeholder
   g. handlePromptSubmit() → processUserInput()
   h. onQuery() → queryGuard.tryStart()
   i. onQueryImpl() → for await...of query()
   j. query() 逐事件 yield → onQueryEvent() → setMessages()
4. 查询结束 → onQuery finally:
   k. queryGuard.end()
   l. resetLoadingState()
   m. onTurnComplete()
   n. 添加 turn 耗时消息
相关推荐
青山如墨雨如画3 小时前
【Claude】Win11电脑下VSCode环境中Claude+Deepseek的报错及解决方法记录日志
vscode·aigc·claude·authropic
云安全助手14 小时前
2026年企业级Claude中转服务深度评测:安全、稳定与速度的终极答案
人工智能·安全·claude·ai大模型
yaocheng的ai分身15 小时前
【转载】Scaling Managed Agents:将“大脑”和“手”解耦
claude
xcLeigh16 小时前
聚合AI工具KULAAI:GPT、Claude、Gemini、DeepSeek热门模型一键使用
人工智能·gpt·claude·gemini·deepseek·聚合ai·kulaai
DO_Community18 小时前
为AI编程降本!OpenCode 原生支持 DigitalOcean 推理路由器
智能路由器·ai编程·claude
Bigger19 小时前
mini-cc 的 MCP 协议:给 AI 装个 USB-C 接口
人工智能·ai编程·claude
ZzT19 小时前
Claude Code 把编排写进代码:Dynamic Workflows 详解
claude
创世宇图20 小时前
Claude Opus 4.8 深度实测:动态多 Agent 协同、Effort Control 与幻觉抑制的工程化解析
ai·llm·agent·claude·ai工程化
m0_535817551 天前
macOS上Claude Code安装配置保姆级教程:国内直连API,从0到1跑通(附避坑指南)
gpt·macos·ai·node.js·claude·claudecode·88api