# 插件系统完全解析

插件系统让第三方开发者为 Claude Code 添加新能力——从 GitHub 集成到数据库客户端，从代码格式化到自定义 Skills（"Skills"即插件提供的特定技能——比如一个 GitHub 插件可以提供"创建 PR"、"查看 Issue"等技能，Claude 会在合适的时机自动调用它们）。它通过 Marketplace 分发、签名验证确保供应链安全、同形字检测防止仿冒攻击、三级权限控制保护用户。本章将解析插件的安装链路、三重安全防线、自动更新策略、依赖解析、以及与 MCP 协议的底层关系。

> **源码位置**：`src/utils/plugins/`（44 个文件）、`src/services/plugins/`（3 个文件）、`src/commands/plugin/`（17 个 UI 文件）

> 💡 **通俗理解**：插件系统就像一座城市的商业街管理——每家店铺（插件）开业前要经过审核（签名验证），相当于营业执照审批；装修时要申报用途（权限申请）；经营时只能在自己的店面范围内活动（MCP 沙箱隔离）；还有机制防止山寨店冒充品牌（同形字检测）。城市管理局（Marketplace）定期巡查（自动更新），如果发现违规店铺会强制关停（下架自动卸载）。

> 🌍 **行业背景**：插件/扩展生态是开发工具的核心竞争力之一，各平台的安全策略差异显著。**VS Code** 的扩展市场（Marketplace）是最成熟的参照——它有发布者验证、恶意扩展检测、但长期以来缺乏细粒度权限控制（直到近年才引入 Workspace Trust）。**Chrome 扩展** 的 Manifest V3 引入了权限声明 + 用户授权的双层模型，Claude Code 的三级权限控制与之类似。**npm** 生态的供应链攻击（typosquatting、依赖混淆）是 Claude Code 同形字检测要防范的现实威胁——2024 年多起 npm 包仿冒事件证明了这类防护的必要性。**GitHub Copilot Extensions** 于 2024 年推出，走 GitHub App 授权模型，安全由 GitHub 平台托管——这是"平台托管安全"与 Claude Code "客户端自主安全"两种完全不同的哲学。**Cursor** 尚未建立独立的插件市场，而是依赖 VS Code 扩展生态（因为它本身是 VS Code 的 fork）——这是"借生态"vs"建生态"两种截然不同的策略。Claude Code 作为 CLI 工具无法复用 VS Code 扩展，被迫自建生态，这个约束条件是理解其插件系统设计的真正起点。

---

> **与第 03 章的边界划分**：本章聚焦插件系统在 MCP 之上新增的安全层、分发机制和更新策略。MCP 协议本身的传输、配置和连接管理，请参阅第 03 章。两章的交汇点在于：插件注册为 MCP 服务器时使用 `config scope: plugin`——这是一种专门的 scope 标记，使得系统可以对插件来源的 MCP 服务器施加差异化的信任策略。

---

## 代码落点表

> 💡 **如何使用这张表**：你不需要记住所有文件。这张表的作用是"地图"——当后文提到某个功能时，你可以回来查"这个功能的代码在哪个文件里"。初次阅读可以跳过，直接进入下面的正文。

| 文件 | 大小 | 核心职责 |
|------|------|----------|
| `pluginLoader.ts` | 110KB | **核心加载器**——从缓存/磁盘加载插件、解析 manifest、缓存管理、版本路径 |
| `marketplaceManager.ts` | 93KB | **Marketplace 管理器**——市场注册/刷新/查询、Git 克隆/拉取、插件搜索 |
| `schemas.ts` | 59KB | **类型定义与官方名称保护**——Zod schema **类型定义**、同形字检测（`isBlockedOfficialName`）、官方 marketplace 名称白名单（实际的 plugin.json / marketplace.json 运行时校验在 `validatePlugin.ts` 中执行，`schemas.ts` 只负责 schema 本身的声明） |
| `mcpbHandler.ts` | 31KB | **MCPB 处理器**——DXT 格式插件的下载/解压/MCP 配置转换 |
| `installedPluginsManager.ts` | 41KB | **安装注册表**——installed_plugins.json 的读写、版本追踪、待更新检测 |
| `validatePlugin.ts` | 28KB | **清单验证器**——plugin.json/marketplace.json 的 Zod schema 严格验证、路径遍历检测 |
| `pluginInstallationHelpers.ts` | 21KB | **安装核心**——`installResolvedPlugin` 主流程、依赖闭包解析、策略守卫、缓存物化 |
| `mcpPluginIntegration.ts` | 20KB | **MCP 桥接**——插件 MCP 服务器的加载/配置/用户配置变量替换 |
| `marketplaceHelpers.ts` | 18KB | **企业策略**——源白名单/黑名单、host/path 模式匹配、策略优先级链 |
| `pluginAutoupdate.ts` | 9.5KB | **自动更新**——后台 marketplace 刷新 + 插件更新、回调通知 |
| `dependencyResolver.ts` | 12KB | **依赖解析**——DFS 闭包计算、循环检测、跨市场安全阻断、加载时降级 |
| `pluginBlocklist.ts` | 4.4KB | **下架检测**——对比 marketplace 清单自动卸载已下架插件 |
| `pluginPolicy.ts` | 0.8KB | **组织策略**——`isPluginBlockedByPolicy` 单点检查 |
| `pluginVersioning.ts` | 5.3KB | **版本计算**——manifest 版本 > 提供版本 > Git SHA > unknown 优先级链 |
| `pluginFlagging.ts` | 5.6KB | **标记管理**——记录自动卸载的插件、48 小时过期、用户可见的通知 |
| `pluginIdentifier.ts` | 3.9KB | **ID 解析**——`name@marketplace` 格式解析/构建、scope 映射 |
| `reconciler.ts` | 8.3KB | **市场协调器**——settings 声明与 known_marketplaces.json 的 diff + 同步 |
| `hintRecommendation.ts` | 5.4KB | **安装推荐**——CLI 工具 stderr 中 `<claude-code-hint />` 触发的插件推荐 |
| `orphanedPluginFilter.ts` | 4.0KB | **孤儿过滤**——ripgrep 排除旧版本插件目录，防止搜索结果污染 |
| `officialMarketplace.ts` | 0.8KB | **官方常量**——`anthropics/claude-plugins-official` 的 GitHub 源和显示名 |

**服务层**（`src/services/plugins/`）：

| 文件 | 大小 | 核心职责 |
|------|------|----------|
| `pluginOperations.ts` | 36KB | **操作层**——install/uninstall/enable/disable/update 的 CLI 与 UI 共享实现（文件大小随版本波动，本列数据取自 Claude Code 2.1.88 SoT 审阅时的快照；如与你本地版本略有出入请以 `ls -la src/services/plugins/` 为准） |
| `PluginInstallationManager.ts` | 6KB | **安装管理器**——managed-settings 自动安装入口 |
| `pluginCliCommands.ts` | 11KB | **CLI 命令**——`claude plugin install/uninstall/list/update` 的命令行解析与执行 |

> 💡 **通俗理解**：这些文件的分层关系就像一座楼——`schemas.ts` 是地基（定义所有数据结构），`pluginLoader.ts` 是电梯（负责把插件从磁盘搬到内存），`marketplaceManager.ts` 是物业（负责所有市场的增删改查），`pluginOperations.ts` 是前台（用户所有的安装/卸载请求都经过这里），`pluginInstallationHelpers.ts` 是后厨（真正干活的核心流程）。

---

> **[图表预留 5-A]**：安全架构图 — 插件从发布到安装的完整信任链（Marketplace → Git SHA 完整性校验 → 同形字检测 → 用户确认 → MCP 连接）

---

## 1. 插件与 MCP 的关系

插件本质上是**带额外元数据和安全层的 MCP 服务器**（如果你还不了解 MCP，可以先简单理解为"AI 连接外部工具的标准接口"——详见第 03 章）。当你安装一个插件时，底层发生的是一个包含安全守卫和依赖解析的多步流水线。

### 1.1 安装流水线详解

`pluginInstallationHelpers.ts` 中的 `installResolvedPlugin` 是所有安装路径（CLI、UI、hint 推荐）的统一入口：

```
/plugin install my-plugin@marketplace
  → [1] 策略守卫：isPluginBlockedByPolicy(pluginId) → 组织黑名单检查
  → [2] 依赖闭包解析：resolveDependencyClosure() → DFS 遍历（深度优先搜索——像沿着一棵树的树枝一路走到底再回头，逐一检查每个依赖项），检测循环/跨市场
  → [3] 传递依赖策略检查：每个依赖也必须通过 isPluginBlockedByPolicy
  → [4] 写入 settings：将整个闭包的 enabledPlugins 一次性写入
  → [5] 物化循环：逐个 cacheAndRegisterPlugin()
    → 从 Marketplace 下载插件源码
    → 签名验证（确保未被篡改）
    → 同形字检测（确保名字不是仿冒品）
    → 计算版本号（manifest > marketplace entry > Git SHA > unknown）
    → 移动到版本化缓存路径 ~/.claude/plugins/cache/<marketplace>/<plugin>/<version>/
    → 可选：ZIP 压缩缓存（isPluginZipCacheEnabled）
    → 写入 installed_plugins.json（V1 和 V2 格式）
  → [6] 清除所有缓存：clearAllCaches()
  → [7] 返回结果：success + 依赖计数后缀（"+ N dependencies"）
```

### 1.2 异常路径分析

安装流水线有 **七种明确的失败类型**，每种都有结构化的错误返回（`InstallCoreResult` 联合类型）：

| 失败类型 | 触发条件 | 用户看到的信息 |
|---------|---------|--------------|
| `blocked-by-policy` | 组织管理员在 managed-settings.json 中禁用了该插件 | "Plugin X is blocked by your organization's policy" |
| `dependency-blocked-by-policy` | 根插件本身未被禁，但它的某个传递依赖被组织策略禁用 | "Cannot install X: dependency Y is blocked" |
| `resolution-failed: cycle` | 依赖链出现循环（A→B→C→A） | "Dependency cycle: A → B → C → A" |
| `resolution-failed: cross-marketplace` | 依赖来自不同的 marketplace 且未在 allowlist 中 | "Dependency X is in marketplace Y, which is not in the allowlist" |
| `resolution-failed: not-found` | 依赖在任何已配置的 marketplace 中都找不到 | "Dependency X not found. Is the Y marketplace added?" |
| `local-source-no-location` | 本地源插件缺少 marketplace 安装位置 | "Cannot install local plugin without marketplace install location" |
| `settings-write-failed` | settings 文件写入失败（磁盘满/权限问题） | "Failed to update settings: ..." |

> 💡 **通俗理解**：这就像网购的退货流程有七个原因选项——"商品被禁售"、"供应商被禁"、"物流循环投递"、"从黑市进货"、"找不到供应商"、"本地店没有进货地址"、"收银机坏了"。每种情况都有对应的处理策略，而不是一句笼统的"安装失败"。

### 1.3 插件提供的能力类型

插件提供的不仅是 MCP 工具，还包括：
- **Skills**（带 `whenToUse` 的主动触发能力——详见第 4 节）
- **Commands**（斜杠命令——加载自 `commands/` 目录下的 Markdown 文件）
- **Agents**（子 Agent——加载自 `agents/` 目录下的 Markdown 文件，支持独立模型/工具集）
- **Hooks**（生命周期钩子——`hooks/hooks.json` 中声明，可拦截 PreToolUse/PostToolUse 等事件）
- **Resources**（MCP 资源）
- **Channel servers**（消息通道，如 Telegram 集成——需通过白名单审查）
- **Output styles**（输出样式——自定义 Claude 输出的格式化规则）

## 2. Marketplace 安全——三重防线

### 2.1 签名验证与名称保护

> 📚 **课程关联**：插件签名验证是**计算机网络/信息安全**课程中"数字签名与 PKI（公钥基础设施）"的直接应用。发布者用私钥签名插件内容，客户端用公钥验证——这与 HTTPS 证书链、Android APK 签名、以及 Linux 包管理器（apt/yum）的 GPG 签名是同一套信任模型。

每个 Marketplace 上的插件都有基于 Git 提交 SHA 的完整性校验（注意：此处是**完整性校验**而非密码学意义上的发布者真实性签名——见下方"密钥管理模型"）。安装时 Claude Code 校验 SHA 确保：
- 插件内容与 Marketplace 上发布的一致
- 安装后的本地副本未被篡改
- 后续更新时能检测到 marketplace 端的变更

**密钥管理模型**：Claude Code 的签名验证采用的是**Marketplace 仓库签名**而非独立的发布者证书体系。具体来说：

1. **官方 Marketplace** 托管在 GitHub（`anthropics/claude-plugins-official`），Git 仓库本身的完整性由 GitHub 平台保证
2. 安装时的"签名验证"实际上是通过 **Git 提交 SHA** 实现的——`pluginVersioning.ts` 中 `calculatePluginVersion` 会记录 Git commit SHA（`getGitCommitSha`），安装后的版本号直接绑定到特定提交
3. 每次更新时，系统对比本地缓存的 SHA 与 marketplace 最新 SHA，只有不一致时才触发更新

这种模型类似于 Linux 发行版的包仓库签名——**信任的是仓库本身**（由 Anthropic 维护的 GitHub 组织），而非每个插件作者独立持有的证书。相比 npm 的 Sigstore 逐包签名方案，这简化了密钥管理但也意味着安全保证的粒度是 marketplace 级而非插件级。

**官方名称保护**：`schemas.ts` 中的 `validateOfficialNameSource` 实现了名称→源的绑定验证：

```typescript
// 保留名称只能来自 anthropics/ 组织
const ALLOWED_OFFICIAL_MARKETPLACE_NAMES = new Set([
  'claude-code-marketplace', 'claude-plugins-official',
  'anthropic-marketplace', 'anthropic-plugins',
  'agent-skills', 'life-sciences', 'knowledge-work-plugins',
])

// 仿冒检测正则：拦截 "official-claude-*", "anthropic-marketplace-*" 等变体
const BLOCKED_OFFICIAL_NAME_PATTERN =
  /(?:official[^a-z0-9]*(anthropic|claude)|...)/i
```

如果第三方尝试注册一个名为 `anthropic-marketplace-new` 的 marketplace，`isBlockedOfficialName` 会拦截——除非该源确实来自 `github.com/anthropics/` 组织。

### 2.2 同形字防护

插件名称会经过 Unicode 同形字检测——防止攻击者注册视觉上相似但实际不同的名称。例如：
- `github-tools` vs `githüb-tools`（ü 替代 u）
- `official-plugin` vs `оfficial-plugin`（西里尔字母 о 替代拉丁字母 o）

**算法实现**：`schemas.ts` 第 79 行定义了检测逻辑：

```typescript
// 只允许 ASCII 可打印字符（U+0020 到 U+007E）
const NON_ASCII_PATTERN = /[^\u0020-\u007E]/

function isBlockedOfficialName(name: string): boolean {
  // 1. 在白名单中的官方名称直接放行
  if (ALLOWED_OFFICIAL_MARKETPLACE_NAMES.has(name.toLowerCase())) return false
  // 2. 包含任何非 ASCII 字符 → 直接拦截
  if (NON_ASCII_PATTERN.test(name)) return true
  // 3. 匹配仿冒模式正则 → 拦截
  return BLOCKED_OFFICIAL_NAME_PATTERN.test(name)
}
```

Claude Code 采用了**最严格的白名单方案**——不是基于 Unicode Confusables 数据库做字符级映射（如 Chrome 的 ICU Confusable Detection），也不是基于整词映射表比对（如 Firefox），而是**直接禁止所有非 ASCII 字符出现在 marketplace 名称中**。

**这个设计决策的权衡**：

| 维度 | Claude Code 方案 | Chrome ICU 方案 | Firefox 整词映射 |
|------|----------------|----------------|----------------|
| 实现复杂度 | 极低（一个正则） | 高（需要 ICU 库） | 中（维护映射表） |
| 误报率 | **高**——中文/日文/韩文名称全部被拦截 | 低——只拦截混合脚本 | 低——精确映射 |
| 漏报率 | 极低——非 ASCII 全禁 | 中——新的 confusable 对可能遗漏 | 中——映射表需要更新 |
| 适用场景 | Marketplace 名称（英文为主） | 域名（需要国际化支持） | 域名（需要国际化支持） |

> 💡 **通俗理解**：Chrome 的同形字检测像是海关的身份验证——逐字核对护照（Unicode 映射表），允许所有国家的公民入境但会检测假证件。Claude Code 的方案更像是"只接受英文护照"——简单粗暴，绝对不会漏过冒牌货，但非英文国家的合法用户也进不来。这对目前以英文为主的 CLI 插件生态是合理的，但如果未来中文开发者社区活跃起来，需要引入更细粒度的方案。

### 2.3 白名单机制

特定高权限功能（如 Channel Permission Relay——允许插件通过 Telegram/Slack 等通道接收和处理消息）需要额外的白名单审查。

**`channelAllowlist.ts` 的完整实现**：

```typescript
// GrowthBook 远程特性开关控制的白名单
function getChannelAllowlist(): ChannelAllowlistEntry[] {
  const raw = getFeatureValue_CACHED_MAY_BE_STALE('tengu_harbor_ledger', [])
  const parsed = ChannelAllowlistSchema().safeParse(raw)
  return parsed.success ? parsed.data : []
}

// 全局 channels 开关（独立于白名单）
function isChannelsEnabled(): boolean {
  return getFeatureValue_CACHED_MAY_BE_STALE('tengu_harbor', false)
}
```

这里的关键架构决策：

1. **远程控制**：白名单通过 GrowthBook（Anthropic 的特性开关服务）下发，5 分钟刷新一次。这意味着 Anthropic 可以在不发版的情况下紧急撤销某个插件的 channel 权限
2. **粒度是 `{marketplace, plugin}` 对**：不是每个 MCP 服务器单独白名单。源码注释解释了原因——"per-server gating was overengineering — a plugin that sprouts a malicious second server is already compromised"
3. **Fail-closed 设计**：如果 GrowthBook 不可达，`getFeatureValue_CACHED_MAY_BE_STALE` 返回缓存值；如果从未获取过，返回默认值 `[]`（空白名单）和 `false`（channels 关闭）。这是安全设计中的 fail-closed 原则——网络不可达时，禁止一切而非放行一切
4. **开发者旁路**：`--dangerously-load-development-channels` flag 可以绕过白名单检查，但名称中的 "dangerously" 前缀确保开发者知道风险

### 2.4 企业策略层（第四道防线）

除了签名/同形字/白名单三重防线，企业部署场景还有 **`marketplaceHelpers.ts` 中的策略层**——这可以看作第四道防线：

```typescript
// 策略优先级：黑名单 > 白名单 > 默认放行
function isSourceAllowedByPolicy(source: MarketplaceSource): boolean {
  // 1. 先检查黑名单（blockedMarketplaces）
  if (isSourceInBlocklist(source)) return false
  // 2. 再检查白名单（strictKnownMarketplaces）
  const allowlist = getStrictKnownMarketplaces()
  if (allowlist === null) return true  // 无限制
  // 3. 支持精确匹配和模式匹配（hostPattern / pathPattern）
  return allowlist.some(allowed => {
    if (allowed.source === 'hostPattern')
      return doesSourceMatchHostPattern(source, allowed)
    if (allowed.source === 'pathPattern')
      return doesSourceMatchPathPattern(source, allowed)
    return areSourcesEqual(source, allowed)
  })
}
```

企业管理员可以通过 `managed-settings.json` 配置：
- `blockedMarketplaces`：黑名单，精确阻断特定 marketplace 源（包括跨格式检测——git URL 格式的 GitHub 仓库也会被匹配到 github 格式的黑名单条目）
- `strictKnownMarketplaces`：白名单，只允许列出的源。支持 `hostPattern`（正则匹配域名）和 `pathPattern`（正则匹配本地路径），适用于大型组织的自托管 marketplace
- `enabledPlugins: { "plugin@marketplace": false }`：单个插件级别的禁用

## 3. 依赖解析

### 3.1 DFS 闭包计算

`dependencyResolver.ts` 实现了 `apt` 风格的依赖解析——依赖是**存在性保证**（"B 的组件必须在 A 运行时可用"），不是模块图：

```
Plugin A (dependencies: ["B"])
  → DFS walk: A → B → B 的 dependencies...
  → 已启用的依赖跳过（避免重复安装到 settings）
  → 循环检测（stack.includes(id) → cycle 错误）
  → 跨市场阻断（不同 marketplace 的依赖默认被禁止）
  → 返回：安装闭包 [B, A]（拓扑序：依赖在前，根在后）
```

**跨市场安全阻断**是一个关键设计——安装来自受信 marketplace 的插件不应该静默拉取不受信 marketplace 的依赖。有两种逃逸方式：
1. 用户手动预先安装跨市场依赖（成为 `alreadyEnabled`，DFS 跳过）
2. 根 marketplace 的 `allowCrossMarketplaceDependenciesOn` 白名单——**注意只有根的白名单生效**，没有传递信任

### 3.2 加载时降级

`verifyAndDemote` 是加载时的安全网——如果某个插件的依赖未启用或不存在，该插件会被**降级**（demoted）而非让整个系统崩溃。这个函数使用**不动点循环**：

```
循环直到无变化：
  对每个已启用的插件：
    检查其所有依赖是否在 enabled 集合中
    如果不满足 → 从 enabled 中移除（触发新一轮循环）
    → 因为移除 A 可能导致依赖 A 的 B 也不满足
```

这类似于操作系统的 `depmod` 机制——一个内核模块被移除后，依赖它的模块也会级联卸载。

## 4. 插件与 Skills 的协作——从被动工具到主动推荐

### 4.1 核心范式转变

插件可以注册 Skills——带有 `whenToUse` 描述的能力，让 AI 可以**主动触发**而非仅响应用户的斜杠命令。这意味着一个安装了 GitHub 插件的 Claude 实例：

- 不仅响应 `/github create-pr` 斜杠命令
- 还会在检测到 "我想创建一个 PR" 这样的意图时**主动建议**使用这个 skill

这描述的是从**被动工具调用**到**主动能力推荐**的范式转变。在 LangChain/AutoGPT 的工具选择中，通常由 LLM 基于工具描述做 routing（每次对话都要消耗 token 评估所有工具）；Claude Code 的 `whenToUse` 模式把路由逻辑**前置到了 skill 声明中**——每个 skill 自己声明"什么时候该用我"，这降低了 LLM 的选择负担。

### 4.2 命令加载机制

`loadPluginCommands.ts`（30KB）负责从插件目录加载 Skills 和 Commands。加载流程：

1. **目录扫描**：遍历插件的 `skills/` 和 `commands/` 目录
2. **Frontmatter 解析**：从 YAML frontmatter 中提取 `description`、`whenToUse`、`allowed-tools`、`shell` 等元数据
3. **命名空间化**：命令名格式为 `{pluginName}:{namespace}:{commandName}`，如 `github:pr:create`
4. **变量替换**：`substitutePluginVariables` 和 `substituteUserConfigInContent` 处理插件选项中的 `${{option_name}}` 占位符

### 4.3 Agent 加载

`loadPluginAgents.ts`（12KB）支持插件注册**子 Agent**——独立的 AI 角色，可以有自己的系统提示、工具集、甚至不同的模型。这使得一个"数据库管理"插件可以注册一个专门的 DBA Agent，拥有只读的数据库查询工具但不具备文件写入能力。

### 4.4 Hooks 注册

`loadPluginHooks.ts`（10KB）让插件可以拦截 Claude Code 的生命周期事件。`hooks/hooks.json` 的 schema 验证是**硬错误**——与 frontmatter 解析的"静默降级"不同，一个格式错误的 hooks.json 会导致整个插件加载失败。这是因为 hook 如果被静默忽略，可能导致安全关键的拦截逻辑被绕过。

### 4.5 安装推荐（Hint 系统）

`hintRecommendation.ts` 实现了一个巧妙的**被动安装推荐**机制：

1. CLI 工具（如 `aws`、`gh`）可以在 stderr 中输出 `<claude-code-hint type="plugin" value="aws-plugin@claude-code-marketplace" />`
2. Claude Code 的 Bash 工具检测到这个标签后，调用 `maybeRecordPluginHint`
3. 经过一系列**同步**过滤（session 去重、用户偏好、已安装检查、策略检查、官方 marketplace 限定、config 增长上限 100）
4. 通过后，异步调用 `resolvePluginHint` 从 marketplace 缓存中获取插件信息
5. 最终向用户显示安装推荐对话框

这个 **show-once** 语义通过 `GlobalConfig.claudeCodeHints.plugin[]` 数组持久化——不论用户选择"安装"还是"跳过"，同一个插件不会再次推荐。

## 5. 自动更新与供应链安全

### 5.1 更新策略概览

`pluginAutoupdate.ts` 实现了完整的后台自动更新流程，在每次 Claude Code 启动时以非阻塞方式运行：

```
启动时：autoUpdateMarketplacesAndPluginsInBackground()
  → [1] 检查是否禁用自动更新（shouldSkipPluginAutoupdate）
  → [2] 获取启用了 autoUpdate 的 marketplace 列表
  → [3] 并行刷新这些 marketplace（git pull / 重新下载）
  → [4] 对比已安装插件与 marketplace 最新版本
  → [5] 逐个更新有变化的插件（updatePluginOp）
  → [6] 通知 REPL 显示"重启以生效"提示
```

### 5.2 更新的安全保证

**更新是非就地的（non-inplace）**——新版本下载到新的版本化目录（`cache/<marketplace>/<plugin>/<new-version>/`），旧版本通过 `.orphaned_at` 标记保留 7 天。这确保：

1. **并发安全**：其他正在运行的 Claude Code 会话仍然引用旧版本目录，不会因为更新而中断
2. **回滚能力**：7 天内旧版本仍在磁盘上（`orphanedPluginFilter.ts` 中的 `getGlobExclusionsForPluginCache` 确保 ripgrep 搜索不会命中这些孤儿目录）
3. **原子性**：`rename` 操作保证目录替换的原子性；对于 marketplace name = plugin name 的特殊情况（子目录冲突），使用临时目录做中转

### 5.3 下架自动卸载

`pluginBlocklist.ts` 实现了**下架检测与强制卸载**——如果一个已安装的插件被 marketplace 下架（从 marketplace.json 的 plugins 数组中移除），且该 marketplace 启用了 `forceRemoveDeletedPlugins`：

```typescript
function detectDelistedPlugins(installed, marketplace, marketplaceName) {
  const marketplacePluginNames = new Set(marketplace.plugins.map(p => p.name))
  // 在已安装列表中找出属于该 marketplace 但不在最新清单中的插件
  for (const pluginId of Object.keys(installed.plugins)) {
    if (!pluginId.endsWith(`@${marketplaceName}`)) continue
    const pluginName = pluginId.slice(0, -suffix.length)
    if (!marketplacePluginNames.has(pluginName)) {
      delisted.push(pluginId)
    }
  }
}
```

被下架的插件会被自动从所有用户可控 scope（user/project/local）卸载，并记录到 `flagged-plugins.json`。用户在 `/plugins` 界面会看到"Flagged"提示，48 小时后自动消失。

> 💡 **通俗理解**：这就像商场定期巡检——如果某个品牌被总部撤销了授权（从 marketplace 下架），商场会直接把这个柜台关掉（自动卸载），然后在公告栏贴一个通知（flagged 标记），过几天通知也会撤下。`managed` scope 的插件不会被自动卸载——那是企业管理员的地盘，需要管理员自己处理。

### 5.4 自动更新的 autoUpdate 策略

不是所有 marketplace 都会自动更新。策略由 `isMarketplaceAutoUpdate` 决定：

```typescript
function isMarketplaceAutoUpdate(name: string, entry: { autoUpdate?: boolean }) {
  return entry.autoUpdate ??
    (ALLOWED_OFFICIAL_MARKETPLACE_NAMES.has(name) &&
     !NO_AUTO_UPDATE_OFFICIAL_MARKETPLACES.has(name))
}
```

- **官方 marketplace**：默认 autoUpdate = true（除了 `knowledge-work-plugins` 被排除）
- **第三方 marketplace**：默认 autoUpdate = false，需要用户显式启用
- **Settings 声明优先**：用户在 settings 中的 `autoUpdate` 设置覆盖所有默认值

这意味着**恶意更新的风险窗口不同**：官方插件通过 Anthropic 审查，自动更新风险可控；第三方插件需要用户主动选择承担自动更新的风险。

## 6. createMovedToPluginCommand 迁移模式

当功能从内置命令迁移到插件时，系统使用一个向后兼容策略。`createMovedToPluginCommand.ts` 创建一个**占位命令**：

```typescript
createMovedToPluginCommand({
  name: 'security-review',      // 旧命令名
  description: '...',
  pluginName: 'security-review', // 新插件名
  pluginCommand: 'review',       // 插件中的命令名
  getPromptWhileMarketplaceIsPrivate: ..., // Marketplace 未公开时的降级逻辑
})
```

当用户输入旧命令名时，内部用户（`USER_TYPE === 'ant'`）会收到安装插件的引导；外部用户则会执行 `getPromptWhileMarketplaceIsPrivate` 提供的降级逻辑（通常是直接执行旧的功能实现）。这确保了：
- 老用户的肌肉记忆不会失效
- 迁移过程透明可见
- 无需在内置代码中维护迁移后的功能
- 有渐进的灰度策略（内部先迁移，外部后跟进）

目前 `pr_comments` 和 `security-review` 两个命令已经使用了这个模式。

## 7. pluginOnlyPolicy

`pluginPolicy.ts` 是整个策略子系统中最简洁的模块（仅 21 行），它实现了一个关键的企业安全能力：

```typescript
function isPluginBlockedByPolicy(pluginId: string): boolean {
  const policyEnabled = getSettingsForSource('policySettings')?.enabledPlugins
  return policyEnabled?.[pluginId] === false
}
```

在企业部署场景中，管理员可以通过 `managed-settings.json` 锁定：
- `enabledPlugins: { "plugin@marketplace": false }` → 禁止特定插件
- 结合 `strictKnownMarketplaces` → 只允许来自审查过的源的 marketplace
- 结合 `blockedMarketplaces` → 黑名单特定源

**关于 pluginOnly 策略的安全边界**：需要注意，即使启用了 pluginOnly 策略限制用户只能使用审查过的插件，插件本身的 MCP 服务器仍然可以发起任意网络请求。pluginOnly 阻止的是用户手动连接未审查的 MCP 服务器，但不限制已安装插件的运行时行为——这是 MCP 进程隔离模型（而非 WASM 沙箱）的固有边界。

## 8. 版本管理与缓存策略

### 8.1 版本计算优先级

`pluginVersioning.ts` 定义了版本号的 **五级优先级链**：

```
1. plugin.json 中的 version 字段 → 如 "1.2.3"
2. marketplace entry 中的 version → 如 "1.0.0"
3. Git commit SHA（预解析的 / installPath 中提取的）→ 如 "a1b2c3d4e5f6"
4. git-subdir 特殊处理：SHA + 路径哈希 → 如 "a1b2c3d4-8f9e0d1c"
5. 'unknown'（最后手段）
```

git-subdir 的特殊处理值得注意——如果一个 monorepo 的两个子目录各有一个插件，它们的 Git SHA 相同但路径不同。路径哈希（SHA256 前 8 位）防止了缓存目录的碰撞。

### 8.2 Marketplace 协调

`reconciler.ts` 负责让 `known_marketplaces.json`（实际状态）与 settings 中的声明（期望状态）保持一致。这是一个**声明式协调**模式——类似 Kubernetes 的 reconciliation loop：

- `diffMarketplaces()`：对比声明与现状，输出 missing / sourceChanged / upToDate
- `reconcileMarketplaces()`：执行 diff 结果——安装缺失的、更新源变更的、跳过最新的
- 操作是**幂等**且**只加不删**的（additive only）

## 9. 设计取舍与评价

**优秀**：
1. **结构化错误处理**：`InstallCoreResult` 联合类型覆盖了所有已知失败模式，每种失败都有可操作的错误信息，这在安全关键系统中是最佳实践
2. **非就地更新 + 孤儿保留**：7 天的 orphan 保留期解决了并发会话问题，`orphanedPluginFilter.ts` 的 ripgrep 排除确保搜索不会污染
3. **依赖安全隔离**：跨 marketplace 依赖默认禁止、传递依赖的策略检查、加载时不动点降级——三层保护确保"安装 A 不会偷偷引入不受信的 B"
4. **企业策略的优先级链**（黑名单 > 白名单 > 默认）覆盖了从初创团队到大型组织的不同安全需求
5. **Hint 推荐的 show-once 语义**和 config 增长上限（100）避免了推荐疲劳和无限增长

**代价**：
1. **插件生态的安全依赖 Marketplace 的审查质量**——签名验证的粒度是 marketplace 级（Git 仓库完整性），而非逐插件的独立签名
2. **同形字检测的 ASCII-only 策略**会误拦所有非 ASCII 名称（中文/日文/韩文插件名），目前没有 appeal 机制或字符白名单
3. **白名单是中心化控制**（GrowthBook 远程下发），不在白名单中的插件无法获得 channel 权限。但 fail-closed 设计是正确的安全选择
4. **pluginOnly 策略不限制插件内部的网络行为**——这是 MCP 进程隔离模型的固有边界，WASM 沙箱（如 Zed 的做法）可以提供更强的隔离但会限制插件能力
5. **自动更新的竞态**：如果更新在 REPL 回调注册之前完成，更新通知会存入 `pendingNotification`——虽然有处理，但用户可能在数秒内错过更新提示

---

