Claude Code 终端 Agent Runtime 全解析(非常详细),泄露源码深度复盘,收藏这篇就够了!
写在前面
昨天(2026 年 3 月 31 号)发生了一件挺荒诞的事。Anthropic 往 npm 上发 @anthropic-ai/claude-code 的新版本,打包的时候把 cli.js.map 给带进去了。
Source map 这东西本来是调试用的——有了它,压缩混淆后的 JS 能原样还原回 TypeScript。结果就是,不是什么精巧的攻击,不是一个零日漏洞,纯粹是一次发布流程的卫生事故,把将近 1900 个 TypeScript 文件的完整源码摊在了所有人面前。
第一时间,我借助cc和codex花了不少时间把恢复出来的源码翻了个遍,用于学习研究。说实话,收获比我预想的大得多,coding一哥有真货。
这不是拿来八卦的料。Claude Code 这东西,如果你只把它当成“终端里跑 Claude”的包装器,那你低估它了。看完源码会发现,它真正花力气的地方根本不在模型调用那一层——而是模型外面套着的整个运行时:怎么启动、怎么管状态、怎么调度工具、怎么做权限、怎么接 MCP、怎么跑子代理、怎么压缩上下文、怎么在终端里渲染……这些东西拼在一起,更像一个 终端 Agent Runtime,而不是一个简单的 CLI。
本文可能是全网最及时、深度、独家的一篇分析文章了,建议你读完收藏。
这篇文章分两部分。前半部分把这次泄露事件本身交代清楚——怎么发生的、漏了什么、边界在哪。后半部分才是重点:Claude Code 的运行时到底长什么样,为什么它能相对稳定地扛住一个本地 coding agent 该干的活。
第一部分:先把事件本身说清楚
“Claude Code”到底泄露了啥
Anthropic 的 Claude Code——一个跑在终端里的 AI 编程 Agent。官方定位是 agentic coding tool,能理解代码库、改文件、跑命令、处理 git 工作流。npm 上搜 @anthropic-ai/claude-code 就能找到。
可以确认的事实很简单:
- • npm 上确实有这个包。
- • 本地拿到的版本是
2.1.88,cli.js.map就在发布产物里。 - • GitHub 上已经有人用 source map 提取出整理仓库了,比如
leeyeel/claude-code-sourcemap。
所以说白了,泄露的是 Claude Code 的客户端运行时代码,不是模型权重,也不是服务端的基础设施。
技术原因:一句话就能说完
发布包里带上了不该出现在生产环境的 source map。
就这么简单。本地拿到的发布产物里躺着 cli.js 和 cli.js.map。仓库的恢复脚本直接读 .map 里的 sourcesContent 字段,就能把 TypeScript 文件全还原出来。
工程上这没什么神秘的——大概率是 bundler 默认生成了 source map,发布前没禁用,.npmignore 也没把 *.map 排掉,就这么发上去了。
从安全角度看,这就是个发布流程卫生问题。讽刺的地方也在这:出事的不是什么精巧的攻击链,而是一个非常平庸的打包失误,所以有网友戏称是A畜故意泄露的。
漏了什么?含金量其实不低
这不是几段残缺的代码片段,是整套客户端运行时骨架。
本地恢复结果显示 restored-src/src 下面有 1902 个文件,其中 .ts/.tsx 占了 1884 个。目录主要分布在 utils、components、commands、tools、services、hooks、ink。
换句话说,这次暴露出来的东西覆盖了:
- • 终端 UI 与渲染层
- • Agent 工具协议和调度逻辑
- • 权限、Hook、沙盒、安全相关实现
- • MCP、技能、插件、子代理机制
- • 远程 bridge、记忆、压缩、会话治理
对想研究 AI coding agent 的人来说,这是一份质量很高的学习样本。
影响边界:别夸也别缩
这类事件容易被两种极端说法带偏。
一种把它吹成“模型泄露”——不准确。泄露的是客户端源码,不是模型权重,没看到 API Key 之类的直接敏感凭证。
另一种则轻描淡写成“就是个客户端”——也不准确。Claude Code 客户端承载了大量关键设计:工具协议、权限模型、Agent 调度、MCP 接入、上下文治理。这些东西足够让外界近乎完整地理解它的产品骨架和工程取向。
比较准确的定位大概是这样:
不是模型级泄露,但对 Claude Code 的运行时实现来说,已经属于一次高价值、高完整度的意外开源。
好了,前因介绍清楚,咱们进入正题。
第二部分:架构深潜
先看全局:它不是命令壳,是运行时
开始前先别急着钻模块。退一步看全景,判断会稳得多。
下面这张图不是源码里自带的“官方架构图”——源码里没这东西。这是我根据入口、状态对象、查询循环、扩展面、隔离面梳理出来的运行时视图:
separate remote-control plane
@anthropic-ai/claude-code package
cli.js / cli.js.map
main.tsx
entrypoints/init.ts
screens/REPL.tsx
AppStateStore
ToolUseContext
QueryEngine
queryLoop
tool scheduler
commands.ts
skills
plugins
MCP subsystem
memdir / MEMORY.md
AgentTool
runAgent
worktree isolation
CCR remote session
bridgeMain
ink renderer
GrowthBook / profiler
如果硬要把这张图压成一句话,大概是这样:
启动与信任边界 -> 会话状态内核 -> 模型查询循环 -> 工具协议与调度 -> 扩展平面(MCP / Skills / Plugins) -> 隔离平面(Subagent / Worktree / Remote) -> 上下文治理与终端交互
普通 CLI 的思路是“解析命令 -> 执行动作 -> 打印结果”。Claude Code 明显在解另一个层次的问题:怎么维护一个长期存在、可中断、可恢复、可扩展的 agent 会话。
这才能解释为什么 commands、tools、state、query、bridge、memdir、ink 这些模块同时都很重。它们不是无序膨胀,是在共同承托一个本来就棘手的问题域。
运行时内核:启动、信任边界、会话状态
底盘看三件事就够了:启动时的关键路径怎么排、trust 边界卡在哪、状态由谁持有。
启动路径:不是顺序脚本,是有意识的优先级管理
main.tsx 顶部的注释很有意思(第 1-7 行)。它没把那几行 side effect 当普通初始化,而是明确解释了:在大模块导入之前,先把 profiler、MDM 原始读取和 keychain 预取发出去,这样后面的启动关键路径就能并行推进。实际调用在第 12、16、20 行。

这不是炫技,是一个很成熟的思路——一开始就在做“什么事可以先跑、什么事得等一等”的分层,而不是把所有初始化按调用顺序一股脑往下堆。
entrypoints/init.ts 延续了这个思路(相关锚点在第 74、137、146、159、247、263 行)。先把 safe env 应用上,再配 mTLS、proxy 和 API preconnect;真正依赖 trust 的完整环境变量和遥测初始化放到后面。启动阶段已经在同时权衡性能和安全了,不是“先跑起来再说”。

信任边界:硬的,但主要钉在交互式 REPL 路径上
interactiveHelpers.tsx:104 的 showSetupScreens() 本质上不是什么欢迎页,是 REPL 的 trust 关卡。当前工作区还没被信任时,它会先过 trust dialog,处理 .mcp.json 审批和 CLAUDE.md 外部 include 提示,最后才应用完整环境。

不过话说回来,这事也不能说得太绝对。在 --print 模式下(main.tsx:2586),程序会直接应用完整环境变量,把该模式视为隐式 trusted;一些命令路径也会跳过 trust dialog,但在帮助文案里明确写了“只在你信任的目录里使用”。
所以更准确的说法是:
Claude Code 把 trust 当运行时硬边界,但这条边界最完整地体现在交互式 REPL 路径;非交互和部分 CLI 命令路径则用模式约束和显式提醒来替代。
这个修正很重要啊——让“trust 是硬边界”变成一句有范围的判断,而不是一句过头话。
AppStateStore:不是组件 state,是会话内核对象
看 AppStateStore.ts,最能说明问题的不是字段多,而是字段的种类杂。它同时装着:
- •
toolPermissionContext - •
remoteConnectionStatus - •
replBridgeEnabled - •
mcp.clients/tools/resources - •
plugins - •
agentDefinitions - •
notifications - •
elicitation

这已经不是传统意义上的页面 state 了——谁能调工具、MCP 连没连、插件状态怎样、有没有待处理通知——各种请求和 UI 都在这同一个地方取值。
底层 store 倒是很克制。createStore()只提供 getState / setState / subscribe,用 Object.is 做去重。复杂度留在状态语义上,不靠更厚的状态框架去遮。 这种取舍很务实。

QueryEngine + queryLoop:一个会话对象,推进一台状态机
QueryEngine.ts:180 自己的注释写得很直白:“One QueryEngine per conversation”。它内部保存的字段确实都是“必须跨 turn 维护”的会话级状态:
- •
mutableMessages - •
permissionDenials - •
totalUsage - •
readFileState - •
discoveredSkillNames - •
loadedNestedMemoryPaths

submitMessage() 从第 209 行开始,核心动作是把这台状态机往前推一步,而不是每次都重新造一台。

query.ts 里的 queryLoop() 则是让这台机器真正转起来的地方——启动 memory prefetch、skill prefetch、发起流式采样、落工具、做压缩、算 continuation 和终止条件。

图比文字直观:
start and overlap
start and overlap
continue
terminal
User message
QueryEngine.submitMessage
system prompt parts
queryLoop
memory prefetch
skill prefetch
Claude API streaming
tool execution
permission and hook pipeline
message and app-state update
compact / session memory / snip
token budget continuation
transcript and usage persistence
有两个细节值得一提。
第一,prefetch 被画成 overlap 而不是顺序阶段。因为源码里 prefetch 的设计目标就是尽量躲到模型流式输出和工具执行期间去重叠,不拦主流程。
第二,终止条件不只有 token budget。queryLoop() 还会因为 max turns、prompt-too-long、媒体失败、stop hook 或 API error 退出。Token budget 只是 continuation 分支的一部分。
到这里,Claude Code 的“会话内核”轮廓已经比较清楚了:一套启动明确、边界明确、状态明确的运行时骨架。
能力协议与控制平面:Tool、Command、调度、权限
内核解决了“会话怎么活着”。接下来该看“会话里什么能被调用、怎么调用、谁来裁决”。
Tool 接口不是函数表,是一套能力协议
Tool.ts 里的 Tool 接口非常厚(第 386、394、402、416、500、514、566、605 行),而且这种厚是有意为之的。它不只是一个 call(args),还带着描述、并发安全性、只读/破坏性判定、中断行为、权限检查、UI 渲染方法等。
Claude Code 从一开始就没把工具当“一个函数”。工具是运行时能力对象——要参与协议,也要参与权限、安全、分类器、UI 和压缩预算。后面那些复杂的调度和权限逻辑之所以能维持住,正是因为它们消费的是有语义的对象,不是一堆裸函数。
buildTool():不知道的时候,先当它危险
buildTool() 看着不起眼,实际上很关键。看它的默认语义:

位置在 Tool.ts:750-783。
背后是一个很明确的立场:不知道的时候,就先当它危险。 不确定能不能并发?先别并发。不确定是不是只读?先别当只读处理。工具接进 agent 运行时之后,出错的代价往往不是一条日志,而是把会话状态、文件状态甚至权限语义一起搞乱。
Command 和 Tool 被故意分成了两条通道
这个边界很关键:用户显式命令和模型可见的能力,走的是两条路。
commands.ts 管的是 slash command 世界(第 258、361、456 行),面向用户意图:菜单、管理动作、入口选择。

tools.ts 管 builtin tool 集,按 feature gate、simple mode、deny rule 做裁剪(第 193、271、345、383 行)。getTools() 本身并不直接返回 MCP tools。MCP tools 是在 REPL live tool pool 组装阶段,通过 merge 流程并进来的(REPL.tsx:2399 附近)。
| 通道 | 面向谁 | 语义 |
|---|---|---|
| Command | 用户 | 显式意图、菜单、管理动作 |
| Tool | 模型 | 能力调用、自动调度、权限协议 |
这两者混在一起的下场通常是:用户交互和模型协议互相缠住,扩展系统的边界越来越糊。Claude Code 把它们分开了,这是对的。
工具调度:并发语义由工具自己声明
toolOrchestration.ts 的做法很干脆:按 isConcurrencySafe 把工具调用分批。非并发安全工具独占执行,连续的并发安全工具组成并发批。

在流式采样场景里,StreamingToolExecutor(第 35、129、153、210 行)直接把“边流边执行”做成了独立执行器,不是等 assistant message 全收完再统一落工具。它得处理一堆边界:并发工具并行、非并发工具独占、中断生成 synthetic error block、streaming fallback 丢弃未完成结果、兄弟工具失败级联取消。
BashTool 则再往上补了一层语义(第 95、178、265、610 行)。它识别搜索型、读取型、静默和常见后台命令,在 assistant 模式下把过长阻塞命令推到后台。说白了,它不是“执行个 shell 就完了”,而是在给 shell 行为加一层结构化理解,让调度和安全都从中获益。
权限系统:不是确认框,是决策流水线
useCanUseTool() 的调用顺序很能说明问题。它不是“检查规则 -> 弹窗”,而是把多种决策来源串起来了:
-
- 静态规则判定
hasPermissionsToUseTool
- 静态规则判定
-
- coordinator 自动化决策
-
- swarm worker 决策
-
- bash speculative classifier 的宽限等待
-
- interactive permission dialog
permissionSetup.ts 走得更远。它没停在“哪些命令危险”的层面,而是直接问:哪些授权规则本身就危险。 它显式区分了 Bash 解释器型、PowerShell 动态执行型危险规则,以及 AgentTool 的危险自动允许规则。
还有个很容易忽略的硬约束,在 toolHooks.ts 的 resolveHookPermissionDecision() 里:PreToolUse hook 的 allow 不会绕过 settings 里的 deny/ask 规则。 (第 324、372、386、392 行)。看着不起眼,但它防止了局部 Hook 把全局安全语义打穿。
到这,控制平面清楚了:能力抽象、命令入口、并发调度和权限裁决不是四块零散逻辑,是一张连着的网。
扩展与隔离:MCP、Skills、Plugins、Subagents、Remote
前两节解决“会话怎么活”和“能力怎么调度”。这一节关心:新能力怎么接进来,以及这些能力在什么隔离平面里跑。
MCP:一等扩展平面,不是附加接口
services/mcp/client.ts 最显眼的一点——它根本没把 MCP 当单一 transport 的“外部调用”。它处理了 SSE、SSE-IDE、WS、WS-IDE、HTTP、SDK、claudeai-proxy、stdio 这些连接方式。
另外它还单独管了鉴权异常、session 过期检测、auth cache TTL、鉴权失败的退化路径。MCP 在 Claude Code 里不是“顺手支持一下”,是一条必须认真维护的能力平面。
MCP 配置治理:原子写入是通用的,内容级去重是局部的
services/mcp/config.ts 有两类实现值得拎出来。
第一,.mcp.json 的写入是原子的:先写临时文件、datasync、保留原权限、再 rename。这种细节平时不显山露水,碰上崩溃或并发写入时差别巨大。
第二,内容级去重不是全局一刀切,主要针对 plugin MCP servers 和 claude.ai connectors 这些高风险重复注入点。用户或项目级别的 MCP 配置还是按作用域和优先级合并。
Skills 和 Plugins:两条不同的扩展轴
skills/loadSkillsDir.ts 是一套 frontmatter 驱动的文本扩展系统——做文件系统发现、token 估算、工具白名单等。本质是提示词扩展。
pluginLoader.ts 明显更偏发布和分发,处理来源优先级、版本化缓存、ZIP 包和 legacy path fallback。
所以扩展平面其实至少分三类:
- • Skill(文本/提示词扩展)
- • Plugin(文件系统组件扩展)
- • MCP(外部能力接入)
这不是“不统一”,恰恰是承认这三类东西本来就不是同一种对象。
子代理不是“再开一个聊天窗口”
AgentTool.tsx 支持的语义远超普通委派:走 teammate 的 name/team_name、worktree 隔离、remote 隔离、fork、后台代理、远端 task 注册。
Worktree 隔离不仅是“新建目录”,还要提前生成 agent ID、注入 fork child 的 path notice、处理 cwd 覆盖,并在正常、异常、中断路径下都做清理;有改动就保留。
runAgent.ts 让子代理继承一整套运行时环境:专属 MCP servers、sidechain transcript、Perfetto tracing 关系,以及 finally 块里完备的 agent-scoped cleanup。
AgentTool
createSubagentContext
agent-specific MCP servers
Child queryLoop
sidechain transcript
task progress and notification
worktree cleanup (AgentTool lifecycle)
agent-scoped cleanup (runAgent finally)
worktree isolation
CCR remote session
需要澄清的一点:worktree cleanup 归 AgentTool.tsx 管,agent-scoped cleanup 归 runAgent() 管,两者不能混为一谈。
bridgeMain.ts:远程控制平面是独立子系统
bridgeMain.ts 的 runBridgeLoop() 管的是另一类问题——active sessions、heartbeat、reconnect、JWT refresh、capacity wake、shutdown cleanup。
从职责看,它更像一条远程 session 管理平面。Claude Code 不止有一种 remote——至少有“子代理 remote 执行”和“bridge 远程控制”这两种。分开看,架构就顺了。
上下文治理与运行体验
骨架基本立起来了。剩下的问题也许最不起眼,但长期来看最能决定体验:一个长生命周期的 agent 会话怎么避免上下文失控,怎么在终端里保持可观测性。
MEMORY.md 是索引,不是正文堆积
memdir.ts 里有一个设计很值得记住:MEMORY.md 被明确定位为索引,不是记忆正文容器。

约束写得很死:最多 200 行、25KB,有专门的 truncateEntrypointContent(),注释里直接写了 “is an index, not a memory”。
我上次写那篇文章,cloud code记忆的文章有说过,现在在源码中得到进一步的确认我翻遍了Claude Code的system prompt,发现它的"记忆"就是一个200行的markdown文件。
长期记忆系统最容易烂掉的方式就是入口文件自己变成上下文垃圾堆。Claude Code 在这里反过来了:入口文件只保留高密度索引,正文拆到单独的 memory file 去。 不过在特定的模式下它也会切成 append-only 的 daily log 机制,但依然遵循“不往入口塞正文”的思路。
压缩不是一键操作,是整套组合拳
上下文治理不等于加个 /compact 就完事。源码里至少能看到 memory prefetch、tool result aggregate budget、snip、auto compact、session memory compaction、token budget continuation。
两个很实际的细节:
-
- 自动压缩带熔断器(
MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES = 3)。上下文已经不可恢复地超限时,再机械地 compact 只是浪费 token。
- 自动压缩带熔断器(
-
- Token 预算不只看“还剩多少”,还看边际收益(
COMPLETION_THRESHOLD = 0.9,DIMINISHING_THRESHOLD = 500)。预算没打满,但新增产出已经很薄时,也会停下。这绝对是跑过长会话被坑过之后才会加的设计。
- Token 预算不只看“还剩多少”,还看边际收益(
REPL.tsx:系统集成面
从架构角度看,screens/REPL.tsx 不太像一个终端页面组件。它把 useCanUseTool、权限上下文、notification 队列、MCP prompt 输入、elicitation dialog、merged tool pool 这几条运行时线索全接到了一起。它是整个 agent runtime 的交互总线。
自定义终端渲染器说明踩过的坑够深了
ink/renderer.ts 里处理复用 Output 以保留 charCache、跟踪 prevFrameContaminated、处理 alt-screen、避免 overlay/selection 污染后续渲染。
这种代码不会长在一个普通的命令行包装器里。它只会出现在终端 diff 渲染、光标一致性、全屏切换都已经成为必须自己兜底的问题的那种系统里。
灰度与可观测:不是加几条日志
GrowthBook 的 remote eval、帜元的曝光记录、startup profiler 的采样,都被做成了正式机制,不是临时调试开关。
当 feature gate、MCP、子代理、远程控制和平台差异同时存在时,没有这层观测和配置刷新机制,系统根本没法长期稳住。
源码证明了什么?
细节够多了。最后把判断压成三层。
源码明确支持的
- • 它不是一次请求式 CLI,是维护长期会话的运行时(
QueryEngine状态机证明)。 - • 工具系统是能力协议,不是单纯的函数掉用(
Tool接口证明)。 - • 权限系统是复合判定流水线(
useCanUseTool()证明)。 - • MCP 是一等扩展平面(多 transport 和鉴权机制证明)。
- • 子代理具备独立隔离与生命周期(
AgentTool+runAgent证明)。 - • 上下文治理是系统级能力(
memdir+autoCompact+tokenBudget证明)。
源码强烈暗示、但应该克制表达的
- • 设计目标明显过了“CLI 包装器”阶段,已经是终端 Agent Runtime。
- • 远程能力、子代理和 MCP 在持续往主路径上走,不是边缘实验。
这些推断与源码高度一致,但属于实现重心的推断,不是官方承诺。
源码推不出的
- • 还原不出 Anthropic 内部 monorepo 的真实结构。
- • 推断不了未公开的产品规划。
- • 断言不了所有 feature gate 的最终归宿。
真实代价
cloud code内部团队用了自身去迭代自己。目前看来,AI codeing造成的屎山他们自己也没法去规避。
- • utils 目录太重。 大量横切逻辑沉在底下,变成了隐式内核,替换代价高。
- • 组合爆炸。 各种模式、环境变量、feature gate 交叉起来,测试矩阵极难完全覆盖。
- • 逼近 God Object。 AppState 承载了太多会话级语义,继续堆上去迟早会撑不住。
- • Renderer 的技术债。 自定义 ink renderer 解决了问题,但也把终端兼容性的维护成本永远留给了自己。
最后
把全文压成一句话:
恢复出的 2.1.88 源码足以说明,Claude Code 已经不是“给 Claude 套了一层 shell 加上文件读写权限”的 CLI;它更接近一个以 Claude 为推理核心、以工具协议、权限裁决、扩展接入、上下文治理和终端交互为骨架的 终端 Agent Runtime。
它真正值得研究的地方,在模型外面那一整圈工程能力,用技术圈的热词叫Harness。很多 AI CLI 做到“能调模型、能调工具”就停了。Claude Code 明显走到了另一个阶段:开始系统化解决一个本地 agent 在真实工程环境里怎么长期、稳定、可恢复地跑下去的问题。
这大概是这次意外开源里,最值得琢磨的东西。
学AI大模型的正确顺序,千万不要搞错了
🤔2026年AI风口已来!各行各业的AI渗透肉眼可见,超多公司要么转型做AI相关产品,要么高薪挖AI技术人才,机遇直接摆在眼前!
有往AI方向发展,或者本身有后端编程基础的朋友,直接冲AI大模型应用开发转岗超合适!
就算暂时不打算转岗,了解大模型、RAG、Prompt、Agent这些热门概念,能上手做简单项目,也绝对是求职加分王🔋

📝给大家整理了超全最新的AI大模型应用开发学习清单和资料,手把手帮你快速入门!👇👇
学习路线:
✅大模型基础认知—大模型核心原理、发展历程、主流模型(GPT、文心一言等)特点解析
✅核心技术模块—RAG检索增强生成、Prompt工程实战、Agent智能体开发逻辑
✅开发基础能力—Python进阶、API接口调用、大模型开发框架(LangChain等)实操
✅应用场景开发—智能问答系统、企业知识库、AIGC内容生成工具、行业定制化大模型应用
✅项目落地流程—需求拆解、技术选型、模型调优、测试上线、运维迭代
✅面试求职冲刺—岗位JD解析、简历AI项目包装、高频面试题汇总、模拟面经
以上6大模块,看似清晰好上手,实则每个部分都有扎实的核心内容需要吃透!
我把大模型的学习全流程已经整理📚好了!抓住AI时代风口,轻松解锁职业新可能,希望大家都能把握机遇,实现薪资/职业跃迁~
这份完整版的大模型 AI 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费】

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)