Book 第一章:先把 Claude Code 看成一套运行时
第一部分:导言

第一章:先把 Claude Code 看成一套运行时

从目录规模、模块分层和总体判断出发,把这份源码先看成一套长生命周期 agent runtime。

1. 这份快照到底是什么

相关深拆:

这不是一个完整 monorepo,更像是一份被导出的 src/ 运行时代码快照。当前仓库里能看到的核心事实是:

  • 根目录基本只有 README.md、研究说明文和 src/
  • 没有常见的 package.jsonpnpm-workspace.yaml、测试配置、构建脚本
  • 代码明显以 Bun 为运行时,CLI 层使用 Commander,终端 UI 使用 React + Ink

所以这次分析的边界也很明确:

  • 我们能较完整地分析“运行时设计”
  • 但暂时看不到“完整构建链、发布脚本、测试组织方式”

2. 我对它的总体判断

如果只用一句话概括,这个项目本质上不是“一个会调大模型的 CLI”,而是一个:

以对话回合引擎为核心、以工具系统为执行面、以 MCP / 插件 / 技能为扩展面、以 REPL / Headless / Bridge 为不同宿主外壳的 agent runtime。

也就是说,它真正的核心不是 UI,而是下面这几个东西:

  1. 启动编排和模式分流
  2. Query / Turn Loop
  3. Tool 抽象与工具调度
  4. 权限与审批模型
  5. 扩展系统:MCP、插件、技能
  6. 会话状态、持久化和上下文压缩

3. 从目录规模反推重心

粗看文件数量,重点非常明显:

目录文件数说明
src/utils564基础设施极重,说明工程复杂度主要在运行时细节
src/components389REPL UI 很重,不只是简单命令行输出
src/commands207Slash command 很多,CLI 表层很丰富
src/tools184真正的能力面在工具系统
src/services130网络、MCP、分析、压缩、插件等都在这里
src/hooks104交互态逻辑大量依赖 React hook 组织
src/ink96终端渲染不是薄封装,而是深度定制

这意味着 Claude Code 的设计重点不是“单次 prompt -> response”,而是“长生命周期会话运行时”。

3.1 这轮新补的横切系统

如果目标是继续往 C# 版抽象推进,这一轮最值得补的七块横切系统已经补齐:

  1. AppState 与 UI/runtime 边界
  2. 认证 / Token / 登录运行时
  3. Telemetry / analytics / attribution
  4. Memory 全景
  5. 插件系统 / Marketplace / 扩展治理
  6. Hooks 运行时总览
  7. 模型 / Provider / Capability 路由

更完整的继续挖掘地图见:

4. 总体架构图

flowchart TB
    User["用户 / IDE / 远端控制"] --> Entry["src/entrypoints/cli.tsx"]
    Entry --> Main["src/main.tsx<br/>启动编排、参数解析、模式分流"]

    Main --> Init["src/entrypoints/init.ts<br/>配置、环境变量、代理、遥测、基础初始化"]
    Main --> Registry["src/commands.ts + src/tools.ts<br/>内建命令与工具注册"]
    Main --> Ext["技能 / 插件 / MCP 配置装载"]

    Ext --> Skills["src/skills/loadSkillsDir.ts<br/>技能目录与 Markdown Skill"]
    Ext --> Plugins["src/utils/plugins/*<br/>插件发现、加载、命令/技能注入"]
    Ext --> MCP["src/services/mcp/*<br/>MCP 连接、工具、命令、资源"]

    Main --> ReplPath["交互式路径"]
    Main --> HeadlessPath["非交互 / SDK 路径"]
    Main --> BridgePath["远程桥接路径"]

    ReplPath --> ReplLauncher["src/replLauncher.tsx"]
    ReplLauncher --> App["src/components/App.tsx"]
    App --> REPL["src/screens/REPL.tsx"]

    HeadlessPath --> Headless["src/cli/print.ts"]
    Headless --> QueryEngine["src/QueryEngine.ts"]

    BridgePath --> Bridge["src/bridge/bridgeMain.ts<br/>远程会话桥接与 session 管理"]

    REPL --> Query["src/query.ts<br/>统一回合引擎"]
    QueryEngine --> Query
    Bridge --> Query

    Query --> PromptCtx["src/context.ts + prompts<br/>系统/用户上下文装配"]
    Query --> Model["src/services/api/claude.js<br/>模型流式调用"]
    Model --> ToolExec["src/services/tools/*<br/>工具调度、并发、流式执行"]

    ToolExec --> Builtin["内建工具<br/>Bash / Read / Edit / Agent / Skill / Todo ..."]
    ToolExec --> McpTool["MCPTool / MCP Resource Tools"]
    ToolExec --> Permission["权限系统<br/>ToolPermissionContext + canUseTool"]
    ToolExec --> Compact["上下文压缩<br/>compact / microcompact / snip"]
    ToolExec --> State["AppState / SessionStorage / FileHistory"]

    State --> REPL

5. 主链路怎么跑

5.1 启动层

src/entrypoints/cli.tsx 负责很薄的一层入口分流:

  • --version 这类 fast path 做极限短路
  • remote-controldaemonbackground sessionsenvironment-runner 这类特殊模式提前分发
  • 正常情况再把控制权交给 src/main.tsx

src/main.tsx 才是真正的总编排器。它做的事情非常多:

  • 初始化配置、遥测、代理、认证、远端设置
  • 解析命令行和权限模式
  • 预热 MCP 配置、插件、技能、模型信息
  • 生成命令列表和工具列表
  • 决定进入哪条运行路径:
    • 交互式 REPL
    • headless print / SDK
    • remote bridge

这说明它的设计不是“程序入口”,而是一个 runtime bootstrapper

5.2 交互式路径

交互式模式大致是:

main.tsx -> replLauncher.tsx -> components/App.tsx -> screens/REPL.tsx

这里的 REPL.tsx 很重,它不是一个“输入框组件”,而是整套会话壳:

  • 输入与快捷键
  • 消息流显示
  • 命令队列
  • IDE 集成
  • 后台任务
  • 队友 / swarm 视图
  • 权限弹窗
  • MCP 连接状态
  • 各种通知和状态条

但它并不直接实现模型循环,最后还是调用 src/query.ts

5.3 非交互路径

非交互模式由 src/cli/print.ts + src/QueryEngine.ts 负责。

这里可以把 QueryEngine 理解成一个更容易复用的“会话内核包装器”:

  • 持有当前消息历史
  • 组装 ToolUseContext
  • 管理一次次 submitMessage()
  • 最终仍然调用 query()

这意味着作者已经在做一件很重要的抽象工作:

把 UI 会话壳 和 对话回合引擎 拆开。

这对 C# 迁移非常关键,因为你完全可以先迁内核,再换宿主。

6. query.ts 才是最核心的 runtime

如果只挑一个文件当 Claude Code 的“心脏”,我会选 src/query.ts

它做的是一个完整的 agent turn loop:

  1. 组装消息、system prompt、上下文
  2. 发起流式模型请求
  3. 解析 assistant 消息里的 tool use
  4. 按并发规则执行工具
  5. 把 tool result 回灌到消息流
  6. 必要时继续下一轮 assistant
  7. 处理中途打断、权限拒绝、fallback、压缩、预算限制

也就是说,这里并不是“请求一次模型”,而是在跑一个 状态机

6.1 回合内部结构图

flowchart LR
    A["当前消息历史"] --> B["组装 System Prompt / User Context"]
    B --> C["流式调用模型"]
    C --> D{"assistant 是否发出 tool_use"}
    D -- 否 --> E["产出最终 assistant 响应"]
    D -- 是 --> F["services/tools/toolOrchestration.ts"]
    F --> G["串行 / 并行执行工具"]
    G --> H["生成 tool_result 消息"]
    H --> I["写回消息历史"]
    I --> J{"是否继续下一轮"}
    J -- 是 --> C
    J -- 否 --> E

    C --> K["compact / microcompact / fallback / token budget"]
    G --> L["permission / hook / telemetry / session storage"]

7. Tool 系统是第二核心

src/Tool.ts 定义了 Claude Code 的工具抽象,里面最重要的不是“execute 一个函数”,而是整套上下文对象:

  • 当前命令集合
  • 当前工具池
  • 当前 MCP client / resources
  • 当前 AppState
  • 读文件缓存
  • 中断控制器
  • 权限处理
  • UI 通知、弹窗、进度状态
  • 文件历史、归因、内容替换状态

这说明 Claude Code 的 Tool 不是“纯函数工具”,而是 运行时参与者

src/tools.ts 则负责把所有工具拼成工具池:

  • 内建工具
  • REPL 专属工具
  • feature flag 控制的工具
  • MCP 资源工具
  • 协调模式 / worktree / simple mode 下的不同工具裁剪

再往下,src/services/tools/toolOrchestration.tstoolExecution.ts 负责:

  • 判断哪些工具能并发
  • 串行或并行调度
  • 包装权限检查
  • 追踪 telemetry / hook / tracing
  • 生成 tool_result 消息

这部分在 C# 里不应该写成一堆 switch case,而应该单独做成:

  • ITool
  • ToolRegistry
  • ToolExecutionContext
  • ToolScheduler
  • PermissionEvaluator

8. 扩展系统是“平台化”的关键

8.1 MCP

src/services/mcp/client.tsuseManageMCPConnections.ts 说明 MCP 在 Claude Code 里不是补充插件,而是一级公民:

  • 会连接多个不同 transport 的 MCP server
  • 把 server 暴露成 tools / commands / resources
  • 动态写回 AppState.mcp
  • 允许 REPL 会话中途增量接入

这很像一个外部能力总线。

8.2 技能

src/skills/loadSkillsDir.ts 表明技能本质上是 Markdown 驱动的命令/提示模板系统

  • 从目录读 SKILL.md
  • 解析 frontmatter
  • 提取 description / when_to_use / allowed-tools / model / effort
  • 最终编译成 Command

换句话说,技能不是“代码插件”,而是“可编排的高层 agent 能力描述”。

8.3 插件

src/utils/plugins/pluginLoader.tsloadPluginCommands.ts 说明插件系统更像“扩展包”:

  • 有 manifest
  • 能提供 commands / agents / hooks
  • 能从 marketplace 或 git 源装载
  • 有版本化缓存和启停管理

所以插件和技能不是一层东西:

  • 技能更像 prompt-asset
  • 插件更像 capability bundle

9. 状态管理并不轻

src/state/AppStateStore.ts 里的 AppState 很大,已经不是“UI 状态”了,而是整个会话 runtime 的共享状态:

  • 权限模式
  • 当前模型和设置
  • REPL bridge 状态
  • MCP clients / tools / commands / resources
  • 插件状态
  • 任务和子 agent
  • 通知、elicitation、thinking、session hooks
  • 文件历史、归因、todo、远端会话等

这说明 Claude Code 的 React 层不只是视图,而是承担了很大一部分 runtime orchestration shell

如果未来你做 C# 版,我不建议完全照搬这个超大状态对象;更合理的是拆成几个有边界的 store:

  • SessionState
  • ToolRuntimeState
  • ExtensionState
  • UiState
  • TaskState

10. 远程桥接不是附属功能

src/bridge/bridgeMain.ts 这套代码量已经足够说明,bridge 不是一个小插件,而是独立子系统:

  • 会拉起或回收 session
  • 维护心跳和 backoff
  • 处理远端环境与本地工作目录的映射
  • 管理 token 刷新、work secret、session id 兼容

所以 Claude Code 不是单机 CLI,它在设计上从一开始就给“远端执行 / IDE 附着 / 会话桥接”留了正式位置。

11. 对 C# 版本最有价值的拆分方式

如果目标是“从设计思路出发,做 C# 版”,我建议先不要直接翻译 TypeScript 文件,而是先抽成下面这几个边界:

Claude Code 现有模块C# 建议落点
entrypoints/cli.tsx + main.tsxProgram.cs + CliBootstrapper
query.ts + QueryEngine.tsConversationRuntime + TurnLoop + ConversationSession
Tool.ts + tools.ts + services/tools/*ITool + ToolRegistry + ToolExecutionContext + ToolScheduler
state/AppState*SessionStore / RuntimeState / UiState 分层
services/mcp/*McpConnectionManager + McpToolAdapter + McpResourceCatalog
skills/loadSkillsDir.tsMarkdownSkillLoader
utils/plugins/*PluginManager + PluginCatalog + PluginPackageResolver
bridge/*RemoteBridgeHost + SessionSupervisor
context.ts + promptsPromptContextBuilder
compact/*ConversationCompactor

最重要的一点是:

先把“统一回合引擎 + 工具调度 + 扩展接入”这三个核心抽出来,再决定 UI 是终端、IDE 还是 Web。

12. 我觉得最值得继续深挖的三个点

后面如果继续往下拆,我建议按这个顺序:

  1. query.ts:把完整 turn loop 状态机画出来
  2. Tool.ts + services/tools/*:把工具执行协议和权限模型拆出来
  3. services/mcp/* + skills/* + plugins/*:把扩展系统的统一抽象找出来

这是最接近 C# 可落地架构骨架的三层。