Skip to content

D2|Plugins:一个包装盒,不是第四套运行时 ​

先别在 core 里给插件再写一条 Turn 循环 ​

读完 D1,下一件错事是把 Plugin 理解成「更重的 Skill」,或者理解成又一个会 submit(Op) 的 agent。然后去 core/src/session/turn.rs 里找 PluginTask。

判断先说清楚:Plugin 是安装单元。 一份 .codex-plugin/plugin.json 把已经存在的四样东西打成包:Skills、MCP 服务器、Hooks、Apps。启用状态写在 用户 config,不写在仓库里当「克隆即安装」。包打开之后,各部件仍走 D1 / D3 / D4 自己的管道——没有第五条 run_turn。


1. 清单在 .codex-plugin/plugin.json ​

可发现的清单路径(exec-server-protocol/src/protocol.rs:47-51):

text
.codex-plugin/plugin.json
.claude-plugin/plugin.json
.cursor-plugin/plugin.json

find_plugin_manifest_path 先认根上符合 Agent Plugins schema 的 plugin.json,再按上面列表找(utils/plugins/src/plugin_namespace.rs:11-16, 42-72)。schema URI:https://agent-plugins.org/schemas/1.0.0/plugin.schema.json。脚手架默认写 .codex-plugin/plugin.json(plugin-creator/references/plugin-json-spec.md:119)。

解析后的清单很瘦(plugin/src/manifest.rs:8-24):name / version / description / keywords,加上 paths:

字段默认落点(清单可改相对路径)打开后交给谁
skills./skills/D1 的 skill 根,scope 挂在 User(插件身份)
mcpServers./.mcp.json 或内嵌对象D3 MCP 连接
hooks./hooks/hooks.jsonD4
apps./.app.jsonChatGPT Apps / connectors(A1:API Key 会清空)

默认文件名在 loader 里(core-plugins/src/loader.rs:68-71)。清单里的路径必须 ./ 开头;自定义路径是补充,不替换默认发现(plugin-json-spec :114-117)。所以空清单的插件仍可能扫到 skills/。

interface 只给 UI:显示名、默认 prompt(最多 3 条、各 128 字)、logo、截图(manifest.rs:42-57,MAX_DEFAULT_PROMPT_* 在 core-plugins/src/manifest.rs:13-14)。它不改沙箱,不改 TurnContext。

没有「一份清单四条路径」会怎样?每个 MCP、每份 Skill 各自安装,版本和启用状态对不上;关掉插件却有一个 .mcp.json 还在用户全局 MCP 表里跑。包装盒的价值是 一起装、一起关。


2. 安装状态在用户 config;MCP 可以单独关 ​

加载入口是 load_plugins_from_layer_stack(loader.rs:131-175):从 配置层 读已配置插件,再和远程已装列表合并。键是 PluginId:PLUGIN@MARKETPLACE(CLI help,plugin_cmd.rs:60-77)。

用户 toml 里是 PluginConfig(config/src/types.rs:916-922):

toml
[plugins."linear@openai-curated"]
enabled = true

enabled 默认 true。关插件走 plugins.<id>.enabled(core-plugins/src/toggles.rs:4-19)。这是 用户层,不是项目 .codex/config.toml 里该写的东西——B5 的项目 denylist 已经挡住 profile,插件安装同样不该随仓库克隆生效。未信任项目更不该靠一份 git 里的 marketplace 把自己装进你的 CODEX_HOME。

插件带来的 MCP 可以在包装盒开着时单独关。PluginMcpServerConfig.enabled(types.rs:925-934)注释写明:清单拥有 怎么启动(stdio 命令、URL),host config 拥有 开不开、鉴权、工具白名单。enabled_tools / disabled_tools / 每工具审批叠在这层。远程装上的插件会把用户已经配过的 mcp_servers overlay 拷过来,避免同步冲掉本机开关(loader.rs:317-326)。

加载插件 MCP 时把这份 policy 传进去(:940-947)。enabled = false 的服务器 跳过 initialize——不是连上再藏工具。这和 B2 的 Deferred 不同:Deferred 是模型暂时看不见;这里是进程根本不拉起。stdio 启动即执行(D3),所以「关」必须发生在 spawn 之前。

没有「用户 config + 每服务器开关」会怎样?要么装插件等于无条件拉起全部 MCP(供应链面,D3);要么关一个 MCP 只能把整个插件卸掉,Skills/Hooks 一起没。包装盒和盒内旋钮必须分开。


3. 市场是目录,CLI 是安装器,TUI /plugins 是浏览器 ​

插件不从 cwd 随便捡。来源是 marketplace:

  • 本机/仓库:marketplace.json,搜索路径包括 .agents/plugins/marketplace.json 以及 Claude/Cursor 兼容名(marketplace.rs:20-25)
  • 个人默认:~/.agents/plugins/marketplace.json(plugin-json-spec :123-127)
  • OpenAI 策展源、远程全局市场(REMOTE_GLOBAL_MARKETPLACE_NAME);远程目录要 ChatGPT 后端(A1 的 ensure_chatgpt_auth)

条目带 policy.installation:NOT_AVAILABLE / AVAILABLE / INSTALLED_BY_DEFAULT,以及 authentication:ON_INSTALL / ON_USE(plugin-json-spec :176-181)。企业可用 requirements 限制允许的 git/本地源(marketplace_policy.rs:35-50)。

CLI(cli/src/plugin_cmd.rs:49-77):

子命令做什么
codex plugin add PLUGIN@MARKETPLACE安装到本地 cache
codex plugin list列出已配置 + 远程可装
codex plugin remove卸载并删 cache
codex plugin marketplace …增删升级市场本身

TUI /plugins 是「browse plugins」(slash_command.rs:146),同一套 manager,不是第三份安装状态。

捆绑/策展市场有名字(OPENAI_BUNDLED_MARKETPLACE_NAME 等)。启动时会 sync 策展 git;非策展 cache 按版本刷新(loader 里 NonCuratedCacheRefreshMode)。安装物落在 CODEX_HOME 一侧的 plugin store,不进你的 git 工作区——和 C2 Memories 一样是身份根上的资产。

没有「市场 + 安装器」会怎样?插件变成 git clone 进项目,未信任闸形同虚设;或者每个客户端自己发明一份 enabled 列表,TUI 开了 CLI 看不见。


4. 打开包装盒之后,部件仍走旧管道 ​

加载一个 enabled 插件时(loader.rs:920-947 附近):

  1. Skills → PluginSkillRoot,进 D1 的 merge;仍可被 skills.config 禁用单份技能
  2. MCP → 解析 .mcp.json / 内嵌对象,再套 plugin.mcp_servers policy,交给连接管理(D3)
  3. Hooks → PluginHookSource;未信任项目不加载 项目 hook(D4),插件 hook 来自已安装包装盒,信任模型是「你点过 Add」
  4. Apps → legacy 清单才 load_plugin_apps(:948-949);Apps 路由还要 uses_codex_backend()(A1)

命令类旧插件会迁到 .codex-plugin/migrated-command-skills/(utils/plugins/src/lib.rs:45-52),变成 Skill 而不是新的 Op。

模型看见的是技能目录和 MCP 工具名,不是「Plugin 对象」。$plugin mention 是定位包装盒里的技能/连接器(D1 的路径匹配),不是调用 plugin.run。


5. Plugin ≠ Extension:一个是包装盒,一个是进程内贡献点 ​

plugin.json 打的是用户能开关的四件套。ext/extension-api 打的是 Memories / Skills / Goal / imagegen / git-attribution 这类 已经编译进 host 的 contributor。装配发生在 app-server/src/extensions.rs 的 thread_extensions,不是 codex plugin add。

两条缝不要焊:

Plugin(本章)Extension(B2 第 6 节)
谁装用户 / 市场host 启动时 install()
状态写哪用户 config.toml 的 plugins.<id>代码里的 registry
关掉等于不加载包装盒、MCP 可不 spawn编译期就不挂这个 contributor
典型Linear、策展 MCP 包imagegen、Goal、Memories

没有这层区分会怎样?「装插件」被理解成改 run_turn;imagegen 被理解成又一个 marketplace 包,去 .codex-plugin/ 里找生成器。包装盒是分发,contributor 是缝。


6. 和 Skill / MCP / 项目 .codex 怎么选 ​

你要分发用
一份说明书 + 脚本单独 Skill(D1),放 user 或 repo .codex/skills
说明书 + MCP + 可选 hook,可开关、可上市场Plugin
只连一个 MCP用户 config 的 mcp_servers(D3),不必做包装盒
仓库政策、测试命令AGENTS.md,不是插件

插件不替代 AGENTS.md,也不该把 ExecPolicy 写进 plugin.json。interface.defaultPrompt 是 UX 起手句,不是项目说明书。


7. 结语:带走一句话 ​

Plugin 是用户 config 里的安装单元:一份 plugin.json 把 Skills / MCP / Hooks / Apps 打成可上架的包——盒可以整关,盒里的 MCP 还可以单独不 spawn;它不是进程内 Extension 总线,打开之后各部件仍走原来的管道。