Skip to content

Commit 6509f31

Browse files
[codex] Support npm marketplace plugin sources (#29375)
## Why Marketplace source deserialization treated `{"source":"npm", ...}` as unsupported. The loader logged and skipped the entry, so npm-backed plugins never appeared in `plugin list --available` and `plugin add` returned "plugin not found". Codex plugins are installed from a plugin root, not from an npm dependency tree. For npm-backed marketplace entries, Codex should fetch the published package contents without running package scripts or installing unrelated dependencies. ## What changed - Add `npm` marketplace plugin sources with `package`, optional semver `version` or version range, and optional HTTPS `registry`. - Reject unsafe npm source fields before materialization, including invalid package names, non-semver version selectors, plaintext or credential-bearing registry URLs, and registry query/fragment data. - Materialize npm plugins with `npm pack --ignore-scripts`, then unpack the resulting tarball through the existing hardened plugin bundle extractor. - Enforce npm archive and extracted-size limits, require the standard npm `package/` archive root, and verify the extracted `package.json` name matches the requested package before installing. - Keep plugin listings, install-source descriptions, CLI JSON/human output, app-server v2 `PluginSource`, TUI source summaries, regenerated schema fixtures, and app-server documentation in sync. ## Impact Marketplaces can distribute Codex plugins from public or configured private HTTPS npm registries using the same install flow as existing materialized plugin sources. `npm` must be available on `PATH` when an npm-backed plugin is installed. Fixes #27831 ## Validation - `just write-app-server-schema` - `just test -p codex-core-plugins -p codex-app-server-protocol -p codex-app-server -p codex-cli` - npm/schema/core-plugin coverage passed in the run. - The full focused command finished with `1739 passed`, `11 failed`, and `6 timed out`; the failures were unrelated local app-server environment failures from `sandbox-exec: sandbox_apply: Operation not permitted` plus one missing `test_stdio_server` helper binary. - Installed an npm-published Codex plugin package through a throwaway local marketplace and throwaway `CODEX_HOME` to exercise the real npm materialization path end to end.
1 parent 526f495 commit 6509f31

21 files changed

Lines changed: 1102 additions & 33 deletions

codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json

Lines changed: 34 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json

Lines changed: 34 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

codex-rs/app-server-protocol/schema/json/v2/PluginInstalledResponse.json

Lines changed: 34 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

codex-rs/app-server-protocol/schema/json/v2/PluginListResponse.json

Lines changed: 34 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

codex-rs/app-server-protocol/schema/json/v2/PluginReadResponse.json

Lines changed: 34 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

codex-rs/app-server-protocol/schema/json/v2/PluginShareListResponse.json

Lines changed: 34 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

codex-rs/app-server-protocol/schema/typescript/v2/PluginSource.ts

Lines changed: 9 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

codex-rs/app-server-protocol/src/protocol/v2/plugin.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -744,6 +744,15 @@ pub enum PluginSource {
744744
ref_name: Option<String>,
745745
sha: Option<String>,
746746
},
747+
#[serde(rename_all = "camelCase")]
748+
#[ts(rename_all = "camelCase")]
749+
Npm {
750+
package: String,
751+
/// Optional npm version or version range.
752+
version: Option<String>,
753+
/// Optional HTTPS registry URL. Authentication stays in the user's npm config.
754+
registry: Option<String>,
755+
},
747756
/// The plugin is available in the remote catalog. Download metadata is
748757
/// kept server-side and is not exposed through the app-server API.
749758
Remote,

codex-rs/app-server-protocol/src/protocol/v2/tests.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2927,7 +2927,7 @@ fn skills_extra_roots_set_params_rejects_relative_roots() {
29272927
}
29282928

29292929
#[test]
2930-
fn plugin_source_serializes_local_git_and_remote_variants() {
2930+
fn plugin_source_serializes_local_git_npm_and_remote_variants() {
29312931
let local_path = if cfg!(windows) {
29322932
r"C:\plugins\linear"
29332933
} else {
@@ -2961,6 +2961,21 @@ fn plugin_source_serializes_local_git_and_remote_variants() {
29612961
}),
29622962
);
29632963

2964+
assert_eq!(
2965+
serde_json::to_value(PluginSource::Npm {
2966+
package: "@acme/plugin".to_string(),
2967+
version: Some("^1.2.0".to_string()),
2968+
registry: Some("https://npm.example.com".to_string()),
2969+
})
2970+
.unwrap(),
2971+
json!({
2972+
"type": "npm",
2973+
"package": "@acme/plugin",
2974+
"version": "^1.2.0",
2975+
"registry": "https://npm.example.com",
2976+
}),
2977+
);
2978+
29642979
assert_eq!(
29652980
serde_json::to_value(PluginSource::Remote).unwrap(),
29662981
json!({

codex-rs/app-server/src/request_processors/plugins.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,15 @@ fn marketplace_plugin_source_to_info(source: MarketplacePluginSource) -> PluginS
101101
ref_name,
102102
sha,
103103
},
104+
MarketplacePluginSource::Npm {
105+
package,
106+
version,
107+
registry,
108+
} => PluginSource::Npm {
109+
package,
110+
version,
111+
registry,
112+
},
104113
}
105114
}
106115

@@ -134,7 +143,7 @@ fn share_context_for_source(
134143
creator_name: None,
135144
share_principals: None,
136145
}),
137-
MarketplacePluginSource::Git { .. } => None,
146+
MarketplacePluginSource::Git { .. } | MarketplacePluginSource::Npm { .. } => None,
138147
}
139148
}
140149

0 commit comments

Comments
 (0)