Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions codex-rs/app-server-protocol/src/protocol/v2/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,8 @@ pub struct PluginSummary {
pub enabled: bool,
pub install_policy: PluginInstallPolicy,
pub install_policy_source: Option<PluginInstallPolicySource>,
#[serde(default)]
pub must_show_installation_interstitial: Option<bool>,
pub auth_policy: PluginAuthPolicy,
/// Availability state for installing and using the plugin.
#[serde(default)]
Expand Down
3 changes: 3 additions & 0 deletions codex-rs/app-server-protocol/src/protocol/v2/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3680,6 +3680,7 @@ fn plugin_share_list_response_serializes_share_items() {
enabled: false,
install_policy: PluginInstallPolicy::Available,
install_policy_source: Some(PluginInstallPolicySource::WorkspaceSetting),
must_show_installation_interstitial: None,
auth_policy: PluginAuthPolicy::OnUse,
availability: PluginAvailability::Available,
interface: None,
Expand All @@ -3703,6 +3704,7 @@ fn plugin_share_list_response_serializes_share_items() {
"enabled": false,
"installPolicy": "AVAILABLE",
"installPolicySource": "WORKSPACE_SETTING",
"mustShowInstallationInterstitial": null,
"authPolicy": "ON_USE",
"availability": "AVAILABLE",
"interface": null,
Expand Down Expand Up @@ -3731,6 +3733,7 @@ fn plugin_summary_defaults_missing_availability_to_available() {
assert_eq!(summary.availability, PluginAvailability::Available);
assert_eq!(summary.local_version, None);
assert_eq!(summary.share_context, None);
assert_eq!(summary.must_show_installation_interstitial, None);
}

#[test]
Expand Down
2 changes: 1 addition & 1 deletion codex-rs/app-server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ Example with notification opt-out:
- `marketplace/add` — add a remote plugin marketplace from an HTTP(S) Git URL, SSH Git URL, or GitHub `owner/repo` shorthand, then persist it into the user marketplace config. Returns the installed root path plus whether the marketplace was already present.
- `marketplace/remove` — remove a configured marketplace by name from the user marketplace config, and delete its installed marketplace root when one exists.
- `marketplace/upgrade` — upgrade all configured Git plugin marketplaces, or one named marketplace when `marketplaceName` is provided. Returns selected marketplace names, upgraded roots, and per-marketplace errors.
- `plugin/list` — list discovered plugin marketplaces and plugin state, including effective marketplace install/auth policy metadata, nullable remote install-policy provenance in `installPolicySource` (`WORKSPACE_SETTING` or `IMPLICIT_CANONICAL_APP`), the remote marketplace `version` and locally materialized `localVersion` when available, plugin `availability` (`AVAILABLE` by default or `DISABLED_BY_ADMIN` for remote plugins blocked upstream), fail-open `marketplaceLoadErrors` entries for marketplace files that could not be parsed or loaded, and best-effort `featuredPluginIds` for the official curated marketplace. Clients can explicitly request the remote `workspace-directory`, `shared-with-me`, or `created-by-me-remote` marketplace kinds. `interface.category` uses the marketplace category when present; otherwise it falls back to the plugin manifest category (**under development; do not call from production clients yet**).
- `plugin/list` — list discovered plugin marketplaces and plugin state, including effective marketplace install/auth policy metadata, nullable remote install-policy provenance in `installPolicySource` (`WORKSPACE_SETTING` or `IMPLICIT_CANONICAL_APP`), the remote marketplace `version` and locally materialized `localVersion` when available, plugin `availability` (`AVAILABLE` by default or `DISABLED_BY_ADMIN` for remote plugins blocked upstream), fail-open `marketplaceLoadErrors` entries for marketplace files that could not be parsed or loaded, and best-effort `featuredPluginIds` for the official curated marketplace. Every `PluginSummary` returned by plugin list, installed, read, and share-list methods includes `mustShowInstallationInterstitial`: remote service values preserve `true` or `false`, while local plugins and remote responses that omit the policy return `null`. Clients should fail closed when the value is `null`. Clients can explicitly request the remote `workspace-directory`, `shared-with-me`, or `created-by-me-remote` marketplace kinds. `interface.category` uses the marketplace category when present; otherwise it falls back to the plugin manifest category (**under development; do not call from production clients yet**).
- `plugin/installed` — list installed plugin rows plus any explicitly requested local install-suggestion plugin names, without fetching the broader remote catalog. Remote rows include nullable `installPolicySource`; local rows return `null`. Mention surfaces can use this narrower view when they need plugin mention payloads rather than plugin-page discovery data (**under development; do not call from production clients yet**).
- `plugin/read` — read one plugin by `marketplacePath` plus `pluginName`, returning marketplace info, a list-style `summary`, manifest descriptions/interface metadata, and bundled skills/hooks/apps/MCP server names. Remote plugin details can include scheduled task summaries from the catalog; `scheduledTasks: null` means the metadata is unavailable, while an empty array means the catalog found no scheduled tasks. Remote plugin details expose the canonical `shareUrl` supplied by the remote catalog when available; it is `null` for local plugins or when the catalog omits it. This field is separate from `summary.shareContext`, which continues to describe user and workspace sharing state. Returned plugin skills include their current `enabled` state after local config filtering; bundled hooks are returned as lightweight declaration summaries keyed for correlation with `hooks/list`. Use `plugin/install`'s `appsNeedingAuth` to drive post-install authentication and `app/list`'s `isAccessible` to determine current connector accessibility (**under development; do not call from production clients yet**).
- `plugin/skill/read` — read remote plugin skill markdown on demand by `remoteMarketplaceName`, `remotePluginId`, and `skillName`. This lets clients preview uninstalled remote plugin skills without downloading the plugin bundle.
Expand Down
3 changes: 3 additions & 0 deletions codex-rs/app-server/src/request_processors/plugins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ fn convert_configured_marketplace_plugin_to_plugin_summary(
source: marketplace_plugin_source_to_info(plugin.source),
install_policy: plugin.policy.installation.into(),
install_policy_source: None,
must_show_installation_interstitial: None,
auth_policy: plugin.policy.authentication.into(),
availability: PluginAvailability::Available,
interface: plugin.interface.map(local_plugin_interface_to_info),
Expand Down Expand Up @@ -1091,6 +1092,7 @@ impl PluginRequestProcessor {
enabled: outcome.plugin.enabled,
install_policy: outcome.plugin.policy.installation.into(),
install_policy_source: None,
must_show_installation_interstitial: None,
auth_policy: outcome.plugin.policy.authentication.into(),
availability: PluginAvailability::Available,
interface: outcome.plugin.interface.map(local_plugin_interface_to_info),
Expand Down Expand Up @@ -2181,6 +2183,7 @@ fn remote_plugin_summary_to_info(summary: RemoteCatalogPluginSummary) -> PluginS
enabled: summary.enabled,
install_policy: summary.install_policy,
install_policy_source: summary.install_policy_source,
must_show_installation_interstitial: summary.must_show_installation_interstitial,
auth_policy: summary.auth_policy,
availability: summary.availability,
interface: summary.interface,
Expand Down
29 changes: 28 additions & 1 deletion codex-rs/app-server/tests/suite/v2/plugin_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,8 @@ enabled = true
let mut global_installed_body: serde_json::Value = serde_json::from_str(
&remote_installed_plugin_body("", "1.2.3", /*enabled*/ true),
)?;
global_installed_body["plugins"][0]["must_show_installation_interstitial"] =
serde_json::json!(false);
let mut remote_only = global_installed_body["plugins"][0].clone();
remote_only["id"] = serde_json::json!("plugins~Plugin_11111111111111111111111111111111");
remote_only["name"] = serde_json::json!("remote-only");
Expand Down Expand Up @@ -310,16 +312,24 @@ enabled = true
remote_marketplace
.plugins
.iter()
.map(|plugin| (plugin.id.clone(), plugin.install_policy_source))
.map(|plugin| {
(
plugin.id.clone(),
plugin.install_policy_source,
plugin.must_show_installation_interstitial,
)
})
.collect::<Vec<_>>(),
vec![
(
"linear@openai-curated-remote".to_string(),
Some(PluginInstallPolicySource::WorkspaceSetting),
Some(false),
),
(
"remote-only@openai-curated-remote".to_string(),
Some(PluginInstallPolicySource::WorkspaceSetting),
Some(false),
),
]
);
Expand Down Expand Up @@ -499,6 +509,7 @@ async fn plugin_list_keeps_valid_marketplaces_when_another_marketplace_fails_to_
enabled: false,
install_policy: PluginInstallPolicy::Available,
install_policy_source: None,
must_show_installation_interstitial: None,
auth_policy: PluginAuthPolicy::OnInstall,
availability: codex_app_server_protocol::PluginAvailability::Available,
interface: None,
Expand Down Expand Up @@ -797,6 +808,7 @@ async fn plugin_list_uses_alternate_discoverable_manifest_and_keeps_undiscoverab
enabled: false,
install_policy: PluginInstallPolicy::Available,
install_policy_source: None,
must_show_installation_interstitial: None,
auth_policy: PluginAuthPolicy::OnInstall,
availability: codex_app_server_protocol::PluginAvailability::Available,
interface: Some(codex_app_server_protocol::PluginInterface {
Expand Down Expand Up @@ -838,6 +850,7 @@ async fn plugin_list_uses_alternate_discoverable_manifest_and_keeps_undiscoverab
enabled: false,
install_policy: PluginInstallPolicy::Available,
install_policy_source: None,
must_show_installation_interstitial: None,
auth_policy: PluginAuthPolicy::OnInstall,
availability: codex_app_server_protocol::PluginAvailability::Available,
interface: None,
Expand Down Expand Up @@ -1753,6 +1766,7 @@ async fn plugin_list_includes_remote_marketplaces_when_remote_plugin_enabled() -
"scope": "GLOBAL",
"installation_policy": "AVAILABLE",
"installation_policy_source": "IMPLICIT_CANONICAL_APP",
"must_show_installation_interstitial": true,
"authentication_policy": "ON_USE",
"status": "ENABLED",
"release": {
Expand Down Expand Up @@ -1793,6 +1807,7 @@ async fn plugin_list_includes_remote_marketplaces_when_remote_plugin_enabled() -
"scope": "GLOBAL",
"installation_policy": "AVAILABLE",
"installation_policy_source": "WORKSPACE_SETTING",
"must_show_installation_interstitial": false,
"authentication_policy": "ON_USE",
"status": "ENABLED",
"release": {
Expand Down Expand Up @@ -1924,6 +1939,10 @@ async fn plugin_list_includes_remote_marketplaces_when_remote_plugin_enabled() -
remote_marketplace.plugins[0].install_policy_source,
Some(PluginInstallPolicySource::ImplicitCanonicalApp)
);
assert_eq!(
remote_marketplace.plugins[0].must_show_installation_interstitial,
Some(true)
);
assert_eq!(
remote_marketplace.plugins[0].availability,
codex_app_server_protocol::PluginAvailability::Available
Expand Down Expand Up @@ -1963,6 +1982,10 @@ async fn plugin_list_includes_remote_marketplaces_when_remote_plugin_enabled() -
cached_catalog["plugins"][0]["installation_policy_source"],
serde_json::json!("IMPLICIT_CANONICAL_APP")
);
assert_eq!(
cached_catalog["plugins"][0]["must_show_installation_interstitial"],
serde_json::json!(true)
);
assert_eq!(
cached_catalog["plugins"][0]["release"]["interface"]["default_prompts"],
serde_json::json!(["Create a Linear issue", "Review my Linear projects"])
Expand Down Expand Up @@ -2057,6 +2080,10 @@ async fn plugin_list_uses_cached_global_remote_catalog_and_refreshes_it() -> Res
remote_marketplace.plugins[0].id,
"linear@openai-curated-remote"
);
assert_eq!(
remote_marketplace.plugins[0].must_show_installation_interstitial,
None
);
wait_for_remote_plugin_request_count(&server, "/ps/plugins/list", /*expected_count*/ 1).await?;
wait_for_cached_remote_catalog_plugin_ids(codex_home.path(), &[cached_remote_plugin_id])
.await?;
Expand Down
5 changes: 5 additions & 0 deletions codex-rs/app-server/tests/suite/v2/plugin_read.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ apps = true
"scope": "GLOBAL",
"installation_policy": "AVAILABLE",
"installation_policy_source": "IMPLICIT_CANONICAL_APP",
"must_show_installation_interstitial": true,
"authentication_policy": "ON_USE",
"release": {
"version": "1.2.1",
Expand Down Expand Up @@ -309,6 +310,10 @@ apps = true
response.plugin.summary.install_policy_source,
Some(PluginInstallPolicySource::ImplicitCanonicalApp)
);
assert_eq!(
response.plugin.summary.must_show_installation_interstitial,
Some(true)
);
assert_eq!(
response
.plugin
Expand Down
4 changes: 4 additions & 0 deletions codex-rs/app-server/tests/suite/v2/plugin_share.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ async fn plugin_share_save_uploads_local_plugin() -> Result<()> {
enabled: true,
install_policy: PluginInstallPolicy::Available,
install_policy_source: None,
must_show_installation_interstitial: Some(false),
auth_policy: PluginAuthPolicy::OnUse,
availability: codex_app_server_protocol::PluginAvailability::Available,
interface: Some(expected_plugin_interface()),
Expand Down Expand Up @@ -613,6 +614,7 @@ async fn plugin_share_list_returns_created_workspace_plugins() -> Result<()> {
enabled: true,
install_policy: PluginInstallPolicy::Available,
install_policy_source: None,
must_show_installation_interstitial: Some(false),
auth_policy: PluginAuthPolicy::OnUse,
availability: codex_app_server_protocol::PluginAvailability::Available,
interface: Some(expected_plugin_interface()),
Expand Down Expand Up @@ -1230,6 +1232,7 @@ async fn plugin_share_delete_removes_created_workspace_plugin() -> Result<()> {
enabled: true,
install_policy: PluginInstallPolicy::Available,
install_policy_source: None,
must_show_installation_interstitial: Some(false),
auth_policy: PluginAuthPolicy::OnUse,
availability: codex_app_server_protocol::PluginAvailability::Available,
interface: Some(expected_plugin_interface()),
Expand Down Expand Up @@ -1357,6 +1360,7 @@ fn remote_plugin_json(plugin_id: &str) -> serde_json::Value {
}
],
"installation_policy": "AVAILABLE",
"must_show_installation_interstitial": false,
"authentication_policy": "ON_USE",
"release": {
"version": "0.1.0",
Expand Down
1 change: 1 addition & 0 deletions codex-rs/core-plugins/src/manager_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -782,6 +782,7 @@ fn remote_installed_plugin_in_marketplace(
enabled: true,
install_policy: codex_app_server_protocol::PluginInstallPolicy::Available,
install_policy_source: None,
must_show_installation_interstitial: None,
auth_policy: codex_app_server_protocol::PluginAuthPolicy::OnUse,
availability: codex_app_server_protocol::PluginAvailability::Available,
interface: None,
Expand Down
Loading
Loading