这篇文章不是“按目录从上往下念一遍文件名”,而是把 Claude Code 当成一套生产级 Agent Runtime 来拆:它如何启动、如何构造上下文、如何驱动查询循环、如何执行工具、如何治理权限、如何承载异步任务与远程运行时,以及为什么这些设计能在复杂产品里长期活下来。

为什么这份源码值得看: 这不是一个普通 CLI 项目的实现细节,而是一套成熟 AI Agent 产品的运行时样本。源码快照只是入口,真正有价值的是它背后的工程取舍:哪些能力被做成统一抽象,哪些风险被放进治理层,哪些复杂度被明确地收口了。
先说结论: Claude Code 的本体并不是一个终端聊天 UI,而是一套以 query.ts 为心脏、以 Tool/Task/Command 为统一抽象、以权限系统和扩展总线为边界控制的 Agent Runtime。
1902src/ 下源码文件数量
512,664TypeScript / TSX 总行数
564utils 文件数,说明基础设施极重
389components 文件数,终端 UI 也非常复杂
207commands 文件数,命令面很宽
184tools 文件数,工具系统是第一等公民
130services 文件数,协议与运行时服务层很厚
104hooks 文件数,UI 与策略被大量 hook 化
重要前提: 这个仓库不是一份完全“原汁原味”的 TSX 源码快照。像 src/components/App.tsxsrc/state/AppState.tsxsrc/screens/REPL.tsxsrc/hooks/useCanUseTool.tsx 这类文件,已经出现了 react/compiler-runtime 和 sourcemap 内联痕迹。也就是说,这份仓库更接近“可阅读的构建产物快照”而不是最初作者提交的源码。因此本文会以运行时结构和调用关系为主,不会假装自己能还原所有原始编码细节。

一、先给结论:这到底是一个什么系统

如果只看 UI,你会以为它是一个终端版聊天应用;如果只看 tools/,你会以为它是一个“给大模型挂了很多工具”的壳;如果只看 bridge/remote/,又会以为它是一个远程控制产品。真正准确的说法是:

Claude Code 是一套面向 Agent 的运行时平台。UI、CLI、远程控制、MCP、插件、技能、异步任务,都只是这个运行时的不同入口和不同扩展面。
Claude Code 高层架构图
图 1:高层架构图。真正的主线是“入口分流 → 运行时装配 → 查询循环 → 工具/任务/权限 → 扩展总线 → 远程与持久化”。

从职责上看,这个系统可以拆成八层:

入口层
负责命令分流、懒加载和冷启动优化,核心文件是 entrypoints/cli.tsx
组合根
负责把配置、认证、策略、工具、命令、技能、插件、MCP、LSP 拼成一个完整运行时,核心文件是 main.tsx
交互层
终端 UI 不是被动显示,而是一个协调器,核心是 screens/REPL.tsx
查询层
每一轮 assistant 行为都由 query.ts 这个状态机驱动。
工具层
工具不是简单函数,而是带 schema、权限、并发语义、进度语义的运行时实体。
治理层
权限、任务、代理、预算、压缩都属于治理层,而不是某个单点功能。
扩展层
MCP、插件、技能、命令、Agent 定义都被汇总到统一抽象上。
远程层
Bridge、远程查看器、直连会话、本地/远程 Agent,本质上都在复用同一套 Query Runtime。

这一点直接解释了为什么这个仓库会出现两个很明显的“偏置”:第一,utils/ 极多,因为这套运行时要处理大量杂事;第二,tools/commands/services/ 很厚,因为产品能力几乎都在这些边界里沉淀。

二、启动链路:先短路,再装配,再进入交互

Claude Code 的入口设计非常成熟。很多 CLI 程序虽然有“主入口”,但实际上会把大量模块在顶层一股脑 import 进来,结果是任何命令都要付出完整冷启动成本。Claude Code 明显不是这么做的。

Claude Code 启动链路图
图 2:启动链路。重点不是“主函数多短”,而是能否把不同命令路径在足够早的阶段分流出去。

src/entrypoints/cli.tsx 的职责可以概括成一句话:在尽可能少加载模块的前提下,把不同运行模式分流到对应子系统。 它做了三件很关键的事。

  1. 把纯信息类命令做成零或极少依赖的快路径,比如 --version
  2. 把长驻型命令做成独立支路,比如 bridgedaemonps/logs/attach/kill
  3. 只有在需要完整会话时,才进入 init.tsmain.tsx
async function main(): Promise<void> {
  const args = process.argv.slice(2)

  if (args.length === 1 && (args[0] === '--version' || args[0] === '-v')) {
    console.log(`${MACRO.VERSION} (Claude Code)`)
    return
  }

  const { profileCheckpoint } = await import('../utils/startupProfiler.js')
  profileCheckpoint('cli_entry')

  if (args[0] === 'remote-control' || args[0] === 'bridge') {
    const { bridgeMain } = await import('../bridge/bridgeMain.js')
    await bridgeMain(args.slice(1))
    return
  }
}

这里最值得学的,不是“if 写得多”,而是把“命令分发”和“运行时装配”明确拆开了。这样一来,冷启动优化有了真正的抓手。

进入 src/entrypoints/init.ts 之后,系统开始做真正的环境准备。这个文件的重要性在于,它不是简单“初始化配置”,而是在建立一条信任前初始化信任后初始化之间的边界:

  • 先加载安全环境变量、关闭清理器、探测 IDE / 仓库 / 平台上下文。
  • 并行启动远端策略、组织限制、代理证书、网络代理、OAuth 信息缓存。
  • 为后面的完整会话准备一个“可信但尽量还没太重”的基础环境。

然后是整个系统的组合根:src/main.tsx。这个文件的重要结论不是“代码很多”,而是它承担了所有运行时对象的拼装工作。尤其有三个细节值得注意:

  1. 它在 very early 阶段就触发 startMdmRawRead()startKeychainPrefetch() 这样的并行预取,说明作者对冷启动路径做过实打实优化。
  2. 它不是先画 UI 再慢慢加载功能,而是先把模型、工具、命令、插件、技能、权限模式、策略限制这些运行时能力准备好。
  3. 它大量使用特性开关和动态导入,所以这套代码库不是一个固定产品,而是一套支持多发行形态的可裁剪运行时。

最后,interactiveHelpers.tsxreplLauncher.tsx 把信任与交互兜起来。前者负责 setup screens、trust dialog、MCP 审批、API key 审批等交互边界;后者则非常克制,只负责在真正需要 REPL 时再懒加载 AppREPL。这说明作者很清楚:对一个终端 Agent 来说,真正昂贵的不是画 UI,而是把完整运行时装进进程。

三、状态与上下文:不是 Redux,而是轻量 store 加显式上下文装配

这套系统的状态管理很有意思。它没有上来就用 Redux 或 Zustand,而是自己实现了一个极轻的外部 store,再用 React 做桥接。

src/state/store.ts 基本就是一个最小实现:getStatesetStatesubscribe。真正重量级的不是 store 本身,而是 AppState 的定义。

src/state/AppStateStore.ts 几乎相当于系统“控制面”的数据模型,里面包含:

  • 用户设置、当前模型、权限上下文、视图状态。
  • 本地任务、远程会话状态、Bridge 状态、后台任务数。
  • MCP 客户端、MCP 工具、MCP 资源、插件状态、Agent 定义。
  • 文件历史、Todo、推断状态、通知、Prompt Suggestion 等等。

也就是说,Claude Code 的状态设计不是“页面状态”意义上的状态,而是整台 Agent Runtime 的实时控制面。这和普通前端应用完全不是一个量级。

export type AppState = DeepImmutable<{
  settings: SettingsJson
  mainLoopModel: ModelSetting
  toolPermissionContext: ToolPermissionContext
  remoteSessionUrl: string | undefined
  remoteConnectionStatus: 'connecting' | 'connected' | 'reconnecting' | 'disconnected'
  replBridgeEnabled: boolean
}> & {
  tasks: { [taskId: string]: TaskState }
  mcp: { clients: MCPServerConnection[]; tools: Tool[]; resources: Record<string, ServerResource[]> }
  plugins: { enabled: LoadedPlugin[]; disabled: LoadedPlugin[]; needsRefresh: boolean }
}

src/state/AppState.tsx 负责把这个 store 桥接进 React。它做得比较克制:通过 useSyncExternalStore 暴露 selector,不允许随便把整个状态对象拿出来用,也防止嵌套 Provider。这说明作者很清楚,真正的风险不是“状态多”,而是“状态对象被到处随手读取”。

与状态并行的,是上下文系统。src/context.tssrc/utils/queryContext.ts 负责把系统上下文与用户上下文装配出来:

  • systemContext 主要负责 Git 快照与运行环境信息。
  • userContext 主要负责 CLAUDE.md、记忆文件等用户持久上下文。
  • constants/prompts.ts 则负责把模块化的系统提示词片段拼起来。

这个设计的关键点是:上下文不是在 query loop 内临时拼字符串,而是被单独抽象成一套可缓存、可注入、可替换的构造过程。 这会大幅降低循环内部的复杂度,也避免依赖环。

四、查询引擎:真正的核心是可恢复的多轮状态机

如果让我只挑一个文件来代表 Claude Code 的“灵魂”,我会选 src/query.ts。因为这里真正定义了系统“怎么像一个 Agent 一样工作”。

Claude Code 查询与工具循环图
图 3:查询循环。用户输入不是直接发给模型,而是先进入一套预处理、上下文组装、流式采样、工具调度、预算控制和压缩恢复的状态机。

这一层至少做了七件事:

  1. 把输入消息、系统提示词、用户上下文、系统上下文拼成 API 请求。
  2. 流式接收 assistant 输出,并在 tool_use block 出现时触发工具执行。
  3. 决定哪些工具可以并发、哪些工具必须独占。
  4. 跟踪 token budget、max output token 恢复、stop hooks。
  5. 在上下文超限时触发自动压缩、微压缩或其他恢复路径。
  6. 把工具结果和压缩边界重新注回消息流。
  7. 最终把 assistant 输出与 usage 信息回写到会话状态。

这意味着 query.ts 本质上不是一个“调用模型 API 的函数”,而是一个带副作用、带恢复逻辑、带预算约束的回合状态机

其配套的 src/QueryEngine.ts 则把这套查询机制封装成 headless / SDK 可复用的对话引擎。它会保存:

  • 累计消息列表。
  • 总 usage。
  • 文件缓存。
  • 权限拒绝记录。
  • 跨回合的 abort controller。

这一步很关键,因为它说明 Claude Code 不是把“终端 UI 逻辑”和“模型回合逻辑”绑死了。UI 只是一个消费者,QueryEngine 才是可复用的内核。

查询循环的前门是 src/utils/handlePromptSubmit.tssrc/utils/processUserInput/processUserInput.ts。这两层的职责是把“用户原始输入”编译成“模型真正看到的消息”。这里的实现思路很像编译器前端:

  • 先识别 slash command、本地 JSX 命令、桥接消息、元消息。
  • 再处理粘贴内容、图片、附件、IDE 选区、hook 附加上下文。
  • 最后决定这次输入究竟是触发模型查询,还是只做本地控制流。

也就是说,输入在进入模型之前,已经被完整归一化过了。这正是成熟 Agent Runtime 与“把文本直接发给 LLM”的根本区别。

五、工具系统:不是函数库,而是一套协议化运行时

Claude Code 最强的部分之一,就是工具系统设计得足够“像运行时”而不是“像 util 函数”。

核心契约在 src/Tool.ts。这个文件定义的不是一个简单的 call() 接口,而是一整套工具元数据:

  • 输入输出 schema。
  • 用户可见名称与描述。
  • 权限检查钩子。
  • 只读属性、并发安全属性。
  • 中断行为、进度事件、结果裁剪策略。

这意味着任何工具要进入系统,都必须先回答:它如何被模型理解、它是否可并发、它是否可能危险、它如何报告进度、它的结果如何进入 transcript。这样的契约足够厚,系统才可能稳定地不断扩展。

src/tools.ts 则是全局工具注册表。这个文件透露出两个很重要的架构意图:

  1. 工具集合不是静态的,会根据 feature flag、发行形态、环境能力动态变化。
  2. 工具的来源非常杂,但最终必须汇总到 getAllBaseTools() 这一处统一真相源。
export function getAllBaseTools(): Tools {
  return [
    AgentTool,
    TaskOutputTool,
    BashTool,
    ...(hasEmbeddedSearchTools() ? [] : [GlobTool, GrepTool]),
    FileReadTool,
    FileEditTool,
    FileWriteTool,
    WebFetchTool,
    WebSearchTool,
    SkillTool,
    EnterPlanModeTool,
    ...(WebBrowserTool ? [WebBrowserTool] : []),
  ]
}

这个注册表的“厚”是必要的。因为你只要支持 Agent、MCP、插件、工作流、远程触发、计划模式、Web 浏览器、Shell、文件编辑,这些能力就一定会在同一个地方相撞。Claude Code 没有回避这个复杂度,而是正面把它收口到了注册层。

更精彩的是 src/services/tools/StreamingToolExecutor.ts。这个类说明 Claude Code 的工具不是“等模型整段输出完再执行”,而是一边采样一边执行工具。它同时满足三件看起来互相冲突的事:

  • 允许并发安全工具并行执行。
  • 要求独占型工具串行执行。
  • 即便并发执行,也要按原始工具出现顺序回放结果。
private canExecuteTool(isConcurrencySafe: boolean): boolean {
  const executingTools = this.tools.filter(t => t.status === 'executing')
  return (
    executingTools.length === 0 ||
    (isConcurrencySafe && executingTools.every(t => t.isConcurrencySafe))
  )
}

这段逻辑背后的思想很重要:并发不是全局开关,而是工具级别的语义。 一旦某个工具声明自己需要独占,它就会阻塞队列;如果全是并发安全工具,则允许流水线化执行。这个设计比“固定串行”或“无脑并行”都高明。

再往下一层,src/services/tools/toolOrchestration.ts 负责把工具调用分成并发安全批和串行批,并且在并发工具执行后,再统一应用 context modifier,避免上下文被并发写乱。这说明作者不仅考虑了执行性能,也考虑了状态一致性。

六、权限、任务与 Agent:Claude Code 的治理中台

很多 Agent 产品的失败点不在模型效果,而在副作用治理。Claude Code 显然把这件事当成了独立子系统。

src/hooks/useCanUseTool.tsx 是权限入口。它不是单纯“给用户弹个确认框”,而是把多种决策源汇总到一起:

  • 静态配置规则。
  • 自动分类器决策。
  • 协调器 / swarm worker 的上层决策。
  • 交互式确认对话框。
  • 拒绝后的通知、埋点与原因记录。

这说明 Claude Code 的权限设计不是 UI 行为,而是运行时行为。只有这样,权限系统才能在终端、远程、队列任务、群体代理之间复用。

与权限并列的,是任务系统。src/Task.ts 定义任务抽象,src/tasks.ts 则像工具注册表一样,维护所有任务类型:

export function getAllTasks(): Task[] {
  const tasks: Task[] = [
    LocalShellTask,
    LocalAgentTask,
    RemoteAgentTask,
    DreamTask,
  ]
  if (LocalWorkflowTask) tasks.push(LocalWorkflowTask)
  if (MonitorMcpTask) tasks.push(MonitorMcpTask)
  return tasks
}

这套设计有一个很漂亮的地方:任务系统是 Agent Runtime 的异步基座。 也就是说,后台 shell、后台 agent、远程 agent、监控任务并不是不同功能,它们只是在同一任务框架里的不同 task type。

这直接为 tools/AgentTool/AgentTool.tsx 铺平了路。AgentTool 不是简单“再开一个 Claude 实例”,而是把下面这些能力一次性绑起来了:

  • Agent 类型选择与模型覆盖。
  • 工作目录覆盖与 Git worktree 隔离。
  • 后台运行与前台接管。
  • 远程 agent 与本地 agent 的分流。
  • 多 Agent / teammate / swarm 模式。
  • Agent 元数据写入与进度跟踪。

换句话说,Agent 在 Claude Code 里不是 prompt trick,而是有完整生命周期管理的任务实体。这是它跟大量“多开几个子对话”产品的本质差异。

Agent 定义本身则由 tools/AgentTool/loadAgentsDir.ts 加载。这个设计也很关键:Agent 不是硬编码在某个 switch 里,而是可以被 Markdown / frontmatter / JSON 声明。声明内容还包括工具白名单、禁用工具、需要的 MCP、记忆范围、执行强度、隔离模式、权限模式等。也就是说,Claude Code 里的 Agent 更接近“声明式运行策略”。

七、MCP、插件、技能与命令:扩展很多,但抽象被收得很稳

如果只看目录数量,你可能会觉得扩展系统会乱成一团。实际情况恰恰相反:Claude Code 的扩展点很多,但抽象边界 surprisingly 稳定。

Claude Code 扩展生态图
图 4:扩展生态图。不同扩展面最后都要接入统一运行时,而不是各玩各的。

1. 命令系统

src/commands.ts 是 slash command 的全局注册表。它的特点是:

  • 静态命令与 feature-gated 命令混合存在。
  • 命令既可以是纯本地命令,也可以是 prompt 命令。
  • 技能、插件命令、MCP prompt command 也能被并入统一命令表。

这意味着“命令”在 Claude Code 里其实是一个统一的控制入口,而不是 UI 语法糖。

2. 技能系统

src/skills/loadSkillsDir.ts 把技能看成一类结构化提示资产。它支持从用户目录、项目目录、策略目录、插件目录、MCP 目录加载 Markdown 技能文件,并解析 frontmatter 中的工具限制、hook、参数、模型、effort、执行环境等。这个设计的价值在于:技能不是死文本,而是可带运行时约束的 prompt-programmed unit。

3. 插件系统

插件分两部分:发现与安装。

  • plugins/builtinPlugins.ts 描述系统自带插件,可以注入技能、hook、MCP server。
  • services/plugins/PluginInstallationManager.ts 则负责后台 reconcile marketplace,并把安装状态写回 AppState。

这个后台安装管理器尤其说明架构成熟:插件安装不是阻塞启动的同步步骤,而是在 REPL 已经起来以后后台进行;安装完成后再刷新插件缓存或提示用户 reload。这里明显体现出“启动时延优先”的设计哲学。

4. MCP 系统

MCP 是整套扩展体系里最重的一块,核心在 services/mcp/client.ts。这个文件本质上做的是协议适配层:

  • 支持 stdioSSEstreamable HTTPWebSocket 等传输。
  • 支持认证、OAuth 刷新、会话过期恢复。
  • 把 MCP 的 tool / prompt / resource 统一转换成 Claude Code 自己的工具和资源抽象。
  • 处理结果裁剪、二进制持久化、鉴权错误与输出持久化。

要点不在“支持很多协议”,而在于 MCP 最终并没有旁路掉 Claude Code 的运行时,而是被重新映射回同一套 Tool/Resource 接口。这保证了权限、消息流、UI、转录、埋点、预算控制都还能继续工作。

八、远程、Bridge 与直连:其实是在复用同一套运行时

这个仓库最容易被低估的部分,是远程能力。很多人会把远程支持当成“多写了一套 server/client”,但从这份源码来看,作者显然在做更大的一件事:把本地 Agent Runtime 平滑搬到远端环境里运行

bridge/bridgeMain.ts 是核心,它负责:

  • 环境注册。
  • 会话孵化与多会话调度。
  • 工作轮询、心跳、重连和超时回收。
  • 需要时创建和清理 worktree。
  • 在 token 过期、网络失败、系统休眠等情况下做恢复。

从实现细节看,这不是一次性任务,而是一个长驻型 control loop。也就是说,Bridge 更像一个“远程环境运行器”,而不是简单的“把 stdout 发到网页”。

remote/RemoteSessionManager.ts 则处理 viewer 侧的会话同步:通过 WebSocket 收事件、通过 HTTP 发请求、桥接权限确认、管理重连状态。它说明“远程查看器”看到的并不是一个 fake transcript,而是真实运行时的事件流镜像。

至于 server/createDirectConnectSession.ts,它提供了另一条入口:直接创建可连接的 session。于是整个远程层其实有三种模式:

  1. Bridge 模式:把本机当成可调度环境。
  2. Remote viewer 模式:连接到远端会话看事件流。
  3. Direct connect 模式:直接创建并接入会话。

这三者看似不同,但底层都回到同一套 Query Runtime。这也是我一直强调的重点:Claude Code 的核心不是某种前端形态,而是底层运行时的一致性。

九、长期记忆与上下文压缩:所谓“大上下文”本质上是治理策略

Agent 产品一旦进入长会话,就一定会遇到两个问题:

  1. 哪些信息要跨会话保留?
  2. 当前会话太长时,什么信息该保、什么信息该丢?

Claude Code 对这两件事都给了工程化回答。

1. Memory 目录不是“隐式记忆”,而是显式文件系统

src/memdir/memdir.ts 定义了持久记忆系统。它的设计非常值得学习:

  • 记忆是文件,而不是隐式向量库。
  • MEMORY.md 是索引,不是内容仓库。
  • 有明确的类型体系:用户、反馈、项目、参考。
  • 有严格的入口文件行数和字节上限,防止索引膨胀。

这种设计非常“工程师友好”,因为记忆变成了可审计、可版本化、可手工修正的资产,而不是产品黑箱。

2. 压缩不是“简单摘要”,而是带恢复语义的上下文管理

services/compact/compact.ts 的实现揭示了一个现实:所谓“超长上下文”并不是靠模型 magically 无限记住,而是靠系统在关键节点主动做上下文治理。这个文件至少做了这些事:

  • 压缩前剥离图片和文档,减少无效 token 消耗。
  • 剥离那些下一轮本来就会重新注入的附件,避免摘要污染。
  • 为重要文件、技能、工具发现结果保留预算。
  • 压缩后重新注入关键 attachment 和必要文件摘要。

所以 Claude Code 真正的设计思想不是“我有无限上下文”,而是“我知道什么该进入长期记忆,什么该进入当前回合上下文,什么该在超限后被浓缩成可恢复摘要”。这是一个非常重要的认知差别。

十、那些不在主干里、却暴露成熟度的外围模块

如果只盯着 query.tstools.tsAgentTool 这些主干文件,很容易漏掉一件事:真正能把产品做成熟的,往往是那些看起来“不那么核心”的外围系统。Claude Code 在这部分也很完整。

1. Hook 系统:把产品行为开放成生命周期,而不是硬编码分支

src/schemas/hooks.ts 和 SDK 的 HOOK_EVENTS 暴露出一个很完整的 Hook 面。它不是只有简单的前置后置两个事件,而是覆盖了工具、权限、压缩、任务、工作树、配置变更等多个生命周期点。

export const HOOK_EVENTS = [
  'PreToolUse',
  'PostToolUse',
  'UserPromptSubmit',
  'SessionStart',
  'PermissionRequest',
  'SubagentStart',
  'PreCompact',
  'PostCompact',
  'FileChanged',
]

更重要的是,Hook 本身也不是一种实现方式,而是四种:commandprompthttpagent。这意味着平台作者没有把“扩展性”限制成 shell 脚本,而是明确允许用户在生命周期里注入本地命令、额外模型判断、HTTP 回调,甚至独立 Agent 验证器。对于一个 Agent 平台来说,这种设计比“不断往核心里塞新功能”更可持续。

2. 快捷键系统:终端 UI 不是玩具,而是一套上下文化输入系统

src/keybindings/schema.tssrc/keybindings/defaultBindings.ts 显示,这套终端 UI 的输入系统已经非常产品化了。它支持:

  • 多上下文绑定,比如 GlobalChatConfirmationTranscriptFooterPlugin
  • 动作绑定和命令绑定并存,既能触发 UI action,也能直接映射 slash command。
  • 平台差异适配,比如 Windows、Bun、不同终端是否支持 VT 模式。
  • 默认绑定叠加用户覆盖,而不是完全替换。

这背后的信号非常明确:Claude Code 的 REPL 不是“把网页聊天搬进终端”,而是一套认真经营的终端交互产品。

3. 定时任务与 Loop:Agent 不只服务当前会话,还能进入调度系统

src/tools/ScheduleCronTool/CronCreateTool.tssrc/hooks/useScheduledTasks.ts 展现的是另一个重要方向:Agent 从“响应一次用户提问”升级成“可以被调度的工作单元”。

这里最值得注意的实现点有四个:

  • Cron 创建工具本身是一个正式 Tool,有输入 schema、验证、结果映射和持久化路径。
  • 定时任务分成 session-only 和 durable 两类,后者会写入磁盘。
  • 调度器不是写死在命令里,而是通过 useScheduledTasks 作为独立运行时挂件常驻。
  • 如果定时任务是发给 teammate 的,系统还会把 prompt 精确路由给对应 Agent;teammate 不存在时,自动清理孤儿 cron。

这说明 Claude Code 并没有把 Agent 看成一个“临时聊天对象”,而是把它纳入了更长生命周期的任务调度模型里。

4. Buddy:彩蛋模块也暴露了产品工程的认真程度

src/buddy/ 表面上像一个玩笑功能,但它暴露了很有意思的工程习惯。比如 src/buddy/prompt.ts 明确规定 companion 和主 assistant 的职责边界,避免模型误把自己当成宠物角色;而 src/buddy/types.ts 则把物种、稀有度、帽子、属性都做成了确定义的类型系统。

更有意思的是,物种名不是直接硬编码字符串,而是通过 String.fromCharCode 构造。这种实现细节说明,哪怕是彩蛋功能,也要和主构建链路、关键字扫描、配置持久化这些工程约束协同工作。成熟产品往往就体现在这种地方。

十一、对 Agent 开发最有启发的五个设计取舍

  1. 入口先分流,组合根后加载。 这让冷启动优化有了结构性抓手。
  2. 工具协议足够厚。 Tool 不是函数,是运行时实体,这样权限、预算、并发、UI 才接得住。
  3. 任务是异步基座。 后台 Agent、远程 Agent、Shell、监控任务都被收口到 Task 抽象上。
  4. 扩展点多,但都回到统一抽象。 命令、技能、插件、MCP、远程能力最终都接入同一个 Query Runtime。
  5. 上下文治理显式化。 Memory、compact、attachment reinjection、token budget 都不是补丁,而是核心机制。

如果把这些取舍再压缩成一句话,那就是:Claude Code 把“能力增长”这件事交给统一抽象,把“风险增长”这件事交给治理层,而不是让每个新功能都各自发明一套机制。

十二、顶层模块索引:把“所有模块”一次看全

下面这张表不是逐文件展开 1902 个文件,而是按 src/ 顶层目录和单文件入口做完整索引。对于如此大的仓库,这是既能覆盖全貌又不至于失真的合理粒度。

模块 类型 职责
main.tsx组合根装配配置、认证、工具、命令、插件、技能、策略、远程与 UI。
interactiveHelpers.tsx交互边界负责 trust/setup screens、审批对话、renderAndRun 之类的会话启动控制。
replLauncher.tsx懒加载入口真正挂载 App 与 REPL 的轻薄入口。
context.ts上下文入口构造并缓存 systemContext 与 userContext。
query.ts查询状态机驱动完整 assistant 回合、流式采样、工具、预算与恢复。
QueryEngine.tsSDK 引擎对外暴露可复用的 headless conversation engine。
tools.ts注册表统一声明所有基础工具与启用逻辑。
Tool.ts核心协议定义 Tool 的 schema、权限、并发、安全和执行契约。
tasks.ts注册表统一声明所有任务类型。
Task.ts核心协议定义任务类型、状态与标识模型。
setup.ts启动辅助配合初始化流程做环境搭建。
ink.tsUI 出口统一导出 Ink 运行时能力。
cost-tracker.ts成本追踪统计会话成本与模型消耗。
costHook.ts成本 hook把成本追踪接到查询生命周期中。
history.ts会话历史管理历史记录、恢复与展示相关逻辑。
dialogLaunchers.tsx对话入口集中启动若干交互对话框。
projectOnboardingState.ts项目入门状态管理项目 onboarding 相关的状态持久化。
assistant/子系统Assistant 模式的会话历史等辅助能力。
bootstrap/子系统跨启动阶段共享的 bootstrap state。
bridge/子系统远程控制、桥接环境、工作轮询、会话孵化、重连与日志。
buddy/子系统伙伴精灵、宠物交互、陪伴式提示等 UI 能力。
cli/子系统CLI handlers、传输层、结构化输出、远程 IO。
commands/子系统所有 slash command 的实现。
components/子系统终端 UI 组件库。
constants/子系统系统提示词、产品常量、限制、键位、输出风格常量。
context/子系统React Context:通知、模态框、队列消息、FPS、邮箱等。
coordinator/子系统协调器模式的开关与模式识别。
entrypoints/子系统CLI、初始化等不同进程入口。
hooks/子系统输入、权限、设置、IDE、任务、通知等各类 hook。
ink/子系统终端渲染、事件分发、ansi/osc/csi 处理。
keybindings/子系统快捷键 schema、解析、匹配、用户绑定加载。
memdir/子系统长期记忆目录、索引、扫描、提示词与年龄策略。
migrations/子系统配置与模型版本迁移脚本。
moreright/子系统额外的 UI 或交互能力扩展。
native-ts/子系统原生能力桥接,如 color diff、file index、yoga layout。
outputStyles/子系统输出风格定义与加载。
plugins/子系统内建插件注册与 bunded plugin 入口。
query/子系统查询配置、依赖注入、停止钩子、token budget。
remote/子系统远程会话管理、权限桥接、WS 适配。
schemas/子系统hooks 等结构化 schema 定义。
screens/子系统REPL、Doctor、Resume Conversation 等页面级终端视图。
server/子系统直连会话管理与服务端会话入口。
services/子系统API、分析埋点、MCP、压缩、插件安装、语音、提示建议等服务层。
skills/子系统技能加载、内建技能与 MCP 技能桥接。
state/子系统AppState store、selector、状态变更监听。
tasks/子系统本地 shell、Agent、远程 Agent、Dream 等任务实现。
tools/子系统Bash、文件、技能、MCP、计划、任务、浏览器等工具实现。
types/子系统命令、权限、插件、消息、事件等类型定义。
upstreamproxy/子系统上游代理与 relay 能力。
utils/子系统这套运行时的底座工具箱,包括 Git、会话、路径、hooks、model、权限、token、日志等。
vim/子系统Vim motions、operators、text objects。
voice/子系统语音模式开关与能力判定。

十三、最后的判断:这份源码真正难的不是“LLM 接口”,而是运行时工程

如果只盯着 services/api/claude.ts 看,很容易得出一个错误结论:Claude Code 的难点在于 API 包装。实际上,API 适配只是它的一部分。真正难的是如何把下面这些东西放进一个统一系统里,还不至于崩:

  • 多模型、多 provider、多传输协议。
  • 流式工具执行与权限治理。
  • 任务、子 Agent、远程环境、Bridge。
  • 技能、插件、MCP、命令、记忆。
  • 长会话压缩、上下文恢复、成本与预算控制。

Claude Code 这份源码最值得学习的地方,不是某个聪明 prompt,也不是某个花哨 UI,而是它把 Agent 产品里最容易变成一团乱麻的部分,全都收束成了可维护的运行时边界。真正稀缺的不是“能不能接模型”,而是“当工具、权限、记忆、任务、远程、插件都长出来以后,系统还能不能继续演化”。这才是它真正的工程价值。