# 插件与 marketplace · 打包分发扩展

> 插件把 skills、MCP 服务器、应用连接器和 hooks 装进一个目录，靠 marketplace 分发。本篇拆开 codex-core-plugins：清单的四个位置与两种格式、marketplace 的来源与策略、安装时复制进版本化缓存的流程，以及每个会话如何把启用的插件拆回各个扩展子系统。

- 作者：David（道雾轩）
- 专栏：Codex 源码解读（https://daiw.net/manual/codex-source.md）
- 最后更新：2026-09-29
- 原文：https://daiw.net/manual/codex-source/plugins
- 转载与引用：请注明出处并附原文链接（https://daiw.net/about/copyright）

# 插件与 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`。字段与操作见手册[插件](https://daiw.net/manual/codex/plugins)。

## 全景：安装一次，每个会话拆开用

```mermaid
flowchart TB
  M[marketplace.json<br/>本地 · Git · 远程目录] --> R[解析插件条目<br/>检查 requirements 的来源白名单]
  R --> S[取得源码<br/>本地路径 · git clone · npm]
  S --> C[PluginStore 复制进缓存<br/>plugins/cache/市场/插件/版本]
  C --> E[用户配置写入<br/>plugins 表 enabled = true]
  E --> P[会话启动时<br/>PluginsManager.plugins_for_config]
  P --> L[load_plugin 读清单]
  L --> K[技能根目录 → skills 扩展]
  L --> Q[MCP 服务器 → 连接管理]
  L --> H[钩子来源 → hooks 引擎]
  L --> A[应用连接器]
```

两条入口共用同一个 `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 格式；否则依次找：

```rust
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` 里分得很清楚：

```rust
    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/名字`，本地来源原地引用。每个插件条目的来源是三选一：

```rust
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 后端的导出包，而且导出包只用于首次引导：

```rust
                        // 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 份；加载期间登录状态变了，就放弃这次结果，同一账号的令牌刷新则重来一遍。

拆出来的四类组件分别交给已有的子系统，插件本身不另开一套执行路径：

- **技能**：插件的技能根目录进入[技能扫描](https://daiw.net/manual/codex-source/skills)，名字加上 `插件名:` 前缀。
- **MCP 服务器**：清单决定怎么启动，用户配置只能在 `[plugins."插件@市场".mcp_servers.服务器]` 下覆盖开关、认证与工具策略——`PluginMcpServerConfig` 的注释写明它“有意不含传输设置”（`codex-rs/config/src/types.rs:1015`）。
- **Hooks**：作为 `PluginHookSource` 交给 [hooks 引擎](https://daiw.net/manual/codex-source/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](https://daiw.net/manual/llm-to-agent/mcp) 一章讲的是用标准协议把外部工具接进来。插件是再往上的一层：不新增能力，只负责把技能、hooks、MCP 配置打包、分发、按版本缓存。与 [Grok Build](https://daiw.net/manual/grok-build/plugins-manifest) 相比，Grok Build 按作用域判定整个插件是否受信、未受信的插件只露出元数据；Codex 把“安装”与“运行代码”分开——装进缓存不等于信任，插件带来的每条 hook 仍要单独审阅，MCP 服务器的开关与工具审批也留在用户配置里。

---

上一篇：[Hooks · 在关键节点插入你的脚本](https://daiw.net/manual/codex-source/hooks) · 下一篇：[MCP 客户端 · 传输与 OAuth 登录](https://daiw.net/manual/codex-source/mcp-client-oauth)
