[codex] Enable remote plugins by default (#30297)

## Summary

- enable the remote plugin feature by default
- promote the remote plugin feature from under development to stable
- preserve the existing `features.remote_plugin` override for explicitly
disabling it
- keep legacy disabled-path coverage explicit in TUI and app-server
tests

## Impact

Remote plugin functionality is enabled by default for configurations
that do not set the feature flag. The existing Codex backend
authentication gate still applies.

## Validation

- `just fmt`
- `just test -p codex-features`
- `just test -p codex-tui
plugins_popup_remote_section_fallback_states_snapshot`
- targeted `codex-app-server` plugin-list and skills-list tests
- `git diff --check`

The full TUI and app-server suites were also exercised locally. All
remote-plugin-related coverage passed; unrelated local
sandbox/test-binary failures remain outside this change.
This commit is contained in:
xl-openai
2026-06-28 11:46:25 -07:00
committed by GitHub
parent bdd282f3bb
commit e428a12d22
15 changed files with 62 additions and 69 deletions
@@ -2027,7 +2027,6 @@ chatgpt_base_url = "{base_url}"
[features]
plugins = true
remote_plugin = true
"#
),
)
@@ -2057,7 +2056,6 @@ chatgpt_base_url = "{}/backend-api/"
[features]
plugins = true
remote_plugin = true
connectors = true
"#,
server.uri()
@@ -71,6 +71,23 @@ plugins = true
)
}
fn write_remote_plugins_disabled_config_with_base_url(
codex_home: &std::path::Path,
base_url: &str,
) -> std::io::Result<()> {
std::fs::write(
codex_home.join("config.toml"),
format!(
r#"chatgpt_base_url = "{base_url}"
[features]
plugins = true
remote_plugin = false
"#,
),
)
}
#[tokio::test]
async fn plugin_list_skips_invalid_marketplace_file_and_reports_error() -> Result<()> {
let codex_home = TempDir::new()?;
@@ -197,7 +214,6 @@ async fn plugin_installed_prefers_remote_curated_conflicts_when_remote_plugin_en
[features]
plugins = true
remote_plugin = true
plugin_sharing = false
[plugins."linear@openai-curated"]
@@ -1987,10 +2003,11 @@ async fn plugin_list_uses_cached_global_remote_catalog_and_refreshes_it() -> Res
}
#[tokio::test]
async fn plugin_list_includes_openai_curated_remote_collection_when_requested() -> Result<()> {
async fn plugin_list_includes_openai_curated_remote_collection_when_remote_plugin_disabled_and_requested()
-> Result<()> {
let codex_home = TempDir::new()?;
let server = MockServer::start().await;
write_plugins_enabled_config_with_base_url(
write_remote_plugins_disabled_config_with_base_url(
codex_home.path(),
&format!("{}/backend-api/", server.uri()),
)?;
@@ -2092,10 +2109,11 @@ async fn plugin_list_includes_openai_curated_remote_collection_when_requested()
}
#[tokio::test]
async fn plugin_list_propagates_explicit_openai_curated_remote_collection_errors() -> Result<()> {
async fn plugin_list_propagates_openai_curated_remote_collection_errors_when_remote_plugin_disabled()
-> Result<()> {
let codex_home = TempDir::new()?;
let server = MockServer::start().await;
write_plugins_enabled_config_with_base_url(
write_remote_plugins_disabled_config_with_base_url(
codex_home.path(),
&format!("{}/backend-api/", server.uri()),
)?;
@@ -2148,10 +2166,11 @@ async fn plugin_list_propagates_explicit_openai_curated_remote_collection_errors
}
#[tokio::test]
async fn plugin_list_skips_explicit_openai_curated_remote_collection_for_api_auth() -> Result<()> {
async fn plugin_list_skips_openai_curated_remote_collection_for_api_auth_when_remote_plugin_disabled()
-> Result<()> {
let codex_home = TempDir::new()?;
let server = MockServer::start().await;
write_plugins_enabled_config_with_base_url(
write_remote_plugins_disabled_config_with_base_url(
codex_home.path(),
&format!("{}/backend-api/", server.uri()),
)?;
@@ -2391,7 +2410,8 @@ async fn plugin_list_does_not_append_global_remote_when_marketplace_kinds_are_ex
}
#[tokio::test]
async fn plugin_installed_includes_remote_shared_with_me_plugins() -> Result<()> {
async fn plugin_installed_includes_remote_shared_with_me_plugins_when_remote_plugin_disabled()
-> Result<()> {
let codex_home = TempDir::new()?;
let server = MockServer::start().await;
std::fs::write(
@@ -2493,7 +2513,8 @@ plugin_sharing = true
}
#[tokio::test]
async fn plugin_installed_includes_workspace_directory_without_plugin_sharing() -> Result<()> {
async fn plugin_installed_includes_workspace_directory_without_plugin_sharing_when_remote_plugin_disabled()
-> Result<()> {
let codex_home = TempDir::new()?;
let server = MockServer::start().await;
std::fs::write(
@@ -2590,7 +2611,6 @@ async fn plugin_installed_includes_created_by_me_when_remote_plugins_enabled() -
[features]
plugins = true
remote_plugin = true
plugin_sharing = false
"#,
server.uri()
@@ -2681,7 +2701,6 @@ async fn plugin_installed_starts_remote_installed_bundle_sync() -> Result<()> {
[features]
plugins = true
remote_plugin = true
plugin_sharing = false
"#,
server.uri()
@@ -2750,10 +2769,10 @@ plugin_sharing = false
}
#[tokio::test]
async fn plugin_list_fetches_workspace_directory_kind_without_remote_plugin_flag() -> Result<()> {
async fn plugin_list_fetches_workspace_directory_kind_when_remote_plugin_disabled() -> Result<()> {
let codex_home = TempDir::new()?;
let server = MockServer::start().await;
write_plugins_enabled_config_with_base_url(
write_remote_plugins_disabled_config_with_base_url(
codex_home.path(),
&format!("{}/backend-api/", server.uri()),
)?;
@@ -2848,7 +2867,6 @@ async fn plugin_list_fetches_user_plugins_in_created_by_me_remote_marketplace()
[features]
plugins = true
remote_plugin = true
plugin_sharing = false
"#,
server.uri()
@@ -4032,7 +4050,6 @@ chatgpt_base_url = "{base_url}"
[features]
plugins = true
remote_plugin = true
"#
),
)
@@ -2132,7 +2132,6 @@ chatgpt_base_url = "{base_url}"
[features]
plugins = true
remote_plugin = true
"#
),
)
@@ -344,7 +344,6 @@ chatgpt_base_url = "{}/backend-api"
[features]
plugins = true
remote_plugin = true
plugin_sharing = false
"#,
server.uri()
@@ -1050,7 +1049,6 @@ chatgpt_base_url = "{}/backend-api"
[features]
plugins = true
remote_plugin = true
plugin_sharing = false
"#,
server.uri()
@@ -1204,7 +1202,6 @@ chatgpt_base_url = "{base_url}"
[features]
plugins = true
remote_plugin = true
"#
),
)
@@ -622,7 +622,6 @@ chatgpt_base_url = "{base_url}"
[features]
plugins = true
remote_plugin = true
"#
),
)
@@ -68,9 +68,7 @@ async fn first_turn_after_external_login_waits_for_recommended_plugins() -> Resu
let config = std::fs::read_to_string(&config_path)?;
std::fs::write(
config_path,
format!(
"{config}\n[features]\napps = true\nplugins = true\nremote_plugin = true\ntool_suggest = true\n"
),
format!("{config}\n[features]\napps = true\nplugins = true\ntool_suggest = true\n"),
)?;
let sqlite_home = codex_home.path().to_string_lossy();
@@ -79,23 +79,6 @@ plugins = true
)
}
fn write_remote_plugins_enabled_config_with_base_url(
codex_home: &std::path::Path,
base_url: &str,
) -> std::io::Result<()> {
std::fs::write(
codex_home.join("config.toml"),
format!(
r#"chatgpt_base_url = "{base_url}"
[features]
plugins = true
remote_plugin = true
"#,
),
)
}
fn write_plugin_with_skill(
repo_root: &std::path::Path,
plugin_name: &str,
@@ -175,7 +158,7 @@ fn write_cached_local_curated_plugin_with_skill(codex_home: &std::path::Path) ->
}
#[tokio::test]
async fn runtime_remote_plugin_enablement_excludes_local_curated_plugin_skills() -> Result<()> {
async fn runtime_remote_plugin_toggle_updates_local_curated_plugin_skills() -> Result<()> {
let codex_home = TempDir::new()?;
let cwd = TempDir::new()?;
let server = MockServer::start().await;
@@ -206,6 +189,18 @@ enabled = true
let mut mcp = TestAppServer::new(codex_home.path()).await?;
timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??;
let disablement_request_id = mcp
.send_experimental_feature_enablement_set_request(ExperimentalFeatureEnablementSetParams {
enablement: BTreeMap::from([("remote_plugin".to_string(), false)]),
})
.await?;
let disablement_response: JSONRPCResponse = timeout(
DEFAULT_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(disablement_request_id)),
)
.await??;
let _: ExperimentalFeatureEnablementSetResponse = to_response(disablement_response)?;
let initial_skills_list_request_id = mcp
.send_skills_list_request(SkillsListParams {
cwds: vec![cwd.path().to_path_buf()],
@@ -266,7 +261,7 @@ async fn skills_list_loads_remote_installed_plugin_skills_from_cache() -> Result
let server = MockServer::start().await;
let expected_skill_path =
std::fs::canonicalize(write_cached_remote_plugin_with_skill(codex_home.path())?)?;
write_remote_plugins_enabled_config_with_base_url(
write_plugins_enabled_config_with_base_url(
codex_home.path(),
&format!("{}/backend-api/", server.uri()),
)?;