Claude Code源码专业评测:工业级AI Coding Agent深度拆解

2026-06-05阅读 0热度 0
Claude

聊点硬核的。关于Claude Code这次的源码泄漏,说实话,读完之后最大的感受是:这不只是一个简单的“LLM + 命令行包装”,而是一个用TypeScript和React(Ink)在终端里完整搭建起来的Agent操作系统。

Claude Code 源码泄漏:拆解一个工业级 AI Coding 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_SNIP feature 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, PowerShellToolShell 命令执行
AgentAgentTool, TeamCreateTool, TeamDeleteTool, SendMessageTool子 Agent、团队管理
任务TaskCreateTool, TaskGetTool, TaskUpdateTool, TaskListTool, TaskStopTool, TaskOutputTool任务生命周期管理
计划EnterPlanModeTool, ExitPlanModeV2Tool, VerifyPlanExecutionTool计划模式
MCP 资源ListMcpResourcesTool, ReadMcpResourceToolMCP 资源读取(注意不含 MCPTool)
其他WebFetchTool, TodoWriteTool, SkillTool, AskUserQuestionTool, LSPTool, BriefTool网页抓取、待办、技能、提问、LSP
高级ToolSearchTool, WorkflowTool, CronCreateTool, MonitorTool, SleepTool工具搜索、工作流、定时任务、监控
WorktreeEnterWorktreeTool, ExitWorktreeToolGit worktree 隔离(实验性)
Kairos/AssistantSendUserFileTool, 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的运行方式:

  1. 创建独立的FileStateCache(文件状态隔离)。
  2. 编译子Agent专属的System Prompt。
  3. 连接必要的MCP服务器。
  4. 启动独立的query()循环。
  5. 记录侧链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(记忆整合)

这是最有趣的设计。当满足两个条件时:

  1. 距离上次整合 ≥ 24小时。
  2. 期间积累了 ≥ 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_SEARCH gate下,每轮对话都会预取可能相关的skill。

Hooks 系统(不是 React Hooks)

utils/hooks/下有一套独立的执行生命周期hook系统:

Hook 类型说明
postSamplingHooks模型输出后处理
execAgentHookAgent 调用前后
execPromptHookLLM prompt 前后
execHttpHookHTTP 请求/响应前后
sessionHooksSession 级函数 hook
registerSkillHooksSkill 定义的 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分钟,只有AsyncHookRegistryasyncTimeout默认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层的分析框架:

层级名称做什么
1Quote Extraction三路并行解析引号:保留双引号、完全去引号、保留引号字符(检测'x'#等边缘情况)
2Dangerous Patterns阻止命令替换$()、进程替换<()、zsh equals expansion =curl、heredoc in sub
3Zsh-Specific Threats阻止zmodload(通向mapfile/zpty/ztcp)、emulate -c(eval等价)、zf_rm等绕过检查的builtin
4Path Validation每命令独立提取路径(cd/rm/cp/mv/git/jq/sed...),正确处理--终止符
5Advanced ThreatsIFS注入、大括号展开、控制字符、Unicode空白、嵌入换行、/proc/self/environ访问
6Destructive Warningrm -rfgit reset --hardDROP TABLEkubectl deleteterraform destroy等(展示警告但不阻止)
7Sed Edit Parser解析sed -i为文件编辑操作(BRE↔ERE转换用null-byte sentinel防注入),处理macOS -i后缀差异
8Permission 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 / Assistantfeature('KAIROS')类似 assistant 模式,有 PushNotification、SendUserFile、SubscribePR、session chooser、install wizard
Coordinatorfeature('COORDINATOR_MODE')多 Agent 协调者模式
Bridge / Remotebridge/ + 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返回trueonTurnComplete是空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(initialState: T, onChange?: OnChange): Store {let state = initialStateconst 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就够了。

十六、局限与坑

  1. 上下文窗口仍然是硬约束:尽管有auto compact,但压缩必然丢失信息。连续失败3次后直接放弃压缩。
  2. 权限系统复杂度高:多阶段权限检查 + 分类器 + hook系统 + 并发竞速,维护成本一定很高。
  3. 循环依赖问题:大量lazy require说明模块边界设计还有改进空间。
  4. Feature Flag膨胀:COORDINATOR_MODE、KAIROS、PROACTIVE、AGENT_TRIGGERS、HISTORY_SNIP、REACTIVE_COMPACT、CONTEXT_COLLAPSE、WEB_BROWSER_TOOL... flag数量已经很多了。
  5. Bun锁定:深度使用bun:bundlefeature(),意味着与Bun强绑定。
  6. Forked Agent的隐性API成本:Auto Memory、Session Memory、Agent Summary、Prompt Suggestion、Auto Dream、Magic Docs —— 每个后台forked agent都是一次API调用。虽然有prompt cache共享优化,但长对话中后台调用的累计成本是可观的。
  7. 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参考实现。

免责声明

本网站新闻资讯均来自公开渠道,力求准确但不保证绝对无误,内容观点仅代表作者本人,与本站无关。若涉及侵权,请联系我们处理。本站保留对声明的修改权,最终解释权归本站所有。

相关阅读

更多
欢迎回来 登录或注册后,可保存提示词和历史记录
登录后可同步收藏、历史记录和常用模板
注册即表示同意服务条款与隐私政策