启动链路
从 CLI 入口到 REPL 渲染的完整初始化序列,涵盖快速路径分发、配置加载、信任校验与状态初始化
职责概述
解决的问题:用户在终端输入 claude 后到出现交互界面,中间需要完成认证、环境检测、配置加载、权限初始化等一系列前置工作。启动链路将这些步骤编排为一条可靠的初始化管道,确保首次安装和日常使用都能快速进入交互状态。
应用场景:① 首次安装后的 OAuth 认证引导 ② 每次启动时的环境检测(Node 版本、终端类型、代理配置)③ 从 CI/CD、IDE 插件、SDK 等不同入口点进入时的差异化初始化 ④ 断线恢复和会话续接。
一句话理解:就像电脑的开机流程——从按下电源键到桌面出现,中间的 BIOS 自检、驱动加载、登录界面都在这一层。
架构设计
核心数据流
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()。
main.tsx:585 在进入 Commander 解析之前完成多项关键预处理:
- 安全设置:设置
NoDefaultCurrentDirectoryInExePath防止 Windows PATH 劫持 - 信号处理:注册 SIGINT 和 exit 处理器
- URL/协议处理:解析
cc://深链接、macOS URL scheme、claude assistant、claude ssh - 模式检测:判断交互/非交互模式,设置 clientType(cli/sdk/github-action 等)
// main.tsx:803-812
const isNonInteractive = hasPrintFlag || hasInitOnlyFlag || hasSdkUrl || !process.stdout.isTTY;
setIsInteractive(!isNonInteractive);
initializeEntrypoint(isNonInteractive);
eagerLoadSettings();
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 等
});
这确保只有真正执行命令时才触发初始化,显示帮助信息时不加载。
entrypoints/init.ts:57 使用 memoize 确保只执行一次,按序初始化:
enableConfigs()— 启用配置系统applySafeConfigEnvironmentVariables()— 仅应用安全环境变量(信任对话框之前)setupGracefulShutdown()— 注册退出清理configureGlobalMTLS()+configureGlobalAgents()— 网络层配置preconnectAnthropicApi()— 预热 TLS 连接- 远程管理设置加载、LSP 清理注册、scratchpad 初始化
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 从正确目录加载
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 — 自动模式同意
replLauncher.tsx:12 是最终启动点,通过动态 import 加载 App 和 REPL 组件:
// 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>);
}
renderAndRun(interactiveHelpers.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.tsx 的 main() 函数中,按照现有模式添加新的快速路径检测。所有 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 注入,支持响应式更新。
架构师决策指南
性能与启动时间的权衡
启动链路的设计围绕"最小化首次可交互时间"展开。关键决策:
- 动态 import vs 静态 import:启动路径上的模块全部使用动态 import,使 Bun 的 tree-shaking 能移除未使用的代码。代价是略微增加代码复杂度和丧失类型推断。
- 并行副作用 vs 串行:MDM 读取和 Keychain 预取在模块加载阶段并行启动,但它们的结果在后续阶段按需等待。这种"发射后等待"模式避免了阻塞主线程。
- preAction 钩子 vs 显式调用:Commander 的 preAction 确保初始化只在实际执行命令时运行,而非在显示帮助时。这是一个关键的延迟优化。
状态管理的分层
系统采用两层状态管理:
- Bootstrap State(
bootstrap/state.ts):全局可变状态,使用 signal 模式。适用于 React 之外的底层代码(如 API 层、工具执行)。 - AppState(
state/AppStateStore.ts):不可变状态容器,通过 React Context 注入。适用于 UI 组件和需要响应式更新的场景。
安全边界的扩展性
信任对话框是所有安全策略的锚点。添加新的安全检查时,应将其放入 showSetupScreens 中信任对话框之后的位置。环境变量分为"安全"和"完整"两套,任何可能被不受信任工作目录控制的环境变量都必须归类到"完整"集合中。
◈ 可视化处理拓扑图
启动链路是一条漏斗式管线:CLI 入口以零加载代价快速分流特殊路径,未匹配的请求依次经过安全预处理、Commander 注册、全局初始化、环境准备和交互设置,最终启动 REPL。每个快速路径在 cli.tsx 就地返回,只加载自身所需模块。
cli.tsx 就地返回(零模块加载),主路径通过 preAction 钩子确保 --help 不触发 setup,feature() 编译时消除未启用功能的代码,startDeferredPrefetches 将非首屏必需工作推迟到 REPL 渲染后。⇉ 核心处理流程详解
Claude Code 的启动过程采用"漏斗式"架构:先在轻量入口快速分流特殊路径,再逐步加载重量级模块。整个过程从进程启动到 REPL 渲染,经过了 8 个关键阶段,每一层都精心控制了模块加载范围以优化冷启动时间。
entrypoints/cli.tsx:33 的 main() 首先检查 --version 等零依赖标志(L37-41)。通过后依次检测 ~15 个快速路径:MCP 服务器模式(L72-93)、daemon worker(L100-106)、bridge 模式(L112-162)、后台会话管理(L185-209)、模板任务(L212-222)等。每个快速路径使用 feature() 编译时消除 + await import() 按需加载,确保不相关代码零开销。main.tsxcli.tsx:288-298 开始捕获早期输入(startCapturingEarlyInput),然后动态 import main.tsx 的 main()。这里的 profileCheckpoint 标记精确测量了 import 耗时(约 135ms),用于性能回归检测。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 预处理。main.tsx:884-967 的 run() 函数创建 Commander 程序实例(L902),注册 ~60 个 CLI 选项。关键设计是 preAction 钩子(L907-967):只在真正执行命令时才初始化,避免 --help 触发昂贵的 setup。钩子内依次完成:MDM/Keychain 异步加载(L914)、init() 全局初始化(L916)、日志 sink 连接(L934)、迁移执行(L950)和远程设置加载(L957-958)。main.tsx:1090-1252 解析 Commander 选项:提取 print/verbose/debug 模式标志,检测 Assistant/Kairos 模式(L1048-1088,需通过信任对话框 + GrowthBook 门控),处理 worktree/tmux/teammate 选项(L1147-1218),并自动设置 SDK URL 的流式格式(L1236-1252)。permissionSetup.ts:872-1033 的 initializeToolPermissionContext() 从 CLI 标志、settings.json 和磁盘规则构建初始权限上下文。Auto 模式需通过 verifyAutoModeGateAccess()(L1078-1260)的异步门控检查,包括 GrowthBook 功能开关、危险权限剥离和 circuit-breaker 状态。main.tsx 的 action handler 中调用 setup()(约在 L1700+ 区域),完成信任对话框展示、MCP 服务器连接、权限验证、worktree 创建和后台任务初始化。startDeferredPrefetches()(main.tsx:388-431)启动不需要在首帧渲染前完成的预取:系统上下文、git 状态、skills 发现。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 检查更安全:即使代码中引用了内部包名,它们也不会出现在外部构建中。
// 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; // 外部构建中此分支完全不存在
}
feature() 既是运行时函数又是编译时标记——编译器能识别 if (feature('X')) 模式并消除整个分支。2. 快速路径漏斗 — 零加载分派
cli.tsx 是整个系统最薄的一层。它通过字符串匹配 process.argv 快速分流 ~15 个子路径,每个子路径只 await import() 自己需要的模块。这意味着 claude --version 的开销几乎等于一次 console.log,而 claude daemon 完全不会加载 React、权限系统或工具定义。
// 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-967 的 preAction 钩子解决了"何时初始化"的经典问题:用户输入 claude --help 时不需要连接网络、加载设置或初始化遥测——只有真正执行命令(action handler 触发)时才做昂贵操作。这种设计将帮助文本的响应时间从数百毫秒降到数十毫秒。
// main.tsx:907-916
program.hook('preAction', async thisCommand => {
// 只在真正执行命令时才触发,--help 不会进入这里
await Promise.all([ensureMdmSettingsLoaded(), ensureKeychainPrefetchCompleted()]);
await init(); // 全局初始化(配置、网络、遥测)
// ...
void loadRemoteManagedSettings(); // 非阻塞,企业设置热加载
void loadPolicyLimits(); // 非阻塞,策略限制
});
void loadRemoteManagedSettings() 和 void loadPolicyLimits() 使用"发射即忘"模式——它们不阻塞 REPL 启动,结果通过热更新机制异步生效。这是"乐观启动"策略的体现:先用默认值启动,等远程配置到达后再更新。4. 信任门控的安全边界
在恶意仓库中,.claude/settings.json 和 .claude/agents/ 是攻击者可控的。系统在 main.tsx:1042-1048 附近实现了信任门控:checkHasTrustDialogAccepted() 必须在启用 assistant 模式、加载 agent 定义或执行 hooks 之前通过。这确保即使攻击者构造了恶意配置,也不会在用户明确信任工作目录前生效。
// main.tsx:1067-1069
if (!checkHasTrustDialogAccepted()) {
console.warn('Assistant mode disabled: directory is not trusted.');
} else {
kairosEnabled = assistantModule.isAssistantForced()
|| (await kairosGate.isKairosEnabled());
// ... 只有信任后才初始化团队和 agent
}
5. profileCheckpoint 性能遥测管线
启动路径中散布的 profileCheckpoint() 调用(cli.tsx:48,292,296、main.tsx:586,607,885,903,1007 等)构建了一个细粒度的启动时间线。每个 checkpoint 记录时间戳和标签,用于回归检测——如果某次提交导致 cli_before_main_import 到 cli_after_main_import 的时间增长超过阈值,CI 会自动报警。
// 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
◈ Agent 实践借鉴 — 客服 Agent 启动设计
场景映射:客服 Agent 启动时会遇到什么真实问题?
一个客服 Agent 上线时要面对的远不止"加载代码"这么简单。真实痛点是:
- 多系统集成:CRM(查客户画像)、订单系统(查订单详情)、支付系统(查退款状态)、知识库(查商品 FAQ)。这四个系统任何一个连接慢,客户都在等待。
- 渠道差异:电话客服坐席需要 ASR/TTS 通道,在线客服需要富文本消息能力,工单处理只需要后台 API。如果把所有渠道的初始化都跑一遍,50% 的工作是浪费的。
- 冷启动即客户等待:坐席签到后,客户电话就打进来了。如果 Agent 还在连接 CRM,客户听到的是沉默。
- 可选依赖:SMS 通知只在部分渠道开启,情感分析只在电话渠道需要。这些不应该拖慢其他渠道的启动。
借鉴 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.allSettled和Promise.all的区别:用Promise.all连接外部系统,一个超时全部失败。应该用allSettled,哪个连不上就降级那个,不影响其他系统。
代码索引
| 文件 | 行数 | 说明 |
|---|---|---|
entrypoints/cli.tsx | ~300 | Bootstrap 入口,快速路径分发 |
main.tsx | ~4684 | 主入口:安全预处理、Commander 注册、REPL 启动 |
entrypoints/init.ts | ~341 | 全局初始化:配置、网络、遥测 |
setup.ts | ~478 | 环境设置:权限验证、worktree、后台任务 |
bootstrap/state.ts | ~1500 | 全局可变状态(sessionId、cwd、遥测等) |
replLauncher.tsx | ~23 | REPL 启动:动态加载 App + REPL 组件 |
interactiveHelpers.tsx | ~366 | 设置对话框序列、renderAndRun |
dialogLaunchers.tsx | ~133 | 对话框启动器(恢复、快照、teleport 等) |
state/store.ts | ~35 | 通用不可变状态容器 |
state/AppStateStore.ts | ~1200+ | AppState 类型定义与默认值工厂 |
state/AppState.tsx | ~150 | AppStateProvider(React Context + 设置变更监听) |
state/onChangeAppState.ts | ~120 | 状态变更副作用(权限模式同步、遥测) |
state/selectors.ts | ~50 | 派生状态选择器(纯函数) |
coordinator/coordinatorMode.ts | ~370 | 协调器模式(多 Agent 调度提示词) |