第三十八章:插件系统为什么要把 Marketplace 和运行时分层
拆 marketplace 声明层、物化层、安装链路、热更新和组织治理。
1. 为什么这章必须单独拆
前面我们已经拆过 MCP 和 Skill。
但 Claude Code 的 plugin 线,并不是 MCP 的一个轻薄包装。
从源码看,这里至少同时存在几层东西:
- marketplace 声明层
- marketplace 物化状态层
- 插件安装与缓存层
- 插件加载与命令/技能注入层
- 插件 hook 热更新层
- 组织策略治理层
这说明 plugin system 不是“装个包”,而是一整套扩展供应链。
相关源码锚点:
src/utils/plugins/marketplaceManager.tssrc/utils/plugins/reconciler.tssrc/utils/plugins/pluginLoader.tssrc/utils/plugins/pluginPolicy.tssrc/utils/plugins/loadPluginHooks.tssrc/services/plugins/PluginInstallationManager.ts
2. 先说结论
我对这一块的判断是:
Claude Code 的插件系统,重点不是“如何运行插件”,而是“如何把插件生态受控地引进 runtime”。
最关键的结论有五个:
- Marketplace declaration 和 materialized state 是分开的。
- 插件安装、缓存、加载、激活是几层不同职责。
- 插件不只加命令和技能,还会热插 hooks。
- 后台 reconcile 和前台 AppState 是桥接关系,不是一个层。
- 组织级策略可以在 plugin 入口直接拦截。
3. 总体结构图
flowchart TD
A["settings / add-dir / 官方 fallback"] --> B["marketplaceManager<br/>声明层"]
B --> C["known_marketplaces.json<br/>物化状态"]
C --> D["reconciler<br/>install / update"]
D --> E["PluginInstallationManager"]
E --> F["pluginLoader"]
F --> G["commands / skills / hooks 注入"]
G --> H["AppState.plugins"]
I["policySettings"] --> F
I --> G
4. Marketplace:为什么要分 intent layer 和 materialized layer
源码锚点:
src/utils/plugins/marketplaceManager.ts
这一层是整套设计里最值钱的部分之一。
它明确把 marketplace 分成两种状态:
4.1 声明层
也就是“用户或配置想要什么”:
- merged settings 里的 marketplace 声明
- add-dir 形式的补充来源
- 官方 marketplace 的隐式 fallback
4.2 物化层
也就是“本地现在实际认得哪些 marketplace”:
known_marketplaces.json~/.claude/plugins/marketplaces/- cache 状态
这样拆的好处特别直接:
- 可以先表达意图,再后台 reconcile
- 可以容忍网络、缓存、安装状态滞后
- 不把配置文件直接当成安装结果
5. Reconciler:这是后台供应链,不是 UI 逻辑
源码锚点:
src/utils/plugins/reconciler.ts
diffMarketplaces() 会先把 declared 和 materialized 做 diff,分成:
- missing
- sourceChanged
- upToDate
然后 reconcileMarketplaces() 再做真正的安装或更新。
这里很关键的一点是:
- 它本身不改
AppState - 它是加法式、幂等式的后台 reconcile
这意味着作者故意把:
- “后台把东西准备好”
- “前台要不要刷新视图”
拆成两层。
6. pluginLoader.ts:运行时激活层
源码锚点:
src/utils/plugins/pluginLoader.ts
加载器这一层管的是:
- marketplace-based plugin
- session-only
--plugin-dir - manifest 校验
- 重名冲突
- enable/disable 状态
- 版本化缓存目录
- legacy cache fallback
- seed cache 探测
这里最值钱的设计点是:
作者没有把“插件目录里有个文件”直接等同于“runtime 能安全激活这个插件”。
中间插了完整的校验和缓存层。
7. Hooks 热插拔:插件不是只会加命令
源码锚点:
src/utils/plugins/loadPluginHooks.ts
这一层非常说明问题。
它会把插件 hook 配置转成 runtime matcher,然后:
- 原子地 clear 再 register
- 监听 policy settings 变化
- 计算 plugin-affecting settings snapshot
- 清 cache
- 触发 fire-and-forget reload
这说明 Claude Code 的插件系统不是“启动时装好,之后不动”。
它允许一部分扩展面热更新。
而且作者专门修过 stop hook 在 clear 后静默丢失的问题,说明这条线已经是生产级运行时的一部分。
8. PluginInstallationManager:前后台桥接层
源码锚点:
src/services/plugins/PluginInstallationManager.ts
这层的职责很清楚:
- 先计算 diff
- 把 pending 状态写进
AppState.plugins.installationStatus - 跑 marketplace reconcile
- 如果是新 marketplace,就 auto-refresh active plugins
- 如果只是更新,则标
needsRefresh
这说明它不是“安装器”那么简单,而是:
把后台供应链结果翻译成 UI/runtime 可观察状态的桥。
9. Governance:组织策略怎么卡在插件入口
源码锚点:
src/utils/plugins/pluginPolicy.ts
pluginPolicy.ts 是个很小、但很值钱的叶子模块。
它只做一件事:
- 从
policySettings读取组织级 plugin allow/deny 配置
这个设计很成熟,因为它避免了:
- loader 自己去到处依赖 settings
- 插件治理逻辑散落进各个激活点
也就是说,扩展治理被故意做成了明确的闸门。
10. 这套设计真正回答了什么问题
如果把这些层放在一起看,Claude Code 实际上回答了五个生态问题:
- 插件市场从哪里声明
- 本地怎么确认哪些市场已物化
- 插件怎么安装、缓存和升级
- 插件怎样在 runtime 中被激活或热更新
- 组织级策略怎么压住扩展入口
这比“插件机制”这个词本身要具体得多。
11. 对 C# 拆分最有用的建议
如果后面做 C# 版,这层至少应该拆成五个边界:
11.1 MarketplaceRegistry
负责:
- marketplace intent
- marketplace materialization
- known state persistence
11.2 MarketplaceReconciler
负责:
- diff
- install / update
- 幂等后台同步
11.3 PluginCatalogLoader
负责:
- manifest 校验
- cache/version 解析
- enable/disable 决策
11.4 PluginRuntimeActivator
负责:
- command / skill / hook 注入
- refresh / hot reload
11.5 PluginGovernanceGuard
负责:
- org allow/deny
- provider / managed settings 约束
12. 一句话收口
Claude Code 的 plugin system 最值得学的地方,不是“支持插件”,而是它把 marketplace、安装、加载、热更新和组织治理拆成了一条受控供应链。