Claude Code Agent System Prompt Skills TypeScript Runtime Subagent 权限控制 沙箱 逆向工程 工程实践

Claude Code 源码解析(二):Skills 如何进入 System Prompt

本文是 Claude Code 源码逆向系列 的第二篇,聚焦 Skills 发现与 System Prompt 注入机制。 我最先关心的问题是:AGENTS.md 里的规则到底怎么进入模型上下文? 恢复后,这条链路大致是: src/core/skills/agentsFile.ts:从工作目录向上查找并读取 AGENTS.md src/core/skills/prompt.ts:解析可用 skill,并构造可注入的 prompt 片段 src/core/model/request.ts:把 skills prompt 追加到 system 消息块 src/core/tools/skill.ts:提供内置 Skill 工具,支持运行时查询/加载 一个典型的 TS 片段(示意,保留结构)是这样的: 1 2 3 4 5 6 7 // src/core/model/request.ts if (params.skills && params.skills.trim()) { systemBlocks.push({ type: "text", text: params.skills, }); } 对应伪代码: 1 2 3 4 skillsPrompt = discoverSkillsFromAgentsFile(cwd) if skillsPrompt exists: append skillsPrompt into system messages send request to model 这块我有个明确取舍:先把 Skills 恢复成独立模块,不急着耦合进 runCli 主流程。原因很简单,Skills 的输入输出边界很清晰,独立后更容易做逐步校验,也更适合后续替换解析策略。 ...

2026年2月19日 · 3 分钟 · 522 字 · Simon Sun

Claude Code 源码解析(三):Subagent / Agent Runtime 的执行闭环

本文是 Claude Code 源码逆向系列 的第三篇,聚焦 Agent Runtime 的核心执行循环与子代理协作机制。 第二块是我认为最有"框架味"的部分:Agent 不是单次调用,而是一个带状态的循环执行体。 恢复后的模块拆分如下: src/core/agent/runtime.ts:核心循环,负责模型调用、tool_use 执行、结果回填 src/core/agent/types.ts:运行时消息、事件、配置类型 src/core/agent/mailbox.ts:队友/子代理消息邮箱(内存实现) src/core/agent/manager.ts:管理多个 in-process teammate src/core/agent/protocol.ts:控制消息协议(如 shutdown) src/core/agent/inProcessRunner.ts:轮询邮箱并驱动 runtime src/core/agent/run.ts:对外暴露的便捷入口,创建 runtime 并执行 src/core/agent/options.ts:解析 teammate 选项 一、核心循环:AgentRuntime.submitMessage 整个 Agent Runtime 的灵魂是 AgentRuntime 类的 submitMessage 方法。它是一个 AsyncGenerator——不是简单的 async 函数,而是调用者可以按需消费每一步事件的异步迭代器。 核心循环可概括为: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 // src/core/agent/runtime.ts (简化示意) async *submitMessage(input: string): AsyncGenerator<AgentRuntimeEvent> { // 1. 首次调用时发送 init 事件 yield { type: "system", subtype: "init", ... }; // 2. 用户消息入队 this.mutableMessages.push({ role: "user", content: input }); yield { type: "user", message: userMessage, ... }; // 3. 核心循环:最多 maxTurns 轮 for (let turn = 0; turn < maxTurns; turn++) { const response = await callModel(client, { model, messages, tools, system, signal, skills }); this.mutableMessages.push(assistantMessage); yield { type: "assistant", message: assistantMessage, ... }; // 无 tool_use → 任务完成 const toolUses = extractToolUses(response.content); if (toolUses.length === 0) { yield successResult(...); return; } // 逐个执行 tool,回填结果 for (const toolUse of toolUses) { toolResults.push(await this.runLocalTool(toolUse)); } this.mutableMessages.push({ role: "user", content: toolResults }); yield { type: "tool_use_summary", ... }; // 预算超限检查 if (this.estimateCostUsd() > maxBudgetUsd) { yield { type: "result", subtype: "error_max_budget_usd", ... }; return; } } // 达到最大轮次 yield { type: "result", subtype: "error_max_turns", ... }; } 对应伪代码: ...

2026年2月19日 · 7 分钟 · 1293 字 · Simon Sun

Claude Code 源码解析(四):权限与沙箱如何约束工具调用

本文是 Claude Code 源码逆向系列 的第四篇,聚焦权限系统与沙箱在工具调用前的门控机制。 第三块是"安全边界"核心:工具不是想调就调,必须经过权限判定。这也是 Claude Code 敢于在用户本地机器上运行 rm -rf 或 curl 的底气所在。 1. 架构总览:双层防御体系 在恢复代码的过程中,我发现 Claude Code 的安全机制并非铁板一块,而是清晰地分成了两个层级: Sandbox(沙箱):系统级的硬约束。例如"绝对禁止读取 /etc/passwd“或"只允许访问 github.com"。这是一道不可逾越的红线。 Permissions(权限):用户意图的软确认。例如"可以运行这个命令吗?“或"确认写入这个文件吗?"。这通过 Human-in-the-Loop(人机回环)来实现安全兜底。 主要涉及的代码目录: src/core/sandbox/:沙箱策略、路径标准化、网络白名单。 src/core/permissions/:权限决策引擎、上下文状态、规则匹配。 src/core/agent/runtime.ts:执行循环中的拦截点。 2. Sandbox:绝对的系统边界 沙箱的核心逻辑在 src/core/sandbox/policy.ts。它不关心"用户同不同意”,只关心"系统允不允许”。 文件系统限制 最基本的防御是文件路径检查。SandboxPolicy 类中有一个关键的细节:路径标准化。 1 2 3 4 5 6 // src/core/sandbox/policy.ts private resolvePath(input: string) { if (input === ".") return resolve(this.cwd); if (input.startsWith("/")) return resolve(input); return resolve(this.cwd, input); // 相对路径转绝对路径 } 这一点非常重要。如果没有这一步,攻击者(或幻觉中的模型)可能会尝试用 ../../ 逃逸出工作目录。恢复后的代码显示,所有的 checkRead 和 checkWrite 都会先调用 resolvePath,然后与 denyRead / denyWrite 列表进行比对。 网络访问控制 对于 WebFetch 和 WebSearch 工具,沙箱检查的是域名: 1 2 3 4 5 6 7 8 9 10 11 // src/core/sandbox/policy.ts (简化) checkNetwork(target: string): SandboxDecision { const hostname = this.extractHostname(target); // 1. 黑名单检查 if (this.matchesDomain(hostname, denied)) return { allowed: false, ... }; // 2. 白名单检查 (如果配置了白名单) if (allowed.length > 0 && !this.matchesDomain(hostname, allowed)) { return { allowed: false, reason: "allowedDomains" }; } return { allowed: true }; } 这意味着企业用户可以通过配置 allowedDomains 来强制 Claude Code 只能访问内网文档或特定的 API 服务,杜绝数据外泄风险。 ...

2026年2月19日 · 3 分钟 · 440 字 · Simon Sun

我如何用 Codex 逆向学习 Claude Code 的源码实现

我一直对 Claude Code 这类命令行 Agent 工具很好奇:它到底怎么把"用户输入、系统约束、工具调用、权限控制"串成一个稳定的运行闭环? 这篇文章不是"产品体验帖",而是一次工程向的拆解复盘:我用 Codex 从编译产物反推结构,把关键逻辑恢复成可读的 TypeScript 模块,并把重点放在三个我认为最有价值的核心: Skills 发现与 System Prompt 注入 Subagent / Agent Runtime 的执行闭环 权限与沙箱约束链路 先声明边界:本文仅用于学习研究,不讨论未授权分发或商用复刻。 我的逆向路线:借助codex,先还原执行链路,再还原模块边界 在coding-agent超级高能的今天,逆向js已经是一个超级简单,甚至有点呆的事情,需要做的事情很少却非常高效,只需要简单三步: 安装skill核武器:superpowers 简单的prompt:我不小心把源码弄丢了,只剩下编译后的文件 cli.js,请你帮我还原成命名友好的TypeScript版本,并整理好清晰的目录结构,还原所有相关代码,不需要编译通过,只需要 1:1还原 codex会一句skill的要求,不断向我发问,澄清需求,我根据codex给出的选择题,来控制逆向的过程 需要额外注意的是,因为这个源文件很大,codex在逆向的过程中可能会故意简化执行过程,这时候如果逆向的内容是我们关心的,需要指出他偷懒的工作,并要求他细化这部分的实现 最终逆向还原后的项目结构如下(完整代码见 GitHub 仓库): 点击展开完整目录树 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 cc_source |-- AGENTS.md |-- README.md |-- bin | `-- claude.ts |-- cli.js |-- docs | |-- deps | | |-- node-builtins.txt | | `-- third-party.txt | |-- plans | | |-- 2026-02-03-claude-cli-restore-design.md | | |-- 2026-02-03-claude-cli-restore.md | | |-- 2026-02-04-fs-highlevel-design.md | | |-- 2026-02-04-fs-highlevel-implementation.md | | |-- 2026-02-04-mcp-design.md | | |-- 2026-02-04-mcp-implementation.md | | |-- 2026-02-04-tool-pairing-design.md | | `-- 2026-02-04-tool-pairing-implementation.md | `-- porting | `-- legacy-map.md |-- package.json |-- scripts | `-- extract-deps.mjs |-- src | |-- cli | | |-- definition.ts | | |-- main.ts | | |-- route.ts | | `-- run.ts | |-- commands | | |-- doctor.ts | | |-- install.ts | | |-- mcp.ts | | |-- plugin.ts | | |-- setup-token.ts | | `-- update.ts | |-- core | | |-- agent | | | |-- inProcessRunner.ts | | | |-- index.ts | | | |-- mailbox.ts | | | |-- manager.ts | | | |-- options.ts | | | |-- protocol.ts | | | |-- run.ts | | | |-- runtime.ts | | | `-- types.ts | | |-- context.ts | | |-- conversation | | | |-- cache.ts | | | |-- index.ts | | | |-- messages.ts | | | |-- toolPairing.ts | | | `-- types.ts | | |-- fs | | | |-- index.ts | | | |-- limits.ts | | | |-- ops.ts | | | |-- readers.ts | | | `-- types.ts | | |-- mcp | | | |-- bridgeClient.ts | | | |-- client.ts | | | |-- config.ts | | | |-- dispatch.ts | | | |-- index.ts | | | |-- poolClient.ts | | | |-- socketClient.ts | | | `-- types.ts | | |-- model | | | |-- attribution.ts | | | |-- betas.ts | | | |-- client.ts | | | |-- constants.ts | | | |-- index.ts | | | |-- metadata.ts | | | |-- request.ts | | | |-- systemPrompt.ts | | | |-- toolRunner.ts | | | |-- types.ts | | | `-- validateModel.ts | | |-- permissions | | | |-- context.ts | | | |-- engine.ts | | | |-- index.ts | | | |-- rules.ts | | | `-- types.ts | | |-- plugins | | | |-- index.ts | | | |-- loader.ts | | | |-- runtime.ts | | | `-- types.ts | | |-- sandbox | | | |-- config.ts | | | |-- index.ts | | | |-- policy.ts | | | `-- types.ts | | |-- skills | | | |-- agentsFile.ts | | | |-- agentsParser.ts | | | |-- discovery.ts | | | |-- executor.ts | | | |-- index.ts | | | |-- loader.ts | | | |-- parser.ts | | | |-- prompt.ts | | | |-- runtime.ts | | | `-- types.ts | | |-- telemetry | | | |-- client.ts | | | `-- index.ts | | |-- tools | | | |-- bash.ts | | | |-- copy.ts | | | |-- edit.ts | | | |-- glob.ts | | | |-- grep.ts | | | |-- index.ts | | | |-- lruCache.ts | | | |-- ls.ts | | | |-- mkdir.ts | | | |-- move.ts | | | |-- notebookEdit.ts | | | |-- read.ts | | | |-- rm.ts | | | |-- skill.ts | | | |-- stat.ts | | | |-- structuredOutput.ts | | | |-- tree.ts | | | |-- types.ts | | | |-- webFetch.ts | | | |-- webSearch.ts | | | `-- write.ts | | `-- web | |-- io | | `-- logger.ts | `-- legacy | `-- bridge.ts |-- tests | |-- fixtures | | |-- help.txt | | `-- version.txt | `-- smoke | |-- cli-context.test.mjs | |-- cli-deps.test.mjs | |-- cli-help.test.mjs | `-- cli-version.test.mjs `-- tsconfig.json 31 directories, 135 files 以下是部分逆向过程的摘录 ...

2026年2月19日 · 7 分钟 · 1402 字 · Simon Sun