Claude Code 原始碼外洩:拆解一個工業級 AI Coding Agent 到底是怎麼造出來的

外洩的 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 依賴(語法高亮 diff、模糊檔案搜尋、yoga flexbox 佈局),顯著縮小了 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 鑰匙圈預讀(OAuth + API key)

這裡的設計思路是:把昂貴的 I/O 操作(鑰匙圈讀取、MDM 配置讀取)提前到 import 階段並行執行。因為 Node/Bun 的 import 本身需要大約 135ms 來載入模組,這段時間正好可以用來做非同步預熱。

整個互動式啟動序列大致如下:

(注意:-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 可以看到,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 迴圈。主要工程亮點如下。

幾個工程亮點:

  1. 流式工具執行(StreamingToolExecutor)

Claude Code 不是等 LLM 完整輸出後再執行工具,而是在流式回應的過程中就開始準備工具呼叫:

import { StreamingToolExecutor } from './services/tools/StreamingToolExecutor.js';
  1. 工具並行 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 會根據指令內容決定。

  1. 自動上下文壓縮(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 保留了更多細粒度上下文。

  1. 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 最值得學習的部分之一。真實的權限檢查流程比「四道關」複雜得多,大致如下:

(流程圖略述:deny rules → hooks/classifier → ask rules → tool 自檢 → permission mode → 使用者確認 / classifier 並發競速 → 實際執行)

一個重要細節: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 的運作方式:

  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 + systemContext
  • buildEffectiveSystemPrompt() ← REPL 層的最終組裝

最終送給模型的 request 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 模式下直接關閉。

第一層: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 = 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
    • 使用者可在 /plugin UI 中 enable/disable
    • 可提供 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_SEARCH gate 下,每輪對話會預取可能相關的 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 分鐘,只有 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 Hash + 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,single-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(zsh 特有威脅)
    • 阻止 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 -rfgit reset --hardDROP TABLEkubectl deleteterraform destroy 等(展示警告但不直接阻止)。
  7. Sed Edit Parser(sed 編輯解析器)
    • 解析 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 返回 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 測試」。

  1. 懶載入打破循環依賴

專案中大量使用 require() 延遲載入:

// Lazy require to avoid circular dependency
const getTeammateUtils = () =>
  require('./utils/teammate.js') as typeof import('./utils/teammate.js')

這是大型 TypeScript 專案常見的痛點解法。

  1. Memoize 無處不在
import memoize from 'lodash-es/memoize.js'

export const getGitStatus = memoize(async () => { ... })

系統上下文、git 狀態等昂貴操作都被 memoize,避免同一次對話中重複計算。

  1. 內部使用者 vs 外部使用者
process.env.USER_TYPE === 'ant'  // Anthropic 內部員工

內部員工有額外的工具(REPLTool、ConfigTool、TungstenTool、SuggestBackgroundPRTool),外部使用者看不到。

  1. 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 自己重新渲染。

  1. 狀態管理: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 就夠了。

十六、局限與坑

  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 參考實作。


原文出處:https://juejin.cn/post/7623066322222153728


精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝9   💬10   ❤️3
318
🥈
我愛JS
📝2   💬6   ❤️2
114
🥉
💬1  
4
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
📢 贊助商廣告 · 我要刊登