技能
Layer 5: Skills, Plugins & MCP Ecosystem
Claude Code 源码深度拆解 — 基于 @anthropic-ai/claude-code npm 包 sourcesContent 还原 分析范围: SkillTool (500行) + loadSkillsDir (750行) + pluginLoader (2700行) + MCP client (3000行) + 20 个卫星模块
- 状态
- 已收录
- 语言
- 中文
- 来源
- Projects/claude-code-deep-dive/layer5-skills-plugins-mcp.html
- 重复副本
- 0
提取结果
提示词片段
┌─────────────────────────────────────────────────────────────────┐ │ SkillTool │ │ 模型调用入口 ── validateInput() → checkPermissions() → call() │ └────────┬──────────────────────┬──────────────────────┬───────────┘ │ │ │ ┌────▼────┐ ┌────▼────┐ ┌────▼────┐ │ Skills │ │ Plugins │ │ MCP │ │ │ │ │ │ │ │ .md文件 │ │plugin.json │ client │ │ 前置声明│ │ 2700行 │ │ 3000行 │ │ 目录格式│ │ 管理器 │ │ 4种传输 │ └────┬────┘ └────┬────┘ └────┬────┘ │ │ │ ▼ ▼ ▼ ┌─────────────────────────────────────────────────────────┐ │ Command 统一类型 (types/command.ts) │ │ PromptCommand | LocalCommand | LocalJSXCommand │ └──────────────────────────┬──────────────────────────────┘ │ ┌──────────▼──────────┐ │ commands.ts │ │ 7 层优先级加载 │ │ 去重 / 过滤 / 注入 │ └─────────────────────┘
export type Command = CommandBase & (PromptCommand | LocalCommand | LocalJSXCommand)
executeShellCommandsInPrompt()
executeShellCommandsInPrompt
processPromptSlashCommand()
cmd.type === 'prompt' && cmd.loadedFrom === 'mcp'
type=prompt, !disableModelInvocation, source!='builtin'
┌───────────────────────────────┐ │ loadAllCommands() 7层加载 │ │ │ │ 1. bundledSkills │ registerBundledSkill() │ 2. builtinPluginSkills │ plugins/bundled/ │ 3. skillDirCommands │ .claude/skills/ (5 sources) │ 4. workflowCommands │ WORKFLOW_SCRIPTS gate │ 5. pluginCommands │ plugin commands/ dir │ 6. pluginSkills │ plugin skills/ dir │ 7. COMMANDS() │ 80+ built-in commands └──────────────┬────────────────┘ │ ┌──────────────▼────────────────┐ │ getCommands() 运行时过滤 │ │ │ │ + meetsAvailabilityRequirement│ │ + isCommandEnabled() │ │ + getDynamicSkills() inject │ │ + MCP skills (via AppState) │ └──────────────┬────────────────┘ │ ┌──────────────▼────────────────┐ │ getSkillToolCommands() │ │ 模型可见的最终列表 │ │ → prompt.ts budget 管理 │ │ → SkillTool system-reminder │ └───────────────────────────────┘
正文
清洗后的原始内容
Layer 5: Skills, Plugins & MCP Ecosystem
Claude Code 源码深度拆解 — 基于 @anthropic-ai/claude-code npm 包 sourcesContent 还原
分析范围: SkillTool (500行) + loadSkillsDir (750行) + pluginLoader (2700行) + MCP client (3000行) + 20 个卫星模块
生态系统架构概述
Claude Code 的可扩展能力由三个子系统协同构成:Skills(技能)、Plugins(插件)和 MCP(Model Context Protocol)。三者最终汇聚为统一的 Command 类型,由 commands.ts 中的 7 层优先级链按序加载。这一设计使得从用户自定义的 Markdown 文件到远程 MCP 服务器的工具,都以相同的接口呈现给模型。
三位一体架构
┌─────────────────────────────────────────────────────────────────┐
│ SkillTool │
│ 模型调用入口 ── validateInput() → checkPermissions() → call() │
└────────┬──────────────────────┬──────────────────────┬───────────┘
│ │ │
┌────▼────┐ ┌────▼────┐ ┌────▼────┐
│ Skills │ │ Plugins │ │ MCP │
│ │ │ │ │ │
│ .md文件 │ │plugin.json │ client │
│ 前置声明│ │ 2700行 │ │ 3000行 │
│ 目录格式│ │ 管理器 │ │ 4种传输 │
└────┬────┘ └────┬────┘ └────┬────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────────┐
│ Command 统一类型 (types/command.ts) │
│ PromptCommand | LocalCommand | LocalJSXCommand │
└──────────────────────────┬──────────────────────────────┘
│
┌──────────▼──────────┐
│ commands.ts │
│ 7 层优先级加载 │
│ 去重 / 过滤 / 注入 │
└─────────────────────┘
三者的分工
| 维度 | Skills | Plugins | MCP |
|---|---|---|---|
| 定义格式 | Markdown + YAML frontmatter | plugin.json manifest + 目录结构 | JSON-RPC 协议 |
| 来源 | 本地目录 / bundled / managed | Marketplace / 本地 / SDK | Stdio / SSE / WebSocket / HTTP |
| 加载时机 | 启动时 + 动态发现 | 启动时(缓存优先) | 连接时 |
| 安全边界 | Shell 命令受 loadedFrom 控制 |
Manifest schema 验证 + 版本隔离 | 传输层认证 + OAuth |
| 核心代码 | loadSkillsDir.ts (750行) | pluginLoader.ts (2700行) | client.ts (3000行) |
统一 Command 桥接
三个子系统产出的所有可调用能力,最终都被转化为 Command 联合类型。对模型而言,无论是用户在 ~/.claude/skills/ 下写的一段 Markdown,还是通过 Marketplace 安装的插件技能,还是远程 MCP 服务器暴露的 prompt,都以完全相同的 PromptCommand 形态出现在 SkillTool 的列表中。这种设计有一个深层后果:模型无法区分能力的来源,只能看到统一的 name + description + whenToUse。
命令加载优先级 7 层
loadAllCommands() 按以下顺序合并所有来源,先出现者在去重时优先保留:
- Bundled Skills -- 编译进二进制的内置技能(
registerBundledSkill()) - Built-in Plugin Skills -- 内置插件提供的技能
- Skill Directory Commands --
.claude/skills/目录下的用户技能 - Workflow Commands -- 工作流脚本(feature gate:
WORKFLOW_SCRIPTS) - Plugin Commands -- Marketplace 插件中
commands/目录的命令 - Plugin Skills -- Marketplace 插件中
skills/目录的技能 - Built-in Commands -- CLI 内置命令(
/help,/clear,/compact等)
在此基础上,getCommands() 还会将动态发现的技能(getDynamicSkills())插入到 Plugin Skills 之后、Built-in Commands 之前的位置。
命令类型系统 (types/command.ts)
200 行的 types/command.ts 定义了整个可扩展层的类型基石。理解 Command 类型就理解了 Skills、Plugins、MCP 三者如何统一。
PromptCommand -- 最核心的类型
PromptCommand 是所有 Skill / Plugin Skill / MCP Skill 的底层类型。以下逐字段说明:
| 字段 | 类型 | 说明 |
|---|---|---|
type | 'prompt' | 标记此命令为 prompt 类型,与 'local'(纯函数执行)和 'local-jsx'(React UI)区分 |
progressMessage | string | 执行时显示的进度文本,如 'running' 或 'loading' |
contentLength | number | Markdown 内容长度(字符数),用于 token 估算 |
argNames | string[]? | 声明式参数名列表,从 frontmatter arguments 字段解析 |
allowedTools | string[]? | 技能执行时自动授权的工具列表 |
model | string? | 模型覆盖,如 'haiku', 'sonnet', 'opus' |
source | 枚举 | 'userSettings' | 'projectSettings' | 'policySettings' | 'builtin' | 'mcp' | 'plugin' | 'bundled' |
pluginInfo | object? | 插件元数据:{ pluginManifest, repository } |
hooks | HooksSettings? | 技能激活时注册的钩子 |
skillRoot | string? | 技能资源的根目录路径 |
context | 'inline' | 'fork'? | 执行上下文:inline 展开到当前对话,fork 在子 Agent 中独立运行 |
agent | string? | fork 模式下使用的 Agent 类型 |
effort | EffortValue? | 推理努力级别,控制 thinking budget |
paths | string[]? | 条件激活的 glob 模式。设置后,技能仅在模型触及匹配文件时可见 |
getPromptForCommand | async function | 核心方法:接收 (args, context),返回 ContentBlockParam[] |
CommandBase -- 共享基座
CommandBase 是所有三种命令类型共享的基础接口:
| 字段 | 说明 |
|---|---|
name | 唯一标识符。Plugin 命令带命名空间前缀如 plugin_name:command |
description | 描述文本,模型用它判断何时调用 |
hasUserSpecifiedDescription | 是否来自 frontmatter 的显式声明(区分自动提取) |
availability | CommandAvailability[]? -- 限制命令可见范围:'claude-ai' 或 'console' |
isEnabled | 运行时判断函数(feature flag 等) |
isHidden | 从 typeahead / help 中隐藏 |
aliases | 别名数组 |
whenToUse | 详细的使用场景描述,拼接到 description 后供模型参考 |
version | 技能版本号 |
disableModelInvocation | 禁止模型通过 SkillTool 主动调用 |
userInvocable | 用户是否可以通过 /skill-name 手动调用 |
loadedFrom | 加载来源:'commands_DEPRECATED' | 'skills' | 'plugin' | 'managed' | 'bundled' | 'mcp' |
kind | 'workflow'? -- 标记工作流类型命令 |
immediate | 是否跳过队列立即执行 |
userFacingName | 显示名称函数,当 plugin 需要去掉前缀时使用 |
source vs loadedFrom 的区别
source 表示配置来源的权限层级(policySettings > userSettings > projectSettings),决定了权限覆盖优先级和 UI 中的来源标注。
loadedFrom 表示技能的物理来源(从哪种目录/机制加载),决定了安全策略(如 MCP 来源的技能禁止执行 shell 命令)和模型可见性过滤。
Command 联合类型
export type Command = CommandBase &
(PromptCommand | LocalCommand | LocalJSXCommand)
三种子类型通过 type 字段区分:'prompt' 扩展为模型上下文,'local' 执行纯函数返回文本,'local-jsx' 渲染 React/Ink UI。
CommandAvailability 枚举
控制命令在不同认证/提供商环境中的可见性:
'claude-ai'-- 仅 claude.ai OAuth 订阅用户(Pro/Max/Team/Enterprise)可见'console'-- 仅 Console API key 直连用户可见(排除 Bedrock/Vertex/Foundry 和自定义 base URL)
Frontmatter 解析 (frontmatterParser.ts)
371 行的 frontmatter 解析器是整个 Skill 系统的入口解码器。它将 Markdown 文件开头的 YAML 前置声明转化为类型安全的 FrontmatterData 对象,处理了 glob 模式中的花括号冲突、特殊字符引号逃逸、布尔值强制转换等诸多边缘情况。
parseFrontmatter() 完整流程
- 正则匹配
/^---\s*\n([\s\S]*?)---\s*\n?/提取---之间的文本 - 首次 YAML 解析尝试(
parseYaml()) - 若失败,调用
quoteProblematicValues()对包含特殊字符的值自动加双引号,然后重试 - 若仍然失败,记录 debug 警告,返回空 frontmatter
- 返回
{ frontmatter, content },content 是去掉 frontmatter 后的 Markdown 正文
特殊字符处理
YAML 特殊字符正则:/[{}[\]*&#!|>%@`]|: /
当检测到以下字符时自动添加双引号包裹:
{ }-- flow mapping 指示符(与 glob 的*.{ts,tsx}冲突)* & # !-- YAML 锚点/注释/标签指示符| >-- block scalar 指示符:-- 键值分隔符(冒号后跟空格时才匹配,所以12:34和 URL 不受影响)
15+ Frontmatter 字段完整说明
| 字段 | 类型 | 作用 |
|---|---|---|
description | string | 技能描述,是模型决定是否调用的主要信号 |
allowed-tools | string | string[] | 技能执行期间自动授权的工具名称列表 |
when_to_use | string | 详细使用场景,拼接到 description 后 |
version | string | 技能版本号(信息性) |
user-invocable | boolean | 用户是否能通过 /name 手动调用。默认 true |
model | string | 模型覆盖。'inherit' 表示使用父级模型 |
effort | string | int | 推理努力级别:low, medium, high, max 或整数 |
context | 'inline' | 'fork' | 执行上下文。fork 创建独立子 Agent |
agent | string | fork 时使用的 Agent 类型(如 'Bash') |
argument-hint | string | 参数提示文本(typeahead 中灰色显示) |
arguments | string | string[] | 声明式参数名,支持 ${ARG_NAME} 替换 |
hide-from-slash-command-tool | boolean-like | 从 SlashCommand 工具中隐藏(旧式兼容字段) |
disable-model-invocation | boolean | 禁止模型通过 SkillTool 主动调用 |
paths | string | string[] | 条件激活的 glob 模式(gitignore 风格匹配) |
shell | 'bash' | 'powershell' | 技能中 ! 块使用的 shell 类型。由作者决定,不读用户设置 |
hooks | HooksSettings | 技能激活时注册的生命周期钩子(通过 HooksSchema 验证) |
Brace Expansion
splitPathInFrontmatter() 处理逗号分隔的路径列表,同时支持花括号展开:
splitPathInFrontmatter("src/*.{ts,tsx}")
// → ["src/*.ts", "src/*.tsx"]
splitPathInFrontmatter("{a,b}/{c,d}")
// → ["a/c", "a/d", "b/c", "b/d"]
实现原理:先按不在花括号内的逗号拆分,再对每个部分递归调用 expandBraces()。花括号内的逗号不被视为分隔符(通过 braceDepth 计数器追踪嵌套深度)。
coerceDescriptionToString()
描述值的强制转换逻辑:
- 字符串 → 原样返回(trim 后空字符串返回 null)
- 数字/布尔值 →
String()转换 - 数组/对象 → 无效,记录警告并返回 null
- null/undefined → 返回 null(调用方回退到从 Markdown 正文提取)
Skill 加载 (loadSkillsDir.ts)
750 行的 loadSkillsDir.ts 是 Skill 子系统的主引擎。它从 5 种来源并行加载技能、去重、分离条件技能、支持动态发现,是理解 Claude Code 技能生命周期的关键文件。
loadSkillsFromSkillsDir() 完整流程
- 读取目录下的所有条目
- 过滤:只接受目录和符号链接(单 .md 文件在
/skills/目录下不被支持) - 对每个目录,尝试读取
skill-name/SKILL.md - 调用
parseFrontmatter()解析 YAML 前置声明 - 调用
parseSkillFrontmatterFields()提取所有字段 - 调用
parseSkillPaths()处理条件路径模式 - 调用
createSkillCommand()创建 Command 对象 - 返回
SkillWithPath[](包含文件路径,用于后续去重)
createSkillCommand() 工厂函数
这个工厂函数接受 20+ 个命名参数,返回一个满足 Command 类型的对象。其核心是 getPromptForCommand 闭包,执行以下管线:
- Base directory 前缀:如果有
baseDir,在内容前添加"Base directory for this skill: {dir}" - 参数替换:
substituteArguments(content, args, true, argumentNames)-- 将${ARG_NAME}替换为实际参数值 - 变量替换:
${CLAUDE_SKILL_DIR}→ 技能目录路径;${CLAUDE_SESSION_ID}→ 当前会话 ID - Shell 命令执行:
executeShellCommandsInPrompt()-- 执行!`...`内联 shell 块。MCP 来源的技能跳过此步骤(安全措施) - 返回
[{ type: 'text', text: finalContent }]
Shell 命令执行 (executeShellCommandsInPrompt) 通过 loadedFrom !== 'mcp' 进行门控。远程 MCP 技能的 Markdown 内容被视为不可信,其中的 ! 块永远不会被执行。这防止了通过 MCP prompt 注入恶意 shell 命令。
getSkillDirCommands() -- 主入口
这是一个 memoize 包装的异步函数,整合所有来源:
- 并行加载 5 种来源:
- Managed Skills:
~/.claude-managed/.claude/skills/(组织管理策略推送) - User Skills:
~/.claude/skills/(用户全局技能) - Project Skills:从 cwd 向上遍历到 HOME 的所有
.claude/skills/ - Additional Dir Skills:
--add-dir参数指定的额外目录 - Legacy Commands:旧式
/commands/目录(通过loadSkillsFromCommandsDir)
- Managed Skills:
- 符号链接去重:对每个文件调用
realpath()获取规范路径,相同物理文件只保留首次出现 - 条件技能分离:有
pathsfrontmatter 的技能被存入conditionalSkillsMap,不加入主列表 - 返回无条件技能列表
estimateSkillFrontmatterTokens()
仅基于 frontmatter 字段(name + description + whenToUse)估算 token 数,因为完整内容只在调用时加载。这使得技能列表的 token 预算可以在 prompt.ts 中精确控制。
动态技能发现
当模型执行文件操作时,discoverSkillDirsForPaths() 被触发:
- 从被操作文件的父目录开始,向上遍历到 cwd(不含 cwd 本身,因为 cwd 级别的技能在启动时已加载)
- 检查每个中间目录是否存在
.claude/skills/ - 跳过被 gitignore 的目录(防止
node_modules中的恶意技能加载) - 新发现的目录调用
addSkillDirectories()加载并合并到dynamicSkillsMap - 触发
skillsLoaded信号,通知其他模块清除缓存
条件技能激活
activateConditionalSkillsForPaths() 使用 ignore 库(gitignore 风格匹配)检查文件路径是否匹配技能的 paths 模式:
// 技能 frontmatter: paths: "src/**/*.ts, tests/**"
// 当模型 Read/Write 匹配路径的文件时,技能被激活
if (skillIgnore.ignores(relativePath)) {
dynamicSkills.set(name, skill) // 移入可见列表
conditionalSkills.delete(name) // 从待激活列表移除
}
Bundled Skills (bundledSkills.ts)
221 行的 bundledSkills.ts 实现了编译进 CLI 二进制的内置技能注册机制。它的 files 字段延迟提取设计特别精巧——用 nonce 目录和 O_NOFOLLOW | O_EXCL 文件写入防御符号链接攻击。
BundledSkillDefinition 类型
| 字段 | 类型 | 说明 |
|---|---|---|
name | string | 技能名称 |
description | string | 描述 |
aliases | string[]? | 别名 |
whenToUse | string? | 使用场景描述 |
argumentHint | string? | 参数提示 |
allowedTools | string[]? | 授权工具列表 |
model | string? | 模型覆盖 |
disableModelInvocation | boolean? | 禁止模型主动调用 |
userInvocable | boolean? | 用户可调用(默认 true) |
isEnabled | () => boolean? | Feature gate 函数 |
hooks | HooksSettings? | 生命周期钩子 |
context | 'inline' | 'fork'? | 执行上下文 |
agent | string? | Agent 类型 |
files | Record<string, string>? | 需要提取到磁盘的参考文件(相对路径 → 内容) |
getPromptForCommand | async function | 核心执行函数 |
files 延迟提取机制
当 bundled skill 定义了 files 字段时,这些文件并不在模块加载时写入磁盘,而是在技能首次被调用时才提取:
getBundledSkillExtractDir(name)返回确定性提取路径:{bundledSkillsRoot}/{skillName}/bundledSkillsRoot包含 per-process nonce(进程级随机数),防止预创建的符号链接攻击- 提取 Promise 被闭包内的
extractionPromise变量缓存,并发调用者 await 同一个 Promise(不竞争写入) - 文件写入使用
O_WRONLY | O_CREAT | O_EXCL | O_NOFOLLOW标志位:O_EXCL:文件已存在则失败(不覆盖)O_NOFOLLOW:最后一段是符号链接则失败- 权限
0o600(仅 owner 可读写),目录0o700
- 提取成功后,在原始 prompt 内容前添加
"Base directory for this skill: {dir}"
内置技能列表
从 skills/bundled/ 目录可以看到以下 bundled skills(每个文件是一个独立的 registerBundledSkill() 调用):
| 文件 | 技能 |
|---|---|
batch.ts | 批处理执行 |
claudeApi.ts + claudeApiContent.ts + claude-api/ | Claude API / Anthropic SDK 使用指南 |
claudeInChrome.ts | Chrome 浏览器集成技能 |
debug.ts | 调试辅助 |
dream.ts | 创意/规划 |
hunter.ts | 代码搜索猎手 |
keybindings.ts | 快捷键配置指南 |
loop.ts | 定时循环执行 |
remember.ts | 记忆管理 |
scheduleRemoteAgents.ts | 远程 Agent 调度 |
simplify.ts | 代码简化审查 |
skillify.ts | 技能创建器 |
stuck.ts | 卡住恢复 |
updateConfig.ts | 配置更新 |
verify.ts + verify/ | 验证执行 |
registerBundledSkill() 实现
注册过程将 BundledSkillDefinition 转换为标准 Command 对象,固定 source: 'bundled'、loadedFrom: 'bundled'、contentLength: 0(bundled skills 不需要 token 估算)。注册发生在模块初始化阶段(import 时的副作用),在 commands.ts 的 getSkills() 中通过 getBundledSkills() 同步获取。
SkillTool 执行 (SkillTool.ts)
SkillTool 是模型调用技能的唯一入口。它的 500+ 行代码实现了从输入验证到权限检查到 inline/fork 双模执行的完整管线,并包含精细的遥测和安全控制。
getAllCommands()
SkillTool 需要同时看到本地技能和 MCP 技能。它从 context.getAppState().mcp.commands 中过滤出 loadedFrom === 'mcp' 的 prompt 命令,与 getCommands() 的本地结果合并,使用 uniqBy('name') 去重(本地优先)。
getSkillToolCommands() 过滤逻辑
模型可见的命令必须满足所有条件:
type === 'prompt'!disableModelInvocationsource !== 'builtin'(内置命令通过其他方式提供给模型)- 以下至少满足一项:
loadedFrom为'bundled' | 'skills' | 'commands_DEPRECATED',或有hasUserSpecifiedDescription,或有whenToUse
validateInput() 验证链
- 去除前导
/斜杠(兼容性处理) - 远程 canonical skill 拦截(仅 ant 用户,
EXPERIMENTAL_SKILL_SEARCH) - 从
getAllCommands()查找匹配命令(支持名称、别名匹配) - 检查
disableModelInvocation标志 - 检查
type === 'prompt'(非 prompt 命令不能通过 SkillTool 调用)
checkPermissions() 权限决策
权限决策遵循严格的优先级:
- Deny 规则:检查
getRuleByContentsForTool()中的拒绝规则。支持精确匹配和前缀匹配(如review:*) - Remote canonical skills:自动授权(ant-only,但在 deny 之后检查)
- Allow 规则:检查显式允许规则
- Safe properties 自动授权:如果技能只有
SAFE_SKILL_PROPERTIES集合中的属性有值,自动允许(允许列表设计——新增属性默认需要权限) - 兜底:返回
ask,提供精确匹配和前缀匹配的 allow rule 建议
call() -- 双模执行
Inline 执行(默认)
调用 processPromptSlashCommand(),将技能内容展开为 user message 注入当前对话。返回 newMessages 和 contextModifier(用于注入 allowedTools、model override、effort level)。
Fork 执行(context === 'fork')
executeForkedSkill() 创建独立子 Agent:
- 生成唯一
agentId - 调用
prepareForkedCommandContext()构建隔离上下文 - 通过
runAgent()在独立 token 预算中执行 - 收集子 Agent 消息,汇报进度
- 提取结果文本,返回
{ status: 'forked', result: ... } - finally 块中调用
clearInvokedSkillsForAgent(agentId)释放内存
Prompt Budget 管理 (prompt.ts)
技能列表占用上下文窗口的 1%(SKILL_BUDGET_CONTEXT_PERCENT = 0.01),默认 8000 字符。当技能过多时:
- Bundled skills 的描述永远不被截断
- 非 bundled skills 的描述按均分预算截断
- 极端情况下,非 bundled skills 降级为仅显示名称(无描述)
- 单条描述硬上限 250 字符(
MAX_LISTING_DESC_CHARS)
Plugin Manifest 与类型 (schemas.ts)
1000+ 行的 schemas.ts 使用 Zod v4 定义了插件生态系统的完整 schema 体系,从 marketplace 名称验证到 MCP 服务器配置到 LSP 集成,覆盖了 Anthropic 官方 marketplace 名称保护和 Unicode 同形攻击防御。
PluginManifest 完整结构
PluginManifestSchema 由多个 .partial() schema 交叉组合而成,每个子 schema 独立验证其关注的字段:
| 子 Schema | 字段 | 说明 |
|---|---|---|
| PluginManifestMetadataSchema | name, version, description, author, homepage, repository, license, keywords, dependencies | 插件元数据 |
| PluginManifestCommandsSchema | commands | 额外命令路径(3 种格式) |
| PluginManifestSkillsSchema | skills | 额外技能目录路径 |
| PluginManifestAgentsSchema | agents | 额外 Agent 文件路径 |
| PluginManifestOutputStylesSchema | outputStyles | 输出样式目录 |
| PluginManifestHooksSchema | hooks | 钩子配置(JSON 路径或内联) |
| PluginManifestMcpServerSchema | mcpServers | MCP 服务器配置(多格式) |
| PluginManifestLspServerSchema | lspServers | LSP 服务器配置 |
| PluginManifestUserConfigSchema | userConfig | 用户可配置选项 |
| PluginManifestChannelsSchema | channels | 消息通道声明(Telegram 等) |
userConfig schema
每个选项字段支持以下属性:
type:'string' | 'number' | 'boolean' | 'directory' | 'file'title: 配置对话框中显示的标签(必填)description: 字段下方的帮助文本(必填)required: 验证空值是否失败default: 用户不输入时的默认值sensitive: 标记为敏感时,值存入 secure storage(macOS keychain),而非 settings.jsonmin/max: 数值类型的范围约束
Marketplace 名称安全
多层防御:
- 保留名称:
ALLOWED_OFFICIAL_MARKETPLACE_NAMES定义了 8 个 Anthropic 官方名称 - 模式检测:正则
BLOCKED_OFFICIAL_NAME_PATTERN拦截冒充变体 - Unicode 同形攻击:
NON_ASCII_PATTERN检测非 ASCII 字符 - 来源验证:
validateOfficialNameSource()强制保留名称只能从anthropicsGitHub org 安装
CommandMetadata (object-mapping 格式)
允许 marketplace 条目为命令提供富元数据:
{
"commands": {
"about": {
"source": "./README.md", // 或 "content": "inline markdown..."
"description": "描述覆盖",
"argumentHint": "[file]",
"model": "haiku",
"allowedTools": ["Read", "Grep"]
}
}
}
.refine() 验证 source 和 content 互斥——必须有且只有一个。
Plugin 加载管理器 (pluginLoader.ts)
2700 行的 pluginLoader.ts 是整个插件子系统的心脏。它处理从 marketplace 发现、git 克隆、npm 安装、版本管理到插件激活的完整生命周期。
loadAllPlugins() 完整流程
- 调用
assemblePluginLoadResult()并传入加载器函数 - 加载器内部调用
loadPluginsFromMarketplaces()扫描所有配置的 marketplace - 对每个 marketplace 条目,调用
loadPluginFromMarketplaceEntry()/loadPluginFromMarketplaceEntryCacheOnly() - 并行加载 session 插件(
--plugin-dir)和 built-in 插件 - 调用
mergeAndDeduplicatePlugins()去重和冲突解决 - 应用 enable/disable 状态
- 返回
PluginLoadResult:{ enabled: LoadedPlugin[], disabled: LoadedPlugin[], errors: PluginError[] }
3 种来源
| 来源 | 格式 | 加载方式 |
|---|---|---|
| Marketplace | plugin_name@marketplace_name | 通过 marketplace.json 注册表发现,git clone + 版本缓存 |
| Session-only | --plugin-dir CLI 参数 | 直接从本地目录加载,不缓存 |
| Built-in | BUILTIN_MARKETPLACE_NAME | 编译进二进制,从 plugins/bundled/ 注册 |
createPluginFromPath() -- 核心组装
从目录路径创建完整的 LoadedPlugin:
- 加载
.claude-plugin/plugin.json(可选,缺失时创建默认 manifest) - 并行检测
commands/、agents/、skills/、output-styles/目录是否存在 - 处理 manifest 中声明的额外路径(3 种格式:单路径、数组、object-mapping)
- 加载
hooks/hooks.json和 manifest 中的额外钩子 - 返回
{ plugin: LoadedPlugin, errors: PluginError[] }
版本管理: 目录结构
~/.claude/plugins/
├── cache/
│ └── {marketplace}/
│ └── {plugin}/
│ └── {version}/ ← 版本隔离目录
│ ├── .claude-plugin/plugin.json
│ ├── commands/
│ ├── skills/
│ └── hooks/
├── npm-cache/ ← npm 包全局缓存
└── data/
└── {marketplace}/{plugin}/ ← 跨版本持久数据 (PLUGIN_DATA)
版本切换通过 symlink 实现:当前活动版本的目录通过 resolvePluginPath() 解析。旧版本保留但不被引用,最终由 orphan cleanup 清理。
Seed Cache
probeSeedCache() 在安装前先检查 seed 目录(预填充缓存),避免首次安装时的网络请求。这对企业环境中的离线部署尤其重要。
LoadedPlugin 类型核心字段
| 字段 | 说明 |
|---|---|
name | 插件名称 |
manifest | 完整 PluginManifest |
path | 插件目录绝对路径 |
source | 来源标识(name@marketplace) |
repository | 向后兼容别名,等于 source |
enabled | 是否启用 |
commandsPath | 自动检测的 commands/ 目录路径 |
commandsPaths | manifest 声明的额外命令路径 |
commandsMetadata | object-mapping 格式的命令元数据 |
skillsPath | skills/ 目录路径 |
skillsPaths | manifest 声明的额外技能路径 |
hooksConfig | 解析后的钩子配置 |
mcpServers | MCP 服务器配置(缓存) |
Plugin 命令与变量替换
loadPluginCommands.ts (510 行)
插件命令加载流程:
getPluginCommands():遍历所有 enabled plugins,从commandsPath、commandsPaths、commandsMetadata三个来源加载命令collectMarkdownFiles():递归收集目录中的 .md 文件transformPluginSkillFiles():处理目录中的 SKILL.md(目录模式优先于散文件)createPluginCommand():创建 Command 对象(与createSkillCommand功能对等,但增加了 plugin 变量替换)
命名空间规则
plugin_name:command_name ← 直接子文件
plugin_name:subdir:command_name ← 嵌套目录
plugin_name:skill_dir_name ← SKILL.md 格式
getPluginSkills() 技能加载
并行处理每个插件的 skillsPath 和 skillsPaths,调用 loadSkillsFromDirectory()。该函数首先检查根目录是否直接包含 SKILL.md(直接技能目录),否则扫描子目录。
pluginOptionsStorage.ts (400 行)
变量替换体系
| 变量 | 替换为 | 作用域 |
|---|---|---|
${CLAUDE_PLUGIN_ROOT} | 插件安装目录(版本隔离,更新时重建) | MCP/LSP 配置、钩子命令、技能内容 |
${CLAUDE_PLUGIN_DATA} | 持久数据目录(跨版本保留) | 同上 |
${CLAUDE_SKILL_DIR} | 当前技能的目录(区别于插件根目录) | 技能内容 |
${CLAUDE_SESSION_ID} | 当前会话 ID | 技能内容 |
${user_config.KEY} | 用户配置值 | MCP/LSP 配置(直接替换)、技能内容(敏感值遮蔽) |
敏感值处理
substituteUserConfigInContent() 用于技能/Agent 内容中的变量替换。与 MCP 配置中的 substituteUserConfigVariables() 不同,它对 sensitive: true 的字段返回占位符而非实际值:
// 技能内容中引用敏感字段
"${user_config.api_key}"
// → "[sensitive option 'api_key' not available in skill content]"
这是因为技能内容会被发送到模型上下文——不应该在 prompt 中暴露 API key 或密码。
存储分层
- 非敏感值:
settings.json → pluginConfigs[pluginId].options - 敏感值:
secureStorage(macOS keychain 或.credentials.json) - 读取时合并,secureStorage 在冲突时胜出
- 写入时先 secureStorage 再 settings.json(keychain 失败则 throw,不会写半个状态)
Plugin Hook 系统
200 行的 loadPluginHooks.ts 将插件的钩子配置转化为 Claude Code 原生的 hook matcher,支持 26 种生命周期事件。
完整事件类型列表
| 事件 | 触发时机 |
|---|---|
PreToolUse | 工具调用前 |
PostToolUse | 工具调用后(成功) |
PostToolUseFailure | 工具调用后(失败) |
PermissionDenied | 权限被拒绝 |
PermissionRequest | 权限请求 |
Notification | 通知 |
UserPromptSubmit | 用户提交 prompt |
SessionStart | 会话开始 |
SessionEnd | 会话结束 |
Stop | 模型停止 |
StopFailure | 停止失败 |
SubagentStart | 子 Agent 启动 |
SubagentStop | 子 Agent 停止 |
PreCompact | 上下文压缩前 |
PostCompact | 上下文压缩后 |
Setup | 初始设置 |
TeammateIdle | 队友空闲 |
TaskCreated | 任务创建 |
TaskCompleted | 任务完成 |
Elicitation | 引发式交互 |
ElicitationResult | 引发结果 |
ConfigChange | 配置变更 |
WorktreeCreate | 工作树创建 |
WorktreeRemove | 工作树移除 |
InstructionsLoaded | 指令加载完成 |
CwdChanged | 工作目录变更 |
FileChanged | 文件变更 |
convertPluginHooksToMatchers()
将插件的 hooksConfig 转换为注入了插件上下文的 PluginHookMatcher[]。每个 matcher 携带 pluginRoot、pluginName、pluginId 三个额外字段,使得 hook 执行器可以在正确的插件目录上下文中运行命令。
原子性 clear-then-register
旧版本在 clearPluginHookCache() 中同时清除 memoize 缓存和 STATE.registeredHooks。这导致任何 clearAllCaches() 调用(插件管理、thinkback 等)都会清空所有插件钩子,直到下一次 loadPluginHooks() 才恢复。Stop 钩子没有保护机制——它们在清除后永远不会重新注册。修复后,清除和注册在 loadPluginHooks() 内部原子执行,旧钩子在新钩子就绪前保持有效。
Hot Reload
setupPluginHookHotReload() 监听 policySettings 变更(远程管理设置推送),通过 getPluginAffectingSettingsSnapshot() 比较 4 个字段的 JSON 快照:enabledPlugins、extraKnownMarketplaces、strictKnownMarketplaces、blockedMarketplaces。只有实际变化时才触发重加载。
MCP 客户端架构 (client.ts)
3000 行的 client.ts 实现了完整的 MCP 客户端栈:4 种传输层适配、OAuth 认证流、工具动态注册、prompt/skill 获取、资源管理和连接生命周期。
4 种传输层
| 类型 | 类 | 使用场景 |
|---|---|---|
stdio | StdioClientTransport | 本地进程(最常用):启动子进程,通过 stdin/stdout 通信 |
sse | SSEClientTransport | 远程服务器(Server-Sent Events):单向推送 + HTTP POST 回传 |
http | StreamableHTTPClientTransport | 远程服务器(Streamable HTTP):双向 HTTP 流 |
ws | WebSocketTransport | WebSocket 全双工连接 |
此外还有特殊类型:sse-ide/ws-ide(IDE 代理)、sdk(SDK 控制)、claudeai-proxy(claude.ai 代理)。
MCPTool 包装器
MCPTool.ts 仅 78 行,定义了一个"壳"工具。所有实质性属性(name、description、prompt、call、inputSchema)在 client.ts 中按 MCP 服务器的工具规格动态覆盖。关键标记 isMcp: true 使得权限系统和遥测可以区分 MCP 工具。
输入 schema 使用 z.object({}).passthrough()——接受任何对象,因为实际的 schema 由 MCP 服务器定义。
连接管理
ensureConnectedClient() 是连接的核心入口:
- 检查连接缓存(per-server 的
MCPServerConnection对象) - 处理
needs-auth状态(OAuth 流程) - 处理 session expired 错误(
McpSessionExpiredError:HTTP 404 + JSON-RPC -32001) - 超时管理:默认 ~27.8 小时(
DEFAULT_MCP_TOOL_TIMEOUT_MS = 100_000_000)
认证流
Claude.ai proxy 连接使用自定义 fetch wrapper(createClaudeAiProxyFetch):
- 在每个请求中注入 OAuth Bearer token
- 401 时调用
handleOAuth401Error()尝试刷新 token - 如果 token 确实更新了(非相同 token 重试),再次发送请求
- 15 分钟 needs-auth 缓存(
MCP_AUTH_CACHE_TTL_MS)避免重复认证尝试
工具描述截断
OpenAPI 生成的 MCP 服务器常常产生 15-60KB 的工具描述。MAX_MCP_DESCRIPTION_LENGTH = 2048 字符硬上限防止模型上下文被单个工具描述淹没。
MCP Skill 融合
MCP prompts 可以作为 Skills 暴露给模型——这是一个通过 mcpSkillBuilders.ts 的注册模式实现的精妙设计,目的是打破 client.ts → loadSkillsDir.ts 的循环依赖。
mcpSkillBuilders.ts: 依赖解耦器
45 行的 mcpSkillBuilders.ts 是一个依赖图叶子模块。它只导入类型(不导入实现),提供一个 write-once 注册表:
// 类型声明
export type MCPSkillBuilders = {
createSkillCommand: typeof createSkillCommand
parseSkillFrontmatterFields: typeof parseSkillFrontmatterFields
}
// 注册表
let builders: MCPSkillBuilders | null = null
export function registerMCPSkillBuilders(b: MCPSkillBuilders): void {
builders = b
}
export function getMCPSkillBuilders(): MCPSkillBuilders {
if (!builders) throw new Error('Not registered yet')
return builders
}
注册在 loadSkillsDir.ts 模块初始化时发生(文件底部的 registerMCPSkillBuilders({...}))。由于 loadSkillsDir.ts 通过 commands.ts 的静态导入在启动时被求值,注册一定在任何 MCP 服务器连接之前完成。
Bun bundled binary 中的非字面量 dynamic import(await import(variable))会失败——specifier 被解析为 /$bunfs/root/... 路径而非源码树路径。字面量 dynamic import 可以工作,但 dependency-cruiser 会追踪它,而 loadSkillsDir 传递性地 reach 几乎所有模块,单条新边会扇出大量循环依赖违规。注册模式是唯一可行的解耦方案。
MCP_SKILLS Feature Gate
MCP prompt → Skill 的转换受 feature('MCP_SKILLS') 门控。开启后:
client.ts中的fetchMcpSkillsForClient被条件 require 加载- MCP 服务器的 prompts 被转化为
loadedFrom: 'mcp'的 Command 对象 - 这些 Command 存储在
AppState.mcp.commands中
SkillTool 中的 MCP Skills
getAllCommands() 从 AppState 中过滤 MCP skills(cmd.type === 'prompt' && cmd.loadedFrom === 'mcp'),与本地命令合并。getMcpSkillCommands() 进一步过滤掉 disableModelInvocation 的命令。
MCP skills 在 SkillTool 执行时有一个关键限制:executeShellCommandsInPrompt() 被 loadedFrom !== 'mcp' 条件跳过,即远程 MCP skill 中的 shell 命令块永远不会被执行。
Plugin MCP 集成 (mcpPluginIntegration.ts)
180 行的集成层将插件的 MCP 服务器配置解析、变量替换、命名空间隔离和 MCPB (DXT) 文件处理统一在一起。
loadPluginMcpServers() 加载优先级
- 最低优先级:
.mcp.json文件(插件目录下的标准 MCP 配置文件) - 中等优先级:
manifest.mcpServers(plugin.json 中声明的服务器),支持多种格式:- 字符串:JSON 文件路径或 MCPB 文件路径
- 数组:多个路径或内联配置的混合
- 对象:直接的 MCP 服务器配置
MCPB (DXT) 文件处理
MCPB 是 MCP Bundle 格式(支持 .mcpb 和 .dxt 扩展名),可以是本地路径或远程 URL。加载流程:
- 调用
loadMcpbFile()下载/解压 - 检查是否需要用户配置(
status: 'needs-config') - 如果需要配置,静默跳过(不是错误),用户可通过
/plugin菜单配置 - 使用 DXT manifest 中的 name 作为 server name
命名空间隔离
addPluginScopeToServers() 为每个插件的 MCP 服务器添加 plugin:{pluginName}:{serverName} 前缀,防止不同插件间的命名冲突。同时设置 scope: 'dynamic' 和 pluginSource 用于追踪来源。
环境变量解析
resolvePluginMcpEnvironment() 按顺序解析:
- Plugin 专用变量(
${CLAUDE_PLUGIN_ROOT}等) - 用户配置变量(
${user_config.KEY}) - 通用环境变量(
${HOME}等,通过expandEnvVarsInString())
对 stdio 类型的服务器,还自动注入 CLAUDE_PLUGIN_ROOT 和 CLAUDE_PLUGIN_DATA 环境变量。
Channel 用户配置
Channels(Telegram、Discord 等消息通道)可以有独立于插件顶级 userConfig 的 per-server 配置。buildMcpUserConfig() 合并两层配置,channel-specific 在冲突时胜出。
命令加载与优先级 (commands.ts)
754 行的 commands.ts 是三个子系统的汇聚点。它定义了 80+ 个内置命令、加载优先级链、去重逻辑和模型可见性过滤。
loadAllCommands() 完整流程
const loadAllCommands = memoize(async (cwd) => {
const [
{ skillDirCommands, pluginSkills, bundledSkills, builtinPluginSkills },
pluginCommands,
workflowCommands,
] = await Promise.all([
getSkills(cwd), // 并行加载 4 类技能
getPluginCommands(), // 加载插件命令
getWorkflowCommands?.(cwd), // 加载工作流命令
])
return [
...bundledSkills, // 1. 编译内置(最高优先)
...builtinPluginSkills, // 2. 内置插件技能
...skillDirCommands, // 3. 用户/项目技能目录
...workflowCommands, // 4. 工作流脚本
...pluginCommands, // 5. 插件命令
...pluginSkills, // 6. 插件技能
...COMMANDS(), // 7. 内置 CLI 命令
]
})
getCommands() -- 运行时过滤
虽然 loadAllCommands 被 memoize,getCommands() 每次调用都重新执行过滤(因为认证状态可能变化):
meetsAvailabilityRequirement():检查availability枚举(claude-ai / console)isCommandEnabled():调用命令的isEnabled()函数(feature flag 等)- 动态技能注入:
getDynamicSkills()的结果去重后插入到 Plugin Skills 之后、Built-in Commands 之前
getSkillToolCommands() vs getSlashCommandToolSkills()
| 函数 | 用途 | 过滤条件 |
|---|---|---|
getSkillToolCommands | 模型通过 SkillTool 可调用的所有命令 | type=prompt, !disableModelInvocation, source!='builtin', 且有描述或 whenToUse |
getSlashCommandToolSkills | 技能统计/管理用 | 同上但限定 loadedFrom 为 skills/plugin/bundled |
getMcpSkillCommands | MCP 提供的技能 | loadedFrom='mcp', !disableModelInvocation(受 MCP_SKILLS gate 控制) |
完整优先级图
┌───────────────────────────────┐
│ loadAllCommands() 7层加载 │
│ │
│ 1. bundledSkills │ registerBundledSkill()
│ 2. builtinPluginSkills │ plugins/bundled/
│ 3. skillDirCommands │ .claude/skills/ (5 sources)
│ 4. workflowCommands │ WORKFLOW_SCRIPTS gate
│ 5. pluginCommands │ plugin commands/ dir
│ 6. pluginSkills │ plugin skills/ dir
│ 7. COMMANDS() │ 80+ built-in commands
└──────────────┬────────────────┘
│
┌──────────────▼────────────────┐
│ getCommands() 运行时过滤 │
│ │
│ + meetsAvailabilityRequirement│
│ + isCommandEnabled() │
│ + getDynamicSkills() inject │
│ + MCP skills (via AppState) │
└──────────────┬────────────────┘
│
┌──────────────▼────────────────┐
│ getSkillToolCommands() │
│ 模型可见的最终列表 │
│ → prompt.ts budget 管理 │
│ → SkillTool system-reminder │
└───────────────────────────────┘
缓存清理策略
| 函数 | 清除范围 |
|---|---|
clearCommandMemoizationCaches() | 仅清除 loadAllCommands / getSkillToolCommands / getSlashCommandToolSkills / skillIndex 的 memoize 缓存。不清除 skill 目录缓存(保留动态技能) |
clearCommandsCache() | 上述全部 + clearPluginCommandCache + clearPluginSkillsCache + clearSkillCaches(完全重加载) |
findCommand() 匹配逻辑
按以下优先级匹配:_.name === commandName → getCommandName(_) === commandName(userFacingName)→ _.aliases?.includes(commandName)。第一个匹配项返回。
Skill 文件变更检测 (skillChangeDetector.ts)
312 行的变更检测器使用 chokidar 监视技能目录,处理了 Bun 的 FSWatcher 死锁 bug、debounce 防抖和 ConfigChange 钩子集成。
监视路径列表
~/.claude/skills/-- 用户全局技能~/.claude/commands/-- 用户全局命令(旧式).claude/skills/-- 项目技能.claude/commands/-- 项目命令(旧式)--add-dir目录下的.claude/skills/
Bun deadlock workaround
Bun 的原生 fs.watch() 的 PathWatcherManager 存在死锁问题:在主线程关闭 watcher 时如果 File Watcher 线程正在投递事件,两者会在 __ulock_wait2 上永远阻塞。chokidar 在 depth: 2 模式下对大型技能树(数百个子目录)在 git 操作时可靠触发此 bug。
解决方案:在 Bun 环境下强制使用 usePolling: true(stat() 轮询),轮询间隔 2000ms。技能文件变更频率很低(手动编辑或 git 操作),2s 延迟换取零死锁风险。
Debounce 300ms
scheduleReload() 将快速连续的文件变更事件合并为单次重载:
function scheduleReload(changedPath: string): void {
pendingChangedPaths.add(changedPath)
if (reloadTimer) clearTimeout(reloadTimer)
reloadTimer = setTimeout(async () => {
// 1. 触发 ConfigChange 钩子(一次,不是每个文件一次)
const results = await executeConfigChangeHooks('skills', paths[0]!)
if (hasBlockingResult(results)) return // 钩子可以阻止重载
// 2. 清除所有缓存
clearSkillCaches()
clearCommandsCache()
resetSentSkillNames()
// 3. 通知监听者
skillsChanged.emit()
}, RELOAD_DEBOUNCE_MS)
}
缓存清理三件套
文件变更触发时,按序清理:
clearSkillCaches()-- 清除 skill 目录加载的 memoize 缓存 + 条件技能 Map + 已激活技能名称 SetclearCommandsCache()-- 清除命令合并的 memoize 缓存resetSentSkillNames()-- 重置已发送给模型的技能名称集合(确保变更后的技能重新出现在 system-reminder 中)
Skill Search (实验性)
受 EXPERIMENTAL_SKILL_SEARCH feature gate 控制的实验性功能,允许模型从远程 canonical skills 仓库中发现和加载技能。
DiscoverSkillsTool
工具名称 discover_skills,允许模型主动搜索远程技能仓库。发现的技能以 _canonical_{slug} 前缀命名,与本地技能区分。
远程 Canonical Skills
SkillTool 中的远程技能处理流程:
validateInput()中stripCanonicalPrefix()提取 slug- 检查
getDiscoveredRemoteSkill(slug)确认在本次会话中被发现过 call()中调用loadRemoteSkill(slug, url)从 AKI/GCS 加载(带本地缓存)- 去掉 YAML frontmatter,注入 base directory header 和变量替换
- 通过
addInvokedSkill()注册到 compaction-preservation 状态 - 直接包装为 user message 注入对话(不经过
processPromptSlashCommand)
本地搜索索引
services/skillSearch/ 目录包含:
localSearch.ts-- 本地技能索引和搜索remoteSkillLoader.ts-- 远程技能下载和缓存remoteSkillState.ts-- 会话内的远程技能发现状态telemetry.ts-- 搜索和加载遥测featureCheck.ts-- Feature gate 检查prefetch.ts-- 技能预加载
当前 mcpSkills.ts 是一个占位文件:export async function fetchMcpSkillsForClient() { return [] }。MCP Skills 的实际获取逻辑尚在开发中。
与 AI-Fleet 的对照
Claude Code 的 Layer 5 与 AI-Fleet 的技能系统在设计哲学上存在根本差异。Claude Code 追求工业级的插件生态和类型安全,AI-Fleet 追求极简的个人工具链。以下逐维度对比。
Frontmatter 字段对比
| 维度 | Claude Code | AI-Fleet |
|---|---|---|
| 字段数量 | 15+ 个 frontmatter 字段,每个有完整的解析/验证/回退逻辑 | 少量非标准字段,无 schema 验证 |
| 执行上下文 | context: 'inline' | 'fork',fork 创建独立子 Agent | 无此概念 |
| 条件激活 | paths 字段 + gitignore 风格匹配 | 无条件激活机制 |
| Shell 控制 | shell: 'bash' | 'powershell',技能作者决定 | 无此字段 |
| 模型覆盖 | model + effort 精确控制 | 无模型覆盖 |
| 钩子支持 | Frontmatter 内可声明 26 种生命周期钩子 | 无钩子系统 |
Plugin 系统对比
| 维度 | Claude Code | AI-Fleet |
|---|---|---|
| 代码量 | pluginLoader.ts 2700 行 + 配套模块数千行 | 无 plugin 系统 |
| 来源管理 | Marketplace 注册表、git clone、npm install、版本隔离 | 手动目录管理 |
| 安全模型 | Manifest schema 验证、marketplace 名称保护、Unicode 攻击防御、路径遍历检查 | 手动审查(skill-vetter 技能) |
| 变量替换 | 5 种变量 + 敏感值分层存储 | 无变量替换机制 |
| 生命周期钩子 | 26 种事件、原子注册、hot reload | 通过 settings.json hooks 手动配置 |
| 用户配置 | Schema-driven 配置对话框、secure storage 分层 | 无此机制 |
MCP 集成对比
| 维度 | Claude Code | AI-Fleet |
|---|---|---|
| MCP Skill 融合 | MCP prompts → PromptCommand,受 MCP_SKILLS gate 控制,安全限制 shell 执行 | MCP 工具直接调用,无 Skill 层中介 |
| 认证 | OAuth、Bearer token、session 管理、15min auth cache | 依赖 .mcp.json 静态配置 |
| Plugin-MCP 桥接 | 插件可声明 MCP 服务器,自动命名空间隔离和变量替换 | 无此桥接 |
| 传输层 | 4 种(Stdio/SSE/HTTP/WebSocket)+ 特殊类型 | Stdio 为主 |
文件监视对比
| 维度 | Claude Code | AI-Fleet |
|---|---|---|
| 实现 | chokidar + Bun 死锁 workaround + debounce 300ms + ConfigChange 钩子 | 无自动监视,手动重载 |
| 缓存策略 | 多层 memoize + 选择性清除(clearCommandMemoizationCaches vs clearCommandsCache) | 无缓存体系 |
| 动态发现 | 文件操作时自动发现子目录中的技能 + 条件技能激活 | 无动态发现 |
Claude Code 的 Layer 5 是为多租户平台级生态设计的——它需要处理不可信的第三方插件、跨组织的策略管控、marketplace 供应链安全。AI-Fleet 是为单用户私有工具链设计的——它追求最小配置、最大灵活性、零 overhead。两者解决的是不同层次的问题,但 Claude Code 的很多设计模式(注册模式解耦循环依赖、frontmatter 渐进式字段解析、条件技能激活)值得 AI-Fleet 在需要时借鉴。