From f380b4873367ba65ac6369bac1b67959b1b5f269 Mon Sep 17 00:00:00 2001 From: felixxia-oai Date: Wed, 5 Aug 2026 21:45:19 +0000 Subject: [PATCH] Move plugin skill snapshot integration tests into core (#37169) ## What changed - Move the integration test for reusing plugin-manager skill snapshots into `codex-core`, which owns both the plugin manager and host skills service integration. - Keep the `codex-core-plugins` test focused on resolving local and remote plugin identities on effective skill roots, and remove its skills-extension dev dependency. - Use the skills extension's `HostSkillsSnapshot` type in its world-state catalog implementation. ## Testing - Verify the host skills service reuses the plugin manager's parsed snapshot after the underlying skill file changes, while preserving plugin identity metadata. GitOrigin-RevId: afc8a0ebccb629360c59a4546bdb9a134c74da4f --- codex-rs/Cargo.lock | 1 - codex-rs/core-plugins/Cargo.toml | 1 - codex-rs/core-plugins/src/manager_tests.rs | 47 +++------- codex-rs/core/src/plugins/mod.rs | 3 + .../core/src/plugins/skill_snapshot_tests.rs | 86 +++++++++++++++++++ .../ext/skills/src/world_state_catalogs.rs | 2 +- 6 files changed, 104 insertions(+), 36 deletions(-) create mode 100644 codex-rs/core/src/plugins/skill_snapshot_tests.rs diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index 76d2af28e4..3b60b78ca1 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -2874,7 +2874,6 @@ dependencies = [ "codex-protocol", "codex-shell-command", "codex-skills", - "codex-skills-extension", "codex-tools", "codex-utils-absolute-path", "codex-utils-path", diff --git a/codex-rs/core-plugins/Cargo.toml b/codex-rs/core-plugins/Cargo.toml index c4f52ab010..3c3526f061 100644 --- a/codex-rs/core-plugins/Cargo.toml +++ b/codex-rs/core-plugins/Cargo.toml @@ -60,7 +60,6 @@ which = { workspace = true } [dev-dependencies] codex-exec-server-test-support = { workspace = true } -codex-skills-extension = { workspace = true } libc = { workspace = true } pretty_assertions = { workspace = true } tempfile = { workspace = true } diff --git a/codex-rs/core-plugins/src/manager_tests.rs b/codex-rs/core-plugins/src/manager_tests.rs index 5182e0f57a..9f7d211f20 100644 --- a/codex-rs/core-plugins/src/manager_tests.rs +++ b/codex-rs/core-plugins/src/manager_tests.rs @@ -48,8 +48,6 @@ use codex_protocol::auth::AuthMode; use codex_protocol::protocol::HookEventName; use codex_protocol::protocol::Product; use codex_skills::SkillConfigRules; -use codex_skills_extension::HostSkillsLoadInput; -use codex_skills_extension::HostSkillsService; use codex_utils_absolute_path::AbsolutePathBuf; use codex_utils_absolute_path::test_support::PathBufExt; use codex_utils_plugins::SkillDiscoveryMode; @@ -2880,7 +2878,7 @@ async fn plugin_cache_ignores_unrelated_session_overrides() { } #[tokio::test] -async fn skill_snapshots_resolve_remote_plugin_identity_from_authoritative_source() { +async fn skill_roots_resolve_remote_plugin_identity_from_authoritative_source() { let mut duplicate_plugin = remote_installed_plugin("sample"); duplicate_plugin.id = "plugins~Plugin_duplicate".to_string(); @@ -2893,7 +2891,6 @@ async fn skill_snapshots_resolve_remote_plugin_identity_from_authoritative_sourc ), ] { let codex_home = TempDir::new().unwrap(); - let codex_home_abs = codex_home.path().to_path_buf().abs(); let plugin_root = codex_home .path() .join("plugins/cache/openai-curated-remote/sample/local"); @@ -2905,8 +2902,10 @@ async fn skill_snapshots_resolve_remote_plugin_identity_from_authoritative_sourc "sample/local", "sample", ); - let skill_path = plugin_root.join("skills/SKILL.md"); - write_file(&skill_path, "---\nname: search\ndescription: first\n---\n"); + write_file( + &plugin_root.join("skills/SKILL.md"), + "---\nname: search\ndescription: first\n---\n", + ); write_file( &codex_home.path().join(CONFIG_TOML_FILE), r#"[features] @@ -2941,39 +2940,21 @@ enabled = true .as_deref(), expected_remote_plugin_id ); - write_file(&skill_path, "---\nname: search\ndescription: second\n---\n"); - - let skills_input = HostSkillsLoadInput::new( - codex_home_abs.clone(), - plugin_outcome.effective_plugin_skill_roots(), - config.config_layer_stack.clone(), - /*bundled_skills_enabled*/ false, - ) - .with_plugin_skill_snapshots(manager.plugin_skill_snapshots_for_config(&config)); - let skills_service = - HostSkillsService::new(codex_home_abs, /*bundled_skills_enabled*/ false); - let snapshot = skills_service - .snapshot_for_config(&skills_input, /*fs*/ None) - .await; - assert_eq!( - snapshot - .outcome() - .skills - .iter() - .map(|skill| { + plugin_outcome + .effective_plugin_skill_roots() + .into_iter() + .map(|root| { ( - skill.description.as_str(), - skill.plugin_id.as_deref(), - skill.remote_plugin_id.as_deref(), + root.plugin_identity.plugin_id, + root.plugin_identity.remote_plugin_id, ) }) .collect::>(), vec![( - "first", - Some("sample@openai-curated-remote"), - expected_remote_plugin_id, - )] + "sample@openai-curated-remote".to_string(), + expected_remote_plugin_id.map(str::to_string), + )], ); } } diff --git a/codex-rs/core/src/plugins/mod.rs b/codex-rs/core/src/plugins/mod.rs index 11e2c338bc..db3b651480 100644 --- a/codex-rs/core/src/plugins/mod.rs +++ b/codex-rs/core/src/plugins/mod.rs @@ -3,6 +3,9 @@ mod injection; mod mentions; mod render; #[cfg(test)] +#[path = "skill_snapshot_tests.rs"] +mod skill_snapshot_tests; +#[cfg(test)] pub(crate) mod test_support; pub(crate) use codex_plugin::PluginCapabilitySummary; diff --git a/codex-rs/core/src/plugins/skill_snapshot_tests.rs b/codex-rs/core/src/plugins/skill_snapshot_tests.rs new file mode 100644 index 0000000000..2e3c8e3a0d --- /dev/null +++ b/codex-rs/core/src/plugins/skill_snapshot_tests.rs @@ -0,0 +1,86 @@ +use codex_core_plugins::PluginsManager; +use codex_core_plugins::store::PluginStore; +use codex_plugin::PluginId; +use codex_protocol::auth::AuthMode; +use codex_protocol::protocol::Product; +use codex_skills_extension::HostSkillsLoadInput; +use codex_skills_extension::HostSkillsService; +use pretty_assertions::assert_eq; + +use super::test_support::load_plugins_config; +use super::test_support::write_file; + +const PLUGIN_CONFIG_NAME: &str = "sample@openai-curated-remote"; +const REMOTE_PLUGIN_ID: &str = "plugins~Plugin_sample"; + +#[tokio::test] +async fn host_skills_service_reuses_plugin_manager_skill_snapshot() { + let codex_home = tempfile::tempdir().expect("create codex home"); + let plugin_root = codex_home + .path() + .join("plugins/cache/openai-curated-remote/sample/local"); + write_file( + &plugin_root.join(".codex-plugin/plugin.json"), + r#"{"name":"sample","description":"sample plugin"}"#, + ); + let skill_path = plugin_root.join("skills/SKILL.md"); + write_file(&skill_path, "---\nname: search\ndescription: first\n---\n"); + write_file( + &codex_home.path().join("config.toml"), + &format!( + r#"[features] +plugins = true +remote_plugin = true + +[plugins."{PLUGIN_CONFIG_NAME}"] +enabled = true +"#, + ), + ); + let plugin_id = PluginId::parse(PLUGIN_CONFIG_NAME).expect("remote plugin id should parse"); + PluginStore::new(codex_home.path().to_path_buf()) + .write_remote_plugin_id(&plugin_id, REMOTE_PLUGIN_ID) + .expect("persist remote plugin id"); + let config = load_plugins_config(codex_home.path()).await; + let plugins_input = config.plugins_config_input(); + let plugins_manager = PluginsManager::new_with_options( + codex_home.path().to_path_buf(), + Some(Product::Codex), + Some(AuthMode::Chatgpt), + ); + let plugin_outcome = plugins_manager.plugins_for_config(&plugins_input).await; + + write_file(&skill_path, "---\nname: search\ndescription: second\n---\n"); + + let skills_input = HostSkillsLoadInput::new( + config.cwd.clone(), + plugin_outcome.effective_plugin_skill_roots(), + config.config_layer_stack.clone(), + /*bundled_skills_enabled*/ false, + ) + .with_plugin_skill_snapshots(plugins_manager.plugin_skill_snapshots_for_config(&plugins_input)); + let skills_service = HostSkillsService::new( + config.codex_home.clone(), + /*bundled_skills_enabled*/ false, + ); + let snapshot = skills_service + .snapshot_for_config(&skills_input, /*fs*/ None) + .await; + + assert_eq!( + snapshot + .outcome() + .skills + .iter() + .filter(|skill| skill.plugin_id.as_deref() == Some(PLUGIN_CONFIG_NAME)) + .map(|skill| { + ( + skill.description.as_str(), + skill.plugin_id.as_deref(), + skill.remote_plugin_id.as_deref(), + ) + }) + .collect::>(), + vec![("first", Some(PLUGIN_CONFIG_NAME), Some(REMOTE_PLUGIN_ID),)] + ); +} diff --git a/codex-rs/ext/skills/src/world_state_catalogs.rs b/codex-rs/ext/skills/src/world_state_catalogs.rs index c9f5572e51..eadb7eed38 100644 --- a/codex-rs/ext/skills/src/world_state_catalogs.rs +++ b/codex-rs/ext/skills/src/world_state_catalogs.rs @@ -1,6 +1,5 @@ use std::sync::Arc; -use codex_core_skills::HostSkillsSnapshot; use codex_core_skills::injection::HostSkillsCatalogInWorldState; use codex_extension_api::ContextualUserFragment; use codex_extension_api::ExtensionEventSink; @@ -9,6 +8,7 @@ use codex_extension_api::WorldStateContributionInput; use codex_extension_api::WorldStateSectionContribution; use codex_protocol::openai_models::ModelInfo; +use crate::HostSkillsSnapshot; use crate::SkillsExtensionConfig; use crate::catalog::SkillCatalog; use crate::provider::SkillListQuery;