职责概述

解决的问题:用户在终端输入 claude 后到出现交互界面,中间需要完成认证、环境检测、配置加载、权限初始化等一系列前置工作。启动链路将这些步骤编排为一条可靠的初始化管道,确保首次安装和日常使用都能快速进入交互状态。

应用场景:① 首次安装后的 OAuth 认证引导 ② 每次启动时的环境检测(Node 版本、终端类型、代理配置)③ 从 CI/CD、IDE 插件、SDK 等不同入口点进入时的差异化初始化 ④ 断线恢复和会话续接。

一句话理解:就像电脑的开机流程——从按下电源键到桌面出现,中间的 BIOS 自检、驱动加载、登录界面都在这一层。

架构设计

Bootstrap 入口层
entrypoints/cli.tsx
bootstrap/state.ts
全局初始化层
entrypoints/init.ts
main.tsx main()
命令注册与环境层
main.tsx run()
setup.ts
交互设置与渲染层
interactiveHelpers.tsx
dialogLaunchers.tsx
replLauncher.tsx
状态管理层
state/store.ts
state/AppStateStore.ts
state/AppState.tsx
state/onChangeAppState.ts

核心数据流

1. cli.tsx 快速路径分发
entrypoints/cli.tsx:33 中的 main() 函数是整个 CLI 的真正入口。它首先检查 --version/-v/-V 实现零模块加载快速退出,然后依次检测十几种快速路径(bridge、daemon、bg sessions、templates 等),每种路径使用动态 import 避免加载无关代码。

// entrypoints/cli.tsx:37-42
if (args.length === 1 && (args[0] === '--version' || ...)) {
  console.log(`${MACRO.VERSION} (Claude Code)`);
  return;
}
未匹配任何快速路径时,通过 startCapturingEarlyInput() 开始捕获终端早期输入(用户在加载过程中键入的字符),然后动态 import main.tsx 并调用 cliMain()
2. main.tsx main() — 安全与协议预处理
main.tsx:585 在进入 Commander 解析之前完成多项关键预处理:
  • 安全设置:设置 NoDefaultCurrentDirectoryInExePath 防止 Windows PATH 劫持
  • 信号处理:注册 SIGINT 和 exit 处理器
  • URL/协议处理:解析 cc:// 深链接、macOS URL scheme、claude assistantclaude ssh
  • 模式检测:判断交互/非交互模式,设置 clientType(cli/sdk/github-action 等)
// main.tsx:803-812
const isNonInteractive = hasPrintFlag || hasInitOnlyFlag || hasSdkUrl || !process.stdout.isTTY;
setIsInteractive(!isNonInteractive);
initializeEntrypoint(isNonInteractive);
eagerLoadSettings();
3. run() — Commander preAction 钩子
main.tsx:884 中的 run() 创建 Commander 程序实例,通过 preAction 钩子执行所有子命令共享的初始化逻辑:
// main.tsx:907-967
program.hook('preAction', async thisCommand => {
  await Promise.all([ensureMdmSettingsLoaded(), ensureKeychainPrefetchCompleted()]);
  await init();
  // ... initSinks, loadRemoteManagedSettings, loadPolicyLimits 等
});
这确保只有真正执行命令时才触发初始化,显示帮助信息时不加载。
4. init.ts — 全局服务初始化
entrypoints/init.ts:57 使用 memoize 确保只执行一次,按序初始化:
  • enableConfigs() — 启用配置系统
  • applySafeConfigEnvironmentVariables() — 仅应用安全环境变量(信任对话框之前)
  • setupGracefulShutdown() — 注册退出清理
  • configureGlobalMTLS() + configureGlobalAgents() — 网络层配置
  • preconnectAnthropicApi() — 预热 TLS 连接
  • 远程管理设置加载、LSP 清理注册、scratchpad 初始化
5. setup.ts — 环境与权限准备
setup.ts:56 接收 cwd、权限模式、worktree 选项等参数,执行:
  • Node.js 版本检查(≥18)
  • UDS 消息服务器启动(用于 teammate 通信)
  • 终端备份恢复(iTerm2 / Terminal.app 中断恢复)
  • Worktree 创建与 tmux 会话管理
  • 后台任务启动:SessionMemory、ContextCollapse、归因钩子、插件预加载
  • 安全沙箱验证(--dangerously-skip-permissions 仅允许在无网络容器中使用)
// setup.ts:160-161
setCwd(cwd);
captureHooksConfigSnapshot(); // 在 setCwd 之后,确保 hooks 从正确目录加载
6. showSetupScreens — 交互式设置对话框序列
interactiveHelpers.tsx:104 按序显示多个设置对话框:
  • Onboarding — 首次使用引导(主题选择等)
  • TrustDialog — 工作区信任确认(安全边界,检查 CLAUDE.md 外部引用)
  • MCP Server Approvals — .mcp.json 服务器审批
  • Grove Policy — 企业策略接受
  • API Key Approval — 自定义 API Key 确认
  • BypassPermissions Mode Dialog — 危险模式警告
  • Auto Mode Opt-in — 自动模式同意
信任确认后触发:GrowthBook 重新初始化、完整环境变量应用、遥测初始化。
7. launchRepl — REPL 启动
replLauncher.tsx:12 是最终启动点,通过动态 import 加载 AppREPL 组件:
// replLauncher.tsx:12-22
export async function launchRepl(root, appProps, replProps, renderAndRun) {
  const { App } = await import('./components/App.js');
  const { REPL } = await import('./screens/REPL.js');
  await renderAndRun(root, <App {...appProps}><REPL {...replProps} /></App>);
}
renderAndRuninteractiveHelpers.tsx:98)调用 root.render() 渲染 React 组件树,然后调用 startDeferredPrefetches() 启动延迟预取,最后 await root.waitUntilExit() 等待 REPL 退出。

关键类型与接口

Bootstrap 全局状态 (bootstrap/state.ts)

// bootstrap/state.ts:45-120 (核心字段)
type State = {
  originalCwd: string           // 原始工作目录
  projectRoot: string           // 项目根目录(稳定,不随 worktree 变化)
  sessionId: SessionId          // 当前会话 ID
  isInteractive: boolean        // 是否交互式会话
  clientType: string            // 客户端类型 (cli/sdk/github-action 等)
  modelUsage: { [modelName: string]: ModelUsage }  // 模型使用统计
  // ...遥测、日志、OpenTelemetry 状态
  agentColorMap: Map<string, AgentColorName>  // Agent 颜色映射
  allowedSettingSources: SettingSource[]     // 允许的设置来源
}

AppState 类型 (state/AppStateStore.ts)

// state/AppStateStore.ts:89+
export type AppState = DeepImmutable<{
  settings: SettingsJson
  verbose: boolean
  mainLoopModel: ModelSetting
  toolPermissionContext: ToolPermissionContext
  tasks: Record<string, TaskState>
  mcp: { clients: MCPServerConnection[]; tools: ... }
  speculationState: SpeculationState
  fastMode: FastModeState
  effortValue: EffortValue | undefined
  fileHistory: FileHistoryState
  attribution: AttributionState
  // ... teammate、bridge、denial tracking 等字段
}>

通用状态容器 (state/store.ts)

// state/store.ts:4-34
export type Store<T> = {
  getState: () => T
  setState: (updater: (prev: T) => T) => void
  subscribe: (listener: Listener) => () => void
}
// 不可变比较优化:Object.is(next, prev) 时跳过通知
export function createStore<T>(initialState: T, onChange?: OnChange<T>): Store<T>

AppWrapperProps (replLauncher.tsx)

// replLauncher.tsx:7-11
type AppWrapperProps = {
  getFpsMetrics: () => FpsMetrics | undefined;
  stats?: StatsStore;
  initialState: AppState;
};

设计模式与亮点

1. 渐进式加载与快速路径 (Fast-Path Dispatch)

cli.tsx 采用策略模式分发到十几种快速路径,每种路径只加载所需模块。这利用了 Bun 的 feature() 函数在构建时进行死代码消除(DCE),确保外部构建不包含内部功能。

这种设计使得 --version 的启动时间接近零(无模块加载),而完整交互模式的启动约需 300-500ms。

2. 副作用排序 (Side-Effect Ordering)

main.tsx:1-20 在所有 import 之前执行三个并行副作用:启动性能分析标记、MDM 子进程启动、macOS Keychain 预取。这些操作与其余 135ms 的模块 import 重叠执行,显著减少首屏延迟。

3. 延迟预取 (Deferred Prefetch)

将非首屏必需的预取工作(插件加载、Git 归因钩子、SessionMemory 初始化等)推迟到 startDeferredPrefetches() 在 REPL 渲染后才执行,避免阻塞首次渲染。

4. 不可变状态容器 (Immutable Store)

state/store.ts 实现了一个极简的不可变状态容器:setState 接受 updater 函数返回新状态,通过 Object.is 比较避免不必要通知。AppStateProvider 在 React 层封装并提供 onChangeAppState 回调,实现状态变更的副作用管理(如权限模式变更时通知 SDK)。

5. 信任边界 (Trust Boundary)

系统区分"安全"和"完整"环境变量:applySafeConfigEnvironmentVariables() 在信任对话框之前执行,applyConfigEnvironmentVariables() 在信任确认之后执行。这防止了不受信任的工作目录通过 settings.json 注入危险环境变量。

开发者实践指南

在添加新的启动阶段时,使用 profileCheckpoint('label') 标记关键时间点,这会输出到启动性能分析报告中。

添加新的快速路径

entrypoints/cli.tsxmain() 函数中,按照现有模式添加新的快速路径检测。所有 import 必须是动态的,以保持最小加载。使用 feature('FLAG') 包裹内部功能:

// entrypoints/cli.tsx 模式
if (feature('MY_FLAG') && args[0] === 'my-command') {
  profileCheckpoint('cli_my_command_path');
  const { myMain } = await import('../myModule/main.js');
  await myMain(args.slice(1));
  return;
}

添加新的设置对话框

interactiveHelpers.tsx showSetupScreens() 中按条件插入新对话框。使用 showSetupDialog 包裹以获得 AppStateProvider 和 KeybindingSetup 的上下文。对话框遵循 (done) => ReactNode 回调模式。

扩展全局状态

新的全局状态优先添加到 AppState 类型中(通过 state/AppStateStore.ts),而非 bootstrap/state.ts。Bootstrap state 仅用于在 React 渲染之前就需要访问的底层状态(如 sessionId、cwd)。AppState 通过 React Context 注入,支持响应式更新。

架构师决策指南

性能与启动时间的权衡

启动链路的设计围绕"最小化首次可交互时间"展开。关键决策:

状态管理的分层

系统采用两层状态管理:

不要在两个状态系统之间创建循环依赖。Bootstrap state 的变更不会触发 React 重渲染;如需 UI 更新,必须同时更新 AppState。

安全边界的扩展性

信任对话框是所有安全策略的锚点。添加新的安全检查时,应将其放入 showSetupScreens 中信任对话框之后的位置。环境变量分为"安全"和"完整"两套,任何可能被不受信任工作目录控制的环境变量都必须归类到"完整"集合中。

可视化处理拓扑图

启动链路是一条漏斗式管线:CLI 入口以零加载代价快速分流特殊路径,未匹配的请求依次经过安全预处理、Commander 注册、全局初始化、环境准备和交互设置,最终启动 REPL。每个快速路径在 cli.tsx 就地返回,只加载自身所需模块。

进程启动 — process.argventrypoints/cli.tsx:33
快速路径分流 — main()cli.tsx:37-274 — 零模块加载判断
↓ 命中快速路径 → 就地返回(零额外加载)
--versioncli.tsx:37
MCP 服务器cli.tsx:72
daemon workercli.tsx:100
bridge 模式cli.tsx:112
后台会话cli.tsx:185
模板任务cli.tsx:212
~10 个其他feature() 守卫
无快速路径匹配 ↓
↓ 进入主路径 — 动态 import main.tsx ↓
Phase 2 — 主路径初始化管线
startCapturingEarlyInput + import main.tsxcli.tsx:288-296 — 约 135ms
安全预处理 — 攻击面防御main.tsx:585-607
Windows PATH 劫持防护main.tsx:591
信号处理器注册main.tsx:595
cc:// URL 重写main.tsx:612
argv 预处理 — 多模式检测main.tsx:647-770 — URI/assistant/SSH
交互模式检测 + 早期设置加载main.tsx:803-812
Commander 程序实例 — preAction 钩子main.tsx:884-967
preAction 异步初始化main.tsx:907-967
命令执行时
MDM/Keychain 预取main.tsx:914
init() — 全局服务初始化 (memoize)entrypoints/init.ts:57
enableConfigsinit.ts:59
安全环境变量init.ts:62
TLS 预连接init.ts:78
Phase 3 — 环境准备与 REPL 启动
选项解析与模式判断main.tsx:1090-1252
权限上下文初始化permissionSetup.ts:872
auto 模式
verifyAutoModeGateAccesspermissionSetup.ts:1078
setup() — 完整环境准备setup.ts:56
Node.js 版本检查setup.ts:62
沙箱验证setup.ts:120
后台任务启动setup.ts:160
showSetupScreens — 交互式设置对话框序列interactiveHelpers.tsx:104
Onboarding
TrustDialog
MCP 审批
API Key
Bypass 警告
launchRepl — REPL 启动replLauncher.tsx:12
动态 import App + REPLreplLauncher.tsx:14-15
root.render() + startDeferredPrefetchesreplLauncher.tsx:18-20
漏斗设计要点:启动链路的核心原则是"延迟加载"——快速路径在 cli.tsx 就地返回(零模块加载),主路径通过 preAction 钩子确保 --help 不触发 setup,feature() 编译时消除未启用功能的代码,startDeferredPrefetches 将非首屏必需工作推迟到 REPL 渲染后。

核心处理流程详解

Claude Code 的启动过程采用"漏斗式"架构:先在轻量入口快速分流特殊路径,再逐步加载重量级模块。整个过程从进程启动到 REPL 渲染,经过了 8 个关键阶段,每一层都精心控制了模块加载范围以优化冷启动时间。

1. CLI 快速入口 — 特殊路径分流
entrypoints/cli.tsx:33main() 首先检查 --version 等零依赖标志(L37-41)。通过后依次检测 ~15 个快速路径:MCP 服务器模式(L72-93)、daemon worker(L100-106)、bridge 模式(L112-162)、后台会话管理(L185-209)、模板任务(L212-222)等。每个快速路径使用 feature() 编译时消除 + await import() 按需加载,确保不相关代码零开销。
2. 主模块加载 — 触发 main.tsx
所有快速路径都不匹配时,cli.tsx:288-298 开始捕获早期输入(startCapturingEarlyInput),然后动态 import main.tsxmain()。这里的 profileCheckpoint 标记精确测量了 import 耗时(约 135ms),用于性能回归检测。
3. 安全预处理 — 攻击面防御
main.tsx:585-607 在任何业务逻辑之前执行安全加固:设置 NoDefaultCurrentDirectoryInExePath='1' 阻止 Windows PATH 劫持(L591),初始化警告处理器(L594),注册退出时的光标重置(L595-597)。接下来处理 cc:// URL 重写(L612-642)、deep link URI(L647-677)、assistant 模式(L685-700)和 SSH 远程(L706-770)等 argv 预处理。
4. Commander 注册与 preAction 初始化
main.tsx:884-967run() 函数创建 Commander 程序实例(L902),注册 ~60 个 CLI 选项。关键设计是 preAction 钩子(L907-967):只在真正执行命令时才初始化,避免 --help 触发昂贵的 setup。钩子内依次完成:MDM/Keychain 异步加载(L914)、init() 全局初始化(L916)、日志 sink 连接(L934)、迁移执行(L950)和远程设置加载(L957-958)。
5. 选项解析与模式判断
main.tsx:1090-1252 解析 Commander 选项:提取 print/verbose/debug 模式标志,检测 Assistant/Kairos 模式(L1048-1088,需通过信任对话框 + GrowthBook 门控),处理 worktree/tmux/teammate 选项(L1147-1218),并自动设置 SDK URL 的流式格式(L1236-1252)。
6. 权限上下文初始化
permissionSetup.ts:872-1033initializeToolPermissionContext() 从 CLI 标志、settings.json 和磁盘规则构建初始权限上下文。Auto 模式需通过 verifyAutoModeGateAccess()(L1078-1260)的异步门控检查,包括 GrowthBook 功能开关、危险权限剥离和 circuit-breaker 状态。
7. setup() — 完整环境准备
main.tsx 的 action handler 中调用 setup()(约在 L1700+ 区域),完成信任对话框展示、MCP 服务器连接、权限验证、worktree 创建和后台任务初始化。startDeferredPrefetches()main.tsx:388-431)启动不需要在首帧渲染前完成的预取:系统上下文、git 状态、skills 发现。
8. REPL 启动 — 进入交互循环
main.tsx:3733-3799 调用 launchRepl(),该函数(replLauncher.tsx)动态加载 App + REPL 组件。传入的配置包括:初始消息、文件历史快照、agent 定义、会话配置等。新会话和恢复会话(/resume-c)都汇聚到同一个 launchRepl 调用点,区别仅在于传入的初始数据不同。
整个启动链路的核心设计原则是"延迟加载":快速路径在 cli.tsx 就地返回,零模块加载;主路径按需初始化——preAction 钩子确保 --help 不触发 setup,feature() 编译时消除未启用的功能代码,startCapturingEarlyInput 在模块加载期间并行捕获用户输入,最大化利用等待时间。

设计精华

1. feature() 编译时特性消除

所有功能开关通过 import { feature } from 'bun:bundle' 引入。Bun 打包器在构建时对 feature('XXX') 进行死代码消除——外部构建直接移除内部功能代码块,使产物体积最小化。这比运行时 if 检查更安全:即使代码中引用了内部包名,它们也不会出现在外部构建中。

Feature Flag Dead Code Elimination
源码中大量使用此模式——快速路径、MCP 集成、bridge 模式等全部用 feature() 包裹
// entrypoints/cli.tsx:86 — 编译时决定是否包含此代码
if (feature('CHICAGO_MCP') && process.argv[2] === '--computer-use-mcp') {
  const { runComputerUseMcpServer } = await import('../utils/computerUse/mcpServer.js');
  await runComputerUseMcpServer();
  return; // 外部构建中此分支完全不存在
}
这种设计使得同一份源码能产出"内部版"和"外部版"两个差异巨大的构建产物,而无需维护分支。关键在于 Bun 的 feature() 既是运行时函数又是编译时标记——编译器能识别 if (feature('X')) 模式并消除整个分支。

2. 快速路径漏斗 — 零加载分派

cli.tsx 是整个系统最薄的一层。它通过字符串匹配 process.argv 快速分流 ~15 个子路径,每个子路径只 await import() 自己需要的模块。这意味着 claude --version 的开销几乎等于一次 console.log,而 claude daemon 完全不会加载 React、权限系统或工具定义。

Zero-Load Fast Path
cli.tsx:37-274 的每个快速路径都遵循同一模式
// cli.tsx:37-41 — 最快的快速路径,零模块加载
if (args.length === 1 && (args[0] === '--version' || args[0] === '-v')) {
  console.log(`${MACRO.VERSION} (Claude Code)`);
  return; // 不加载 profiler、config、任何业务模块
}

// cli.tsx:100-106 — daemon worker 只加载自己
if (feature('DAEMON') && args[0] === '--daemon-worker') {
  const { runDaemonWorker } = await import('../daemon/workerRegistry.js');
  await runDaemonWorker(args[1]);
  return;
}
快速路径模式的精妙之处在于每个分支都是"自包含"的——只加载自己需要的代码,且 return 后不会继续执行后续逻辑。这使得添加新的子命令不影响已有路径的性能。

3. preAction 延迟初始化钩子

main.tsx:907-967preAction 钩子解决了"何时初始化"的经典问题:用户输入 claude --help 时不需要连接网络、加载设置或初始化遥测——只有真正执行命令(action handler 触发)时才做昂贵操作。这种设计将帮助文本的响应时间从数百毫秒降到数十毫秒。

Lazy Initialization via preAction Hook
Commander 的 preAction 钩子确保只在命令执行时初始化
// main.tsx:907-916
program.hook('preAction', async thisCommand => {
  // 只在真正执行命令时才触发,--help 不会进入这里
  await Promise.all([ensureMdmSettingsLoaded(), ensureKeychainPrefetchCompleted()]);
  await init(); // 全局初始化(配置、网络、遥测)
  // ...
  void loadRemoteManagedSettings(); // 非阻塞,企业设置热加载
  void loadPolicyLimits();           // 非阻塞,策略限制
});
preAction 钩子内部的 void loadRemoteManagedSettings()void loadPolicyLimits() 使用"发射即忘"模式——它们不阻塞 REPL 启动,结果通过热更新机制异步生效。这是"乐观启动"策略的体现:先用默认值启动,等远程配置到达后再更新。

4. 信任门控的安全边界

在恶意仓库中,.claude/settings.json.claude/agents/ 是攻击者可控的。系统在 main.tsx:1042-1048 附近实现了信任门控:checkHasTrustDialogAccepted() 必须在启用 assistant 模式、加载 agent 定义或执行 hooks 之前通过。这确保即使攻击者构造了恶意配置,也不会在用户明确信任工作目录前生效。

Trust Gate Pattern
所有安全敏感操作都锚定在信任对话框之后
// main.tsx:1067-1069
if (!checkHasTrustDialogAccepted()) {
  console.warn('Assistant mode disabled: directory is not trusted.');
} else {
  kairosEnabled = assistantModule.isAssistantForced()
    || (await kairosGate.isKairosEnabled());
  // ... 只有信任后才初始化团队和 agent
}
信任门控是纵深防御的关键一环。它不替代权限系统(权限检查在运行时每个工具调用时都执行),而是阻止在启动阶段的"灰色地带"——hooks 执行、agent 提示词注入、settings.json 中的自动权限规则——这些在用户尚未确认信任时就可能触发。

5. profileCheckpoint 性能遥测管线

启动路径中散布的 profileCheckpoint() 调用(cli.tsx:48,292,296main.tsx:586,607,885,903,1007 等)构建了一个细粒度的启动时间线。每个 checkpoint 记录时间戳和标签,用于回归检测——如果某次提交导致 cli_before_main_importcli_after_main_import 的时间增长超过阈值,CI 会自动报警。

Startup Profiler Timeline
checkpoint 从入口到 REPL 渲染覆盖完整链路
// cli.tsx 中关键 checkpoint 序列
profileCheckpoint('cli_entry');           // L48: 入口开始
profileCheckpoint('cli_before_main_import'); // L292: 加载 main.tsx 前
profileCheckpoint('cli_after_main_import');  // L296: 加载完成
profileCheckpoint('cli_after_main_complete'); // L298: main() 返回后

// main.tsx 中继续
profileCheckpoint('main_function_start');     // L586
profileCheckpoint('run_function_start');      // L885
profileCheckpoint('preAction_start');         // L908
profileCheckpoint('action_handler_start');    // L1007
性能遥测不仅用于 CI 回归检测,还通过 Statsig 聚合到后端——产品团队能看到 P50/P95 的各阶段耗时,精确定位"某次发布后 macOS 上的 Keychain 预取变慢了 200ms"这类问题。这种数据驱动的性能优化让启动时间在功能持续增长的同时保持稳定。

Agent 实践借鉴 — 客服 Agent 启动设计

场景映射:客服 Agent 启动时会遇到什么真实问题?

一个客服 Agent 上线时要面对的远不止"加载代码"这么简单。真实痛点是:

借鉴 CC:Claude Code 是怎么解决启动问题的?

CC 的启动是一个"漏斗式"管线:cli.tsx 先做零依赖的快速路径分流(如 --version 直接返回),不匹配快速路径的才进入主初始化管线。主管线内部采用渐进式加载:先展示 REPL 界面 → 再加载工具定义 → 再连接 MCP 服务器。整个过程通过 profileCheckpoint() 在每个阶段打点,方便定位哪个阶段慢了。

// CC 的启动管线(简化)
// cli.tsx: 零依赖快速路径
if (args[0] === '--version') { console.log(VERSION); return; }

// 主路径:渐进式加载
profileCheckpoint('cli_entry');
startCapturingEarlyInput();                // 并行捕获用户输入
const { cliMain } = await import('./main.js');  // ~135ms
profileCheckpoint('cli_after_import');

// main.tsx preAction: 只在命令执行时初始化
program.hook('preAction', async () => {
  await Promise.all([loadMDM(), prefetchKeychain()]);  // 并行预取
  await init();          // 全局初始化(配置、网络、遥测)
  void loadRemoteSettings();  // 发射即忘,不阻塞启动
});

// 最后:渲染 REPL 界面,后台继续加载工具和 MCP
launchRepl();
startDeferredPrefetches();  // 插件、git 状态、skills 延迟加载

客服 Agent 怎么改:多阶段启动管线

借鉴 CC 的渐进式加载思路,但不用 feature flag 编译时消除(客服 Agent 不需要同一份代码出两个构建产物,用运行时配置区分渠道即可)。改造后的启动管线分三个阶段:

// 客服 Agent 多阶段启动管线
type ChannelType = 'phone' | 'online' | 'ticket';

async function bootstrapAgent(channel: ChannelType, config: AgentConfig) {
  const checkpoints = new Map<string, number>();
  const mark = (label: string) => checkpoints.set(label, Date.now());

  // ===== 阶段 1:建立对话能力(类比 CC 的 cli.tsx → 展示 REPL)=====
  // 目标:让坐席看到界面,客户能发消息,即使后面系统还没连上
  mark('stage1_start');
  const chatEngine = initChatEngine(channel);  // 同步,<10ms
  const basicPrompt = buildSystemPrompt(config.basePrompt);
  mark('stage1_complete');
  // 此时坐席已经能看到对话界面,客户消息已能接收(暂存在队列中)

  // ===== 阶段 2:加载业务工具(类比 CC 的工具注册 + REPL 渲染)=====
  // 目标:Agent 能开始处理基础问题(查 FAQ、查知识库)
  mark('stage2_start');
  const baseTools = loadBaseTools();           // 查知识库、查 FAQ、转人工(所有渠道通用)
  const channelTools = loadChannelTools(channel); // 按渠道加载
  // online 渠道才有"发优惠券",phone 渠道才有"查通话录音"
  const allTools = [...baseTools, ...channelTools];
  mark('stage2_complete');
  // 此时 Agent 已经能回答常见问题、查知识库

  // ===== 阶段 3:连接外部系统(类比 CC 的 MCP 连接 + 延迟预取)=====
  // 目标:Agent 能处理需要外部系统的请求(查订单、查物流、退款)
  // 这些连接异步进行,不阻塞前面的阶段
  mark('stage3_start');
  const externalConnections: Record<string, Promise<any>> = {};

  // 并行连接所有外部系统
  const connectPromises = {
    crm:     connectCRM(config.crmUrl),           // 客户画像
    order:   connectOrderSystem(config.orderUrl),  // 订单查询
    payment: connectPaymentSystem(config.payUrl),  // 退款状态
    sms:     channel === 'online' ? connectSMS(config.smsUrl) : Promise.resolve(null),
    // ↑ SMS 只在在线渠道连接,电话/工单渠道跳过(懒加载)
  };

  // 不等待连接完成,先让 Agent 上线
  // 工具调用时检查连接状态,未就绪则返回"系统加载中,请稍后"
  externalConnections.ready = Promise.allSettled(Object.values(connectPromises))
    .then(() => { mark('stage3_complete'); });

  return {
    chatEngine,
    basicPrompt,
    allTools,
    externalConnections,
    getCheckpoints: () => Object.fromEntries(checkpoints),
  };
}

// 渠道差异化工具加载(运行时配置,不需要编译时 feature flag)
function loadChannelTools(channel: ChannelType): Tool[] {
  const toolMap: Record<ChannelType, string[]> = {
    online: ['sendCoupon', 'sendLink', 'richTextReply'],
    phone:  ['checkCallRecording', 'transferCall'],
    ticket: ['createTicket', 'updateTicket', 'assignTicket'],
  };
  return toolMap[channel].map(name => buildTool(toolDefs[name]));
}

启动遥测:定位"慢在哪里"

// 借鉴 CC 的 profileCheckpoint 模式
// 客服场景需要监控每个阶段的耗时,方便定位启动瓶颈
interface StartupReport {
  totalMs: number;
  stages: {
    stage1_dialogReady: number;   // 目标 < 50ms
    stage2_toolsReady: number;    // 目标 < 200ms
    stage3_externalReady: number; // 目标 < 2000ms(异步,不阻塞)
  };
  channel: ChannelType;
  timestamp: string;
}

// 每次启动上报到监控面板
// 告警规则:stage1 > 100ms 触发 P1 告警(客户等待)
// 告警规则:stage3 的某个系统连接 > 5s 触发 P2 告警(降级服务)

落地清单

必须抄的:
  • 渐进式加载:先让对话能力就绪(阶段 1),再加载工具(阶段 2),最后连外部系统(阶段 3)。客户感知到的等待时间只取决于阶段 1。
  • 启动分阶段遥测:每个阶段打时间戳,上报到监控。当 CRM 连接超时导致 stage3 变慢时,能立刻知道"是 CRM 慢了"而不是"Agent 整体慢了"。
  • 并行连接外部系统:CRM、订单、支付同时连,不要串行。借鉴 CC 的 Promise.all([loadMDM(), prefetchKeychain()])
  • 延迟加载非必要能力:SMS 通知、情感分析等只在特定渠道才需要,不加载。
不需要抄的:
  • Feature flag 编译时消除:CC 用 feature('XXX') 是为了同一份源码出内外两个构建。客服 Agent 不需要这个——渠道差异用运行时配置就够了,直接 if (channel === 'online') 更直观。
  • 零依赖快速路径:CC 有 --version 等十几个零加载路径,客服 Agent 只有一个入口(坐席签到),不需要这个模式。
  • 早期输入捕获:CC 在模块加载的 135ms 窗口捕获用户打字。客服 Agent 启动时客户还没接入,不需要这个。
常见坑:
  • 把所有外部系统连接放在阶段 1:CRM 连接超时 3s,坐席对着空白界面等 3 秒。正确做法是阶段 1 只做 UI 就绪(<50ms),外部系统全部异步连接。
  • 串行连接外部系统:先连 CRM → 再连订单 → 再连支付。如果每个 500ms,总计 1.5s。改为 Promise.allSettled() 并行连接,最慢的那个决定总时间。
  • 工具未就绪时客户消息丢失:阶段 2 还没加载完,客户发来"我要退货"。如果消息队列没做好,这条消息就丢了。正确做法是阶段 1 就建好消息队列,后续阶段消费。
  • 忽略 Promise.allSettledPromise.all 的区别:用 Promise.all 连接外部系统,一个超时全部失败。应该用 allSettled,哪个连不上就降级那个,不影响其他系统。

代码索引

文件行数说明
entrypoints/cli.tsx~300Bootstrap 入口,快速路径分发
main.tsx~4684主入口:安全预处理、Commander 注册、REPL 启动
entrypoints/init.ts~341全局初始化:配置、网络、遥测
setup.ts~478环境设置:权限验证、worktree、后台任务
bootstrap/state.ts~1500全局可变状态(sessionId、cwd、遥测等)
replLauncher.tsx~23REPL 启动:动态加载 App + REPL 组件
interactiveHelpers.tsx~366设置对话框序列、renderAndRun
dialogLaunchers.tsx~133对话框启动器(恢复、快照、teleport 等)
state/store.ts~35通用不可变状态容器
state/AppStateStore.ts~1200+AppState 类型定义与默认值工厂
state/AppState.tsx~150AppStateProvider(React Context + 设置变更监听)
state/onChangeAppState.ts~120状态变更副作用(权限模式同步、遥测)
state/selectors.ts~50派生状态选择器(纯函数)
coordinator/coordinatorMode.ts~370协调器模式(多 Agent 调度提示词)