Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

前言

Claude Code 是 Anthropic 官方推出的命令行工具,为开发者提供强大的 AI 辅助编程能力。本文档深入解析 Claude Code 的源码实现,涵盖架构设计、核心模块、终端 UI 实现等各个方面。

关于本文档

本文档面向希望深入理解 Claude Code 内部实现的开发者,通过源码分析帮助读者:

  • 理解 AI Agent 工具的设计模式
  • 学习终端 UI 的实现技巧
  • 掌握复杂 CLI 工具的架构设计

章节结构

  • 第一部分(第1-3章):项目概览、核心概念与架构设计
  • 第二部分(第4-8章):核心功能模块,包括查询引擎、工具系统、技能系统等
  • 第三部分(第9-12章):UI 层与服务层实现
  • 附录:类型定义、配置参考与修复记录

第1章:项目概览

本章介绍 Claude Code 项目的整体情况,包括项目定位、技术栈、目录结构和源码泄露背景。

1.1 项目定位

Claude Code 是 Anthropic 官方推出的命令行 AI 编程助手工具,为开发者提供在终端中直接与 Claude AI 交互的能力。

功能特性

Note

Claude Code 定位为「Agentic CLI Tool」,与传统的代码补全工具(如 GitHub Copilot)不同,它能够理解复杂任务并自主执行多步骤操作。

核心功能包括:

功能类别具体特性
代码操作Read、Edit、Write、Grep、Glob 文件操作
命令执行Bash 命令运行,支持后台任务
网络能力WebFetch、WebSearch 网络信息获取
多 AgentAgentTool 支持子 Agent 派发
技能系统Skill 机制扩展能力边界
MCP 协议支持外部 MCP Server 集成
权限管理多级权限控制和安全检查

在 AI 编码工具栈中的位置

graph TD
    subgraph "Layer 1: 代码补全"
        C1[GitHub Copilot]
        C2[Cursor Inline]
    end
    
    subgraph "Layer 2: 对话式编码"
        CC[Claude Code]
        CD[Codex CLI]
        CX[Cursor Chat]
    end
    
    subgraph "Layer 3: 任务编排"
        OM[OpenMatrix]
        AA[AutoGPT]
    end
    
    C1 --> CC
    C2 --> CC
    CC --> OM
    
    L1["解决「写代码」"] -.-> C1
    L2["解决「复杂任务」"] -.-> CC  
    L3["解决「可信交付」"] -.-> OM

Claude Code 位于 Layer 2,介于代码补全工具和任务编排系统之间:

  • 向下承接:能够理解代码上下文,执行代码生成、修改、调试等操作
  • 向上支撑:为 OpenMatrix 等编排系统提供原子级执行能力

1.2 技术栈概览

graph TD
    A[Bun Runtime] --> B[TypeScript]
    B --> C[React + Ink TUI]
    B --> D[Anthropic SDK]
    B --> E[MCP SDK]
    D --> F[Messages API]
    D --> G[Tool Use]
    D --> H[Streaming]

核心技术栈

类别技术版本说明
运行时Bun-高性能 JS 运行时,替代 Node.js
语言TypeScript-类型安全的开发体验
终端 UIReact + Ink^6.8.0React 风格的终端组件库
CLI 框架Commander.js^14.0.0命令行参数解析
AI SDK@anthropic-ai/sdk^0.80.0Anthropic Messages API
协议@modelcontextprotocol/sdk^1.29.0MCP 协议实现

关键依赖

{
  "dependencies": {
    "@anthropic-ai/sdk": "^0.80.0",      // Claude API 客户端
    "@modelcontextprotocol/sdk": "^1.29.0", // MCP 协议支持
    "ink": "^6.8.0",                     // 终端 React 渲染
    "react": "^19.2.4",                  // React 核心(内部预发布版)
    "chalk": "^5.6.2",                   // 终端颜色输出
    "execa": "^9.6.1",                   // 进程执行
    "chokidar": "^5.0.0",                // 文件监听
    "ws": "^8.20.0",                     // WebSocket 通信
    "zod": "^4.3.6"                      // Schema 验证(内部版)
  }
}

Warning

部分依赖版本号来自泄露源码,可能是 Anthropic 内部预发布版本,与 npm 公开版本不一致。公开版本参考:React 18.3.1、Zod 3.24.x。

Tip

Bun 作为运行时的优势:内置 TypeScript 支持、更快的包管理、原生 ESM 模块加载,无需预编译即可运行 .ts 文件。

1.3 项目结构一览

graph TD
    E[bin/claude-haha<br/>入口脚本] --> C[cli.tsx<br/>CLI入口]
    C --> M[main.tsx<br/>TUI主逻辑 804KB]
    M --> Q[QueryEngine.ts<br/>查询引擎]
    M --> T[Tool.ts<br/>工具基类]
    T --> TOOLS[tools/<br/>46个工具]
    M --> SKILLS[skills/<br/>Skill系统]
    M --> CMDS[commands/<br/>斜杠命令]
    M --> SERV[services/<br/>服务层]
    M --> UI[ink/components/<br/>终端UI]

目录树结构(精简)

claude-code-haha-main/
├── bin/claude-haha              # 入口脚本
├── preload.ts                   # Bun preload(全局变量设置)
├── src/
│   ├── entrypoints/cli.tsx      # CLI 主入口
│   ├── main.tsx                 # 🔴 TUI 主逻辑(804KB)
│   ├── QueryEngine.ts           # 🔴 查询引擎(46KB)
│   ├── Tool.ts                  # 🔴 工具基类(29KB)
│   ├── tools.ts                 # 工具注册表(17KB)
│   ├── commands.ts              # 斜杠命令注册表(25KB)
│   │
│   ├── tools/                   # 🔴 46个工具子目录
│   │   ├── BashTool/            # - Bash 命令执行
│   │   ├── FileEditTool/        # - 文件编辑
│   │   ├── AgentTool/           # - 子 Agent 派发
│   │   ├── SkillTool/           # - Skill 调用
│   │   ├── MCPTool/             # - MCP 协议
│   │   └── ...                  # 其他 41 个工具
│   │
│   ├── skills/                  # 🔴 Skill 系统
│   │   ├── bundled/             # 内置 Skills 目录
│   │   └── bundledSkills.ts     # Skill 注册机制
│   │
│   ├── commands/                # 🔴 斜杠命令
│   │   ├── init.ts              # /init 命令
│   │   ├── review.ts            # /review 命令
│   │   ├── mcp/                 # /mcp 子命令组
│   │   └── ...
│   │
│   ├── services/                # 服务层(MCP/API/OAuth/LSP)
│   ├── ink/                     # Ink 终端渲染引擎
│   ├── components/              # UI 组件库
│   ├── hooks/                   # React hooks
│   ├── utils/                   # 工具函数
│   └── types/                   # TypeScript 类型定义
└── .env.example                 # 环境变量模板

🔴 标记为核心分析目标,将在后续章节深入解析。

核心文件说明

文件大小说明
main.tsx804KBTUI 主逻辑,包含 Commander 命令解析、React/Ink 渲染、消息处理循环
QueryEngine.ts46KB查询引擎,处理 API 请求和响应流
Tool.ts29KB工具基类定义,包含类型和执行框架
tools.ts17KB工具注册表,汇总所有可用工具
commands.ts25KB斜杠命令注册表

Warning

main.tsx 文件体积达 804KB,推测是编译打包产物而非源码形式,后续章节将详细分析其结构。

1.4 泄露源码背景与修复说明

免责声明

本源码基于 2025-03-31 从 Anthropic npm registry 泄露的 Claude Code 源码。所有原始源码版权归 Anthropic 所有。仅供学习和研究用途,请勿用于商业或生产环境。

泄露背景

2025年3月31日,Anthropic 的内部 npm registry 发生配置错误,导致 Claude Code 的完整源码被意外公开。泄露的源码无法直接运行,需要修复多个阻塞问题才能启动。

修复记录

问题根因分析修复方案
TUI 不启动入口脚本把无参数启动路由到了 recovery CLI恢复走 cli.tsx 完整入口
启动卡死verify skill 导入缺失的 .md 文件,Bun text loader 无限挂起创建 stub .md 文件
--print 卡死filePersistence/types.ts 缺失创建类型桩文件
--print 卡死ultraplan/prompts.txt 缺失创建资源桩文件
Enter 键无响应modifiers-napi native 包缺失,isModifierPressed() 抛异常加 try-catch 容错
setup 被跳过preload.ts 自动设置 LOCAL_RECOVERY=1 跳过初始化移除默认设置
flowchart LR
    O1[TUI 不启动] --> F1[修复入口路由] --> R1[完整 TUI]
    O2[启动卡死] --> F2[创建 stub 文件] --> R2[正常交互]
    O3[Enter 无响应] --> F3[添加容错] --> R3[API 连通]

本仓库定位

本仓库 (claude-code-haha-main) 是修复后的版本,主要改动:

  1. 入口修复:恢复正确的 CLI 启动路径
  2. 依赖补全:创建缺失的 stub 文件
  3. 容错增强:对 native 模块缺失添加 fallback
  4. API 兼容:支持自定义 API 端点(如 MiniMax、OpenRouter)

Tip

修复后可以完整运行 Ink TUI 交互界面,与官方 Claude Code 体验一致。支持 --print 无头模式和 MCP 服务器接入。


下一章将深入探讨 Claude Code 的核心概念,包括 Agent、Tool、Skill、MCP 协议等。

第2章:核心概念

本章介绍 Claude Code 中的核心概念和术语,包括 Agent、Tool、Skill、MCP 协议、权限系统和会话管理。

2.1 Agent 模型定义

Agent 的角色和职责

在 Claude Code 中,Agent 是执行任务的智能实体。它接收用户输入,通过 LLM 进行推理,选择合适的工具执行操作,最终返回结果。

graph TD
    subgraph AgentCore["Agent 核心"]
        A[LLM 推理引擎]
        B[上下文管理]
        C[工具选择器]
    end
    
    subgraph Inputs["输入"]
        I1[用户消息]
        I2[系统提示]
        I3[历史上下文]
    end
    
    subgraph Outputs["输出"]
        O1[文本响应]
        O2[工具调用]
        O3[状态更新]
    end
    
    I1 --> A
    I2 --> A
    I3 --> B
    B --> A
    A --> C
    C --> O2
    A --> O1
    B --> O3

Agent 的核心职责:

职责说明
理解意图解析用户输入,理解任务目标
规划执行决定使用哪些工具、按什么顺序执行
调用工具生成工具调用请求(Tool Use)
处理结果解析工具返回结果,决定下一步行动
维护上下文管理对话历史、文件状态、会话信息

Agent 与 Tool 的关系

sequenceDiagram
    participant U as User
    participant A as Agent
    participant T as Tool
    participant S as System
    
    U->>A: 输入请求
    A->>A: 推理分析
    A->>T: 选择并调用工具
    T->>S: 执行操作
    S->>T: 返回结果
    T->>A: 工具结果
    A->>A: 处理结果
    A->>U: 输出响应
    
    note over A,T: Agent 决策使用哪个 Tool
    note over T,S: Tool 执行具体操作

Agent 是决策层,Tool 是执行层

  • Agent 负责「做什么」—— 根据用户意图选择工具
  • Tool 负责「怎么做」—— 执行具体的系统操作

Note

Claude Code 的 Agent 模型是单轮对话驱动的:每次用户输入触发一轮 API 调用,Agent 在该轮内决定使用哪些工具。这与 AutoGPT 等自主循环模式不同,Agent 的执行边界由用户交互划定。

2.2 Tool 工具概念

工具的定义

Tool 是 Claude Code 与外部系统交互的标准化接口。每个工具定义了:

  • 名称(name)
  • 描述(description)
  • 输入 Schema(JSON Schema)
  • 执行逻辑(execute 函数)
// Tool.ts 中的核心类型定义
export type Tool<TTools extends Tools = Tools> = {
  name: string                  // 工具名称
  description: string           // 工具描述
  inputSchema: ToolInputJSONSchema  // 输入参数 Schema
  execute: (                    // 执行函数
    args: unknown,
    context: ToolUseContext
  ) => Promise<ToolResult>
  progress?: ToolProgress       // 进度回调
  validate?: (args: unknown) => ValidationResult  // 参数验证
}

工具的类型分类

Claude Code 提供约 46 种工具,按功能分类:

graph TD
    subgraph FileOps["文件操作"]
        F1[FileReadTool]
        F2[FileEditTool]
        F3[FileWriteTool]
        F4[GlobTool]
        F5[GrepTool]
    end
    
    subgraph System["系统操作"]
        S1[BashTool]
        S2[PowerShellTool]
        S3[NotebookEditTool]
    end
    
    subgraph Network["网络操作"]
        N1[WebFetchTool]
        N2[WebSearchTool]
    end
    
    subgraph AgentOps["Agent 操作"]
        A1[AgentTool]
        A2[SkillTool]
        A3[TaskCreateTool]
        A4[TaskStopTool]
    end
    
    subgraph MCP["MCP 操作"]
        M1[MCPTool]
        M2[ListMcpResourcesTool]
        M3[ReadMcpResourceTool]
    end
    
    subgraph UI["UI 操作"]
        U1[AskUserQuestionTool]
        U2[TodoWriteTool]
    end
    
    subgraph Misc["其他"]
        X1[EnterWorktreeTool]
        X2[ExitWorktreeTool]
        X3[ConfigTool]
        X4[LSPTool]
    end

工具类型详细说明

类别工具说明
文件操作FileReadTool读取文件内容,支持图片、PDF
FileEditTool编辑文件,支持精确字符串替换
FileWriteTool创建或覆写文件
GlobTool文件模式匹配搜索
GrepTool文件内容正则搜索
系统操作BashTool执行 Shell 命令
PowerShellToolWindows PowerShell 命令
网络操作WebFetchTool获取 URL 内容
WebSearchToolWeb 搜索
Agent 操作AgentTool派发子 Agent 执行任务
SkillTool调用 Skill 扩展能力
MCP 操作MCPTool调用 MCP Server 工具
状态管理TodoWriteTool任务列表管理
工作流EnterWorktreeTool进入 Git Worktree

工具调用流程

sequenceDiagram
    participant LLM as Claude LLM
    participant QE as QueryEngine
    participant TR as Tool Registry
    participant T as Tool Instance
    participant PM as Permission Manager
    participant SYS as System
    
    LLM->>QE: Tool Use Block
    QE->>TR: lookup(tool_name)
    TR->>QE: Tool Instance
    QE->>PM: check_permission(tool, args)
    PM->>QE: Permission Result
    
    alt Permission Granted
        QE->>T: execute(args, context)
        T->>SYS: Perform Operation
        SYS->>T: Result
        T->>QE: Tool Result Block
        QE->>LLM: Return Result
    else Permission Denied
        PM->>QE: Deny Message
        QE->>LLM: Permission Error
    end

Tip

工具执行前会进行权限检查。用户可以在配置中设置 alwaysAllowalwaysDenyalwaysAsk 规则,控制工具的自动执行行为。

2.3 Skill 技能概念

Skill 与 Tool 的区别

Skill 是一种特殊的「宏工具」,它不是执行单一操作,而是注入一组预定义的提示词和行为模板。

graph TD
    subgraph Tool["Tool 模型"]
        T1[单一操作]
        T2[明确参数]
        T3[原子执行]
    end
    
    subgraph Skill["Skill 模型"]
        S1[提示词注入]
        S2[行为模板]
        S3[上下文扩展]
        S4[允许工具列表]
    end
    
    Tool --> |"底层执行"| Skill
维度ToolSkill
粒度原子操作(如读文件)任务模板(如代码审查)
定义TypeScript 实现Markdown 提示词
执行系统调用LLM 推理 + Tool 组合
扩展需要代码开发用户可自定义 .md 文件

Skill 的加载机制

flowchart TD
    A[启动] --> B{Skill 来源}
    B --> |"bundled"| C[内置 Skills]
    B --> |"disk"| D[磁盘 Skills]
    B --> |"plugin"| E[插件 Skills]
    
    C --> F[registerBundledSkill]
    D --> G[loadSkillsDir]
    E --> H[loadPluginSkills]
    
    F --> I[Skill Registry]
    G --> I
    H --> I
    
    I --> J[Command Registry]

Skill 的三种来源:

  1. Bundled Skills:编译内置的 Skills,随 CLI 二进制分发
  2. Disk Skills:用户目录 ~/.claude/commands/ 下的 .md 文件
  3. Plugin Skills:通过插件系统加载的外部 Skills
// bundledSkills.ts 中的 Skill 定义
export type BundledSkillDefinition = {
  name: string                  // Skill 名称
  description: string           // 描述
  aliases?: string[]            // 别名
  whenToUse?: string            // 使用时机说明
  argumentHint?: string         // 参数提示
  allowedTools?: string[]       // 允许使用的工具列表
  model?: string                // 指定模型
  hooks?: HooksSettings         // 钩子配置
  getPromptForCommand: (args, context) => Promise<ContentBlockParam[]>
}

内置 Skills 示例

Skill用途
init初始化 CLAUDE.md 项目文档
review代码审查 PR
security-review安全审查
update-config更新配置
keybindings-help键位绑定帮助
simplify代码简化优化
claude-apiClaude API 调试
loop定时循环执行

Note

Skills 通过 /skill-name 斜杠命令调用。例如 /init 触发 init skill,/review 触发 review skill。

2.4 MCP 协议概念

MCP (Model Context Protocol) 简介

MCP 是 Anthropic 推出的开放协议,用于连接 LLM 应用与外部数据源和工具。

graph TD
    subgraph ClaudeCode["Claude Code"]
        A[MCP Client]
    end
    
    subgraph MCPServer["MCP Server"]
        B1[GitHub MCP]
        B2[Postgres MCP]
        B3[Filesystem MCP]
        B4[Custom MCP]
    end
    
    subgraph Resources["资源"]
        R1[GitHub API]
        R2[数据库]
        R3[文件系统]
        R4[自定义服务]
    end
    
    A --> |"MCP Protocol"| B1
    A --> |"MCP Protocol"| B2
    A --> |"MCP Protocol"| B3
    A --> |"MCP Protocol"| B4
    
    B1 --> R1
    B2 --> R2
    B3 --> R3
    B4 --> R4

MCP 协议的核心概念:

概念说明
ToolsMCP Server 提供的可调用工具
ResourcesMCP Server 提供的可读资源(如文件、数据库记录)
PromptsMCP Server 提供的预定义提示词模板
SamplingMCP Server 请求 LLM 生成内容的能力

MCP Server 与 Claude Code 的交互

sequenceDiagram
    participant CC as Claude Code
    participant MCP as MCP Client
    participant Server as MCP Server
    
    CC->>MCP: 启动时连接 Server
    MCP->>Server: initialize
    Server->>MCP: capabilities
    
    MCP->>Server: listTools
    Server->>MCP: tools list
    MCP->>CC: 注册工具
    
    MCP->>Server: listResources
    Server->>MCP: resources list
    MCP->>CC: 注册资源
    
    note over CC,Server: 用户交互中
    
    CC->>MCP: Tool Use (MCP Tool)
    MCP->>Server: callTool
    Server->>MCP: result
    MCP->>CC: Tool Result

Tip

MCP 协议让 Claude Code 能够接入任意外部系统,无需修改核心代码。用户只需配置 MCP Server 即可扩展能力边界。

2.5 Permission 权限系统

权限检查流程

Claude Code 实现了细粒度的权限控制,确保工具执行的安全性。

flowchart TD
    A[Tool Use Request] --> B{检查规则}
    
    B --> C[alwaysAllowRules]
    B --> D[alwaysDenyRules]
    B --> E[alwaysAskRules]
    
    C --> F{匹配?}
    D --> G{匹配?}
    E --> H{匹配?}
    
    F --> |"Yes"| I[自动允许]
    F --> |"No"| J[继续检查]
    
    G --> |"Yes"| K[自动拒绝]
    G --> |"No"| J
    
    H --> |"Yes"| L[弹出确认]
    H --> |"No"| M[默认模式]
    
    J --> M
    M --> N{Permission Mode}
    
    N --> |"accept"| I
    N --> |"default"| L
    N --> |"bypass"| I
    
    L --> O{用户决策}
    O --> |"Allow"| I
    O --> |"Deny"| K
    O --> |"Allow Always"| P[添加到 alwaysAllow]

权限类型

// 权限类型定义
export type PermissionMode = 
  | 'default'    // 每次询问
  | 'accept'     // 自动接受(但检查规则)
  | 'bypass'     // 绕过所有检查(危险模式)

export type PermissionResult = 
  | 'allow'      // 允许执行
  | 'deny'       // 拒绝执行
  | 'ask'        // 需要用户确认

权限规则配置示例:

{
  "alwaysAllow": [
    "Bash(npm install)",
    "Read(**/*.ts)",
    "Grep(**/*)"
  ],
  "alwaysDeny": [
    "Bash(rm -rf /)",
    "Write(/etc/*)"
  ],
  "alwaysAsk": [
    "Bash(git push)"
  ]
}

安全警告

bypass 权限模式会绕过所有安全检查,仅建议在隔离环境或完全信任的场景下使用。

2.6 Session 会话概念

会话生命周期

Session 代表一次完整的用户交互周期,从启动到退出。

stateDiagram-v2
    [*] --> Init: 启动 CLI
    Init --> Ready: 初始化完成
    Ready --> Active: 用户输入
    Active --> Processing: 处理请求
    Processing --> ToolCall: 工具调用
    ToolCall --> Processing: 工具结果
    Processing --> Active: 响应完成
    Active --> Compact: 上下文压缩
    Compact --> Active: 压缩完成
    Active --> Exit: 用户退出
    Exit --> [*]

Session 的关键阶段:

阶段说明
Init加载配置、连接 MCP Server、初始化状态
Ready等待用户输入
Active处理用户消息、LLM 交互
Processing执行工具调用、处理响应流
Compact上下文压缩,管理 Token 数量
Exit清理资源、保存状态

上下文管理

graph TD
    subgraph Context["上下文组成"]
        C1[System Prompt]
        C2[Tool Definitions]
        C3[Conversation History]
        C4[File State Cache]
    end
    
    subgraph TokenLimit["Token 管理"]
        T1[输入 Token]
        T2[输出 Token]
        T3[总预算]
    end
    
    C1 --> T1
    C2 --> T1
    C3 --> T1
    
    T1 --> L{超出预算?}
    L --> |"Yes"| M[上下文压缩]
    L --> |"No"| N[正常处理]
    
    M --> P[保留关键信息]
    P --> C3

上下文压缩策略:

  1. 消息截断:保留最近 N 条消息
  2. 内容摘要:将长内容替换为摘要
  3. 工具结果压缩:大型工具结果转为引用
  4. 文件状态清理:清理不再相关的文件缓存

Note

Claude Code 使用 200K Token 上下文窗口,但实际可用空间受 System Prompt 和 Tool Definitions 占用影响。上下文压缩会在接近上限时自动触发。


下一章将深入分析 Claude Code 的架构设计,包括整体架构图和请求生命周期。

第3章:架构设计

本章介绍 Claude Code 的整体架构设计,揭示其分层结构、模块依赖关系、数据流向和多 Agent 协作模式。

3.1 架构分层图

Claude Code 采用分层架构设计,清晰分离入口层、核心层和服务层的职责。

graph TD
    subgraph "入口层 Entry Layer"
        CLI[cli.tsx<br/>CLI 主入口]
        CMD[Commander.js<br/>参数解析]
        INIT[init.ts<br/>初始化入口]
    end
    
    subgraph "核心层 Core Layer"
        MAIN[main.tsx<br/>主逻辑入口]
        QE[QueryEngine<br/>查询引擎]
        TOOL[Tool System<br/>工具系统]
        STATE[AppState<br/>状态管理]
    end
    
    subgraph "服务层 Service Layer"
        API[API Service<br/>Anthropic API]
        MCP[MCP Service<br/>MCP 服务器]
        BRIDGE[Bridge<br/>远程控制]
        LSP[LSP Service<br/>语言服务]
    end
    
    subgraph "UI层 UI Layer"
        INK[Ink<br/>终端渲染]
        REPL[REPL.tsx<br/>交互界面]
        COMP[Components<br/>UI组件]
    end
    
    CLI --> MAIN
    CMD --> CLI
    MAIN --> QE
    QE --> TOOL
    TOOL --> API
    TOOL --> MCP
    MAIN --> INK
    INK --> REPL
    REPL --> COMP
    MAIN --> STATE
    MAIN --> BRIDGE
    TOOL --> LSP

层级职责表

层级核心模块主要职责
入口层cli.tsx, Commander.js命令行参数解析、启动模式选择
核心层QueryEngine, Tool System消息处理、工具调用、状态管理
服务层API, MCP, Bridge外部服务通信、协议适配
UI层Ink, REPL, Components终端渲染、用户交互

架构特点

Claude Code 的架构设计强调:1) 入口快速路径(fast-path)优化启动速度;2) React/Ink 终端 UI 提供交互体验;3) MCP 协议支持扩展工具生态。

3.2 模块依赖关系

核心模块依赖图

graph LR
    subgraph "入口点"
        CLI[cli.tsx]
        LOCAL[localRecoveryCli.ts]
    end
    
    subgraph "主流程"
        MAIN[main.tsx]
        SETUP[setup.ts]
        REPL_L[replLauncher.tsx]
    end
    
    subgraph "查询处理"
        QE[QueryEngine.ts]
        QUERY[query.ts]
    end
    
    subgraph "工具系统"
        TOOLS[tools.ts]
        BASH[BashTool]
        EDIT[EditTool]
        GLOB[GlobTool]
        AGENT[AgentTool]
    end
    
    subgraph "命令系统"
        CMDS[commands.ts]
        COMMIT[commit.ts]
        REVIEW[review.ts]
    end
    
    CLI --> MAIN
    CLI --> LOCAL
    MAIN --> SETUP
    MAIN --> REPL_L
    REPL_L --> QE
    QE --> QUERY
    QE --> TOOLS
    TOOLS --> BASH
    TOOLS --> EDIT
    TOOLS --> GLOB
    TOOLS --> AGENT
    MAIN --> CMDS
    CMDS --> COMMIT
    CMDS --> REVIEW

源码目录结构

src/
├── entrypoints/          # 入口点
│   ├── cli.tsx           # CLI 主入口
│   ├── init.ts           # 初始化入口
│   └── agentSdkTypes.ts  # SDK 类型定义
│
├── main.tsx              # 主逻辑(Commander.js + Ink)
├── setup.ts              # 启动初始化
├── replLauncher.tsx      # REPL 启动器
│
├── QueryEngine.ts        # 查询引擎(核心)
├── query.ts              # 查询处理
├── tools.ts              # 工具注册
├── commands.ts           # 命令注册
│
├── ink/                  # Ink 终端渲染引擎
│   ├── components/       # 核心组件
│   │   ├── App.tsx       # 根组件
│   │   └── AppContext.ts # 全局上下文
│   ├── events/           # 事件系统
│   ├── reconciler.ts     # React reconciler
│   └── termio/           # 终端 I/O
│
├── components/           # UI 组件
│   ├── PromptInput/      # 输入组件
│   ├── Messages.tsx      # 消息渲染
│   └── Spinner/          # 加载指示器
│
├── tools/                # Agent 工具
│   ├── BashTool/         # Shell 命令
│   ├── FileEditTool/     # 文件编辑
│   ├── FileReadTool/     # 文件读取
│   ├── GlobTool/         # 文件匹配
│   ├── GrepTool/         # 内容搜索
│   ├── AgentTool/        # 子 Agent
│   ├── WebFetchTool/     # 网页获取
│   └── ...               # 其他工具
│
├── commands/             # 斜杠命令
│   ├── commit.ts         # /commit
│   ├── review.ts         # /review
│   ├── init.ts           # /init
│   └── ...               # 其他命令
│
├── services/             # 服务层
│   ├── api/              # API 服务
│   ├── mcp/              # MCP 服务
│   ├── lsp/              # LSP 服务
│   └── analytics/        # 分析服务
│
├── state/                # 状态管理
│   ├── AppStateStore.ts  # 应用状态
│   └── store.ts          # 状态存储
│
├── utils/                # 工具函数
│   ├── config.ts         # 配置管理
│   ├── auth.ts           # 认证
│   ├── permissions/      # 权限系统
│   └── hooks/            # Hook 系统
│
├── skills/               # Skill 系统
│   ├── bundled/          # 内置 Skills
│   └── ...               # 其他 Skills
│
├── bridge/               # 远程控制
│   ├── bridgeMain.ts     # Bridge 主逻辑
│   ├── bridgeApi.ts      # Bridge API
│   └── ...               # 其他 Bridge 模块
│
├── bootstrap/            # 启动状态
│   └── state.ts          # 全局状态
│
├── buddy/                # Companion 伴侣
│   ├── companion.ts      # 伴侣逻辑
│   └── sprites.ts        # 动画精灵
│
├── context.ts            # 上下文管理
├── Tool.ts               # 工具基类
├── Task.ts               # 任务定义
└── tasks.ts              # 任务管理

关键模块依赖关系

graph TD
    subgraph "入口依赖链"
        CLI --> MAIN
        MAIN --> SETUP
        MAIN --> CMDS_GET[getCommands]
        MAIN --> REPL_L
    end
    
    subgraph "QueryEngine 依赖"
        QE --> QUERY
        QE --> TOOLS_GET[getTools]
        QE --> STATE_GET[getAppState]
        QE --> PERMISSIONS[permissionCheck]
    end
    
    subgraph "工具依赖链"
        TOOLS --> TOOL_IMPL[Tool Implementations]
        TOOL_IMPL --> SERVICES[Services]
        SERVICES --> API
        SERVICES --> MCP
        SERVICES --> LSP
    end
    
    subgraph "UI 依赖链"
        INK --> TERMINAL[Terminal I/O]
        REPL --> COMP_IMPL[Component Implementations]
        REPL --> STATE
    end

模块组织原则

模块按职责划分:入口模块负责启动;核心模块负责业务逻辑;服务模块负责外部通信;UI 模块负责渲染。依赖关系单向流动,避免循环依赖。

3.3 数据流向图

用户输入到响应输出的完整数据流

sequenceDiagram
    participant U as 用户
    participant REPL as REPL.tsx
    participant QE as QueryEngine
    participant TOOL as Tool System
    participant API as API Service
    participant MCP as MCP Service
    participant OUT as 终端输出
    
    U->>REPL: 输入 Prompt
    REPL->>QE: 提交用户消息
    QE->>QE: processUserInput()
    QE->>API: 发送 API 请求
    API->>QE: 返回 Assistant 响应
    
    loop 工具调用循环
        QE->>TOOL: 解析 tool_use
        TOOL->>TOOL: 执行工具
        TOOL->>MCP: MCP 工具调用(可选)
        MCP->>TOOL: 返回 MCP 结果
        TOOL->>QE: 返回工具结果
        QE->>API: 继续对话
        API->>QE: 下一步响应
    end
    
    QE->>REPL: 更新消息列表
    REPL->>OUT: 渲染输出
    OUT->>U: 显示结果

数据流关键节点

节点输入输出处理逻辑
REPL.tsx用户键盘输入UserMessage输入处理、历史记录
QueryEngineMessage[]AssistantMessage消息组装、API 调用
Tool Systemtool_use blocktool_result工具匹配、权限检查、执行
API Servicemessages + toolsstream responseAnthropic API 通信
MCP ServiceMCP requestMCP responseMCP 协议适配

消息处理流程

graph TD
    subgraph "输入处理"
        A[用户输入] --> B[PromptInput]
        B --> C[processUserInput]
        C --> D[createUserMessage]
    end
    
    subgraph "查询构建"
        D --> E[fetchSystemPromptParts]
        E --> F[buildQueryContext]
        F --> G[组装 Messages]
    end
    
    subgraph "API 调用"
        G --> H[prepareApiRequest]
        H --> I[streamResponse]
        I --> J[accumulateUsage]
    end
    
    subgraph "响应处理"
        J --> K[parseContentBlocks]
        K --> L{有 tool_use?}
        L --> |"是"| M[执行工具]
        L --> |"否"| N[完成响应]
        M --> O[返回 tool_result]
        O --> H
    end
    
    subgraph "输出渲染"
        N --> P[updateMessages]
        P --> Q[renderMessages]
        Q --> R[终端显示]
    end

状态同步机制

graph LR
    subgraph "状态流向"
        INPUT[用户输入] --> STATE[AppState]
        STATE --> QE[QueryEngine]
        QE --> STATE_UPDATE[状态更新]
        STATE_UPDATE --> UI[UI 渲染]
        UI --> RENDER[渲染输出]
    end
    
    subgraph "持久化"
        STATE --> STORAGE[SessionStorage]
        STORAGE --> FILE[JSONL 文件]
    end

状态一致性

状态变更需要立即持久化到 SessionStorage(JSONL 文件),确保崩溃恢复和 Context 压缩后可恢复。

3.4 多 Agent 架构图

Claude Code 支持多 Agent 协作模式,通过 AgentTool 实现子 Agent 派发和结果整合。

Agent 协作模式

graph TD
    subgraph "主 Agent"
        MAIN_AGENT[Main Agent<br/>QueryEngine]
        ORCHESTRATOR[Orchestrator<br/>任务派发]
    end
    
    subgraph "子 Agent 类型"
        EXPLORE[Explore Agent<br/>代码探索]
        PLAN[Plan Agent<br/>任务规划]
        CODE[Code Agent<br/>代码实现]
        RESEARCH[Research Agent<br/>信息检索]
        CUSTOM[Custom Agent<br/>自定义]
    end
    
    subgraph "协作机制"
        DISPATCH[Task Dispatch<br/>任务派发]
        ISOLATE[Worktree Isolation<br/>工作树隔离]
        AGGREGATE[Result Aggregation<br/>结果聚合]
        SYNC[State Sync<br/>状态同步]
    end
    
    MAIN_AGENT --> ORCHESTRATOR
    ORCHESTRATOR --> DISPATCH
    DISPATCH --> EXPLORE
    DISPATCH --> PLAN
    DISPATCH --> CODE
    DISPATCH --> RESEARCH
    DISPATCH --> CUSTOM
    
    EXPLORE --> ISOLATE
    PLAN --> ISOLATE
    CODE --> ISOLATE
    
    ISOLATE --> AGGREGATE
    AGGREGATE --> SYNC
    SYNC --> MAIN_AGENT

Agent 定义结构

interface AgentDefinition {
  name: string;              // Agent 名称
  color: AgentColorName;     // 颜色标识(用于 UI)
  description: string;       // 功能描述
  model?: string;            // 指定模型
  tools?: string[];          // 可用工具列表
  capabilities?: string[];   // 能力声明
  systemPrompt?: string;     // 自定义系统提示
  isolated?: boolean;        // 是否隔离执行
}

Agent 工具调用流程

sequenceDiagram
    participant MAIN as Main Agent
    participant AT as AgentTool
    participant WT as Worktree
    participant SUB as Sub Agent
    participant API as API Service
    
    MAIN->>AT: 检测 Agent 调用请求
    AT->>AT: 解析 AgentDefinition
    AT->>WT: 创建隔离 Worktree(可选)
    WT->>SUB: 启动 Sub Agent
    SUB->>API: 发送请求
    API->>SUB: 返回响应
    loop 工具执行
        SUB->>SUB: 执行工具
        SUB->>API: 继续对话
    end
    SUB->>AT: 返回最终结果
    AT->>WT: 清理 Worktree(可选)
    AT->>MAIN: 返回聚合结果

Agent 颜色系统

Agent 使用颜色标识区分,便于 UI 渲染:

颜色名称用途
blue探索 Agent代码探索、文件搜索
yellow规划 Agent任务分解、方案设计
green实现 Agent代码编写、编辑
purple研究 Agent信息检索、分析
orange自定义 Agent用户定义的 Agent

Worktree 隔离

Agent 可以在独立 Git Worktree 中执行,隔离文件修改,避免与主工作区冲突。通过 --worktree 参数或 AgentDefinition.isolated 启用。

并行 Agent 执行

graph TD
    subgraph "并行派发"
        A[主 Agent] --> B[分析任务]
        B --> C{可并行?}
        C --> |"是"| D[派发多个 Agent]
        C --> |"否"| E[串行执行]
        
        D --> F[Agent 1]
        D --> G[Agent 2]
        D --> H[Agent 3]
        
        F --> I[结果聚合]
        G --> I
        H --> I
        
        I --> J[整合响应]
        E --> J
        J --> K[返回主对话]
    end

Teammate 模式(多人协作)

graph TD
    subgraph "Teammate 架构"
        SWARM[Swarm Orchestrator]
        TM1[Teammate 1]
        TM2[Teammate 2]
        TM3[Teammate 3]
        
        SHARED[Shared Context<br/>Team Memory]
        MSG[Message Bus<br/>UDS Messaging]
    end
    
    SWARM --> TM1
    SWARM --> TM2
    SWARM --> TM3
    
    TM1 --> SHARED
    TM2 --> SHARED
    TM3 --> SHARED
    
    TM1 --> MSG
    TM2 --> MSG
    TM3 --> MSG
    
    MSG --> SWARM

Agent Swarms

Agent Swarms 是高级协作模式,支持多个 Agent 同时工作,通过 Team Memory 同步状态,通过 UDS Messaging 传递消息。需要 isAgentSwarmsEnabled() 开启。

3.5 服务层架构

API 服务架构

graph TD
    subgraph "API 请求流程"
        REQ[API Request] --> PREPARE[prepareApiRequest]
        PREPARE --> AUTH[Authentication]
        AUTH --> HEADERS[Request Headers]
        HEADERS --> STREAM[Stream Response]
        
        STREAM --> PARSE[Parse Events]
        PARSE --> ACCUMULATE[accumulateUsage]
        ACCUMULATE --> HANDLE[Handle Response]
    end
    
    subgraph "认证方式"
        KEY[API Key<br/>x-api-key header]
        TOKEN[Auth Token<br/>Bearer header]
        OAUTH[OAuth Token<br/>Claude AI]
    end
    
    AUTH --> KEY
    AUTH --> TOKEN
    AUTH --> OAUTH

MCP 服务架构

graph TD
    subgraph "MCP 协议"
        CLIENT[MCP Client]
        TRANSPORT[Transport Layer]
        
        STDIO[Stdio Transport]
        SSE[SSE Transport]
        HTTP[HTTP Transport]
        
        SERVER[MCP Server]
        RESOURCES[Resources]
        TOOLS[Tools]
        PROMPTS[Prompts]
    end
    
    CLIENT --> TRANSPORT
    TRANSPORT --> STDIO
    TRANSPORT --> SSE
    TRANSPORT --> HTTP
    
    STDIO --> SERVER
    SSE --> SERVER
    HTTP --> SERVER
    
    SERVER --> RESOURCES
    SERVER --> TOOLS
    SERVER --> PROMPTS

MCP 工具集成

// MCP 工具转换为 Claude Code 工具
interface McpTool {
  name: string;           // 工具名称(带 server 前缀)
  description: string;    // 工具描述
  inputSchema: JSONSchema; // 输入 schema
  serverName: string;     // 来源服务器
}

// 工具调用流程
McpClient.callTool(toolName, args) -> McpServer -> ToolResult

MCP 协议

Model Context Protocol (MCP) 是 Anthropic 定义的模型上下文协议,支持工具、资源、提示的标准化接入。Claude Code 支持多种传输方式(Stdio、SSE、HTTP)。

3.6 设计原则

快速启动原则

Fast-Path 设计

入口 cli.tsx 实现快速路径优化:

  • --version 零模块加载直接输出
  • --dump-system-prompt 动态加载最小模块
  • 特殊子命令(daemon、bridge、ps)独立处理路径
  • 正常启动延迟加载 main.tsx

状态持久化原则

所有状态变更立即持久化:

持久化原则

  1. 消息历史保存到 JSONL 文件
  2. 工具调用结果记录到日志
  3. 配置变更写入 settings.json
  4. 状态崩溃可恢复

单一职责原则

模块单一职责
cli.tsx快速路径判断、参数解析
main.tsxCommander.js 配置、启动选择
setup.ts环境初始化、预加载
QueryEngine消息处理、API 调用循环
Tool System工具注册、匹配、执行
Ink/App终端渲染、事件处理
REPL用户交互、输入处理

下一章将深入探讨 入口与启动流程 的实现细节。

第4章:入口与启动流程

本章深入分析 Claude Code 的入口点和启动流程,包括 CLI 参数处理、初始化步骤和 REPL 界面启动。

4.1 cli.tsx 入口解析

src/entrypoints/cli.tsx 是 Claude Code 的顶层入口点,负责快速路径判断和延迟加载。

入口核心结构

// src/entrypoints/cli.tsx
import { feature } from 'bun:bundle';

// 前置副作用:corepack 配置和 CCR 环境变量
process.env.COREPACK_ENABLE_AUTO_PIN = '0';
if (process.env.CLAUDE_CODE_REMOTE === 'true') {
  process.env.NODE_OPTIONS = '--max-old-space-size=8192';
}

// Ablation baseline:实验性环境变量批量设置
if (feature('ABLATION_BASELINE') && process.env.CLAUDE_CODE_ABLATION_BASELINE) {
  for (const k of ['CLAUDE_CODE_SIMPLE', ...]) {
    process.env[k] ??= '1';
  }
}

/**
 * Bootstrap entrypoint - 检查特殊标志后加载完整 CLI
 */
async function main(): Promise<void> {
  const args = process.argv.slice(2);
  
  // Fast-path 判断...
  
  // 正常路径:加载 main.tsx
  const { main: cliMain } = await import('../main.js');
  await cliMain();
}

void main();

快速路径(Fast-Path)判断

graph TD
    A[main 函数入口] --> B[解析 args]
    B --> C{判断快速路径}
    
    C --> |"--version/-v"| D[直接输出版本<br/>零模块加载]
    C --> |"--dump-system-prompt"| E[动态加载配置<br/>输出系统提示]
    C --> |"--claude-in-chrome-mcp"| F[启动 Chrome MCP]
    C --> |"--chrome-native-host"| G[启动 Chrome Native Host]
    C --> |"--daemon-worker"| H[启动 Daemon Worker]
    C --> |"remote-control/rc"| I[启动 Bridge]
    C --> |"daemon"| J[启动 Daemon]
    C --> |"ps/logs/attach/kill"| K[Session 管理]
    C --> |"new/list/reply"| L[Templates 处理]
    C --> |"--tmux --worktree"| M[Exec tmux worktree]
    
    D --> N[return]
    E --> N
    F --> N
    G --> N
    H --> N
    I --> N
    J --> N
    K --> N
    L --> N
    M --> O{handled?}
    O --> |"是"| N
    O --> |"否"| P[继续正常路径]
    
    C --> |"无特殊标志"| P
    P --> Q[加载 main.tsx]
    Q --> R[cliMain]

快速路径代码详解

async function main(): Promise<void> {
  const args = process.argv.slice(2);
  
  // 1. --version:零模块加载
  if (args.length === 1 && (args[0] === '--version' || args[0] === '-v')) {
    console.log(`${MACRO.VERSION} (Claude Code)`);
    return;
  }
  
  // 2. 加载启动 profiler
  const { profileCheckpoint } = await import('../utils/startupProfiler.js');
  profileCheckpoint('cli_entry');
  
  // 3. --dump-system-prompt:动态加载最小模块
  if (feature('DUMP_SYSTEM_PROMPT') && args[0] === '--dump-system-prompt') {
    const { enableConfigs } = await import('../utils/config.js');
    enableConfigs();
    const { getSystemPrompt } = await import('../constants/prompts.js');
    const prompt = await getSystemPrompt([], model);
    console.log(prompt.join('\n'));
    return;
  }
  
  // 4. Chrome MCP / Native Host
  if (process.argv[2] === '--claude-in-chrome-mcp') {
    const { runClaudeInChromeMcpServer } = await import(...);
    await runClaudeInChromeMcpServer();
    return;
  }
  
  // 5. Bridge remote-control
  if (feature('BRIDGE_MODE') && args[0] === 'remote-control' || ...) {
    const { bridgeMain } = await import('../bridge/bridgeMain.js');
    await bridgeMain(args.slice(1));
    return;
  }
  
  // 6. Daemon
  if (feature('DAEMON') && args[0] === 'daemon') {
    const { daemonMain } = await import('../daemon/main.js');
    await daemonMain(args.slice(1));
    return;
  }
  
  // 7. Session management (ps/logs/attach/kill)
  if (feature('BG_SESSIONS') && args[0] === 'ps' || ...) {
    const bg = await import('../cli/bg.js');
    switch (args[0]) {
      case 'ps': await bg.psHandler(args.slice(1)); break;
      case 'logs': await bg.logsHandler(args[1]); break;
      case 'attach': await bg.attachHandler(args[1]); break;
      case 'kill': await bg.killHandler(args[1]); break;
      default: await bg.handleBgFlag(args);
    }
    return;
  }
  
  // 8. --tmux --worktree:提前 exec 到 tmux
  if (hasTmuxFlag && hasWorktreeFlag) {
    const { execIntoTmuxWorktree } = await import('../utils/worktree.js');
    const result = await execIntoTmuxWorktree(args);
    if (result.handled) return;
  }
  
  // 9. 正常路径:加载完整 CLI
  const { startCapturingEarlyInput } = await import('../utils/earlyInput.js');
  startCapturingEarlyInput();
  
  const { main: cliMain } = await import('../main.js');
  await cliMain();
}

快速路径优化

快速路径设计是为了最小化模块加载,提升特殊命令的响应速度。--version 实现零模块加载,其他快速路径只加载必要模块。

4.2 Commander.js 参数处理

main.tsx 使用 Commander.js 定义完整的 CLI 参数和子命令。

Commander.js 配置结构

// main.tsx 核心结构
import { Command as CommanderCommand, Option } from '@commander-js/extra-typings';

async function main() {
  const program = new CommanderCommand();
  
  program
    .name('claude')
    .version(MACRO.VERSION)
    .description('Claude Code - AI coding assistant')
    
    // 全局选项
    .option('-p, --print', 'Headless print mode')
    .option('--model <model>', 'Override model')
    .option('--resume <sessionId>', 'Resume session')
    .option('--dangerously-skip-permissions', 'Skip all permissions')
    .option('-w, --worktree', 'Create worktree for session')
    .option('--tmux', 'Create tmux session')
    .option('--bare', 'Simple/bare mode')
    .option('--disallowed-tools <tools>', 'Disallowed tools')
    .option('--allowedTools <tools>', 'Allowed tools')
    .option('--mcp-config <path>', 'MCP config path')
    .option('--settings <path>', 'Settings path')
    .option('-c, --context <dirs>', 'Additional directories')
    .option('--add-dir <dirs>', 'Add directories to context')
    .option('--output-format <format>', 'Output format (text/json/stream-json)')
    .option('--input-format <format>', 'Input format')
    .option('--max-tokens <n>', 'Max output tokens')
    .option('--verbose', 'Verbose output')
    .option('--help', 'Show help');
  
  // 子命令
  program
    .command('update')
    .description('Update Claude Code')
    .action(() => handleUpdate());
  
  program
    .command('mcp')
    .description('MCP server management')
    .action(() => handleMcp());
  
  program
    .command('config')
    .description('Configure settings')
    .action(() => handleConfig());
  
  // 解析参数
  program.parse();
  
  const options = program.opts();
  // 根据选项选择启动模式...
}

支持的命令行参数

参数说明示例
-p, --print无头模式,单次问答-p "explain this"
--model <model>覆盖默认模型--model claude-3-opus
--resume <id>恢复会话--resume session-123
--dangerously-skip-permissions跳过所有权限检查仅限沙箱环境
-w, --worktree创建 worktree 隔离-w --tmux
--tmux创建 tmux 会话--tmux
--bare简化模式--bare
--disallowed-tools禁用工具列表--disallowed-tools Bash,Glob
--allowedTools允许工具列表--allowedTools Read,Edit
--mcp-configMCP 配置路径--mcp-config ~/.claude/mcp.json
--settings设置文件路径--settings ~/.claude/settings.json
-c, --context附加目录-c ../other-project
--add-dir添加目录到上下文--add-dir ./docs
--output-format输出格式text, json, stream-json
--input-format输入格式text, json
--max-tokens最大输出 token--max-tokens 4096
--verbose详细输出--verbose

启动模式选择

graph TD
    A[解析 Commander 选项] --> B{判断启动模式}
    
    B --> |"--print"| C[Headless Print Mode]
    B --> |"--resume"| D[Resume Session]
    B --> |"--worktree"| E[Worktree Session]
    B --> |"LOCAL_RECOVERY"| F[Recovery CLI]
    B --> |"默认"| G[Interactive TUI]
    
    C --> H[cli/print.ts]
    D --> I[loadConversationForResume]
    E --> J[createWorktreeForSession]
    F --> K[localRecoveryCli.ts]
    G --> L[launchRepl]

权限跳过限制

--dangerously-skip-permissions 仅允许在 Docker/Bubblewrap 沙箱中使用,且不能有互联网访问。root/sudo 用户禁止使用。

4.3 setup.ts 初始化流程

setup.ts 执行启动前的必要初始化,包括环境检测、配置加载和后台任务启动。

setup 函数签名

// src/setup.ts
export async function setup(
  cwd: string,
  permissionMode: PermissionMode,
  allowDangerouslySkipPermissions: boolean,
  worktreeEnabled: boolean,
  worktreeName: string | undefined,
  tmuxEnabled: boolean,
  customSessionId?: string | null,
  worktreePRNumber?: number,
  messagingSocketPath?: string,
): Promise<void> {
  // 初始化步骤...
}

初始化流程图

graph TD
    subgraph "前置检查"
        A[Node.js 版本检查] --> B{>= 18?}
        B --> |"否"| C[退出报错]
        B --> |"是"| D[设置 customSessionId]
    end
    
    subgraph "UDS Messaging"
        D --> E{非 bare 模式?}
        E --> |"是"| F[startUdsMessaging]
        F --> G[绑定 Socket]
    end
    
    subgraph "Teammate Snapshot"
        G --> H{Agent Swarms 启用?}
        H --> |"是"| I[captureTeammateModeSnapshot]
    end
    
    subgraph "终端备份恢复"
        I --> J{非非交互?}
        J --> |"是"| K[checkAndRestoreITerm2Backup]
        K --> L[checkAndRestoreTerminalBackup]
    end
    
    subgraph "路径设置"
        L --> M[setCwd]
        M --> N[setOriginalCwd]
        N --> O[setProjectRoot]
    end
    
    subgraph "本地恢复模式"
        O --> P{LOCAL_RECOVERY?}
        P --> |"是"| Q[早返回]
    end
    
    subgraph "Hooks 配置"
        P --> |"否"| R[captureHooksConfigSnapshot]
        R --> S[initializeFileChangedWatcher]
    end
    
    subgraph "Worktree 处理"
        S --> T{worktreeEnabled?}
        T --> |"是"| U[createWorktreeForSession]
        U --> V[createTmuxSessionForWorktree]
        V --> W[setCwd worktreePath]
    end
    
    subgraph "后台任务"
        W --> X[initSessionMemory]
        X --> Y[lockCurrentVersion]
        Y --> Z[getCommands prefetch]
        Z --> AA[loadPluginHooks]
        AA --> AB[initSinks]
    end
    
    subgraph "权限检查"
        AB --> AC{bypassPermissions?}
        AC --> |"是"| AD[环境安全检查]
        AD --> AE{沙箱 + 无网络?}
        AE --> |"否"| AF[退出报错]
    end
    
    subgraph "完成"
        AE --> |"是"| AG[setup 完成]
        AC --> |"否"| AG
    end

setup 核心代码

export async function setup(...): Promise<void> {
  logForDiagnosticsNoPII('info', 'setup_started');
  
  // 1. Node.js 版本检查
  const nodeVersion = process.version.match(/^v(\d+)\./)?.[1];
  if (!nodeVersion || parseInt(nodeVersion) < 18) {
    console.error('Error: Claude Code requires Node.js version 18 or higher.');
    process.exit(1);
  }
  
  // 2. 设置 custom session ID
  if (customSessionId) {
    switchSession(asSessionId(customSessionId));
  }
  
  // 3. UDS Messaging Server(Mac/Linux)
  if (!isBareMode() || messagingSocketPath !== undefined) {
    if (feature('UDS_INBOX')) {
      const m = await import('./utils/udsMessaging.js');
      await m.startUdsMessaging(messagingSocketPath ?? m.getDefaultUdsSocketPath());
    }
  }
  
  // 4. Teammate snapshot
  if (!isBareMode() && isAgentSwarmsEnabled()) {
    const { captureTeammateModeSnapshot } = await import('./utils/swarm/backends/teammateModeSnapshot.js');
    captureTeammateModeSnapshot();
  }
  
  // 5. 终端备份恢复
  if (!getIsNonInteractiveSession()) {
    if (isAgentSwarmsEnabled()) {
      await checkAndRestoreITerm2Backup();
    }
    await checkAndRestoreTerminalBackup();
  }
  
  // 6. 设置工作目录
  setCwd(cwd);
  setOriginalCwd(cwd);
  setProjectRoot(cwd);
  
  // 7. 本地恢复模式早返回
  if (process.env.CLAUDE_CODE_LOCAL_RECOVERY === '1') {
    return;
  }
  
  // 8. Hooks 配置快照
  captureHooksConfigSnapshot();
  initializeFileChangedWatcher(cwd);
  
  // 9. Worktree 创建
  if (worktreeEnabled) {
    const worktreeSession = await createWorktreeForSession(...);
    setCwd(worktreeSession.worktreePath);
    setProjectRoot(getCwd());
    saveWorktreeState(worktreeSession);
    clearMemoryFileCaches();
    updateHooksConfigSnapshot();
  }
  
  // 10. 后台任务
  if (!isBareMode()) {
    initSessionMemory();
  }
  void lockCurrentVersion();
  void getCommands(getProjectRoot());
  
  // 11. 权限安全检查
  if (permissionMode === 'bypassPermissions' || allowDangerouslySkipPermissions) {
    if (process.platform !== 'win32' && process.getuid() === 0) {
      console.error('Cannot use --dangerously-skip-permissions with root');
      process.exit(1);
    }
    const [isDocker, hasInternet] = await Promise.all([
      envDynamic.getIsDocker(),
      env.hasInternetAccess(),
    ]);
    if (!isSandboxed || hasInternet) {
      console.error('Cannot use bypass in non-sandbox with internet');
      process.exit(1);
    }
  }
}

setup 时机

setup() 在 main.tsx 的 init() 之后调用,确保配置已加载。setup 完成后,Ink TUI 开始渲染。

4.4 main.tsx 主逻辑入口

main.tsx 是 CLI 的主逻辑入口,负责 Commander.js 配置、启动模式选择和 REPL 启动。

main.tsx 入口函数结构

// main.tsx(简化结构)
async function main() {
  // 1. 前置初始化(side-effects)
  profileCheckpoint('main_tsx_entry');
  startMdmRawRead();      // MDM 配置预读取
  startKeychainPrefetch(); // macOS keychain 预读取
  
  // 2. 导入大量模块(延迟加载优化)
  // ...约 135ms 的模块加载
  
  // 3. 迁移函数调用
  migrateAutoUpdatesToSettings();
  migrateBypassPermissionsAcceptedToSettings();
  migrateFennecToOpus();
  // ...其他迁移
  
  // 4. 构建 Commander program
  const program = buildCommanderProgram();
  
  // 5. 解析参数
  program.parse();
  const options = program.opts();
  const args = program.args;
  
  // 6. 根据选项选择启动路径
  if (options.print) {
    // --print 无头模式
    await runHeadlessPrint(options, args);
  } else if (options.resume) {
    // --resume 恢复会话
    await runResumeSession(options);
  } else if (getIsNonInteractiveSession()) {
    // 非交互模式
    await runNonInteractive();
  } else {
    // 交互 TUI 模式
    await init();              // 初始化配置
    await setup(...);          // 环境初始化
    await launchRepl(...);     // 启动 REPL
  }
}

main.tsx 执行流程

graph TD
    subgraph "前置阶段"
        A[profileCheckpoint] --> B[startMdmRawRead]
        B --> C[startKeychainPrefetch]
        C --> D[模块导入<br/>~135ms]
    end
    
    subgraph "迁移阶段"
        D --> E[runMigrations]
        E --> F[migrateAutoUpdates]
        F --> G[migrateBypassPermissions]
        G --> H[migrateModelDefaults]
    end
    
    subgraph "Commander 配置"
        H --> I[buildCommanderProgram]
        I --> J[定义选项]
        J --> K[定义子命令]
        K --> L[program.parse]
    end
    
    subgraph "启动路径选择"
        L --> M{options.print?}
        M --> |"是"| N[runHeadlessPrint]
        M --> |"否"| O{options.resume?}
        O --> |"是"| P[runResumeSession]
        O --> |"否"| Q{非交互?}
        Q --> |"是"| R[runNonInteractive]
        Q --> |"否"| S[交互模式]
        
        S --> T[init 配置加载]
        T --> U[setup 环境初始化]
        U --> V[initBuiltinPlugins]
        V --> W[initBundledSkills]
        W --> X[launchRepl]
    end

init() 初始化函数

// src/entrypoints/init.ts
export async function init(): Promise<void> {
  // 1. 启用配置
  enableConfigs();
  
  // 2. 初始化分析 sinks
  initSinks();
  
  // 3. 初始化遥测
  initializeTelemetryAfterTrust();
  
  // 4. 初始化 GrowthBook
  await initializeGrowthBook();
  
  // 5. 加载策略限制
  await loadPolicyLimits();
  
  // 6. 加载远程管理设置
  await loadRemoteManagedSettings();
  
  // 7. 应用安全环境变量
  applySafeConfigEnvironmentVariables();
}

主入口函数调用顺序

// main.tsx 主入口调用顺序
async function main() {
  // Phase 1: 前置副作用
  profileCheckpoint('main_tsx_entry');
  startMdmRawRead();
  startKeychainPrefetch();
  
  // Phase 2: 大量模块导入
  // ... imports
  
  // Phase 3: 迁移
  runMigrations();
  
  // Phase 4: Commander 配置
  const program = buildProgram();
  program.parse();
  
  // Phase 5: 获取选项
  const options = program.opts();
  const args = program.args;
  
  // Phase 6: 初始化(交互模式)
  await init();
  
  // Phase 7: setup
  await setup(
    getCwd(),
    permissionMode,
    options.dangerouslySkipPermissions,
    options.worktree,
    options.worktreeName,
    options.tmux,
    options.customSessionId,
  );
  
  // Phase 8: 初始化内置插件和 Skills
  initBuiltinPlugins();
  initBundledSkills();
  
  // Phase 9: 启动 REPL
  await launchRepl(root, appProps, replProps, renderAndRun);
}

启动性能优化

启动流程包含多项性能优化:MDM/keychain 预读取并行执行;模块延迟加载;快速路径零/最小加载。实测启动时间约 200-300ms。

4.5 REPL 界面启动

replLauncher.tsx 负责 REPL 界面的启动,加载 App 和 REPL 组件后渲染。

replLauncher 结构

// src/replLauncher.tsx
import React from 'react';
import type { StatsStore } from './context/stats.js';
import type { Root } from './ink.js';
import type { Props as REPLProps } from './screens/REPL.js';
import type { AppState } from './state/AppStateStore.js';
import type { FpsMetrics } from './utils/fpsTracker.js';

type AppWrapperProps = {
  getFpsMetrics: () => FpsMetrics | undefined;
  stats?: StatsStore;
  initialState: AppState;
};

export async function launchRepl(
  root: Root,
  appProps: AppWrapperProps,
  replProps: REPLProps,
  renderAndRun: (root: Root, element: React.ReactNode) => Promise<void>,
): Promise<void> {
  // 动态加载组件
  const { App } = await import('./components/App.js');
  const { REPL } = await import('./screens/REPL.js');
  
  // 渲染并运行
  await renderAndRun(
    root,
    <App {...appProps}>
      <REPL {...replProps} />
    </App>
  );
}

REPL 启动流程

graph TD
    A[launchRepl 调用] --> B[动态加载 App.js]
    B --> C[动态加载 REPL.js]
    C --> D[构建 React 组件树]
    
    D --> E[App 组件]
    E --> F[REPL 组件]
    F --> G[PromptInput]
    F --> H[Messages]
    F --> I[Spinner]
    
    G --> J[renderAndRun]
    H --> J
    I --> J
    
    J --> K[Ink reconciler]
    K --> L[终端渲染]
    L --> M[事件循环启动]

Ink App 组件结构

// src/ink/components/App.tsx(简化)
class App extends PureComponent<Props, State> {
  // 终端事件处理
  handleKeyPress = (key: ParsedKey) => { ... }
  handleClick = (col: number, row: number) => { ... }
  
  // 生命周期
  componentDidMount() {
    // 启动 stdin 事件监听
    // 设置终端模式(mouse tracking, extended keys)
  }
  
  render() {
    return (
      <AppContext.Provider value={...}>
        <StdinContext.Provider value={stdin}>
          <TerminalSizeContext.Provider value={...}>
            <ClockProvider>
              <TerminalFocusProvider>
                {this.props.children}
              </TerminalFocusProvider>
            </ClockProvider>
          </TerminalSizeContext.Provider>
        </StdinContext.Provider>
      </AppContext.Provider>
    );
  }
}

REPL.tsx 核心结构

// src/screens/REPL.tsx(简化结构)
interface REPLProps {
  commands: Command[];
  tools: Tools;
  initialState: AppState;
  // ...其他属性
}

function REPL(props: REPLProps) {
  // 状态管理
  const [messages, setMessages] = useState<Message[]>([]);
  const [input, setInput] = useState('');
  const [isProcessing, setIsProcessing] = useState(false);
  
  // 提交处理
  const handleSubmit = async (userInput: string) => {
    setMessages(prev => [...prev, createUserMessage(userInput)]);
    setIsProcessing(true);
    
    try {
      // 调用 QueryEngine
      const result = await queryEngine.process(userInput);
      setMessages(prev => [...prev, result]);
    } finally {
      setIsProcessing(false);
    }
  };
  
  // 渲染
  return (
    <Box flexDirection="column">
      {/* 消息列表 */}
      <Messages messages={messages} />
      
      {/* 加载指示器 */}
      {isProcessing && <Spinner />}
      
      {/* 输入组件 */}
      <PromptInput
        value={input}
        onChange={setInput}
        onSubmit={handleSubmit}
        commands={props.commands}
      />
      
      {/* Companion 伴侣 */}
      <CompanionSprite />
    </Box>
  );
}

组件树结构

graph TD
    subgraph "App 组件上下文"
        APP[App.tsx]
        APP_CTX[AppContext]
        STDIN_CTX[StdinContext]
        SIZE_CTX[TerminalSizeContext]
        CLOCK[ClockProvider]
        FOCUS[TerminalFocusProvider]
    end
    
    subgraph "REPL 组件"
        REPL[REPL.tsx]
        MSGS[Messages.tsx]
        INPUT[PromptInput]
        SPIN[Spinner]
        COMP_SPRITE[CompanionSprite]
    end
    
    subgraph "消息子组件"
        USER_MSG[UserPromptMessage]
        ASSIST_MSG[AssistantMessage]
        TOOL_MSG[ToolUseMessage]
        ERROR_MSG[ErrorMessage]
    end
    
    APP --> APP_CTX
    APP_CTX --> STDIN_CTX
    STDIN_CTX --> SIZE_CTX
    SIZE_CTX --> CLOCK
    CLOCK --> FOCUS
    FOCUS --> REPL
    
    REPL --> MSGS
    REPL --> INPUT
    REPL --> SPIN
    REPL --> COMP_SPRITE
    
    MSGS --> USER_MSG
    MSGS --> ASSIST_MSG
    MSGS --> TOOL_MSG
    MSGS --> ERROR_MSG

事件处理流程

sequenceDiagram
    participant T as Terminal
    participant APP as App.tsx
    participant INK as Ink Engine
    participant REPL as REPL.tsx
    participant QE as QueryEngine
    
    T->>APP: stdin 数据
    APP->>APP: parseMultipleKeypresses
    APP->>INK: dispatchKeyboardEvent
    
    alt Enter 按键
        INK->>REPL: onSubmit
        REPL->>QE: processUserInput
        QE->>REPL: AssistantMessage
        REPL->>INK: 更新状态
        INK->>T: 渲染输出
    else 其他按键
        INK->>REPL: onChange
        REPL->>INK: 更新输入框
    end

Ink 渲染机制

Ink 使用自定义 React reconciler,将 React 组件树渲染到终端 ANSI 序列。支持 mouse tracking、extended key reporting、OSC 8 hyperlinks 等终端特性。

4.6 Recovery CLI 降级模式

当完整 TUI 出现问题时,可以使用 Recovery CLI 降级模式。

Recovery CLI 入口

// src/localRecoveryCli.ts
import readline from 'readline';

export async function runRecoveryCli() {
  const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
  });
  
  // 简化的 readline 交互
  rl.question('> ', async (input) => {
    if (input.trim() === 'exit') {
      rl.close();
      return;
    }
    
    try {
      // 简化的查询处理
      const response = await simpleQuery(input);
      console.log(response);
    } catch (error) {
      console.error('Error:', error.message);
    }
    
    rl.prompt();
  });
  
  rl.prompt();
}

启动 Recovery CLI

# 通过环境变量启用
CLAUDE_CODE_FORCE_RECOVERY_CLI=1 ./bin/claude-haha

# 或通过 LOCAL_RECOVERY
CLAUDE_CODE_LOCAL_RECOVERY=1 ./bin/claude-haha
graph TD
    A[启动] --> B{LOCAL_RECOVERY<br/>or FORCE_RECOVERY_CLI?}
    B --> |"是"| C[跳过 setup]
    C --> D[localRecoveryCli]
    D --> E[readline 交互]
    B --> |"否"| F[正常 TUI 流程]

Recovery CLI 限制

Recovery CLI 不支持:Ink 组件渲染、Companion 伴侣、进度指示器、鼠标交互。仅提供基本的 readline 问答功能。

4.7 启动流程完整图

sequenceDiagram
    participant BIN as bin/claude-haha
    participant CLI as cli.tsx
    participant MAIN as main.tsx
    participant INIT as init.ts
    participant SETUP as setup.ts
    participant REPL as replLauncher
    participant INK as Ink/App
    participant QE as QueryEngine
    
    BIN->>CLI: Bun 启动
    CLI->>CLI: 快速路径判断
    
    alt 快速路径
        CLI->>CLI: 处理并返回
    else 正常路径
        CLI->>MAIN: import main.js
        MAIN->>MAIN: 前置副作用
        MAIN->>MAIN: 模块导入
        MAIN->>MAIN: 迁移
        MAIN->>MAIN: Commander parse
        
        MAIN->>INIT: init()
        INIT->>INIT: enableConfigs
        INIT->>INIT: initSinks
        INIT->>INIT: loadPolicyLimits
        
        MAIN->>SETUP: setup()
        SETUP->>SETUP: Node 版本检查
        SETUP->>SETUP: UDS Messaging
        SETUP->>SETUP: setCwd
        SETUP->>SETUP: Hooks 快照
        SETUP->>SETUP: 后台任务
        
        MAIN->>MAIN: initPlugins/Skills
        MAIN->>REPL: launchRepl()
        REPL->>INK: App + REPL
        INK->>INK: componentDidMount
        INK->>INK: stdin 监听
        
        loop 用户交互
            INK->>QE: handleSubmit
            QE->>INK: updateMessages
        end
    end

下一章将深入探讨 QueryEngine 查询引擎 的核心实现。

第5章:查询引擎

本章深入分析 Claude Code 的查询引擎实现,揭示消息处理流程、流式响应处理和计费追踪机制。

5.1 QueryEngine.ts 核心代码解析

QueryEngine 是 Claude Code 的核心组件,负责管理整个对话生命周期和消息处理循环。

类结构定义

export class QueryEngine {
  private config: QueryEngineConfig
  private mutableMessages: Message[]
  private abortController: AbortController
  private permissionDenials: SDKPermissionDenial[]
  private totalUsage: NonNullableUsage
  private hasHandledOrphanedPermission = false
  private readFileState: FileStateCache
  private discoveredSkillNames = new Set<string>()
  private loadedNestedMemoryPaths = new Set<string>()

  constructor(config: QueryEngineConfig) {
    this.config = config
    this.mutableMessages = config.initialMessages ?? []
    this.abortController = config.abortController ?? createAbortController()
    this.permissionDenials = []
    this.readFileState = config.readFileCache
    this.totalUsage = EMPTY_USAGE
  }
}

设计理念

QueryEngine 采用「One QueryEngine per conversation」的设计原则,每个对话实例独立管理状态,确保消息历史、文件缓存、使用量统计等数据在多轮交互中正确持久化。

配置参数结构

export type QueryEngineConfig = {
  cwd: string                        // 工作目录
  tools: Tools                       // 可用工具列表
  commands: Command[]                // 斜杠命令列表
  mcpClients: MCPServerConnection[]  // MCP 服务器连接
  agents: AgentDefinition[]          // Agent 定义列表
  canUseTool: CanUseToolFn           // 工具权限检查函数
  getAppState: () => AppState        // 状态获取函数
  setAppState: (f: (prev: AppState) => AppState) => void
  initialMessages?: Message[]        // 初始消息列表
  readFileCache: FileStateCache      // 文件读取缓存
  customSystemPrompt?: string        // 自定义系统提示
  appendSystemPrompt?: string        // 附加系统提示
  userSpecifiedModel?: string        // 用户指定模型
  thinkingConfig?: ThinkingConfig    // Thinking 配置
  maxTurns?: number                  // 最大轮次
  maxBudgetUsd?: number              // 最大预算
  jsonSchema?: Record<string, unknown> // 结构化输出 Schema
}

核心方法:submitMessage

submitMessage 是 QueryEngine 的核心方法,采用异步生成器模式处理消息流:

async *submitMessage(
  prompt: string | ContentBlockParam[],
  options?: { uuid?: string; isMeta?: boolean },
): AsyncGenerator<SDKMessage, void, unknown> {
  // 1. 初始化配置
  this.discoveredSkillNames.clear()
  setCwd(cwd)
  
  // 2. 包装权限检查函数
  const wrappedCanUseTool = async (tool, input, context, ...) => {
    const result = await canUseTool(...)
    if (result.behavior !== 'allow') {
      this.permissionDenials.push({
        tool_name: sdkCompatToolName(tool.name),
        tool_use_id: toolUseID,
        tool_input: input,
      })
    }
    return result
  }
  
  // 3. 获取系统提示组件
  const { defaultSystemPrompt, userContext, systemContext } = 
    await fetchSystemPromptParts({ tools, mainLoopModel, ... })
  
  // 4. 处理用户输入
  const { messagesFromUserInput, shouldQuery, ... } = 
    await processUserInput({ input: prompt, mode: 'prompt', ... })
  
  // 5. 进入查询循环
  for await (const message of query({ messages, systemPrompt, ... })) {
    // 处理各类消息:assistant、user、stream_event、attachment...
    yield* normalizeMessage(message)
  }
  
  // 6. 返回最终结果
  yield { type: 'result', subtype: 'success', ... }
}

5.2 消息处理流程

完整消息处理时序图

sequenceDiagram
    participant User as 用户
    participant REPL as REPL.tsx
    participant QE as QueryEngine
    participant PU as processUserInput
    participant API as API Service
    participant Query as query()
    participant Tool as Tool System
    
    User->>REPL: 输入 Prompt
    REPL->>QE: submitMessage(prompt)
    QE->>QE: 初始化状态
    QE->>PU: processUserInput()
    PU->>PU: 解析斜杠命令
    PU->>PU: 处理附件
    PU-->>QE: 返回消息列表
    
    QE->>QE: 持久化用户消息
    QE->>QE: 构建 ProcessUserInputContext
    QE->>QE: 获取系统提示
    
    QE->>Query: query(messages, systemPrompt)
    Query->>API: 发送 API 请求
    API-->>Query: 流式响应
    
    loop 消息循环
        Query->>Query: 解析 content_block
        alt tool_use 块
            Query->>Tool: runTools()
            Tool->>Tool: 权限检查
            Tool->>Tool: 执行工具
            Tool-->>Query: tool_result
        else text 块
            Query->>QE: yield assistant message
        end
    end
    
    Query-->>QE: 完成
    QE->>QE: 计算 token 使用量
    QE-->>REPL: yield result
    REPL->>User: 显示结果

消息类型分类

类型说明处理方式
assistantAI 响应消息添加到消息列表,yield 给调用者
user用户消息添加到消息列表,turnCount++
stream_event流事件更新 usage 统计
attachment附件消息添加到消息列表
progress进度消息实时 yield
system系统消息处理 compact_boundary
tombstone移除标记跳过处理

消息流转状态图

stateDiagram-v2
    [*] --> UserInput: 用户输入
    UserInput --> ProcessInput: 处理输入
    ProcessInput --> BuildContext: 构建上下文
    
    BuildContext --> QueryLoop: 进入查询循环
    QueryLoop --> APICall: 发送 API 请求
    
    APICall --> StreamResponse: 流式响应
    StreamResponse --> ParseBlock: 解析 content_block
    
    ParseBlock --> ToolUse: tool_use 块
    ParseBlock --> TextBlock: text 块
    
    ToolUse --> PermissionCheck: 权限检查
    PermissionCheck --> Execute: 允许执行
    PermissionCheck --> Deny: 拒绝执行
    Execute --> ToolResult: 返回结果
    Deny --> ToolResult: 返回错误
    
    ToolResult --> QueryLoop: 继续循环
    TextBlock --> YieldMessage: yield 消息
    YieldMessage --> QueryLoop: 继续循环
    
    QueryLoop --> EndTurn: 结束条件
    EndTurn --> CalculateCost: 计算费用
    CalculateCost --> [*]: 返回结果

5.3 流式响应处理

SSE (Server-Sent Events) 处理

Claude Code 使用 Anthropic Messages API 的流式响应模式,通过 SSE 处理实时数据。

graph TD
    subgraph "流式响应处理"
        API[API Request] --> STREAM[Stream Connection]
        STREAM --> EVENT1[event: message_start]
        EVENT1 --> EVENT2[event: content_block_start]
        EVENT2 --> EVENT3[event: content_block_delta]
        EVENT3 --> EVENT4[event: content_block_stop]
        EVENT4 --> EVENT5[event: message_delta]
        EVENT5 --> EVENT6[event: message_stop]
    end
    
    subgraph "事件处理"
        EVENT1 --> USAGE[初始化 Usage]
        EVENT2 --> BLOCK[开始 Content Block]
        EVENT3 --> ACCUM[累积内容]
        EVENT5 --> FINAL[最终 Usage]
    end

流事件类型定义

type StreamEvent = 
  | { type: 'message_start'; message: { usage: Usage } }
  | { type: 'content_block_start'; index: number; content_block: ContentBlock }
  | { type: 'content_block_delta'; index: number; delta: Delta }
  | { type: 'content_block_stop'; index: number }
  | { type: 'message_delta'; delta: { stop_reason: string }; usage: Usage }
  | { type: 'message_stop' }

Usage 统计追踪

// 在 QueryEngine 中追踪 usage
if (message.event.type === 'message_start') {
  currentMessageUsage = EMPTY_USAGE
  currentMessageUsage = updateUsage(currentMessageUsage, message.event.message.usage)
}

if (message.event.type === 'message_delta') {
  currentMessageUsage = updateUsage(currentMessageUsage, message.event.usage)
  if (message.event.delta.stop_reason != null) {
    lastStopReason = message.event.delta.stop_reason
  }
}

if (message.event.type === 'message_stop') {
  this.totalUsage = accumulateUsage(this.totalUsage, currentMessageUsage)
}

Token 计费追踪(cost-tracker.ts)

// cost-tracker.ts 核心函数
export function addToTotalSessionCost(
  cost: number,
  usage: Usage,
  model: string,
): number {
  const modelUsage = addToTotalModelUsage(cost, usage, model)
  addToTotalCostState(cost, modelUsage, model)

  // 更新计数器
  getCostCounter()?.add(cost, { model })
  getTokenCounter()?.add(usage.input_tokens, { model, type: 'input' })
  getTokenCounter()?.add(usage.output_tokens, { model, type: 'output' })
  getTokenCounter()?.add(usage.cache_read_input_tokens ?? 0, { model, type: 'cacheRead' })
  getTokenCounter()?.add(usage.cache_creation_input_tokens ?? 0, { model, type: 'cacheCreation' })

  return totalCost
}

// 模型使用统计
function addToTotalModelUsage(cost: number, usage: Usage, model: string): ModelUsage {
  const modelUsage = getUsageForModel(model) ?? {
    inputTokens: 0,
    outputTokens: 0,
    cacheReadInputTokens: 0,
    cacheCreationInputTokens: 0,
    webSearchRequests: 0,
    costUSD: 0,
    contextWindow: 0,
    maxOutputTokens: 0,
  }

  modelUsage.inputTokens += usage.input_tokens
  modelUsage.outputTokens += usage.output_tokens
  modelUsage.cacheReadInputTokens += usage.cache_read_input_tokens ?? 0
  modelUsage.cacheCreationInputTokens += usage.cache_creation_input_tokens ?? 0
  modelUsage.webSearchRequests += usage.server_tool_use?.web_search_requests ?? 0
  modelUsage.costUSD += cost
  
  return modelUsage
}

费用计算公式

总费用 = 输入 tokens 费用 + 输出 tokens 费用 + 缓存读取费用 + 缓存创建费用 + 网络搜索费用。缓存读取费用约为输入费用的 10%,缓存创建费用约为输入费用的 25%。

费用追踪流程图

flowchart LR
    subgraph "API 响应"
        RESP[API Response] --> USAGE[Usage 数据]
    end
    
    subgraph "费用计算"
        USAGE --> CALC[calculateUSDCost]
        CALC --> COST[费用金额]
    end
    
    subgraph "状态更新"
        COST --> STATE[addToTotalCostState]
        STATE --> COUNTER[Counter 更新]
        COUNTER --> CONFIG[保存到 Config]
    end
    
    subgraph "展示"
        STATE --> FORMAT[formatTotalCost]
        FORMAT --> DISPLAY[终端输出]
    end

5.4 query.ts 辅助函数

query.ts 是查询循环的核心实现,处理工具调用、上下文压缩、错误恢复等逻辑。

查询循环状态管理

type State = {
  messages: Message[]
  toolUseContext: ToolUseContext
  autoCompactTracking: AutoCompactTrackingState | undefined
  maxOutputTokensRecoveryCount: number
  hasAttemptedReactiveCompact: boolean
  maxOutputTokensOverride: number | undefined
  pendingToolUseSummary: Promise<ToolUseSummaryMessage | null> | undefined
  stopHookActive: boolean | undefined
  turnCount: number
  transition: Continue | undefined
}

主查询函数结构

export async function* query(params: QueryParams): AsyncGenerator<...> {
  let state: State = {
    messages: params.messages,
    toolUseContext: params.toolUseContext,
    maxOutputTokensOverride: params.maxOutputTokensOverride,
    autoCompactTracking: undefined,
    turnCount: 1,
    // ...
  }

  // 主循环
  while (true) {
    yield { type: 'stream_request_start' }
    
    // 1. 上下文处理
    let messagesForQuery = [...getMessagesAfterCompactBoundary(messages)]
    messagesForQuery = await applyToolResultBudget(messagesForQuery, ...)
    
    // 2. Snip 处理
    if (feature('HISTORY_SNIP')) {
      const snipResult = snipModule!.snipCompactIfNeeded(messagesForQuery)
      messagesForQuery = snipResult.messages
    }
    
    // 3. Microcompact
    const microcompactResult = await deps.microcompact(messagesForQuery, ...)
    
    // 4. Autocompact
    const { compactionResult } = await deps.autocompact(messagesForQuery, ...)
    
    // 5. API 调用
    for await (const message of deps.callModel({ messages, systemPrompt, ... })) {
      // 处理流式消息
      if (message.type === 'assistant') {
        assistantMessages.push(message)
        // 检测 tool_use 块
        const toolUseBlocks = message.message.content.filter(c => c.type === 'tool_use')
        if (toolUseBlocks.length > 0) {
          needsFollowUp = true
        }
      }
    }
    
    // 6. 工具执行
    if (needsFollowUp) {
      const toolUpdates = streamingToolExecutor?.getRemainingResults() 
        ?? runTools(toolUseBlocks, assistantMessages, canUseTool, toolUseContext)
      for await (const update of toolUpdates) {
        if (update.message) yield update.message
      }
    }
    
    // 7. 继续循环或结束
    if (!needsFollowUp) {
      return { reason: 'completed' }
    }
    
    state = { messages: [...messagesForQuery, ...assistantMessages, ...toolResults], ... }
  }
}

错误恢复机制

graph TD
    subgraph "错误类型"
        E1[Prompt Too Long 413]
        E2[Max Output Tokens]
        E3[Image Size Error]
        E4[API Rate Limit]
    end
    
    subgraph "恢复策略"
        R1[Reactive Compact]
        R2[Token Escalation]
        R3[Media Strip Retry]
        R4[Wait and Retry]
    end
    
    subgraph "结果"
        S1[成功恢复]
        S2[失败返回错误]
    end
    
    E1 --> R1 --> S1
    E1 --> R1 --> S2
    E2 --> R2 --> S1
    E3 --> R3 --> S1
    E4 --> R4 --> S1

错误恢复限制

max_output_tokens 恢复最多尝试 3 次;prompt_too_long 在 Reactive Compact 失败后直接返回错误。这些限制防止无限循环消耗资源。

上下文压缩触发条件

触发条件压缩方式说明
Token 超过阈值Autocompact自动调用摘要 API
Snip 标记Snip Compact移除标记的冗余内容
请求级别Microcompact缓存编辑优化
413 错误Reactive Compact紧急压缩恢复
flowchart TB
    subgraph "压缩检查顺序"
        CHECK[检查 Token 数量] --> SNIP{需要 Snip?}
        SNIP --> |"是"| SNIP_EXEC[执行 Snip]
        SNIP --> |"否"| MICRO{需要 Microcompact?}
        SNIP_EXEC --> MICRO
        MICRO --> |"是"| MICRO_EXEC[执行 Microcompact]
        MICRO --> |"否"| AUTO{需要 Autocompact?}
        MICRO_EXEC --> AUTO
        AUTO --> |"是"| AUTO_EXEC[执行 Autocompact]
        AUTO --> |"否"| API[发送 API 请求]
        AUTO_EXEC --> API
    end

下一章将深入分析 工具系统 的实现细节。

第6章:工具系统

本章深入分析 Claude Code 的工具系统实现,包括工具基类设计、各类工具的实现细节、工具注册机制和权限检查流程。

6.1 Tool.ts 基类设计

Tool.ts 定义了所有工具的基类接口,是工具系统的核心抽象。

Tool 类型定义

export type Tool<
  Input extends AnyObject = AnyObject,
  Output = unknown,
  P extends ToolProgressData = ToolProgressData,
> = {
  // 标识属性
  readonly name: string              // 工具名称
  aliases?: string[]                 // 别名列表(兼容重命名)
  searchHint?: string                // 搜索提示(3-10词)
  readonly shouldDefer?: boolean     // 是否延迟加载
  readonly alwaysLoad?: boolean      // 是否始终加载
  readonly strict?: boolean          // 是否启用严格模式
  
  // Schema 定义
  readonly inputSchema: Input        // Zod 输入 Schema
  readonly inputJSONSchema?: ToolInputJSONSchema  // JSON Schema(MCP工具)
  outputSchema?: z.ZodType<unknown>  // 输出 Schema
  
  // 核心方法
  call(
    args: z.infer<Input>,
    context: ToolUseContext,
    canUseTool: CanUseToolFn,
    parentMessage: AssistantMessage,
    onProgress?: ToolCallProgress<P>,
  ): Promise<ToolResult<Output>>
  
  description(
    input: z.infer<Input>,
    options: { isNonInteractiveSession: boolean; ... },
  ): Promise<string>
  
  // 行为判断方法
  isEnabled(): boolean               // 是否启用
  isConcurrencySafe(input): boolean  // 是否并发安全
  isReadOnly(input): boolean         // 是否只读
  isDestructive?(input): boolean     // 是否破坏性操作
  
  // 权限方法
  checkPermissions(input, context): Promise<PermissionResult>
  validateInput?(input, context): Promise<ValidationResult>
  
  // UI 渲染方法
  userFacingName(input): string
  renderToolUseMessage(input, options): React.ReactNode
  renderToolResultMessage?(content, progressMessages, options): React.ReactNode
  renderToolUseProgressMessage?(progressMessages, options): React.ReactNode
  
  // 其他方法
  prompt(options): Promise<string>   // 工具提示生成
  mapToolResultToToolResultBlockParam(content, toolUseID): ToolResultBlockParam
  getToolUseSummary?(input): string | null  // 简要描述
  getActivityDescription?(input): string | null  // Spinner 描述
}

buildTool 工厂函数

const TOOL_DEFAULTS = {
  isEnabled: () => true,
  isConcurrencySafe: (_input?: unknown) => false,  // 默认不安全
  isReadOnly: (_input?: unknown) => false,         // 默认可写
  isDestructive: (_input?: unknown) => false,
  checkPermissions: (input, _ctx) =>
    Promise.resolve({ behavior: 'allow', updatedInput: input }),
  toAutoClassifierInput: (_input?: unknown) => '',
  userFacingName: (_input?: unknown) => '',
}

export function buildTool<D extends AnyToolDef>(def: D): BuiltTool<D> {
  return {
    ...TOOL_DEFAULTS,
    userFacingName: () => def.name,
    ...def,
  } as BuiltTool<D>
}

Fail-Closed 设计

工具默认值采用 fail-closed(失败即关闭)原则:isConcurrencySafe 默认 false,isReadOnly 默认 false。这意味着安全相关属性必须显式声明为安全,而非默认安全。

ToolUseContext 定义

export type ToolUseContext = {
  options: {
    commands: Command[]
    debug: boolean
    mainLoopModel: string
    tools: Tools
    verbose: boolean
    thinkingConfig: ThinkingConfig
    mcpClients: MCPServerConnection[]
    mcpResources: Record<string, ServerResource[]>
    isNonInteractiveSession: boolean
    agentDefinitions: AgentDefinitionsResult
    maxBudgetUsd?: number
    customSystemPrompt?: string
    appendSystemPrompt?: string
  }
  
  abortController: AbortController
  readFileState: FileStateCache
  getAppState(): AppState
  setAppState(f: (prev: AppState) => AppState): void
  
  // 回调函数
  setToolJSX?: SetToolJSXFn
  addNotification?: (notif: Notification) => void
  appendSystemMessage?: (msg: SystemMessage) => void
  sendOSNotification?: (opts) => void
  
  // 状态追踪
  messages: Message[]
  fileReadingLimits?: { maxTokens?: number; maxSizeBytes?: number }
  globLimits?: { maxResults?: number }
  toolDecisions?: Map<string, { source: string; decision: 'accept' | 'reject'; timestamp: number }>
  queryTracking?: QueryChainTracking
  
  // Agent 相关
  agentId?: AgentId
  agentType?: string
}

6.2 tools/ 目录各工具实现

工具分类总览

graph TD
    subgraph "文件操作工具"
        READ[FileReadTool<br/>文件读取]
        WRITE[FileWriteTool<br/>文件写入]
        EDIT[FileEditTool<br/>文件编辑]
    end
    
    subgraph "搜索工具"
        GLOB[GlobTool<br/>文件匹配]
        GREP[GrepTool<br/>内容搜索]
        SEARCH[ToolSearchTool<br/>工具搜索]
    end
    
    subgraph "执行工具"
        BASH[BashTool<br/>Shell 命令]
        TASK[TaskOutputTool<br/>任务输出]
        TASKSTOP[TaskStopTool<br/>任务停止]
    end
    
    subgraph "网络工具"
        WEBFETCH[WebFetchTool<br/>网页获取]
        WEBSEARCH[WebSearchTool<br/>网络搜索]
    end
    
    subgraph "Agent 工具"
        AGENT[AgentTool<br/>子 Agent]
        SKILL[SkillTool<br/>技能调用]
        WORKTREE[EnterWorktreeTool<br/>工作树]
    end
    
    subgraph "状态管理工具"
        TODO[TodoWriteTool<br/>任务列表]
        CONFIG[ConfigTool<br/>配置管理]
        PLAN[EnterPlanModeTool<br/>计划模式]
    end
    
    subgraph "其他工具"
        LSP[LSPTool<br/>语言服务]
        NOTEBOOK[NotebookEditTool<br/>Jupyter 编辑]
        ASK[AskUserQuestionTool<br/>用户提问]
    end

文件操作工具

FileReadTool

// 核心功能:读取文件内容
const FileReadTool = buildTool({
  name: 'Read',
  inputSchema: z.object({
    file_path: z.string(),
    offset: z.number().optional(),
    limit: z.number().optional(),
  }),
  
  async call(args, context, canUseTool, parentMessage, onProgress) {
    // 权限检查
    const permissionResult = await canUseTool(this, args, context, ...)
    if (permissionResult.behavior !== 'allow') {
      return { data: { error: permissionResult.reason } }
    }
    
    // 读取文件
    const content = await readFile(args.file_path, args.offset, args.limit)
    
    // 更新缓存
    context.readFileState.set(args.file_path, content)
    
    return { data: { content, path: args.file_path } }
  },
  
  isReadOnly: () => true,
  maxResultSizeChars: Infinity,  // 不持久化结果
})

FileEditTool

// 核心功能:精确编辑文件
const FileEditTool = buildTool({
  name: 'Edit',
  inputSchema: z.object({
    file_path: z.string(),
    old_string: z.string(),
    new_string: z.string(),
    replace_all: z.boolean().optional(),
  }),
  
  async call(args, context, ...) {
    // 验证 old_string 存在
    const currentContent = await readFile(args.file_path)
    if (!currentContent.includes(args.old_string)) {
      return { data: { error: 'old_string not found' } }
    }
    
    // 执行编辑
    const newContent = args.replace_all
      ? currentContent.replaceAll(args.old_string, args.new_string)
      : currentContent.replace(args.old_string, args.new_string)
    
    await writeFile(args.file_path, newContent)
    
    return { data: { success: true } }
  },
  
  isDestructive: () => true,  // 标记为破坏性操作
})

FileWriteTool

// 核心功能:写入新文件
const FileWriteTool = buildTool({
  name: 'Write',
  inputSchema: z.object({
    file_path: z.string(),
    content: z.string(),
  }),
  
  isDestructive: (input) => {
    // 覆盖已存在文件为破坏性
    return fs.existsSync(input.file_path)
  },
  
  checkPermissions: async (input, context) => {
    // 检查是否覆盖重要文件
    if (await isProtectedFile(input.file_path)) {
      return { behavior: 'deny', reason: 'Protected file' }
    }
    return { behavior: 'allow', updatedInput: input }
  },
})

搜索工具

GlobTool

// 核心功能:文件模式匹配
const GlobTool = buildTool({
  name: 'Glob',
  inputSchema: z.object({
    pattern: z.string(),
    path: z.string().optional(),
  }),
  
  searchHint: 'find files matching pattern',
  
  async call(args, context, ...) {
    const matches = await glob(args.pattern, {
      cwd: args.path ?? getCwd(),
      absolute: true,
    })
    
    return { data: { matches } }
  },
  
  isReadOnly: () => true,
  isConcurrencySafe: () => true,
  isSearchOrReadCommand: () => ({ isSearch: true, isRead: false }),
})

GrepTool

// 核心功能:内容正则搜索
const GrepTool = buildTool({
  name: 'Grep',
  inputSchema: z.object({
    pattern: z.string(),
    path: z.string().optional(),
    glob: z.string().optional(),
    output_mode: z.enum(['content', 'files_with_matches', 'count']).optional(),
    -C: z.number().optional(),  // 上下文行数
  }),
  
  searchHint: 'search file contents regex pattern',
  
  async call(args, context, ...) {
    const results = await grep(args.pattern, {
      path: args.path ?? getCwd(),
      glob: args.glob,
      outputMode: args.output_mode ?? 'files_with_matches',
      contextLines: args['-C'],
    })
    
    return { data: results }
  },
  
  isSearchOrReadCommand: () => ({ isSearch: true, isRead: false }),
})

执行工具

BashTool

// 核心功能:执行 Shell 命令
const BashTool = buildTool({
  name: 'Bash',
  inputSchema: z.object({
    command: z.string(),
    description: z.string().optional(),
    timeout: z.number().optional(),
    run_in_background: z.boolean().optional(),
  }),
  
  async call(args, context, canUseTool, parentMessage, onProgress) {
    // 进度回调
    onProgress?.({
      toolUseID: '...',
      data: { type: 'bash', command: args.command, status: 'running' }
    })
    
    // 执行命令
    const result = await execa(args.command, {
      timeout: args.timeout ?? 120000,
      cwd: getCwd(),
      shell: true,
    })
    
    onProgress?.({
      toolUseID: '...',
      data: { type: 'bash', command: args.command, status: 'completed' }
    })
    
    return {
      data: {
        stdout: result.stdout,
        stderr: result.stderr,
        exitCode: result.exitCode,
      }
    }
  },
  
  isDestructive: (input) => {
    // 检查是否破坏性命令
    const destructivePatterns = ['rm', 'delete', 'drop', 'truncate']
    return destructivePatterns.some(p => input.command.includes(p))
  },
})

Agent 工具

AgentTool

// 核心功能:派发子 Agent
const AgentTool = buildTool({
  name: 'Agent',
  inputSchema: z.object({
    name: z.string().optional(),
    color: z.enum(['blue', 'yellow', 'green', 'purple', 'orange']).optional(),
    description: z.string().optional(),
    prompt: z.string(),
    tools: z.array(z.string()).optional(),
    model: z.string().optional(),
    isolated: z.boolean().optional(),
  }),
  
  async call(args, context, ...) {
    // 创建 AgentDefinition
    const agentDef: AgentDefinition = {
      name: args.name ?? 'default',
      color: args.color ?? 'blue',
      description: args.description ?? '',
      model: args.model,
      tools: args.tools,
      isolated: args.isolated ?? false,
    }
    
    // 如果需要隔离,创建 Worktree
    if (args.isolated) {
      const worktree = await createWorktree()
      // ... 在 worktree 中执行 Agent
    }
    
    // 启动子 Agent
    const result = await runSubAgent(agentDef, args.prompt, context)
    
    return { data: result }
  },
})

6.3 工具注册与调用机制

tools.ts 工具注册

export function getAllBaseTools(): Tools {
  return [
    AgentTool,
    TaskOutputTool,
    BashTool,
    ...(hasEmbeddedSearchTools() ? [] : [GlobTool, GrepTool]),
    ExitPlanModeV2Tool,
    FileReadTool,
    FileEditTool,
    FileWriteTool,
    NotebookEditTool,
    WebFetchTool,
    TodoWriteTool,
    WebSearchTool,
    TaskStopTool,
    AskUserQuestionTool,
    SkillTool,
    EnterPlanModeTool,
    ...(process.env.USER_TYPE === 'ant' ? [ConfigTool, TungstenTool] : []),
    ...(isEnvTruthy(process.env.ENABLE_LSP_TOOL) ? [LSPTool] : []),
    ...(isWorktreeModeEnabled() ? [EnterWorktreeTool, ExitWorktreeTool] : []),
    // ... 更多工具
  ]
}

export const getTools = (permissionContext: ToolPermissionContext): Tools => {
  // Simple 模式过滤
  if (isEnvTruthy(process.env.CLAUDE_CODE_SIMPLE)) {
    return filterToolsByDenyRules([BashTool, FileReadTool, FileEditTool], permissionContext)
  }
  
  // 获取基础工具并过滤
  const tools = getAllBaseTools()
  let allowedTools = filterToolsByDenyRules(tools, permissionContext)
  
  // REPL 模式处理
  if (isReplModeEnabled()) {
    allowedTools = allowedTools.filter(tool => !REPL_ONLY_TOOLS.has(tool.name))
  }
  
  // isEnabled 过滤
  return allowedTools.filter(tool => tool.isEnabled())
}

// 组合工具池(内置 + MCP)
export function assembleToolPool(
  permissionContext: ToolPermissionContext,
  mcpTools: Tools,
): Tools {
  const builtInTools = getTools(permissionContext)
  const allowedMcpTools = filterToolsByDenyRules(mcpTools, permissionContext)
  
  // 排序并去重(内置工具优先)
  return uniqBy(
    [...builtInTools].sort(byName).concat(allowedMcpTools.sort(byName)),
    'name',
  )
}

工具调用流程

sequenceDiagram
    participant Model as Claude Model
    participant Query as query()
    participant Matcher as Tool Matcher
    participant Permission as Permission Check
    participant Tool as Tool Instance
    participant Executor as Tool Executor
    
    Model->>Query: tool_use block
    Query->>Matcher: 查找工具
    Matcher->>Matcher: toolMatchesName()
    Matcher-->>Query: Tool 实例
    
    Query->>Permission: canUseTool()
    Permission->>Permission: validateInput()
    Permission->>Permission: checkPermissions()
    Permission->>Permission: 用户确认(交互模式)
    Permission-->>Query: PermissionResult
    
    alt 允许执行
        Query->>Tool: call(input, context, ...)
        Tool->>Executor: 执行操作
        Executor-->>Tool: 结果数据
        Tool-->>Query: ToolResult
    else 拒绝执行
        Query-->>Model: tool_result (error)
    end

工具匹配函数

export function toolMatchesName(
  tool: { name: string; aliases?: string[] },
  name: string,
): boolean {
  return tool.name === name || (tool.aliases?.includes(name) ?? false)
}

export function findToolByName(tools: Tools, name: string): Tool | undefined {
  return tools.find(t => toolMatchesName(t, name))
}

6.4 权限检查流程

权限检查流程图

flowchart TD
    subgraph "权限检查流程"
        START[Tool Use Request] --> VALIDATE{validateInput?}
        VALIDATE --> |"有"| VALIDATE_EXEC[执行输入验证]
        VALIDATE --> |"无"| PERMISSION[进入权限检查]
        VALIDATE_EXEC --> VALIDATE_RESULT{验证通过?}
        VALIDATE_RESULT --> |"否"| DENY1[返回错误]
        VALIDATE_RESULT --> |"是"| PERMISSION
        
        PERMISSION --> RULES[检查 Permission Rules]
        RULES --> ALWAYS_ALLOW{AlwaysAllow?}
        ALWAYS_ALLOW --> |"是"| ALLOW[允许执行]
        ALWAYS_ALLOW --> |"否"| ALWAYS_DENY{AlwaysDeny?}
        ALWAYS_DENY --> |"是"| DENY2[拒绝执行]
        ALWAYS_DENY --> |"否"| ALWAYS_ASK{AlwaysAsk?}
        ALWAYS_ASK --> |"是"| PROMPT[提示用户]
        ALWAYS_ASK --> |"否"| DEFAULT{默认规则}
        DEFAULT --> MODE{PermissionMode?}
        
        MODE --> |"auto"| AUTO_DECISION[自动决策]
        MODE --> |"default"| INTERACTIVE[交互确认]
        MODE --> |"plan"| PLAN_CHECK[Plan 模式检查]
        
        AUTO_DECISION --> ALLOW_OR_DENY
        INTERACTIVE --> USER_INPUT{用户选择}
        USER_INPUT --> |"允许"| ALLOW
        USER_INPUT --> |"拒绝"| DENY3
        PLAN_CHECK --> PLAN_ALLOW{符合 Plan?}
        PLAN_ALLOW --> |"是"| ALLOW
        PLAN_ALLOW --> |"否"| DENY4
        
        ALLOW --> EXECUTE[执行工具]
        DENY1 --> RETURN[返回结果]
        DENY2 --> RETURN
        DENY3 --> RETURN
        DENY4 --> RETURN
        EXECUTE --> RETURN
    end

PermissionResult 类型

export type PermissionResult = {
  behavior: 'allow' | 'deny'
  reason?: string
  updatedInput?: { [key: string]: unknown }
  decision?: 'accept' | 'reject'
  source?: 'rule' | 'user' | 'auto'
}

权限模式说明

模式说明行为
default默认模式交互式用户确认
auto自动模式根据规则自动决策
plan计划模式仅允许计划内操作
bypass绕过模式允许所有操作(危险)

bypass 模式风险

bypass 模式允许所有操作无需确认,仅用于测试或受控环境。生产环境应使用 defaultauto 模式。

权限规则配置

type ToolPermissionContext = {
  mode: PermissionMode
  additionalWorkingDirectories: Map<string, AdditionalWorkingDirectory>
  alwaysAllowRules: ToolPermissionRulesBySource  // 允许规则
  alwaysDenyRules: ToolPermissionRulesBySource   // 拒绝规则
  alwaysAskRules: ToolPermissionRulesBySource    // 确认规则
  isBypassPermissionsModeAvailable: boolean
  shouldAvoidPermissionPrompts?: boolean         // 后台 Agent
}

// 规则示例
const alwaysAllowRules = {
  userSettings: { Bash: ['git *', 'npm *'] },
  projectSettings: { Read: ['.claude/*'] },
}

const alwaysDenyRules = {
  projectSettings: { Bash: ['rm *', 'sudo *'] },
}

工具执行包装

// runTools 实现
export async function* runTools(
  toolUseBlocks: ToolUseBlock[],
  assistantMessages: AssistantMessage[],
  canUseTool: CanUseToolFn,
  toolUseContext: ToolUseContext,
): AsyncGenerator<ToolUpdate> {
  for (const block of toolUseBlocks) {
    const tool = findToolByName(toolUseContext.options.tools, block.name)
    
    if (!tool) {
      yield {
        message: createToolResultMessage(block.id, `Unknown tool: ${block.name}`),
      }
      continue
    }
    
    // 权限检查
    const permissionResult = await canUseTool(tool, block.input, toolUseContext, ...)
    
    if (permissionResult.behavior === 'deny') {
      yield {
        message: createToolResultMessage(block.id, permissionResult.reason ?? 'Denied'),
      }
      continue
    }
    
    // 执行工具
    const result = await tool.call(block.input, toolUseContext, canUseTool, ...)
    
    yield {
      message: createToolResultMessage(block.id, result.data),
    }
  }
}

下一章将分析 技能系统 的实现细节。

第7章:技能系统

本章深入分析 Claude Code 的技能系统实现,包括 Skill 目录结构、加载机制、内置 Skills 详解和自定义 Skill 开发指南。

7.1 skills/ 目录结构

目录概览

src/skills/
├── bundled/                     # 内置 Skills 目录
│   ├── index.ts                 # 入口索引
│   ├── batch.ts                 # 批量处理 Skill
│   ├── claudeApi.ts             # Claude API Skill
│   ├── claudeApiContent.ts      # API 内容处理
│   ├── debug.ts                 # 调试 Skill
│   ├── keybindings.ts           # 键绑定 Skill
│   ├── loop.ts                  # 循环执行 Skill
│   ├── loremIpsum.ts            # 测试文本 Skill
│   ├── remember.ts              # 记忆 Skill
│   ├── simplify.ts              # 代码简化 Skill
│   ├── skillify.ts              # Skill 创建 Skill
│   ├── stuck.ts                 # 卡住处理 Skill
│   ├── updateConfig.ts          # 配置更新 Skill
│   ├── verify/                  # 验证 Skills 子目录
│   │   ├── index.ts
│   │   └── ...
│   └── scheduleRemoteAgents.ts  # 远程 Agent 调度
│
├── bundledSkills.ts             # 内置 Skill 注册机制
├── loadSkillsDir.ts             # Skill 目录加载逻辑
└── mcpSkillBuilders.ts          # MCP Skill 构建器

Skill 文件组织结构

graph TD
    subgraph "Skill 来源"
        BUNDLED[bundled/ 目录<br/>内置 Skills]
        USER[userSettings<br/>~/.claude/skills/]
        PROJECT[projectSettings<br/>.claude/skills/]
        PLUGIN[Plugin Skills<br/>外部插件]
        MCP[MCP Skills<br/>MCP Server 提供]
    end
    
    subgraph "加载器"
        LOADER[loadSkillsDir.ts]
        REGISTER[bundledSkills.ts]
        PLUGIN_LOADER[loadPluginCommands.ts]
        MCP_BUILDER[mcpSkillBuilders.ts]
    end
    
    subgraph "注册"
        REGISTRY[Command Registry]
        SKILL_TOOL[SkillTool]
    end
    
    BUNDLED --> REGISTER --> REGISTRY
    USER --> LOADER --> REGISTRY
    PROJECT --> LOADER --> REGISTRY
    PLUGIN --> PLUGIN_LOADER --> REGISTRY
    MCP --> MCP_BUILDER --> REGISTRY
    
    REGISTRY --> SKILL_TOOL

7.2 Skill 加载机制

Skill 文件格式(.md 文件)

Skill 文件采用 Markdown 格式,支持 YAML frontmatter 定义元数据:

---
name: my-skill
description: My custom skill for specific tasks
aliases: [my-skill-alias]
whenToUse: Use this skill when you need to perform X
argumentHint: [target_file]
allowedTools: [Read, Edit, Bash]
model: claude-sonnet-4-20250514
disableModelInvocation: false
userInvocable: true
---

# My Skill

This skill helps you perform specific tasks.

## Instructions

1. First, read the target file
2. Analyze the content
3. Make necessary modifications

## Example

When you run `/my-skill src/main.ts`:
- The skill will read the file
- Analyze its structure
- Suggest improvements

Frontmatter 字段说明

字段类型说明
namestringSkill 名称(唯一标识)
descriptionstring功能描述
aliasesstring[]别名列表
whenToUsestring使用场景说明
argumentHintstring参数提示
allowedToolsstring[]可用工具列表
modelstring指定使用的模型
disableModelInvocationboolean是否禁用模型调用
userInvocableboolean是否用户可调用
hooksHooksSettingsHook 配置

BundledSkill 注册机制

// bundledSkills.ts 核心实现
export type BundledSkillDefinition = {
  name: string
  description: string
  aliases?: string[]
  whenToUse?: string
  argumentHint?: string
  allowedTools?: string[]
  model?: string
  disableModelInvocation?: boolean
  userInvocable?: boolean
  isEnabled?: () => boolean
  hooks?: HooksSettings
  context?: 'inline' | 'fork'
  agent?: string
  files?: Record<string, string>  // 附加文件
  getPromptForCommand: (args: string, context: ToolUseContext) => Promise<ContentBlockParam[]>
}

const bundledSkills: Command[] = []

export function registerBundledSkill(definition: BundledSkillDefinition): void {
  const { files } = definition
  
  // 如果有附加文件,延迟提取到磁盘
  let skillRoot: string | undefined
  let getPromptForCommand = definition.getPromptForCommand
  
  if (files && Object.keys(files).length > 0) {
    skillRoot = getBundledSkillExtractDir(definition.name)
    // 闭包本地缓存:每个进程只提取一次
    let extractionPromise: Promise<string | null> | undefined
    const inner = definition.getPromptForCommand
    getPromptForCommand = async (args, ctx) => {
      extractionPromise ??= extractBundledSkillFiles(definition.name, files)
      const extractedDir = await extractionPromise
      const blocks = await inner(args, ctx)
      if (extractedDir === null) return blocks
      return prependBaseDir(blocks, extractedDir)
    }
  }
  
  const command: Command = {
    type: 'prompt',
    name: definition.name,
    description: definition.description,
    aliases: definition.aliases,
    hasUserSpecifiedDescription: true,
    allowedTools: definition.allowedTools ?? [],
    // ... 其他属性
    source: 'bundled',
    loadedFrom: 'bundled',
    getPromptForCommand,
  }
  bundledSkills.push(command)
}

export function getBundledSkills(): Command[] {
  return [...bundledSkills]
}

loadSkillsDir.ts 加载逻辑

// 从目录加载 Skills
export async function getSkillDirCommands(cwd: string): Promise<Command[]> {
  const skillsDirs = [
    // 用户配置目录
    join(getClaudeConfigHomeDir(), 'skills'),
    // 项目配置目录
    join(cwd, '.claude', 'skills'),
    // 其他配置目录
    ...getAdditionalDirectoriesForClaudeMd().map(d => join(d, 'skills')),
  ]
  
  const commands: Command[] = []
  
  for (const dir of skillsDirs) {
    if (!fs.existsSync(dir)) continue
    
    const files = await loadMarkdownFilesForSubdir(dir)
    for (const file of files) {
      const command = await parseSkillFile(file, dir)
      if (command) commands.push(command)
    }
  }
  
  return commands
}

// 解析 Skill 文件
async function parseSkillFile(file: MarkdownFile, baseDir: string): Promise<Command | null> {
  const { frontmatter, content } = parseFrontmatter(file.content)
  
  // 验证必要字段
  if (!frontmatter.name) return null
  
  return {
    type: 'prompt',
    name: frontmatter.name,
    description: coerceDescriptionToString(frontmatter.description) 
      ?? extractDescriptionFromMarkdown(content),
    aliases: frontmatter.aliases,
    whenToUse: frontmatter.whenToUse,
    allowedTools: frontmatter.allowedTools ?? [],
    model: frontmatter.model,
    contentLength: content.length,
    source: getSettingSource(baseDir),
    loadedFrom: 'skills',
    userInvocable: frontmatter.userInvocable ?? true,
    getPromptForCommand: async (args, context) => {
      // 参数替换
      const processedContent = substituteArguments(content, args)
      return [{ type: 'text', text: processedContent }]
    },
  }
}

Skill 加载流程图

sequenceDiagram
    participant Init as 启动初始化
    participant Bundled as bundledSkills.ts
    participant Loader as loadSkillsDir.ts
    participant Parser as Frontmatter Parser
    participant Registry as Command Registry
    
    Init->>Bundled: registerBundledSkill()
    Bundled->>Registry: 注册内置 Skills
    
    Init->>Loader: getSkillDirCommands(cwd)
    Loader->>Loader: 遍历 skills 目录
    Loader->>Parser: 解析 .md 文件
    
    Parser->>Parser: parseFrontmatter()
    Parser->>Parser: 验证字段
    Parser-->>Loader: Command 对象
    
    Loader-->>Registry: 注册 Skills
    
    Registry->>Registry: getCommands()
    Registry-->>Init: 合并所有 Skills

7.3 内置 Skills 详解

核心 Skills 分类

graph TD
    subgraph "开发辅助"
        INIT[init<br/>初始化项目]
        REVIEW[review<br/>代码审查]
        SECURITY[security-review<br/>安全审查]
        DEBUG[debug<br/>调试辅助]
        SIMPLIFY[simplify<br/>代码简化]
    end
    
    subgraph "配置管理"
        CONFIG[updateConfig<br/>配置更新]
        KEYBIND[keybindings-help<br/>键绑定]
        PERMISSION[fewer-permission-prompts<br/>权限优化]
    end
    
    subgraph "API 相关"
        API[claude-api<br/>API 开发]
        API_CONTENT[claudeApiContent<br/>API 内容]
    end
    
    subgraph "流程控制"
        LOOP[loop<br/>循环执行]
        BATCH[batch<br/>批量处理]
        STUCK[stuck<br/>卡住处理]
    end
    
    subgraph "记忆系统"
        REMEMBER[remember<br/>记忆存储]
        VERIFY[verify<br/>验证检查]
    end

init Skill

// init Skill 定义
registerBundledSkill({
  name: 'init',
  description: 'Initialize a new CLAUDE.md file with codebase documentation',
  aliases: ['initialize'],
  whenToUse: 'Use when starting work on a new project to create documentation',
  argumentHint: '[project_path]',
  getPromptForCommand: async (args, context) => {
    return [
      { type: 'text', text: `
# Project Initialization

Analyze the codebase at ${args || 'current directory'} and create:

1. CLAUDE.md file with:
   - Project overview
   - Key files and their purposes
   - Build/test commands
   - Architecture summary

2. Recommended configurations:
   - .claude/settings.json if needed
   - MCP server recommendations

Use Glob to discover files, Read to analyze key files, and Write to create CLAUDE.md.
      ` }
    ]
  },
})

review Skill

registerBundledSkill({
  name: 'review',
  description: 'Review a pull request',
  aliases: ['pr-review'],
  whenToUse: 'Use when you need to review code changes in a PR',
  argumentHint: '[pr_url_or_number]',
  allowedTools: ['Bash', 'Read', 'Grep', 'Glob'],
  getPromptForCommand: async (args, context) => {
    // 解析 PR 信息
    const prInfo = await parsePRArg(args)
    
    return [
      { type: 'text', text: `
# Pull Request Review

Review PR ${prInfo.number}: ${prInfo.title}

## Tasks
1. Fetch the PR diff using \`gh pr diff ${prInfo.number}\`
2. Analyze the changes for:
   - Code quality issues
   - Potential bugs
   - Security concerns
   - Performance implications
3. Check if tests cover the changes
4. Provide actionable feedback

## Output Format
- Summary of changes
- Critical issues (if any)
- Suggestions for improvement
- Overall assessment
      ` }
    ]
  },
})

security-review Skill

registerBundledSkill({
  name: 'security-review',
  description: 'Complete a security review of pending changes',
  whenToUse: 'Use before merging sensitive changes or deploying to production',
  getPromptForCommand: async (args, context) => {
    return [
      { type: 'text', text: `
# Security Review

Perform a comprehensive security review of the current branch changes.

## Checklist
1. **Authentication & Authorization**
   - Check for proper auth checks
   - Verify permission boundaries

2. **Data Protection**
   - Sensitive data handling
   - Encryption requirements
   - Data validation

3. **Injection Prevention**
   - SQL injection
   - XSS vulnerabilities
   - Command injection

4. **API Security**
   - Input validation
   - Rate limiting
   - Error handling

5. **Dependency Security**
   - Known vulnerabilities
   - License compliance

Provide a detailed report with severity ratings and remediation steps.
      ` }
    ]
  },
})

claude-api Skill

registerBundledSkill({
  name: 'claude-api',
  description: 'Build, debug, and optimize Claude API / Anthropic SDK apps',
  whenToUse: 'TRIGGER when code imports anthropic/@anthropic-ai/sdk',
  allowedTools: ['Read', 'Edit', 'Write', 'Bash', 'Grep', 'Glob'],
  getPromptForCommand: async (args, context) => {
    return [
      { type: 'text', text: `
# Claude API Development

Assist with Claude API / Anthropic SDK development.

## Focus Areas
- Prompt caching optimization
- Model version migration (4.5 → 4.6 → 4.7)
- Tool use implementation
- Streaming response handling
- Batch processing
- Error handling and retries

## Best Practices
1. Always include prompt caching where applicable
2. Use proper error handling for API errors
3. Implement retry logic for transient failures
4. Track token usage for cost optimization

## Code Patterns
- Show proper SDK initialization
- Demonstrate tool definition patterns
- Provide streaming examples
      ` }
    ]
  },
})

Skill 触发机制

部分 Skill(如 claude-api)使用 whenToUse 字段定义触发条件。当代码匹配导入语句(如 import anthropic)时,Skill 会自动被建议使用。

7.4 自定义 Skill 开发

创建文件型 Skill

  1. 创建 .md 文件:
---
name: my-custom-skill
description: My custom skill description
aliases: [my-skill]
whenToUse: Use this when...
argumentHint: [arg1] [arg2]
allowedTools: [Read, Edit, Bash]
---

# My Custom Skill

Detailed instructions for the skill.

## Arguments

- `arg1`: First argument description
- `arg2`: Second argument description

## Steps

1. First step instruction
2. Second step instruction
3. ...

## Output

Expected output format.
  1. 放置到正确目录:
# 用户级 Skill
~/.claude/skills/my-custom-skill.md

# 项目级 Skill
.claude/skills/my-custom-skill.md

创建 Bundled Skill

// 在 bundled/index.ts 中注册
import { registerBundledSkill } from '../bundledSkills.js'

registerBundledSkill({
  name: 'my-bundled-skill',
  description: 'A bundled skill that ships with the CLI',
  whenToUse: 'Use when condition X is met',
  argumentHint: '[target]',
  allowedTools: ['Read', 'Write', 'Bash'],
  
  // 附加参考文件(可选)
  files: {
    'templates/default.md': '# Template content...',
    'schemas/config.json': '{"type": "object", ...}',
  },
  
  getPromptForCommand: async (args, context) => {
    const target = args || 'default'
    
    return [
      { type: 'text', text: `
# My Bundled Skill

Target: ${target}

## Instructions
Detailed instructions here...

## Available Resources
- templates/default.md (if needed)
- schemas/config.json (for validation)
      ` }
    ]
  },
})

Skill 开发最佳实践

flowchart TB
    subgraph "Skill 设计原则"
        P1[明确单一职责]
        P2[提供清晰指令]
        P3[限制工具范围]
        P4[使用 whenToUse]
        P5[处理参数错误]
    end
    
    subgraph "Skill 内容结构"
        S1[标题和概述]
        S2[参数说明]
        S3[执行步骤]
        S4[输出格式]
        S5[错误处理]
    end
    
    subgraph "测试验证"
        T1[测试边界情况]
        T2[验证工具权限]
        T3[检查输出格式]
    end
    
    P1 --> S1
    P2 --> S3
    P3 --> T2
    P4 --> S1
    P5 --> S5
    S1 --> T1
    S3 --> T3
    S4 --> T3

工具范围限制

使用 allowedTools 限制 Skill 可用的工具,避免不必要的权限请求。例如,只读 Skill 不应包含 EditWrite

Skill 调用流程

sequenceDiagram
    participant User as 用户
    participant REPL as REPL
    participant Parser as 命令解析
    participant SkillTool as SkillTool
    participant Skill as Skill Instance
    participant Model as Claude Model
    
    User->>REPL: /my-skill arg1 arg2
    REPL->>Parser: 解析斜杠命令
    Parser->>Parser: findCommand('my-skill')
    Parser-->>REPL: Command 对象
    
    REPL->>SkillTool: 调用 Skill
    SkillTool->>Skill: getPromptForCommand('arg1 arg2')
    Skill-->>SkillTool: ContentBlockParam[]
    
    SkillTool->>Model: 发送 Skill 内容
    Model->>Model: 执行 Skill 指令
    Model->>SkillTool: 工具调用请求
    
    SkillTool->>SkillTool: 检查 allowedTools
    SkillTool->>SkillTool: 执行工具
    SkillTool-->>Model: tool_result
    
    Model-->>SkillTool: 最终响应
    SkillTool-->>REPL: Skill 结果
    REPL->>User: 显示结果

下一章将分析 命令系统 的实现细节。

第8章:命令系统

本章深入分析 Claude Code 的命令系统实现,包括命令目录结构、注册机制、主要命令详解和命令类型分类。

8.1 commands/ 目录结构

目录概览

src/commands/
├── add-dir/                     # 添加工作目录
├── agents/                      # Agent 管理
├── branch/                      # 分支切换
├── clear/                       # 清屏
├── compact/                     # 上下文压缩
├── config/                      # 配置管理
├── context/                     # 上下文查看
├── cost/                        # 费用统计
├── doctor/                      # 系统诊断
├── exit/                        # 退出 REPL
├── fast/                        # Fast Mode 切换
├── files/                       # 文件列表
├── help/                        # 帮助信息
├── hooks/                       # Hook 管理
├── init.js                      # 项目初始化
├── keybindings/                 # 键绑定配置
├── login/                       # 登录认证
├── logout/                      # 注销
├── mcp/                         # MCP 服务器管理
├── memory/                      # 记忆管理
├── model/                       # 模型选择
├── permissions/                 # 权限管理
├── plan/                        # Plan Mode
├── plugin/                      # 插件管理
├── resume/                      # 恢复会话
├── review.js                    # PR 审查
├── security-review.js           # 安全审查
├── session/                     # 会话管理
├── skills/                      # Skill 管理
├── status/                      # 状态查看
├── terminalSetup/               # 终端配置
├── theme/                       # 主题设置
├── vim/                         # Vim Mode
├── ...
├── commit.js                    # Git 提交
├── commit-push-pr.js            # 提交推送 PR
├── init.ts                      # 初始化入口
└── commands.ts                  # 命令注册表

命令类型分类

graph TD
    subgraph "命令类型"
        PROMPT[prompt<br/>发送到模型]
        LOCAL[local<br/>本地执行]
        LOCAL_JSX[local-jsx<br/>渲染 UI]
    end
    
    subgraph "Prompt 命令"
        P1[init<br/>初始化]
        P2[review<br/>审查]
        P3[security-review<br/>安全审查]
        P4[compact<br/>压缩]
    end
    
    subgraph "Local 命令"
        L1[clear<br/>清屏]
        L2[cost<br/>费用]
        L3[status<br/>状态]
        L4[model<br/>模型]
    end
    
    subgraph "Local-JSX 命令"
        J1[config<br/>配置]
        J2[mcp<br/>MCP]
        J3[login<br/>登录]
        J4[permissions<br/>权限]
    end
    
    PROMPT --> P1
    PROMPT --> P2
    PROMPT --> P3
    PROMPT --> P4
    LOCAL --> L1
    LOCAL --> L2
    LOCAL --> L3
    LOCAL --> L4
    LOCAL_JSX --> J1
    LOCAL_JSX --> J2
    LOCAL_JSX --> J3
    LOCAL_JSX --> J4

Command 类型定义

export type Command = 
  | PromptCommand
  | LocalCommand
  | LocalJSXCommand

export type PromptCommand = {
  type: 'prompt'
  name: string
  description: string
  aliases?: string[]
  
  // Skill 相关属性
  whenToUse?: string
  argumentHint?: string
  allowedTools?: string[]
  model?: string
  disableModelInvocation?: boolean
  userInvocable?: boolean
  contentLength: number
  
  // 来源信息
  source: 'builtin' | 'bundled' | 'skills' | 'plugin' | 'mcp'
  loadedFrom?: LoadedFrom
  
  // Hook 配置
  hooks?: HooksSettings
  
  // 执行方法
  getPromptForCommand: (args: string, context: ToolUseContext) => Promise<ContentBlockParam[]>
}

export type LocalCommand = {
  type: 'local'
  name: string
  description: string
  aliases?: string[]
  
  // 非交互模式处理
  nonInteractive?: LocalCommandNonInteractive
  
  // 执行方法
  handler: (args: string, context: LocalCommandContext) => Promise<LocalCommandResult>
}

export type LocalJSXCommand = {
  type: 'local-jsx'
  name: string
  description: string
  aliases?: string[]
  
  // JSX 渲染
  component: React.ComponentType<{ args: string; context: LocalJSXCommandContext }>
  
  // 可用性检查
  availability?: ('claude-ai' | 'console')[]
  isEnabled?: () => boolean
}

8.2 命令注册机制

commands.ts 注册逻辑

// 命令导入
import addDir from './commands/add-dir/index.js'
import commit from './commands/commit.js'
import config from './commands/config/index.js'
import init from './commands/init.js'
import review from './commands/review.js'
import securityReview from './commands/security-review.js'
// ... 更多命令导入

// 命令数组
const COMMANDS = memoize((): Command[] => [
  addDir,
  advisor,
  agents,
  branch,
  clear,
  config,
  cost,
  doctor,
  exit,
  fast,
  files,
  help,
  init,
  keybindings,
  mcp,
  model,
  permissions,
  plan,
  review,
  securityReview,
  session,
  skills,
  status,
  theme,
  vim,
  // ... 更多命令
])

// 构建命令名称集合
export const builtInCommandNames = memoize(
  (): Set<string> =>
    new Set(COMMANDS().flatMap(_ => [_.name, ...(_.aliases ?? [])])),
)

// 主命令加载函数
const loadAllCommands = memoize(async (cwd: string): Promise<Command[]> => {
  const [
    { skillDirCommands, pluginSkills, bundledSkills, builtinPluginSkills },
    pluginCommands,
    workflowCommands,
  ] = await Promise.all([
    getSkills(cwd),
    getPluginCommands(),
    getWorkflowCommands ? getWorkflowCommands(cwd) : Promise.resolve([]),
  ])

  return [
    ...bundledSkills,
    ...builtinPluginSkills,
    ...skillDirCommands,
    ...workflowCommands,
    ...pluginCommands,
    ...pluginSkills,
    ...COMMANDS(),
  ]
})

// 获取可用命令
export async function getCommands(cwd: string): Promise<Command[]> {
  const allCommands = await loadAllCommands(cwd)
  
  // 获取动态 Skills
  const dynamicSkills = getDynamicSkills()
  
  // 过滤可用命令
  const baseCommands = allCommands.filter(
    _ => meetsAvailabilityRequirement(_) && isCommandEnabled(_),
  )
  
  // 合并动态 Skills
  return [...baseCommands, ...uniqueDynamicSkills]
}

命令加载流程图

sequenceDiagram
    participant Main as main.tsx
    participant Commands as commands.ts
    participant Skills as skills/
    participant Plugins as plugins/
    participant Registry as Command Registry
    
    Main->>Commands: getCommands(cwd)
    Commands->>Commands: loadAllCommands(cwd)
    
    Commands->>Skills: getSkills(cwd)
    Skills-->>Commands: skillDirCommands, bundledSkills
    
    Commands->>Plugins: getPluginCommands()
    Plugins-->>Commands: pluginCommands, pluginSkills
    
    Commands->>Commands: COMMANDS()
    Commands-->>Commands: 内置命令列表
    
    Commands->>Commands: 合并所有命令
    Commands->>Commands: meetsAvailabilityRequirement()
    Commands->>Commands: isCommandEnabled()
    
    Commands-->>Main: 可用命令列表

可用性检查

export function meetsAvailabilityRequirement(cmd: Command): boolean {
  if (!cmd.availability) return true
  
  for (const a of cmd.availability) {
    switch (a) {
      case 'claude-ai':
        if (isClaudeAISubscriber()) return true
        break
      case 'console':
        if (
          !isClaudeAISubscriber() &&
          !isUsing3PServices() &&
          isFirstPartyAnthropicBaseUrl()
        ) return true
        break
    }
  }
  return false
}

命令查找函数

export function findCommand(
  commandName: string,
  commands: Command[],
): Command | undefined {
  return commands.find(
    _ =>
      _.name === commandName ||
      getCommandName(_) === commandName ||
      _.aliases?.includes(commandName),
  )
}

export function getCommand(commandName: string, commands: Command[]): Command {
  const command = findCommand(commandName, commands)
  if (!command) {
    throw ReferenceError(
      `Command ${commandName} not found. Available commands: ${commands
        .map(_ => getCommandName(_))
        .sort()
        .join(', ')}`,
    )
  }
  return command
}

8.3 主要命令详解

/init 命令

// init 命令定义
export default {
  type: 'prompt',
  name: 'init',
  description: 'Initialize a new CLAUDE.md file with codebase documentation',
  aliases: ['initialize'],
  contentLength: 0,
  progressMessage: 'initializing',
  source: 'builtin',
  async getPromptForCommand(args, context) {
    const cwd = args || getCwd()
    
    return [
      { type: 'text', text: `
# Project Initialization

Analyze the codebase at ${cwd} and create:

## CLAUDE.md Structure
1. Project overview
2. Key directories and their purposes
3. Important files
4. Build/test/lint commands
5. Architecture summary
6. Development guidelines

## Instructions
1. Use Glob to discover project structure
2. Use Read to analyze key configuration files
3. Use Grep to find patterns and conventions
4. Use Write to create CLAUDE.md

Focus on practical, actionable information for future sessions.
      ` }
    ]
  },
}

/commit 命令

// commit 命令定义(Internal Only)
export default {
  type: 'prompt',
  name: 'commit',
  description: 'Create a git commit',
  contentLength: 0,
  progressMessage: 'creating commit',
  source: 'builtin',
  async getPromptForCommand(args, context) {
    return [
      { type: 'text', text: `
# Create Git Commit

Create a git commit with the staged changes.

## Requirements
1. Run \`git status\` to see staged changes
2. Run \`git diff --cached\` to see diff
3. Analyze changes and draft commit message
4. Run \`git commit\` with the message

## Commit Message Guidelines
- Focus on the "why" rather than the "what"
- Be concise (under 72 chars for title)
- Use imperative mood
- Include Co-Authored-By attribution

## Important
- Never use --no-verify
- Never skip hooks
- If pre-commit hook fails, fix the issue and retry

Co-Authored-By: Claude <noreply@anthropic.com>
      ` }
    ]
  },
}

Internal Only Commands

/commit/review/security-review 等命令标记为 INTERNAL_ONLY_COMMANDS,仅在 USER_TYPE === 'ant' 且非 Demo 环境下可用。这些命令涉及敏感操作,不暴露给外部用户。

/review 命令

// review 命令定义
export default {
  type: 'prompt',
  name: 'review',
  description: 'Review a pull request',
  aliases: ['pr-review'],
  contentLength: 0,
  progressMessage: 'reviewing',
  source: 'builtin',
  async getPromptForCommand(args, context) {
    // 解析 PR 参数
    const prNumber = parsePRArg(args)
    
    return [
      { type: 'text', text: `
# Pull Request Review

Review PR #${prNumber} comprehensively.

## Steps
1. Fetch PR info: \`gh pr view ${prNumber}\`
2. Get diff: \`gh pr diff ${prNumber}\`
3. Analyze changes for:
   - Code quality
   - Bug risks
   - Security concerns
   - Test coverage
4. Check commit messages
5. Verify CI status

## Output Format
### Summary
Brief overview of changes

### Critical Issues
High-priority problems requiring attention

### Suggestions
Improvement recommendations

### Overall Assessment
Approve / Request Changes / Comment
      ` }
    ]
  },
}

/config 命令(Local-JSX)

// config 命令定义(交互式 UI)
export default {
  type: 'local-jsx',
  name: 'config',
  description: 'Configure Claude Code settings',
  availability: ['claude-ai', 'console'],
  
  component: ({ args, context }) => {
    // 渲染配置 UI
    return (
      <Box flexDirection="column">
        <Text bold>Configuration Settings</Text>
        {/* 配置选项 UI */}
      </Box>
    )
  },
  
  isEnabled: () => true,
}

/mcp 命令(子命令结构)

// mcp 命令定义(带子命令)
export default {
  type: 'local-jsx',
  name: 'mcp',
  description: 'Manage MCP servers',
  
  // 子命令
  subcommands: {
    add: {
      description: 'Add an MCP server',
      handler: async (args) => { /* ... */ },
    },
    remove: {
      description: 'Remove an MCP server',
      handler: async (args) => { /* ... */ },
    },
    list: {
      description: 'List configured MCP servers',
      handler: async () => { /* ... */ },
    },
    restart: {
      description: 'Restart an MCP server',
      handler: async (args) => { /* ... */ },
    },
  },
}

命令类型处理流程

flowchart TB
    subgraph "命令输入"
        INPUT[/command args] --> PARSE[解析命令名和参数]
    end
    
    subgraph "命令查找"
        PARSE --> FIND[findCommand]
        FIND --> CHECK{找到命令?}
        CHECK --> |"否"| ERROR[抛出错误]
        CHECK --> |"是"| TYPE{命令类型?}
    end
    
    subgraph "类型处理"
        TYPE --> |"prompt"| PROMPT[getPromptForCommand]
        TYPE --> |"local"| LOCAL[handler]
        TYPE --> |"local-jsx"| JSX[渲染 Component]
    end
    
    subgraph "执行结果"
        PROMPT --> MODEL[发送到模型]
        LOCAL --> RESULT[返回结果文本]
        JSX --> UI[显示 UI]
        
        MODEL --> TOOLS[工具调用循环]
        TOOLS --> OUTPUT[最终输出]
    end

远程模式命令过滤

// 远程安全命令列表
export const REMOTE_SAFE_COMMANDS: Set<Command> = new Set([
  session,    // 显示远程会话 QR
  exit,       // 退出
  clear,      // 清屏
  help,       // 帮助
  theme,      // 主题
  color,      // Agent 颜色
  vim,        // Vim 模式
  cost,       // 费用
  usage,      // 使用统计
  copy,       // 复制消息
  // ...
])

// Bridge 安全命令
export const BRIDGE_SAFE_COMMANDS: Set<Command> = new Set([
  compact,    // 上下文压缩
  clear,      // 清屏
  cost,       // 费用
  summary,    // 总结
  releaseNotes, // 更新日志
  files,      // 文件列表
])

// 过滤远程模式命令
export function filterCommandsForRemoteMode(commands: Command[]): Command[] {
  return commands.filter(cmd => REMOTE_SAFE_COMMANDS.has(cmd))
}

命令生命周期

stateDiagram-v2
    [*] --> Registered: 导入命令
    Registered --> Loaded: loadAllCommands
    Loaded --> Available: meetsAvailabilityRequirement + isEnabled
    Available --> Invoked: 用户调用
    Invoked --> Executing: 执行命令
    Executing --> Completed: 完成
    Executing --> Failed: 错误
    Completed --> [*]
    Failed --> [*]
    
    Available --> Disabled: isEnabled = false
    Disabled --> [*]

命令设计建议

  1. prompt 命令用于发送到模型处理,适合复杂任务
  2. local 命令用于快速本地操作,无需模型参与
  3. local-jsx 命令用于需要交互 UI 的场景
  4. 使用 availability 限制命令可见性
  5. 使用 aliases 提供便捷别名

至此,核心模块篇已完整分析了 Claude Code 的查询引擎、工具系统、技能系统和命令系统。后续章节将继续分析 UI 组件、Bridge 远程控制和服务层实现。

第九章:Ink 终端渲染引擎

本章深入分析 Claude Code 的终端 UI 渲染引擎 —— Ink 框架的实现原理和优化策略。

9.1 Ink 框架原理

什么是 Ink

Ink 是一个「React for CLI」的终端 UI 渲染框架,它将 React 的组件化思想带入命令行界面开发。

Note

Ink 使用 React Reconciler 将 React 组件树渲染到终端屏幕,开发者可以用熟悉的 React 语法(组件、Hooks、Context)来构建交互式终端应用。

核心架构

graph TD
    subgraph React["React 层"]
        RC[React Components]
        RH[React Hooks]
        RX[React Context]
    end
    
    subgraph Reconciler["Reconciler 层"]
        RR[React Reconciler]
        DM[DOM 节点管理]
    end
    
    subgraph Layout["布局层"]
        YG[Yoga 布局引擎]
        LN[Layout Node]
    end
    
    subgraph Render["渲染层"]
        RN[render-node-to-output]
        OP[Output 缓冲区]
        SC[Screen 缓冲区]
    end
    
    subgraph Terminal["终端层"]
        TB[终端输出]
        ANSI[ANSI 转义码]
    end
    
    RC --> RR
    RH --> RR
    RX --> RR
    RR --> DM
    DM --> YG
    YG --> LN
    LN --> RN
    RN --> OP
    OP --> SC
    SC --> TB
    TB --> ANSI

React Reconciler 适配

Ink 通过自定义 React Reconciler 实现终端渲染。关键配置位于 src/ink/reconciler.ts

const reconciler = createReconciler<
  ElementNames,      // 容器元素类型
  Props,             // 属性类型
  DOMElement,        // 容器实例
  DOMElement,        // 根容器类型
  TextNode,          // 文本节点类型
  DOMElement,        // 子容器类型
  unknown,           // 实例句柄
  unknown,           // 公共实例
  DOMElement,        // 容器实例
  HostContext,       // Host Context
  null,              // UpdatePayload
  NodeJS.Timeout,    // Timeout 类型
  -1,                 // No Timeout 常量
  null               //Suspension 类型
>({
  getRootHostContext: () => ({ isInsideText: false }),
  
  createInstance(originalType, newProps, root, hostContext) {
    const node = createNode(type);
    // 应用属性到节点
    for (const [key, value] of Object.entries(newProps)) {
      applyProp(node, key, value);
    }
    return node;
  },
  
  createTextInstance(text, root, hostContext) {
    return createTextNode(text);
  },
  
  // 其他 reconciler 方法...
});

元素类型映射

graph LR
    subgraph React["React 组件"]
        B[Box]
        T[Text]
        L[Link]
    end
    
    subgraph Ink["Ink 元素"]
        IB[ink-box]
        IT[ink-text]
        IV[ink-virtual-text]
        IL[ink-link]
    end
    
    B --> IB
    T --> IT
    T -.->|"嵌套在 Text 内"| IV
    L --> IL

9.2 src/ink/ 目录核心代码

文件结构

src/ink/
├── reconciler.ts          # React Reconciler 配置
├── renderer.ts            # 主渲染器
├── render-node-to-output.ts  # 节点到输出渲染
├── output.ts              # 输出缓冲区管理
├── dom.ts                 # DOM 节点操作
├── stringWidth.ts         # 字符宽度计算
├── wrap-text.ts           # 文本换行处理
├── screen.ts              # Screen 缓冲区
├── layout/
│   ├── engine.ts          # 布局引擎
│   ├── yoga.ts            # Yoga 绑定
│   └── node.ts            # 布局节点
├── events/
│   ├── dispatcher.ts      # 事件分发
│   ├── input-event.ts     # 输入事件
│   └── keyboard-event.ts  # 键盘事件
├── hooks/
│   ├── use-input.ts       # 输入 Hook
│   ├── use-app.ts         # App Hook
│   └── use-stdin.ts       # Stdin Hook
└── components/
    ├── AppContext.ts      # App Context
    └── StdinContext.ts    # Stdin Context

renderer.ts 核心逻辑

渲染器负责将布局计算后的节点树转换为屏幕输出:

export default function createRenderer(
  node: DOMElement,
  stylePool: StylePool,
): Renderer {
  let output: Output | undefined;
  
  return options => {
    const { frontFrame, backFrame, terminalWidth, terminalRows } = options;
    
    // 计算布局尺寸
    const computedHeight = node.yogaNode?.getComputedHeight();
    const computedWidth = node.yogaNode?.getComputedWidth();
    
    // 验证布局有效性
    if (!node.yogaNode || hasInvalidHeight || hasInvalidWidth) {
      return createEmptyFrame();
    }
    
    const width = Math.floor(node.yogaNode.getComputedWidth());
    const height = Math.floor(node.yogaNode.getComputedHeight());
    
    // 创建或重用 Output
    const screen = backScreen ?? createScreen(width, height, ...);
    if (output) {
      output.reset(width, height, screen);
    } else {
      output = new Output({ width, height, stylePool, screen });
    }
    
    // 渲染节点树到输出
    renderNodeToOutput(node, output, { prevScreen });
    
    return {
      screen: output.get(),
      viewport: { width: terminalWidth, height: terminalRows },
      cursor: { x: 0, y: screen.height, visible: !isTTY }
    };
  };
}

Tip

渲染器采用双缓冲技术(frontFrame/backFrame),只在必要时执行全屏刷新,极大减少了终端闪烁。

DOM 节点管理

dom.ts 定义了终端 DOM 树的节点类型和操作:

export type ElementNames =
  | 'ink-root'
  | 'ink-box'
  | 'ink-text'
  | 'ink-virtual-text'
  | 'ink-link'
  | 'ink-progress'
  | 'ink-raw-ansi';

export type DOMElement = {
  nodeName: ElementNames;
  attributes: Record<string, DOMNodeAttribute>;
  childNodes: DOMNode[];
  yogaNode?: LayoutNode;      // Yoga 布局节点
  dirty: boolean;              // 需要重新渲染
  scrollTop?: number;          // 滚动位置
  scrollHeight?: number;       // 滚动内容高度
  _eventHandlers?: Record<string, unknown>;
};

export type TextNode = {
  nodeName: '#text';
  nodeValue: string;
};

节点操作函数

// 创建节点
export const createNode = (nodeName: ElementNames): DOMElement => {
  const needsYogaNode = nodeName !== 'ink-virtual-text';
  const node: DOMElement = {
    nodeName,
    style: {},
    attributes: {},
    childNodes: [],
    yogaNode: needsYogaNode ? createLayoutNode() : undefined,
    dirty: false,
  };
  return node;
};

// 添加子节点
export const appendChildNode = (node, childNode) => {
  childNode.parentNode = node;
  node.childNodes.push(childNode);
  if (childNode.yogaNode) {
    node.yogaNode?.insertChild(childNode.yogaNode, ...);
  }
  markDirty(node);
};

// 标记节点脏
export const markDirty = (node?: DOMNode) => {
  // 向上传播 dirty 标记
  while (current) {
    current.dirty = true;
    current = current.parentNode;
  }
};

9.3 Yoga 布局引擎

Flexbox 布局适配

Ink 使用 Yoga 布局引擎实现 Flexbox 布局:

graph TD
    subgraph React["React 组件"]
        B1[Box flexDirection=row]
        B2[Box flex=1]
        B3[Box width=20]
    end
    
    subgraph Yoga["Yoga 布局"]
        Y1[计算宽度分配]
        Y2[计算子节点位置]
        Y3[输出绝对坐标]
    end
    
    B1 --> Y1
    B2 --> Y1
    B3 --> Y1
    Y1 --> Y2
    Y2 --> Y3
    
    Y3 --> O1[x=0, y=0, width=60]
    Y3 --> O2[x=0, y=0, width=40]
    Y3 --> O3[x=40, y=0, width=20]

布局计算流程

sequenceDiagram
    participant RR as React Reconciler
    participant DM as DOM Manager
    participant YG as Yoga Engine
    participant RN as Renderer
    
    RR->>DM: createInstance(Box)
    DM->>YG: createLayoutNode()
    DM->>YG: applyStyles(yogaNode, style)
    
    RR->>DM: appendChild(parent, child)
    DM->>YG: insertChild(yogaNode)
    
    RR->>YG: calculateLayout()
    YG->>YG: 计算所有节点位置
    
    YG->>RN: getComputedLeft/Top/Width/Height
    RN->>RN: renderNodeToOutput

样式应用

// styles.ts
export type Styles = {
  flexDirection?: 'row' | 'row-reverse' | 'column' | 'column-reverse';
  justifyContent?: 'flex-start' | 'center' | 'flex-end' | 'space-between' | 'space-around';
  alignItems?: 'flex-start' | 'center' | 'stretch' | 'flex-end';
  flexWrap?: 'wrap' | 'nowrap' | 'wrap-reverse';
  width?: number | string;
  height?: number | string;
  minWidth?: number;
  maxWidth?: number;
  padding?: number;
  margin?: number;
  backgroundColor?: Color;
  borderColor?: Color;
  borderStyle?: 'single' | 'double' | 'round';
  overflow?: 'visible' | 'hidden' | 'scroll';
  textWrap?: 'wrap' | 'wrap-trim' | 'truncate' | 'truncate-start' | 'truncate-middle';
};

// 应用样式到 Yoga 节点
applyStyles(yogaNode: LayoutNode, style: Styles) {
  if (style.flexDirection) {
    yogaNode.setFlexDirection(mapFlexDirection(style.flexDirection));
  }
  if (style.width) {
    yogaNode.setWidth(parseDimension(style.width));
  }
  // ...更多样式映射
}

9.4 终端渲染优化

输出缓冲机制

graph TD
    subgraph Buffer["缓冲区管理"]
        SC1[Screen 当前帧]
        SC2[Screen 上一帧]
        OP[Output 操作队列]
    end
    
    subgraph Operations["操作类型"]
        W[WriteOperation]
        B[BlitOperation]
        C[ClearOperation]
        S[ShiftOperation]
    end
    
    subgraph Output["输出生成"]
        D[diff 增量计算]
        A[ANSI 序列生成]
    end
    
    OP --> W
    OP --> B
    OP --> C
    OP --> S
    
    W --> SC1
    B --> SC1
    C --> SC1
    S --> SC1
    
    SC1 --> D
    SC2 --> D
    D --> A

Output 类设计

export default class Output {
  private readonly operations: Operation[] = [];
  private charCache: Map<string, ClusteredChar[]> = new Map();
  
  // 写入文本
  write(x: number, y: number, text: string, softWrap?: boolean[]) {
    this.operations.push({ type: 'write', x, y, text, softWrap });
  }
  
  // 从前一帧复制区域
  blit(src: Screen, x: number, y: number, width: number, height: number) {
    this.operations.push({ type: 'blit', src, x, y, width, height });
  }
  
  // 清除区域
  clear(region: Rectangle, fromAbsolute?: boolean) {
    this.operations.push({ type: 'clear', region, fromAbsolute });
  }
  
  // 滚动行
  shift(top: number, bottom: number, n: number) {
    this.operations.push({ type: 'shift', top, bottom, n });
  }
  
  // 生成最终输出
  get(): Screen {
    // 处理所有操作,生成 Screen
    for (const operation of this.operations) {
      switch (operation.type) {
        case 'write': writeLineToScreen(...);
        case 'blit': blitRegion(...);
        case 'clear': markDamage(...);
        case 'shift': shiftRows(...);
      }
    }
    return screen;
  }
}

Tip

Blit 操作是性能优化的关键:对于未变化的区域,直接从前一帧复制而非重新渲染,大幅减少终端写入量。

ANSI 转义码处理

// termio/ansi.ts
export const ANSI_CODES = {
  // 颜色
  RESET: '\x1b[0m',
  BOLD: '\x1b[1m',
  DIM: '\x1b[2m',
  
  // 前景色
  RED: '\x1b[31m',
  GREEN: '\x1b[32m',
  YELLOW: '\x1b[33m',
  BLUE: '\x1b[34m',
  MAGENTA: '\x1b[35m',
  CYAN: '\x1b[36m',
  WHITE: '\x1b[37m',
  
  // 光标控制
  CURSOR_UP: '\x1b[A',
  CURSOR_DOWN: '\x1b[B',
  CURSOR_FORWARD: '\x1b[C',
  CURSOR_BACK: '\x1b[D',
  CURSOR_HIDE: '\x1b[?25l',
  CURSOR_SHOW: '\x1b[?25h',
  CURSOR_POSITION: '\x1b[H',
  
  // 屏幕控制
  CLEAR_SCREEN: '\x1b[2J',
  CLEAR_LINE: '\x1b[2K',
  ALTERNATE_SCREEN: '\x1b[?1049h',
  MAIN_SCREEN: '\x1b[?1049l',
};

字符宽度计算(中文字符)

// stringWidth.ts
function stringWidthJavaScript(str: string): number {
  // 快速路径:纯 ASCII
  let isPureAscii = true;
  for (let i = 0; i < str.length; i++) {
    const code = str.charCodeAt(i);
    if (code >= 127 || code === 0x1b) {
      isPureAscii = false;
      break;
    }
  }
  if (isPureAscii) {
    // 只计算可打印字符
    let width = 0;
    for (let i = 0; i < str.length; i++) {
      if (str.charCodeAt(i) > 0x1f) width++;
    }
    return width;
  }
  
  // Unicode 处理
  for (const { segment: grapheme } of getGraphemeSegmenter().segment(str)) {
    // Emoji 处理
    if (EMOJI_REGEX.test(grapheme)) {
      width += getEmojiWidth(grapheme);
      continue;
    }
    // 中文字符等宽字符处理
    for (const char of grapheme) {
      const codePoint = char.codePointAt(0)!;
      if (!isZeroWidth(codePoint)) {
        width += eastAsianWidth(codePoint, { ambiguousAsWide: false });
        break;  // grapheme 只计第一个非零宽字符
      }
    }
  }
  return width;
}

字符宽度注意事项

  • 中文、日文、韩文等 CJK 字符宽度为 2
  • Emoji 宽度通常为 2,但某些组合 Emoji 可能更宽
  • 需要处理 grapheme cluster(如 👨‍👩‍👧‍👦 是一个视觉单元)
  • ANSI 转义码不计入宽度

滚动优化

// DECSTBM 滚动提示
export type ScrollHint = {
  top: number;      // 滚动区域顶部(0-indexed)
  bottom: number;   // 滚动区域底部
  delta: number;    // 滚动量(>0 向上滚动)
};

// 渲染时检测滚动变化
if (contentCached && contentCached.y !== contentY) {
  const delta = contentCached.y - contentY;
  if (Math.abs(delta) < innerHeight) {
    // 小范围滚动:使用 DECSTBM + blit+shift 快速路径
    scrollHint = { top: regionTop, bottom: regionBottom, delta };
    output.blit(prevScreen, ...);
    output.shift(top, bottom, delta);
    // 只渲染边缘行
  } else {
    // 大范围滚动:全区域重渲染
    layoutShifted = true;
  }
}

渲染脏节点优化

// render-node-to-output.ts
const cached = nodeCache.get(node);

// 节点未变化且位置不变:直接复制
if (
  !node.dirty &&
  !skipSelfBlit &&
  cached &&
  cached.x === x &&
  cached.y === y &&
  cached.width === width &&
  cached.height === height &&
  prevScreen
) {
  output.blit(prevScreen, Math.floor(x), Math.floor(y), ...);
  return;  // 跳过子树渲染
}

// 节点变化或位置改变:重新渲染
if (cached && (node.dirty || positionChanged)) {
  output.clear({ ...cached }, ...);  // 清除旧位置
}

// 缓存新位置
nodeCache.set(node, { x, y, width, height, top: yogaTop });
node.dirty = false;

9.5 文本处理

文本换行

// wrap-text.ts
export default function wrapText(
  text: string,
  maxWidth: number,
  wrapType: Styles['textWrap'],
): string {
  if (wrapType === 'wrap') {
    return wrapAnsi(text, maxWidth, { trim: false, hard: true });
  }
  
  if (wrapType === 'wrap-trim') {
    return wrapAnsi(text, maxWidth, { trim: true, hard: true });
  }
  
  if (wrapType!.startsWith('truncate')) {
    // 截断处理
    let position: 'end' | 'middle' | 'start' = 'end';
    if (wrapType === 'truncate-middle') position = 'middle';
    if (wrapType === 'truncate-start') position = 'start';
    return truncate(text, maxWidth, position);
  }
  
  return text;  // 不处理
}

ANSI 文本处理

// wrapAnsi.ts
// 处理包含 ANSI 转义码的文本换行
export function wrapAnsi(
  text: string,
  width: number,
  options: { trim?: boolean; hard?: boolean }
): string {
  // 按行分割
  const lines = text.split('\n');
  
  // 对每行处理
  return lines.map(line => {
    // 检查是否需要换行
    if (stringWidth(line) <= width) return line;
    
    // ANSI-aware 换行
    return wrapLine(line, width, options);
  }).join('\n');
}

样式文本渲染

// colorize.ts
export function applyTextStyles(text: string, styles: TextStyles): string {
  let result = text;
  
  // 应用颜色
  if (styles.color) {
    result = applyColor(result, styles.color);
  }
  if (styles.backgroundColor) {
    result = applyBackground(result, styles.backgroundColor);
  }
  
  // 应用修饰
  if (styles.bold) result = `\x1b[1m${result}\x1b[22m`;
  if (styles.italic) result = `\x1b[3m${result}\x1b[23m`;
  if (styles.underline) result = `\x1b[4m${result}\x1b[24m`;
  if (styles.dimColor) result = `\x1b[2m${result}\x1b[22m`;
  
  return result;
}

9.6 事件系统

事件分发器

graph TD
    subgraph Input["输入源"]
        SI[Stdin]
        KB[Keyboard]
        MS[Mouse]
    end
    
    subgraph Events["事件类型"]
        IE[InputEvent]
        KE[KeyboardEvent]
        FE[FocusEvent]
        CE[ClickEvent]
    end
    
    subgraph Dispatcher["事件分发"]
        ED[EventDispatcher]
        CP[Capture Phase]
        BP[Bubble Phase]
    end
    
    subgraph Handlers["事件处理"]
        HC[事件处理器]
        CO[组件回调]
    end
    
    SI --> IE --> ED
    KB --> KE --> ED
    MS --> CE --> ED
    
    ED --> CP --> HC --> CO
    HC --> BP --> ED

KeyboardEvent

// events/keyboard-event.ts
export type Key = {
  upArrow?: boolean;
  downArrow?: boolean;
  leftArrow?: boolean;
  rightArrow?: boolean;
  return?: boolean;
  escape?: boolean;
  ctrl?: boolean;
  shift?: boolean;
  meta?: boolean;
  tab?: boolean;
  backspace?: boolean;
  delete?: boolean;
  pageUp?: boolean;
  pageDown?: boolean;
  home?: boolean;
  end?: boolean;
};

export type InputEvent = {
  input: string;    // 输入字符
  key: Key;         // 特殊键
  type: 'input';
};

use-input Hook

// hooks/use-input.ts
const useInput = (inputHandler: Handler, options: Options = {}) => {
  const { setRawMode, internal_exitOnCtrlC, internal_eventEmitter } = useStdin();
  
  // 启用 raw mode
  useLayoutEffect(() => {
    if (options.isActive === false) return;
    setRawMode(true);
    return () => setRawMode(false);
  }, [options.isActive, setRawMode]);
  
  // 注册事件监听
  useEffect(() => {
    internal_eventEmitter?.on('input', handleData);
    return () => {
      internal_eventEmitter?.removeListener('input', handleData);
    };
  }, [internal_eventEmitter, handleData]);
};

9.7 性能优化策略

增量渲染

graph TD
    A[检测变化] --> B{节点变化?}
    B --> |"是"| C[标记 dirty]
    B --> |"否"| D[跳过渲染]
    
    C --> E[向上传播 dirty]
    E --> F[重新计算布局]
    F --> G[渲染变化节点]
    
    D --> H[使用 prevScreen blit]
    H --> I[复制不变区域]
    
    G --> J[生成增量输出]
    I --> J
    J --> K[写入终端]

缓存策略

// node-cache.ts
const nodeCache = new WeakMap<DOMElement, CachedRect>();

type CachedRect = {
  x: number;
  y: number;
  width: number;
  height: number;
  top: number;  // Yoga computedTop
};

// 字符缓存(Output 内)
private charCache: Map<string, ClusteredChar[]> = new Map();

// 样式池(Session 级别)
const stylePool: StylePool = {
  intern(styles: AnsiCode[]): number {
    // 返回样式 ID,避免重复存储
  }
};

渲染时序优化

// reconciler.ts
resetAfterCommit(rootNode) {
  // 1. 计算布局(阻塞)
  rootNode.onComputeLayout();
  
  // 2. 触发渲染(异步)
  rootNode.onRender?.();
}

Tip

布局计算是阻塞操作,但渲染是异步触发。这确保 React 更新完成后再统一渲染,避免中间状态闪烁。


下一章将探讨 UI 组件设计,分析 Claude Code 的核心交互组件。

第10章:UI 组件

本章深入分析 Claude Code 的 UI 组件设计,涵盖核心组件、交互组件和布局组件的实现原理。

10.1 components/ 目录结构

整体架构

Claude Code 的 UI 组件库是一个大型 React/Ink 组件集合,位于 src/components/ 目录:

graph TD
    subgraph Core["核心层"]
        APP[App.tsx<br/>顶层 Context 包装]
        MSG[Message.tsx<br/>消息渲染入口]
        MSGS[Messages.tsx<br/>消息列表管理]
    end
    
    subgraph Input["输入组件"]
        TI[TextInput.tsx<br/>文本输入框]
        BTI[BaseTextInput.tsx<br/>输入基础组件]
        VI[VimTextInput.tsx<br/>Vim 模式输入]
        SB[SearchBox.tsx<br/>搜索框]
    end
    
    subgraph Output["输出组件"]
        SL[StatusLine.tsx<br/>状态栏]
        VML[VirtualMessageList.tsx<br/>虚拟消息列表]
        MD[Markdown.tsx<br/>Markdown 渲染]
        HC[HighlightedCode.tsx<br/>代码高亮]
    end
    
    subgraph Interaction["交互组件"]
        SP[Spinner.tsx<br/>加载动画]
        TP[TokenWarning.tsx<br/>Token 警告]
        FD[Feedback.tsx<br/>反馈收集]
        DLG[Dialog.tsx<br/>对话框系统]
    end
    
    subgraph Messages["消息渲染"]
        ATM[AssistantTextMessage]
        ATU[AssistantToolUseMessage]
        UTM[UserTextMessage]
        UTR[UserToolResultMessage]
        STM[SystemTextMessage]
    end
    
    APP --> MSGS
    MSGS --> VML
    VML --> MSG
    MSG --> Messages
    
    TI --> BTI
    VML --> SL
    
    Messages --> ATM
    Messages --> ATU
    Messages --> UTM
    Messages --> UTR
    Messages --> STM

文件组织

src/components/
├── App.tsx                    # 顶层 Context Provider 包装
├── Message.tsx                # 单条消息渲染(79KB)
├── Messages.tsx               # 消息列表管理(147KB)
├── VirtualMessageList.tsx     # 虚拟滚动列表(148KB)
├── StatusLine.tsx             # 状态栏(49KB)
├── TextInput.tsx              # 文本输入组件(21KB)
├── BaseTextInput.tsx          # 输入基础逻辑(19KB)
├── VimTextInput.tsx           # Vim 模式输入(16KB)
├── Spinner.tsx                # 加载动画(87KB)
├── Markdown.tsx               # Markdown 渲染(28KB)
├── HighlightedCode.tsx        # 语法高亮代码(17KB)
├── FileEditToolDiff.tsx       # 文件编辑差异展示(22KB)
├── StructuredDiff.tsx         # 结构化 Diff(25KB)
├── Feedback.tsx               # 用户反馈(87KB)
├── TokenWarning.tsx           # Token 警告提示(21KB)
├── FullscreenLayout.tsx       # 全屏布局(84KB)
├── Stats.tsx                  # 统计信息展示(152KB)
├── GlobalSearchDialog.tsx     # 全局搜索对话框(43KB)
├── ModelPicker.tsx            # 模型选择器(54KB)
├── ThemePicker.tsx            # 主题选择器(35KB)
├── messages/                  # 消息类型子组件
│   ├── AssistantTextMessage.tsx
│   ├── AssistantToolUseMessage.tsx
│   ├── AssistantThinkingMessage.tsx
│   ├── UserTextMessage.tsx
│   ├── UserToolResultMessage/
│   ├── SystemTextMessage.tsx
│   └── AttachmentMessage.tsx
├── design-system/             # 设计系统基础组件
│   ├── Dialog.tsx
│   ├── ListItem.tsx
│   ├── Tabs.tsx
│   ├── ThemedText.tsx
│   ├── ThemedBox.tsx
│   ├── ProgressBar.tsx
│   └── ThemeProvider.tsx
├── hooks/                     # 组件专用 Hooks
│   ├── HooksConfigMenu.tsx
│   ├── PromptDialog.tsx
│   └── SelectHookMode.tsx
├── shell/                     # Shell 输出组件
├── mcp/                       # MCP 相关组件
├── permissions/               # 权限相关组件
├── skills/                    # Skill 组件
├── tasks/                     # 任务状态组件
└── ui/                        # 通用 UI 元素

Note

组件目录规模庞大(150+ 文件),体现了 Claude Code 作为交互式 CLI 工具的复杂度。核心组件如 Message.tsxMessages.tsxVirtualMessageList.tsx 均超过 100KB,说明消息渲染是系统最复杂的部分。

10.2 核心组件解析

App.tsx - 顶层 Context 包装

App 组件是整个交互会话的顶层包装器,提供全局 Context:

// App.tsx
type Props = {
  getFpsMetrics: () => FpsMetrics | undefined;  // FPS 性能监控
  stats?: StatsStore;                            // 统计数据存储
  initialState: AppState;                        // 应用初始状态
  children: React.ReactNode;
};

export function App({ getFpsMetrics, stats, initialState, children }: Props) {
  return (
    <FpsMetricsProvider getFpsMetrics={getFpsMetrics}>
      <StatsProvider store={stats}>
        <AppStateProvider 
          initialState={initialState}
          onChangeAppState={onChangeAppState}
        >
          {children}
        </AppStateProvider>
      </StatsProvider>
    </FpsMetricsProvider>
  );
}

Tip

App 组件使用三层 Provider 包装:

  1. FpsMetricsProvider - 提供 FPS 性能数据
  2. StatsProvider - 提供统计信息(Token、Cost 等)
  3. AppStateProvider - 提供全局应用状态 这种分层设计允许子组件按需订阅不同层级的数据。

Message.tsx - 消息渲染分发

Message 组件是单条消息的渲染入口,根据消息类型分发到对应子组件:

// Message.tsx
export type Props = {
  message: NormalizedUserMessage | AssistantMessage | AttachmentMessage 
           | SystemMessage | GroupedToolUseMessage | CollapsedReadSearchGroup;
  lookups: ReturnType<typeof buildMessageLookups>;
  containerWidth?: number;
  tools: Tools;
  commands: Command[];
  verbose: boolean;
  inProgressToolUseIDs: Set<string>;
  progressMessagesForMessage: ProgressMessage[];
  shouldAnimate: boolean;
  isTranscriptMode: boolean;
};

function MessageImpl({ message, ...props }: Props) {
  switch (message.type) {
    case "attachment":
      return <AttachmentMessage 
        attachment={message.attachment} 
        verbose={verbose} 
      />;
    
    case "assistant":
      return (
        <Box flexDirection="column" width={containerWidth ?? "100%"}>
          {message.message.content.map((block, index) => 
            <AssistantMessageBlock 
              key={index}
              param={block}
              tools={tools}
              inProgressToolUseIDs={inProgressToolUseIDs}
              shouldAnimate={shouldAnimate}
              ...
            />
          )}
        </Box>
      );
    
    case "user":
      // 用户消息渲染...
    
    case "system":
      return <SystemTextMessage ... />;
    
    default:
      return null;
  }
}

消息类型映射

graph LR
    subgraph Input["消息类型"]
        UM[UserMessage]
        AM[AssistantMessage]
        SM[SystemMessage]
        AT[AttachmentMessage]
        GT[GroupedToolUseMessage]
    end
    
    subgraph Render["渲染组件"]
        UTM[UserTextMessage]
        UTR[UserToolResultMessage]
        ATM[AssistantTextMessage]
        ATU[AssistantToolUseMessage]
        ATH[AssistantThinkingMessage]
        STM[SystemTextMessage]
        ATT[AttachmentMessage]
        GTC[GroupedToolUseContent]
    end
    
    UM --> UTM
    UM --> UTR
    AM --> ATM
    AM --> ATU
    AM --> ATH
    SM --> STM
    AT --> ATT
    GT --> GTC

AssistantMessageBlock 组件

对于 Assistant 消息,根据 content block 类型分发:

// Message.tsx 内部函数
function AssistantMessageBlock({ param, tools, ...props }) {
  switch (param.type) {
    case 'text':
      return <AssistantTextMessage 
        text={param.text} 
        shouldAnimate={shouldAnimate}
        width={width}
      />;
    
    case 'thinking':
      return <AssistantThinkingMessage 
        thinking={param.thinking}
        isTranscriptMode={isTranscriptMode}
      />;
    
    case 'tool_use':
      const tool = tools[param.name];
      return <AssistantToolUseMessage 
        toolUse={param}
        tool={tool}
        inProgress={inProgressToolUseIDs.has(param.id)}
        shouldAnimate={shouldAnimate}
      />;
    
    case 'redacted_thinking':
      return <AssistantRedactedThinkingMessage />;
    
    default:
      return null;
  }
}

10.3 输入组件

TextInput.tsx - 文本输入框

TextInput 是用户输入的主要入口,支持多种模式:

// TextInput.tsx
export type Props = BaseTextInputProps & {
  highlights?: TextHighlight[];  // 文本高亮区域
};

export default function TextInput(props: Props): React.ReactNode {
  const [theme] = useTheme();
  const isTerminalFocused = useTerminalFocus();
  const settings = useSettings();
  const reducedMotion = settings.prefersReducedMotion ?? false;
  
  // Voice 模式状态
  const voiceState = feature('VOICE_MODE') 
    ? useVoiceState(s => s.voiceState) 
    : 'idle' as const;
  const isVoiceRecording = voiceState === 'recording';
  const audioLevels = feature('VOICE_MODE') 
    ? useVoiceState(s => s.voiceAudioLevels) 
    : [];
  
  // 动画帧引用(语音波形)
  const smoothedRef = useRef<number[]>(new Array(CURSOR_WAVEFORM_WIDTH).fill(0));
  const needsAnimation = isVoiceRecording && !reducedMotion;
  const [animRef, animTime] = feature('VOICE_MODE') 
    ? useAnimationFrame(needsAnimation ? 50 : null) 
    : [() => {}, 0];
  
  // 剪贴板图片提示
  useClipboardImageHint(isTerminalFocused, !!props.onImagePaste);
  
  // 光标渲染:语音波形 vs 标准 inverse
  const canShowCursor = isTerminalFocused && !accessibilityEnabled;
  let invert: (text: string) => string;
  
  if (!canShowCursor) {
    invert = (text) => text;
  } else if (isVoiceRecording && !reducedMotion) {
    // 语音波形光标
    const smoothed = smoothedRef.current;
    const raw = audioLevels.length > 0 ? audioLevels[audioLevels.length - 1] : 0;
    const target = Math.min(raw * LEVEL_BOOST, 1);
    smoothed[0] = (smoothed[0] ?? 0) * SMOOTH + target * (1 - SMOOTH);
    const barIndex = Math.max(1, Math.min(
      Math.round(displayLevel * (BARS.length - 1)), 
      BARS.length - 1
    ));
    const hue = (animTime / 1000) * 90 % 360;
    const { r, g, b } = isSilent ? { r: 128, g: 128, b: 128 } : hueToRgb(hue);
    invert = () => chalk.rgb(r, g, b)(BARS[barIndex]);
  } else {
    invert = chalk.inverse;
  }
  
  // 文本输入状态管理
  const textInputState = useTextInput({
    value: props.value,
    onChange: props.onChange,
    onSubmit: props.onSubmit,
    onExit: props.onExit,
    onHistoryUp: props.onHistoryUp,
    onHistoryDown: props.onHistoryDown,
    multiline: props.multiline,
    cursorChar: props.showCursor ? ' ' : '',
    invert,
    themeText: color('text', theme),
    columns: props.columns,
    maxVisibleLines: props.maxVisibleLines,
    onImagePaste: props.onImagePaste,
    inlineGhostText: props.inlineGhostText,
  });
  
  return (
    <Box ref={animRef}>
      <BaseTextInput 
        inputState={textInputState}
        terminalFocus={isTerminalFocused}
        highlights={props.highlights}
        invert={invert}
        hidePlaceholderText={isVoiceRecording}
        {...props}
      />
    </Box>
  );
}

Tip

TextInput 支持语音模式下的动态波形光标:使用 useAnimationFrame 50ms 周期刷新,根据 audioLevels 计算波形高度,通过 EMA(指数移动平均)平滑过渡,避免抖动。

BaseTextInput.tsx - 输入基础组件

BaseTextInput 处理文本输入的核心逻辑:

// BaseTextInput.tsx
export function BaseTextInput({ inputState, terminalFocus, invert, ...props }) {
  const { onInput, renderedValue, cursorLine, cursorColumn } = inputState;
  
  // 光标位置声明
  const cursorRef = useDeclaredCursor({
    line: cursorLine,
    column: cursorColumn,
    active: Boolean(props.focus && props.showCursor && terminalFocus)
  });
  
  // 粘贴处理
  const { wrappedOnInput, isPasting } = usePasteHandler({
    onPaste: props.onPaste,
    onInput: (input, key) => {
      if (isPasting && key.return) return;  // 粘贴时忽略 Enter
      onInput(input, key);
    },
    onImagePaste: props.onImagePaste
  });
  
  // Placeholder 渲染
  const { showPlaceholder, renderedPlaceholder } = renderPlaceholder({
    placeholder: props.placeholder,
    value: props.value,
    showCursor: props.showCursor,
    focus: props.focus,
    terminalFocus,
    invert
  });
  
  // 注册输入监听
  useInput(wrappedOnInput, { isActive: props.focus });
  
  // 命令参数提示
  const commandWithoutArg = props.value?.trim().indexOf(" ") === -1;
  const showArgumentHint = Boolean(
    props.argumentHint && 
    commandWithoutArg && 
    props.value?.startsWith("/")
  );
  
  // 高亮过滤(排除光标位置)
  const cursorFiltered = props.highlights?.filter(h => 
    h.dimColor || 
    props.cursorOffset < h.start || 
    props.cursorOffset >= h.end
  );
  
  return (
    <Box flexDirection="column">
      {showPlaceholder && renderedPlaceholder}
      <HighlightedInput 
        value={renderedValue}
        highlights={filteredHighlights}
        invert={invert}
        ...
      />
      {showArgumentHint && <ArgumentHint />}
      {children}
    </Box>
  );
}

10.4 输出组件

StatusLine.tsx - 状态栏

StatusLine 显示会话状态信息(模型、Token、Cost 等):

// StatusLine.tsx
type Props = {
  messagesRef: React.RefObject<Message[]>;
  lastAssistantMessageId: string | null;
  vimMode?: VimMode;
};

function StatusLineInner({ messagesRef, lastAssistantMessageId, vimMode }: Props) {
  const settings = useSettings();
  const permissionMode = useAppState(s => s.toolPermissionContext.mode);
  const statusLineText = useAppState(s => s.statusLineText);
  
  // 构建状态栏命令输入
  const input = buildStatusLineCommandInput(
    permissionMode,
    exceeds200kTokens,
    settings,
    messages,
    addedDirs,
    mainLoopModel,
    vimMode
  );
  
  return (
    <Box flexDirection="row">
      <Text>{statusLineText || renderDefaultStatusLine(input)}</Text>
    </Box>
  );
}

// 状态栏数据结构
type StatusLineCommandInput = {
  session_name?: string;
  model: { id: string; display_name: string };
  workspace: {
    current_dir: string;
    project_dir: string;
    added_dirs: string[];
  };
  cost: {
    total_cost_usd: number;
    total_duration_ms: number;
    total_lines_added: number;
    total_lines_removed: number;
  };
  context_window: {
    total_input_tokens: number;
    total_output_tokens: number;
    context_window_size: number;
    used_percentage: number;
    remaining_percentage: number;
  };
  rate_limits?: {
    five_hour?: { used_percentage: number; resets_at: string };
    seven_day?: { used_percentage: number; resets_at: string };
  };
  worktree?: {
    name: string;
    path: string;
    branch: string;
  };
};

VirtualMessageList.tsx - 虚拟滚动列表

VirtualMessageList 实现高效的虚拟滚动,处理大量消息:

// VirtualMessageList.tsx
const HEADROOM = 3;  // scrollTo 时保留的顶部缓冲行数

export type StickyPrompt = {
  text: string;
  scrollTo: () => void;
} | 'clicked';

export type JumpHandle = {
  jumpToIndex: (i: number) => void;
  setSearchQuery: (q: string) => void;
  nextMatch: () => void;
  prevMatch: () => void;
  setAnchor: () => void;  // 搜索锚点
  warmSearchIndex: () => Promise<number>;  // 预热搜索缓存
  disarmSearch: () => void;  // 手动滚动退出搜索
};

type Props = {
  messages: RenderableMessage[];
  scrollRef: RefObject<ScrollBoxHandle>;
  columns: number;  // 宽度变化时失效缓存
  itemKey: (msg: RenderableMessage) => string;
  renderItem: (msg: RenderableMessage, index: number) => React.ReactNode;
  onItemClick?: (msg: RenderableMessage) => void;
  isItemClickable?: (msg: RenderableMessage) => boolean;
  isItemExpanded?: (msg: RenderableMessage) => boolean;
  extractSearchText?: (msg: RenderableMessage) => string;
  trackStickyPrompt?: boolean;
  jumpRef?: RefObject<JumpHandle>;
  onSearchMatchesChange?: (count: number, current: number) => void;
  scanElement?: (el: DOMElement) => MatchPosition[];
  setPositions?: (state: PositionsState | null) => void;
};

function stickyPromptText(msg: RenderableMessage): string | null {
  // WeakMap 缓存,避免重复计算
  const cached = promptTextCache.get(msg);
  if (cached !== undefined) return cached;
  
  const result = computeStickyPromptText(msg);
  promptTextCache.set(msg, result);
  return result;
}

function computeStickyPromptText(msg: RenderableMessage): string | null {
  if (msg.type === 'user') {
    if (msg.isMeta || msg.isVisibleInTranscriptOnly) return null;
    const block = msg.message.content[0];
    if (block?.type !== 'text') return null;
    // 去除系统提醒前缀
    const text = stripSystemReminders(block.text);
    if (text.startsWith('<')) return null;  // XML 包装内容不显示
    return text.slice(0, STICKY_TEXT_CAP);  // 截断超长文本
  }
  return null;
}

虚拟滚动注意事项

  • 高度缓存依赖 columns 参数,窗口宽度变化时需要失效
  • itemKey 函数用于 React 列表 diff,必须稳定唯一
  • 搜索文本使用 WeakMap 缓存,消息对象作为 key,自动 GC
  • 粘性提示文本有 500 字符上限,避免超大粘贴内容撑爆内存

Markdown.tsx - Markdown 渲染

Markdown 组件处理富文本渲染:

// Markdown.tsx(简化)
export function Markdown({ children, width, theme }: Props) {
  const parsed = parseMarkdown(children);
  
  return (
    <Box flexDirection="column" width={width}>
      {parsed.blocks.map((block, i) => {
        switch (block.type) {
          case 'paragraph':
            return <Text key={i}>{renderInline(block.content)}</Text>;
          
          case 'code':
            return <HighlightedCode 
              key={i}
              code={block.code}
              language={block.lang}
              theme={theme}
            />;
          
          case 'heading':
            return <Text key={i} bold>{block.content}</Text>;
          
          case 'list':
            return <ListItems items={block.items} />;
          
          case 'blockquote':
            return <Box borderStyle="round">
              <Text dimColor>{block.content}</Text>
            </Box>;
          
          case 'table':
            return <MarkdownTable data={block.data} />;
          
          default:
            return null;
        }
      })}
    </Box>
  );
}

10.5 交互组件

Spinner.tsx - 加载动画

Spinner 组件提供多种加载动画风格:

// Spinner.tsx
const FRAMES = {
  dots: ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'],
  line: ['─', '━', '┃', '┏', '┓', '┫', '┣', '┳', '┻', '╋'],
  pipe: ['┏', '┓', '┛', '┗', '┏', '┓', '┛', '┗'],
  moon: ['🌑', '🌒', '🌓', '🌔', '🌕', '🌖', '🌗', '🌘'],
  circle: ['◜', '◠', '◝', '◞', '◟', '◡', '◟'],
  arrow: ['←', '↖', '↑', '↗', '→', '↘', '↓', '↙'],
};

export function Spinner({ type = 'dots', color }: Props) {
  const [frameIndex, setFrameIndex] = useState(0);
  const frames = FRAMES[type];
  const interval = type === 'moon' ? 80 : 80;  // 月相稍慢
  
  useEffect(() => {
    if (reducedMotion) return;
    const timer = setInterval(() => {
      setFrameIndex(i => (i + 1) % frames.length);
    }, interval);
    return () => clearInterval(timer);
  }, [frames, interval, reducedMotion]);
  
  return (
    <Text color={color}>
      {reducedMotion ? '...' : frames[frameIndex]}
    </Text>
  );
}

交互流程图

sequenceDiagram
    participant User as 用户
    participant TI as TextInput
    participant BTI as BaseTextInput
    participant App as App Context
    participant API as API Layer
    participant VML as VirtualMessageList
    participant MSG as Message
    
    User->>TI: 输入文本
    TI->>BTI: useTextInput()
    BTI->>BTI: useInput() 监听
    BTI->>App: onSubmit()
    App->>API: 发送请求
    API-->>App: 流式响应
    
    loop 每个响应块
        App->>VML: 添加消息
        VML->>MSG: 渲染消息
        MSG->>MSG: 类型分发
    end
    
    VML-->>User: 更新显示
    User->>VML: 滚动/点击
    VML->>VML: 虚拟滚动优化

10.6 设计系统

design-system/ 组件

设计系统提供统一风格的 UI 基础组件:

// design-system/ThemedText.tsx
export function ThemedText({ color: colorKey, children, ...props }: Props) {
  const [theme] = useTheme();
  const textColor = color(colorKey, theme);
  
  return <Text color={textColor} {...props}>{children}</Text>;
}

// design-system/Dialog.tsx
export function Dialog({ title, children, onClose }: Props) {
  const [theme] = useTheme();
  
  return (
    <Box 
      flexDirection="column"
      borderStyle="round"
      borderColor={color('border', theme)}
    >
      <Box borderBottom>
        <ThemedText color="accent">{title}</ThemedText>
      </Box>
      <Box flexDirection="column">
        {children}
      </Box>
      <Box borderTop>
        <KeyboardShortcutHint keys={['Esc']} action="关闭" />
      </Box>
    </Box>
  );
}

// design-system/ListItem.tsx
export function ListItem({ selected, children, onSelect }: Props) {
  const [theme] = useTheme();
  const isHovered = useHover();
  
  return (
    <Box 
      backgroundColor={selected ? color('selection', theme) : undefined}
      onClick={onSelect}
    >
      <Text>
        {selected ? '❯' : ' '}
        {children}
      </Text>
    </Box>
  );
}

组件层级图

graph TD
    subgraph Providers["Context Providers"]
        TP[ThemeProvider]
        FP[FpsMetricsProvider]
        SP[StatsProvider]
        AP[AppStateProvider]
    end
    
    subgraph Layouts["布局组件"]
        FL[FullscreenLayout]
        SB[ScrollBox]
        PAN[Pane]
    end
    
    subgraph Primitives["基础组件"]
        TT[ThemedText]
        TB[ThemedBox]
        DLG[Dialog]
        LI[ListItem]
        TB[Tabs]
        PB[ProgressBar]
    end
    
    subgraph Composite["复合组件"]
        TI[TextInput]
        VML[VirtualMessageList]
        MSG[Message]
        SL[StatusLine]
    end
    
    TP --> TT
    TP --> TB
    TP --> DLG
    
    FL --> SB
    SB --> VML
    VML --> MSG
    
    TI --> TT
    MSG --> TT
    SL --> TT
    
    AP --> VML
    SP --> SL

10.7 状态管理与交互设计

AppState 状态结构

// state/AppState.ts
export type AppState = {
  // 工具权限上下文
  toolPermissionContext: {
    mode: PermissionMode;
    additionalWorkingDirectories: string[];
  };
  
  // 状态栏文本
  statusLineText: string | null;
  
  // 光标导航状态
  cursorNavState: MessageActionsState | null;
  
  // 会话状态
  sessionId: string;
  isPaused: boolean;
  
  // UI 状态
  vimMode: VimMode | null;
  pendingExit: boolean;
};

// 状态变更回调
export const onChangeAppState = (state: AppState) => {
  // 同步到持久化存储
  // 触发副作用
};

交互模式

graph TD
    subgraph InputModes["输入模式"]
        NM[Normal 模式<br/>默认输入]
        VM[Vim 模式<br/>Vi 键绑定]
        MM[Multiline 模式<br/>多行编辑]
        VMODE[Voice 模式<br/>语音输入]
    end
    
    subgraph Navigation["导航操作"]
        JK[j/k 滚动]
        GD[G/D 翻页]
        NK[n/N 搜索跳转]
        MMODE[m/M 标记跳转]
    end
    
    subgraph Commands["命令操作"]
        SC[/斜杠命令]
        ENTER[Enter 提交]
        ESC[Esc 退出]
        CTRL[Ctrl+C 中断]
    end
    
    NM --> VM
    VM --> JK
    VM --> GD
    VM --> NK
    NM --> MM
    NM --> VMODE
    
    NM --> SC
    NM --> ENTER
    NM --> ESC
    NM --> CTRL

Tip

交互设计遵循渐进增强原则:

  1. 基础模式:Normal 输入 + 标准键绑定
  2. 高级模式:Vim 键绑定 + 搜索导航
  3. 辅助模式:Multiline 编辑 + Voice 语音 每种模式独立开关,用户可按偏好组合。

10.8 性能优化策略

虚拟滚动优化

// VirtualMessageList.tsx 内部
const heightCache = new Map<string, number>();

function getItemHeight(msg: RenderableMessage, index: number): number {
  const key = itemKey(msg);
  const cached = heightCache.get(key);
  if (cached !== undefined && columnsValid) return cached;
  
  // 计算实际高度
  const height = computeMessageHeight(msg, width);
  heightCache.set(key, height);
  return height;
}

// 粘性提示追踪
function StickyTracker({ scrollTop, messages }: Props) {
  // 找到最后一个可见的用户提示
  const lastVisiblePrompt = findLastVisiblePrompt(scrollTop, messages);
  
  // 更新粘性状态
  updateStickyPrompt({
    text: stickyPromptText(lastVisiblePrompt),
    scrollTo: () => scrollToMessage(lastVisiblePrompt)
  });
}

渲染缓存

// 消息搜索文本缓存
const fallbackLowerCache = new WeakMap<RenderableMessage, string>();

function defaultExtractSearchText(msg: RenderableMessage): string {
  const cached = fallbackLowerCache.get(msg);
  if (cached !== undefined) return cached;
  
  const lowered = renderableSearchText(msg).toLowerCase();
  fallbackLowerCache.set(msg, lowered);
  return lowered;
}

// 光标位置缓存
const promptTextCache = new WeakMap<RenderableMessage, string | null>();

function stickyPromptText(msg: RenderableMessage): string | null {
  const cached = promptTextCache.get(msg);
  if (cached !== undefined) return cached;
  
  const result = computeStickyPromptText(msg);
  promptTextCache.set(msg, result);
  return result;
}

Note

缓存策略使用 WeakMap,以消息对象作为 key:

  • 消息对象生命周期与组件树绑定
  • 消息被移除时自动 GC,无需手动清理
  • 避免内存泄漏,适合动态列表场景

下一章将探讨 服务层架构,分析 MCP、API、OAuth 等服务的实现原理。

第11章:Bridge 桥接层

本章介绍 Claude Code 的 Bridge 桥接层实现,包括远程控制(Remote Control)功能的架构设计和通信机制。

11.1 Bridge 目录结构

Bridge 模块位于 src/bridge/ 目录,包含约 30 个 TypeScript 文件:

src/bridge/
├── types.ts              # 类型定义和常量
├── bridgeApi.ts          # Bridge API 客户端
├── bridgeConfig.ts       # Bridge 配置解析
├── bridgeMain.ts         # Bridge 主入口
├── bridgeMessaging.ts    # 消息处理
├── createSession.ts      # Session 创建
├── sessionRunner.ts      # Session 运行器
├── replBridge.ts         # REPL Bridge 实现
├── replBridgeTransport.ts # 传输层抽象
├── jwtUtils.ts           # JWT 工具和 Token 刷新
├── bridgeEnabled.ts      # Bridge 启用检测
├── bridgeDebug.ts        # 调试工具
├── bridgeStatusUtil.ts   # 状态管理
├── bridgeUI.ts           # UI 层集成
├── initReplBridge.ts     # Bridge 初始化
├── inboundMessages.ts    # 入站消息处理
├── inboundAttachments.ts # 入站附件处理
├── flushGate.ts          # 输出缓冲控制
├── capacityWake.ts       # 能力唤醒
├── pollConfig.ts         # 配置轮询
├── sessionIdCompat.ts    # Session ID 兼容性
├── trustedDevice.ts      # 可信设备管理
├── workSecret.ts         # 工作密钥解析
└── ...

Note

Bridge 模块是 Claude Code 实现远程控制功能的核心,允许用户通过 claude.ai 网站远程控制本地运行的 Claude Code CLI。

11.2 核心类型定义

11.2.1 Session 相关类型

// Session 超时常量(24小时)
export const DEFAULT_SESSION_TIMEOUT_MS = 24 * 60 * 60 * 1000

// Session 完成状态
export type SessionDoneStatus = 'completed' | 'failed' | 'interrupted'

// Session 活动类型
export type SessionActivityType = 'tool_start' | 'text' | 'result' | 'error'

// Session 活动记录
export type SessionActivity = {
  type: SessionActivityType
  summary: string    // 如 "Editing src/foo.ts"
  timestamp: number
}

11.2.2 工作密钥类型

// 工作密钥(Session 启动参数)
export type WorkSecret = {
  version: number
  session_ingress_token: string   // Session 入口 Token
  api_base_url: string            // API 基础 URL
  sources: Array<{
    type: string
    git_info?: { type: string; repo: string; ref?: string; token?: string }
  }>
  auth: Array<{ type: string; token: string }>
  claude_code_args?: Record<string, string> | null
  mcp_config?: unknown | null
  environment_variables?: Record<string, string> | null
  use_code_sessions?: boolean     // CCR v2 兼容标识
}

11.2.3 启动模式

// Bridge Session 工作目录选择模式
export type SpawnMode =
  | 'single-session'  // 单 Session,Session 结束后 Bridge 退出
  | 'worktree'        // 持久服务,每个 Session 独立 git worktree
  | 'same-dir'        // 持久服务,所有 Session 共享 cwd

11.3 Session 创建流程

Session 创建通过 createBridgeSession 函数实现,用于 claude remote-control 命令和 /remote-control 斜杠命令:

sequenceDiagram
    participant User as 用户
    participant Bridge as Bridge
    participant OAuth as OAuth 服务
    participant API as claude.ai API

    User->>Bridge: 发起 remote-control
    Bridge->>OAuth: 获取 Access Token
    OAuth-->>Bridge: accessToken
    Bridge->>OAuth: 获取 Organization UUID
    OAuth-->>Bridge: orgUUID
    Bridge->>API: POST /v1/sessions
    API-->>Bridge: sessionId
    Bridge->>Bridge: 启动 sessionRunner
    Bridge-->>User: 返回 sessionId

11.3.1 createBridgeSession 函数

export async function createBridgeSession({
  environmentId,
  title,
  events,
  gitRepoUrl,
  branch,
  signal,
  baseUrl,
  getAccessToken,
  permissionMode,
}): Promise<string | null> {
  // 1. 获取 OAuth Token
  const accessToken = getAccessToken?.() ?? getClaudeAIOAuthTokens()?.accessToken
  if (!accessToken) return null

  // 2. 获取 Organization UUID
  const orgUUID = await getOrganizationUUID()
  if (!orgUUID) return null

  // 3. 构建 Git 源信息
  let gitSource = null
  let gitOutcome = null
  if (gitRepoUrl) {
    const parsed = parseGitRemote(gitRepoUrl)
    if (parsed) {
      gitSource = {
        type: 'git_repository',
        url: `https://${parsed.host}/${parsed.owner}/${parsed.name}`,
        revision: branch,
      }
      gitOutcome = {
        type: 'git_repository',
        git_info: {
          type: 'github',
          repo: `${parsed.owner}/${parsed.name}`,
          branches: [`claude/${branch || 'task'}`],
        },
      }
    }
  }

  // 4. 发送创建请求
  const response = await axios.post(`${baseUrl}/v1/sessions`, {
    environment_id: environmentId,
    title,
    events,
    source: gitSource,
    expected_outcome: gitOutcome,
    permission_mode: permissionMode,
  })

  return response.data.id
}

11.4 JWT 认证与 Token 刷新

11.4.1 JWT 解码

jwtUtils.ts 提供了 JWT Token 解码功能,无需验证签名:

// 解码 JWT payload(不验证签名)
export function decodeJwtPayload(token: string): unknown | null {
  // 去除 sk-ant-si- 前缀
  const jwt = token.startsWith('sk-ant-si-')
    ? token.slice('sk-ant-si-'.length)
    : token

  const parts = jwt.split('.')
  if (parts.length !== 3 || !parts[1]) return null

  return jsonParse(Buffer.from(parts[1], 'base64url').toString('utf8'))
}

// 解码 JWT expiry
export function decodeJwtExpiry(token: string): number | null {
  const payload = decodeJwtPayload(token)
  if (payload && typeof payload === 'object' && 'exp' in payload) {
    return payload.exp
  }
  return null
}

11.4.2 Token 刷新调度器

flowchart TD
    A[schedule] --> B[计算刷新时间]
    B --> C[设置定时器]
    C --> D{Token 即将过期?}
    D -->|Yes| E[调用 onRefresh]
    E --> F[获取新 Token]
    F --> G{成功?}
    G -->|Yes| H[重新 schedule]
    G -->|No| I[失败计数 +1]
    I --> J{超过 MAX_FAILURES?}
    J -->|Yes| K[停止刷新]
    J -->|No| L[延迟重试]
// 刷新缓冲时间:提前 5 分钟刷新
const TOKEN_REFRESH_BUFFER_MS = 5 * 60 * 1000

// 备用刷新间隔:30 分钟
const FALLBACK_REFRESH_INTERVAL_MS = 30 * 60 * 1000

// 最大连续失败次数
const MAX_REFRESH_FAILURES = 3

export function createTokenRefreshScheduler({
  getAccessToken,
  onRefresh,
  label,
  refreshBufferMs,
}): {
  schedule: (sessionId: string, token: string) => void
  scheduleFromExpiresIn: (sessionId: string, expiresInSeconds: number) => void
  cancel: (sessionId: string) => void
  cancelAll: () => void
} {
  const timers = new Map<string, ReturnType<typeof setTimeout>>()
  const failureCounts = new Map<string, number>()
  const generations = new Map<string, number>()

  // 调度刷新
  function schedule(sessionId: string, token: string) {
    const expiry = decodeJwtExpiry(token)
    if (!expiry) {
      // 无法解析过期时间,使用备用间隔
      scheduleFromExpiresIn(sessionId, FALLBACK_REFRESH_INTERVAL_MS / 1000)
      return
    }

    const nowMs = Date.now()
    const expiryMs = expiry * 1000
    const delayMs = expiryMs - nowMs - refreshBufferMs

    if (delayMs <= 0) {
      // 已过期或即将过期,立即刷新
      doRefresh(sessionId)
    } else {
      setTimer(sessionId, delayMs)
    }
  }

  return { schedule, scheduleFromExpiresIn, cancel, cancelAll }
}

11.5 传输层架构

Bridge 支持两种传输协议版本:

11.5.1 V1 协议(WebSocket)

graph LR
    A[claude.ai] --> B[WebSocket]
    B --> C[replBridgeTransport]
    C --> D[Session Runner]
    D --> E[Claude Code REPL]

11.5.2 V2 协议(CCR)

graph LR
    A[claude.ai] --> B[CCR API]
    B --> C[codeSessionApi]
    C --> D[Session Runner]
    D --> E[Claude Code REPL]

Tip

V2 协议通过 CCR (Claude Code Runtime) 兼容层支持 Code Sessions,提供更好的隔离性和扩展性。

11.6 消息协议

11.6.1 入站消息类型

inboundMessages.ts 处理来自远程客户端的消息:

// 消息类型定义
type InboundMessage =
  | { type: 'text'; content: string }
  | { type: 'attachment'; data: Attachment }
  | { type: 'permission_response'; decision: 'allow' | 'deny' }
  | { type: 'interrupt' }
  | { type: 'ping' }

11.6.2 消息处理流程

sequenceDiagram
    participant Client as 远程客户端
    participant Transport as 传输层
    participant Messaging as bridgeMessaging
    participant Runner as sessionRunner
    participant REPL as Claude Code

    Client->>Transport: 发送消息
    Transport->>Messaging: 解析消息
    Messaging->>Messaging: 验证权限
    Messaging->>Runner: 分发消息
    Runner->>REPL: 注入到对话
    REPL-->>Runner: 处理结果
    Runner-->>Messaging: 输出事件
    Messaging-->>Transport: 编码响应
    Transport-->>Client: 发送响应

11.7 认证配置参数

// Bridge 配置
export type BridgeConfig = {
  dir: string              // 工作目录
  machineName: string      // 机器名称
  branch: string           // Git 分支
  gitRepoUrl: string | null
  maxSessions: number      // 最大 Session 数
  spawnMode: SpawnMode     // 启动模式
  verbose: boolean         // 详细日志
  sandbox: boolean         // 沙箱模式
  bridgeId: string         // Bridge 实例 UUID
  workerType: string       // 工作类型
  environmentId: string    // 环境注册 ID
}

本章小结

Bridge 模块是 Claude Code 远程控制功能的核心实现,主要特点:

  1. 双协议支持:V1 WebSocket 和 V2 CCR 兼容
  2. JWT Token 管理:自动刷新机制确保长连接稳定
  3. 多种启动模式:single-session、worktree、same-dir
  4. 完整消息协议:支持文本、附件、权限响应等多种消息类型

下一章将介绍 Services 服务层的详细设计。

第12章:Services 服务层

本章介绍 Claude Code 的 Services 服务层设计,包括 API 客户端、MCP 协议实现、OAuth 认证等核心服务。

12.1 Services 目录结构

服务层位于 src/services/ 目录,包含多个子模块:

src/services/
├── api/                  # API 客户端
│   ├── client.ts         # Anthropic API 客户端
│   ├── claude.ts         # Claude API 封装
│   ├── errors.ts         # 错误处理
│   ├── errorUtils.ts     # 错误工具
│   ├── withRetry.ts      # 重试机制
│   ├── usage.ts          # 使用量追踪
│   └── filesApi.ts       # Files API
│
├── mcp/                  # MCP 协议实现
│   ├── client.ts         # MCP 客户端
│   ├── config.ts         # MCP 配置解析
│   ├── auth.ts           # MCP OAuth
│   ├── types.ts          # MCP 类型定义
│   ├── utils.ts          # MCP 工具函数
│   └ normalization.ts  # 数据规范化
│
├── oauth/                # OAuth 认证
│   ├── client.ts         # OAuth 客户端
│   └ auth-code-listener.ts # Auth Code 监听
│
├── analytics/            # 分析服务
│   ├── index.ts          # 分析入口
│   ├── growthbook.ts     # GrowthBook 集成
│   ├── sink.ts           # 数据收集
│   └ datadog.ts        # DataDog 集成
│
├── compact/              # 上下文压缩
│   ├── compact.ts        # 压缩实现
│   ├── autoCompact.ts    # 自动压缩
│   └ microCompact.ts   # 微压缩
│
├── lsp/                  # LSP 服务
│   ├── LSPClient.ts      # LSP 客户端
│   ├── LSPServerManager.ts # LSP 服务器管理
│   └ config.ts           # LSP 配置
│
├── voice/                # 语音服务
│   ├── voice.ts          # 语音处理
│   ├── voiceStreamSTT.ts # 语音转文字
│
└── SessionMemory/        # 会话记忆
    ├── sessionMemory.ts  # 记忆实现
    └ prompts.ts        # 记忆提示词

12.2 API 客户端配置

12.2.1 多云服务商支持

Claude Code 支持多种 API 服务商:

graph TD
    A[API Client] --> B{Provider Detection}
    B --> C[Anthropic Direct]
    B --> D[AWS Bedrock]
    B --> D2[Azure Foundry]
    B --> E[Google Vertex AI]

    C --> F[ANTHROPIC_API_KEY]
    D --> G[AWS Credentials]
    D2 --> H[ANTHROPIC_FOUNDRY_API_KEY]
    E --> I[GCP Credentials]

Note

Claude Code 通过环境变量自动检测 API 服务商,无需手动配置切换。

12.2.2 环境变量配置

/**
 * 各服务商的环境变量配置:
 *
 * Direct API:
 * - ANTHROPIC_API_KEY: 直接 API 访问密钥
 *
 * AWS Bedrock:
 * - AWS credentials: 通过 aws-sdk 默认配置
 * - AWS_REGION / AWS_DEFAULT_REGION: 区域设置(默认 us-east-1)
 *
 * Azure Foundry:
 * - ANTHROPIC_FOUNDRY_RESOURCE: Azure 资源名称
 * - ANTHROPIC_FOUNDRY_API_KEY: Microsoft Foundry API Key
 * - 或使用 Azure AD DefaultAzureCredential
 *
 * Vertex AI:
 * - ANTHROPIC_VERTEX_PROJECT_ID: GCP 项目 ID
 * - CLOUD_ML_REGION: GCP 区域
 * - 标准 GCP credentials 配置
 */

12.2.3 客户端创建逻辑

function createAnthropicClient(): Anthropic {
  const provider = getAPIProvider()

  switch (provider) {
    case 'anthropic':
      return new Anthropic({
        apiKey: getAnthropicApiKey(),
        baseURL: process.env.ANTHROPIC_BASE_URL,
      })

    case 'bedrock':
      return createBedrockClient()

    case 'foundry':
      return createFoundryClient()

    case 'vertex':
      return createVertexClient()
  }
}

12.3 错误处理机制

12.3.1 错误类型定义

Claude Code 使用 Anthropic SDK 提供的错误类型:

// 从 SDK 导入的错误类型
import {
  APIConnectionError,       // 连接错误
  APIConnectionTimeoutError, // 连接超时
  APIError,                  // 通用 API 错误
  APIUserAbortError,         // 用户中止
} from '@anthropic-ai/sdk'

// 错误消息前缀
export const API_ERROR_MESSAGE_PREFIX = 'API Error'
export const PROMPT_TOO_LONG_ERROR_MESSAGE = 'Prompt is too long'

Note

SDK 提供的错误类型已覆盖大多数场景,Claude Code 主要处理错误消息的格式化和展示,而非自定义错误类。

12.3.2 重试机制

withRetry.ts 实现智能重试策略,使用 AsyncGenerator 模式:

flowchart TD
    A[API Call] --> B{Success?}
    B -->|Yes| C[Yield Result]
    B -->|No| D{529 Error?}
    D -->|Yes| E{Foreground Source?}
    E -->|Yes| F[Retry with Backoff]
    E -->|No| G[Bail Out]
    D -->|No| H{Other Retryable?}
    H -->|Yes| I[Retry]
    H -->|No| G
    F --> A
    I --> A
// 重试配置常量
const DEFAULT_MAX_RETRIES = 10
const BASE_DELAY_MS = 500

// 前台查询源(用户正在等待结果,会重试 529)
const FOREGROUND_529_RETRY_SOURCES = new Set([
  'repl_main_thread',
  'sdk',
  'agent:custom',
  'compact',
  // ... 更多前台源
])

// AsyncGenerator 模式支持流式错误消息
export async function* withRetry<T>(
  getClient: () => Promise<Anthropic>,
  operation: (client, attempt, context) => Promise<T>,
  options: RetryOptions,
): AsyncGenerator<SystemAPIErrorMessage, T>

Tip

AsyncGenerator 模式允许在重试过程中实时输出错误消息,用户可以看到「正在重试...」等状态更新。

12.4 MCP 协议实现

12.4.1 MCP 配置解析

mcp/config.ts 解析 MCP 服务器配置:

// MCP 服务器配置格式
export type McpServerConfig = {
  command?: string        // Stdio 启动命令
  args?: string[]         // 命令参数
  url?: string            // HTTP/SSE URL
  transport?: 'stdio' | 'sse' | 'http'
  env?: Record<string, string>
  capabilities?: {
    tools?: boolean
    resources?: boolean
    prompts?: boolean
  }
}

12.4.2 MCP 客户端初始化

sequenceDiagram
    participant Claude as Claude Code
    participant Client as MCP Client
    participant Server as MCP Server

    Claude->>Client: createMcpClient(config)
    Client->>Server: connect (stdio/sse/http)
    Server-->>Client: connected
    Client->>Server: initialize
    Server-->>Client: capabilities
    Client->>Server: listTools
    Server-->>Client: tools list
    Client->>Claude: register tools
export async function initializeMcpClient(
  config: McpServerConfig
): Promise<Client> {
  const client = new Client(
    { name: 'claude-code', version: '1.0.0' },
    { capabilities: { tools: {}, resources: {}, prompts: {} } }
  )

  // 创建传输层
  let transport: Transport
  if (config.transport === 'stdio') {
    transport = new StdioClientTransport({
      command: config.command!,
      args: config.args ?? [],
      env: config.env,
    })
  } else if (config.transport === 'sse') {
    transport = new SSEClientTransport(new URL(config.url!))
  } else {
    transport = new StreamableHTTPClientTransport(new URL(config.url!))
  }

  await client.connect(transport)
  return client
}

12.4.3 MCP Tool 调用

export async function callMcpTool(
  client: Client,
  toolName: string,
  args: Record<string, unknown>
): Promise<CallToolResult> {
  const result = await client.request(
    { method: 'tools/call', params: { name: toolName, arguments: args } },
    CallToolResultSchema
  )

  return result
}

12.4.4 MCP OAuth 认证

mcp/auth.ts 处理 MCP 服务器的 OAuth 认证流程:

sequenceDiagram
    participant Claude as Claude Code
    participant MCP as MCP Client
    participant Auth as Auth Server
    participant User as 用户

    MCP->>Claude: 需要认证
    Claude->>Auth: 获取 OAuth URL
    Auth-->>Claude: auth_url
    Claude->>User: 打开浏览器
    User->>Auth: 授权
    Auth-->>Claude: callback (auth_code)
    Claude->>Auth: exchange code for token
    Auth-->>Claude: access_token
    Claude->>MCP: 提供 token
    MCP->>MCP Server: 认证完成

12.5 OAuth 认证服务

12.5.1 OAuth 客户端

oauth/client.ts 实现 OAuth 2.0 流程,配置来自 constants/oauth.ts

// Claude.ai OAuth scopes - 用于 Claude.ai 订阅用户
export const CLAUDE_AI_OAUTH_SCOPES = [
  'user:profile',           // 用户基本信息
  'user:inference',         // API 调用权限
  'user:sessions:claude_code', // Claude Code 会话
  'user:mcp_servers',       // MCP 服务器管理
  'user:file_upload',       // 文件上传
]

// OAuth 配置结构
type OauthConfig = {
  BASE_API_URL: string
  CONSOLE_AUTHORIZE_URL: string
  CLAUDE_AI_AUTHORIZE_URL: string
  TOKEN_URL: string
  CLIENT_ID: string
  // ...更多配置
}

Note

OAuth 使用 platform.claude.com 作为认证平台,claude.ai 作为用户界面入口。

生产环境配置

const PROD_OAUTH_CONFIG = {
  BASE_API_URL: 'https://api.anthropic.com',
  CONSOLE_AUTHORIZE_URL: 'https://platform.claude.com/oauth/authorize',
  CLAUDE_AI_AUTHORIZE_URL: 'https://claude.com/cai/oauth/authorize',
  TOKEN_URL: 'https://platform.claude.com/v1/oauth/token',
  CLIENT_ID: '9d1c250a-e61b-44d9-88ed-5944d1962f5e',
}

获取组织 UUID

export async function getOrganizationUUID(): Promise<string | null> {
  const tokens = getClaudeAIOAuthTokens()
  if (!tokens) return null

  const response = await fetch(`${OAUTH_API_URL}/organizations`, {
    headers: { Authorization: `Bearer ${tokens.accessToken}` },
  })

  if (!response.ok) return null
  const data = await response.json()
  return data.id
}

12.5.2 Token 管理

export async function checkAndRefreshOAuthTokenIfNeeded(): Promise<boolean> {
  const tokens = getClaudeAIOAuthTokens()

  if (!tokens) return false

  // 检查是否即将过期
  const expiry = decodeJwtExpiry(tokens.accessToken)
  if (!expiry) return false

  const now = Math.floor(Date.now() / 1000)
  const buffer = 5 * 60 // 5 分钟缓冲

  if (expiry - now < buffer) {
    // 需要刷新
    return await refreshAccessToken(tokens.refreshToken)
  }

  return true
}

12.6 上下文压缩服务

12.6.1 Compact 机制

当对话历史过长时,Claude Code 会自动压缩上下文:

flowchart TD
    A[对话历史增长] --> B{超过阈值?}
    B -->|No| C[继续对话]
    B -->|Yes| D[触发 Compact]
    D --> E[提取关键信息]
    E --> F[生成摘要]
    F --> G[替换历史]
    G --> H[继续对话]
export async function compactConversation(
  messages: MessageParam[]
): Promise<MessageParam[]> {
  // 1. 分析消息结构
  const groups = groupMessagesByTopic(messages)

  // 2. 提取关键信息
  const keyPoints = extractKeyPoints(groups)

  // 3. 生成压缩摘要
  const summary = await generateCompactSummary(keyPoints)

  // 4. 构建新的消息列表
  return [
    { role: 'user', content: `[Context Summary]\n${summary}` },
    { role: 'assistant', content: 'I understand the context. Let\'s continue.' },
    ...messages.slice(-10)  // 保留最近的 10 条消息
  ]
}

12.6.2 Micro Compact

微压缩用于快速清理低价值内容:

export function microCompact(messages: MessageParam[]): MessageParam[] {
  return messages.map(msg => {
    if (msg.role === 'assistant') {
      // 移除冗长的确认回复
      if (isVerboseConfirmation(msg.content)) {
        return { ...msg, content: 'Done.' }
      }
    }
    return msg
  })
}

12.7 辅助服务

12.7.1 Analytics 服务

// 分析事件类型
export type AnalyticsEvent =
  | 'session_start'
  | 'session_end'
  | 'tool_call'
  | 'api_request'
  | 'error_occurred'

// 发送分析数据
export function logEvent(event: AnalyticsEvent, data: Record<string, unknown>) {
  sink.send({
    event,
    timestamp: Date.now(),
    sessionId: getSessionId(),
    ...data,
  })
}

12.7.2 LSP 服务

// LSP 客户端管理
export class LSPServerManager {
  private servers: Map<string, LSPServerInstance> = new Map()

  async startServer(languageId: string, config: LSPConfig): Promise<void> {
    const instance = new LSPServerInstance(languageId, config)
    await instance.start()
    this.servers.set(languageId, instance)
  }

  async getDiagnostics(filePath: string): Promise<Diagnostic[]> {
    const languageId = detectLanguage(filePath)
    const server = this.servers.get(languageId)
    if (!server) return []
    return server.getDiagnostics(filePath)
  }
}

本章小结

Services 服务层是 Claude Code 的后端支撑系统,主要特点:

  1. 多云服务商支持:Anthropic、AWS Bedrock、Azure Foundry、Vertex AI
  2. MCP 协议完整实现:支持 Stdio、SSE、HTTP 三种传输
  3. 智能错误处理:指数退避重试、错误分类处理
  4. 上下文管理:自动压缩、微压缩、会话记忆

通过本章的学习,读者可以深入了解 Claude Code 如何与各种后端服务集成,以及如何实现稳定的 API 调用和智能的上下文管理。

附录 A:类型定义速查

本附录收录 Claude Code 源码中的核心 TypeScript 类型定义,提供快速索引和概览。

Note

详细说明和设计原理请参考对应章节。类型定义文件位于 src/types/ 目录。

A.1 核心 TypeScript 类型汇总

A.1.1 工具系统类型

类型名称定义文件用途说明
Toolsrc/Tool.ts工具接口定义,包含 name、call、inputSchema、checkPermissions 等核心方法
Toolssrc/Tool.ts工具集合类型 readonly Tool[]
ToolDefsrc/Tool.ts工具定义输入类型,用于 buildTool() 函数
ToolResult<T>src/Tool.ts工具执行结果包装器
ToolUseContextsrc/Tool.ts工具执行上下文,包含 messages、abortController、permissionContext 等
ToolProgress<P>src/Tool.ts工具进度消息类型
ToolInputJSONSchemasrc/Tool.tsJSON Schema 格式的工具输入定义
PermissionResultsrc/types/permissions.ts权限检查结果类型
PermissionDecisionsrc/types/permissions.ts权限决策类型(allow/ask/deny)

A.1.2 命令与 Skill 类型

类型名称定义文件用途说明
Commandsrc/types/command.ts斜杠命令类型,合并 CommandBase 与三种命令实现
PromptCommandsrc/types/command.tsPrompt 类型命令,包含 getPromptForCommand 方法
LocalCommandsrc/types/command.ts本地命令类型,包含 load() 懒加载
LocalJSXCommandsrc/types/command.tsJSX 命令类型,用于交互式 UI 命令
CommandBasesrc/types/command.ts命令基础属性(name、description、isEnabled 等)
BundledSkillDefinitionsrc/skills/bundledSkills.ts内置 Skill 定义,包含 name、description、getPromptForCommand 等
CommandAvailabilitysrc/types/command.ts命令可用性约束类型('claude-ai' | 'console')

A.1.3 查询引擎类型

类型名称定义文件用途说明
QueryEngineConfigsrc/QueryEngine.tsQueryEngine 配置,包含 cwd、tools、mcpClients、agents 等
QueryChainTrackingsrc/Tool.ts查询链追踪信息(chainId、depth)
ValidationResultsrc/Tool.ts输入验证结果类型
CompactProgressEventsrc/Tool.ts压缩进度事件类型

A.1.4 会话与标识类型

类型名称定义文件用途说明
SessionIdsrc/types/ids.ts会话 ID 品牌类型,防止与其他 ID 混淆
AgentIdsrc/types/ids.tsAgent ID 品牌类型,用于子 Agent 标识
LogOptionsrc/types/logs.ts会话日志选项,包含 date、messages、sessionId 等
SerializedMessagesrc/types/logs.ts序列化消息类型,用于日志存储
TranscriptMessagesrc/types/logs.ts转录消息类型,包含 parentUuid、isSidechain 等
PersistedWorktreeSessionsrc/types/logs.tsWorktree 会话持久化数据

A.1.5 Bridge 类型

类型名称定义文件用途说明
BridgeConfigsrc/bridge/types.tsBridge 配置,包含 dir、machineName、spawnMode 等
WorkResponsesrc/bridge/types.ts工作响应类型,包含 id、environment_id、state、secret
WorkSecretsrc/bridge/types.ts工作密钥类型,包含 session_ingress_token、api_base_url
SessionHandlesrc/bridge/types.ts会话句柄,包含 sessionId、done、kill、activities
SessionActivitysrc/bridge/types.ts会话活动类型(tool_start、text、result、error)
SpawnModesrc/bridge/types.ts会话启动模式(single-session、worktree、same-dir)
BridgeWorkerTypesrc/bridge/types.tsBridge 工作类型(claude_code、claude_code_assistant)

A.1.6 插件类型

类型名称定义文件用途说明
PluginManifestsrc/types/plugin.ts插件清单定义
LoadedPluginsrc/types/plugin.ts已加载插件类型,包含 manifest、path、enabled 等
PluginErrorsrc/types/plugin.ts插件错误联合类型,包含 20+ 种具体错误类型
PluginLoadResultsrc/types/plugin.ts插件加载结果(enabled、disabled、errors)
PluginRepositorysrc/types/plugin.ts插件仓库配置
BuiltinPluginDefinitionsrc/types/plugin.ts内置插件定义

A.1.7 权限类型

类型名称定义文件用途说明
PermissionModesrc/types/permissions.ts权限模式(default、acceptEdits、bypassPermissions 等)
PermissionBehaviorsrc/types/permissions.ts权限行为(allow、deny、ask)
PermissionRulesrc/types/permissions.ts权限规则,包含 source、ruleBehavior、ruleValue
PermissionUpdatesrc/types/permissions.ts权限更新操作类型
PermissionDecisionReasonsrc/types/permissions.ts权限决策原因类型
ClassifierResultsrc/types/permissions.ts分类器结果类型
YoloClassifierResultsrc/types/permissions.tsAuto Mode 分类器结果
ToolPermissionContextsrc/types/permissions.ts工具权限上下文

A.1.8 Hook 类型

类型名称定义文件用途说明
HookCallbacksrc/types/hooks.tsHook 回调类型
HookResultsrc/types/hooks.tsHook 执行结果
AggregatedHookResultsrc/types/hooks.ts聚合 Hook 结果
HookProgresssrc/types/hooks.tsHook 进度消息
PromptRequestsrc/types/hooks.tsPrompt 请求类型
PromptResponsesrc/types/hooks.tsPrompt 响应类型
PermissionRequestResultsrc/types/hooks.ts权限请求结果

A.1.9 UI 输入类型

类型名称定义文件用途说明
BaseTextInputPropssrc/types/textInputTypes.ts文本输入基础属性
VimTextInputPropssrc/types/textInputTypes.tsVim 模式输入属性
TextInputStatesrc/types/textInputTypes.ts文本输入状态
VimModesrc/types/textInputTypes.tsVim 模式(INSERT、NORMAL)
PromptInputModesrc/types/textInputTypes.tsPrompt 输入模式
QueuePrioritysrc/types/textInputTypes.ts队列优先级(now、next、later)
QueuedCommandsrc/types/textInputTypes.ts队列命令类型
OrphanedPermissionsrc/types/textInputTypes.ts孤立权限类型

A.1.10 Connector 类型

类型名称定义文件用途说明
ConnectorTextBlocksrc/types/connectorText.tsConnector 文本块类型
ConnectorTextDeltasrc/types/connectorText.tsConnector 文本增量类型

A.2 类型关系图

graph TB
    subgraph "核心类型体系"
        Tool[Tool<br/>工具接口]
        Tools[Tools<br/>工具集合]
        ToolDef[ToolDef<br/>工具定义]
        
        ToolDef -->|buildTool| Tool
        Tool -->|组成| Tools
    end
    
    subgraph "权限类型体系"
        PermissionMode[PermissionMode<br/>权限模式]
        PermissionBehavior[PermissionBehavior<br/>权限行为]
        PermissionRule[PermissionRule<br/>权限规则]
        PermissionDecision[PermissionDecision<br/>权限决策]
        PermissionResult[PermissionResult<br/>权限结果]
        
        PermissionMode --> PermissionDecision
        PermissionBehavior --> PermissionRule
        PermissionRule --> PermissionDecision
        PermissionDecision --> PermissionResult
    end
    
    subgraph "命令类型体系"
        CommandBase[CommandBase<br/>命令基础]
        Command[Command<br/>命令联合]
        PromptCommand[PromptCommand<br/>Prompt 命令]
        LocalCommand[LocalCommand<br/>本地命令]
        LocalJSXCommand[LocalJSXCommand<br/>JSX 命令]
        
        CommandBase --> Command
        PromptCommand --> Command
        LocalCommand --> Command
        LocalJSXCommand --> Command
    end
    
    subgraph "会话类型体系"
        SessionId[SessionId<br/>会话 ID]
        AgentId[AgentId<br/>Agent ID]
        LogOption[LogOption<br/>日志选项]
        SerializedMessage[SerializedMessage<br/>序列化消息]
        
        SessionId --> LogOption
        AgentId --> SerializedMessage
    end
    
    subgraph "Bridge 类型体系"
        BridgeConfig[BridgeConfig<br/>Bridge 配置]
        WorkResponse[WorkResponse<br/>工作响应]
        SessionHandle[SessionHandle<br/>会话句柄]
        SpawnMode[SpawnMode<br/>启动模式]
        
        BridgeConfig --> SessionHandle
        WorkResponse --> SessionHandle
        SpawnMode --> BridgeConfig
    end
    
    subgraph "插件类型体系"
        PluginManifest[PluginManifest<br/>插件清单]
        LoadedPlugin[LoadedPlugin<br/>已加载插件]
        PluginError[PluginError<br/>插件错误]
        PluginLoadResult[PluginLoadResult<br/>加载结果]
        
        PluginManifest --> LoadedPlugin
        LoadedPlugin --> PluginLoadResult
        PluginError --> PluginLoadResult
    end
    
    Tool --> PermissionResult
    ToolUseContext --> Tool
    ToolUseContext --> PermissionMode
    Command --> ToolUseContext
    QueryEngine --> ToolUseContext
    QueryEngine --> Tools
    QueryEngine --> Command
    BridgeConfig --> SessionId
    HookResult --> PermissionDecision
    BundledSkillDefinition --> Command

A.3 关键类型定义示例

A.3.1 Tool 核心结构

type Tool<
  Input extends AnyObject = AnyObject,
  Output = unknown,
  P extends ToolProgressData = ToolProgressData,
> = {
  readonly name: string;
  aliases?: string[];
  readonly inputSchema: Input;
  call(args, context, canUseTool, parentMessage, onProgress): Promise<ToolResult<Output>>;
  description(input, options): Promise<string>;
  checkPermissions(input, context): Promise<PermissionResult>;
  isEnabled(): boolean;
  isConcurrencySafe(input): boolean;
  isReadOnly(input): boolean;
  isDestructive?(input): boolean;
  userFacingName(input): string;
  prompt(options): Promise<string>;
  // ... 渲染方法
};

A.3.2 Command 联合类型

type Command = CommandBase &
  (PromptCommand | LocalCommand | LocalJSXCommand);

type CommandBase = {
  name: string;
  description: string;
  availability?: CommandAvailability[];
  isEnabled?: () => boolean;
  isHidden?: boolean;
  aliases?: string[];
  argumentHint?: string;
  whenToUse?: string;
  version?: string;
  disableModelInvocation?: boolean;
  userInvocable?: boolean;
  loadedFrom?: 'skills' | 'plugin' | 'managed' | 'bundled' | 'mcp';
  // ...
};

A.3.3 PermissionResult 结构

type PermissionResult<Input = { [key: string]: unknown }> =
  | PermissionDecision<Input>
  | {
      behavior: 'passthrough';
      message: string;
      decisionReason?: PermissionDecision<Input>['decisionReason'];
      suggestions?: PermissionUpdate[];
      blockedPath?: string;
      pendingClassifierCheck?: PendingClassifierCheck;
    };

type PermissionDecision<Input> =
  | PermissionAllowDecision<Input>
  | PermissionAskDecision<Input>
  | PermissionDenyDecision;

A.3.4 QueryEngineConfig 结构

type QueryEngineConfig = {
  cwd: string;
  tools: Tools;
  commands: Command[];
  mcpClients: MCPServerConnection[];
  agents: AgentDefinition[];
  canUseTool: CanUseToolFn;
  getAppState: () => AppState;
  setAppState: (f: (prev: AppState) => AppState) => void;
  initialMessages?: Message[];
  readFileCache: FileStateCache;
  customSystemPrompt?: string;
  appendSystemPrompt?: string;
  userSpecifiedModel?: string;
  thinkingConfig?: ThinkingConfig;
  maxTurns?: number;
  maxBudgetUsd?: number;
  // ...
};

Tip

品牌类型(Branded Types)如 SessionIdAgentId 使用 TypeScript 的类型品牌机制,在编译时防止不同类型 ID 的混淆,提高代码安全性。

附录 B:配置与环境变量

本附录详细说明 Claude Code 的配置系统和环境变量。

Note

环境变量配置文件位于 .env.example,配置文件通常存放在 .claude/ 目录。

B.1 环境变量详解

B.1.1 API 认证配置

变量必填说明
ANTHROPIC_API_KEY二选一API Key,通过 x-api-key 请求头发送
ANTHROPIC_AUTH_TOKEN二选一Auth Token,通过 Authorization: Bearer 请求头发送

Warning

两种认证方式二选一,不能同时使用。ANTHROPIC_API_KEY 适用于直接 API 调用,ANTHROPIC_AUTH_TOKEN 适用于 OAuth 认证场景。

B.1.2 API 端点配置

变量必填说明
ANTHROPIC_BASE_URL自定义 API 端点,默认为 Anthropic 官方 API

Tip

设置 ANTHROPIC_BASE_URL 可接入兼容 Anthropic API 的第三方服务(如 MiniMax、OpenRouter 等)。

B.1.3 模型配置

变量必填说明
ANTHROPIC_MODEL默认使用的模型名称
ANTHROPIC_DEFAULT_SONNET_MODELSonnet 级别模型映射
ANTHROPIC_DEFAULT_HAIKU_MODELHaiku 级别模型映射
ANTHROPIC_DEFAULT_OPUS_MODELOpus 级别模型映射
// 模型配置示例(MiniMax API)
ANTHROPIC_MODEL=MiniMax-M2.7-highspeed
ANTHROPIC_DEFAULT_SONNET_MODEL=MiniMax-M2.7-highspeed
ANTHROPIC_DEFAULT_HAIKU_MODEL=MiniMax-M2.7-highspeed
ANTHROPIC_DEFAULT_OPUS_MODEL=MiniMax-M2.7-highspeed

B.1.4 超时与性能配置

变量必填说明
API_TIMEOUT_MSAPI 请求超时时间(毫秒),默认 600000(10分钟)

B.1.5 遥测与网络配置

变量必填说明
DISABLE_TELEMETRY设为 1 禁用遥测数据收集
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC设为 1 禁用非必要网络请求

Note

建议在本地运行时设置 DISABLE_TELEMETRY=1CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 以减少不必要的网络请求。

B.1.6 完整 .env 配置示例

# API 认证(二选一)
ANTHROPIC_API_KEY=sk-xxx
# ANTHROPIC_AUTH_TOKEN=sk-xxx

# API 端点(可选)
ANTHROPIC_BASE_URL=https://api.minimaxi.com/anthropic

# 模型配置
ANTHROPIC_MODEL=MiniMax-M2.7-highspeed
ANTHROPIC_DEFAULT_SONNET_MODEL=MiniMax-M2.7-highspeed
ANTHROPIC_DEFAULT_HAIKU_MODEL=MiniMax-M2.7-highspeed
ANTHROPIC_DEFAULT_OPUS_MODEL=MiniMax-M2.7-highspeed

# 超时配置(毫秒)
API_TIMEOUT_MS=3000000

# 禁用遥测和非必要网络请求
DISABLE_TELEMETRY=1
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1

B.2 配置文件说明

B.2.1 .claude/ 目录配置

.claude/ 目录是 Claude Code 的项目配置目录,包含以下配置文件:

文件用途
settings.json项目级设置,包含权限规则、MCP 服务器配置等
settings.local.json本地设置(不提交到版本控制)
commands/自定义命令目录
skills/自定义 Skill 目录
agents/Agent 定义目录
// settings.json 示例
{
  "permissions": {
    "allow": ["Bash(git *)", "Read", "Edit"],
    "deny": ["Bash(rm -rf *)"]
  },
  "mcpServers": {
    "playwright": {
      "command": "npx",
      "args": ["@anthropic-ai/mcp-server-playwright"]
    }
  }
}

B.2.2 MCP 服务器配置

MCP (Model Context Protocol) 服务器配置支持多种格式:

{
  "mcpServers": {
    "server-name": {
      "command": "npx",
      "args": ["-y", "mcp-server-name"],
      "env": {
        "API_KEY": "xxx"
      }
    },
    "remote-server": {
      "url": "https://example.com/mcp",
      "headers": {
        "Authorization": "Bearer xxx"
      }
    }
  }
}

B.2.3 Skills 配置

Skills 可以通过多种方式配置:

  1. 内置 Skills: 通过 BundledSkillDefinition 注册
  2. 项目 Skills: 在 .claude/skills/ 目录中定义
  3. 插件 Skills: 通过插件系统加载
// BundledSkillDefinition 结构
type BundledSkillDefinition = {
  name: string;
  description: string;
  aliases?: string[];
  argumentHint?: string;
  allowedTools?: string[];
  model?: string;
  disableModelInvocation?: boolean;
  userInvocable?: boolean;
  isEnabled?: () => boolean;
  hooks?: HooksSettings;
  context?: 'inline' | 'fork';
  agent?: string;
  files?: Record<string, string>;
  getPromptForCommand(args, context): Promise<ContentBlockParam[]>;
};

B.2.4 权限配置

权限系统支持三种配置方式:

{
  "permissions": {
    "allow": ["Bash(git *)", "Read", "Edit", "Grep"],
    "deny": ["Bash(rm -rf /*)", "Bash(sudo *)"],
    "ask": ["Bash(npm install *)"]
  },
  "permissionMode": "default"
}
权限模式说明
default默认模式,根据规则决定
acceptEdits自动接受编辑操作
bypassPermissions绕过所有权限检查
dontAsk不询问用户,自动决策
plan计划模式

B.2.5 Hooks 配置

Hooks 系统支持多种事件类型:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "echo 'Tool use detected'"
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Edit",
        "hooks": [
          {
            "type": "callback",
            "callback": "async (input) => { return { continue: true } }"
          }
        ]
      }
    ]
  }
}

支持的 Hook 事件类型:

事件触发时机
PreToolUse工具执行前
PostToolUse工具执行后
PostToolUseFailure工具执行失败后
UserPromptSubmit用户提交消息后
SessionStart会话开始时
Setup设置阶段
SubagentStart子 Agent 启动时
PermissionDenied权限拒绝时
Notification通知事件
PermissionRequest权限请求时
Elicitation引导请求时
CwdChanged工作目录变更时
FileChanged文件变更时
WorktreeCreateWorktree 创建时

B.3 命令行参数

B.3.1 启动参数

参数说明
-p, --print无头模式,单次问答
--resume恢复上次会话
--session-id指定会话 ID
--permission-mode设置权限模式
--mcp-config指定 MCP 配置文件
--verbose详细输出模式
--debug调试模式

B.3.2 模型参数

参数说明
--model指定使用的模型
--fallback-model设置备用模型

B.3.3 其他参数

参数说明
--worktree在 Worktree 中运行
--agent指定 Agent 类型
--help显示帮助信息

Tip

完整的命令行参数可通过 ./bin/claude-haha --help 查看。

附录 C:修复记录

本附录记录 Claude Code 泄露源码修复过程中的问题分析和解决方案。

Warning

本附录内容基于 Claude Code Haha 项目(泄露源码修复版本)的 README.md 文档整理。

C.1 泄露源码修复记录

C.1.1 修复总览

泄露的 Claude Code 源码无法直接运行,主要修复了以下问题:

问题根因修复
TUI 不启动入口脚本把无参数启动路由到了 recovery CLI恢复走 cli.tsx 完整入口
启动卡死verify skill 导入缺失的 .md 文件,Bun text loader 无限挂起创建 stub .md 文件
--print 卡死filePersistence/types.ts 缺失创建类型桩文件
--print 卡死ultraplan/prompt.txt 缺失创建资源桩文件
Enter 键无响应modifiers-napi native 包缺失,isModifierPressed() 抛异常导致 handleEnter 中断,onSubmit 永远不执行加 try-catch 容错
setup 被跳过preload.ts 自动设置 LOCAL_RECOVERY=1 跳过全部初始化移除默认设置

C.2 修复过程详解

C.2.1 TUI 不启动问题

问题现象

执行 ./bin/claude-haha 后,TUI 界面不出现,直接进入 recovery CLI 模式。

根因分析

入口脚本中的路由逻辑将无参数启动错误地路由到了 localRecoveryCli.ts,而不是完整的 cli.tsx 入口。

修复方案

修改入口脚本,恢复正确的启动路由:

// 修复:无参数启动应走 cli.tsx 完整入口
// 原代码:
// if (args.length === 0) { runRecoveryCLI() }

// 修复后:
if (args.length === 0) {
  await import('./src/entrypoints/cli.tsx')
}

C.2.2 启动卡死问题

问题现象

启动过程中程序卡住不动,无任何响应。

根因分析

verify skill 导入了缺失的 .md 文件,由于 Bun 的 text loader 无法找到文件,导致无限等待挂起。

// verify skill 导入了不存在的 .md 文件
import verificationPrompt from './prompt.md'

修复方案

创建 stub .md 文件,确保 Bun text loader 能正常加载:

# Verification Stub

This is a placeholder file for the verification skill.

同时修复其他缺失的文件:

  • filePersistence/types.ts - 创建类型桩文件
  • ultraplan/prompt.txt - 创建资源桩文件

C.2.3 Enter 键无响应问题

问题现象

在交互界面中按下 Enter 键没有任何响应,消息无法提交。

根因分析

这是一个最隐蔽的 bug:

  1. modifiers-napi 是一个 native 包,用于检测修饰键状态
  2. 泄露源码缺少这个 native 包
  3. isModifierPressed() 函数调用时抛出异常
  4. 异常中断了 handleEnter 函数的执行
  5. onSubmit 回调永远不执行,导致 Enter 键无响应
// 问题代码路径
function handleEnter() {
  if (isModifierPressed('shift')) {  // 这里抛异常
    // multiline handling
  }
  onSubmit(input)  // 永远不执行
}

修复方案

isModifierPressed() 调用处添加 try-catch 容错:

// 修复:添加 try-catch 容错
function handleEnter() {
  let isShiftPressed = false
  try {
    isShiftPressed = isModifierPressed('shift')
  } catch (e) {
    // native 包缺失,默认 false
    isShiftPressed = false
  }
  
  if (isShiftPressed) {
    // multiline handling
  }
  onSubmit(input)  // 正常执行
}

Tip

这个问题最难发现,因为:

  1. 异常发生在 native 模块调用中
  2. 没有明显错误提示
  3. UI 看起来正常,只是 Enter 键不工作

C.2.4 setup 被跳过问题

问题现象

启动初始化阶段(setup)被完全跳过,直接进入主界面。

根因分析

preload.ts 文件中错误地设置了 LOCAL_RECOVERY=1 环境变量,导致初始化流程被跳过。

// preload.ts 中的问题代码
process.env.LOCAL_RECOVERY = '1'  // 强制跳过 setup

修复方案

移除 preload.ts 中的默认设置:

// 修复:移除强制跳过设置
// process.env.LOCAL_RECOVERY = '1'  // 删除这行

// 只在需要时设置
if (process.env.CLAUDE_CODE_FORCE_RECOVERY_CLI === '1') {
  process.env.LOCAL_RECOVERY = '1'
}

C.3 修复技术要点

C.3.1 文件桩文件创建

对于缺失的资源文件,需要创建最小化的桩文件:

// 类型桩文件示例 (filePersistence/types.ts)
export type FilePersistenceState = unknown
export type PersistedFileEntry = unknown
export function createFilePersistence(): FilePersistenceState {
  return null
}

C.3.2 Native 包容错处理

对于 native 包缺失的情况,采用以下容错策略:

  1. Try-Catch 包装: 在调用处捕获异常
  2. 默认值兜底: 异常时使用安全的默认值
  3. 功能降级: 不影响核心功能运行

C.3.3 启动路由修复

入口脚本路由逻辑的修复原则:

  1. 无参数启动 -> TUI 完整入口
  2. -p/--print 参数 -> 无头模式
  3. CLAUDE_CODE_FORCE_RECOVERY_CLI=1 -> Recovery CLI

C.4 降级模式说明

如果完整 TUI 出现问题,可以使用简化版 readline 交互模式:

CLAUDE_CODE_FORCE_RECOVERY_CLI=1 ./bin/claude-haha

降级模式特点:

  • 简化的命令行交互
  • 基本的问答功能
  • 不依赖 Ink 终端渲染引擎
  • 适合问题排查和调试

Note

Windows 用户推荐使用以下方式启动 TUI:

bun --env-file=.env ./src/entrypoints/cli.tsx

</div>
</div>
这种方式绕过 shell 脚本,直接调用 Bun 运行入口文件。

C.5 Windows 平台注意事项

Windows 平台运行 Claude Code Haha 需要注意:

  1. 依赖 Git Bash: 启动脚本 bin/claude-haha 是 bash 脚本,需要 Git for Windows
  2. 推荐方式: 直接使用 PowerShell/cmd 调用 Bun
  3. 不可用功能: 语音输入、Computer Use、Sandbox 隔离等
# Windows 启动方式
bun --env-file=.env ./src/entrypoints/cli.tsx

# Windows 无头模式
bun --env-file=.env ./src/entrypoints/cli.tsx -p "your prompt"

# Windows 降级模式
bun --env-file=.env ./src/localRecoveryCli.ts