泄露的 Claude Code 全量源码,终于让我们有机会看清楚:一个日活百万的 AI Coding Agent,在工程层面到底做了哪些事。这篇文章从启动流程、Agent 循环、工具系统、权限模型、上下文管理、多 Agent 协作等维度逐一拆解。
一、先讲结论
Claude Code 并不是一个简单的"LLM + 命令行包装"。它是一个用 TypeScript + React (Ink) 构建的、运行在终端里的完整 Agent 操作系统。
几个关键判断:
- 技术栈:主要基于 Bun 运行时(构建期依赖
bun:bundle做死代码消除),同时部分路径兼容 Node(如容器环境),UI 用 React Ink,API 用 Anthropic SDK,整体约 60+ 内置工具、60+ 用户命令,代码量非常大。此外还用纯 TypeScript 重写了三个原本的 native 依赖(syntax-highlighted diff、fuzzy file search、yoga flexbox layout),显著缩小了 NAPI 依赖面——但并未彻底消除,仓库中仍有audio-capture-napi(语音录制)、url-handler-napi(macOS URL scheme)、image-processor-napi(图片处理/剪贴板)、modifiers-napi(键盘修饰键检测)等 NAPI 模块在用 - 核心循环:经典的 ReAct 循环(Query → LLM → ToolUse → ToolResult → LLM → ...),但在此基础上做了大量工程优化——流式执行、并行工具调用、自动上下文压缩
- 权限模型:这是整个系统最重的部分之一,多阶段权限检查(deny rules → ask rules → 工具自检 → permission mode → 白名单 → 用户确认 / classifier 并发竞速),安全性设计得很扎实
- 多 Agent 架构:内置 Coordinator 模式(多 Agent 协作)和 Agent Swarms(团队创建/删除),已经不是单 Agent 产品了
- Bridge 模式:支持远程执行,通过 WebSocket 实现桌面端 ↔ 远程服务器的会话桥接
- 记忆与梦境:可以看作三层渐进式记忆管线(Auto Memory Extraction → Session Memory → Auto Dream 跨会话记忆整合),各层都有独立 gate 和运行条件,后台 forked agent 模式实现,不阻塞主对话
- 插件/技能/Hooks 扩展体系:不只是工具扩展,还有 marketplace 安装、skill frontmatter 配置、pre/post 执行 hooks、MCP skill builders —— 已经是完整的扩展平台
如果你在造 Coding Agent,这份源码是最好的参考实现之一。
二、启动流程:极致的并行初始化
看 main.tsx 的前 20 行就知道,Anthropic 在启动性能上下了很大功夫:
// 这三个 side-effect 必须在所有其他 import 之前运行:
import { profileCheckpoint } from './utils/startupProfiler.js';
profileCheckpoint('main_tsx_entry');
import { startMdmRawRead } from './utils/settings/mdm/rawRead.js';
startMdmRawRead(); // 并行:MDM 配置子进程
import { startKeychainPrefetch } from './utils/secureStorage/keychainPrefetch.js';
startKeychainPrefetch(); // 并行:macOS keychain 预读(OAuth + API key)
这里的设计思路是:把昂贵的 I/O 操作(钥匙串读取、MDM 配置读取)提前到 import 阶段并行执行。因为 Node/Bun 的 import 本身需要 ~135ms 来加载模块,这段时间正好可以用来做异步预热。
整个交互式启动序列大致如下:
sequenceDiagram
participant M as main.tsx
participant MDM as MDM Reader
participant KC as Keychain
participant T as Trust Dialog
participant GB as GrowthBook
participant R as REPL
M->>MDM: startMdmRawRead() [并行,import 阶段]
M->>KC: startKeychainPrefetch() [并行,import 阶段]
M->>T: showSetupScreens() [信任对话框]
Note over T: 用户必须先同意信任<br>才能继续
T->>M: Trust accepted
M->>GB: GrowthBook reset/reinit [信任后]
M->>M: applyConfigEnvironmentVariables()
M->>M: initializeTelemetryAfterTrust()
M->>R: launchRepl()
Note over R: first render 后 deferred prefetch:<br>initializeAnalyticsGates(),<br>prefetchOfficialMcpUrls() 等
注意:-p / --print 非交互模式下不会弹出 Trust Dialog——源码注释明确写了 "trust is implicit in -p mode"。
关键点:
- Trust Dialog 是安全门:在用户确认信任之前,不会加载完整的环境变量和配置。GrowthBook 的 reset/reinit 也发生在 trust 之后
- 两套 Feature Flag 机制不要混淆:
feature('COORDINATOR_MODE')来自bun:bundle,是构建期死代码消除,编译后不存在;GrowthBook/Statsig 是运行期远程配置,走getFeatureValue_CACHED_MAY_BE_STALE()/checkStatsigFeatureGate_CACHED_MAY_BE_STALE()代码路径 - 懒加载 + 条件导入:大量使用
require()延迟加载来打破循环依赖
三、Agent 核心循环:QueryEngine 是大脑
Claude Code 的核心循环有两条路径:
交互模式:REPL → query()(直接调用)
Headless/SDK 模式:QueryEngine → query()
一个容易误解的点:REPL 并不经过 QueryEngine。QueryEngine 的注释明确写了 "供 headless/SDK 使用,REPL 是 future phase"。从 screens/REPL.tsx 第 2793 行可以看到,REPL 直接 for await (const event of query({...})) 调用 query()。
QueryEngine:Headless/SDK 的会话管理
QueryEngine 是 headless/SDK 模式下每个对话的状态持有者,负责:
export class QueryEngine {
private mutableMessages: Message[] // 消息历史
private abortController: AbortController // 中断控制
private permissionDenials: SDKPermissionDenial[] // 权限拒绝记录
private totalUsage: NonNullableUsage // 累计 token 用量
private readFileState: FileStateCache // 文件状态缓存
private discoveredSkillNames = new Set<string>() // 技能发现追踪
}
它的核心方法是 submitMessage(),每次用户发送消息时被调用,触发一轮完整的 Agent 循环。
query():单轮执行的核心
query() 函数是真正的执行引擎,实现了一个 generator 模式的 Agent 循环:
flowchart TD
A[用户消息] --> B[编译 System Prompt]
B --> C[调用 Claude API<br>流式响应]
C --> D{响应包含<br>tool_use?}
D -->|是| E[执行工具]
E --> F[收集 tool_result]
F --> C
D -->|否| G[返回最终响应]
G --> H{token 超阈值?}
H -->|是| I[自动压缩 compact]
I --> A
H -->|否| J[等待下一条消息]
几个工程亮点:
1. 流式工具执行(StreamingToolExecutor)
Claude Code 不是等 LLM 完整输出后再执行工具,而是在流式响应的过程中就开始准备工具调用:
import { StreamingToolExecutor } from './services/tools/StreamingToolExecutor.js'
2. 工具并行 vs 串行的智能分区(两套实现)
这里有一个很容易忽略的点:源码中有两套并行执行实现,分别用于不同场景。
旧路径:toolOrchestration.ts(批量分区)
function partitionToolCalls(toolUseMessages, toolUseContext): Batch[] {
// 对每个工具调用,根据 tool.isConcurrencySafe(parsedInput) 判定:
// 1. 连续的 concurrency-safe 工具 → 并行执行(最多 10 并发)
// 2. 单个非 concurrency-safe 工具 → 串行执行
}
新路径:StreamingToolExecutor(流式并发)
当 streamingToolExecution gate 开启时,工具不再等 LLM 一次性返回所有 tool_use 再分区,而是一边流式接收 tool_use 块,一边立即开始执行:
class StreamingToolExecutor {
addTool(block: ToolUseBlock, assistantMessage: AssistantMessage) {
// 流式接收到一个 tool_use 就立即入队
// 如果当前没有非并发安全的工具在执行,立即启动
}
private canExecuteTool(isConcurrencySafe: boolean): boolean {
const executingTools = this.tools.filter(t => t.status === 'executing')
return executingTools.length === 0 ||
(isConcurrencySafe && executingTools.every(t => t.isConcurrencySafe))
}
}
还有一个精巧的错误传播机制:当某个并发 Bash 工具出错时,StreamingToolExecutor 会通过 siblingAbortController(父 AbortController 的子控制器)立即取消兄弟进程,而不会中断父 query 循环。
注意判定依据不是简单的"只读 vs 写入",而是每个工具自己实现的 isConcurrencySafe(input) 方法,会根据具体输入参数判断。比如 FileReadTool 通常返回 true,但 BashTool 会根据命令内容决定。
3. 自动上下文压缩(Auto Compact)
当对话的 token 数接近上下文窗口时,系统会自动触发压缩:
// 阈值 = 有效上下文窗口 - 13000 tokens 缓冲
export const AUTOCOMPACT_BUFFER_TOKENS = 13_000
// 连续失败超过 3 次就停止重试(避免浪费 API 调用)
const MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES = 3
但 auto compact 只是压缩策略之一。源码中至少还有这些路径:
- Auto Compact:标准的摘要压缩,调用 Claude 对历史消息做摘要替换
- Session Memory Compaction:把重要信息沉淀到 session memory
- Reactive Compact:在
prompt_too_long错误时被动触发的紧急压缩 - Microcompact:更轻量的增量压缩,通过 cache editing 实现
- History Snip:按 snip boundary 截断历史,在启用
HISTORY_SNIPfeature flag 时参与请求前压缩 - Context Collapse:折叠大块上下文(feature flag
CONTEXT_COLLAPSE)
这些路径不是互斥的——snip 和 microcompact 可以同时运行,autocompact 在它们之后触发。
从 query.ts 的循环体可以看到完整的压缩流水线执行顺序:
每次循环迭代 → applyToolResultBudget(裁剪工具结果大小)
→ snipCompactIfNeeded(按边界截断)
→ microcompact(时间维度清理旧工具结果)
→ contextCollapse(折叠大块上下文)
→ autocompact(摘要压缩)
每一层都可能缩减上下文,后面的层看到的是前面处理过的结果。如果 snip + microcompact + collapse 已经把 token 数压到阈值以下,autocompact 就不用触发了——这比单纯的 autocompact 保留了更多细粒度上下文。
4. max_output_tokens 恢复循环
当 Claude 的回复被截断(达到 max_output_tokens 限制)时:
const MAX_OUTPUT_TOKENS_RECOVERY_LIMIT = 3
// 最多重试 3 次,每次自动 continue
四、工具系统:60+ 工具的注册与分发
工具注册表
tools.ts 是内置工具(built-in tools) 的注册中心。MCP 工具(MCPTool、McpAuthTool)不在这里注册,而是在 MCP client 层(services/mcp/client.ts)动态创建,最后由 assembleToolPool() 合并进统一的工具池。
从源码可以看到 built-in 工具列表(不含 MCP 动态工具):
| 类别 | 工具 | 说明 |
|---|---|---|
| 文件操作 | FileReadTool, FileEditTool, FileWriteTool, NotebookEditTool | 读、编辑、写、笔记本 |
| 搜索 | GlobTool, GrepTool, WebSearchTool | 文件搜索、内容搜索、网络搜索 |
| 执行 | BashTool, PowerShellTool | Shell 命令执行 |
| Agent | AgentTool, TeamCreateTool, TeamDeleteTool, SendMessageTool | 子 Agent、团队管理 |
| 任务 | TaskCreateTool, TaskGetTool, TaskUpdateTool, TaskListTool, TaskStopTool, TaskOutputTool | 任务生命周期管理 |
| 计划 | EnterPlanModeTool, ExitPlanModeV2Tool, VerifyPlanExecutionTool | 计划模式 |
| MCP 资源 | ListMcpResourcesTool, ReadMcpResourceTool | MCP 资源读取(注意不含 MCPTool) |
| 其他 | WebFetchTool, TodoWriteTool, SkillTool, AskUserQuestionTool, LSPTool, BriefTool | 网页抓取、待办、技能、提问、LSP |
| 高级 | ToolSearchTool, WorkflowTool, CronCreateTool, MonitorTool, SleepTool | 工具搜索、工作流、定时任务、监控 |
| Worktree | EnterWorktreeTool, ExitWorktreeTool | Git worktree 隔离(实验性) |
| Kairos/Assistant | SendUserFileTool, PushNotificationTool, SubscribePRTool | 推送、PR 订阅(内部构建) |
| 调试 | CtxInspectTool, OverflowTestTool, SnipTool, TerminalCaptureTool | 上下文检查、溢出测试(Feature Flag) |
Feature Flag 驱动的条件编译
工具注册大量使用了 Bun 的 feature() 做编译期死代码消除:
const WorkflowTool = feature('WORKFLOW_SCRIPTS')
? (() => {
require('./tools/WorkflowTool/bundled/index.js').initBundledWorkflows()
return require('./tools/WorkflowTool/WorkflowTool.js').WorkflowTool
})()
: null
const MonitorTool = feature('MONITOR_TOOL')
? require('./tools/MonitorTool/MonitorTool.js').MonitorTool
: null
当某个 feature flag 关闭时,对应的 require() 不会执行,相关代码会被 Bun 的 bundler 彻底剔除。这不是运行时检查,而是构建期优化。
工具权限过滤
在工具到达 LLM 之前,会经过 deny rules 过滤:
export function filterToolsByDenyRules(tools, permissionContext) {
return tools.filter(tool => !getDenyRuleForTool(permissionContext, tool))
}
被 deny 的工具连 schema 都不会发送给模型——模型根本不知道这些工具的存在。
五、权限系统:多阶段防线
这是 Claude Code 最值得学习的部分之一。真实的权限检查流程比"四道关"复杂得多,大致如下:
flowchart TD
A[模型请求工具调用] --> B{Deny Rules<br>全局黑名单}
B -->|被禁止| X[直接拒绝]
B -->|通过| C{Ask Rules<br>强制询问规则}
C -->|匹配| G
C -->|不匹配| D[tool.checkPermissions<br>工具自身权限检查]
D --> E{requiresUserInteraction<br>/ safetyCheck}
E -->|安全| F{Permission Mode}
E -->|需交互| G
F -->|bypass| H[直接执行]
F -->|auto| K[自动决策层<br>acceptEdits fast-path /<br>安全 allowlist / classifier]
K -->|允许| H
K -->|仍需确认或拒绝| G
F -->|default/plan/acceptEdits| I{Always Allow Rules<br>白名单匹配}
I -->|匹配| H
I -->|不匹配| G[用户确认对话框]
G --> J{Hooks / Classifier<br>与用户交互并发竞速}
J -->|允许| H[执行工具]
J -->|拒绝| X
一个重要细节:hooks/classifier 不是在用户交互之后串行执行的,而是和用户确认对话框并发竞速——哪个先返回结果就用哪个。
权限模式(PermissionMode)
源码中的模式比"三种"多得多:
// types/permissions.ts
export const EXTERNAL_PERMISSION_MODES = [
'acceptEdits', // 自动接受编辑操作
'bypassPermissions', // 旁路,所有操作自动通过
'default', // 默认,敏感操作需确认
'dontAsk', // 不弹确认框(后台 agent 用)
'plan', // 计划模式,只读自动通过
] as const
// 内部还有:
export type InternalPermissionMode =
| ExternalPermissionMode
| 'auto' // feature flag 开启时的自动模式
| 'bubble' // 权限冒泡(子 agent → 父 agent)
Bash 工具的安全检查
BashTool 的权限检查特别复杂,单独有一个 bashSecurity.ts:
- AST 解析:用
parseForSecurity()解析 bash 命令的 AST,而不是简单的字符串匹配 - sed 编辑检测:单独的
sedEditParser.ts来判断 sed 命令是否在做文件编辑 - 破坏性命令警告:
destructiveCommandWarning.ts检测rm -rf等危险命令 - 沙箱执行:
shouldUseSandbox()判断是否需要在 sandbox 中执行 - 路径验证:
pathValidation.ts确保不会操作项目外的文件
Classifier 辅助判断
源码中有 TRANSCRIPT_CLASSIFIER feature flag:
const classifierDecisionModule = feature('TRANSCRIPT_CLASSIFIER')
? require('./classifierDecision.js')
: null
这意味着 Anthropic 在权限判断中还引入了一个分类器模型,来辅助判断工具调用是否安全。
六、任务系统:7 种任务类型
Task.ts 定义了任务的类型系统:
export type TaskType =
| 'local_bash' // 本地 Shell 命令
| 'local_agent' // 本地子 Agent
| 'remote_agent' // 远程 Agent
| 'in_process_teammate' // 进程内队友(Agent Swarms)
| 'local_workflow' // 本地工作流
| 'monitor_mcp' // MCP 监控
| 'dream' // "做梦"(后台思考?)
export type TaskStatus =
| 'pending' | 'running' | 'completed' | 'failed' | 'killed'
每个任务都有一个密码学安全的 ID:
// 前缀 + 8字节随机 = 36^8 ≈ 2.8 万亿种组合
// 足以抵御符号链接攻击
export function generateTaskId(type: TaskType): string {
const prefix = getTaskIdPrefix(type) // b/a/r/t/w/m/d
const bytes = randomBytes(8)
// ...
}
七、多 Agent 协作:Coordinator Mode 与 Agent Swarms
Coordinator Mode
当 CLAUDE_CODE_COORDINATOR_MODE=1 时,Claude Code 进入协调者模式:
export function isCoordinatorMode(): boolean {
if (feature('COORDINATOR_MODE')) {
return isEnvTruthy(process.env.CLAUDE_CODE_COORDINATOR_MODE)
}
return false
}
在这个模式下:
- 协调者只能使用 AgentTool、TaskStopTool、SendMessageTool、SyntheticOutputTool(不能直接操作文件)
- 工作者(子 Agent)才能使用 Bash/Read/Edit 等工具
- 通过
SendMessageTool在 Agent 间传递消息
// constants/tools.ts
export const COORDINATOR_MODE_ALLOWED_TOOLS = new Set([
AGENT_TOOL_NAME,
TASK_STOP_TOOL_NAME,
SEND_MESSAGE_TOOL_NAME,
SYNTHETIC_OUTPUT_TOOL_NAME,
])
Agent Swarms(团队)
// 源码中的团队管理工具
TeamCreateTool // 创建团队成员
TeamDeleteTool // 删除团队成员
SendMessageTool // 向团队成员发消息
ListPeersTool // 列出所有 Agent 同伴
这就是 Anthropic 的"多 Agent 协作"实现——不是通过外部编排框架,而是直接内建在工具系统里。
子 Agent 执行流程
runAgent.ts 揭示了子 Agent 的运行方式:
- 创建独立的
FileStateCache(文件状态隔离) - 编译子 Agent 专属的 System Prompt
- 连接必要的 MCP 服务器
- 启动独立的
query()循环 - 记录侧链 transcript(用于观察和调试)
子 Agent 有自己的工具限制(CUSTOM_AGENT_DISALLOWED_TOOLS)。外部构建中,AgentTool 默认在禁止列表里,防止无限递归;但 Anthropic 内部构建(USER_TYPE === 'ant')允许嵌套 agent。此外,in-process teammate 在特定条件下也能获得 AgentTool。
Fork Subagent:一种更轻量的子 Agent
除了上面通过 AgentTool 显式创建的子 Agent,源码中还有一种 Fork Subagent 模式(FORK_SUBAGENT feature flag):
- 继承完整上下文:fork child 拿到父对话的所有消息 + 字节精确相同的 system prompt
- Prompt Cache 共享:因为 system prompt 字节完全一致,fork child 可以命中父对话的 prompt cache,大幅降低成本
- 权限冒泡:fork child 的权限模式是
'bubble',权限请求会上浮到父 agent 由用户确认 - 防递归:
isInForkChild()检测并阻止嵌套 fork
这个设计非常聪明——Fork Subagent 本质上是"用当前对话上下文开一个分支去做某件事",而不是 AgentTool 那种"创建一个新 agent 从零开始"。适用于"帮我同时验证这三个文件"这类场景。
八、上下文系统:你看到的 System Prompt 背后
context.ts 只负责产出 systemContext / userContext 两个片段——前者主要是 git 状态快照,后者主要是 CLAUDE.md 内容。
// context.ts → getSystemContext() 收集:
const [branch, mainBranch, status, log, userName] = await Promise.all([
getBranch(),
getDefaultBranch(),
execFileNoThrow(gitExe(), ['status', '--short']),
execFileNoThrow(gitExe(), ['log', '--oneline', '-n', '5']),
execFileNoThrow(gitExe(), ['config', 'user.name']),
])
// → 打包成 git 状态快照
真正的 System Prompt 组装分散在多个文件中:
getSystemPrompt() ← constants/prompts.ts:基础提示词模板
fetchSystemPromptParts() ← utils/queryContext.ts:协调 systemPrompt + userContext + systemContext
buildEffectiveSystemPrompt() ← REPL 层的最终组装
最终发送给模型的请求 payload 包括:
systemPrompt(以下内容拼接而成):
基础 System Prompt(prompts.ts)
+ 环境信息(OS、shell、日期)
+ systemContext(git 状态快照)
+ userContext(CLAUDE.md 内容)
+ Memory 文件(用户记忆)
+ Coordinator 上下文(如果是协调者模式)
+ customSystemPrompt / appendSystemPrompt(用户自定义)
+ 技能(Skills)上下文
tools(独立字段,不属于 system prompt):
built-in tools + MCP tools 的 schema 定义
CLAUDE.md:多层级配置
Claude Code 的 CLAUDE.md 不只是"项目根目录的一个文件",而是一个多层级配置系统。claudemd.ts 开头的注释写得很清楚:
加载顺序(从低优先级到高优先级):
1. Managed memory(/etc/claude-code/CLAUDE.md)— 全局管理员指令
2. User memory(~/.claude/CLAUDE.md)— 用户级全局指令
3. Project memory — 项目级指令,包括:
- CLAUDE.md
- .claude/CLAUDE.md
- .claude/rules/*.md
(从 git root 到当前目录递归查找,越近优先级越高)
4. Local memory(CLAUDE.local.md)— 本地私有项目指令(不应提交到 git)
还支持 @include 指令引用其他文件,形成组合配置。
Memory 系统:三层记忆 + Auto Dream
这是博客首次发现时容易低估的部分。Claude Code 的记忆不是一个简单的 MEMORY.md 文件——从代码结构来看,可以归纳为一个三层渐进式记忆管线(我的分析框架,非源码官方分层)。需要注意的是,这三层都带有独立的 gate 或运行条件,不是无条件常驻能力:Auto Memory 要检查 isAutoMemoryEnabled() 且 remote 模式会跳过;Session Memory 由 tengu_session_memory feature flag 控制;Auto Dream 在 KAIROS 和 remote 模式下直接关闭:
flowchart LR
A[对话进行中] -->|每轮结束| B[Auto Memory Extraction]
B -->|写入| C["~/.claude/projects/path/memory/"]
A -->|token 阈值| D[Session Memory]
D -->|写入| E["~/.claude/sessions/id/session-memory.md"]
F["跨会话 24h+ 5+ sessions"] -->|触发| G[Auto Dream]
G -->|整合多个 session| C
第一层:Auto Memory Extraction
每轮 query 结束后(不再有 tool_use 时),后台 forked agent 自动提取对话中的关键洞察,写入项目记忆目录。
- 使用
runForkedAgent()+ Prompt Cache 共享(和主对话共用 cache prefix,避免双倍 API 成本) - 有 cursor 追踪(
sinceUuid):如果主 agent 自己已经写了记忆,forked agent 会跳过,避免重复 - 工具受限:只能用 Read/Grep/Glob + Edit/Write(仅 auto-memory 路径)
第二层:Session Memory
当对话足够长时(三重阈值:10K tokens 初始化 + 5K tokens 增量 + 3 次工具调用),自动提取 session 级笔记。
这个 session memory 还有一个精巧的用途:Session Memory Compaction 可以用 session memory 的内容来替代 autocompact 的摘要——因为 session memory 是增量提取的,质量可能比一次性摘要更好。
第三层:Auto Dream(记忆整合)
这是最有趣的设计。当满足两个条件:
- 距离上次整合 ≥ 24 小时
- 期间积累了 ≥ 5 个 session transcript
Claude Code 会启动一个 "dream" 任务(TaskType = 'dream'),用 forked agent 遍历多个 session 的 transcript,把分散的记忆整合沉淀到项目记忆中。
const DEFAULTS: AutoDreamConfig = {
minHours: 24,
minSessions: 5,
}
Dream 任务有完整的生命周期管理(register → phase tracking → complete/fail),可以被 kill 并回滚 consolidation lock 的 mtime。这解释了 Task.ts 中那个神秘的 'dream' 类型。
MEMORY.md 入口文件
所有记忆内容通过 MEMORY.md 入口文件注入 system prompt:
export const MAX_ENTRYPOINT_LINES = 200
export const MAX_ENTRYPOINT_BYTES = 25_000
有行数和字节数双重截断保护——因为用户可能写出单行超长的 MEMORY.md(实际观测到 197KB 在 200 行以下的情况)。
九、MCP 集成:不只是客户端
Claude Code 对 MCP(Model Context Protocol)的支持非常深入:
// 支持所有 MCP 传输方式
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js'
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'
- 四种传输协议:Stdio、SSE、StreamableHTTP、WebSocket
- OAuth 认证:内建 OAuth 流程支持 MCP 服务器认证
- 资源缓存:
prefetchAllMcpResources()预加载 MCP 资源 - 工具合并:MCP 工具和内置工具在
assembleToolPool()中统一合并,内置工具优先 - 官方注册表:
prefetchOfficialMcpUrls()从官方注册表获取 MCP 服务器列表
十、Bridge 模式:远程执行架构
bridge/ 目录揭示了 Claude Code 的远程执行能力:
bridgeMain.ts ← 入口
bridgeMessaging.ts ← 消息序列化(ndjson)
bridgePermissionCallbacks.ts ← 远程权限委派
replBridge.ts ← REPL 桥接
replBridgeTransport.ts ← WebSocket 传输层
sessionRunner.ts ← 远程会话运行器
trustedDevice.ts ← 设备信任
jwtUtils.ts ← JWT 认证
这套架构允许 Claude Code 在这种场景下工作:
- 桌面 VS Code 连接远程服务器
- 权限弹窗在本地桌面显示
- 实际工具执行在远程服务器
- 通过 WebSocket 多路复用传输
十一、扩展体系:Plugin + Skill + Hooks
Claude Code 不只是一个封闭的工具集——它已经构建了一套完整的扩展体系。
Plugin 系统
Built-in Plugins(内建插件)
├── 随 CLI 发布,{name}@builtin 格式
├── 用户可 enable/disable(/plugin UI)
└── 可提供 skills + hooks + MCP servers
Marketplace Plugins(市场插件)
├── 声明在 settings(可信源)
├── 缓存在 ~/.claude/marketplaces/
├── 后台安装不阻塞启动
└── 安装后自动 cache 清理 + MCP 重连
插件安装管理器(PluginInstallationManager.ts)实现了一个完整的 diff-reconcile 流程:先算出 missing/updated/up-to-date/failed 的差异,再异步并行安装,带进度回调到 UI。
Skill 系统
Skill 是比 Plugin 更轻量的扩展单元——本质上是带 YAML frontmatter 的 Markdown 文件:
- 支持
@include引用、argument 替换、shell 执行指令 - 来源多样:bundled / user / project / plugin / MCP
- MCP Skill Builders:MCP 服务器可以动态注册 skill builder,在运行时创建新的 skill
- 发现机制:
EXPERIMENTAL_SKILL_SEARCHgate 下,每轮对话都会预取可能相关的 skill
Hooks 系统(不是 React Hooks)
utils/hooks/ 下有一套独立的执行生命周期 hook 系统:
| Hook 类型 | 说明 |
|---|---|
| postSamplingHooks | 模型输出后处理 |
| execAgentHook | Agent 调用前后 |
| execPromptHook | LLM prompt 前后 |
| execHttpHook | HTTP 请求/响应前后 |
| sessionHooks | Session 级函数 hook |
| registerSkillHooks | Skill 定义的 hook 自动注册 |
Hook 不只是 shell command——源码定义了四种 hook 类型:command(shell 命令)、prompt(LLM prompt)、agent(多轮 Agent 查询)、http(HTTP POST)。各类型的默认超时也不同:shell/tool hook 默认 10 分钟,prompt hook 默认 30 秒,agent hook 默认 60 秒,http hook 默认 10 分钟,只有 AsyncHookRegistry 的 asyncTimeout 默认 15 秒。所有 hook 支持进度增量输出(stdout/stderr delta 实时推送 UI)。
十二、UI 层:终端里的 React
Claude Code 用 Ink(React for CLI)构建终端 UI。以下是高层示意的组件结构(非实际渲染树):
App (AppStateProvider + KeybindingSetup)
├── REPL (主循环)
│ ├── VirtualMessageList (虚拟滚动消息列表)
│ ├── PromptInput (文本输入,支持 Vim 模式)
│ └── StatusLine (底部状态栏)
└── Dialogs (信任对话框、设置、入职引导)
几个有趣的点:
- Vim 模式:完整的 vim/ 目录,实现了 normal/insert/visual/command 四种模式、motion/operator/text-object 全套
- Voice:语音输入/输出,但要求 Anthropic OAuth 认证(不支持 API key),走 claude.ai 的 voice_stream 端点
- 虚拟滚动:100+ 组件的消息列表不是全量渲染
- Buddy 系统:隐藏的聊天伙伴精灵系统。用 FNV-1a 哈希 + Mulberry32 PRNG 从用户配置确定性生成,有 5 级稀有度(common → legendary),有属性点分配。纯 fun feature
- native-ts 纯 TS 实现:
native-ts/目录包含三个原本是 native NAPI 模块的纯 TypeScript 重写:color-diff/:语法高亮 diff(替代 Rust syntect,改用 highlight.js)file-index/:模糊文件搜索(替代 Rust nucleo,异步构建 index,每 4ms yield 事件循环)yoga-layout/:Flexbox 布局引擎(替代 Meta 的 C++ yoga-layout,单 pass 实现 Ink 所需的子集)
十三、Bash 安全:可以抽象成 8 层的纵深防御
博客初版只提了 BashTool 安全的概要。深入 bashSecurity.ts 后发现,源码定义了 23 个独立的安全检查 ID(BASH_SECURITY_CHECK_IDS),代码中多处提到 "defense-in-depth" 设计理念,但并没有官方分层命名。以下是我将这些检查点归纳为 8 层的分析框架:
| 层级 | 名称 | 做什么 |
|---|---|---|
| 1 | Quote Extraction | 三路并行解析引号:保留双引号、完全去引号、保留引号字符(检测 'x'# 等边缘情况) |
| 2 | Dangerous Patterns | 阻止命令替换 $()、进程替换 <()、zsh equals expansion =curl、heredoc in sub |
| 3 | Zsh-Specific Threats | 阻止 zmodload(通向 mapfile/zpty/ztcp)、emulate -c(eval 等价)、zf_rm 等绕过检查的 builtin |
| 4 | Path Validation | 每命令独立提取路径(cd/rm/cp/mv/git/jq/sed...),正确处理 -- 终止符 |
| 5 | Advanced Threats | IFS 注入、大括号展开、控制字符、Unicode 空白、嵌入换行、/proc/self/environ 访问 |
| 6 | Destructive Warning | rm -rf、git reset --hard、DROP TABLE、kubectl delete、terraform destroy 等(展示警告但不阻止) |
| 7 | Sed Edit Parser | 解析 sed -i 为文件编辑操作(BRE↔ERE 转换用 null-byte sentinel 防注入),处理 macOS -i 后缀差异 |
| 8 | Permission Framework | 只读命令自动放行、权限树匹配、新规则建议 |
一个特别有趣的安全细节:zsh 的 =curl 语法会展开为 /usr/bin/curl,这意味着如果你在 deny rules 里禁了 Bash(curl:*) ,攻击者可以通过 =curl 绕过。Claude Code 在第 2 层专门阻止了这种 zsh equals expansion。
十四、隐藏的产品形态:KAIROS / Assistant / Direct Connect
从 feature flag 和代码结构推测,Claude Code 的代码库可能支撑着多种产品形态(以下是从代码结构推测的分类,并非源码的官方命名):
| 形态 | Feature Flag | 说明 |
|---|---|---|
| CLI | 默认构建 | 我们用的命令行版本 |
| KAIROS / Assistant | feature('KAIROS') | 类似 assistant 模式,有 PushNotification、SendUserFile、SubscribePR、session chooser、install wizard |
| Coordinator | feature('COORDINATOR_MODE') | 多 Agent 协调者模式 |
| Bridge / Remote | bridge/ + remote/ 目录 | 远程会话桥接(代码中出现 "CCR" 缩写,但具体产品定位不明) |
除了 bridge/,还有两个远程执行路径:
remote/:RemoteSessionManager+SessionsWebSocket,通过 SDK control messages 实现远程权限桥server/:DirectConnectSessionManager,WebSocket 直连模式,Bun 原生 WebSocket headers 支持
这意味着 Anthropic 在用一套代码库同时开发本地 CLI、远程托管、和可能的 SaaS assistant 产品。moreright/ 目录只有一个 useMoreRight.tsx stub 文件(注释写明 "Stub for external builds — the real hook is internal only"),其中 onBeforeQuery 返回 true,onTurnComplete 是空 no-op,只有 render 返回 null——真实实现应该在 Anthropic 内部构建中。
十五、值得注意的工程模式
1. 两套 Feature Flag:构建期 vs 运行期
这是整个项目最独特的模式。通过 Bun 的 feature() 函数:
import { feature } from 'bun:bundle'
const coordinatorModeModule = feature('COORDINATOR_MODE')
? require('./coordinator/coordinatorMode.js')
: null
这不是运行时 if-else——Bun bundler 在构建时会根据 feature flag 配置,把关闭的分支整个删掉。这意味着不同的构建产物可以有完全不同的功能集。
但同时还有一套运行期远程配置,通过 GrowthBook/Statsig 下发:
// 运行期检查,走远程配置
getFeatureValue_CACHED_MAY_BE_STALE('some_feature')
checkStatsigFeatureGate_CACHED_MAY_BE_STALE('tengu_scratch')
这两套系统服务于不同目的:构建期消除维度是"产品形态"(CLI vs KAIROS vs Coordinator),运行期配置维度是"灰度发布"和"A/B 测试"。
2. 懒加载打破循环依赖
项目中大量使用 require() 延迟加载:
// Lazy require to avoid circular dependency
const getTeammateUtils = () =>
require('./utils/teammate.js') as typeof import('./utils/teammate.js')
这是大型 TypeScript 项目的常见痛点解决方案。
3. Memoize 无处不在
import memoize from 'lodash-es/memoize.js'
export const getGitStatus = memoize(async () => { ... })
系统上下文、git 状态等昂贵操作都被 memoize,避免同一次对话中重复计算。
4. 内部用户 vs 外部用户
process.env.USER_TYPE === 'ant' // Anthropic 内部员工
内部员工有额外的工具(REPLTool、ConfigTool、TungstenTool、SuggestBackgroundPRTool),外部用户看不到。
5. Forked Agent 模式:Prompt Cache 共享的后台执行
这是整个项目中最被低估的工程模式之一。Auto Memory Extraction、Agent Summary、Magic Docs、Prompt Suggestion、Auto Dream 等后台功能都基于同一个模式:
// runForkedAgent() + CacheSafeParams
// 关键:fork 出的 agent 和主对话共享 system prompt 前缀
// → 命中 prompt cache → fork 的 API 成本极低
为了保证 cache 命中,fork agent 甚至会特意保持 system prompt 字节完全一致——GrowthBook 冷/热启动时可能返回不同值,所以渲染好的 system prompt 通过 toolUseContext.renderedSystemPrompt 显式传递,而不是让 fork child 自己重新渲染。
6. 状态管理:36 行的极简 Store
全局状态管理的核心是 state/store.ts——只有 36 行代码:
export function createStore<T>(initialState: T, onChange?: OnChange<T>): Store<T> {
let state = initialState
const listeners = new Set<Listener>()
return {
getState: () => state,
setState: (updater) => { /* ... */ },
subscribe: (listener) => { listeners.add(listener); return () => listeners.delete(listener) },
}
}
通过 React 的 useSyncExternalStore 集成到 Ink 组件树。没有 Redux、Zustand 或任何状态管理库。证明了对于这种确定性状态流的应用,最简单的 pub-sub store 就够了。
十六、局限与坑
- 上下文窗口仍然是硬约束:尽管有 auto compact,但压缩必然丢失信息。连续失败 3 次后直接放弃压缩。
- 权限系统复杂度高:多阶段权限检查 + 分类器 + hook 系统 + 并发竞速,维护成本一定很高。
- 循环依赖问题:大量 lazy require 说明模块边界设计还有改进空间。
- Feature Flag 膨胀:COORDINATOR_MODE、KAIROS、PROACTIVE、AGENT_TRIGGERS、HISTORY_SNIP、REACTIVE_COMPACT、CONTEXT_COLLAPSE、WEB_BROWSER_TOOL... flag 数量已经很多了。
- Bun 锁定:深度使用
bun:bundle的feature(),意味着与 Bun 强绑定。 - Forked Agent 的隐性 API 成本:Auto Memory、Session Memory、Agent Summary、Prompt Suggestion、Auto Dream、Magic Docs —— 每个后台 forked agent 都是一次 API 调用。虽然有 prompt cache 共享优化,但长对话中后台调用的累计成本是可观的。
- native-ts 的功能子集:纯 TS 重写的三个 native 模块都是功能子集(yoga 不支持 RTL 和 aspect-ratio,highlight.js 不像 syntect 那样 scope plain identifiers),可能在极端场景下表现不一致。
十七、最后总结
读完这份源码,我的判断是:
Claude Code 不是一个 demo 级产品,它是一个经过深度工程化的工业级 Agent 系统。 从启动的并行初始化优化到工具并发调度,从多层权限模型到编译期死代码消除,每一个细节都透露着"这是一个真实服务百万用户的产品"。
几个值得 Agent 开发者学习的设计:
- 权限系统不是事后补丁,而是从架构层面内建的第一等公民
- 工具并行 vs 串行的自动判断,是一个被低估但影响很大的优化
- Feature Flag + 编译期消除,让一套代码支撑多种产品形态(CLI、Bridge、Coordinator、KAIROS/Assistant)
- Agent 不是单体,而是一套可以嵌套、组合、协作的执行单元
这可能是目前市面上最值得深入学习的 Coding Agent 参考实现。