前言
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 交互的能力。
功能特性
核心功能包括:
| 功能类别 | 具体特性 |
|---|---|
| 代码操作 | Read、Edit、Write、Grep、Glob 文件操作 |
| 命令执行 | Bash 命令运行,支持后台任务 |
| 网络能力 | WebFetch、WebSearch 网络信息获取 |
| 多 Agent | AgentTool 支持子 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 | - | 类型安全的开发体验 |
| 终端 UI | React + Ink | ^6.8.0 | React 风格的终端组件库 |
| CLI 框架 | Commander.js | ^14.0.0 | 命令行参数解析 |
| AI SDK | @anthropic-ai/sdk | ^0.80.0 | Anthropic Messages API |
| 协议 | @modelcontextprotocol/sdk | ^1.29.0 | MCP 协议实现 |
关键依赖
{
"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 验证(内部版)
}
}
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.tsx | 804KB | TUI 主逻辑,包含 Commander 命令解析、React/Ink 渲染、消息处理循环 |
QueryEngine.ts | 46KB | 查询引擎,处理 API 请求和响应流 |
Tool.ts | 29KB | 工具基类定义,包含类型和执行框架 |
tools.ts | 17KB | 工具注册表,汇总所有可用工具 |
commands.ts | 25KB | 斜杠命令注册表 |
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) 是修复后的版本,主要改动:
- 入口修复:恢复正确的 CLI 启动路径
- 依赖补全:创建缺失的 stub 文件
- 容错增强:对 native 模块缺失添加 fallback
- API 兼容:支持自定义 API 端点(如 MiniMax、OpenRouter)
下一章将深入探讨 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 负责「怎么做」—— 执行具体的系统操作
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 命令 |
| PowerShellTool | Windows PowerShell 命令 | |
| 网络操作 | WebFetchTool | 获取 URL 内容 |
| WebSearchTool | Web 搜索 | |
| 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
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
| 维度 | Tool | Skill |
|---|---|---|
| 粒度 | 原子操作(如读文件) | 任务模板(如代码审查) |
| 定义 | 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 的三种来源:
- Bundled Skills:编译内置的 Skills,随 CLI 二进制分发
- Disk Skills:用户目录
~/.claude/commands/下的.md文件 - 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-api | Claude API 调试 |
loop | 定时循环执行 |
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 协议的核心概念:
| 概念 | 说明 |
|---|---|
| Tools | MCP Server 提供的可调用工具 |
| Resources | MCP Server 提供的可读资源(如文件、数据库记录) |
| Prompts | MCP Server 提供的预定义提示词模板 |
| Sampling | MCP 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
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)"
]
}
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
上下文压缩策略:
- 消息截断:保留最近 N 条消息
- 内容摘要:将长内容替换为摘要
- 工具结果压缩:大型工具结果转为引用
- 文件状态清理:清理不再相关的文件缓存
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 | 终端渲染、用户交互 |
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
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 | 输入处理、历史记录 |
| QueryEngine | Message[] | AssistantMessage | 消息组装、API 调用 |
| Tool System | tool_use block | tool_result | 工具匹配、权限检查、执行 |
| API Service | messages + tools | stream response | Anthropic API 通信 |
| MCP Service | MCP request | MCP response | MCP 协议适配 |
消息处理流程
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
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 |
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 同时工作,通过 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
Model Context Protocol (MCP) 是 Anthropic 定义的模型上下文协议,支持工具、资源、提示的标准化接入。Claude Code 支持多种传输方式(Stdio、SSE、HTTP)。
3.6 设计原则
快速启动原则
入口 cli.tsx 实现快速路径优化:
--version零模块加载直接输出--dump-system-prompt动态加载最小模块- 特殊子命令(daemon、bridge、ps)独立处理路径
- 正常启动延迟加载 main.tsx
状态持久化原则
所有状态变更立即持久化:
单一职责原则
| 模块 | 单一职责 |
|---|---|
| cli.tsx | 快速路径判断、参数解析 |
| main.tsx | Commander.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();
}
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-config | MCP 配置路径 | --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]
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);
}
}
}
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);
}
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 使用自定义 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 流程]
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: 显示结果
消息类型分类
| 类型 | 说明 | 处理方式 |
|---|---|---|
assistant | AI 响应消息 | 添加到消息列表,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
上下文压缩触发条件
| 触发条件 | 压缩方式 | 说明 |
|---|---|---|
| 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(失败即关闭)原则: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 | 绕过模式 | 允许所有操作(危险) |
权限规则配置
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 字段说明
| 字段 | 类型 | 说明 |
|---|---|---|
name | string | Skill 名称(唯一标识) |
description | string | 功能描述 |
aliases | string[] | 别名列表 |
whenToUse | string | 使用场景说明 |
argumentHint | string | 参数提示 |
allowedTools | string[] | 可用工具列表 |
model | string | 指定使用的模型 |
disableModelInvocation | boolean | 是否禁用模型调用 |
userInvocable | boolean | 是否用户可调用 |
hooks | HooksSettings | Hook 配置 |
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(如 claude-api)使用 whenToUse 字段定义触发条件。当代码匹配导入语句(如 import anthropic)时,Skill 会自动被建议使用。
7.4 自定义 Skill 开发
创建文件型 Skill
- 创建
.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.
- 放置到正确目录:
# 用户级 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
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>
` }
]
},
}
/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 --> [*]
prompt命令用于发送到模型处理,适合复杂任务local命令用于快速本地操作,无需模型参与local-jsx命令用于需要交互 UI 的场景- 使用
availability限制命令可见性 - 使用
aliases提供便捷别名
至此,核心模块篇已完整分析了 Claude Code 的查询引擎、工具系统、技能系统和命令系统。后续章节将继续分析 UI 组件、Bridge 远程控制和服务层实现。
第九章:Ink 终端渲染引擎
本章深入分析 Claude Code 的终端 UI 渲染引擎 —— Ink 框架的实现原理和优化策略。
9.1 Ink 框架原理
什么是 Ink
Ink 是一个「React for CLI」的终端 UI 渲染框架,它将 React 的组件化思想带入命令行界面开发。
核心架构
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 }
};
};
}
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;
}
}
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?.();
}
下一章将探讨 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 元素
组件目录规模庞大(150+ 文件),体现了 Claude Code 作为交互式 CLI 工具的复杂度。核心组件如 Message.tsx、Messages.tsx、VirtualMessageList.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>
);
}
App 组件使用三层 Provider 包装:
FpsMetricsProvider- 提供 FPS 性能数据StatsProvider- 提供统计信息(Token、Cost 等)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>
);
}
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
交互设计遵循渐进增强原则:
- 基础模式:Normal 输入 + 标准键绑定
- 高级模式:Vim 键绑定 + 搜索导航
- 辅助模式: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;
}
下一章将探讨 服务层架构,分析 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 # 工作密钥解析
└── ...
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]
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 远程控制功能的核心实现,主要特点:
- 双协议支持:V1 WebSocket 和 V2 CCR 兼容
- JWT Token 管理:自动刷新机制确保长连接稳定
- 多种启动模式:single-session、worktree、same-dir
- 完整消息协议:支持文本、附件、权限响应等多种消息类型
下一章将介绍 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]
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'
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>
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
// ...更多配置
}
生产环境配置:
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 的后端支撑系统,主要特点:
- 多云服务商支持:Anthropic、AWS Bedrock、Azure Foundry、Vertex AI
- MCP 协议完整实现:支持 Stdio、SSE、HTTP 三种传输
- 智能错误处理:指数退避重试、错误分类处理
- 上下文管理:自动压缩、微压缩、会话记忆
通过本章的学习,读者可以深入了解 Claude Code 如何与各种后端服务集成,以及如何实现稳定的 API 调用和智能的上下文管理。
附录 A:类型定义速查
本附录收录 Claude Code 源码中的核心 TypeScript 类型定义,提供快速索引和概览。
A.1 核心 TypeScript 类型汇总
A.1.1 工具系统类型
| 类型名称 | 定义文件 | 用途说明 |
|---|---|---|
Tool | src/Tool.ts | 工具接口定义,包含 name、call、inputSchema、checkPermissions 等核心方法 |
Tools | src/Tool.ts | 工具集合类型 readonly Tool[] |
ToolDef | src/Tool.ts | 工具定义输入类型,用于 buildTool() 函数 |
ToolResult<T> | src/Tool.ts | 工具执行结果包装器 |
ToolUseContext | src/Tool.ts | 工具执行上下文,包含 messages、abortController、permissionContext 等 |
ToolProgress<P> | src/Tool.ts | 工具进度消息类型 |
ToolInputJSONSchema | src/Tool.ts | JSON Schema 格式的工具输入定义 |
PermissionResult | src/types/permissions.ts | 权限检查结果类型 |
PermissionDecision | src/types/permissions.ts | 权限决策类型(allow/ask/deny) |
A.1.2 命令与 Skill 类型
| 类型名称 | 定义文件 | 用途说明 |
|---|---|---|
Command | src/types/command.ts | 斜杠命令类型,合并 CommandBase 与三种命令实现 |
PromptCommand | src/types/command.ts | Prompt 类型命令,包含 getPromptForCommand 方法 |
LocalCommand | src/types/command.ts | 本地命令类型,包含 load() 懒加载 |
LocalJSXCommand | src/types/command.ts | JSX 命令类型,用于交互式 UI 命令 |
CommandBase | src/types/command.ts | 命令基础属性(name、description、isEnabled 等) |
BundledSkillDefinition | src/skills/bundledSkills.ts | 内置 Skill 定义,包含 name、description、getPromptForCommand 等 |
CommandAvailability | src/types/command.ts | 命令可用性约束类型('claude-ai' | 'console') |
A.1.3 查询引擎类型
| 类型名称 | 定义文件 | 用途说明 |
|---|---|---|
QueryEngineConfig | src/QueryEngine.ts | QueryEngine 配置,包含 cwd、tools、mcpClients、agents 等 |
QueryChainTracking | src/Tool.ts | 查询链追踪信息(chainId、depth) |
ValidationResult | src/Tool.ts | 输入验证结果类型 |
CompactProgressEvent | src/Tool.ts | 压缩进度事件类型 |
A.1.4 会话与标识类型
| 类型名称 | 定义文件 | 用途说明 |
|---|---|---|
SessionId | src/types/ids.ts | 会话 ID 品牌类型,防止与其他 ID 混淆 |
AgentId | src/types/ids.ts | Agent ID 品牌类型,用于子 Agent 标识 |
LogOption | src/types/logs.ts | 会话日志选项,包含 date、messages、sessionId 等 |
SerializedMessage | src/types/logs.ts | 序列化消息类型,用于日志存储 |
TranscriptMessage | src/types/logs.ts | 转录消息类型,包含 parentUuid、isSidechain 等 |
PersistedWorktreeSession | src/types/logs.ts | Worktree 会话持久化数据 |
A.1.5 Bridge 类型
| 类型名称 | 定义文件 | 用途说明 |
|---|---|---|
BridgeConfig | src/bridge/types.ts | Bridge 配置,包含 dir、machineName、spawnMode 等 |
WorkResponse | src/bridge/types.ts | 工作响应类型,包含 id、environment_id、state、secret |
WorkSecret | src/bridge/types.ts | 工作密钥类型,包含 session_ingress_token、api_base_url |
SessionHandle | src/bridge/types.ts | 会话句柄,包含 sessionId、done、kill、activities |
SessionActivity | src/bridge/types.ts | 会话活动类型(tool_start、text、result、error) |
SpawnMode | src/bridge/types.ts | 会话启动模式(single-session、worktree、same-dir) |
BridgeWorkerType | src/bridge/types.ts | Bridge 工作类型(claude_code、claude_code_assistant) |
A.1.6 插件类型
| 类型名称 | 定义文件 | 用途说明 |
|---|---|---|
PluginManifest | src/types/plugin.ts | 插件清单定义 |
LoadedPlugin | src/types/plugin.ts | 已加载插件类型,包含 manifest、path、enabled 等 |
PluginError | src/types/plugin.ts | 插件错误联合类型,包含 20+ 种具体错误类型 |
PluginLoadResult | src/types/plugin.ts | 插件加载结果(enabled、disabled、errors) |
PluginRepository | src/types/plugin.ts | 插件仓库配置 |
BuiltinPluginDefinition | src/types/plugin.ts | 内置插件定义 |
A.1.7 权限类型
| 类型名称 | 定义文件 | 用途说明 |
|---|---|---|
PermissionMode | src/types/permissions.ts | 权限模式(default、acceptEdits、bypassPermissions 等) |
PermissionBehavior | src/types/permissions.ts | 权限行为(allow、deny、ask) |
PermissionRule | src/types/permissions.ts | 权限规则,包含 source、ruleBehavior、ruleValue |
PermissionUpdate | src/types/permissions.ts | 权限更新操作类型 |
PermissionDecisionReason | src/types/permissions.ts | 权限决策原因类型 |
ClassifierResult | src/types/permissions.ts | 分类器结果类型 |
YoloClassifierResult | src/types/permissions.ts | Auto Mode 分类器结果 |
ToolPermissionContext | src/types/permissions.ts | 工具权限上下文 |
A.1.8 Hook 类型
| 类型名称 | 定义文件 | 用途说明 |
|---|---|---|
HookCallback | src/types/hooks.ts | Hook 回调类型 |
HookResult | src/types/hooks.ts | Hook 执行结果 |
AggregatedHookResult | src/types/hooks.ts | 聚合 Hook 结果 |
HookProgress | src/types/hooks.ts | Hook 进度消息 |
PromptRequest | src/types/hooks.ts | Prompt 请求类型 |
PromptResponse | src/types/hooks.ts | Prompt 响应类型 |
PermissionRequestResult | src/types/hooks.ts | 权限请求结果 |
A.1.9 UI 输入类型
| 类型名称 | 定义文件 | 用途说明 |
|---|---|---|
BaseTextInputProps | src/types/textInputTypes.ts | 文本输入基础属性 |
VimTextInputProps | src/types/textInputTypes.ts | Vim 模式输入属性 |
TextInputState | src/types/textInputTypes.ts | 文本输入状态 |
VimMode | src/types/textInputTypes.ts | Vim 模式(INSERT、NORMAL) |
PromptInputMode | src/types/textInputTypes.ts | Prompt 输入模式 |
QueuePriority | src/types/textInputTypes.ts | 队列优先级(now、next、later) |
QueuedCommand | src/types/textInputTypes.ts | 队列命令类型 |
OrphanedPermission | src/types/textInputTypes.ts | 孤立权限类型 |
A.1.10 Connector 类型
| 类型名称 | 定义文件 | 用途说明 |
|---|---|---|
ConnectorTextBlock | src/types/connectorText.ts | Connector 文本块类型 |
ConnectorTextDelta | src/types/connectorText.ts | Connector 文本增量类型 |
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;
// ...
};
附录 B:配置与环境变量
本附录详细说明 Claude Code 的配置系统和环境变量。
B.1 环境变量详解
B.1.1 API 认证配置
| 变量 | 必填 | 说明 |
|---|---|---|
ANTHROPIC_API_KEY | 二选一 | API Key,通过 x-api-key 请求头发送 |
ANTHROPIC_AUTH_TOKEN | 二选一 | Auth Token,通过 Authorization: Bearer 请求头发送 |
B.1.2 API 端点配置
| 变量 | 必填 | 说明 |
|---|---|---|
ANTHROPIC_BASE_URL | 否 | 自定义 API 端点,默认为 Anthropic 官方 API |
B.1.3 模型配置
| 变量 | 必填 | 说明 |
|---|---|---|
ANTHROPIC_MODEL | 否 | 默认使用的模型名称 |
ANTHROPIC_DEFAULT_SONNET_MODEL | 否 | Sonnet 级别模型映射 |
ANTHROPIC_DEFAULT_HAIKU_MODEL | 否 | Haiku 级别模型映射 |
ANTHROPIC_DEFAULT_OPUS_MODEL | 否 | Opus 级别模型映射 |
// 模型配置示例(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_MS | 否 | API 请求超时时间(毫秒),默认 600000(10分钟) |
B.1.5 遥测与网络配置
| 变量 | 必填 | 说明 |
|---|---|---|
DISABLE_TELEMETRY | 否 | 设为 1 禁用遥测数据收集 |
CLAUDE_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 可以通过多种方式配置:
- 内置 Skills: 通过
BundledSkillDefinition注册 - 项目 Skills: 在
.claude/skills/目录中定义 - 插件 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 | 文件变更时 |
WorktreeCreate | Worktree 创建时 |
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 | 显示帮助信息 |
附录 C:修复记录
本附录记录 Claude Code 泄露源码修复过程中的问题分析和解决方案。
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:
modifiers-napi是一个 native 包,用于检测修饰键状态- 泄露源码缺少这个 native 包
isModifierPressed()函数调用时抛出异常- 异常中断了
handleEnter函数的执行 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) // 正常执行
}
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 包缺失的情况,采用以下容错策略:
- Try-Catch 包装: 在调用处捕获异常
- 默认值兜底: 异常时使用安全的默认值
- 功能降级: 不影响核心功能运行
C.3.3 启动路由修复
入口脚本路由逻辑的修复原则:
- 无参数启动 -> TUI 完整入口
-p/--print参数 -> 无头模式CLAUDE_CODE_FORCE_RECOVERY_CLI=1-> Recovery CLI
C.4 降级模式说明
如果完整 TUI 出现问题,可以使用简化版 readline 交互模式:
CLAUDE_CODE_FORCE_RECOVERY_CLI=1 ./bin/claude-haha
降级模式特点:
- 简化的命令行交互
- 基本的问答功能
- 不依赖 Ink 终端渲染引擎
- 适合问题排查和调试
Windows 用户推荐使用以下方式启动 TUI:
bun --env-file=.env ./src/entrypoints/cli.tsx
</div>
</div>
这种方式绕过 shell 脚本,直接调用 Bun 运行入口文件。
C.5 Windows 平台注意事项
Windows 平台运行 Claude Code Haha 需要注意:
- 依赖 Git Bash: 启动脚本
bin/claude-haha是 bash 脚本,需要 Git for Windows - 推荐方式: 直接使用 PowerShell/cmd 调用 Bun
- 不可用功能: 语音输入、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