claude-code-best/docs/features/plugin-auto-update.md
IronRookieCoder 3a463313ea fix(plugin): make marketplace auto-update configurable by default
## Bug 详情
/plugin marketplace 中的 Enable/Disable auto-update 会继承 CLI binary auto-updater 的禁用状态。在 CSC 默认关闭 CLI auto-updater 的场景下,plugin marketplace 的自动更新配置入口不可见,用户无法按 marketplace 配置 autoUpdate。

## 根因
shouldSkipPluginAutoupdate() 复用了 isAutoUpdaterDisabled(),导致 ENABLE_AUTOUPDATER、/config channel、development build、nonessential traffic 等 CLI updater 门禁同时影响 plugin marketplace 自动更新。

## 修复方案
将 plugin marketplace auto-update 门禁收敛为 DISABLE_AUTOUPDATER 和 FORCE_AUTOUPDATE_PLUGINS:默认不跳过 plugin auto-update;DISABLE_AUTOUPDATER 显式关闭;FORCE_AUTOUPDATE_PLUGINS 可覆盖关闭状态。

## 变更要点
- 调整 shouldSkipPluginAutoupdate(),使 plugin auto-update 独立于 CLI binary updater 配置
- 新增 plugin autoupdate 门禁单元测试
- 补充 plugin auto-update 机制文档,记录 UI、配置优先级和环境变量行为

## 自测
- bun test src/utils/__tests__/pluginAutoupdateConfig.test.ts
- bun test src/utils/settings/__tests__/config.test.ts
- bun run typecheck 仍失败,失败项为仓库既有 Zod 类型、缺声明和依赖类型问题,非本次变更引入
2026-05-16 16:58:20 +08:00

185 lines
8.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Plugin Auto-Update 机制说明
本文记录当前代码中的 plugin auto-update 机制包括启动链路、配置来源、UI 配置项、手动更新行为,以及复核时发现的配置注意点。
## 结论摘要
Plugin auto-update 是启动后的后台任务,用于刷新开启了 `autoUpdate` 的 marketplaces并把这些 marketplaces 下已安装且与当前项目相关的 plugins bump 到新版本缓存。
它由两层配置决定:
1. Plugin auto-update 门禁不能处于跳过状态:`DISABLE_AUTOUPDATER=1` 会关闭它,除非设置 `FORCE_AUTOUPDATE_PLUGINS=true` 强制运行。
2. 单个 marketplace 必须被判定为 `autoUpdate: true`
更新是 disk-only后台任务只更新版本缓存和 `installed_plugins.json`,当前会话内存中的 plugin 列表不会立即替换。用户需要运行 `/reload-plugins` 或重启后才会使用新版本。
## 启动链路
后台入口在 `src/utils/backgroundHousekeeping.ts`
- `startBackgroundHousekeeping()` 会调用 `autoUpdateMarketplacesAndPluginsInBackground()`
- REPL 中第一次提交后会启动 housekeeping。
- headless 主流程在非 bare 模式下也会动态导入并启动 housekeeping。
核心实现位于 `src/utils/plugins/pluginAutoupdate.ts`
1. `autoUpdateMarketplacesAndPluginsInBackground()` 先调用 `shouldSkipPluginAutoupdate()`
2. 如果未跳过,读取启用 auto-update 的 marketplaces。
3. 对这些 marketplaces 调用 `refreshMarketplace()`
4. 调用 `updatePluginsForMarketplaces()` 更新这些 marketplaces 下已安装的 plugins。
5. 如果有 plugin 被更新,通过 `onPluginsAutoUpdated()` 通知 UI。
后台刷新 marketplace 时会传入 `disableCredentialHelper: true`,避免启动后台任务触发交互式凭据提示。
## Plugin Auto-Update 门禁
门禁在 `src/utils/config.ts``shouldSkipPluginAutoupdate()`
- 默认不跳过 plugin auto-update因此 `/plugin marketplace` 中的 `Enable/Disable auto-update` 默认可见。
- `DISABLE_AUTOUPDATER=1` 会跳过 plugin auto-update并隐藏 marketplace 详情里的 `Enable/Disable auto-update`
- `FORCE_AUTOUPDATE_PLUGINS=1` 可以在 `DISABLE_AUTOUPDATER=1` 时重新启用 plugin auto-update 及其 UI 配置入口。
这与官方插件文档中的配置方式一致:如果用户禁用了自动更新,但仍希望插件自动更新可配置/可运行,可同时设置:
```bash
export DISABLE_AUTOUPDATER=1
export FORCE_AUTOUPDATE_PLUGINS=1
```
注意:`/config` 中的 CLI binary auto-update 状态、development build 限制、`ENABLE_AUTOUPDATER` 门禁和 `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC` 不再决定 plugin marketplace 的 per-marketplace auto-update 开关是否可见。
## Marketplace `autoUpdate` 判定
判定逻辑在 `src/utils/plugins/schemas.ts``isMarketplaceAutoUpdate()`
- 如果 marketplace entry 明确设置了 `autoUpdate`,使用显式值。
- 否则,官方 marketplace 默认开启 auto-update。
- 第三方 marketplace 默认关闭 auto-update。
- `knowledge-work-plugins` 虽在官方名称集合中,但默认不自动更新。
官方名称集合包括:
- `claude-code-marketplace`
- `claude-code-plugins`
- `claude-plugins-official`
- `anthropic-marketplace`
- `anthropic-plugins`
- `agent-skills`
- `life-sciences`
- `knowledge-work-plugins`
其中 `knowledge-work-plugins``NO_AUTO_UPDATE_OFFICIAL_MARKETPLACES` 中,因此默认关闭。
## 配置来源和优先级
Plugin auto-update 涉及两类 marketplace 配置:
1. 状态层:`known_marketplaces.json`
- schema 在 `KnownMarketplaceSchema`
- 字段包括 `source`、`installLocation`、`lastUpdated`、可选 `autoUpdate`
- 记录已 materialized 的 marketplace 缓存位置和刷新状态。
2. 意图层settings 中的 `extraKnownMarketplaces`
- schema 在 `ExtraKnownMarketplaceSchema`
- 也支持可选 `autoUpdate`
- 用于 user/project/local/managed settings 声明希望存在的 marketplace。
自动更新读取启用 marketplaces 时,会先读取 `known_marketplaces.json`,再用 settings 声明的 `extraKnownMarketplaces[name].autoUpdate` 覆盖状态层值。也就是说settings 声明的 `autoUpdate` 优先于 `known_marketplaces.json` 中的 `autoUpdate`
`getDeclaredMarketplaces()` 的合并顺序是:
1. 隐式官方 marketplace 声明。
2. `--add-dir` 里的 extra marketplaces。
3. merged settings 里的 `extraKnownMarketplaces`
## `/plugin marketplace` UI
Marketplace 管理 UI 在 `src/commands/plugin/ManageMarketplaces.tsx`
详情菜单包含:
- `Browse plugins`
- `Update marketplace`
- `Enable auto-update``Disable auto-update`
- `Remove marketplace`
`Enable/Disable auto-update` 只有在 `shouldSkipPluginAutoupdate()` 为 false 时才显示。默认情况下该条件为 false因此 marketplace auto-update 配置默认可见。只有显式设置 `DISABLE_AUTOUPDATER=1` 且未设置 `FORCE_AUTOUPDATE_PLUGINS=1` 时才会隐藏。
点击该开关后会调用 `setMarketplaceAutoUpdate()`
- 更新 `known_marketplaces.json` 中该 marketplace 的 `autoUpdate`
- 如果该 marketplace 是 settings 声明的,还会写回声明它的同一个 settings source避免下次被 settings 覆盖。
- seed-managed marketplace 不允许切换 auto-update会报错提示 seed 内容由管理员控制。
当某 marketplace 的 auto-update 已启用时详情页底部会显示说明CoStrict 会自动更新该 marketplace 及其已安装 plugins。
## `/config` UI
`/config` 中的 `Auto-update channel` 属于全局 CLI auto-updater 配置,不是 plugin 专用配置。
它会展示:
- `latest`
- `stable`
- `disabled` 和 disabled reason
Plugin auto-update 不使用 `latest/stable` channel 来选择插件版本,也不通过 `/config` 配置。插件版本来自 marketplace 刷新后的 plugin source、manifest version、git commit 等版本计算逻辑。
## 手动更新和自动更新的区别
手动更新路径包括:
- `/plugin marketplace update`
- marketplace 详情页中的 `Update marketplace`
手动更新会:
1. 调用 `refreshMarketplace()` 刷新指定 marketplace。
2. 调用 `updatePluginsForMarketplaces()` bump 该 marketplace 下已安装的 plugins。
3. 清理缓存并刷新 UI 状态。
手动更新不依赖该 marketplace 的 `autoUpdate` 标记,也不通过后台 auto-update 的通知回调。它是用户主动操作。
自动更新只处理 auto-update enabled marketplaces并在后台静默执行失败通常只写 debug log不阻塞用户。
## Plugin 更新细节
实际 plugin bump 由 `src/services/plugins/pluginOperations.ts``updatePluginOp()` 完成:
1. 解析 plugin ID。
2. 从 marketplace 找到 plugin entry。
3.`installed_plugins.json` 找到对应 scope 和 projectPath 的安装记录。
4. 对远程 source 下载临时副本;对本地 source 使用 marketplace 中的相对路径。
5. 调用 `calculatePluginVersion()` 计算新版本。
6. 如果当前安装已经是新版本,返回 already up to date。
7. 否则复制到 versioned cache。
8. 调用 `updateInstallationPathOnDisk()` 写回 `installed_plugins.json`
9. 如果旧版本路径不再被任何安装引用,标记为 orphaned。
`updateInstallationPathOnDisk()` 只改磁盘文件,并清掉 installed plugins cache它不会更新当前会话中的 in-memory installed plugins。这个设计保证后台更新不会在运行中替换正在使用的 plugin。
## Pending 更新和通知
后台 auto-update 成功后:
- 如果 REPL 已注册 callback立即调用 callback。
- 如果 auto-update 先完成而 REPL hook 尚未注册,会先保存到 `pendingNotification`,注册后补发。
UI hook 在 `src/hooks/notifs/usePluginAutoupdateNotification.tsx`
- remote mode 下不显示。
- 通知内容为 plugin 名称和 `Run /reload-plugins to apply`
- 通知优先级为 low默认 10 秒超时。
`getAutoUpdatedPluginNames()` 会通过 `hasPendingUpdates()` 判断磁盘安装记录和内存安装记录是否不同,并返回 pending update 的 plugin 名称。
## 关键注意点
- Plugin marketplace auto-update 配置默认可见。
- `DISABLE_AUTOUPDATER=1` 会关闭 plugin auto-update`FORCE_AUTOUPDATE_PLUGINS=1` 可专门恢复 plugin auto-update。
- 官方 marketplaces 默认 auto-update第三方 marketplaces 默认不 auto-update。
- settings 中 `extraKnownMarketplaces[name].autoUpdate` 优先于 `known_marketplaces.json`
- 手动 marketplace update 会同步 bump 已安装 plugins但不等于开启 auto-update。
- auto-update 是 disk-only当前会话需要 `/reload-plugins` 或重启才能应用。
- `/config``Auto-update channel` 不决定 plugin 版本来源。