# Claude Code 源码学习手册

# Claude Code 源码学习手册

> 源码版本：Claude Code v2.1.88（~51万行 TypeScript）
> 分析日期：2026-03-31
>
> 基于 Claude Code 2.1.88 源码（~51万行 TypeScript）的架构导读手册
> 目标读者：**有基础编程概念（知道什么是变量、函数、循环），但不熟悉 TypeScript**，想通过理解 Claude Code 源码来深入掌握 AI Agent 开发的人

---

## 你不需要精通 TypeScript，就能读懂这本书

你可能已经写过 Python、Java、Go 或者其他语言的代码，对变量、函数、循环这些基本概念不陌生。但 TypeScript 对你来说比较陌生，看着一堆类型声明和泛型有点发懵。你想通过 Claude Code 的源码搞明白一个完整的 AI Agent 是怎么搭起来的。

这本书就像一个资深工程师坐在你旁边，手把手带你走进 Claude Code 的代码库。不是甩给你一堆代码让你自己看，而是**每一行关键代码旁边都有中文注释，告诉你这行在干什么、为什么这么写**。

就算你从没写过 TypeScript——没关系。**第零章会把这些语言特有的概念过一遍**，重点讲类型系统、async/await、装饰器这些 TypeScript 特色，用的全是 Claude Code 里的真实代码做例子。学完那一章，你再往下看就不会迷路。

**每章的结构都是：**

1. **这解决什么问题** — 不用术语，一段话讲清楚
2. **架构图** — ASCII 或表格，让你一眼看到模块之间怎么配合
3. **代码走读** — 摘录真实代码，每行旁边都有注释，解释"在干什么"和"为什么这么写"
4. **和其他模块的关系** — 这块代码依赖谁、被谁依赖
5. **设计评价** — 好在哪，有什么可以改进
6. **开发范式提炼** — 你能直接抄走的写代码规范
7. **学习路径** — 如果你想深入这块技术，该看什么书、上什么课、怎么做练习

读的时候，建议打开 Claude Code 源码目录跟着看。代码路径以 `` 为根。看不懂某段代码？没关系，先跳过，看注释就够了。

**你可能会问：我真的一点代码基础都没有，也能看懂吗？**

能。这本书的每一行注释都假设你不懂技术。遇到专业术语第一次出现时会解释。如果你发现某个地方没解释清楚，说明我写得不够好，你可以跳过那个细节——不影响你理解整体架构。

---

# 第零章：编程基础速成

> 这一章是给"零基础读者"的。如果你已经会写代码，直接跳到第一章。  
> 所有例子都来自 Claude Code 的真实代码。

## 0.1 什么是 TypeScript

TypeScript 是 JavaScript 的"加强版"。JavaScript 是网页和很多应用背后的编程语言，但它有个毛病：变量的类型是自由的，你把一个数字放进变量，后面可能不小心又塞了文字进去，程序就出 bug 了。

TypeScript 在 JavaScript 基础上加了**类型检查**——你可以告诉编译器"这个变量只能放数字"，如果有人往里塞了文字，编译器会在你运行之前就报错。

**类比：** 想象你开了一家餐厅。JavaScript 就像不做分类的厨房——食材随便放，鱼和水果可能混在一起。TypeScript 像给每个容器贴了标签："这是鱼缸，只能放鱼"、"这是果篮，只能放水果"。贴了标签之后，放错了东西一眼就能看出来。

Claude Code 整个项目都是用 TypeScript 写的，有 51 万行。你在这本书里看到的每一段代码都是 TypeScript。

```typescript
// 这是一个 TypeScript 代码片段
// "string" 表示这个变量只能存文字（不能存数字）
const name: string = "Claude"

// "number" 表示这个变量只能存数字（不能存文字）
const version: number = 2.1

// 如果你写 name = 123，TypeScript 编译器会报错
// 因为 123 是数字，而 name 被规定只能存文字
```

**你可能会问：TypeScript 和 JavaScript 有什么区别？我需要学哪个？**

TypeScript 是 JavaScript 的超集——所有 JavaScript 代码在 TypeScript 里都能跑。TypeScript 多了类型检查，写起来更安全。学 TypeScript 就行，等于同时学了 JavaScript。

## 0.2 什么是 import

`import` 是"从别的文件里拿东西来用"的意思。一个大项目不可能把所有代码写在一个文件里——文件会太大，没法维护。所以代码会被拆成很多文件，每个文件负责一小块功能。`import` 就是从其他文件里把需要的东西拿过来。

**类比：** 你在厨房做饭。`import` 就像从冰箱里拿食材。你不用自己种菜，冰箱里已经有现成的。你只需要 `import { 鸡蛋 } from '冰箱'`，然后用它做菜就行。

```typescript
// 这行代码从 'commander' 这个"冰箱"里拿了 Commander 这个"食材"
// 'commander' 是一个外部依赖包（别人写好的工具库）
import { Command as CommanderCommand } from 'commander'

// 这行从项目内部的 './tools.js' 文件里拿了 getTools 这个"功能"
// './' 开头 = 项目内部文件（不是外部依赖）
import { getTools } from './tools.js'

// 为什么有的用引号包着路径，有的用 @ 开头？
// 引号里的路径 = 文件位置（相对路径或绝对路径）
// 没有路径的 = 外部依赖（需要先安装才能用，就像食材要先买回家）
```

**你可能会问：`import` 和"从网上下载"有什么区别？**

`import` 是在代码运行时，从已经装好的包或项目文件里拿东西。不是从网上下载——那些包在项目初始化时（`npm install`）就已经下载好了。

## 0.3 什么是函数

函数就是"一段可以反复使用的代码块"。你把一组操作打包成一个函数，给它起个名字，以后需要的时候直接叫这个名字就行。

**类比：** 函数就像菜谱。菜谱上写着"番茄炒蛋"的做法：打蛋、切番茄、热油、翻炒。你不用每次做菜都从头想步骤，只要看菜谱（调用函数）就行。

```typescript
// 这是一个函数：名叫 main，作用是启动 Claude Code
// "async" 表示这个函数里有异步操作（下一节解释）
// "Promise<void>" 表示这个函数执行完不返回值（只是做事）
async function main(): Promise<void> {
  // 1. 先拿到用户在命令行输入的参数
  // process.argv = 用户启动程序时输入的所有参数
  // slice(2) = 跳过前两个（node 路径和脚本路径），只留用户真正输入的
  const args = process.argv.slice(2)

  // 2. 如果用户输入了 --version，打印版本号然后结束
  if (args[0] === '--version') {
    console.log('2.1.88')  // 把版本号打印到屏幕上
    return  // "return" = 函数结束，不再往下执行
  }

  // 3. 如果不是 --version，那就启动完整的 Claude Code
  // "await import(...)" = 动态加载（用到的时候才加载，不用的不加载，省时间）
  const { main: cliMain } = await import('../main.js')
  await cliMain()  // 调用真正的主函数
}

// 调用这个函数 = 运行上面这段代码
main()
```

**关键概念：**
- `function` = 声明一个函数（写菜谱）
- `main()` = 调用这个函数（按菜谱做菜）
- `return` = 函数结束，返回结果（菜做好了，端出去）
- 参数（`args`）= 函数的输入（菜谱上写的"需要3个番茄"）

## 0.4 什么是异步（async/await）

同步代码是"一件做完再做下一件"。异步代码是"发出去一个任务，不等它完成，先做别的事，等任务完成了再回来处理结果"。

**类比：** 你在厨房做饭。同步方式：烧水，站在锅旁边等水开（啥也不干），水开了再切菜。异步方式：烧上水，趁等水开的时候去切菜，水开了（它会叫你）再去处理。异步就是"同时干多件事"的能力。

```typescript
// "async" 放在 function 前面，表示这个函数里有异步操作
async function loadData() {
  // "await" = 等待这个异步操作完成
  // fetch = 向某个网址发送请求（就像打开浏览器访问网页）
  // 在等的期间，程序可以去做别的事（比如更新界面）
  const response = await fetch('https://api.anthropic.com/v1/messages')

  // 只有当 fetch 完成后，才会执行下面这行
  // .json() = 把服务器返回的数据转换成 JavaScript 能用的对象
  const data = await response.json()

  return data  // 把数据返回给调用者
}
```

**在 Claude Code 里的典型用法：**

```typescript
// query.ts 的核心循环——这就是 Claude Code 的"心脏"
// "async function*" 表示这是一个"异步生成器"（可以一边产生结果一边返回）
async function* query(params) {
  // 1. 发送 API 请求给 Anthropic（异步，可能要等几秒才有响应）
  // 在等响应期间，程序不会卡住——UI 还能正常显示
  const streamResult = yield* deps.makeApiStream(...)

  // 2. 收到模型响应后，执行工具调用（也可能要等，比如执行 shell 命令）
  const toolResults = yield* runTools(toolUseBlocks, ...)

  // 3. 把结果加入消息列表，继续下一轮循环
  messages.push(...toolResults)
}
```

**你可能会问：为什么异步代码这么重要？**

因为 AI Agent 需要频繁调用 API（发消息给模型、执行工具），每次调用都要等网络响应。如果用同步方式，程序会卡住什么都不干。异步让程序在等 API 响应时还能处理其他事情（比如更新 UI、处理用户输入）。

## 0.5 什么是类型（Types）

类型就是"给数据分类"。TypeScript 里有很多种类型，每种类型代表一种"数据形状"。

**类比：** 餐厅菜单上的分类。"热菜"是一种类型——里面的菜都是热的，有名字、价格。"凉菜"是另一种类型——也是菜，但特点不同。类型系统就是在说："这个数据必须长这样，不能长那样。"

```typescript
// 基本类型——最简单的数据分类
const name: string = "Claude"       // string = 文字（一串字符）
const version: number = 2.1         // number = 数字
const isActive: boolean = true      // boolean = 真/假（是/否）

// 对象类型——把多个字段组合在一起，描述一个"东西"
type User = {
  name: string        // 必须有名字（文字类型）
  age: number         // 必须有年龄（数字类型）
  email?: string      // 可以有邮箱，也可以没有（? = 可选字段）
}

// 联合类型——值只能是其中一种
type Permission = 'allow' | 'deny' | 'ask'
// 意思是：Permission 类型的值只能是 'allow'、'deny' 或 'ask' 三选一
// 如果你写了 Permission x = '随便'，TypeScript 编译器会报错
```

**Claude Code 里的真实例子：**

```typescript
// PermissionResult 就是一个联合类型
// "权限结果"只可能是四种之一：允许、询问、拒绝、透传
// "discriminated union" = 用一个公共字段（behavior）来区分不同类型
type PermissionResult =
  | { behavior: 'allow'; updatedInput?: Input }     // 允许，并可能修改输入内容
  | { behavior: 'ask'; message: string }            // 询问用户的意见
  | { behavior: 'deny'; message: string }           // 拒绝，并告诉原因
  | { behavior: 'passthrough'; message: string }    // 透传给下一层检查

// 为什么这么设计？因为写代码的人必须处理所有四种情况
// TypeScript 编译器会检查：你有没有处理 'allow'？有没有处理 'deny'？
// 漏了一个就报错——这就防止了"忘记处理某种情况"的 bug
```

## 0.6 什么是 React

React 是一个用来构建用户界面的库。它用"组件"的方式组织界面——每个组件负责一小块 UI，组件可以嵌套组合。

**类比：** 拼乐高。每块乐高积木是一个"组件"——有的负责当墙壁，有的负责当窗户。你可以把小积木拼成大积木（组件嵌套组件），最后拼出一栋房子（完整界面）。

```typescript
// 这是一个 React 组件：名叫 ToolProgress
// 它负责渲染"工具执行中"的进度信息
// { messages: string[] } = 这个组件接收一个参数：消息列表（文字数组）
function ToolProgress({ messages }: { messages: string[] }) {
  // return 后面是这个组件"长什么样"（渲染什么内容）
  return (
    <div>
      {/* messages.map = 遍历每条消息，把它渲染成一个 <p> 标签 */}
      {/* key={msg} = React 需要每个列表项有个唯一标识，用来高效更新 */}
      {messages.map(msg => (
        <p key={msg}>{msg}</p>
      ))}
    </div>
  )
}

// 在别的地方使用这个组件，就像用 HTML 标签一样：
// <ToolProgress messages={["读取文件...", "分析代码..."]} />
```

**Claude Code 为什么用 React？**

因为 Claude Code 用一个叫 Ink 的库，把 React 渲染到了终端（命令行界面）里。也就是说，你在终端里看到的那些花花绿绿的输出，其实是 React 组件在渲染。React 不只是做网页的——它是一种组织界面代码的方式。

## 0.7 什么是 API

API 就是"应用程序之间的接口"。你的程序想从另一个服务拿数据或让它做事，就需要通过 API 发请求。

**类比：** 点外卖。你（你的程序）通过外卖 App（API）告诉餐厅（另一个服务）"我要一份宫保鸡丁"。餐厅做好后通过外卖 App 把菜送回来。你不需要知道餐厅的厨房长什么样、厨师怎么炒菜——你只需要知道"通过这个接口，我能点餐、能拿到菜"。

```typescript
// Claude Code 发送 API 请求给 Anthropic（就像你在外卖 App 下单）
const response = await fetch('https://api.anthropic.com/v1/messages', {
  method: 'POST',                             // POST = 发送数据（不是获取数据）
  headers: {                                  // 请求头 = "附带的证件和说明"
    'Content-Type': 'application/json',       // 告诉服务器："我发的数据是 JSON 格式"
    'x-api-key': apiKey,                      // API 密钥 = 你的"外卖会员卡号"
  },
  body: JSON.stringify({                      // body = 请求的具体内容
    model: 'claude-sonnet-4-20250514',        // 用哪个模型（选哪个"厨师"）
    messages: [{ role: 'user', content: '你好' }],  // 你说什么话
    tools: [...],                             // 告诉模型有哪些工具可以用
  }),
})

// response 就是"外卖送回来的菜"——模型的回复
const data = await response.json()
// data 里面包含了模型说了什么、用了什么工具等信息
```

**在 Claude Code 里的 API 调用流程：**

```
你的输入（"帮我读一下 main.ts"）
    ↓
Claude Code 把你的请求 + 工具列表打包成 API 请求（就像下单）
    ↓
发送给 Anthropic API（就像提交订单）
    ↓
模型思考，决定用 FileReadTool（厨师决定用什么做法）
    ↓
API 返回响应（"我要调用 FileReadTool"）（厨师说"我需要你先帮我拿食材"）
    ↓
Claude Code 执行 FileReadTool，读取文件（你帮厨师拿食材）
    ↓
把文件内容再发给 API（把食材给厨师）
    ↓
模型分析文件内容，给你回复（厨师做好菜，外卖送到你手上）
```

---

## 第零章小结

到这里你已经知道了：
- **TypeScript** = 带类型检查的编程语言（给容器贴标签的厨房）
- **import** = 从别的文件拿东西来用（从冰箱拿食材）
- **函数** = 一段可以反复使用的代码块（菜谱）
- **异步** = 同时干多件事的能力（烧水时去切菜）
- **类型** = 给数据分类（菜单上的热菜/凉菜）
- **React** = 用组件拼界面（拼乐高）
- **API** = 程序之间的接口（点外卖）

这些概念在后面的每一章都会反复出现。看不懂某一段代码的时候，回来翻这一章就行。

**学习路径：如果你想系统学编程基础**

1. **JavaScript 入门**：[JavaScript.info](https://javascript.info/)（免费，中文版也有），从头到尾过一遍，大概需要 2 周
2. **TypeScript 入门**：[TypeScript 官方手册](https://www.typescriptlang.org/docs/handbook/)，重点看"基础类型"和"接口"两章
3. **React 入门**：[React 官方教程](https://react.dev/learn)，做一遍井字棋教程就行
4. **动手练习**：用 Claude Code 写一个小工具——比如一个读取文件并统计字数的 CLI 工具。让它边写边给你解释代码，这就是最好的学习方式

**练习建议：** 打开 Claude Code，输入 `/init` 创建一个新项目，然后让它写一个 "Hello World" 程序。逐行让它解释代码在干什么。这就是 vibe coding 的开始。

---

# 第一章：项目整体结构

## 1.1 目录结构总览

Claude Code 的代码库有 46 个一级目录，50+ 个顶层文件。不是按"技术层"分的，而是按"业务能力"分的。

```

├── entrypoints/       # 入口：CLI、SDK、MCP 各种启动路径
├── main.tsx           # 主入口：参数解析、初始化、启动 REPL
├── tools.ts           # 工具注册中心：getTools() 汇集 40+ 个工具
├── Tool.ts            # 工具接口定义：Tool、ToolUseContext、buildTool()
├── query.ts           # 核心查询循环：异步生成器驱动 Agent 的主循环
├── QueryEngine.ts     # 查询引擎封装
├── tools/             # 40+ 个工具，每个工具一个目录
│   ├── BashTool/      # Shell 命令执行（最复杂的工具）
│   ├── FileReadTool/  # 文件读取
│   ├── AgentTool/     # 子 Agent（核心，233K）
│   ├── MCPTool/       # MCP 协议工具
│   └── ...            # 其他工具
├── state/             # 全局状态管理
│   ├── store.ts       # 自研 Store（createStore）
│   ├── AppStateStore.ts # AppState 类型定义
│   └── selectors.ts   # 状态选择器
├── types/             # 核心类型定义
│   ├── permissions.ts # 权限类型
│   ├── message.ts     # 消息类型
│   └── hooks.ts       # 钩子类型
├── screens/           # 屏幕级组件（REPL、Doctor）
├── components/        # UI 组件库
├── ink/               # 终端渲染层（React + Ink 的封装）
├── services/          # 后端服务
│   ├── mcp/           # MCP 客户端
│   ├── compact/       # 上下文压缩
│   ├── api/           # API 调用
│   ├── analytics/     # 埋点
│   └── plugins/       # 插件管理
├── bridge/            # 远程控制（Bridge）：WebSocket + 轮询
├── context.ts         # 系统上下文（git 信息等）
├── plugins/           # 插件系统
├── skills/            # Skill 系统
├── utils/             # 工具函数（最大目录，几十个文件）
├── commands/          # /斜杠命令
├── hooks/             # React hooks
├── coordinator/       # 协调器模式（多 Agent 协作）
├── server/            # HTTP/WebSocket 服务端
├── remote/            # 远程会话管理
├── schemas/           # Zod schema
├── bootstrap/         # 启动状态
├── migrations/        # 配置迁移
├── constants/         # 常量
├── cli/               # CLI 子命令处理
├── vim/               # Vim 集成
├── voice/             # 语音功能
├── tasks/             # 任务系统
└── ...其他
```

**目录职责：**

| 目录 | 职责 |
|------|------|
| `entrypoints/` | 各种启动入口（CLI、SDK、MCP） |
| `tools/` | 40+ 个工具的实现，每个工具独立目录 |
| `state/` | 全局状态管理，自研轻量 Store |
| `types/` | 核心类型定义，跨模块共享 |
| `services/` | 后端服务层（MCP、压缩、分析等） |
| `bridge/` | 远程控制 Bridge（WebSocket 轮询） |
| `ink/` | 终端 UI 渲染层 |
| `components/` | UI 组件库 |
| `screens/` | 屏幕级组件 |
| `plugins/` | 插件加载和管理 |
| `skills/` | Skill 系统（斜杠命令） |
| `utils/` | 通用工具函数 |
| `commands/` | /斜杠命令注册 |
| `coordinator/` | 多 Agent 协调器 |
| `server/` | HTTP/WS 服务端 |
| `remote/` | 远程会话管理 |
| `bootstrap/` | 启动状态管理 |
| `migrations/` | 配置迁移逻辑 |

## 1.2 文件组织规范

### 工具目录的标准结构

每个工具都是一个独立目录，内部遵循一致的文件命名约定。以 `BashTool/` 为例：

```
tools/BashTool/
├── BashTool.tsx         # 主实现：Tool 接口的 call、checkPermissions 等
├── bashPermissions.ts   # 权限检查逻辑
├── bashSecurity.ts      # 安全分析（AST 解析、命令分类）
├── prompt.ts            # 工具描述（给模型看的 prompt）
├── UI.tsx               # 终端渲染（React 组件）
├── toolName.ts          # 工具名称常量
├── utils.ts             # 工具专用工具函数
├── commandSemantics.ts  # 命令语义分析
├── sedEditParser.ts     # sed 编辑解析
└── ...其他辅助文件
```

**每个文件的职责非常明确：**

| 文件 | 职责 | 谁读它 |
|------|------|--------|
| `prompt.ts` | 工具的文本描述，定义给模型看的 prompt | 系统提示词组装 |
| `BashTool.tsx` | 工具的核心实现：call、validate、checkPermissions | query.ts 调用 |
| `UI.tsx` | 终端渲染：renderToolUseMessage、renderToolResultMessage | REPL 渲染 |
| `toolName.ts` | 工具名常量（如 `BASH_TOOL_NAME = 'Bash'`） | 避免循环依赖 |
| `bashPermissions.ts` | 权限检查：哪些命令允许、哪些拒绝 | BashTool.tsx |
| `bashSecurity.ts` | 安全分析：AST 解析、命令分类 | bashPermissions.ts |
| `utils.ts` | 工具内部的辅助函数 | BashTool.tsx |

**为什么这么组织？三个好处：**

1. **关注点分离**：权限逻辑和工具逻辑分开，改权限不影响工具，改安全分析不影响 UI
2. **文件大小可控**：BashTool 相关代码可能有 3000+ 行，拆成 10+ 个文件后每个文件几百行
3. **循环依赖管理**：`toolName.ts` 只导出常量字符串，避免了 A → B → A 的 import 循环

### FileReadTool 的结构（简单工具）

```
tools/FileReadTool/
├── FileReadTool.ts    # 主实现
├── prompt.ts          # 工具描述
├── UI.tsx             # 渲染
├── imageProcessor.ts  # 图片处理
└── limits.ts          # 读取限制
```

只有 5 个文件，因为读文件的操作相对简单。工具的复杂度直接体现在目录内的文件数量上。

## 1.3 入口文件链

Claude Code 的启动不是"一个 main 函数搞定"的。它分了很多层，每一层做不同的事。

### 启动链路图

```
cli.tsx (入口)
  │  ├── 检查 --version / --dump-system-prompt 等快速路径
  │  ├── 检查 bridge / daemon / bg 等子命令快速路径
  │  └── 最终：import main.tsx
  ▼
main.tsx (主逻辑)
  │  ├── 参数解析（Commander.js）
  │  ├── init() 初始化
  │  ├── setup() 工作目录、权限上下文
  │  ├── 工具加载、MCP 连接
  │  ├── 信任对话框（showSetupScreens）
  │  └── 分发：交互模式 → launchRepl / headless → runHeadless
  ▼
replLauncher.tsx → interactiveHelpers.tsx → screens/REPL.tsx
  │  ├── 创建 Ink root
  │  ├── 渲染 REPL 组件
  │  └── 等待用户输入
  ▼
query.ts (查询循环)
  │  ├── 异步生成器：query() → queryLoop()
  │  ├── 发送 API 请求
  │  ├── 接收流式响应
  │  ├── 执行工具调用
  │  └── 循环直到 Stop turn
```

### cli.tsx：极简入口

```typescript
// cli.tsx 的核心逻辑：优先处理快速路径
// 整个文件只有一个职责：根据用户输入的参数，决定走哪条路
async function main(): Promise<void> {
  // process.argv = 用户启动程序时输入的所有参数
  // slice(2) = 跳过前两个（node 可执行文件路径 + 脚本文件路径），只留用户真正输入的参数
  const args = process.argv.slice(2)

  // 快速路径 1：用户输入了 --version 或 -v
  // args.length === 1 且第一个参数是版本号标志 → 直接打印版本号，不需要加载任何其他模块
  // 这就是为什么 Claude Code 的 --version 非常快——它根本不加载 React、工具、API 等任何东西
  if (args.length === 1 && (args[0] === '--version' || args[0] === '-v')) {
    // MACRO.VERSION 是构建时注入的版本号字符串（不是运行时读取的）
    console.log(`${MACRO.VERSION} (Claude Code)`)
    return  // 结束函数，不往下执行
  }

  // 快速路径 2：各种子命令，每个子命令对应一个独立的入口
  // remote-control = 远程控制桥接（通过 WebSocket 控制 Claude Code）
  if (args[0] === 'remote-control') { /* bridgeMain() */ return }
  // daemon = 后台守护进程模式
  if (args[0] === 'daemon') { /* daemonMain() */ return }
  // ps/attach = 查看/附加到后台运行的 Claude Code 进程
  if (args[0] === 'ps' || args[0] === 'attach') { /* bg.js */ return }

  // 默认路径：以上都不匹配 → 加载完整的 CLI（这才是大多数人用 Claude Code 的方式）
  // "await import(...)" = 动态导入：只有走到这一步才加载 main.js（几千行代码）
  // 如果用户只是输入 --version，这些代码根本不会被加载 → 启动极快
  const { main: cliMain } = await import('../main.js')
  await cliMain()  // 调用 main.tsx 里的 main() 函数
}
```

**设计要点：** 所有 import 都是动态的（`await import()`）。这样 `--version` 路径不需要加载任何模块，启动速度极快。你可能会问：为什么要这么小心？因为 Claude Code 的 main.js 有 4600+ 行，如果每次启动都要加载，`--version` 就会慢好几秒。这种"按需加载"的思路在大型项目里很常见。

### main.tsx：真正的主逻辑

main.tsx 有 **4600+ 行**，是整个代码库最核心的文件。它做了以下几件事：

```typescript
// main.tsx 的核心流程（简化版）
// 这个函数是 Claude Code 的"总指挥"——串联所有初始化步骤
export async function main() {
  // 1. 参数解析：用 Commander.js 库来解析命令行参数
  // CommanderCommand 是一个第三方库，帮你定义 --xxx 选项和对应的处理逻辑
  const program = new CommanderCommand()
    .option('-p, --print', '...')        // -p 或 --print：非交互模式，直接打印结果
    .option('--model <model>', '...')     // --model：指定使用的 AI 模型
    // ... 实际有 50+ 个选项，这只是示意

  // 2. preAction 钩子：在真正执行命令之前，先做初始化
  // "钩子" = 在某个动作发生前后自动执行的代码
  // preAction = 在用户真正开始用 Claude Code 之前执行
  program.hook('preAction', async () => {
    await init()                    // 初始化：读取配置文件、检查认证 token
    runMigrations()                 // 配置迁移：如果用户从旧版本升级，自动迁移配置格式
    loadRemoteManagedSettings()     // 企业设置：公司管理员可能统一推送了配置
  })

  // 3. 主 action：这是真正开始工作的地方
  // "action" = Commander.js 在解析完参数后调用的函数
  // prompt = 用户在命令行直接输入的文字（如 claude "帮我改bug"）
  // options = 解析后的所有选项（如 --model claude-sonnet）
  program.action(async (prompt, options) => {
    // 3a. 加载工具列表：getTools() 返回所有可用工具（Bash、文件读写、搜索等）
    // toolPermissionContext = 权限上下文，决定哪些工具可用、哪些被禁用
    const tools = getTools(toolPermissionContext)

    // 3b. 执行 setup：设置工作目录、权限模式、信任检查等
    // 同样用动态导入（await import），不用的模块不加载
    const { setup } = await import('./setup.js')
    await setup(cwd, permissionMode, ...)

    // 3c. 根据模式分发：是交互式（你在终端里打字）还是 headless（脚本调用）
    if (isNonInteractiveSession) {
      // headless 模式：没有交互界面，适合 CI/CD 或脚本调用
      // 比如：claude "帮我生成README" --print > README.md
      const { runHeadless } = await import('src/cli/print.js')
      await runHeadless(inputPrompt, ...)
    } else {
      // 交互模式：这就是你平时用 Claude Code 的方式——在终端里打字对话
      // createRoot = 创建 Ink（终端 React）的根节点
      // launchRepl = 启动交互循环（读取你的输入 → 调用模型 → 显示结果）
      const root = await createRoot(renderOptions)
      await launchRepl(root, { ... }, sessionConfig, renderAndRun)
    }
  })

  // 最后一步：让 Commander.js 解析命令行参数，然后调用上面定义的 action
  await program.parseAsync(process.argv)
}
```

**为什么分这么多层？**

1. **cli.tsx 只做路由**：它检查参数，决定走哪个快速路径。所有 import 都是动态的，不走的路径不会加载代码
2. **main.tsx 做编排**：它串联初始化、工具加载、MCP 连接、权限检查等步骤
3. **REPL.tsx 做交互**：它管理用户输入、消息渲染、键盘事件
4. **query.ts 做执行**：它是真正的 Agent 循环——发请求、收响应、执行工具、循环

这种分层让每个文件有单一职责，也让快速路径（`--version`、`daemon`）的启动时间降到最低。

**学习路径：如果你想深入理解项目结构和启动流程**

1. **Commander.js**：Claude Code 用它解析命令行参数。[Commander.js 官方文档](https://github.com/tj/commander.js)，花 1 小时看 Readme 就够了，它的 API 很直觉
2. **Node.js 模块系统**：理解 `import` / `require` / 动态导入的区别。推荐 [Node.js 官方文档 - Modules](https://nodejs.org/api/esm.html)
3. **关注点分离原则**：每个文件只做一件事。这是所有好项目的共同特征。推荐读《代码整洁之道》前三章
4. **练习建议**：打开 Claude Code 的 `entrypoints/cli.tsx` 文件，试着理解每一行在干什么。然后用 Claude Code 让它帮你写一个简化版的 CLI 入口——只实现 `--version` 和默认启动两个路径。这就是把学到的东西变成自己能力的过程

---

# 第二章：类型系统设计

## 2.1 核心类型定义

Claude Code 的类型系统是整个架构的骨架。三个核心类型文件定义了系统的边界。

### Tool 类型（Tool.ts）

`Tool` 接口是 Claude Code 中**最重要的类型**。所有 40+ 个工具都必须实现这个接口。

```typescript
// Tool 类型定义：所有 40+ 个工具都必须"长得像"这样
// 这就像一个"接口合同"——每个工具承诺"我有这些属性和方法"
// Input = 这个工具接收什么参数（比如 FileReadTool 接收文件路径）
// Output = 这个工具返回什么结果（比如 FileReadTool 返回文件内容）
// P = 进度数据的类型（工具执行中可以报告进度）
export type Tool<
  Input extends AnyObject = AnyObject,   // 输入参数的类型（默认任意对象）
  Output = unknown,                       // 输出结果的类型（默认未知）
  P extends ToolProgressData = ToolProgressData,  // 进度数据类型
> = {
  // ========== 基本信息 ==========

  // readonly = 只读，赋值后不能再改（工具名叫什么就叫什么，不能中途改名）
  readonly name: string
  // 别名：如果工具改了名，旧名字还能用（向后兼容）
  // 比如原来的 'cat' 改成 'read'，但老用户可能还在用 'cat'
  aliases?: string[]
  // 搜索提示：当用户搜索工具时，用这些关键词匹配
  searchHint?: string

  // ========== 核心方法 ==========

  // call() = 工具的"执行函数"——模型说"我要用这个工具"时，call() 被调用
  // args = 模型传入的参数（比如文件路径）
  // context = 执行上下文（当前会话配置、权限等）
  // canUseTool = 权限检查函数（问"这个操作允许执行吗？"）
  // parentMessage = 调用这个工具的那条 AI 消息
  // onProgress = 进度回调（执行中可以报告"正在处理..."）
  call(
    args: z.infer<Input>,        // z.infer<Input> = 从 Zod schema 推导出 TypeScript 类型
    context: ToolUseContext,      // 执行上下文（配置、权限、状态）
    canUseTool: CanUseToolFn,    // 权限检查回调
    parentMessage: AssistantMessage,  // 触发这次工具调用的 AI 消息
    onProgress?: ToolCallProgress<P>,  // 可选的进度报告回调
  ): Promise<ToolResult<Output>>  // 返回工具执行结果（异步）

  // description() = 告诉模型"这个工具是干什么的"
  // 为什么是异步的？因为有些工具的描述需要读文件系统（比如 BashTool 需要知道沙箱配置）
  description(
    input: z.infer<Input>,       // 用户可能传入的参数
    options: {
      isNonInteractiveSession: boolean  // 是否非交互模式（影响描述的详细程度）
      toolPermissionContext: ToolPermissionContext  // 当前权限配置
      tools: Tools              // 其他可用工具（某些工具描述会引用其他工具）
    },
  ): Promise<string>             // 返回一段描述文字

  // ========== Schema（数据格式定义）==========

  // inputSchema = 用 Zod 定义这个工具接受什么参数
  // 为什么用 Zod 不用 interface？因为同一份 schema 可以同时做：
  //   1. TypeScript 类型检查（写代码时）
  //   2. 运行时验证（执行时检查参数对不对）
  //   3. 生成 JSON Schema 给 API（告诉模型这个工具的参数格式）
  readonly inputSchema: Input
  outputSchema?: z.ZodType<unknown>  // 可选：输出数据的格式

  // ========== 行为标记（告诉系统这个工具的特性）==========

  // isConcurrencySafe = "这个工具能和其他工具同时跑吗？"
  // 比如"读文件"是安全的（同时读两个文件没问题）
  // 但"执行 shell 命令"不安全（两个命令同时跑可能互相干扰）
  isConcurrencySafe(input: z.infer<Input>): boolean

  // isEnabled = "这个工具现在可用吗？"（有些工具可能被配置关闭）
  isEnabled(): boolean

  // isReadOnly = "这个工具只读数据，不修改任何东西吗？"
  // 只读工具在某些权限模式下可以自动放行
  isReadOnly(input: z.infer<Input>): boolean

  // isDestructive = "这个工具可能造成不可逆的操作吗？"（比如删除文件）
  isDestructive?(input: z.infer<Input>): boolean

  // ========== 权限 ==========

  // checkPermissions = "给定这些参数，允许执行吗？"
  // 返回 allow（允许）、deny（拒绝）、ask（询问用户）之一
  checkPermissions(
    input: z.infer<Input>,
    context: ToolUseContext,
  ): Promise<PermissionResult>

  // ========== Prompt（给模型看的工具描述）==========

  // prompt() = 生成给 AI 模型看的工具说明
  // 和 description() 不同：description 是简要描述，prompt 是完整说明
  // 模型读了 prompt 才知道"这个工具怎么用、参数怎么填"
  prompt(options: {
    getToolPermissionContext: () => Promise<ToolPermissionContext>
    tools: Tools
    agents: AgentDefinition[]
  }): Promise<string>

  // ========== UI 渲染（控制工具在终端里的显示效果）==========

  // userFacingName = 工具在用户面前显示的名字（可能和 name 不同）
  userFacingName(input: Partial<z.infer<Input>> | undefined): string

  // renderToolUseMessage = 渲染"模型正在使用这个工具"的消息
  // 比如 FileReadTool 会显示 "📖 Reading file: main.ts"
  renderToolUseMessage(input, options): React.ReactNode

  // renderToolResultMessage = 渲染工具执行结果
  // 比如 FileReadTool 会显示文件内容（带行号）
  renderToolResultMessage(content, progressMessages, options): React.ReactNode

  // 以下三个是可选的渲染方法
  renderToolUseProgressMessage?(progressMessages, options): React.ReactNode  // 执行中的进度
  renderToolUseRejectedMessage?(input, options): React.ReactNode  // 用户拒绝执行时的显示
  renderToolUseErrorMessage?(result, options): React.ReactNode  // 执行出错时的显示

  // ========== 序列化 ==========

  // mapToolResultToToolResultBlockParam = 把工具结果转成 API 需要的格式
  // 模型需要工具结果才能继续思考，这个方法做格式转换
  mapToolResultToToolResultBlockParam(
    content: Output,             // 工具的输出
    toolUseID: string,           // 这次工具调用的唯一 ID（和请求对应）
  ): ToolResultBlockParam        // 返回 API 需要的格式

  // ========== 安全分类器 ==========

  // toAutoClassifierInput = 把工具输入转成安全分类器能分析的格式
  // 在 auto 权限模式下，系统用 AI 判断"这个操作安全吗"
  toAutoClassifierInput(input: z.infer<Input>): unknown
}
```

**逐字段解释关键设计决策：**

- **`Input extends AnyObject`**：用 Zod schema 而不是 interface，这样可以从 schema 生成 JSON Schema 给 API，同时在运行时做验证
- **`call()` 接收 `context` 和 `canUseTool`**：不是通过全局状态访问，而是显式注入。这让工具可以被独立测试
- **`isConcurrencySafe()`**：query.ts 用这个决定能否并行执行多个工具调用
- **`description()` 是异步的**：因为有些工具描述需要读取文件系统（如 BashTool 需要知道沙箱配置）
- **全套渲染方法**：工具不仅执行逻辑，还负责自己的 UI 表现。这让每个工具可以完全控制自己的显示效果

### buildTool：工具工厂

Claude Code 提供了 `buildTool()` 函数来简化工具创建。你不需要手写 Tool 类型的每一个字段，只需要提供你关心的，剩下的用默认值填充：

```typescript
// TOOL_DEFAULTS = 所有工具的"默认值"
// 如果你创建一个工具时没有指定某个字段，就用这里的默认值
const TOOL_DEFAULTS = {
  isEnabled: () => true,                      // 默认启用（除非你显式关闭）
  // _input = 参数（下划线前缀表示"我知道有这个参数但我不用它"）
  isConcurrencySafe: (_input?: unknown) => false,   // 默认不并发安全（保守策略：宁可慢一点，不能出错）
  isReadOnly: (_input?: unknown) => false,          // 默认可写（保守策略：宁可要权限，不能无权限操作）
  isDestructive: (_input?: unknown) => false,       // 默认非破坏性
  // 默认权限检查：直接允许（大多数工具不需要复杂的权限逻辑）
  checkPermissions: (input, _ctx) =>
    Promise.resolve({ behavior: 'allow', updatedInput: input }),
  // 默认分类器输入：空字符串（大多数工具不需要 AI 安全分类）
  toAutoClassifierInput: (_input?: unknown) => '',
  // 默认用户可见名称：空字符串（会 fallback 到工具的 name 字段）
  userFacingName: (_input?: unknown) => '',
}

// buildTool() = 工具工厂函数
// 泛型 <D extends AnyToolDef> = D 是一个"工具定义"对象
// 返回类型 BuiltTool<D> = 根据 D 自动推导出完整的 Tool 类型
export function buildTool<D extends AnyToolDef>(def: D): BuiltTool<D> {
  return {
    ...TOOL_DEFAULTS,          // 1. 先铺上所有默认值
    userFacingName: () => def.name,  // 2. 如果没提供 userFacingName，默认用工具名
    ...def,                    // 3. 用你传入的定义覆盖默认值
    // 后写的覆盖先写的：如果你传了 isReadOnly: () => true，就会覆盖默认的 false
  } as BuiltTool<D>
}
```

**设计要点：**

- **fail-closed 默认值**：`isConcurrencySafe` 默认 `false`，`isReadOnly` 默认 `false`。工具必须显式声明"我是安全的"，而不是默认安全
- **类型推导**：`BuiltTool<D>` 类型根据传入的定义自动推导返回类型，确保类型安全
- **覆盖而不是继承**：用展开运算符覆盖默认值，而不是用类继承

### ToolUseContext：工具执行上下文

```typescript
export type ToolUseContext = {
  options: {
    commands: Command[]
    debug: boolean
    mainLoopModel: string
    tools: Tools
    verbose: boolean
    thinkingConfig: ThinkingConfig
    mcpClients: MCPServerConnection[]
    isNonInteractiveSession: boolean
    agentDefinitions: AgentDefinitionsResult
    maxBudgetUsd?: number
    customSystemPrompt?: string
    appendSystemPrompt?: string
    refreshTools?: () => Tools    // 运行时刷新工具列表
  }
  abortController: AbortController
  readFileState: FileStateCache
  getAppState(): AppState
  setAppState(f: (prev: AppState) => AppState): void
  setToolJSX?: SetToolJSXFn
  messages: Message[]
  agentId?: AgentId               // 子 Agent 的 ID
  agentType?: string              // 子 Agent 的类型
  contentReplacementState?: ContentReplacementState
  renderedSystemPrompt?: SystemPrompt  // 用于 prompt cache 复用
  // ... 更多字段
}
```

**这个类型的三个核心职责：**

1. **传递配置**：`options` 包含所有会话级配置（模型、工具、调试开关等）
2. **提供状态访问**：`getAppState()` / `setAppState()` 让工具能读写全局状态
3. **支持子 Agent**：`agentId`、`messages`、`renderedSystemPrompt` 支持子 Agent 执行

### 消息类型

```typescript
// types/message.ts 中的核心消息类型（简化版）
type Message =
  | UserMessage
  | AssistantMessage
  | SystemMessage
  | AttachmentMessage
  | ProgressMessage
  | ToolUseSummaryMessage
  | TombstoneMessage

type UserMessage = {
  type: 'user'
  uuid: string
  message: { role: 'user'; content: string | Array<ToolResultParam> }
  toolUseResult?: unknown
  isMeta?: boolean
}

type AssistantMessage = {
  type: 'assistant'
  uuid: string
  message: { role: 'assistant'; content: Array<ContentBlock> }
  thinkingEnabled?: boolean
  apiError?: string
}
```

### 权限类型

```typescript
// types/permissions.ts

// 权限模式
export type PermissionMode =
  | 'default'
  | 'acceptEdits'
  | 'bypassPermissions'
  | 'plan'
  | 'dontAsk'
  | 'auto'           // AI 分类器自动判断
  | 'bubble'

// 权限行为
export type PermissionBehavior = 'allow' | 'deny' | 'ask'

// 权限结果
export type PermissionResult<Input> =
  | PermissionAllowDecision<Input>   // 允许
  | PermissionAskDecision<Input>     // 询问用户
  | PermissionDenyDecision           // 拒绝
  | { behavior: 'passthrough'; ... } // 透传（交给其他检查）

// 权限决策原因
export type PermissionDecisionReason =
  | { type: 'rule'; rule: PermissionRule }
  | { type: 'mode'; mode: PermissionMode }
  | { type: 'classifier'; classifier: string; reason: string }
  | { type: 'hook'; hookName: string; ... }
  | { type: 'safetyCheck'; reason: string; classifierApprovable: boolean }
  // ...
```

**设计亮点：** `PermissionResult` 用 discriminated union（`behavior` 字段）来区分三种结果，让调用方必须处理所有情况（编译器会检查）。`PermissionDecisionReason` 记录了决策原因，方便调试和审计。

## 2.2 TypeScript 高级用法

### 泛型 + 条件类型：BuiltTool

```typescript
type BuiltTool<D> = Omit<D, DefaultableToolKeys> & {
  [K in DefaultableToolKeys]-?: K extends keyof D
    ? undefined extends D[K]
      ? ToolDefaults[K]
      : D[K]
    : ToolDefaults[K]
}
```

这段类型的意思是：对于每个"可默认"的字段，如果 `D` 提供了就用 `D` 的类型，否则用默认类型。`-?` 确保这些字段在返回类型中一定是 required 的。

### Discriminated Union：PermissionResult

```typescript
type PermissionResult = 
  | { behavior: 'allow'; updatedInput?: Input; ... }
  | { behavior: 'ask'; message: string; ... }
  | { behavior: 'deny'; message: string; ... }
  | { behavior: 'passthrough'; message: string; ... }
```

调用方必须 `switch (result.behavior)` 处理所有情况，编译器强制完整性检查。

### 模板字面量类型：Feature Flags

```typescript
// feature() 函数使用模板字面量类型来约束 feature flag 名称
if (feature('COORDINATOR_MODE')) { ... }
if (feature('TRANSCRIPT_CLASSIFIER')) { ... }
if (feature('KAIROS')) { ... }
```

`feature()` 是 `bun:bundle` 提供的构建时 dead code elimination 函数。返回值在构建时确定，未命中的分支会被 DCE 移除。

### DeepImmutable

```typescript
export type DeepImmutable<T> = {
  readonly [K in keyof T]: T[K] extends object
    ? T[K] extends Map<infer K2, infer V>
      ? ReadonlyMap<K2, V>
      : DeepImmutable<T[K]>
    : T[K]
}
```

`AppState` 使用 `DeepImmutable` 包裹，确保从 `getAppState()` 读取的状态不能被意外修改。修改状态必须通过 `setAppState()`。

## 2.3 类型设计范式

从 Claude Code 的类型设计中提炼出的规范：

### 范式 1：用 Zod Schema 定义输入

```typescript
// 不要用 interface 定义工具输入
interface BadInput { file_path: string }

// 要用 Zod schema
const schema = z.object({
  file_path: z.string().describe('Absolute path to the file'),
})
```

好处：同一份 schema 既能做运行时验证，又能生成 JSON Schema 给 API。

### 范式 2：Discriminated Union 而不是可选字段

```typescript
// 不好：用可选字段区分情况
type Bad = { allow?: boolean; deny?: boolean; message?: string }

// 好：用 discriminated union
type Good = 
  | { behavior: 'allow' }
  | { behavior: 'deny'; message: string }
```

### 范式 3：常量文件避免循环依赖

```typescript
// tools/BashTool/toolName.ts
export const BASH_TOOL_NAME = 'Bash'

// 其他文件引用常量而不是直接 import BashTool
import { BASH_TOOL_NAME } from '../BashTool/toolName.js'
```

### 范式 4：DeepImmutable 保护状态

```typescript
// AppState 用 DeepImmutable 包裹
export type AppState = DeepImmutable<{ ... }>

// 只有 setAppState 可以修改
const state = getAppState()  // 只读
setAppState(prev => ({ ...prev, verbose: !prev.verbose }))  // 可写
```

### Checklist

- [ ] 工具输入用 Zod schema 定义
- [ ] 多种情况用 discriminated union
- [ ] 常量单独成文件避免循环依赖
- [ ] 只读数据用 `readonly` 或 `DeepImmutable`
- [ ] 工具名用 `toolName.ts` 导出常量

**学习路径：如果你想掌握 TypeScript 类型系统**

1. **TypeScript Handbook - Everyday Types**：[官方手册](https://www.typescriptlang.org/docs/handbook/2/types-from-types.html)，重点看 "Type Narrowing" 和 "Discriminated Unions" 两节——这两项是 Claude Code 类型设计的核心
2. **Zod 入门**：[Zod 官方文档](https://zod.dev/)，Zod 的 API 很少，花 2 小时就能看完。重点理解 `z.object()`、`z.infer<>` 和 `.describe()` 这三个概念
3. **深入理解泛型**：推荐 Matt Pocock 的 [Total TypeScript](https://www.totaltypescript.com/)（有免费内容），特别是 "TypeScript Generics" 系列
4. **练习建议**：用 Claude Code 让它帮你定义一个"天气查询工具"的类型——包括输入 schema（城市名、日期）、输出类型（温度、湿度）、权限类型（允许/拒绝/询问）。这就是把这一章的内容变成你自己的能力。重点练习 discriminated union——给天气 API 的错误定义三种情况（城市不存在、API 限额、网络错误），每种情况附带不同的错误信息

---

# 第三章：状态管理

## 3.1 这解决什么问题

Agent 产品需要管理大量运行时状态：当前消息列表、工具列表、MCP 连接、权限上下文、UI 状态等。Claude Code 没有使用 Redux 或 Zustand，而是自研了一个极简的 Store。

## 3.2 Store 实现

### createStore：40 行搞定

```typescript
type Listener = () => void
type OnChange<T> = (args: { newState: T; oldState: T }) => void

export type Store<T> = {
  getState: () => T
  setState: (updater: (prev: T) => T) => void
  subscribe: (listener: Listener) => () => void
}

export function createStore<T>(
  initialState: T,
  onChange?: OnChange<T>,
): Store<T> {
  let state = initialState
  const listeners = new Set<Listener>()

  return {
    getState: () => state,

    setState: (updater: (prev: T) => T) => {
      const prev = state
      const next = updater(prev)
      if (Object.is(next, prev)) return  // 浅比较，不变则跳过
      state = next
      onChange?.({ newState: next, oldState: prev })
      for (const listener of listeners) listener()
    },

    subscribe: (listener: Listener) => {
      listeners.add(listener)
      return () => listeners.delete(listener)
    },
  }
}
```

**关键设计决策：**

1. **函数式更新**：`setState` 接收 `(prev: T) => T` 而不是直接传新值。这样避免闭包过期问题
2. **浅比较跳过**：`Object.is(next, prev)` 如果返回同一个对象引用，不触发通知
3. **onChange 回调**：可选的 `onChange` 在状态变更时被调用，用于副作用（如持久化）
4. **subscribe 返回取消函数**：标准的发布-订阅模式

### AppState：全局状态类型

```typescript
export type AppState = DeepImmutable<{
  settings: SettingsJson
  verbose: boolean
  mainLoopModel: ModelSetting
  toolPermissionContext: ToolPermissionContext
  mcp: {
    clients: MCPServerConnection[]
    tools: Tool[]
    commands: Command[]
    resources: Record<string, ServerResource[]>
  }
  plugins: { enabled: LoadedPlugin[]; disabled: LoadedPlugin[]; ... }
  tasks: { [taskId: string]: TaskState }
  todos: { ... }
  // ... 几十个字段
}> & {
  // 非不可变字段
  tasks: { [taskId: string]: TaskState }
  agentNameRegistry: Map<string, AgentId>
}
```

**AppState 是一个巨型对象**，包含了会话的所有状态。这和 Redux 的单一 Store 思路类似，但没有 reducer 的概念。

### onChangeAppState：副作用监听

```typescript
// state/onChangeAppState.ts
export function onChangeAppState({ newState, oldState }: { ... }) {
  // MCP 状态变更 → 更新工具列表
  // verbose 变更 → 切换日志级别
  // model 变更 → 更新 token 计算参数
  // ...
}
```

## 3.3 和 Redux / Zustand 的对比

| 特性 | Claude Code Store | Redux | Zustand |
|------|------------------|-------|---------|
| 代码量 | ~40 行 | 几千行 | ~200 行 |
| 概念 | getState / setState / subscribe | Action / Reducer / Middleware | getState / setState |
| 不可变性 | 手动（DeepImmutable 类型） | 强制（reducer 返回新对象） | 手动 |
| 中间件 | 无（onChange 回调） | 完整中间件链 | 无 |
| 选择器 | 手动（selectors.ts） | createSelector | 手动 |
| DevTools | 无 | 完整支持 | 基础支持 |

**为什么选择自研？**

1. **够用就好**：Agent 产品不需要 Redux 那样的 action/reducer/middleware 体系。状态变更直接 `setState` 就行
2. **减少依赖**：40 行代码比引入一个库更可控
3. **类型安全**：DeepImmutable 比 Redux 的 immer 更轻量

## 3.4 状态管理范式

### 范式 1：函数式更新

```typescript
// 不好：直接读-改-写（闭包过期风险）
const state = store.getState()
state.verbose = true
store.setState(() => state)

// 好：函数式更新
store.setState(prev => ({ ...prev, verbose: true }))
```

### 范式 2：子状态更新

```typescript
// 更新 MCP 子状态
store.setState(prev => ({
  ...prev,
  mcp: {
    ...prev.mcp,
    tools: [...prev.mcp.tools, newTool],
  },
}))
```

### 范式 3：不变性保证

```typescript
// AppState 用 DeepImmutable 包裹
// 读取状态时 TypeScript 会阻止直接修改
const state = getAppState()
state.verbose = true  // 编译错误！
```

### Checklist

- [ ] 状态更新用函数式 `setState(prev => ...)`
- [ ] 全局状态类型用 `DeepImmutable` 包裹
- [ ] 副作用放在 onChange 回调里
- [ ] 子状态更新保持不可变性（展开运算符）

---

# 第四章：工具系统实现

> 详见产品分析篇第 3 章关于工具提示词的分析

## 4.1 这解决什么问题

Agent 的核心能力来自工具。没有工具，模型只能聊天；有了工具，模型能读文件、执行命令、搜索网页。工具系统需要解决：

1. **定义统一接口**：所有工具遵循同一套 API
2. **安全执行**：每个工具调用都要经过权限检查
3. **UI 一致**：每个工具在终端中的显示风格统一
4. **动态注册**：MCP 工具可以在运行时加入

## 4.2 工具注册机制

### getTools：工具汇集中心

```typescript
// tools.ts
export const getTools = (permissionContext: ToolPermissionContext): Tools => {
  // 简单模式：只有 Bash、Read、Edit
  if (isEnvTruthy(process.env.CLAUDE_CODE_SIMPLE)) {
    const simpleTools: Tool[] = [BashTool, FileReadTool, FileEditTool]
    return filterToolsByDenyRules(simpleTools, permissionContext)
  }

  // 获取所有基础工具
  const tools = getAllBaseTools().filter(tool => !specialTools.has(tool.name))
  
  // 过滤被拒绝的工具
  let allowedTools = filterToolsByDenyRules(tools, permissionContext)
  
  // REPL 模式：隐藏原始工具（它们在 REPL 内部可用）
  if (isReplModeEnabled()) {
    allowedTools = allowedTools.filter(tool => !REPL_ONLY_TOOLS.has(tool.name))
  }

  // 过滤 disabled 工具
  const isEnabled = allowedTools.map(_ => _.isEnabled())
  return allowedTools.filter((_, i) => isEnabled[i])
}
```

### getAllBaseTools：工具清单

```typescript
export function getAllBaseTools(): Tools {
  return [
    AgentTool,
    TaskOutputTool,
    BashTool,
    ...(hasEmbeddedSearchTools() ? [] : [GlobTool, GrepTool]),
    ExitPlanModeV2Tool,
    FileReadTool,
    FileEditTool,
    FileWriteTool,
    NotebookEditTool,
    WebFetchTool,
    TodoWriteTool,
    WebSearchTool,
    TaskStopTool,
    AskUserQuestionTool,
    SkillTool,
    EnterPlanModeTool,
    ...(process.env.USER_TYPE === 'ant' ? [ConfigTool] : []),
    ...(SuggestBackgroundPRTool ? [SuggestBackgroundPRTool] : []),
    ...(SleepTool ? [SleepTool] : []),
    BriefTool,
    ListMcpResourcesTool,
    ReadMcpResourceTool,
    ...(isToolSearchEnabledOptimistic() ? [ToolSearchTool] : []),
    // ... 更多工具
  ]
}
```

**设计要点：**

1. **条件注册**：用展开运算符（`...condition ? [Tool] : []`）动态决定哪些工具可用
2. **Feature Flag**：`feature('PROACTIVE')` 在构建时决定 SleepTool 是否存在
3. **懒加载**：用 `require()` 而不是 `import()`，避免循环依赖和不必要的模块加载
4. **deny 规则过滤**：`filterToolsByDenyRules` 在注册阶段就移除被禁止的工具

### assembleToolPool：合并内置工具和 MCP 工具

```typescript
export function assembleToolPool(
  permissionContext: ToolPermissionContext,
  mcpTools: Tools,
): Tools {
  const builtInTools = getTools(permissionContext)
  const allowedMcpTools = filterToolsByDenyRules(mcpTools, permissionContext)

  // 排序保证 prompt cache 稳定性
  const byName = (a: Tool, b: Tool) => a.name.localeCompare(b.name)
  return uniqBy(
    [...builtInTools].sort(byName).concat(allowedMcpTools.sort(byName)),
    'name',
  )
}
```

**为什么要排序？** 因为工具列表会被拼入系统提示词，排序后相同的工具有相同的顺序，可以利用 Anthropic 的 prompt caching。如果不排序，MCP 工具的连接顺序不确定，每次工具列表不同，cache miss。

## 4.3 完整工具走读：FileReadTool

### 目录结构

```
tools/FileReadTool/
├── FileReadTool.ts    # 主实现
├── prompt.ts          # 工具描述
├── UI.tsx             # 渲染
├── imageProcessor.ts  # 图片处理
└── limits.ts          # 读取限制
```

### prompt.ts：工具描述怎么写

```typescript
export const FILE_READ_TOOL_NAME = 'Read'
export const DESCRIPTION = 'Read a file from the local filesystem.'

export function renderPromptTemplate(
  lineFormat: string,
  maxSizeInstruction: string,
  offsetInstruction: string,
): string {
  return `Reads a file from the local filesystem. You can access any file directly by using this tool.
Assume this tool is able to read all files on the machine. If the User provides a path to a file assume that path is valid. It is okay to read a file that does not exist; an error will be returned.

Usage:
- The file_path parameter must be an absolute path, not a relative path
- By default, it reads up to 2000 lines starting from the beginning of the file${maxSizeInstruction}
${offsetInstruction}
${lineFormat}
- This tool allows Claude Code to read images (eg PNG, JPG, etc). When reading an image file the contents are presented visually as Claude Code is a multimodal LLM.
- This tool can read Jupyter notebooks (.ipynb files)...
- This tool can only read files, not directories. To read a directory, use an ls command via the Bash tool.`
}
```

**prompt.ts 的设计模式：**

1. **常量和模板分开**：`FILE_READ_TOOL_NAME`、`DESCRIPTION` 是常量，`renderPromptTemplate` 是模板函数
2. **模板参数化**：`maxSizeInstruction`、`offsetInstruction` 根据运行时条件变化
3. **清晰的边界**：告诉模型能做什么（读文件）、不能做什么（读目录）、遇到错误怎么办

### FileReadTool.ts：核心实现

```typescript
// 用 buildTool 创建工具
export const FileReadTool = buildTool({
  name: FILE_READ_TOOL_NAME,
  async description() {
    return DESCRIPTION
  },

  // Zod schema 定义输入参数
  inputSchema: z.object({
    file_path: z.string().describe('The absolute path to the file to read'),
    offset: z.number().optional().describe('The line number to start reading from'),
    limit: z.number().optional().describe('The number of lines to read'),
  }),

  // 只读操作
  isReadOnly: () => true,
  isConcurrencySafe: () => true,  // 并行读取安全

  async prompt({ getToolPermissionContext, tools }) {
    const permissionContext = await getToolPermissionContext()
    // 根据权限上下文生成不同的描述
    return renderPromptTemplate(...)
  },

  async call(args, context, canUseTool, parentMessage, onProgress) {
    const { file_path, offset, limit } = args

    // 1. 权限检查
    const permissionResult = await checkReadPermissionForTool(file_path, context)
    if (permissionResult.behavior !== 'allow') {
      return { data: { type: 'error', error: permissionResult.message } }
    }

    // 2. 读取文件
    const content = await readFileInRange(file_path, offset, limit)
    
    // 3. 返回结果
    return {
      data: {
        type: 'text',
        content: addLineNumbers(content, offset ?? 1),
      },
    }
  },

  // UI 渲染委托给 UI.tsx
  renderToolUseMessage: renderToolUseMessage,
  renderToolResultMessage: renderToolResultMessage,
  renderToolUseErrorMessage: renderToolUseErrorMessage,
})
```

**FileReadTool 的代码模式：**

1. **buildTool 包裹**：不用手写默认方法
2. **Zod schema**：定义输入参数，自动验证
3. **异步 prompt**：根据运行时环境生成描述
4. **call 方法**：权限检查 → 执行 → 返回结果
5. **渲染委托**：UI 逻辑在 UI.tsx 中

## 4.4 BashTool 安全系统深度走读

BashTool 是 Claude Code 中**最复杂**的工具。它需要执行 shell 命令，但 shell 命令的安全风险极高。BashTool 的目录里有 18 个文件，其中大部分是安全相关的。

### 目录结构

```
tools/BashTool/
├── BashTool.tsx             # 主实现
├── bashPermissions.ts       # 权限检查（允许/拒绝/询问）
├── bashSecurity.ts          # 安全分析（AST 解析）
├── commandSemantics.ts      # 命令语义分析
├── prompt.ts                # 工具描述
├── UI.tsx                   # 渲染
├── toolName.ts              # 名称常量
├── utils.ts                 # 工具函数
├── sedEditParser.ts         # sed 编辑解析
├── sedValidation.ts         # sed 验证
├── pathValidation.ts        # 路径验证
├── modeValidation.ts        # 模式验证
├── readOnlyValidation.ts    # 只读验证
├── destructiveCommandWarning.ts  # 破坏性命令警告
├── commentLabel.ts          # 注释标签
├── bashCommandHelpers.ts    # 命令辅助
└── shouldUseSandbox.ts      # 沙箱判断
```

### 权限检查流程

```
用户输入命令
    │
    ▼
BashTool.checkPermissions()
    │
    ├── 1. alwaysDenyRules 检查 → deny 则直接拒绝
    │
    ├── 2. alwaysAllowRules 检查 → allow 则直接放行
    │
    ├── 3. bashSecurity.ts 安全分析
    │     ├── AST 解析命令结构
    │     ├── 命令分类（只读/写入/破坏性）
    │     └── 路径检查（是否在允许目录内）
    │
    ├── 4. 模式匹配
    │     ├── 匹配用户设置的规则（如 "git *"）
    │     └── 匹配工具的 preparePermissionMatcher
    │
    └── 5. 默认行为
          ├── plan 模式 → ask
          ├── acceptEdits 模式 → ask（非写操作 allow）
          └── default 模式 → ask
```

### 命令语义分析

```typescript
// bashSecurity.ts 中的关键概念
// 命令被分类为：
// - 'read': cat, head, tail, grep, find, ls, wc, diff, etc.
// - 'write': cp, mv, rm, mkdir, touch, tee, etc.
// - 'destructive': rm -rf, dd, mkfs, etc.
```

**安全系统的核心思路是分层防御：**

1. **规则层**：用户/管理员设置的 alwaysDeny/alwaysAllow 规则
2. **AST 层**：解析命令的语法树，分析语义
3. **分类层**：把命令分类为只读/写入/破坏性
4. **路径层**：检查命令操作的文件路径是否在允许范围内
5. **沙箱层**：在沙箱中执行命令（可选）

## 4.5 工具设计范式

### 范式 1：buildTool 而不是手动实现

```typescript
// 不好：手动实现所有方法
export const MyTool: Tool = {
  name: 'MyTool',
  isEnabled: () => true,
  isConcurrencySafe: () => false,
  isReadOnly: () => false,
  // ... 必须写完所有字段
}

// 好：用 buildTool
export const MyTool = buildTool({
  name: 'MyTool',
  // 只写需要的字段，其他用默认值
})
```

### 范式 2：关注点分离

```
MyTool/
├── MyTool.ts        # 核心逻辑
├── prompt.ts        # 给模型看的描述
├── UI.tsx           # 给用户看的渲染
├── toolName.ts      # 名称常量（避免循环依赖）
└── utils.ts         # 辅助函数
```

### 范式 3：fail-closed 默认值

```typescript
const TOOL_DEFAULTS = {
  isConcurrencySafe: () => false,   // 默认不并发
  isReadOnly: () => false,          // 默认可写
  isDestructive: () => false,       // 默认非破坏性
}
```

工具必须显式声明自己是安全的，而不是默认安全。

### 范式 4：prompt 是异步的

```typescript
async prompt({ getToolPermissionContext, tools }) {
  const ctx = await getToolPermissionContext()
  // 根据运行时配置生成不同的描述
  return renderPromptTemplate(...)
}
```

prompt 不是静态字符串，而是根据运行时环境（权限模式、沙箱配置等）动态生成。

### Checklist

- [ ] 用 `buildTool()` 创建工具
- [ ] 每个工具独立目录，内部按职责分文件
- [ ] `toolName.ts` 导出名称常量
- [ ] `isReadOnly` / `isConcurrencySafe` 显式声明
- [ ] `prompt()` 是异步的，根据运行时条件生成
- [ ] 权限检查在 `checkPermissions()` 中，不在 `call()` 中

---

# 第五章：Agent 系统实现

> 详见设计范式篇第 4 章

## 5.1 这解决什么问题

单个 Agent 的能力有限。Claude Code 通过 AgentTool 实现了子 Agent 机制——一个 Agent 可以派生子 Agent 去执行子任务，子 Agent 完成后把结果返回给父 Agent。这就是多 Agent 协作的基础。

## 5.2 AgentTool 走读

AgentTool 是 Claude Code 中最大的工具（233K），它实现了子 Agent 的派生和管理。

### 工具定义

```typescript
// tools/AgentTool/AgentTool.tsx（简化）
export const AgentTool = buildTool({
  name: AGENT_TOOL_NAME,  // 'Agent'

  inputSchema: z.object({
    prompt: z.string().describe('The task for the agent to perform'),
    mode: z.enum(['run', 'resume']).describe('Whether to start a new agent or resume an existing one'),
    agent_type: z.string().optional().describe('The type of agent to use'),
    resume: z.string().optional().describe('Agent ID to resume'),
    description: z.string().optional().describe('A short (3-5 word) description'),
  }),

  isConcurrencySafe: () => false,   // 子 Agent 不并发安全
  isReadOnly: (input) => false,

  async call(args, context, canUseTool, parentMessage, onProgress) {
    if (args.mode === 'resume') {
      // 恢复已有的子 Agent
      return resumeAgent(args, context, canUseTool, parentMessage, onProgress)
    }
    // 创建新的子 Agent
    return runAgent(args, context, canUseTool, parentMessage, onProgress)
  },
})
```

### call 方法的执行流程

```
父 Agent 调用 AgentTool.call()
    │
    ├── 1. 创建子 Agent 上下文
    │     ├── 继承父 Agent 的 ToolUseContext
    │     ├── 创建新的 abortController
    │     ├── 克隆 messages（不共享引用）
    │     └── 设置 agentId（新 UUID）
    │
    ├── 2. 构建子 Agent 的系统提示词
    │     ├── 继承父 Agent 的系统提示词
    │     ├── 添加子 Agent 特定指令
    │     └── 处理 prompt cache 复用
    │
    ├── 3. 运行子 Agent 的 query 循环
    │     ├── 调用 query() 异步生成器
    │     ├── 收集子 Agent 的消息
    │     ├── 处理工具调用（子 Agent 可以用工具）
    │     └── 发送 ProgressMessage 给父 Agent
    │
    └── 4. 返回结果
          ├── 收集子 Agent 的最终消息
          ├── 构建 ToolResult
          └── 返回给父 Agent
```

## 5.3 子 Agent 执行：runAgent

```typescript
// tools/AgentTool/runAgent.ts（简化）
async function* runAgent(args, context, canUseTool, parentMessage, onProgress) {
  // 1. 创建子 Agent 上下文
  const subagentContext = createSubagentContext(context, args)

  // 2. 构建查询参数
  const queryParams: QueryParams = {
    messages: subagentContext.messages,
    systemPrompt: subagentContext.systemPrompt,
    userContext: {},
    systemContext: {},
    canUseTool: subagentContext.canUseTool,
    toolUseContext: subagentContext.toolUseContext,
    querySource: 'agent',
  }

  // 3. 运行查询循环
  const queryResult = query(queryParams)
  for await (const event of queryResult) {
    if (event.type === 'assistant') {
      // 转发父 Agent 的进度
      onProgress?.({
        toolUseID: parentMessage.toolUseID,
        data: { type: 'agent_progress', message: event },
      })
    }
  }

  // 4. 返回结果
  return {
    data: {
      result: 'Agent completed successfully',
      messages: subagentContext.messages,
    },
  }
}
```

## 5.4 Fork 机制：forkSubagent

Fork 机制允许子 Agent 继承父 Agent 的上下文（包括消息历史），用于需要"当前上下文"的子任务。

```typescript
// tools/AgentTool/forkSubagent.ts（简化）
export function forkSubagent(
  parentContext: ToolUseContext,
  parentMessages: Message[],
  options: ForkOptions,
): SubagentContext {
  // 1. 克隆消息（不共享引用）
  const clonedMessages = parentMessages.map(m => ({ ...m }))

  // 2. 继承 prompt cache
  // 子 Agent 的系统提示词前缀和父 Agent 一致
  // 这样可以复用 Anthropic 的 prompt cache
  const renderedSystemPrompt = parentContext.renderedSystemPrompt

  // 3. 创建子 Agent 的 ToolUseContext
  const subagentContext: ToolUseContext = {
    ...parentContext,
    agentId: generateAgentId(),
    messages: clonedMessages,
    abortController: new AbortController(),
    // 子 Agent 的 setAppState 是 no-op（不影响父 Agent 状态）
    setAppState: () => {},
    // 保留 contentReplacementState 以共享工具结果缓存
    contentReplacementState: parentContext.contentReplacementState,
    renderedSystemPrompt,
  }

  return { toolUseContext: subagentContext, messages: clonedMessages }
}
```

**Prompt Cache 复用的关键代码：**

```typescript
// 子 Agent 的系统提示词前缀和父 Agent 完全一致
// Anthropic 的 prompt caching 基于前缀匹配
// 前缀相同 = cache hit = 省 token + 快
const renderedSystemPrompt = parentContext.renderedSystemPrompt
```

## 5.5 Agent 定义加载

```typescript
// tools/AgentTool/loadAgentsDir.ts
export async function getAgentDefinitionsWithOverrides(cwd: string) {
  // 1. 扫描内置 Agent
  const builtInAgents = getBuiltInAgents()

  // 2. 扫描项目目录的 .claude/agents/
  const projectAgents = await loadAgentsDir(join(cwd, '.claude', 'agents'))

  // 3. 扫描用户目录的 ~/.claude/agents/
  const userAgents = await loadAgentsDir(join(getClaudeHomeDir(), 'agents'))

  // 4. 合并（优先级：项目 > 用户 > 内置）
  const allAgents = [...builtInAgents, ...userAgents, ...projectAgents]
  const activeAgents = getActiveAgentsFromList(allAgents)

  return { allAgents, activeAgents }
}
```

### AgentDefinition 类型

```typescript
export type AgentDefinition = {
  agentType: string                    // Agent 类型标识
  description: string                  // 描述
  getSystemPrompt: () => string       // 获取系统提示词
  model?: string                       // 使用的模型
  source: 'built-in' | 'user' | 'project' | 'flagSettings'
  memory?: string                      // 记忆文件路径
  tools?: string[]                     // 允许的工具
  initialPrompt?: string               // 初始提示词
}
```

## 5.6 Agent 系统范式

### 范式 1：上下文继承而非共享

```typescript
// 不好：直接共享父 Agent 的上下文
const childContext = parentContext  // 共享引用 → 状态污染

// 好：克隆上下文
const childContext = {
  ...parentContext,
  abortController: new AbortController(),  // 独立 abort
  setAppState: () => {},                     // no-op
  messages: parentMessages.map(m => ({ ...m })),  // 深拷贝消息
}
```

### 范式 2：ProgressMessage 通信

```typescript
// 子 Agent 通过 ProgressMessage 向父 Agent 报告进度
onProgress?.({
  toolUseID: parentMessage.toolUseID,
  data: { type: 'agent_progress', message: event },
})
```

### 范式 3：Prompt Cache 复用

```typescript
// 子 Agent 复用父 Agent 的渲染后系统提示词
// 这样 Anthropic 的 prompt caching 可以命中
const renderedSystemPrompt = parentContext.renderedSystemPrompt
```

### Checklist

- [ ] 子 Agent 有独立的 abortController
- [ ] 子 Agent 的 setAppState 是 no-op（不影响父 Agent）
- [ ] 消息历史用克隆而不是共享引用
- [ ] 通过 ProgressMessage 向父 Agent 报告进度
- [ ] 复用 renderedSystemPrompt 以利用 prompt caching

---

# 第六章：查询循环（Query Loop）

## 6.1 这解决什么问题

Query Loop 是整个 Claude Code 的心脏。它实现了 Agent 的核心循环：**用户输入 → API 调用 → 模型响应 → 工具执行 → 结果回填 → 继续**。这个循环持续到模型发出 Stop 指令。

## 6.2 query.ts 核心走读

query.ts 的核心是一个异步生成器函数，用 `while(true)` 循环反复执行：准备 API 请求 → 流式发送 → 处理响应 → 执行工具 → 结果回填。循环有三种退出条件：Stop（模型正常结束）、maxTurns（达到轮次上限）、error（API 错误超重试次数）。

它用 `async function*`（异步生成器）实现——调用方用 `for await` 实时接收每个事件，天然支持流式输出和背压控制。

> **完整代码走读（含逐行中文注释）见《Claude Code Agent 系统技术分析》第 2 章。** 那里有 query.ts 的依赖链、Generator 函数详解和 shouldStop 逻辑分析。这里我们把篇幅留给本章独有的内容：流式工具执行和 Query Loop 范式。

## 6.3 StreamingToolExecutor：流式工具执行

```typescript
// services/tools/StreamingToolExecutor.ts（简化）
export class StreamingToolExecutor {
  private runningTools = new Map<string, Promise<ToolResult<unknown>>>()
  private maxConcurrency: number

  async executeTool(
    toolUse: ToolUseBlock,
    context: ToolUseContext,
    canUseTool: CanUseToolFn,
    parentMessage: AssistantMessage,
  ): Promise<ToolResult<unknown>> {
    // 查找工具
    const tool = findToolByName(context.options.tools, toolUse.name)
    if (!tool) {
      return { data: { type: 'error', error: `Unknown tool: ${toolUse.name}` } }
    }

    // 检查权限
    const permissionResult = await canUseTool(toolUse, context)
    if (permissionResult.behavior !== 'allow') {
      return { data: { type: 'error', error: permissionResult.message } }
    }

    // 执行工具
    return tool.call(
      toolUse.input as z.infer<typeof tool.inputSchema>,
      context,
      canUseTool,
      parentMessage,
    )
  }

  async executeToolsParallel(
    toolUses: ToolUseBlock[],
    context: ToolUseContext,
    canUseTool: CanUseToolFn,
    parentMessage: AssistantMessage,
  ): Promise<Map<string, ToolResult<unknown>>> {
    const results = new Map<string, ToolResult<unknown>>()
    const concurrencySafe: ToolUseBlock[] = []
    const concurrencyUnsafe: ToolUseBlock[] = []

    // 分类：可并发 vs 不可并发
    for (const toolUse of toolUses) {
      const tool = findToolByName(context.options.tools, toolUse.name)
      if (tool?.isConcurrencySafe(toolUse.input)) {
        concurrencySafe.push(toolUse)
      } else {
        concurrencyUnsafe.push(toolUse)
      }
    }

    // 并发执行安全工具
    const safeResults = await Promise.all(
      concurrencySafe.map(t => this.executeTool(t, context, canUseTool, parentMessage))
    )
    concurrencySafe.forEach((t, i) => results.set(t.id, safeResults[i]))

    // 串行执行不安全工具
    for (const toolUse of concurrencyUnsafe) {
      const result = await this.executeTool(toolUse, context, canUseTool, parentMessage)
      results.set(toolUse.id, result)
    }

    return results
  }
}
```

**并发控制的核心：**

1. **分类**：根据 `isConcurrencySafe()` 将工具分为可并发和不可并发
2. **并发执行**：可并发的工具用 `Promise.all` 并行执行
3. **串行执行**：不可并发的工具按顺序执行
4. **结果收集**：用 Map 按 tool_use_id 收集结果

## 6.4 Query Loop 范式

### 范式 1：异步生成器驱动主循环

```typescript
// 不好：回调地狱
function query(params, onMessage, onTool, onError) { ... }

// 好：异步生成器
async function* query(params): AsyncGenerator<Message> {
  for await (const event of apiStream) {
    yield event
  }
}
```

### 范式 2：依赖注入

```typescript
// query.ts 的 deps 参数
type QueryDeps = {
  makeApiStream: (...) => AsyncGenerator<StreamEvent>
  runTools: (...) => AsyncGenerator<Message>
  // ...
}

// 生产环境用真实实现
const productionDeps: QueryDeps = { ... }

// 测试环境用 mock
const testDeps: QueryDeps = { ... }
```

### 范式 3：流式处理

```typescript
// 不好：等整个响应完成再处理
const response = await apiCall()
for (const block of response.content) { ... }

// 好：边接收边处理
for await (const event of apiStream) {
  if (event.type === 'content_block_delta') {
    // 实时渲染
  }
}
```

### Checklist

- [ ] 主循环用异步生成器实现
- [ ] 依赖通过 deps 参数注入
- [ ] 工具按 isConcurrencySafe 分类执行
- [ ] 流式处理 API 响应
- [ ] 错误处理有重试上限

## 第X章：实战案例 — 从零写一个简单 Agent

### 这解决什么问题

纸上谈兵不如动手。我们用 Claude Code 的架构思想，从零写一个简单的 Agent，让不懂代码的人也能理解 Agent 系统是怎么跑起来的。

### 核心概念

一个 Agent 只有三件事：
1. 看（读取输入）
2. 想（调用大模型）
3. 做（执行工具）

就像你让一个实习生干活：先看你的需求（看），想一想该怎么办（想），然后去做（做），做完再回来汇报（循环）。

### 代码实现（逐行注释）

```typescript
// 文件：my-agent.ts

// 1. 定义一个工具——Agent 的"手"
const fileReadTool = {
  name: 'read_file',          // 工具名：告诉模型这是什么
  description: '读取文件内容',  // 工具描述：告诉模型什么时候用
  parameters: {               // 参数定义：告诉模型怎么传参
    type: 'object',
    properties: {
      path: { type: 'string', description: '文件路径' }
    }
  },
  // 实际执行：工具真正干活的地方
  execute: async (params: { path: string }) => {
    return await readFile(params.path)  // 读文件，返回内容
  }
}

// 2. Agent 主循环——"看 → 想 → 做"循环
async function agentLoop(userMessage: string) {
  const messages = [{ role: 'user', content: userMessage }]

  while (true) {  // 不断循环，直到模型说"我做完了"
    // 想：把消息和工具发给大模型
    const response = await callClaude({
      messages: messages,
      tools: [fileReadTool]   // 告诉模型你有哪些工具
    })

    // 检查：模型有没有调用工具？
    if (!response.hasToolCall) {
      // 没有工具调用 = 任务完成，模型给了最终答案
      console.log(response.text)
      break
    }

    // 做：执行模型要求的工具
    const result = await fileReadTool.execute(response.toolParams)

    // 看：把结果加回消息列表，下一轮模型会看到
    messages.push({
      role: 'tool',
      content: result
    })
    // ↑ 循环继续，模型看到结果后决定下一步
  }
}

// 3. 启动！
agentLoop("请读一下 README.md 的内容")
```

### 这就是全部了

一个 Agent 就是这个循环的不断重复：
1. 模型看消息 → 2. 模型决定调工具 → 3. 执行工具 → 4. 结果回给模型 → 回到第 1 步

Claude Code 的复杂性来自于：
- 工具更多（40+ 个）→ 需要权限管理
- 循环更长 → 需要上下文压缩
- 一个不够 → 需要子 Agent
- 要跑得快 → 需要并发控制、prompt 缓存

但核心就这三件事：看、想、做。

### 学习路径

- 想深入理解 Agent 架构 → 读 `query.ts`（核心循环）、`Tool.ts`（工具定义）
- 想学工具系统 → 从 `tools/FileReadTool/` 开始，这是最简单的工具
- 想学子 Agent → 读 `tools/AgentTool/runAgent.ts`
- 想动手练 → 给这个简单 Agent 加第二个工具（比如写文件），然后试试让它做个小任务
- 想理解安全机制 → 读 `tools/BashTool/bashPermissions.ts`（权限检查）和 `tools/BashTool/bashSecurity.ts`（命令安全分析）。这两个文件比较大，建议先读 bashPermissions 的前 200 行理解"什么时候允许、什么时候拒绝、什么时候问用户"的决策流程