职责概述

解决的问题:用户想给 CC 加新能力,但不想写代码。同时,开发者想分发自己做的 CC 扩展。需要一个完整的插件生态:用户能发现、安装、管理插件;开发者能打包、发布、更新插件;CC 能安全地加载和运行插件。

应用场景:① 用户从市场安装一个"代码审查"插件,CC 就会自动在每次提交前做 review ② 团队在 .claude/commands/ 目录放几个 Markdown 文件,团队成员就能用 /review/deploy 等自定义命令 ③ 插件开发者打包技能+MCP Server+Hooks 为一个完整的分发单元 ④ 文件变化时自动热加载更新后的插件。

一句话理解:就像 VS Code 的扩展系统——有市场、有安装、有配置、有热加载,用户和开发者各取所需。

架构设计

React 集成层
hooks/useManagePlugins.ts
hooks/useMergedTools.ts
utils/plugins/refresh.ts
插件生命周期
utils/plugins/pluginLoader.ts(loadAllPlugins)
utils/plugins/validatePlugin.ts
utils/plugins/dependencyResolver.ts
utils/plugins/pluginPolicy.ts
技能系统
skills/bundledSkills.ts(注册表)
skills/loadSkillsDir.ts(目录扫描)
skills/mcpSkillBuilders.ts(MCP 技能)
utils/skills/skillChangeDetector.ts(文件监控)
插件组件加载
utils/plugins/loadPluginCommands.ts
utils/plugins/loadPluginHooks.ts
utils/plugins/loadPluginAgents.ts
utils/plugins/mcpPluginIntegration.ts
utils/plugins/lspPluginIntegration.ts
市场与安装
utils/plugins/officialMarketplace.ts
utils/plugins/pluginVersioning.ts
utils/plugins/installedPluginsManager.ts
utils/plugins/orphanedPluginFilter.ts

核心数据流

插件加载流程

1. loadAllPlugins()
从 session(--plugin-dir)、marketplace(已安装)、builtin 三个来源收集插件,返回 { enabled, disabled, errors }
2. createPluginFromPath()
读取 plugin.json 清单,校验 skills/agents/hooks/mcpServers 路径,加载 hooks 配置,合并用户设置,构建 LoadedPlugin 对象
3. verifyAndDemote()
依赖解析器检查每个启用插件的依赖是否满足,缺失依赖的插件自动降级为 disabled
4. 组件加载
并发加载 commands、agents、hooks、MCP servers、LSP servers,错误收集到 errors 数组
5. AppState 同步
useManagePlugins 将 enabled/disabled/commands/errors 写入 AppState.plugins,触发 MCP 重连和工具池重组

技能发现流程

1. Bundled 技能
initBundledSkills() 在启动时调用各 register*Skill() 函数,注册到 BUNDLED_SKILLS 全局注册表,立即可用
2. 目录技能
loadSkillsFromSkillsDir() 扫描 .claude/skills/ 和 settings 层级的 skills/ 目录,解析 frontmatter 创建 Command 对象
3. 插件技能
从 LoadedPlugin 的 skills/ 目录加载 Markdown 文件,合并到插件的 commands 列表
4. MCP 技能
从 MCP 服务器的 skill:// 资源发现技能定义,通过 fetchMcpSkillsForClient() 转化为 Command
5. 条件激活
带 paths frontmatter 的技能仅在编辑匹配文件时通过 activateConditionalSkillsForPaths() 激活

关键类型与接口

插件定义类型

// skills/bundledSkills.ts:15-41
export type BundledSkillDefinition = {
  name: string
  description: string
  allowedTools?: string[]
  argumentHint?: string
  whenToUse?: string
  model?: string
  disableModelInvocation?: boolean
  userInvocable?: boolean
  defaultEnabled?: boolean
  isEnabled?: () => boolean
  // 钩子、上下文、Agent 定义等
  hooks?: HooksSettings
  context?: string
  agent?: AgentDefinition
  // 参考文件(提取到磁盘供模型读取)
  files?: Record<string, string>
  // 自定义提示生成
  getPromptForCommand?: (args: string, toolUseContext: ToolUseContext)
    => Promise<ContentBlockParam[]>
}

内建插件定义

// plugins/builtinPlugins.ts — 内建插件注册表
const BUILTIN_PLUGINS: Map<string, BuiltinPluginDefinition> = new Map()

export function registerBuiltinPlugin(definition: BuiltinPluginDefinition): void {
  BUILTIN_PLUGINS.set(definition.name, definition)
}

// 获取启用的内建插件,转换为 LoadedPlugin 对象
export function getBuiltinPlugins(): {
  enabled: LoadedPlugin[]
  disabled: LoadedPlugin[]
}

技能 Frontmatter 解析

// skills/loadSkillsDir.ts:185-265
export function parseSkillFrontmatterFields(
  frontmatter: FrontmatterData,
  markdownContent: string,
  resolvedName: string,
  descriptionFallbackLabel: 'Skill' | 'Custom command' = 'Skill',
): {
  name: string
  description: string
  allowedTools: string[]
  argumentHint?: string
  whenToUse?: string
  model?: string
  // ... 更多字段
}

依赖解析结果

// utils/plugins/dependencyResolver.ts:58-67
export type ResolutionResult =
  | { type: 'ok'; id: PluginId }
  | { type: 'missing'; id: PluginId; requiredBy: PluginId }
  | { type: 'cross-marketplace'; id: PluginId; requiredBy: PluginId }

// DFS 遍历传递依赖闭包
export async function resolveDependencyClosure(
  rootId: PluginId,
  lookup: (id: PluginId) => Promise<DependencyLookupResult | null>,
  alreadyEnabled: ReadonlySet<PluginId>,
  allowedCrossMarketplaces: string[],
): Promise<ResolutionResult[]>

设计模式与亮点

注册表模式

Bundled 技能通过全局注册表模式管理:BUNDLED_SKILLS Map(bundledSkills.ts:47)存储所有注册的技能定义。每个 bundled/xxx.ts 文件导出一个 register*Skill() 函数,在 initBundledSkills()(bundled/index.ts:24)中统一调用。这种模式允许条件注册(通过 feature() 门控)且保持注册逻辑的局部性。

特性门控加载

多个 bundled 技能受 feature() 门控:KAIROS 门控 dream/loop 技能、REVIEW_ARTIFACT 门控 hunter 技能、AGENT_TRIGGERS 门控 loop 技能、BUILDING_CLAUDE_APPS 门控 claudeApi 技能。运行时通过每个技能自己的 isEnabled 回调做细粒度控制,注册时用 feature() 做编译级排除。

版本化缓存隔离

pluginLoader.ts(L139-188)为每个插件维护版本化的缓存路径:cache/<marketplace>/<name>/<version>/。安装时先缓存到临时目录,校验成功后原子移动到最终路径。支持从 npm、Git(含子目录)、本地路径安装,并有 seed 目录预填充机制。

文件监控与热重载

skillChangeDetector.ts 使用 chokidar 监控所有技能目录(用户/项目/插件/动态),文件变更时触发防抖重载(300ms 窗口合并快速变更),通过 onDynamicSkillsLoaded 回调链通知消费者。这允许开发者在编辑技能文件后即时看到效果。

三层技能合并策略:技能从三个来源汇聚到统一的 Command 列表。Bundled 技能优先级最高(标记为 source: 'bundled'),目录技能按设置层级覆盖(project > user > local),插件技能通过 getPluginCommands() 合并。useMergedTools 最终通过 assembleToolPool() 合并所有来源。

开发者实践指南

创建新的 Bundled 技能

按照以下步骤创建一个新的 Bundled 技能:

  1. skills/bundled/ 目录下创建新文件(如 mySkill.ts
  2. 导出一个 registerMySkill() 函数,内部调用 registerBundledSkill()
  3. skills/bundled/index.tsinitBundledSkills() 中导入并调用注册函数
// skills/bundled/mySkill.ts
import { registerBundledSkill } from '../bundledSkills.js'
export function registerMySkill() {
  registerBundledSkill({
    name: 'my-skill',
    description: '我的自定义技能',
    allowedTools: ['Read', 'Write'],
    userInvocable: true,
    getPromptForCommand: async (args) => [
      { type: 'text', text: `执行: ${args}` }
    ],
  })
}

创建目录级技能

.claude/skills/ 目录下放置 Markdown 文件,使用 YAML frontmatter:

---
name: my-project-skill
description: 项目专用技能
allowed-tools: Read, Bash(grep *)
argument-hint: "<search-term>"
---

请搜索项目中的 $ARGUMENTS 并分析结果。

创建插件

插件目录结构:

my-plugin/
  plugin.json        # 清单文件
  skills/            # 技能 Markdown 文件
  agents/            # Agent 定义
  hooks.json         # Hook 配置
  mcpServers/        # MCP 服务器配置
使用 utils/plugins/validatePlugin.ts 中的 validateManifest() 在发布前校验插件。它会检查清单格式、路径穿越安全、frontmatter 语法等。

架构师决策指南

插件隔离模型

插件之间通过 ID 隔离(name@marketplace),依赖解析通过 DFS 遍历传递闭包。关键决策是"松耦合":每个插件的 MCP 服务器、Hooks、技能都是独立加载的,一个插件的失败不影响其他插件。verifyAndDemote()(dependencyResolver.ts:177)在加载时自动将缺少依赖的插件降级为 disabled,而非报错阻止加载。

刷新策略

插件状态变更通过 /reload-plugins 命令触发,而非自动刷新。useManagePlugins 检测到 needsRefresh 时只显示通知。这是因为自动刷新曾导致缓存一致性 bug——只清除了 loadAllPlugins 的 memoize 缓存,但下游的 commands/agents/hooks 加载器仍返回旧数据。

安全边界

插件系统有三层安全边界:

  1. 验证层(validatePlugin.ts)—— 检查路径穿越、manifest 格式、组件文件 frontmatter 语法
  2. 策略层(pluginPolicy.ts)—— 企业策略可限制允许的插件来源
  3. 运行时层(hooks.json 的 hooks)—— 插件提供的 hooks 在执行时受权限系统约束
设计取舍:Bundled 技能和插件技能共享同一个 Command 类型,但来源标记不同(source: 'bundled' vs 'plugin')。这使得权限系统需要区分处理——bundled 技能默认受信任,插件技能需要额外权限确认。

可视化处理拓扑图

插件与技能系统是一个多源发现、加载验证、统一注册的三阶段管线。插件从 session/marketplace/built-in 三个来源汇聚,经过 manifest 验证和路径安全检查后,将其 skills、hooks、MCP 配置注册到 Claude Code 运行时。所有技能最终统一为 Command 对象,模型调用时无感知来源差异。

loadAllPlugins() 入口assemblePluginLoadResult() — pluginLoader.ts:3155
Session 插件--plugin-dir CLI
loadSessionOnlyPlugins()
L2928 · 最高优先级
Marketplace 插件loadPluginsFromMarketplaces()
L1888 · 版本化缓存
Built-in 插件getBuiltinPlugins()
builtinPlugins.ts:57
预装 seed 缓存
mergePluginSources()pluginLoader.ts:3009 — session 优先 → marketplace → built-in
企业策略过滤pluginPolicy.ts — 来源限制 + pluginBlocklist 黑名单
通过 ↓
合并后的有效插件列表LoadedPlugin[] — 去重后

Phase 2 — 安装分发、Manifest 验证与加载

安装分发(按 source 类型)loadPluginFromMarketplaceEntry() — pluginLoader.ts:2191
npminstallFromNpm()
gitgitClone()
githubinstallFromGitHub()
git-subdirinstallFromGitSubdir()
localinstallFromLocal()
loadPluginManifest() — plugin.json 解析pluginLoader.ts:1147 — 验证 name, version, description
validatePluginPaths() + 安全验证pluginLoader.ts:1265 — 检查组件文件存在 + 路径穿越检测
验证失败
PluginError记录错误 + 跳过该插件
验证通过 ↓
createPluginFromPath()pluginLoader.ts:1348 — 构建完整 LoadedPlugin 对象
copyPluginToVersionedCache()pluginLoader.ts:365 — 缓存到 plugins/cache/v1/{id}/{ver}/

Phase 3 — 组件注册(Hooks / Skills / Commands)

finishLoadingPluginFromPath()pluginLoader.ts:2420 — 注册所有组件
Hooks 注册loadPluginHooks()
hooks.json → HooksSettings
mergeHooksSettings()
Skills/Commands 注册createSkillCommand()
.md → frontmatter → Command
source: bundled|plugin
MCP 配置注册插件提供的 MCP servers
dedupPluginMcpServers() 去重
技能加载三个层次loadSkillsDir.ts / bundledSkills.ts
Bundled SkillsregisterBundledSkill()
内置: verify, simplify...
source: 'bundled'
目录 SkillsloadSkillsFromSkillsDir()
.claude/skills/*.md
source: 'user'|'project'
插件 Skills从 marketplace 插件
加载的 .md 技能
source: 'plugin'

Phase 4 — 工具合并与权限应用

useMergedTools() 统一合并hooks/useMergedTools.ts — 内置 + MCP + Skills
权限分流通过 source 字段区分信任级别
Bundled — 默认信任source: 'bundled'
无需额外权限确认
Plugin — 需权限确认source: 'plugin'
额外权限检查流程
条件技能激活activateConditionalSkillsForPaths() — 文件路径匹配 → 动态加载
统一 Command 工具池就绪模型通过 getPromptForCommand() 调用,无感知来源差异
统一抽象设计:插件系统最核心的设计是"三层技能来源的统一 Command 抽象"。无论是内置 bundled skill(TypeScript 函数)、目录 skill(.claude/skills/*.md)、还是插件 skill(marketplace 安装包),最终都转化为同一个 Command 类型对象。source 字段仅用于权限分流——bundled 默认信任,plugin 需额外确认。版本化缓存的三级查找(主缓存 → seed → legacy)确保跨版本兼容和快速加载。安全验证层通过路径穿越检测和 frontmatter 语法校验防止恶意插件。

核心处理流程详解

插件与技能系统是将外部能力注入 Claude Code 的核心机制。插件(Plugin)是包含 skills、hooks 和 MCP 配置的完整包;技能(Skill/Command)是可被模型调用的 prompt 模板。以下是从插件发现到技能激活的完整处理链路:

1. 多源插件发现
loadAllPlugins()(pluginLoader.ts,通过 assemblePluginLoadResult() 在 L3155 编排)从三个来源收集插件:session 插件(--plugin-dir CLI 参数,loadSessionOnlyPlugins() L2928)、marketplace 插件(loadPluginsFromMarketplaces() L1888)、和 built-in 插件(getBuiltinPlugins() builtinPlugins.ts:57)。三个来源的结果通过 mergePluginSources()(pluginLoader.ts:3009)合并,session 优先级最高 — pluginLoader.ts:2928-3211
2. Marketplace 安装与缓存
对于 marketplace 插件,loadPluginFromMarketplaceEntry()(pluginLoader.ts:2191)根据 source 类型分发安装:npm 通过 installFromNpm()(L492)、git 通过 gitClone()(L534)、github 通过 installFromGitHub()(L662)、git-subdir 通过 installFromGitSubdir()(L718)、local 通过 installFromLocal()(L856)。安装完成后通过 copyPluginToVersionedCache()(L365)缓存到版本化目录 — pluginLoader.ts:492-1098
3. Manifest 解析与验证
loadPluginManifest()(pluginLoader.ts:1147)从 plugin.json 解析清单,验证必要字段(name、version、description)。createPluginFromPath()(pluginLoader.ts:1348)是核心加载函数,它调用 validatePluginPaths()(L1265)检查所有组件文件是否存在,并通过 validatePlugin.ts 执行深度验证(路径穿越检测、frontmatter 语法校验)— pluginLoader.ts:1147-1770
4. Hooks 加载与策略过滤
loadPluginHooks()(pluginLoader.ts:1224)从插件的 hooks.json 加载 hook 配置,通过 HooksSettingsSchema 验证格式。企业策略通过 pluginPolicy.ts 限制允许的插件来源。安全验证层(validatePlugin.ts)检查路径穿越(确保插件文件不会通过 ../ 访问插件目录外的文件)和 manifest 格式 — pluginLoader.ts:1224-1242, validatePlugin.ts
5. 技能发现与加载
技能从三个层次加载:bundled skills(registerBundledSkill(),bundledSkills.ts:53,在启动时注册内置技能如 verify、simplify 等);目录 skills(loadSkillsFromSkillsDir(),loadSkillsDir.ts:407,从 .claude/skills/ 目录扫描 .md 文件);动态 skills(addSkillDirectories(),loadSkillsDir.ts:923,条件激活基于 paths frontmatter 的技能)。每个 .md 文件通过 frontmatter 解析为 PromptCommand 对象 — loadSkillsDir.ts:270-480
6. Frontmatter 解析与 Command 构建
parseSkillFrontmatterFields()(loadSkillsDir.ts:185)从 markdown frontmatter 提取技能元数据(name、description、allowed-tools、model 等)。createSkillCommand()(loadSkillsDir.ts:270)将解析结果构建为 Command 对象,包含 getPromptForCommand() 方法——该方法在调用时读取 .md 文件内容作为 prompt 注入,同时支持 Bundled Skill 的 extractBundledSkillFiles()(bundledSkills.ts:131)提取引用文件到磁盘 — loadSkillsDir.ts:185-401
7. 条件技能激活
带有 paths frontmatter 的技能是条件激活的(parseSkillPaths(),loadSkillsDir.ts:159)。activateConditionalSkillsForPaths()(loadSkillsDir.ts:997)在文件被访问时触发——从文件路径向上遍历到 cwd,发现匹配的 skill 目录后通过 addSkillDirectories() 动态加载。这实现了"打开 Python 文件时自动激活 Python 相关技能"的上下文感知行为 — loadSkillsDir.ts:997-1058
8. 工具合并与权限应用
useMergedTools()(hooks/useMergedTools.ts)将内置工具、MCP 工具和技能合并为统一的工具池。插件提供的 MCP 服务器通过 dedupPluginMcpServers() 去重。权限系统通过 source 字段区分 bundled(source: 'bundled')和 plugin(source: 'plugin')技能——bundled 技能默认受信任,插件技能需要额外权限确认 — hooks/useMergedTools.ts, builtinPlugins.ts:57-102
关键处理逻辑:插件系统最核心的设计是"三层技能来源的统一 Command 抽象"。无论是内置 bundled skill(代码中硬编码的 TypeScript 函数)、目录 skill(用户 .claude/skills/ 下的 markdown 文件)、还是插件 skill(从 marketplace 安装的包中的技能),最终都转化为同一个 Command 类型对象。这种统一抽象使得模型无需关心技能来源差异,调用路径完全一致:getPromptForCommand() → prompt 注入 → 模型执行。

设计精华

1. 统一 Command 抽象与来源多态

Bundled skill、目录 skill、插件 skill 三者使用同一个 Command 类型,但 source 字段不同。这使得下游系统(工具合并、权限检查、UI 渲染)可以用统一接口处理所有技能,只在需要区分时检查 sourcecreateSkillCommand()(loadSkillsDir.ts:270)是统一构建入口,无论来源如何,产出的对象都有相同的 getPromptForCommand() 方法签名。

技能构建统一入口(loadSkillsDir.ts:270-401)
所有来源的技能最终通过 createSkillCommand() 构建
export function createSkillCommand({
  name, description, source, filePath, content,
  frontmatter: { allowedTools, model, ... }
}): Command {
  return {
    type: 'prompt',
    name,
    source,  // 'bundled' | 'user' | 'project' | 'plugin'
    userFacingName: () => name,
    getPromptForCommand: async (args, ctx) => {
      // 读取 .md 文件内容,支持参数替换
      // Bundled skill 额外提取引用文件
      return contentBlocks
    }
  }
}
设计洞察:这种"统一接口 + 来源标记"模式是开闭原则的完美实践。添加新的技能来源(比如未来的远程技能仓库)只需实现 Command 接口并设置新的 source 值,无需修改任何下游代码。

2. 版本化缓存的多级查找

插件安装后通过 getVersionedCachePath()(pluginLoader.ts:172)缓存到版本化目录(plugins/cache/v1/{pluginId}/{version}/)。加载时通过 resolvePluginPath()(pluginLoader.ts:266)按优先级查找:先查主缓存目录,再查 seed 目录(预装插件),最后查 legacy 非版本化缓存。这种三级查找确保了:(1) 版本升级时新旧缓存共存不冲突;(2) 新安装可以使用预装的 seed 缓存加速;(3) 老版本的非版本化缓存仍可被找到。

三级缓存查找(pluginLoader.ts:266-287)
主缓存 → seed 缓存 → legacy 缓存,逐级降级
export async function resolvePluginPath(pluginId, version) {
  // 1. 版本化主缓存
  const versionedPath = getVersionedCachePath(pluginId, version)
  if (await exists(versionedPath)) return versionedPath
  // 2. Seed 缓存(预装)
  const seedPath = await probeSeedCache(pluginId, version)
  if (seedPath) return seedPath
  // 3. Legacy 非版本化缓存
  const legacyPath = getLegacyCachePath(pluginId)
  if (await exists(legacyPath)) return legacyPath
}
设计洞察:三级查找的核心思想是"写入路径简单,读取路径健壮"。写入时只写主缓存;读取时容忍多种缓存布局(seed 预装、legacy 迁移遗留)。这使得插件系统的升级和迁移无需清理旧缓存——旧缓存自然降级为 fallback。

3. 条件技能的文件路径触发

普通技能在启动时一次性加载,而条件技能(带 paths frontmatter 的技能)在运行时按需激活。activateConditionalSkillsForPaths()(loadSkillsDir.ts:997)接收当前被访问的文件路径列表,通过 discoverSkillDirsForPaths()(loadSkillsDir.ts:861)从文件路径向上遍历目录树,发现匹配的 .claude/skills/ 目录。这种"文件访问 → 目录遍历 → 技能激活"的延迟绑定实现了上下文感知:只有当用户实际操作相关文件时,对应的技能才被注入模型上下文。

设计洞察:条件技能的设计避免了"加载所有可能的技能"的上下文膨胀。在一个包含数十个技能目录的大型 monorepo 中,用户可能只用到其中 2-3 个。条件激活确保模型只看到与当前工作相关的技能,既节省了 token 预算,也减少了模型的选择困惑。

4. 插件安全的三层防御

插件安全通过验证层、策略层和运行时层三重保障。验证层(validatePlugin.ts)检查路径穿越攻击——插件的组件文件路径(如 hooks.json、skill .md 文件)不能通过 ../ 引用插件目录外的文件。策略层(pluginPolicy.ts)允许企业管理员限制允许的插件来源。运行时层通过 source 字段区分信任等级——bundled 技能(source: 'bundled')跳过权限确认,插件技能(source: 'plugin')必须经过用户批准。

设计洞察:三层防御遵循了"纵深防御"原则——即使某一层被绕过(比如一个精心构造的 manifest 绕过了路径穿越检测),后续层仍然提供保护。策略层和运行时层是独立配置的,管理员可以只开启策略限制(只允许特定来源的插件)而不改变运行时行为,反之亦然。

Agent 实践借鉴 — 客服 Agent 插件/扩展设计

场景映射:客服 agent 在插件扩展上的真实问题

不同业务线的客服需要不同的技能,技能需要由业务方自己开发维护。典型痛点包括:

借鉴 CC + 客服改造

1. 技能清单(Skill Manifest)定义(借鉴 CC plugin.json)

CC 的 plugin.json 声明插件提供的 skills/agents/hooks。客服场景的技能清单声明 name、description、permissions、handler:

// 借鉴 CC 的 plugin.json → 客服场景的技能清单
interface SkillManifest {
  name: string                    // 技能唯一标识,如 "query_logistics"
  version: string                 // 版本号,如 "1.2.0"
  description: string             // 技能描述,如 "查询物流轨迹和预计到达时间"
  businessLine: string            // 所属业务线,如 "ecommerce" | "finance" | "education"
  triggerPatterns: string[]       // 触发模式,如 ["查物流", "物流查询", "快递到哪了"]
  permissions: SkillPermission[]  // 声明所需权限
  inputSchema: {                  // 参数 schema
    type: 'object',
    properties: {
      orderNo: { type: 'string', description: '订单号' },
      expressCompany: { type: 'string', description: '快递公司(可选)' },
    },
    required: ['orderNo'],
  }
  handler: SkillHandler           // 技能处理函数
}

interface SkillPermission {
  system: string                  // 外部系统,如 "crm" | "erp" | "payment"
  access: 'read' | 'write'        // 权限级别
  scope: string                   // 权限范围,如 "order:*" | "customer:phone"
}

// 示例:物流查询技能的清单
const queryLogisticsSkill: SkillManifest = {
  name: 'query_logistics',
  version: '1.2.0',
  description: '查询物流轨迹和预计到达时间',
  businessLine: 'ecommerce',
  triggerPatterns: ['查物流', '物流查询', '快递到哪了', '我的包裹到哪了'],
  permissions: [
    { system: 'erp', access: 'read', scope: 'order:logistics' },
    { system: 'logistics', access: 'read', scope: 'tracking:*' },
  ],
  inputSchema: {
    type: 'object',
    properties: {
      orderNo: { type: 'string', description: '订单号' },
    },
    required: ['orderNo'],
  },
  handler: async (params) => { /* ... */ },
}

2. 三层加载:内置 → 企业 → 第三方(借鉴 CC 三层扩展)

CC 的 Bundled → 目录 → 插件三层加载,对应客服场景的:内置技能 → 企业自定义技能 → 第三方技能:

// 借鉴 CC 的三层技能加载 → 客服场景的三层技能体系
type SkillSource = 'builtin' | 'enterprise' | 'third_party'

// 统一技能接口——借鉴 CC 的 Command 抽象
interface CustomerServiceSkill {
  name: string
  source: SkillSource           // 仅用于权限分流
  manifest: SkillManifest
  execute(params: any, context: SkillContext): Promise<SkillResult>
}

// 三层加载——优先级从高到低,同名技能高优先级覆盖低优先级
async function loadAllSkills(): Promise<Map<string, CustomerServiceSkill>> {
  const skillRegistry = new Map<string, CustomerServiceSkill>()

  // Layer 1: 内置技能(最高信任,代码审计过)
  // 查订单、查物流、退款处理等核心技能
  const builtinSkills = await loadBuiltinSkills()
  for (const skill of builtinSkills) {
    skillRegistry.set(skill.name, { ...skill, source: 'builtin' })
  }

  // Layer 2: 企业自定义技能(业务方开发,内部分发)
  // 从企业技能仓库加载,如 "利率计算"、"预约试听"
  const enterpriseSkills = await loadEnterpriseSkills()
  for (const skill of enterpriseSkills) {
    skillRegistry.set(skill.name, { ...skill, source: 'enterprise' })
  }

  // Layer 3: 第三方技能(最低信任,需额外权限确认)
  // 从第三方市场加载,如 "某 SaaS 工单系统集成"
  const thirdPartySkills = await loadThirdPartySkills()
  for (const skill of thirdPartySkills) {
    // 同名不覆盖(内置/企业优先级更高)
    if (!skillRegistry.has(skill.name)) {
      skillRegistry.set(skill.name, { ...skill, source: 'third_party' })
    }
  }

  return skillRegistry
}

// 按业务线过滤——电商客服只看到电商技能
function filterSkillsByBusinessLine(
  registry: Map<string, CustomerServiceSkill>,
  businessLine: string,
): CustomerServiceSkill[] {
  return Array.from(registry.values()).filter(
    skill => skill.manifest.businessLine === businessLine
           || skill.manifest.businessLine === 'common'  // 通用技能
  )
}

3. 权限声明 + 运行时检查(借鉴 CC source 字段权限分流)

CC 用 source 字段区分 bundled(默认信任)和 plugin(需权限确认)。客服场景按技能来源和权限声明做运行时检查:

// 借鉴 CC 的 source 字段权限分流 → 客服场景的权限检查
async function executeSkillWithPermissionCheck(
  skill: CustomerServiceSkill,
  params: any,
  context: SkillContext,
): Promise<SkillResult> {
  // 1. 按 source 信任级别分流
  if (skill.source === 'third_party') {
    // 第三方技能:必须确认用户授权
    const approved = await confirmUserApproval(skill.manifest)
    if (!approved) {
      return { success: false, error: '用户未授权第三方技能执行' }
    }
  }
  // builtin 和 enterprise 技能:跳过用户确认(已通过内部审计)

  // 2. 运行时权限检查——技能声明了哪些权限,必须全部满足
  for (const perm of skill.manifest.permissions) {
    const hasPermission = await checkSystemPermission(
      context.agentId,
      perm.system,
      perm.access,
      perm.scope,
    )
    if (!hasPermission) {
      return {
        success: false,
        error: `技能 ${skill.name} 需要 ${perm.system} 的 ${perm.access}(${perm.scope}) 权限,当前 agent 无此权限`,
      }
    }
  }

  // 3. 执行技能
  try {
    const result = await skill.execute(params, context)
    return result
  } catch (error) {
    logError(`技能 ${skill.name} 执行失败`, error)
    return { success: false, error: `技能执行异常: ${error.message}` }
  }
}

4. 技能隔离:单个技能加载失败不影响其他(借鉴 CC 错误隔离)

// 借鉴 CC 的 errors 数组收集模式 → 技能加载隔离
interface SkillLoadResult {
  loaded: CustomerServiceSkill[]
  failed: SkillLoadError[]
}

interface SkillLoadError {
  skillName: string
  source: SkillSource
  error: string
}

async function loadSkillsSafely(
  manifests: SkillManifest[],
): Promise<SkillLoadResult> {
  const loaded: CustomerServiceSkill[] = []
  const failed: SkillLoadError[] = []

  // 并行加载,每个技能独立 try-catch
  await Promise.allSettled(
    manifests.map(async (manifest) => {
      try {
        // 验证清单格式
        validateManifest(manifest)
        // 验证权限声明完整性
        validatePermissions(manifest.permissions)
        // 加载并注册技能
        const skill = await compileSkill(manifest)
        // 健康检查:确保依赖的外部系统可达
        await healthCheckSkill(skill)
        loaded.push(skill)
      } catch (error) {
        // 单个技能失败不影响其他技能
        failed.push({
          skillName: manifest.name,
          source: manifest.businessLine as SkillSource,
          error: error.message,
        })
        logWarning(`技能 ${manifest.name} 加载失败: ${error.message}`)
        // 不 throw!继续加载其他技能
      }
    })
  )

  logInfo(`技能加载完成: ${loaded.length} 成功, ${failed.length} 失败`)
  return { loaded, failed }
}

// 借鉴 CC 的 verifyAndDemote:依赖缺失时降级而非报错
function handleSkillDependencyFailure(
  skill: CustomerServiceSkill,
  missingSystems: string[],
): void {
  logWarning(`技能 ${skill.name} 依赖的系统 ${missingSystems.join(', ')} 不可达,技能已降级为只读模式`)
  // 不删除技能,而是降级为只读(可以查看配置但不能执行)
  skill.execute = async () => ({
    success: false,
    error: `依赖系统不可用: ${missingSystems.join(', ')},请联系运维`,
  })
}

落地清单

必须抄的:
1. 统一技能接口——所有技能(内置/企业/第三方)实现同一个 CustomerServiceSkill 接口,agent 调用时不感知技能来源。
2. 权限声明模型——技能必须在清单中声明所需权限(哪个系统、读/写、什么范围),运行时逐一检查。
3. 加载失败隔离——单个技能加载失败不阻塞其他技能,用 failed 数组收集错误。
4. 三层优先级——同名技能高优先级覆盖低优先级(内置 > 企业 > 第三方),防止第三方技能覆盖核心功能。
不需要抄的:
1. 市场发现/版本化缓存——客服场景用内部分发(企业技能仓库),不需要 CC 的官方 Marketplace 和版本化缓存机制。
2. 技能文件监控热更新——客服场景技能变更走发布流程(测试 → 灰度 → 全量),不需要 CC 的 chokidar 文件监控实时热重载。
3. 条件技能激活——CC 根据文件路径匹配激活技能,客服场景按业务线过滤即可,不需要路径匹配。
4. 依赖解析 DFS 闭包——客服技能之间的依赖关系简单,不需要 CC 那么复杂的传递依赖解析。
常见坑: 1. 一个技能崩溃导致整个 agent 不可用——最常见的问题。第三方"利率计算"技能抛了未捕获异常,整个 agent 挂了。必须在 execute 外层包 try-catch,技能失败返回错误信息而非崩溃。 2. 权限检查太粗——给所有技能相同权限(如全部 CRM 读写),"查物流"技能不需要写 CRM 但也能写。必须在清单中逐个声明权限,运行时逐个检查,最小权限原则。 3. 技能注册顺序导致覆盖冲突——企业自定义的"查订单"技能被第三方"查订单"技能覆盖了,因为加载顺序不对。必须按优先级加载(内置 > 企业 > 第三方),同名高优先级不被低优先级覆盖。

代码索引

文件行数说明
hooks/useManagePlugins.ts~305React Hook:插件初始化加载、错误收集、通知
hooks/useMergedTools.ts~45React Hook:合并内置工具 + MCP 工具,应用过滤和去重
plugins/builtinPlugins.ts~159内建插件注册表:注册、查询、启用/禁用、技能提取
plugins/bundled/index.ts~80Bundled 技能初始化入口:条件注册各技能
skills/bundledSkills.ts~221技能注册表:registerBundledSkill、文件提取、路径校验
skills/loadSkillsDir.ts~1087目录技能加载:frontmatter 解析、动态技能、条件激活
skills/mcpSkillBuilders.ts~45MCP 技能构建器注册表:桥接 MCP 资源到技能系统
utils/plugins/pluginLoader.ts~3303插件加载核心:发现、缓存、安装、清单解析、合并
utils/plugins/validatePlugin.ts~904插件验证:清单校验、路径穿越检测、组件文件验证
utils/plugins/dependencyResolver.ts~306依赖解析:DFS 闭包遍历、跨市场检查、反向依赖
utils/plugins/refresh.ts~216插件刷新:/reload-plugins 的实现,重载所有组件
utils/plugins/loadPluginHooks.ts~小型加载插件的 hooks.json 配置
utils/plugins/installedPluginsManager.ts~中型已安装插件管理:增删查改
utils/plugins/pluginPolicy.ts~小型插件策略:企业允许/拒绝列表
utils/plugins/pluginVersioning.ts~中型版本管理:版本比较、升级检查
utils/plugins/orphanedPluginFilter.ts~小型孤儿插件过滤:清理无对应配置的缓存
utils/plugins/officialMarketplace.ts~中型官方市场:市场发现、条目查询
utils/plugins/schemas.ts~中型插件 Schema 定义:manifest、marketplace、PluginId
utils/plugins/headlessPluginInstall.ts~中型无头模式:非交互式插件安装
utils/plugins/performStartupChecks.tsx~中型启动检查:版本兼容、弃用通知
utils/plugins/fetchTelemetry.ts~小型插件遥测:安装/使用统计
utils/plugins/lspPluginIntegration.ts~中型LSP 集成:插件提供的 LSP 服务器
utils/skills/skillChangeDetector.ts~312技能变更检测:chokidar 文件监控、防抖重载