Claude Code源码专业评测:工业级AI Coding 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来加载模块,这段时间正好可以用来做异步预热。
整个交互式启动序列大致如下:
sequenceDiagramparticipant M as main.tsxparticipant MDM as MDM Readerparticipant KC as Keychainparticipant T as Trust Dialogparticipant GB as GrowthBookparticipant R as REPLM->>MDM: startMdmRawRead() [并行,import 阶段]M->>KC: startKeychainPrefetch() [并行,import 阶段]M->>T: showSetupScreens() [信任对话框]Note over T: 用户必须先同意信任
才能继续T->>M: Trust acceptedM->>GB: GrowthBook reset/reinit [信任后]M->>M: applyConfigEnvironmentVariables()M->>M: initializeTelemetryAfterTrust()M->>R: launchRepl()Note over R: first render 后 deferred prefetch:
initializeAnalyticsGates(),
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()延迟加载来打破循环依赖,这是大型TypeScript项目的常见痛点解决方案。
三、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 TDA[用户消息] --> B[编译 System Prompt]B --> C[调用 Claude API
流式响应]C --> D{响应包含
tool_use?}D -->|是| E[执行工具]E --> F[收集 tool_result]F --> CD -->|否| G[返回最终响应]G --> H{token 超阈值?}H -->|是| I[自动压缩 compact]I --> AH -->|否| 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})(): nullconst 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 TDA[模型请求工具调用] --> B{Deny Rules
全局黑名单}B -->|被禁止| X[直接拒绝]B -->|通过| C{Ask Rules
强制询问规则}C -->|匹配| GC -->|不匹配| D[tool.checkPermissions
工具自身权限检查]D --> E{requiresUserInteraction
/ safetyCheck}E -->|安全| F{Permission Mode}E -->|需交互| GF -->|bypass| H[直接执行]F -->|auto| K[自动决策层
acceptEdits fast-path /
安全 allowlist / classifier]K -->|允许| HK -->|仍需确认或拒绝| GF -->|default/plan/acceptEdits| I{Always Allow Rules
白名单匹配}I -->|匹配| HI -->|不匹配| G[用户确认对话框]G --> J{Hooks / Classifier
与用户交互并发竞速}J -->|允许| H[执行工具]J -->|拒绝| X
一个重要细节:hooks/classifier不是在用户交互之后串行执行的,而是和用户确认对话框并发竞速——哪个先返回结果就用哪个。
权限模式(PermissionMode)
源码中的模式比“三种”多得多:
// types/permissions.tsexport 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/dconst 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.tsexport 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 + systemContextbuildEffectiveSystemPrompt() ← 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 LRA[对话进行中] -->|每轮结束| 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 = 200export 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 serversMarketplace 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 层的纵深防御
深入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 a void circular dependencyconst 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
通过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参考实现。
