大家好,我是子昕。
前两天Claude Code的源码泄露后,我花了两天时间,拆解了其完整源码——29 个子系统、184 个工具文件、100 多个命令。
这不是泛泛而谈的介绍,而是一份带着具体数字和函数名的深度报告,分享给你们。
29 子系统 · 43 工具组 · 100+ 命令 · 7 阶段启动 · 6 层压缩
这篇文章适合谁
所有使用 Claude Code 的人——无论你是产品经理、设计师还是开发者。本文用生活比喻解释每个概念,同时在“打开引擎盖”区域保留源码级细节。
你不需要懂 TypeScript。遇到技术术语时,会在括号里用大白话解释。
读完你会知道:为什么 Claude 有时候“忘记”之前说的话、为什么有时候弹确认框、为什么有时候启动慢、怎么省钱、怎么用好 100 多个命令。
数据来源:本报告基于 2026 年 3 月 31 日公开的 Claude Code TypeScript 归档源码,以及社区对其的 Python/Rust 重写项目的交叉验证。核心数字(如压缩阈值、Token 估算公式、CLAUDE.md 字符限制等)已在 Rust 重写源码中逐一确认。
01 架构全景:这不是一个聊天工具
你以为 Claude Code 只是一个命令行版的 ChatGPT?它的源码告诉你,它其实是一个有 29 个子系统的 AI 操作系统——复杂程度堪比一个小型 IDE。
Claude Code 用 TypeScript + Bun(一个比 Node.js 更快的 JS 运行时)开发,用 React/Ink(用写网页的方式写命令行界面——想象用 React 画终端 UI)构建交互界面。29 个子系统分 5 层。
UI 层 — 你看到的一切
| components | screens | keybindings | vim | voice |
|---|---|---|---|---|
| 389 | 3 | 14 | 5 | 1 |
运行时核心 — 大脑和神经系统
| services | hooks | state | bootstrap | entrypoints |
|---|---|---|---|---|
| 130 | 104 | 6 | 1 | 8 |
工具与扩展 — 手和脚
| skills | plugins | memdir | bridge |
|---|---|---|---|
| 20 | 2 | 8 | 31 |
远程与协作 — 联网能力
| remote | server | coordinator | upstreamproxy |
|---|---|---|---|
| 4 | 3 | 1 | 2 |
基础设施 — 地基和管道
| utils | constants | types | schemas | migrations |
|---|---|---|---|---|
| 564 | 21 | 11 | 1 | 11 |
🔧 打开引擎盖
- 最大的模块是
utils(564 个文件)——相当于一个工具箱,里面装满了字符串处理、文件操作、网络请求等基础函数。 hooks有 104 个。在 React 里,hook 是“状态变化时自动执行的函数”(类似 Java 的 @EventListener)。104 个 hook 说明这个系统的状态管理非常精细。bridge(31 个文件)是 TypeScript 和底层原生代码之间的桥梁——类似 Java 的 JNI,用来调用操作系统级别的功能(音频、图片处理、快捷键等)。entrypoints(8 个)= 8 种启动方式:命令行、API 服务器、SSH 远程、浏览器直连等。同一套核心代码,8 种运行形态。
5 个关键架构判断
- 消息驱动 — 用户输入、AI 回复、工具执行结果、系统通知,全部统一为 Message 对象。transcript(对话记录)是整个系统的核心数据结构,类似数据库的 WAL 日志。
- Prompt Cache 优先 — 系统提示被分成“不变的部分”和“每次都变的部分”,中间用
DYNAMIC_BOUNDARY(动态边界线)隔开。不变的部分会被缓存(类似 Redis 缓存查询结果),下次请求直接复用,省钱。 - 4 层扩展叠加 — Command(命令)+ Skill(技能)+ Plugin(插件)+ MCP(外部协议)四层互不冲突,让 Claude Code 从“工具”变成“平台”。
- Agent 一等公民 — 子代理不是简单的函数调用,而是拥有独立的工具集、模型选择、权限和 Git 工作区的完整运行时。
- 技术栈 — TypeScript + Bun(高性能 JS 运行时)+ React/Ink(终端 UI 框架)+ Zod v4(数据校验,类似 Java 的 Bean Validation)+ @anthropic-ai/sdk + MCP SDK
02 七阶段启动:从敲下命令到就绪
为什么 Claude Code 第一次启动要等几秒?因为它在背后做了 7 件事——有些是为了快(并行加载),有些是为了安全(信任检查)。
① 预取副作用[并行]MDM 读取、Keychain 预取、项目扫描——三件事同时做,不互相等待
② 环境守卫[安全]检查运行环境:Bun 版本对不对?权限够不够?有没有异常?
③ CLI 解析 + 信任门[信任门]解析你输入的参数,然后过“信任门”——不信任的环境下,插件和 MCP 都不会加载
④ 并行加载[并行]setup() 和 commands/agents 同时加载,进一步缩短等待时间
⑤ 延迟初始化[安全隔离]信任通过后才初始化“有风险”的部分:plugin、skill、MCP 预取、session hooks
⑥ 模式路由[路由]分发到 6 种运行模式:本地 / 远程 / SSH / teleport / 直连 / 深度链接
⑦ 查询引擎就绪[就绪]QueryEngine 进入循环,等待你的第一条输入
🔧 打开引擎盖
- 信任门(trust gate)在第 3 步。类比:就像 Android 的“未知来源应用”开关——关闭时,第三方插件和 MCP 服务器都不会被加载。
- 第 4 步和第 5 步的分界很关键:setup() 是“安全无关”的基础设施(类似 Spring 的 @Configuration);plugins/MCP 是“有风险”的扩展(类似 Spring 的 @Import 外部 jar),必须等信任通过后才执行。
- 6 种运行模式共享同一套核心逻辑,通过依赖注入切换传输层——类似 Spring 的 Profile 机制。
实用建议
- 启动慢?大概率是 MCP 服务器卡住了。用
/mcp命令检查状态。 - 第一次在新项目运行会触发项目扫描(第 1 步),后续有缓存会快很多。
02+ Claude 怎么读你的项目
每次对话开始前,Claude 会默默扫描你的项目——读取配置、检查 Git 状态、加载你写的规则文件。理解这个过程,你就能精确控制 Claude 的行为。
这是 Claude Code 最实用但最少被介绍的机制。你在 CLAUDE.md 里写的每一条规则,都会被注入到 Claude 的系统提示中——永远不会被压缩丢弃。
CLAUDE.md 发现逻辑
// 从当前目录开始,逐级向上搜索到根目录 /
// 每个目录检查 4 个路径:
for dir in [当前目录, 父目录, 祖父目录, ... , /] {
检查 dir/CLAUDE.md // 项目规则(提交到 Git)
检查 dir/CLAUDE.local.md // 本机规则(不提交 Git)
检查 dir/.claude/CLAUDE.md // 隐藏目录版
检查 dir/.claude/instructions.md // 别名
}
// 去重:对每个文件内容计算哈希,相同内容只保留第一个
// 单文件上限:4,000 字符 总预算:12,000 字符
// 超出部分标注 [truncated]
实用建议
- 项目根目录的 CLAUDE.md 是最重要的——它对所有子目录生效。
- 个人偏好(如用中文回复)写在
~/.claude/CLAUDE.md,不会影响团队。 - 敏感的本地配置写在
CLAUDE.local.md——它不会被提交到 Git。 - 4000 字符约等于 80 行简洁的规则。写不下说明你写太啰嗦了。
推荐的 CLAUDE.md 模板
# 项目:你的项目名
## 概述
一句话描述项目做什么。技术栈:TypeScript + React + PostgreSQL
## 规则
- 所有回复用中文
- 代码注释用英文
- 提交信息格式:type(scope): description
- 不要添加不必要的注释和文档
## 关键路径
- src/core/ — 核心业务逻辑
- src/api/ — REST 接口层
- src/db/ — 数据库迁移和模型
## 约定
- 测试文件放在 __tests__/ 目录
- 环境变量用 .env.example 记录
- TODO: 和 FIXME: 标记待办事项
🔧 打开引擎盖
- Git 状态通过
git --no-optional-locks status --short --branch获取,结果附加到系统提示中。 - 去重算法:先将内容的空白规范化(合并连续空行),再用
DefaultHasher计算 u64 哈希。 - CLAUDE.md 内容被包装在
# Claude instructions节中,每个文件带路径和作用域标注。 - 加载顺序是从根目录到当前目录(先祖先后子孙),去重时先到的优先。
03 对话循环:每句话背后的流水线
为什么 Claude 有时候来回折腾好几轮才给答案?为什么有时候突然停下来说“让我读一下这个文件”?因为每次对话都是一条流水线在运转。
QueryEngine(查询引擎) 是整个对话系统的心脏。它控制着每一次 AI 请求的生命周期——从你按下回车,到看见回复。
| 参数 | 值 | 说明 |
|---|---|---|
| max_turns | 8 | TS版8轮 / Rust版16轮 |
| max_budget_tokens | 2,000 | 单次预算上限 |
| compact_after_turns | 12 | 超过 12 轮自动压缩 |
端到端数据流
你输入 → CLI 解析 → QueryEngine 组装请求
(系统提示 + 历史消息 + 工具清单 → 一个 JSON)
→ HTTP POST 到 Claude API → SSE 流式返回(逐字推送,不是等写完才发)
→ 检测 tool_use(工具调用信号:Claude 说"我需要执行一个操作")
├── 有 tool_use → 权限检查 → 执行工具 → 结果送回 → 下一轮
└── 无 tool_use → 对话结束 ✓
// 3 种终止条件:
正常结束 — Claude 不再需要工具
强制停止 — 达到 max_turns(8) 上限
预算耗尽 — 超过 max_budget_tokens(2000)
run_turn() 核心循环伪代码
async function run_turn(userInput) {
session.push(UserMessage(userInput))
for (let turn = 0; turn < max_turns; turn++) {
const events = await api.stream(systemPrompt, session.messages)
const msg = buildMessage(events) // TextDelta 累积 + ToolUse 拼装
session.push(msg)
const tools = extractToolUses(msg)
if (tools.length === 0) break// 正常结束
for (const tool of tools) {
const result = await orchestrator.execute(tool)
session.push(ToolResult(result))
}
if (shouldAutoCompact(session)) autoCompact(session)
}
}
🔧 打开引擎盖
- System Prompt(系统提示)组装:静态前缀 +
DYNAMIC_BOUNDARY(动态边界线)+ 动态上下文。边界线的作用:让静态部分命中 Prompt Cache(提示词缓存),下次请求不用重新计算——直接省钱。类比:就像把 SQL 查询的固定部分做成 PreparedStatement。 - Token 估算公式(Rust 版):
字符数 ÷ 4 + 1。不精确,但省去了调用 tokenizer 的开销。 - SSE 帧格式:
event: content_block_delta\ndata: {"type":"text_delta","text":"Hello"}。特殊事件ping和[DONE]会被静默跳过。 - 遇到 HTTP
429/500/502/503/504会自动重试,指数退避(exponential backoff):200ms → 400ms → 800ms,上限 2 秒,最多重试 2 次。类比:就像打电话占线,等一会再打,每次等更久一点。
04 六层上下文压缩:AI 的选择性遗忘
聊了很久之后,Claude 突然“忘记”了你之前说的话?不是 Bug——是 6 层压缩策略在工作。就像笔记本快写满了,秘书会把旧内容浓缩成摘要,只保留最近几页的详细笔记。
第 1 层:tool_result 预算截断工具返回的结果太长?按 Token 数截断,超长部分变成 [snipped]。最轻量的一层,实时发生。 类比:快递单太长,只保留前半页。
第 2 层:snip compact(旧结果清理) 更早之前的工具结果,整个替换为 [snipped] 标记。消息结构保留,只丢弃已不重要的内容。 类比:会议记录里,把上周的附件删掉,只留标题。
第 3 层:microcompact(缓存修复) 修复 Prompt Cache(提示词缓存)的基线。当缓存被破坏时,通过轻量压缩恢复基线,避免每次请求都重新计算。 类比:Redis 缓存失效后,重新预热而不是全量重建。
第 4 层:autoCompact(自动压缩) 消息数超过 12 轮时自动触发。保留最后 N 条消息,前面的生成摘要。 源码:if messages > compact_after_turns(12)
第 5 层:reactiveCompact(紧急压缩) API 返回 context_length_exceeded(上下文超长)错误时,立即压缩并重试。这是安全网。 类比:硬盘满了,紧急清理临时文件。
第 6 层:context collapse(全量重置) 最后手段:在 compact boundary(压缩断点)处重置。下次 /resume 时从断点恢复,不需要重放全部历史。 类比:数据库崩溃后从最近的 checkpoint 恢复。
压缩时到底保留了什么
summarize_messages() 函数精确提取 7 类信息:
- 消息统计 — 压缩了多少条消息(user/assistant/tool 分别几条)
- 工具清单 — 用过哪些工具(去重+排序)
- 最近 3 条用户请求 — 你最近问了什么(每条截断 160 字符)
- 待办事项 — 搜索含
TODO``NEXT``PENDING``FOLLOW UP``REMAINING的文本 - 关键文件路径 — 提取含
/且扩展名为.rs .ts .tsx .js .json .md的路径,最多 8 个 - 当前工作推断 — 最后一条非空文本块(截断 200 字符)
- 完整时间线 — 每条消息的角色+内容摘要(每块截断 160 字符)
实战意义:想让 Claude 在压缩后记住某件事?用 TODO: 或 NEXT: 前缀写出来。想让它记住某个文件?在对话中提到完整路径(如 src/api/auth.ts)。
🔧 打开引擎盖
- Rust 版的压缩触发条件更简单:
消息数 > 4 AND 估算 Token ≥ 10,000,两个条件必须同时满足。 - 摘要包含 6 类信息:消息统计、使用过的工具列表、最近 3 条用户请求、待办事项(搜索
TODO/NEXT/PENDING/FOLLOW UP/REMAINING关键词)、关键文件路径(只认.rs/.ts/.tsx/.js/.json/.md6 种扩展名,最多 8 个)、当前工作推断。 - 每段摘要截断到
160 字符。CLAUDE.md 单文件最大4,000 字符,总预算12,000 字符。
实用建议
- 主动用
/compact比等自动压缩更可控。重要信息尽早写进 CLAUDE.md——它不会被压缩。 - 如果你在对话中有重要待办,用 TODO 或 NEXT 明确标记——压缩算法会专门搜索这些关键词。
- 遇到
context_length_exceeded错误不用慌,第 5 层会自动处理。
05 四层扩展体系:从工具到平台
为什么 Claude Code 能集成 GitHub、Slack、数据库?因为它不是一个工具,而是一个平台。4 层扩展体系让它可以无限扩展能力。
第 1 层:Commands(命令)— 100+ 个
硬编码的 TypeScript 函数,执行速度最快。包括基础命令(/help, /compact)和高级命令(/ultraplan, /review)。 类比:Java 里的 @Controller 方法——直接写死在代码里,改了要重新编译。
第 2 层:Skills(技能)— 20 模块
Markdown 文件 + 条件匹配,比 Command 更灵活。比如 /commit 只在 Git 仓库中才可用。 类比:Spring 的 @ConditionalOnProperty——满足条件才激活。本质是结构化的 prompt 模板。
第 3 层:Plugins(插件)— 能力包
最强大的扩展:一个 Plugin 可以同时注册命令、技能、hook、MCP 服务器、甚至 Agent。支持 Marketplace 市场。 类比:Spring Boot Starter——一个依赖引入一整套能力。100 多个命令中约 61 个来自 Plugin。
第 4 层:MCP(外部协议)— 5 种传输
Model Context Protocol——让 Claude 接入任何外部服务。支持 stdio / SSE / HTTP / WebSocket / claudeai-proxy 五种传输方式。 类比:JDBC 让 Java 连接任何数据库,MCP 让 Claude 连接任何服务。
🔧 打开引擎盖
- MCP 工具命名规则:
mcp__{服务器名}__{工具名},如mcp__github__create-pr。双下划线是分隔符。名称规范化:只保留a-z A-Z 0-9 _ -,其他字符替换为_。 - MCP 通信用 HTTP 风格的
Content-Length帧:Content-Length: 127\r\n\r\n{"jsonrpc":"2.0",...}。为什么不用换行分割?因为 JSON 内容本身可能包含换行符,会导致解析错误。Content-Length 先告诉你有多少字节,再精确读取——和 HTTP 协议同一个思路。 - 目前只有
Stdio 传输(通过子进程的 stdin/stdout 通信)真正完整实现了。其他 4 种传输方式会被记录为“不支持”但不报错——优雅降级。
为什么这样设计
4 层互不冲突是关键决策。Command 处理快速内置操作,Skill 处理可配置的 prompt 流程,Plugin 打包完整的产品能力,MCP 接入无限的外部世界。这让 Claude Code 从“一个命令行工具”变成了“一个可编程的 AI Agent 平台”。
06 工具运行时:43 个工具组
184 个工具文件,分成 43 个工具组。每个工具必须实现统一协议:call(执行)+ checkPermissions(权限检查)+ render(渲染结果)+ isReadOnly(是否只读)。类比 Java 的接口:所有工具都实现同一个 Tool interface。
核心工具 — 几乎每次对话都会用到
| 工具 | 文件数 | 功能 |
|---|---|---|
| BashTool | 18 | 执行终端命令,支持超时和后台运行 |
| FileReadTool | 5 | 读取文件,支持按行分页 |
| FileEditTool | 6 | 精准字符串替换(不是整文件覆盖) |
| FileWriteTool | 3 | 创建或覆盖文件 |
| GlobTool | 3 | 按文件名模式搜索(如 *.java) |
| GrepTool | 3 | 正则搜索文件内容(类似 grep 命令) |
| WebFetchTool | 5 | 抓取网页内容 |
| WebSearchTool | 3 | 互联网搜索 |
AI 代理 — Claude 可以启动“分身”并行工作
| 工具 | 文件数 | 功能 |
|---|---|---|
| AgentTool | 20 | 最大工具,含 7 种内置 Agent |
| SendMessageTool | 4 | Agent 之间互相发消息 |
| TeamCreate/Delete | 8 | 创建和解散 Agent 团队 |
任务管理 — 跟踪多步骤工作进度
| 工具 | 文件数 | 功能 |
|---|---|---|
| TaskCreate/Get/List/Update/Stop | 17 | 完整的任务 CRUD |
| TodoWriteTool | 3 | 管理待办清单 |
其他 — 规划、配置、笔记本、定时任务等
| 工具 | 文件数 | 功能 |
|---|---|---|
| EnterPlanModeTool | 4 | 进入规划模式(只看不做) |
| LSPTool | 6 | 语言服务协议(代码补全、跳转定义) |
| ScheduleCronTool | 5 | 定时任务调度 |
| PowerShellTool | 14 | Windows PowerShell 支持 |
🔧 打开引擎盖
AgentTool是最大的单个工具(20 个文件!),内含 7 种内置 Agent:codeGuideAgent(代码导航)、exploreAgent(代码探索)、generalPurposeAgent(通用)、planAgent(规划)、verificationAgent(验证)等。每个子 Agent 有独立的工具集和权限。StreamingToolExecutor(流式工具执行器)支持并发执行多个工具 + 实时进度渲染。但读写操作会被分组——不会同时读和写同一个文件。- 工具注册顺序是稳定的(stable ordering)。为什么?因为工具列表是系统提示的一部分,顺序变了 Prompt Cache 就失效了——又是省钱的设计。
glob_search结果按修改时间倒序排列,硬截断 100 个。edit_file如果找不到要替换的文字会直接报错,不会静默跳过。
07 权限系统:三道门保护你
为什么有时候 Claude 突然弹出确认框?为什么有时候直接拒绝?因为每个操作都要过权限检查。
🟢 ReadOnly(只读)
只能看不能碰。读文件、搜索、浏览网页。
🟡 WorkspaceWrite(工作区写入)
可以修改项目文件。写入、编辑、管理任务。
🔴 DangerFullAccess(完全访问)
可以执行任何命令。运行终端、安装软件。
🔧 打开引擎盖
- 没注册的工具默认需要最高权限
DangerFullAccess——宁可多拦不能漏放。 - denial 追踪:
deniedTools.add(toolId)防止"请求→拒绝→再请求"死循环。 - 规则引擎支持 glob 匹配:
bash:rm *拦截所有含 rm 的命令。 - 只有一条升级路径:WorkspaceWrite → DangerFullAccess。ReadOnly 无法直接跳级。
08 持久化:AI 的记忆系统
8 层持久化,从进程内状态到跨会话长期记忆。类比:从 CPU 寄存器到硬盘,计算机的存储也是分层的。
| 层级 | 模块 | 说明 |
|---|---|---|
| ⚡ | bootstrap/state.ts | 进程级全局状态。类比 Java 的 static 变量。 |
| 🗄️ | AppStateStore | Zustand 式状态管理(类似 Redux 但更轻量),6 个文件。 |
| 📜 | sessionStorage.ts | JSONL 格式(每行一个 JSON 对象)。/resume 恢复的核心。 |
| ⌨️ | history.ts | 输入历史,按上下箭头翻历史命令。 |
| 📌 | compact boundary | 压缩断点。/resume 从断点恢复。类比数据库 checkpoint。 |
| 📷 | fileHistory | 文件变更快照,可回退。内容哈希去重。 |
| 🧠 | memdir/MEMORY.md | 跨会话记忆目录,8 个文件,有年龄管理。 |
| 📊 | analytics/OTel | OpenTelemetry + Datadog 监测。 |
🔧 打开引擎盖
- JSONL 优势:追加写入、崩溃数据不丢、可尾部读取。
compact_boundary是特殊行——/resume 遇到就停止向前读。- 凭证采用原子写入:先写
.tmp再rename,崩溃也不损坏文件。 - base64url 是手写实现:字母表用
-_代替+/,不加填充=。
09 成本定价与命令速查
源码硬编码了模型定价。了解这些数字可以立刻省钱。
| 模型 | 输入 | 输出 | 缓存写 | 缓存读 |
|---|---|---|---|---|
| Haiku | $1 | $5 | $1.25 | $0.10 |
| Sonnet (默认) | $15 | $75 | $18.75 | $1.50 |
| Opus | $15 | $75 | $18.75 | $1.50 |
单位:$/百万Token。模型识别用 includes("haiku") 字符串匹配。
省钱技巧
- 输出价格是输入的 5 倍——让 Claude 少废话比减少输入更省钱
- 简单任务用
/model haiku,输出价格只有 Sonnet 的 1/15 - 用
/compact压缩上下文——输入 Token 少了就便宜了
一次对话到底花多少钱
// 假设一次典型开发对话:20 轮,包含文件读写
Sonnet 模型:
输入 ~50K tokens × $15/M = $0.75
输出 ~5K tokens × $75/M = $0.375
缓存命中 ~40K tokens × $1.50/M = $0.06 // 省了不少
合计 ≈ $1.19 / 次对话
Haiku 模型(同样对话):
输入 ~50K tokens × $1/M = $0.05
输出 ~5K tokens × $5/M = $0.025
合计 ≈ $0.08 / 次对话
// 差距 15 倍。简单任务用 Haiku,复杂任务用 Sonnet
// 一天 20 次对话:Sonnet ≈ $24 / Haiku ≈ $1.6
20 个常用命令
基础
| 命令 | 功能 |
|---|---|
/help | 列出命令 |
/status | 会话状态 |
/clear | 清空会话 |
/exit | 退出 |
对话
| 命令 | 功能 |
|---|---|
/compact | 压缩上下文 |
/resume | 恢复会话 |
/export | 导出记录 |
/session | 管理会话 |
开发
| 命令 | 功能 |
|---|---|
/diff | Git 变更 |
/commit | Git 提交 |
/review | 代码审查 |
/init | 创建 CLAUDE.md |
配置
| 命令 | 功能 |
|---|---|
/model | 切换模型 |
/permissions | 切换权限 |
/config | 查看配置 |
/memory | 查看指令 |
高级
| 命令 | 功能 |
|---|---|
/ultraplan | 深度规划 |
/teleport | 远程连接 |
/buddy | 协作模式 |
/plugin | 管理插件 |
深潜区:更多源码级细节
以下内容为进阶读者准备。如果你觉得前面的内容“意犹未尽”,这里有你想要的全部深度。
系统提示的 10 层结构
每次对话前,Claude 收到的“隐藏剧本”由 10 个部分严格按顺序拼接。中间用 DYNAMIC_BOUNDARY(动态边界线)隔开——线上面的部分走缓存(省钱),线下面的每次都重新生成。
// 固定部分(走 Prompt Cache)
1. 角色介绍 — "你是 Claude Code,Anthropic 的官方 CLI 工具"
2. 输出风格 — 自定义风格(如果配置了)
3. 系统规则 — 安全边界、行为准则
4. 任务指南 — 如何处理编程任务
5. 操作规范 — 谨慎执行,避免破坏性操作
═══ DYNAMIC_BOUNDARY ═══ 以下每次都变
6. 环境信息 — 操作系统、工作目录、日期
7. 项目上下文 — Git 分支和文件状态
8. CLAUDE.md 指令 — 你写的项目专属规则
9. 运行时配置 — 已加载的配置文件
10. 附加段落 — MCP 工具描述等
流式拼图:回复怎么一个字一个字拼出来
Claude 的回复通过 SSE(Server-Sent Events)逐字推送。关键:ToolUse 事件会打断正在累积的文字。
// build_assistant_message() 核心逻辑:
TextDelta("让") → text = "让"
TextDelta("我来") → text = "让我来"
TextDelta("看看") → text = "让我来看看"
ToolUse { name: "read_file" } → ⚡ flush! text → Text块
→ 新增 ToolUse块 { read_file }
// ...工具执行,结果返回,下一轮...
TextDelta("文件") → text = "文件"
MessageStop → ⚡ flush! text → Text块
// 最终: [Text"让我来看看", ToolUse{read_file}, Text"文件内容..."]
工具编排层:toolOrchestration.ts
工具不是一个个串行执行的——编排层负责安全分组和并行执行。类比:Java 的线程池 + 读写锁。
- 安全分组:读操作可以并行(同时读多个文件),但写操作必须串行(不能同时写同一个文件)。类比:数据库的读写锁。
- StreamingToolExecutor:支持流式并发执行 + 实时进度渲染。你看到的“正在读取文件...”进度条就是它渲染的。
- Schema 校验:每个工具调用前,用
Zod v4(类似 Java 的 Bean Validation)校验输入参数。格式不对直接拒绝,不会传给工具。 - OTel 链路追踪:每次工具执行都有 OpenTelemetry span,可以在 Datadog 里看到每个工具的耗时。
MCP 握手:6 步建立连接
1. CLI 启动服务器子进程 // 执行 command + args,设置独立环境变量
2. 发送 initialize 请求 // 协议版本 "2025-03-26",客户端自称 "runtime"
3. 服务器回复能力清单 // initialized = true
4. CLI 发送 tools/list // 支持 cursor 分页,循环获取直到 next_cursor 为 null
5. 每个工具加前缀 // mcp__{server}__{tool},同时记录原始名用于调用
6. 就绪 // 后续调用共享同一进程,请求 ID 从 1 递增(saturating_add 防溢出)
authorize() 权限检查伪代码
function authorize(toolName, currentMode) {
required = toolRequirements[toolName]
?? DangerFullAccess // 没注册 = 最高权限
if (currentMode >= required) return Allow // 够了就过
if (current === WorkspaceWrite
&& required === DangerFullAccess)
return prompter.ask_user() // 弹确认框
return Deny // 差距太大直接拒绝
}
OAuth PKCE 登录:从浏览器到命令行
为什么命令行能通过浏览器完成登录?背后是 PKCE(Proof Key for Code Exchange)协议——一套密码学握手。
1. CLI 读取 /dev/urandom 32 字节 → base64url 编码 → "暗号"(verifier)
2. SHA-256(暗号) → "挑战码"(challenge)
3. 打开浏览器 → 带着挑战码去授权页面
4. 用户点"允许" → 浏览器跳转 localhost:{port}
5. CLI 本地监听 → 收到授权码
6. 授权码 + 原始暗号 → 换取令牌
7. 令牌保存到 ~/.claude/credentials.json
- base64url 是手写实现:字母表用
-_代替+/,不加填充= - 凭证采用原子写入:先写
.tmp再rename,崩溃也不损坏文件 - 过期检测:
expires_at <= 当前时间戳。有 refresh_token 自动刷新,没有要求重新登录
配置系统:5 层洋葱
配置像洋葱——一层包一层,后加载的覆盖先加载的。类比:CSS 的层叠规则,或 Spring 的 Property 优先级。
// 优先级从低到高:
1. ~/.claude.json // 全局默认(兼容旧版)
2. ~/.claude/settings.json // 个人偏好
3. 项目/.claude.json // 项目共享(提交到 Git)
4. 项目/.claude/settings.json // 项目设置(新格式)
5. 项目/.claude/settings.local.json // 本机专属(不提交 Git)
- 合并策略是
deep merge:后者只覆盖冲突键。A 设了 model 和 theme,B 只设了 model,合并后 B 的 model 生效,A 的 theme 保留。 - 权限字符串映射:
default/plan/read-only→ ReadOnly,acceptEdits/auto→ WorkspaceWrite,dontAsk→ DangerFullAccess - 环境变量
CLAUDE_CONFIG_HOME可覆盖默认的~/.claude目录
会话存储格式(JSONL)
{"role":"user","content":"帮我读一下 README","ts":1710000001}
{"role":"assistant","content":[{"type":"text","text":"让我读一下"},{"type":"tool_use","id":"t1","name":"FileReadTool"}],"ts":1710000002}
{"role":"tool","content":[{"type":"tool_result","tool_use_id":"t1","content":"# My Project..."}],"ts":1710000003}
{"role":"assistant","content":[{"type":"text","text":"文件内容是..."}],"usage":{"input_tokens":1200,"output_tokens":45},"ts":1710000004}
{"type":"compact_boundary","summary":"对话已压缩...","ts":1710000005}
29 子系统深潜
BUDDY(虚拟宠物系统)
- 6 个文件实现了一个终端里的 Tamagotchi(电子宠物)。有
CompanionSprite.tsx(精灵动画,500ms 帧率)、companion.ts(18 种情绪状态)、prompt.ts(宠物对话 prompt)。 - 用
/buddy命令激活。宠物会根据你的编码状态变化情绪——写 Bug 时它会伤心。
COORDINATOR(多 Agent 协调器)
- 1 个文件
coordinatorMode.ts,负责多个 Agent 之间的任务分配和结果汇总。 - 当你用
/ultraplan启动深度规划时,Coordinator 会拆分任务、分发给多个子 Agent、收集结果、合并答案。
BRIDGE(原生能力桥接)
- 31 个文件,连接 TypeScript 和底层原生代码。类似 Java 的 JNI。
- 核心能力:
bridgeApi.ts(API 桥接)、bridgeConfig.ts(配置桥接)、bridgeDebug.ts(调试桥接)。通过vendored/目录引入 Rust 编译的原生模块(音频处理、图片处理、键盘修饰符、URL 处理)。
VIM 模式
- 5 个文件实现了终端内的 vim 操作:
motions.ts(光标移动)、operators.ts(操作符如 d/y/c)、textObjects.ts(文本对象如 iw/ap)。 - 用
/vim命令切换。不是完整的 vim,但支持常用的 hjkl 移动、dd 删行、yy 复制等。
VOICE(语音输入)
- 1 个文件
voiceModeEnabled.ts,控制语音输入模式的开关。 - 语音识别通过 bridge 层调用原生音频模块,转成文字后送入 QueryEngine。
MIGRATIONS(数据迁移)
- 11 个文件,每个负责一种配置迁移。例如:
migrateAutoUpdatesToSettings.ts(自动更新迁移)、migrateBypassPermissionsAcceptedToSettings.ts(权限旁路迁移)。 - 类比:数据库的 Flyway 迁移脚本。确保旧版本的配置能平滑升级到新版本格式。
SERVICES(130 个核心服务文件)
- 这是运行时最大的子系统之一。核心服务包括:
services/api/claude.ts— Claude API 调用:请求组装(beta header + tool schema + prompt cache 配置)、认证头管理、响应解析services/AgentSummary/— Agent 执行结果摘要生成services/MagicDocs/— 智能文档查询(根据代码上下文自动查找相关文档)- 还包括:通知服务、订阅管理、用量统计、安全审计等。
HOOKS(104 个状态钩子)
- 分为三大类:UI 钩子(组件状态更新)、API 钩子(请求前后拦截)、通知钩子(如
useAutoModeUnavailableNotification、useCanSwitchToExistingSubscription)。 - 类比:Spring 的 AOP 拦截器 + Event Listener。每个钩子监听特定状态变化,触发对应的副作用。
- 文件建议功能
fileSuggestions.ts也是一个 hook——当你输入文件路径时自动补全。
REMOTE + UPSTREAM PROXY
remote/(4 个文件):RemoteSessionManager.ts(远程会话管理)、SessionsWebSocket.ts(WebSocket 连接维护)、remotePermissionBridge.ts(远程权限桥接——在远程机器上执行命令时,权限确认回传到本地)。upstreamproxy/(2 个文件):relay.ts(请求中继)和upstreamproxy.ts(上游代理配置)。用于企业环境中 API 请求需要经过代理服务器的场景。
OUTPUT STYLES + KEYBINDINGS
outputStyles/(1 个文件):loadOutputStylesDir.ts从~/.claude/output-styles/目录加载自定义输出风格(Markdown 渲染主题)。keybindings/(14 个文件):完整的快捷键系统。defaultBindings.ts定义默认快捷键,KeybindingContext.tsx管理快捷键上下文(不同模式下快捷键不同)。
Git Worktree 隔离
- 当子 Agent 需要修改代码时,不是直接在你的工作目录操作,而是创建一个独立的
Git worktree(Git 工作树——同一个仓库的另一个检出目录)。 - 好处:子 Agent 的修改不会干扰你正在编辑的文件。完成后通过 merge 合并回来。类比:Git 的 feature branch,但是在文件系统层面隔离。
- 用
EnterWorktreeTool进入,ExitWorktreeTool退出。
错误处理策略
- 不只是 reactiveCompact。源码有多种错误变体:
ApiError(8+ 种:认证失败、限流、上下文超长、网络超时等)、ToolError(权限拒绝、参数校验失败、执行超时)、SessionError(文件损坏、版本不兼容)。 - 每种错误有对应的恢复策略:认证失败 → 自动刷新令牌;限流 → 指数退避重试;上下文超长 → reactiveCompact;文件损坏 → 从 compact_boundary 恢复。
- 用户可见的错误会通过 hooks 系统触发通知(toast 弹窗),不会让程序崩溃。
常见困惑速查
为什么 Claude 突然忘记了之前说的话?
不是 Bug。当对话超过 12 轮(或 Token 估算 ≥ 10,000),自动压缩会触发。旧消息被浓缩为摘要。重要信息写进 CLAUDE.md 就永远不会丢。 → 第 4 章 6 层压缩
为什么有时候弹确认框,有时候直接执行?
三级权限:读文件(绿灯直接过)→ 写文件(黄灯自动过)→ 跑命令(红灯需确认)。未注册的工具默认要求最高权限。 → 第 7 章 权限系统
为什么 Claude 好像在兜圈子来回好几轮?
每次对话是一个循环:回复 → 检测工具需求 → 执行工具 → 结果送回 → 下一轮。最多循环 8~16 轮。 → 第 3 章 对话循环
启动为什么有时候很慢?
MCP 服务器初始化和项目首次扫描最容易卡。用 /mcp 检查状态。后续有缓存会快很多。 → 第 2 章 7 阶段启动
怎么最快省钱?
三步:①简单任务用 /model haiku(便宜 15 倍)②定期 /compact 压缩上下文 ③让 Claude 少废话——输出价格是输入的 5 倍。 → 第 9 章 成本定价
/resume 是怎么恢复对话的?
会话以 JSON 格式保存到 ~/.claude/sessions/。/resume 读取保存的文件,遇到 compact_boundary 就停止。 → 第 8 章 持久化
读完之后你可以做什么
- 用
/model haiku切到便宜模型,立刻省钱 - 用
/init创建 CLAUDE.md(最大 4000 字符,写规则清单最高效) - 对话中用 TODO、NEXT 标记待办——压缩时会专门保留
- 感觉 Claude "忘事"了?用
/compact手动压缩 - 试试
/ultraplan、/review、/buddy这些高级命令
全景数据
| 指标 | 子系统 | 工具组 | 工具文件 | 命令 | 阶段启动 | 层压缩 | 层扩展 | 级权限 |
|---|---|---|---|---|---|---|---|---|
| 数字 | 29 | 43 | 184 | 100+ | 7 | 6 | 4 | 3 |
基于对原版 Claude Code 源码的完整拆解分析,每一个数字、每一条规则,都有源码依据。
更多内容,欢迎关注微信公众号【子昕AI编程】~