插件与 marketplace · 打包分发扩展
插件把 skills、MCP 服务器、应用连接器和 hooks 装进一个目录,靠 marketplace 分发。本篇拆开 codex-core-plugins:清单的四个位置与两种格式、marketplace 的来源与策略、安装时复制进版本化缓存的流程,以及每个会话如何把启用的插件拆回各个扩展子系统。
插件与 marketplace · 打包分发扩展
前面三篇讲的配置、技能、hooks,加上下一篇的 MCP,都能单独使用。插件是它们之上的分发单元:一个目录,一份清单,通过 marketplace 安装、启用、升级。实现集中在 codex-core-plugins(codex-rs/core-plugins/,去掉测试约 2.3 万行),共享的数据模型在 codex-plugin(codex-rs/plugin/)。
怎么用
插件目录里放 .codex-plugin/plugin.json 清单和 skills/、.mcp.json、.app.json、hooks/hooks.json 等组件;marketplace 是一份 marketplace.json,列出插件及其来源。命令行用 codex plugin add 名字@市场、codex plugin marketplace add 来源,TUI 用 /plugins。字段与操作见手册插件。
全景:安装一次,每个会话拆开用
两条入口共用同一个 PluginsManager:codex plugin 命令行直接在本进程里构造它(codex-rs/cli/src/plugin_cmd.rs:731),TUI 的 /plugins 则经 app-server 的 plugin/list、plugin/install、marketplace/add 等方法调用。
清单:四个位置,两种格式
find_plugin_manifest_path 先看插件根目录的 plugin.json,只有它的 $schema 以 https://agent-plugins.org/schemas/ 开头才认作可移植的 Agent Plugins 格式;否则依次找:
pub const DISCOVERABLE_PLUGIN_MANIFEST_PATHS: &[&str] = &[
".codex-plugin/plugin.json",
".claude-plugin/plugin.json",
".cursor-plugin/plugin.json",
];(codex-rs/exec-server-protocol/src/protocol.rs:49)
是符号链接的根目录清单直接不认。后三种统称 Legacy 格式,Claude Code 与 Cursor 的插件可以不改清单直接装。解析在 codex-rs/core-plugins/src/manifest.rs:skills、mcpServers、apps、hooks 里的路径必须以 ./ 开头、不能含 ..,否则这个字段被忽略并记警告;interface.defaultPrompt 最多 3 条、每条最长 128 字符。清单没写的组件走默认位置:skills/、.mcp.json、.app.json、hooks/hooks.json(codex-rs/core-plugins/src/loader.rs:67)。
两种格式的待遇不同,load_plugin 里分得很清楚:
loaded_plugin.skill_discovery_mode = match loaded_manifest.format {
PluginManifestFormat::Legacy => SkillDiscoveryMode::Recursive,
PluginManifestFormat::AgentPlugin => SkillDiscoveryMode::DirectChildren,
};
// ...
let (hook_sources, hook_load_warnings) =
if loaded_manifest.format == PluginManifestFormat::AgentPlugin {
(Vec::new(), Vec::new())
} else {
load_plugin_hooks((codex-rs/core-plugins/src/loader.rs:896)
Agent Plugins 格式的技能只认 skills/ 下一层目录,不加载 hooks,也不加载应用连接器;Legacy 格式递归扫描技能目录。Legacy 插件里 Claude Code 风格的 commands/ 斜杠命令,会在安装时被转写成技能,放进 .codex-plugin/migrated-command-skills/;frontmatter 没写描述、或转写后超过 4000 字节的命令直接跳过(codex-rs/core-plugins/src/command_migration/plugin.rs:25)。
marketplace:插件从哪来
marketplace 清单认四个相对位置:.agents/plugins/marketplace.json、.agents/plugins/api_marketplace.json、.claude-plugin/marketplace.json、.cursor-plugin/marketplace.json(codex-rs/core-plugins/src/marketplace.rs:20)。个人 marketplace 从家目录自动发现;用 codex plugin marketplace add 加的会写进用户配置的 [marketplaces.名字],Git 来源克隆到 $CODEX_HOME/.tmp/marketplaces/名字,本地来源原地引用。每个插件条目的来源是三选一:
pub enum MarketplacePluginSource {
Local {
path: AbsolutePathBuf,
},
Git {
url: String,
path: Option<String>,
ref_name: Option<String>,
sha: Option<String>,
},
Npm {
package: String,
version: Option<String>,
registry: Option<String>,
},
}(codex-rs/core-plugins/src/marketplace.rs:128)
条目还带 policy.installation(AVAILABLE、INSTALLED_BY_DEFAULT、NOT_AVAILABLE)与 policy.authentication(ON_INSTALL、ON_USE)。管理员可以在 requirements 里设 [marketplaces] restrict_to_allowed_sources = true,再用 allowed_sources 列出允许的 Git 地址与 ref、主机名正则或本地路径,安装前由 MarketplacePolicy::validate_install 把关,已配置的插件也按同一策略过滤(codex-rs/core-plugins/src/marketplace_policy.rs:43)。
OpenAI 自己的精选目录有两种形态。用 ChatGPT 账号这类走 Codex 后端的方式登录、且功能开关 remote_plugin(默认开)打开时,远程目录生效,由后端提供 openai-curated-remote 等目录;远程目录不可用时,才在后台把 GitHub 上的 openai/plugins 仓库同步到 $CODEX_HOME/.tmp/plugins,作为本地的 openai-curated(codex-rs/core-plugins/src/manager.rs:723)。同步先用 git 拉,失败改走 GitHub HTTP API,再失败才考虑 ChatGPT 后端的导出包,而且导出包只用于首次引导:
// The export archive is a lagging backup path. Only use it to bootstrap a
// missing local curated snapshot, never to refresh an existing one.(codex-rs/core-plugins/src/startup_sync.rs:142)
同步过程用文件锁防止多个 Codex 进程同时写。远程目录里的插件以压缩包下载,下载上限 100 MiB、解压后上限 512 MiB(codex-rs/core-plugins/src/remote_bundle.rs:32)。
安装:复制进版本化缓存
install_resolved_plugin(codex-rs/core-plugins/src/manager.rs:2223)的步骤:先把来源物化成本地目录(Git 克隆、npm 拉包或直接用本地路径),再交给 PluginStore 复制进 $CODEX_HOME/plugins/cache/市场/插件/版本/——先复制到同级临时目录并核对清单在复制过程中没被改动,再整体改名启用;新版本启用后删掉旧版本目录,同版本重装则先把旧目录挪去备份,替换失败可以回滚(codex-rs/core-plugins/src/store.rs:550)。清单的 name 必须与 marketplace 里的插件名一致。版本号取清单的 version,缺省为 local(Agent Plugins 格式缺省 1.0.0),精选目录的插件用仓库 commit 的前 8 位。最后调用 set_user_plugin_enabled,在用户配置写入 [plugins."插件@市场"] enabled = true。
加载时若同一插件目录下仍有多个版本,local 优先,否则取语义化版本最高的一个;插件自己的可写数据目录是 $CODEX_HOME/plugins/data/插件-市场。缓存是一份拷贝,改了插件源码必须重新安装才生效。
加载:每个会话把插件拆回去
PluginsManager::plugins_for_config(codex-rs/core-plugins/src/manager.rs:792)从生效配置里读出 [plugins] 表,经 marketplace 策略过滤,再并上远程安装的插件,逐个 load_plugin。结果按配置与登录身份缓存,最多留 8 份;加载期间登录状态变了,就放弃这次结果,同一账号的令牌刷新则重来一遍。
拆出来的四类组件分别交给已有的子系统,插件本身不另开一套执行路径:
- 技能:插件的技能根目录进入技能扫描,名字加上
插件名:前缀。 - MCP 服务器:清单决定怎么启动,用户配置只能在
[plugins."插件@市场".mcp_servers.服务器]下覆盖开关、认证与工具策略——PluginMcpServerConfig的注释写明它“有意不含传输设置”(codex-rs/config/src/types.rs:1015)。 - Hooks:作为
PluginHookSource交给 hooks 引擎,命令能用PLUGIN_ROOT、PLUGIN_DATA,但照样要逐条信任。 - 应用连接器:
.app.json声明的连接器 ID,并入会话的应用列表。
用户在输入里用 @ 链接到某个插件(结构化提及的路径形如 plugin://插件@市场)时,build_plugin_injections 会生成一段开发者提示,列出该插件在本会话可用的 MCP 服务器、应用和技能前缀,上限 4 KiB(codex-rs/core/src/plugins/render.rs:8)。
和《从 LLM 到 Coding Agent》对照
那本书没有插件这一层,它的 MCP 一章讲的是用标准协议把外部工具接进来。插件是再往上的一层:不新增能力,只负责把技能、hooks、MCP 配置打包、分发、按版本缓存。与 Grok Build 相比,Grok Build 按作用域判定整个插件是否受信、未受信的插件只露出元数据;Codex 把“安装”与“运行代码”分开——装进缓存不等于信任,插件带来的每条 hook 仍要单独审阅,MCP 服务器的开关与工具审批也留在用户配置里。
上一篇:Hooks · 在关键节点插入你的脚本 · 下一篇:MCP 客户端 · 传输与 OAuth 登录