这篇文章不是“按目录从上往下念一遍文件名”,而是把 Claude Code 当成一套生产级 Agent Runtime 来拆:它如何启动、如何构造上下文、如何驱动查询循环、如何执行工具、如何治理权限、如何承载异步任务与远程运行时,以及为什么这些设计能在复杂产品里长期活下来。
query.ts 为心脏、以 Tool/Task/Command 为统一抽象、以权限系统和扩展总线为边界控制的 Agent Runtime。
src/ 下源码文件数量utils 文件数,说明基础设施极重components 文件数,终端 UI 也非常复杂commands 文件数,命令面很宽tools 文件数,工具系统是第一等公民services 文件数,协议与运行时服务层很厚hooks 文件数,UI 与策略被大量 hook 化src/components/App.tsx、src/state/AppState.tsx、src/screens/REPL.tsx、src/hooks/useCanUseTool.tsx 这类文件,已经出现了 react/compiler-runtime 和 sourcemap 内联痕迹。也就是说,这份仓库更接近“可阅读的构建产物快照”而不是最初作者提交的源码。因此本文会以运行时结构和调用关系为主,不会假装自己能还原所有原始编码细节。
一、先给结论:这到底是一个什么系统
如果只看 UI,你会以为它是一个终端版聊天应用;如果只看 tools/,你会以为它是一个“给大模型挂了很多工具”的壳;如果只看 bridge/ 和 remote/,又会以为它是一个远程控制产品。真正准确的说法是:
Claude Code 是一套面向 Agent 的运行时平台。UI、CLI、远程控制、MCP、插件、技能、异步任务,都只是这个运行时的不同入口和不同扩展面。
从职责上看,这个系统可以拆成八层:
负责命令分流、懒加载和冷启动优化,核心文件是
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 明显不是这么做的。
src/entrypoints/cli.tsx 的职责可以概括成一句话:在尽可能少加载模块的前提下,把不同运行模式分流到对应子系统。 它做了三件很关键的事。
- 把纯信息类命令做成零或极少依赖的快路径,比如
--version。 - 把长驻型命令做成独立支路,比如
bridge、daemon、ps/logs/attach/kill。 - 只有在需要完整会话时,才进入
init.ts与main.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。这个文件的重要结论不是“代码很多”,而是它承担了所有运行时对象的拼装工作。尤其有三个细节值得注意:
- 它在 very early 阶段就触发
startMdmRawRead()和startKeychainPrefetch()这样的并行预取,说明作者对冷启动路径做过实打实优化。 - 它不是先画 UI 再慢慢加载功能,而是先把模型、工具、命令、插件、技能、权限模式、策略限制这些运行时能力准备好。
- 它大量使用特性开关和动态导入,所以这套代码库不是一个固定产品,而是一套支持多发行形态的可裁剪运行时。
最后,interactiveHelpers.tsx 和 replLauncher.tsx 把信任与交互兜起来。前者负责 setup screens、trust dialog、MCP 审批、API key 审批等交互边界;后者则非常克制,只负责在真正需要 REPL 时再懒加载 App 与 REPL。这说明作者很清楚:对一个终端 Agent 来说,真正昂贵的不是画 UI,而是把完整运行时装进进程。
三、状态与上下文:不是 Redux,而是轻量 store 加显式上下文装配
这套系统的状态管理很有意思。它没有上来就用 Redux 或 Zustand,而是自己实现了一个极轻的外部 store,再用 React 做桥接。
src/state/store.ts 基本就是一个最小实现:getState、setState、subscribe。真正重量级的不是 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.ts 和 src/utils/queryContext.ts 负责把系统上下文与用户上下文装配出来:
systemContext主要负责 Git 快照与运行环境信息。userContext主要负责CLAUDE.md、记忆文件等用户持久上下文。constants/prompts.ts则负责把模块化的系统提示词片段拼起来。
这个设计的关键点是:上下文不是在 query loop 内临时拼字符串,而是被单独抽象成一套可缓存、可注入、可替换的构造过程。 这会大幅降低循环内部的复杂度,也避免依赖环。
四、查询引擎:真正的核心是可恢复的多轮状态机
如果让我只挑一个文件来代表 Claude Code 的“灵魂”,我会选 src/query.ts。因为这里真正定义了系统“怎么像一个 Agent 一样工作”。
这一层至少做了七件事:
- 把输入消息、系统提示词、用户上下文、系统上下文拼成 API 请求。
- 流式接收 assistant 输出,并在 tool_use block 出现时触发工具执行。
- 决定哪些工具可以并发、哪些工具必须独占。
- 跟踪 token budget、max output token 恢复、stop hooks。
- 在上下文超限时触发自动压缩、微压缩或其他恢复路径。
- 把工具结果和压缩边界重新注回消息流。
- 最终把 assistant 输出与 usage 信息回写到会话状态。
这意味着 query.ts 本质上不是一个“调用模型 API 的函数”,而是一个带副作用、带恢复逻辑、带预算约束的回合状态机。
其配套的 src/QueryEngine.ts 则把这套查询机制封装成 headless / SDK 可复用的对话引擎。它会保存:
- 累计消息列表。
- 总 usage。
- 文件缓存。
- 权限拒绝记录。
- 跨回合的 abort controller。
这一步很关键,因为它说明 Claude Code 不是把“终端 UI 逻辑”和“模型回合逻辑”绑死了。UI 只是一个消费者,QueryEngine 才是可复用的内核。
查询循环的前门是 src/utils/handlePromptSubmit.ts 和 src/utils/processUserInput/processUserInput.ts。这两层的职责是把“用户原始输入”编译成“模型真正看到的消息”。这里的实现思路很像编译器前端:
- 先识别 slash command、本地 JSX 命令、桥接消息、元消息。
- 再处理粘贴内容、图片、附件、IDE 选区、hook 附加上下文。
- 最后决定这次输入究竟是触发模型查询,还是只做本地控制流。
也就是说,输入在进入模型之前,已经被完整归一化过了。这正是成熟 Agent Runtime 与“把文本直接发给 LLM”的根本区别。
五、工具系统:不是函数库,而是一套协议化运行时
Claude Code 最强的部分之一,就是工具系统设计得足够“像运行时”而不是“像 util 函数”。
核心契约在 src/Tool.ts。这个文件定义的不是一个简单的 call() 接口,而是一整套工具元数据:
- 输入输出 schema。
- 用户可见名称与描述。
- 权限检查钩子。
- 只读属性、并发安全属性。
- 中断行为、进度事件、结果裁剪策略。
这意味着任何工具要进入系统,都必须先回答:它如何被模型理解、它是否可并发、它是否可能危险、它如何报告进度、它的结果如何进入 transcript。这样的契约足够厚,系统才可能稳定地不断扩展。
src/tools.ts 则是全局工具注册表。这个文件透露出两个很重要的架构意图:
- 工具集合不是静态的,会根据 feature flag、发行形态、环境能力动态变化。
- 工具的来源非常杂,但最终必须汇总到
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 稳定。
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。这个文件本质上做的是协议适配层:
- 支持
stdio、SSE、streamable HTTP、WebSocket等传输。 - 支持认证、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。于是整个远程层其实有三种模式:
- Bridge 模式:把本机当成可调度环境。
- Remote viewer 模式:连接到远端会话看事件流。
- Direct connect 模式:直接创建并接入会话。
这三者看似不同,但底层都回到同一套 Query Runtime。这也是我一直强调的重点:Claude Code 的核心不是某种前端形态,而是底层运行时的一致性。
九、长期记忆与上下文压缩:所谓“大上下文”本质上是治理策略
Agent 产品一旦进入长会话,就一定会遇到两个问题:
- 哪些信息要跨会话保留?
- 当前会话太长时,什么信息该保、什么信息该丢?
Claude Code 对这两件事都给了工程化回答。
1. Memory 目录不是“隐式记忆”,而是显式文件系统
src/memdir/memdir.ts 定义了持久记忆系统。它的设计非常值得学习:
- 记忆是文件,而不是隐式向量库。
MEMORY.md是索引,不是内容仓库。- 有明确的类型体系:用户、反馈、项目、参考。
- 有严格的入口文件行数和字节上限,防止索引膨胀。
这种设计非常“工程师友好”,因为记忆变成了可审计、可版本化、可手工修正的资产,而不是产品黑箱。
2. 压缩不是“简单摘要”,而是带恢复语义的上下文管理
services/compact/compact.ts 的实现揭示了一个现实:所谓“超长上下文”并不是靠模型 magically 无限记住,而是靠系统在关键节点主动做上下文治理。这个文件至少做了这些事:
- 压缩前剥离图片和文档,减少无效 token 消耗。
- 剥离那些下一轮本来就会重新注入的附件,避免摘要污染。
- 为重要文件、技能、工具发现结果保留预算。
- 压缩后重新注入关键 attachment 和必要文件摘要。
所以 Claude Code 真正的设计思想不是“我有无限上下文”,而是“我知道什么该进入长期记忆,什么该进入当前回合上下文,什么该在超限后被浓缩成可恢复摘要”。这是一个非常重要的认知差别。
十、那些不在主干里、却暴露成熟度的外围模块
如果只盯着 query.ts、tools.ts、AgentTool 这些主干文件,很容易漏掉一件事:真正能把产品做成熟的,往往是那些看起来“不那么核心”的外围系统。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 本身也不是一种实现方式,而是四种:command、prompt、http、agent。这意味着平台作者没有把“扩展性”限制成 shell 脚本,而是明确允许用户在生命周期里注入本地命令、额外模型判断、HTTP 回调,甚至独立 Agent 验证器。对于一个 Agent 平台来说,这种设计比“不断往核心里塞新功能”更可持续。
2. 快捷键系统:终端 UI 不是玩具,而是一套上下文化输入系统
src/keybindings/schema.ts 和 src/keybindings/defaultBindings.ts 显示,这套终端 UI 的输入系统已经非常产品化了。它支持:
- 多上下文绑定,比如
Global、Chat、Confirmation、Transcript、Footer、Plugin。 - 动作绑定和命令绑定并存,既能触发 UI action,也能直接映射 slash command。
- 平台差异适配,比如 Windows、Bun、不同终端是否支持 VT 模式。
- 默认绑定叠加用户覆盖,而不是完全替换。
这背后的信号非常明确:Claude Code 的 REPL 不是“把网页聊天搬进终端”,而是一套认真经营的终端交互产品。
3. 定时任务与 Loop:Agent 不只服务当前会话,还能进入调度系统
src/tools/ScheduleCronTool/CronCreateTool.ts 和 src/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 开发最有启发的五个设计取舍
- 入口先分流,组合根后加载。 这让冷启动优化有了结构性抓手。
- 工具协议足够厚。 Tool 不是函数,是运行时实体,这样权限、预算、并发、UI 才接得住。
- 任务是异步基座。 后台 Agent、远程 Agent、Shell、监控任务都被收口到 Task 抽象上。
- 扩展点多,但都回到统一抽象。 命令、技能、插件、MCP、远程能力最终都接入同一个 Query Runtime。
- 上下文治理显式化。 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.ts | SDK 引擎 | 对外暴露可复用的 headless conversation engine。 |
tools.ts | 注册表 | 统一声明所有基础工具与启用逻辑。 |
Tool.ts | 核心协议 | 定义 Tool 的 schema、权限、并发、安全和执行契约。 |
tasks.ts | 注册表 | 统一声明所有任务类型。 |
Task.ts | 核心协议 | 定义任务类型、状态与标识模型。 |
setup.ts | 启动辅助 | 配合初始化流程做环境搭建。 |
ink.ts | UI 出口 | 统一导出 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 产品里最容易变成一团乱麻的部分,全都收束成了可维护的运行时边界。真正稀缺的不是“能不能接模型”,而是“当工具、权限、记忆、任务、远程、插件都长出来以后,系统还能不能继续演化”。这才是它真正的工程价值。