597 lines
21 KiB
Markdown
597 lines
21 KiB
Markdown
|
|
# 第一阶段:启动流程详解
|
|||
|
|
|
|||
|
|
> 从 `bun run dev` 到用户看到交互界面的完整路径
|
|||
|
|
|
|||
|
|
## 启动链路总览
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
bun run dev
|
|||
|
|
→ package.json scripts.dev: "bun run src/entrypoints/cli.tsx"
|
|||
|
|
→ cli.tsx: polyfill 注入 + 快速路径检查
|
|||
|
|
→ import("../main.jsx") → cliMain()
|
|||
|
|
→ main.tsx: main() → run()
|
|||
|
|
→ Commander 参数解析 → preAction 钩子
|
|||
|
|
→ action handler: 服务初始化 → showSetupScreens
|
|||
|
|
→ launchRepl()
|
|||
|
|
→ replLauncher.tsx: <App><REPL /></App>
|
|||
|
|
→ REPL.tsx: 渲染交互界面,等待用户输入
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 1. cli.tsx(321 行)— 入口与快速路径分发
|
|||
|
|
|
|||
|
|
**文件路径**: `src/entrypoints/cli.tsx`
|
|||
|
|
|
|||
|
|
### 1.1 全局 Polyfill(第 1-53 行)
|
|||
|
|
|
|||
|
|
模块加载时立即执行的 side-effect,在 `main()` 之前运行。
|
|||
|
|
|
|||
|
|
#### feature() 桩函数(第 3 行)
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
const feature = (_name: string) => false;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
原版 Claude Code 构建时,Bun bundler 通过 `bun:bundle` 提供 `feature()` 函数,用于**编译时 feature flag**(类似 C 的 `#ifdef`)。反编译版没有构建流程,所以直接定义为永远返回 `false`。
|
|||
|
|
|
|||
|
|
**效果**:所有 Anthropic 内部功能分支全部禁用,包括:
|
|||
|
|
- `COORDINATOR_MODE` — 协调器模式
|
|||
|
|
- `KAIROS` — 助手模式
|
|||
|
|
- `DAEMON` — 后台守护进程
|
|||
|
|
- `BRIDGE_MODE` — 远程控制
|
|||
|
|
- `SSH_REMOTE` — SSH 远程
|
|||
|
|
- `BG_SESSIONS` — 后台会话
|
|||
|
|
- ... 等 20+ 个 flag
|
|||
|
|
|
|||
|
|
#### MACRO 全局对象(第 4-14 行)
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
globalThis.MACRO = {
|
|||
|
|
VERSION: "2.1.888",
|
|||
|
|
BUILD_TIME: new Date().toISOString(),
|
|||
|
|
FEEDBACK_CHANNEL: "",
|
|||
|
|
ISSUES_EXPLAINER: "",
|
|||
|
|
NATIVE_PACKAGE_URL: "",
|
|||
|
|
PACKAGE_URL: "",
|
|||
|
|
VERSION_CHANGELOG: "",
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
原版构建时 Bun 会把这些值内联到代码里。这里模拟注入,让后续代码读 `MACRO.VERSION` 时能拿到值。
|
|||
|
|
|
|||
|
|
#### 构建常量(第 16-18 行)
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
BUILD_TARGET = "external"; // 标记为"外部"构建(非 Anthropic 内部)
|
|||
|
|
BUILD_ENV = "production"; // 生产环境
|
|||
|
|
INTERFACE_TYPE = "stdio"; // 标准输入输出模式
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
这三个全局变量在代码各处被读取,用来区分运行环境。`"external"` 意味着很多 `("external" as string) === 'ant'` 的检查会返回 false。
|
|||
|
|
|
|||
|
|
#### 环境修补(第 22-33 行)
|
|||
|
|
|
|||
|
|
- 禁用 corepack 自动 pin(防止污染 package.json)
|
|||
|
|
- 远程模式下设置 Node.js 堆内存上限 8GB
|
|||
|
|
|
|||
|
|
#### ABLATION_BASELINE(第 40-53 行)
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
if (feature("ABLATION_BASELINE") && ...) { ... }
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`feature()` 返回 false,**永远不执行**。Anthropic 内部 A/B 测试代码。
|
|||
|
|
|
|||
|
|
### 1.2 main() 函数(第 60-317 行)
|
|||
|
|
|
|||
|
|
设计模式:**分层快速路径(fast path cascading)**——按开销从低到高逐级检查,命中即返回。
|
|||
|
|
|
|||
|
|
#### 快速路径列表
|
|||
|
|
|
|||
|
|
| 优先级 | 行号 | 检查条件 | 功能 | 开销 | 可执行 |
|
|||
|
|
|--------|------|---------|------|------|--------|
|
|||
|
|
| 1 | 64-72 | `--version` / `-v` | 打印版本号退出 | **零 import** | 是 |
|
|||
|
|
| 2 | 81-94 | `feature("DUMP_SYSTEM_PROMPT")` | 导出系统提示 | - | 否(flag) |
|
|||
|
|
| 3 | 95-99 | `--claude-in-chrome-mcp` | Chrome MCP 服务 | 动态 import | 是 |
|
|||
|
|
| 4 | 101-105 | `--chrome-native-host` | Chrome Native Host | 动态 import | 是 |
|
|||
|
|
| 5 | 108-116 | `feature("CHICAGO_MCP")` | Computer Use MCP | - | 否(flag) |
|
|||
|
|
| 6 | 123-127 | `feature("DAEMON")` | Daemon Worker | - | 否(flag) |
|
|||
|
|
| 7 | 133-178 | `feature("BRIDGE_MODE")` | 远程控制 | - | 否(flag) |
|
|||
|
|
| 8 | 181-190 | `feature("DAEMON")` | Daemon 主进程 | - | 否(flag) |
|
|||
|
|
| 9 | 195-225 | `feature("BG_SESSIONS")` | ps/logs/attach/kill | - | 否(flag) |
|
|||
|
|
| 10 | 228-240 | `feature("TEMPLATES")` | 模板任务 | - | 否(flag) |
|
|||
|
|
| 11 | 244-253 | `feature("BYOC_ENVIRONMENT_RUNNER")` | BYOC 运行器 | - | 否(flag) |
|
|||
|
|
| 12 | 258-264 | `feature("SELF_HOSTED_RUNNER")` | 自托管运行器 | - | 否(flag) |
|
|||
|
|
| 13 | 267-293 | `--tmux` + `--worktree` | tmux worktree | 动态 import | 是 |
|
|||
|
|
|
|||
|
|
#### 参数修正(第 296-307 行)
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
// --update/--upgrade → 重写为 update 子命令
|
|||
|
|
if (args[0] === "--update") process.argv = [..., "update"];
|
|||
|
|
// --bare → 设置简单模式环境变量
|
|||
|
|
if (args.includes("--bare")) process.env.CLAUDE_CODE_SIMPLE = "1";
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 最终出口(第 310-316 行)
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
const { startCapturingEarlyInput } = await import("../utils/earlyInput.js");
|
|||
|
|
startCapturingEarlyInput(); // 捕获用户提前输入的内容
|
|||
|
|
const { main: cliMain } = await import("../main.jsx");
|
|||
|
|
await cliMain(); // 进入 main.tsx 重型初始化
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
所有快速路径都没命中时(99% 的情况),才走到这里。
|
|||
|
|
|
|||
|
|
### 1.3 启动(第 320 行)
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
void main();
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`void` 表示不关心 Promise 返回值。
|
|||
|
|
|
|||
|
|
### 1.4 关键设计思想
|
|||
|
|
|
|||
|
|
- **快速路径**:`--version` 零开销返回,不加载任何模块
|
|||
|
|
- **动态 import**:`await import()` 替代静态 import,每条路径只加载自己需要的模块
|
|||
|
|
- **feature flag 过滤**:`feature()` 返回 false 使大量内部功能成为死代码
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 2. main.tsx(4683 行)— 重型初始化与 Commander CLI
|
|||
|
|
|
|||
|
|
**文件路径**: `src/main.tsx`
|
|||
|
|
|
|||
|
|
整个项目最大的单文件,但结构清晰:**辅助函数 → main() → run()**。
|
|||
|
|
|
|||
|
|
### 2.1 Import 区(第 1-215 行)
|
|||
|
|
|
|||
|
|
200+ 行 import,加载几乎所有子系统。关键的是前三个 **side-effect import**(import 即执行):
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
// 第 9 行:记录时间戳
|
|||
|
|
profileCheckpoint('main_tsx_entry');
|
|||
|
|
|
|||
|
|
// 第 16 行:启动 MDM 子进程读取(macOS plutil)
|
|||
|
|
startMdmRawRead();
|
|||
|
|
|
|||
|
|
// 第 20 行:启动 keychain 预读取(OAuth token、API key)
|
|||
|
|
startKeychainPrefetch();
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
这三个在 import 阶段就**并行启动子进程**,和后续 ~135ms 的模块加载同时进行——**用并行隐藏延迟**。
|
|||
|
|
|
|||
|
|
### 2.2 辅助函数(第 216-584 行)
|
|||
|
|
|
|||
|
|
| 函数 | 行号 | 作用 |
|
|||
|
|
|------|------|------|
|
|||
|
|
| `logManagedSettings()` | 216 | 记录企业托管设置到分析日志 |
|
|||
|
|
| `isBeingDebugged()` | 232 | 检测调试模式,**外部构建下直接 exit(1)**(第 266 行) |
|
|||
|
|
| `logSessionTelemetry()` | 279 | Session 遥测(技能、插件) |
|
|||
|
|
| `getCertEnvVarTelemetry()` | 291 | SSL 证书环境变量收集 |
|
|||
|
|
| `runMigrations()` | 326 | 数据迁移(模型重命名、设置格式升级等) |
|
|||
|
|
| `prefetchSystemContextIfSafe()` | 360 | 信任关系建立后安全预取系统上下文 |
|
|||
|
|
| `startDeferredPrefetches()` | 388 | REPL 首次渲染后的延迟预取 |
|
|||
|
|
| `eagerLoadSettings()` | 502 | 在 init() 之前提前加载 `--settings` 参数 |
|
|||
|
|
| `initializeEntrypoint()` | 517 | 根据运行模式设置 `CLAUDE_CODE_ENTRYPOINT` |
|
|||
|
|
|
|||
|
|
还有 `_pendingConnect`、`_pendingSSH`、`_pendingAssistantChat` 三个状态变量(第 542-583 行),用于暂存子命令参数。
|
|||
|
|
|
|||
|
|
### 2.3 main() 函数(第 585-856 行)
|
|||
|
|
|
|||
|
|
`main()` 本身不长,做完环境检测后调用 `run()`:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
main()
|
|||
|
|
├── 安全设置(NoDefaultCurrentDirectoryInExePath)
|
|||
|
|
├── 信号处理(SIGINT → exit, exit → 恢复光标)
|
|||
|
|
├── feature flag 保护的特殊路径(全部跳过)
|
|||
|
|
├── 检测 -p/--print / --init-only → 判断是否交互模式
|
|||
|
|
├── clientType 判断(cli / sdk-typescript / remote / github-action 等)
|
|||
|
|
├── eagerLoadSettings()
|
|||
|
|
└── await run() ← 进入真正的逻辑
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2.4 run() 函数(第 884-4683 行)
|
|||
|
|
|
|||
|
|
占 3800 行,是整个文件的核心。
|
|||
|
|
|
|||
|
|
#### Commander 初始化 + preAction 钩子(第 884-967 行)
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
const program = new CommanderCommand()
|
|||
|
|
.configureHelp(createSortedHelpConfig())
|
|||
|
|
.enablePositionalOptions();
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**preAction 钩子**(所有命令执行前都会运行):
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
preAction
|
|||
|
|
├── await ensureMdmSettingsLoaded() ← 等 MDM 子进程完成
|
|||
|
|
├── await ensureKeychainPrefetchCompleted() ← 等 keychain 预读完成
|
|||
|
|
├── await init() ← 一次性初始化
|
|||
|
|
├── initSinks() ← 分析日志接收器
|
|||
|
|
├── runMigrations() ← 数据迁移
|
|||
|
|
├── loadRemoteManagedSettings() ← 企业远程设置(非阻塞)
|
|||
|
|
└── loadPolicyLimits() ← 策略限制(非阻塞)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 主命令 Option 定义(第 968-1006 行)
|
|||
|
|
|
|||
|
|
定义了 40+ CLI 参数,关键的包括:
|
|||
|
|
|
|||
|
|
| 参数 | 作用 |
|
|||
|
|
|------|------|
|
|||
|
|
| `-p, --print` | 非交互模式,输出后退出 |
|
|||
|
|
| `--model <model>` | 指定模型(如 sonnet、opus) |
|
|||
|
|
| `--permission-mode <mode>` | 权限模式 |
|
|||
|
|
| `-c, --continue` | 继续最近对话 |
|
|||
|
|
| `-r, --resume` | 恢复指定对话 |
|
|||
|
|
| `--mcp-config` | MCP 服务器配置文件 |
|
|||
|
|
| `--allowedTools` | 允许的工具列表 |
|
|||
|
|
| `--system-prompt` | 自定义系统提示 |
|
|||
|
|
| `--dangerously-skip-permissions` | 跳过所有权限检查 |
|
|||
|
|
| `--output-format` | 输出格式(text/json/stream-json) |
|
|||
|
|
| `--effort <level>` | 推理努力级别(low/medium/high/max) |
|
|||
|
|
| `--bare` | 最小模式 |
|
|||
|
|
|
|||
|
|
#### action 处理器(第 1006-3808 行)
|
|||
|
|
|
|||
|
|
主命令的执行逻辑,内部按阶段和场景分支:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
action(async (prompt, options) => {
|
|||
|
|
│
|
|||
|
|
├── [1007-1600] 参数解析与预处理
|
|||
|
|
│ ├── --bare 模式
|
|||
|
|
│ ├── 解析 model / permission-mode / thinking / effort
|
|||
|
|
│ ├── 解析 MCP 配置、工具列表、系统提示
|
|||
|
|
│ └── 初始化工具权限上下文
|
|||
|
|
│
|
|||
|
|
├── [1600-2220] 服务初始化
|
|||
|
|
│ ├── MCP 客户端连接
|
|||
|
|
│ ├── 插件加载 + 技能初始化
|
|||
|
|
│ ├── 工具列表组装
|
|||
|
|
│ └── 初始 AppState 构建
|
|||
|
|
│
|
|||
|
|
├── [2220-2315] UI 初始化(交互模式)
|
|||
|
|
│ ├── createRoot() — 创建 Ink 渲染根节点
|
|||
|
|
│ ├── showSetupScreens() — 信任对话框、OAuth 登录、引导
|
|||
|
|
│ └── 登录后刷新各种服务
|
|||
|
|
│
|
|||
|
|
├── [2315-2582] 后续初始化
|
|||
|
|
│ ├── LSP 管理器、插件版本管理
|
|||
|
|
│ ├── session 注册、遥测日志
|
|||
|
|
│ └── 遥测上报
|
|||
|
|
│
|
|||
|
|
├── [2584-3050] --print 非交互模式分支
|
|||
|
|
│ ├── 构建 headless AppState + store
|
|||
|
|
│ └── 交给 print.ts 执行
|
|||
|
|
│
|
|||
|
|
└── [3050-3808] 交互模式:启动 REPL(7 个分支)
|
|||
|
|
├── --continue → 加载最近对话 → launchRepl()
|
|||
|
|
├── DIRECT_CONNECT → ❌ flag 关闭
|
|||
|
|
├── SSH_REMOTE → ❌ flag 关闭
|
|||
|
|
├── KAIROS assistant → ❌ flag 关闭
|
|||
|
|
├── --resume <id> → 恢复指定对话 → launchRepl()
|
|||
|
|
├── --resume 无 ID → 显示对话选择器
|
|||
|
|
└── 默认(无参数) → launchRepl() ★最常走的路径
|
|||
|
|
})
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 子命令注册(第 3808-4683 行)
|
|||
|
|
|
|||
|
|
| 子命令 | 行号 | 作用 |
|
|||
|
|
|--------|------|------|
|
|||
|
|
| `claude mcp` | 3892 | MCP 服务器管理(serve/add/remove/list/get) |
|
|||
|
|
| `claude server` | 3960 | Session 服务器(❌ flag 关闭) |
|
|||
|
|
| `claude auth` | 4098 | 认证管理(login/logout/status/token) |
|
|||
|
|
| `claude plugin` | 4148 | 插件管理(install/uninstall/list/update) |
|
|||
|
|
| `claude setup-token` | 4267 | 设置长期认证 token |
|
|||
|
|
| `claude agents` | 4278 | 列出已配置的 agents |
|
|||
|
|
| `claude doctor` | 4346 | 健康检查 |
|
|||
|
|
| `claude update` | 4362 | 检查更新 |
|
|||
|
|
| `claude install` | 4394 | 安装原生构建 |
|
|||
|
|
| `claude log` | 4411 | 查看对话日志(内部) |
|
|||
|
|
| `claude completion` | 4491 | Shell 自动补全 |
|
|||
|
|
|
|||
|
|
最后执行解析:
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
await program.parseAsync(process.argv);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2.5 main.tsx 学习建议
|
|||
|
|
|
|||
|
|
- **不要通读**。记住三段结构:辅助函数 → main() → run()
|
|||
|
|
- `feature()` 返回 false 的分支全部跳过,可忽略 50%+ 代码
|
|||
|
|
- `("external" as string) === 'ant'` 的分支也跳过(内部构建专用)
|
|||
|
|
- 需要深入某功能时,通过搜索定位对应代码段
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 3. replLauncher.tsx(22 行)— 胶水层
|
|||
|
|
|
|||
|
|
**文件路径**: `src/replLauncher.tsx`
|
|||
|
|
|
|||
|
|
极其简单,就做一件事:
|
|||
|
|
|
|||
|
|
```tsx
|
|||
|
|
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>);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- `App` — 全局 Provider(AppState、Stats、FpsMetrics)
|
|||
|
|
- `REPL` — 交互界面组件
|
|||
|
|
- `renderAndRun` — 把 React 元素渲染到 Ink 终端
|
|||
|
|
|
|||
|
|
动态 import 保持了按需加载的策略。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 4. REPL.tsx(5009 行)— 交互界面
|
|||
|
|
|
|||
|
|
**文件路径**: `src/screens/REPL.tsx`
|
|||
|
|
|
|||
|
|
项目第二大文件,是用户直接交互的界面。一个巨型 React 函数组件。
|
|||
|
|
|
|||
|
|
### 4.1 文件结构
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
REPL.tsx (5009 行)
|
|||
|
|
├── [1-310] Import 区(150+ import)
|
|||
|
|
├── [312-525] 辅助组件
|
|||
|
|
│ ├── median() — 数学工具函数
|
|||
|
|
│ ├── TranscriptModeFooter — 转录模式底栏
|
|||
|
|
│ ├── TranscriptSearchBar — 转录搜索栏
|
|||
|
|
│ └── AnimatedTerminalTitle — 终端标题动画
|
|||
|
|
├── [527-571] Props 类型定义
|
|||
|
|
└── [573-5009] REPL() 组件主体
|
|||
|
|
├── [600-900] 状态声明(50+ 个 useState/useRef/useAppState)
|
|||
|
|
├── [900-2750] 副作用与回调(useEffect/useCallback)
|
|||
|
|
├── [2750-2860] onQueryImpl — 核心:执行 API 查询
|
|||
|
|
├── [2860-3030] onQuery — 查询守卫与并发控制
|
|||
|
|
├── [3030-3145] 查询相关辅助回调
|
|||
|
|
├── [3146-3550] onSubmit — 用户提交处理
|
|||
|
|
├── [3550-4395] 更多副作用与状态管理
|
|||
|
|
└── [4396-5009] JSX 渲染
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4.2 Props
|
|||
|
|
|
|||
|
|
从 main.tsx 通过 launchRepl() 传入:
|
|||
|
|
|
|||
|
|
| Prop | 类型 | 含义 |
|
|||
|
|
|------|------|------|
|
|||
|
|
| `commands` | `Command[]` | 可用的斜杠命令 |
|
|||
|
|
| `debug` | `boolean` | 调试模式 |
|
|||
|
|
| `initialTools` | `Tool[]` | 初始工具集 |
|
|||
|
|
| `initialMessages` | `MessageType[]` | 初始消息(恢复对话时有值) |
|
|||
|
|
| `pendingHookMessages` | `Promise<...>` | 延迟加载的 hook 消息 |
|
|||
|
|
| `mcpClients` | `MCPServerConnection[]` | MCP 服务器连接 |
|
|||
|
|
| `systemPrompt` | `string` | 自定义系统提示 |
|
|||
|
|
| `appendSystemPrompt` | `string` | 追加系统提示 |
|
|||
|
|
| `onBeforeQuery` | `fn` | 查询前回调,返回 false 可阻止查询 |
|
|||
|
|
| `onTurnComplete` | `fn` | 轮次完成回调 |
|
|||
|
|
| `mainThreadAgentDefinition` | `AgentDefinition` | 主线程 Agent 定义 |
|
|||
|
|
| `thinkingConfig` | `ThinkingConfig` | 思考模式配置 |
|
|||
|
|
| `disabled` | `boolean` | 禁用输入 |
|
|||
|
|
|
|||
|
|
### 4.3 状态管理
|
|||
|
|
|
|||
|
|
分三层:
|
|||
|
|
|
|||
|
|
**全局 AppState(通过 useAppState 选择器读取):**
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
const toolPermissionContext = useAppState(s => s.toolPermissionContext);
|
|||
|
|
const verbose = useAppState(s => s.verbose);
|
|||
|
|
const mcp = useAppState(s => s.mcp);
|
|||
|
|
const plugins = useAppState(s => s.plugins);
|
|||
|
|
const agentDefinitions = useAppState(s => s.agentDefinitions);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**本地状态(useState):**
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
const [messages, setMessages] = useState(initialMessages ?? []);
|
|||
|
|
const [inputValue, setInputValue] = useState('');
|
|||
|
|
const [screen, setScreen] = useState<Screen>('prompt');
|
|||
|
|
const [streamingText, setStreamingText] = useState(null);
|
|||
|
|
const [streamingToolUses, setStreamingToolUses] = useState([]);
|
|||
|
|
// ... 50+ 个状态
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**关键 Ref:**
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
const queryGuard = useRef(new QueryGuard()).current; // 查询并发控制
|
|||
|
|
const messagesRef = useRef(messages); // 消息的同步引用(避免闭包问题)
|
|||
|
|
const abortController = ...; // 取消请求控制器
|
|||
|
|
const responseLengthRef = useRef(0); // 响应长度追踪
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4.4 核心数据流:用户输入 → API 调用
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
用户按回车
|
|||
|
|
│
|
|||
|
|
▼
|
|||
|
|
onSubmit (第 3146 行)
|
|||
|
|
├── 斜杠命令?→ immediate command 直接执行 或 handlePromptSubmit 路由
|
|||
|
|
├── 空输入?→ 忽略
|
|||
|
|
├── 空闲检测 → 可能弹出"是否开始新对话"对话框
|
|||
|
|
├── 加入历史记录
|
|||
|
|
│
|
|||
|
|
▼
|
|||
|
|
handlePromptSubmit (外部函数,src/utils/handlePromptSubmit.ts)
|
|||
|
|
├── 斜杠命令 → 路由到对应 Command handler
|
|||
|
|
├── 普通文本 → 构建 UserMessage,调用 onQuery()
|
|||
|
|
│
|
|||
|
|
▼
|
|||
|
|
onQuery (第 2860 行) — 并发守卫层
|
|||
|
|
├── queryGuard.tryStart() → 已有查询?排队等待
|
|||
|
|
├── setMessages([...old, ...newMessages]) — 追加用户消息
|
|||
|
|
├── onQueryImpl()
|
|||
|
|
│
|
|||
|
|
▼
|
|||
|
|
onQueryImpl (第 2750 行) — 真正执行 API 调用
|
|||
|
|
│
|
|||
|
|
├── 1. 并行加载上下文:
|
|||
|
|
│ await Promise.all([
|
|||
|
|
│ getSystemPrompt(), // 构建系统提示
|
|||
|
|
│ getUserContext(), // 用户上下文
|
|||
|
|
│ getSystemContext(), // 系统上下文(git、平台等)
|
|||
|
|
│ ])
|
|||
|
|
│
|
|||
|
|
├── 2. buildEffectiveSystemPrompt() — 合成最终系统提示
|
|||
|
|
│
|
|||
|
|
├── 3. for await (const event of query({...})) ★核心★
|
|||
|
|
│ │ 调用 src/query.ts 的 query() AsyncGenerator
|
|||
|
|
│ │ 流式产出事件
|
|||
|
|
│ │
|
|||
|
|
│ └── onQueryEvent(event) — 处理每个流式事件
|
|||
|
|
│ ├── 更新 streamingText(打字机效果)
|
|||
|
|
│ ├── 更新 messages(工具调用结果)
|
|||
|
|
│ └── 更新 inProgressToolUseIDs
|
|||
|
|
│
|
|||
|
|
└── 4. 收尾:resetLoadingState()、onTurnComplete()
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**核心代码(第 2797-2807 行)**:
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
for await (const event of query({
|
|||
|
|
messages: messagesIncludingNewMessages,
|
|||
|
|
systemPrompt,
|
|||
|
|
userContext,
|
|||
|
|
systemContext,
|
|||
|
|
canUseTool,
|
|||
|
|
toolUseContext,
|
|||
|
|
querySource: getQuerySourceForREPL()
|
|||
|
|
})) {
|
|||
|
|
onQueryEvent(event);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`query()` 来自 `src/query.ts`,是第二阶段要学的核心函数。
|
|||
|
|
|
|||
|
|
### 4.5 QueryGuard 并发控制
|
|||
|
|
|
|||
|
|
防止同时发起多个 API 请求的状态机:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
idle ──tryStart()──▶ running ──end()──▶ idle
|
|||
|
|
│
|
|||
|
|
└── tryStart() 返回 null(已在运行)
|
|||
|
|
→ 新消息排入队列
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- `tryStart()` — 原子操作,检查并转换 idle→running,返回 generation 号
|
|||
|
|
- `end(generation)` — 检查 generation 匹配后转换 running→idle
|
|||
|
|
- 防止 cancel+resubmit 竞态条件
|
|||
|
|
|
|||
|
|
### 4.6 JSX 渲染
|
|||
|
|
|
|||
|
|
两个互斥的渲染分支:
|
|||
|
|
|
|||
|
|
#### Transcript 模式(第 4396-4493 行)
|
|||
|
|
|
|||
|
|
按 `v` 键切换,只读浏览对话历史,支持搜索:
|
|||
|
|
|
|||
|
|
```tsx
|
|||
|
|
<KeybindingSetup>
|
|||
|
|
<AnimatedTerminalTitle />
|
|||
|
|
<GlobalKeybindingHandlers />
|
|||
|
|
<ScrollKeybindingHandler />
|
|||
|
|
<CancelRequestHandler />
|
|||
|
|
<FullscreenLayout
|
|||
|
|
scrollable={<Messages />}
|
|||
|
|
bottom={<TranscriptSearchBar /> 或 <TranscriptModeFooter />}
|
|||
|
|
/>
|
|||
|
|
</KeybindingSetup>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Prompt 模式(第 4552-5009 行)
|
|||
|
|
|
|||
|
|
主交互界面,从上到下:
|
|||
|
|
|
|||
|
|
```tsx
|
|||
|
|
<KeybindingSetup>
|
|||
|
|
<AnimatedTerminalTitle /> // 终端 tab 标题
|
|||
|
|
<GlobalKeybindingHandlers /> // 全局快捷键
|
|||
|
|
<CommandKeybindingHandlers /> // 命令快捷键
|
|||
|
|
<ScrollKeybindingHandler /> // 滚动快捷键
|
|||
|
|
<CancelRequestHandler /> // Ctrl+C 取消
|
|||
|
|
<MCPConnectionManager> // MCP 连接管理
|
|||
|
|
<FullscreenLayout
|
|||
|
|
overlay={<PermissionRequest />} // 权限审批覆盖层
|
|||
|
|
scrollable={ // 可滚动区域
|
|||
|
|
<>
|
|||
|
|
<Messages /> // ★ 对话消息渲染
|
|||
|
|
<UserTextMessage /> // 用户输入占位
|
|||
|
|
{toolJSX} // 工具 UI
|
|||
|
|
<SpinnerWithVerb /> // 加载动画
|
|||
|
|
</>
|
|||
|
|
}
|
|||
|
|
bottom={ // 固定底部
|
|||
|
|
<>
|
|||
|
|
{/* 各种对话框 */}
|
|||
|
|
<SandboxPermissionRequest />
|
|||
|
|
<PromptDialog />
|
|||
|
|
<ElicitationDialog />
|
|||
|
|
<CostThresholdDialog />
|
|||
|
|
<FeedbackSurvey />
|
|||
|
|
|
|||
|
|
{/* ★ 用户输入框 */}
|
|||
|
|
<PromptInput
|
|||
|
|
onSubmit={onSubmit}
|
|||
|
|
commands={commands}
|
|||
|
|
isLoading={isLoading}
|
|||
|
|
messages={messages}
|
|||
|
|
// ... 20+ props
|
|||
|
|
/>
|
|||
|
|
</>
|
|||
|
|
}
|
|||
|
|
/>
|
|||
|
|
</MCPConnectionManager>
|
|||
|
|
</KeybindingSetup>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4.7 REPL.tsx 学习建议
|
|||
|
|
|
|||
|
|
- 核心只有一条线:`onSubmit → onQuery → query() → onQueryEvent → 更新消息`
|
|||
|
|
- 其余 4000+ 行是 UI 细节:快捷键、对话框、动画、边界情况处理
|
|||
|
|
- `feature('...')` 保护的 JSX 全部跳过
|
|||
|
|
- `("external" as string) === 'ant'` 的分支也跳过
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 关键设计模式总结
|
|||
|
|
|
|||
|
|
| 模式 | 位置 | 说明 |
|
|||
|
|
|------|------|------|
|
|||
|
|
| 快速路径 | cli.tsx | 按开销从低到高逐级检查,零开销处理简单请求 |
|
|||
|
|
| 动态 import | cli.tsx / main.tsx | `await import()` 延迟加载,每条路径只加载需要的模块 |
|
|||
|
|
| Side-effect import | main.tsx 顶部 | import 阶段就并行启动子进程,用并行隐藏延迟 |
|
|||
|
|
| feature flag | 全局 | `feature()` 永远返回 false,编译时消除死代码 |
|
|||
|
|
| preAction 钩子 | main.tsx run() | Commander.js 命令执行前统一初始化 |
|
|||
|
|
| QueryGuard | REPL.tsx | 状态机防止并发 API 请求,带 generation 计数防竞态 |
|
|||
|
|
| React/Ink | UI 层 | 用 React 组件模型渲染终端 UI,支持全屏和虚拟滚动 |
|
|||
|
|
|
|||
|
|
## 需要忽略的代码模式
|
|||
|
|
|
|||
|
|
| 模式 | 来源 | 说明 |
|
|||
|
|
|------|------|------|
|
|||
|
|
| `_c(N)` 调用 | React Compiler | 反编译产生的 memoization 样板代码 |
|
|||
|
|
| `feature('FLAG')` 后面的代码 | Bun bundler | 全部是死代码,在当前版本不会执行 |
|
|||
|
|
| `("external" as string) === 'ant'` | 构建目标检查 | 永远为 false(external !== ant) |
|
|||
|
|
| tsc 类型错误 | 反编译 | `unknown`/`never`/`{}` 类型,不影响 Bun 运行 |
|
|||
|
|
| `packages/@ant/` | stub 包 | 空实现,仅满足 import 依赖 |
|