mirror of
https://github.com/openai/codex.git
synced 2026-11-13 14:46:14 +00:00
Propagate remote plugin IDs to skill metadata (#35261)
## What changed - Carry a plugin's local and remote identities together from plugin loading into `SkillMetadata`. - Resolve remote IDs from the installed-plugin snapshot when available, falling back to persisted install metadata only when no snapshot exists. - Include plugin identity in skill cache keys so identity changes refresh cached skill metadata. ## Testing - Cover snapshot and persisted identity resolution, local marketplace isolation, cached skill refreshes, and propagation through plugin skill snapshots. GitOrigin-RevId: aabeeb631a43361fe817358ace7f1ea8ba5db708
This commit is contained in:
1 parent
32329b289d
commit
07fd04abb1
28 files changed
+550
-170
No files matched your search
@@ -17,6 +17,7 @@ fn make_skill(name: &str, path: &str) -> SkillMetadata {
|
||||
path_to_skills_md: test_path_buf(path).abs(),
|
||||
scope: codex_protocol::protocol::SkillScope::User,
|
||||
plugin_id: None,
|
||||
remote_plugin_id: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ fn test_skill_metadata(skill_doc_path: AbsolutePathBuf) -> SkillMetadata {
|
||||
path_to_skills_md: skill_doc_path,
|
||||
scope: codex_protocol::protocol::SkillScope::User,
|
||||
plugin_id: None,
|
||||
remote_plugin_id: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ use codex_protocol::protocol::SkillScope;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use codex_utils_absolute_path::AbsolutePathBufGuard;
|
||||
use codex_utils_path_uri::PathUri;
|
||||
use codex_utils_plugins::PluginIdentity;
|
||||
use codex_utils_plugins::PluginSkillRoot;
|
||||
use codex_utils_plugins::SkillDiscoveryMode;
|
||||
use dirs::home_dir;
|
||||
@@ -195,7 +196,7 @@ pub struct SkillRoot {
|
||||
pub path: AbsolutePathBuf,
|
||||
pub scope: SkillScope,
|
||||
pub file_system: Arc<dyn ExecutorFileSystem>,
|
||||
pub plugin_id: Option<String>,
|
||||
pub plugin_identity: Option<PluginIdentity>,
|
||||
pub plugin_namespace: Option<String>,
|
||||
pub plugin_root: Option<AbsolutePathBuf>,
|
||||
pub discovery_mode: SkillDiscoveryMode,
|
||||
@@ -269,7 +270,7 @@ async fn skill_roots_with_home_dir(
|
||||
path: root.path,
|
||||
scope: SkillScope::User,
|
||||
file_system: Arc::clone(&LOCAL_FS),
|
||||
plugin_id: Some(root.plugin_id),
|
||||
plugin_identity: Some(root.plugin_identity),
|
||||
plugin_namespace: Some(root.plugin_namespace),
|
||||
plugin_root: Some(root.plugin_root),
|
||||
discovery_mode: root.discovery_mode,
|
||||
@@ -278,7 +279,7 @@ async fn skill_roots_with_home_dir(
|
||||
path,
|
||||
scope: SkillScope::User,
|
||||
file_system: Arc::clone(&LOCAL_FS),
|
||||
plugin_id: None,
|
||||
plugin_identity: None,
|
||||
plugin_namespace: None,
|
||||
plugin_root: None,
|
||||
discovery_mode: SkillDiscoveryMode::Recursive,
|
||||
@@ -310,7 +311,7 @@ fn skill_roots_from_layer_stack_inner(
|
||||
path: config_folder.join(SKILLS_DIR_NAME),
|
||||
scope: SkillScope::Repo,
|
||||
file_system: Arc::clone(repo_fs),
|
||||
plugin_id: None,
|
||||
plugin_identity: None,
|
||||
plugin_namespace: None,
|
||||
plugin_root: None,
|
||||
discovery_mode: SkillDiscoveryMode::Recursive,
|
||||
@@ -324,7 +325,7 @@ fn skill_roots_from_layer_stack_inner(
|
||||
path: config_folder.join(SKILLS_DIR_NAME),
|
||||
scope: SkillScope::User,
|
||||
file_system: Arc::clone(&LOCAL_FS),
|
||||
plugin_id: None,
|
||||
plugin_identity: None,
|
||||
plugin_namespace: None,
|
||||
plugin_root: None,
|
||||
discovery_mode: SkillDiscoveryMode::Recursive,
|
||||
@@ -336,7 +337,7 @@ fn skill_roots_from_layer_stack_inner(
|
||||
path: home_dir.join(AGENTS_DIR_NAME).join(SKILLS_DIR_NAME),
|
||||
scope: SkillScope::User,
|
||||
file_system: Arc::clone(&LOCAL_FS),
|
||||
plugin_id: None,
|
||||
plugin_identity: None,
|
||||
plugin_namespace: None,
|
||||
plugin_root: None,
|
||||
discovery_mode: SkillDiscoveryMode::Recursive,
|
||||
@@ -349,7 +350,7 @@ fn skill_roots_from_layer_stack_inner(
|
||||
path: system_cache_root_dir(&config_folder),
|
||||
scope: SkillScope::System,
|
||||
file_system: Arc::clone(&LOCAL_FS),
|
||||
plugin_id: None,
|
||||
plugin_identity: None,
|
||||
plugin_namespace: None,
|
||||
plugin_root: None,
|
||||
discovery_mode: SkillDiscoveryMode::Recursive,
|
||||
@@ -362,7 +363,7 @@ fn skill_roots_from_layer_stack_inner(
|
||||
path: config_folder.join(SKILLS_DIR_NAME),
|
||||
scope: SkillScope::Admin,
|
||||
file_system: Arc::clone(&LOCAL_FS),
|
||||
plugin_id: None,
|
||||
plugin_identity: None,
|
||||
plugin_namespace: None,
|
||||
plugin_root: None,
|
||||
discovery_mode: SkillDiscoveryMode::Recursive,
|
||||
@@ -408,7 +409,7 @@ async fn repo_agents_skill_roots(
|
||||
path: agents_skills,
|
||||
scope: SkillScope::Repo,
|
||||
file_system: Arc::clone(&fs),
|
||||
plugin_id: None,
|
||||
plugin_identity: None,
|
||||
plugin_namespace: None,
|
||||
plugin_root: None,
|
||||
discovery_mode: SkillDiscoveryMode::Recursive,
|
||||
@@ -530,6 +531,7 @@ async fn load_skills_under_root(
|
||||
outcome: &mut SkillLoadOutcome,
|
||||
) {
|
||||
let fs = skill_root.file_system.as_ref();
|
||||
let plugin_identity = skill_root.plugin_identity.as_ref();
|
||||
let plugin_root = match skill_root.plugin_root.as_ref() {
|
||||
Some(plugin_root) => Some(canonicalize_for_skill_identity(fs, plugin_root).await),
|
||||
None => None,
|
||||
@@ -659,7 +661,7 @@ async fn load_skills_under_root(
|
||||
&skill.path,
|
||||
&skill.path_uri,
|
||||
skill_root.scope,
|
||||
skill_root.plugin_id.as_deref(),
|
||||
plugin_identity,
|
||||
plugin_root,
|
||||
)
|
||||
.await
|
||||
@@ -696,7 +698,7 @@ async fn parse_skill_file(
|
||||
path: &AbsolutePathBuf,
|
||||
path_uri: &PathUri,
|
||||
scope: SkillScope,
|
||||
plugin_id: Option<&str>,
|
||||
plugin_identity: Option<&PluginIdentity>,
|
||||
plugin_root: Option<&AbsolutePathBuf>,
|
||||
) -> Result<SkillMetadata, SkillParseError> {
|
||||
let metadata_path = path_uri
|
||||
@@ -734,7 +736,8 @@ async fn parse_skill_file(
|
||||
policy,
|
||||
path_to_skills_md: path.clone(),
|
||||
scope,
|
||||
plugin_id: plugin_id.map(str::to_string),
|
||||
plugin_id: plugin_identity.map(|identity| identity.plugin_id.clone()),
|
||||
remote_plugin_id: plugin_identity.and_then(|identity| identity.remote_plugin_id.clone()),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -505,6 +505,7 @@ async fn loads_skills_from_home_agents_dir_for_user_scope() -> anyhow::Result<()
|
||||
path_to_skills_md: normalized(&skill_path),
|
||||
scope: SkillScope::User,
|
||||
plugin_id: None,
|
||||
remote_plugin_id: None,
|
||||
}]
|
||||
);
|
||||
|
||||
@@ -571,7 +572,7 @@ async fn load_user_skills_root(root: &Path) -> SkillLoadOutcome {
|
||||
path: root.abs(),
|
||||
scope: SkillScope::User,
|
||||
file_system: Arc::clone(&LOCAL_FS),
|
||||
plugin_id: None,
|
||||
plugin_identity: None,
|
||||
plugin_namespace: None,
|
||||
plugin_root: None,
|
||||
discovery_mode: SkillDiscoveryMode::Recursive,
|
||||
@@ -593,6 +594,7 @@ fn expected_user_skill(path: &Path, name: &str, description: &str) -> SkillMetad
|
||||
path_to_skills_md: normalized(path),
|
||||
scope: SkillScope::User,
|
||||
plugin_id: None,
|
||||
remote_plugin_id: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -680,6 +682,7 @@ async fn loads_skill_dependencies_metadata_from_yaml() {
|
||||
path_to_skills_md: normalized(&skill_path),
|
||||
scope: SkillScope::User,
|
||||
plugin_id: None,
|
||||
remote_plugin_id: None,
|
||||
}]
|
||||
);
|
||||
}
|
||||
@@ -736,6 +739,7 @@ interface:
|
||||
path_to_skills_md: normalized(skill_path.as_path()),
|
||||
scope: SkillScope::User,
|
||||
plugin_id: None,
|
||||
remote_plugin_id: None,
|
||||
}]
|
||||
);
|
||||
}
|
||||
@@ -890,6 +894,7 @@ async fn accepts_icon_paths_under_assets_dir() {
|
||||
path_to_skills_md: normalized(&skill_path),
|
||||
scope: SkillScope::User,
|
||||
plugin_id: None,
|
||||
remote_plugin_id: None,
|
||||
}]
|
||||
);
|
||||
}
|
||||
@@ -931,6 +936,7 @@ async fn ignores_invalid_brand_color() {
|
||||
path_to_skills_md: normalized(&skill_path),
|
||||
scope: SkillScope::User,
|
||||
plugin_id: None,
|
||||
remote_plugin_id: None,
|
||||
}]
|
||||
);
|
||||
}
|
||||
@@ -985,6 +991,7 @@ async fn ignores_default_prompt_over_max_length() {
|
||||
path_to_skills_md: normalized(&skill_path),
|
||||
scope: SkillScope::User,
|
||||
plugin_id: None,
|
||||
remote_plugin_id: None,
|
||||
}]
|
||||
);
|
||||
}
|
||||
@@ -1027,6 +1034,7 @@ async fn drops_interface_when_icons_are_invalid() {
|
||||
path_to_skills_md: normalized(&skill_path),
|
||||
scope: SkillScope::User,
|
||||
plugin_id: None,
|
||||
remote_plugin_id: None,
|
||||
}]
|
||||
);
|
||||
}
|
||||
@@ -1059,7 +1067,10 @@ interface:
|
||||
path: plugin_root.join("skills").abs(),
|
||||
scope: SkillScope::User,
|
||||
file_system: Arc::clone(&LOCAL_FS),
|
||||
plugin_id: Some("twilio-developer-kit@test".to_string()),
|
||||
plugin_identity: Some(PluginIdentity {
|
||||
plugin_id: "twilio-developer-kit@test".to_string(),
|
||||
remote_plugin_id: None,
|
||||
}),
|
||||
plugin_namespace: None,
|
||||
plugin_root: Some(plugin_root_abs.clone()),
|
||||
discovery_mode: SkillDiscoveryMode::Recursive,
|
||||
@@ -1094,6 +1105,7 @@ interface:
|
||||
path_to_skills_md: normalized(&skill_path),
|
||||
scope: SkillScope::User,
|
||||
plugin_id: Some("twilio-developer-kit@test".to_string()),
|
||||
remote_plugin_id: None,
|
||||
}]
|
||||
);
|
||||
}
|
||||
@@ -1122,7 +1134,10 @@ interface:
|
||||
path: plugin_root.join("skills").abs(),
|
||||
scope: SkillScope::User,
|
||||
file_system: Arc::clone(&LOCAL_FS),
|
||||
plugin_id: Some("twilio-developer-kit@test".to_string()),
|
||||
plugin_identity: Some(PluginIdentity {
|
||||
plugin_id: "twilio-developer-kit@test".to_string(),
|
||||
remote_plugin_id: None,
|
||||
}),
|
||||
plugin_namespace: None,
|
||||
plugin_root: Some(plugin_root.abs()),
|
||||
discovery_mode: SkillDiscoveryMode::Recursive,
|
||||
@@ -1149,6 +1164,7 @@ interface:
|
||||
path_to_skills_md: normalized(&skill_path),
|
||||
scope: SkillScope::User,
|
||||
plugin_id: Some("twilio-developer-kit@test".to_string()),
|
||||
remote_plugin_id: None,
|
||||
}]
|
||||
);
|
||||
}
|
||||
@@ -1194,6 +1210,7 @@ async fn loads_skills_via_symlinked_subdir_for_user_scope() {
|
||||
path_to_skills_md: normalized(&shared_skill_path),
|
||||
scope: SkillScope::User,
|
||||
plugin_id: None,
|
||||
remote_plugin_id: None,
|
||||
}]
|
||||
);
|
||||
}
|
||||
@@ -1280,6 +1297,7 @@ async fn does_not_loop_on_symlink_cycle_for_user_scope() {
|
||||
path_to_skills_md: normalized(&skill_path),
|
||||
scope: SkillScope::User,
|
||||
plugin_id: None,
|
||||
remote_plugin_id: None,
|
||||
}]
|
||||
);
|
||||
}
|
||||
@@ -1300,7 +1318,7 @@ async fn loads_skills_via_symlinked_subdir_for_admin_scope() {
|
||||
path: admin_root.path().abs(),
|
||||
scope: SkillScope::Admin,
|
||||
file_system: Arc::clone(&LOCAL_FS),
|
||||
plugin_id: None,
|
||||
plugin_identity: None,
|
||||
plugin_namespace: None,
|
||||
plugin_root: None,
|
||||
discovery_mode: SkillDiscoveryMode::Recursive,
|
||||
@@ -1327,6 +1345,7 @@ async fn loads_skills_via_symlinked_subdir_for_admin_scope() {
|
||||
path_to_skills_md: normalized(&shared_skill_path),
|
||||
scope: SkillScope::Admin,
|
||||
plugin_id: None,
|
||||
remote_plugin_id: None,
|
||||
}]
|
||||
);
|
||||
}
|
||||
@@ -1367,6 +1386,7 @@ async fn loads_skills_via_symlinked_subdir_for_repo_scope() {
|
||||
path_to_skills_md: normalized(&linked_skill_path),
|
||||
scope: SkillScope::Repo,
|
||||
plugin_id: None,
|
||||
remote_plugin_id: None,
|
||||
}]
|
||||
);
|
||||
}
|
||||
@@ -1388,7 +1408,7 @@ async fn system_scope_ignores_symlinked_subdir() {
|
||||
path: system_root.abs(),
|
||||
scope: SkillScope::System,
|
||||
file_system: Arc::clone(&LOCAL_FS),
|
||||
plugin_id: None,
|
||||
plugin_identity: None,
|
||||
plugin_namespace: None,
|
||||
plugin_root: None,
|
||||
discovery_mode: SkillDiscoveryMode::Recursive,
|
||||
@@ -1428,7 +1448,7 @@ async fn respects_max_scan_depth_for_user_scope() {
|
||||
path: skills_root.abs(),
|
||||
scope: SkillScope::User,
|
||||
file_system: Arc::clone(&LOCAL_FS),
|
||||
plugin_id: None,
|
||||
plugin_identity: None,
|
||||
plugin_namespace: None,
|
||||
plugin_root: None,
|
||||
discovery_mode: SkillDiscoveryMode::Recursive,
|
||||
@@ -1455,6 +1475,7 @@ async fn respects_max_scan_depth_for_user_scope() {
|
||||
path_to_skills_md: normalized(&within_depth_path),
|
||||
scope: SkillScope::User,
|
||||
plugin_id: None,
|
||||
remote_plugin_id: None,
|
||||
}]
|
||||
);
|
||||
}
|
||||
@@ -1483,6 +1504,7 @@ async fn loads_valid_skill() {
|
||||
path_to_skills_md: normalized(&skill_path),
|
||||
scope: SkillScope::User,
|
||||
plugin_id: None,
|
||||
remote_plugin_id: None,
|
||||
}]
|
||||
);
|
||||
}
|
||||
@@ -1516,6 +1538,7 @@ async fn falls_back_to_directory_name_when_skill_name_is_missing() {
|
||||
path_to_skills_md: normalized(&skill_path),
|
||||
scope: SkillScope::User,
|
||||
plugin_id: None,
|
||||
remote_plugin_id: None,
|
||||
}]
|
||||
);
|
||||
}
|
||||
@@ -1541,7 +1564,10 @@ async fn namespaces_plugin_skills_using_provided_namespace() {
|
||||
path: plugin_root.join("skills").abs(),
|
||||
scope: SkillScope::User,
|
||||
file_system: Arc::clone(&LOCAL_FS),
|
||||
plugin_id: Some("sample@test".to_string()),
|
||||
plugin_identity: Some(PluginIdentity {
|
||||
plugin_id: "sample@test".to_string(),
|
||||
remote_plugin_id: None,
|
||||
}),
|
||||
plugin_namespace: Some("sample".to_string()),
|
||||
plugin_root: Some(plugin_root.abs()),
|
||||
discovery_mode: SkillDiscoveryMode::Recursive,
|
||||
@@ -1568,6 +1594,7 @@ async fn namespaces_plugin_skills_using_provided_namespace() {
|
||||
path_to_skills_md: normalized(&skill_path),
|
||||
scope: SkillScope::User,
|
||||
plugin_id: Some("sample@test".to_string()),
|
||||
remote_plugin_id: None,
|
||||
}]
|
||||
);
|
||||
}
|
||||
@@ -1790,7 +1817,10 @@ async fn plugin_skill_name_length_limit_allows_max_qualified_name() {
|
||||
path: plugin_root.join("skills").abs(),
|
||||
scope: SkillScope::User,
|
||||
file_system: Arc::clone(&LOCAL_FS),
|
||||
plugin_id: Some("sample@test".to_string()),
|
||||
plugin_identity: Some(PluginIdentity {
|
||||
plugin_id: "sample@test".to_string(),
|
||||
remote_plugin_id: None,
|
||||
}),
|
||||
plugin_namespace: Some(plugin_name.clone()),
|
||||
plugin_root: Some(plugin_root.abs()),
|
||||
discovery_mode: SkillDiscoveryMode::Recursive,
|
||||
@@ -1817,6 +1847,7 @@ async fn plugin_skill_name_length_limit_allows_max_qualified_name() {
|
||||
path_to_skills_md: normalized(&skill_path),
|
||||
scope: SkillScope::User,
|
||||
plugin_id: Some("sample@test".to_string()),
|
||||
remote_plugin_id: None,
|
||||
}]
|
||||
);
|
||||
}
|
||||
@@ -1841,7 +1872,10 @@ async fn plugin_skill_name_length_limit_rejects_overlong_qualified_name() {
|
||||
path: plugin_root.join("skills").abs(),
|
||||
scope: SkillScope::User,
|
||||
file_system: Arc::clone(&LOCAL_FS),
|
||||
plugin_id: Some("sample@test".to_string()),
|
||||
plugin_identity: Some(PluginIdentity {
|
||||
plugin_id: "sample@test".to_string(),
|
||||
remote_plugin_id: None,
|
||||
}),
|
||||
plugin_namespace: Some(plugin_name.clone()),
|
||||
plugin_root: Some(plugin_root.abs()),
|
||||
discovery_mode: SkillDiscoveryMode::Recursive,
|
||||
@@ -1873,7 +1907,10 @@ async fn direct_child_discovery_ignores_nested_skills() {
|
||||
path: skills_root.abs(),
|
||||
scope: SkillScope::User,
|
||||
file_system: Arc::clone(&LOCAL_FS),
|
||||
plugin_id: Some("plugin@test".to_string()),
|
||||
plugin_identity: Some(PluginIdentity {
|
||||
plugin_id: "plugin@test".to_string(),
|
||||
remote_plugin_id: None,
|
||||
}),
|
||||
plugin_namespace: Some("plugin".to_string()),
|
||||
plugin_root: Some(plugin_root.abs()),
|
||||
discovery_mode: SkillDiscoveryMode::DirectChildren,
|
||||
@@ -1896,6 +1933,7 @@ async fn direct_child_discovery_ignores_nested_skills() {
|
||||
path_to_skills_md: normalized(&direct),
|
||||
scope: SkillScope::User,
|
||||
plugin_id: Some("plugin@test".to_string()),
|
||||
remote_plugin_id: None,
|
||||
}]
|
||||
);
|
||||
}
|
||||
@@ -1917,7 +1955,10 @@ async fn direct_child_discovery_skips_skills_resolving_outside_plugin_root() {
|
||||
path: skills_root.abs(),
|
||||
scope: SkillScope::User,
|
||||
file_system: Arc::clone(&LOCAL_FS),
|
||||
plugin_id: Some("plugin@test".to_string()),
|
||||
plugin_identity: Some(PluginIdentity {
|
||||
plugin_id: "plugin@test".to_string(),
|
||||
remote_plugin_id: None,
|
||||
}),
|
||||
plugin_namespace: Some("plugin".to_string()),
|
||||
plugin_root: Some(plugin_root.abs()),
|
||||
discovery_mode: SkillDiscoveryMode::DirectChildren,
|
||||
@@ -1958,6 +1999,7 @@ async fn loads_short_description_from_metadata() {
|
||||
path_to_skills_md: normalized(&skill_path),
|
||||
scope: SkillScope::User,
|
||||
plugin_id: None,
|
||||
remote_plugin_id: None,
|
||||
}]
|
||||
);
|
||||
}
|
||||
@@ -1990,6 +2032,7 @@ async fn loads_unquoted_description_containing_colon_space() {
|
||||
path_to_skills_md: normalized(&skill_path),
|
||||
scope: SkillScope::User,
|
||||
plugin_id: None,
|
||||
remote_plugin_id: None,
|
||||
}]
|
||||
);
|
||||
}
|
||||
@@ -2022,6 +2065,7 @@ async fn loads_unquoted_short_description_containing_colon_space_and_apostrophe(
|
||||
path_to_skills_md: normalized(&skill_path),
|
||||
scope: SkillScope::User,
|
||||
plugin_id: None,
|
||||
remote_plugin_id: None,
|
||||
}]
|
||||
);
|
||||
}
|
||||
@@ -2054,6 +2098,7 @@ async fn loads_unrecognized_frontmatter_fields_that_need_quotes() {
|
||||
path_to_skills_md: normalized(&skill_path),
|
||||
scope: SkillScope::User,
|
||||
plugin_id: None,
|
||||
remote_plugin_id: None,
|
||||
}]
|
||||
);
|
||||
}
|
||||
@@ -2086,6 +2131,7 @@ async fn preserves_block_scalar_body_while_repairing_other_fields() {
|
||||
path_to_skills_md: normalized(&skill_path),
|
||||
scope: SkillScope::User,
|
||||
plugin_id: None,
|
||||
remote_plugin_id: None,
|
||||
}]
|
||||
);
|
||||
}
|
||||
@@ -2203,6 +2249,7 @@ async fn loads_skills_from_repo_root() {
|
||||
path_to_skills_md: normalized(&skill_path),
|
||||
scope: SkillScope::Repo,
|
||||
plugin_id: None,
|
||||
remote_plugin_id: None,
|
||||
}]
|
||||
);
|
||||
}
|
||||
@@ -2239,6 +2286,7 @@ async fn loads_skills_from_agents_dir_without_codex_dir() {
|
||||
path_to_skills_md: normalized(&skill_path),
|
||||
scope: SkillScope::Repo,
|
||||
plugin_id: None,
|
||||
remote_plugin_id: None,
|
||||
}]
|
||||
);
|
||||
}
|
||||
@@ -2293,6 +2341,7 @@ async fn loads_skills_from_all_codex_dirs_under_project_root() {
|
||||
path_to_skills_md: normalized(&nested_skill_path),
|
||||
scope: SkillScope::Repo,
|
||||
plugin_id: None,
|
||||
remote_plugin_id: None,
|
||||
},
|
||||
SkillMetadata {
|
||||
name: "root-skill".to_string(),
|
||||
@@ -2304,6 +2353,7 @@ async fn loads_skills_from_all_codex_dirs_under_project_root() {
|
||||
path_to_skills_md: normalized(&root_skill_path),
|
||||
scope: SkillScope::Repo,
|
||||
plugin_id: None,
|
||||
remote_plugin_id: None,
|
||||
},
|
||||
]
|
||||
);
|
||||
@@ -2461,7 +2511,7 @@ async fn merges_root_results_in_input_order_when_scans_finish_out_of_order() {
|
||||
SkillScope::User
|
||||
},
|
||||
file_system: Arc::clone(&root_file_system),
|
||||
plugin_id: None,
|
||||
plugin_identity: None,
|
||||
plugin_namespace: Some("test".to_string()),
|
||||
plugin_root: None,
|
||||
discovery_mode: SkillDiscoveryMode::Recursive,
|
||||
@@ -2527,7 +2577,7 @@ async fn skill_root_scans_wait_for_shared_capacity() {
|
||||
path: root.abs(),
|
||||
scope: SkillScope::Repo,
|
||||
file_system: Arc::clone(&LOCAL_FS),
|
||||
plugin_id: None,
|
||||
plugin_identity: None,
|
||||
plugin_namespace: Some("test".to_string()),
|
||||
plugin_root: None,
|
||||
discovery_mode: SkillDiscoveryMode::Recursive,
|
||||
@@ -2580,6 +2630,7 @@ async fn loads_skills_from_codex_dir_when_not_git_repo() {
|
||||
path_to_skills_md: normalized(&skill_path),
|
||||
scope: SkillScope::Repo,
|
||||
plugin_id: None,
|
||||
remote_plugin_id: None,
|
||||
}]
|
||||
);
|
||||
}
|
||||
@@ -2596,7 +2647,7 @@ async fn deduplicates_by_path_preferring_first_root() {
|
||||
path: root.path().abs(),
|
||||
scope: SkillScope::Repo,
|
||||
file_system: Arc::clone(&LOCAL_FS),
|
||||
plugin_id: None,
|
||||
plugin_identity: None,
|
||||
plugin_namespace: None,
|
||||
plugin_root: None,
|
||||
discovery_mode: SkillDiscoveryMode::Recursive,
|
||||
@@ -2605,7 +2656,7 @@ async fn deduplicates_by_path_preferring_first_root() {
|
||||
path: root.path().abs(),
|
||||
scope: SkillScope::User,
|
||||
file_system: Arc::clone(&LOCAL_FS),
|
||||
plugin_id: None,
|
||||
plugin_identity: None,
|
||||
plugin_namespace: None,
|
||||
plugin_root: None,
|
||||
discovery_mode: SkillDiscoveryMode::Recursive,
|
||||
@@ -2633,6 +2684,7 @@ async fn deduplicates_by_path_preferring_first_root() {
|
||||
path_to_skills_md: normalized(&skill_path),
|
||||
scope: SkillScope::Repo,
|
||||
plugin_id: None,
|
||||
remote_plugin_id: None,
|
||||
}]
|
||||
);
|
||||
}
|
||||
@@ -2675,6 +2727,7 @@ async fn keeps_duplicate_names_from_repo_and_user() {
|
||||
path_to_skills_md: normalized(&repo_skill_path),
|
||||
scope: SkillScope::Repo,
|
||||
plugin_id: None,
|
||||
remote_plugin_id: None,
|
||||
},
|
||||
SkillMetadata {
|
||||
name: "dupe-skill".to_string(),
|
||||
@@ -2686,6 +2739,7 @@ async fn keeps_duplicate_names_from_repo_and_user() {
|
||||
path_to_skills_md: normalized(&user_skill_path),
|
||||
scope: SkillScope::User,
|
||||
plugin_id: None,
|
||||
remote_plugin_id: None,
|
||||
},
|
||||
]
|
||||
);
|
||||
@@ -2749,6 +2803,7 @@ async fn keeps_duplicate_names_from_nested_codex_dirs() {
|
||||
path_to_skills_md: first_path,
|
||||
scope: SkillScope::Repo,
|
||||
plugin_id: None,
|
||||
remote_plugin_id: None,
|
||||
},
|
||||
SkillMetadata {
|
||||
name: "dupe-skill".to_string(),
|
||||
@@ -2760,6 +2815,7 @@ async fn keeps_duplicate_names_from_nested_codex_dirs() {
|
||||
path_to_skills_md: second_path,
|
||||
scope: SkillScope::Repo,
|
||||
plugin_id: None,
|
||||
remote_plugin_id: None,
|
||||
},
|
||||
]
|
||||
);
|
||||
@@ -2832,6 +2888,7 @@ async fn loads_skills_when_cwd_is_file_in_repo() {
|
||||
path_to_skills_md: normalized(&skill_path),
|
||||
scope: SkillScope::Repo,
|
||||
plugin_id: None,
|
||||
remote_plugin_id: None,
|
||||
}]
|
||||
);
|
||||
}
|
||||
@@ -2891,6 +2948,7 @@ async fn loads_skills_from_system_cache_when_present() {
|
||||
path_to_skills_md: normalized(&skill_path),
|
||||
scope: SkillScope::System,
|
||||
plugin_id: None,
|
||||
remote_plugin_id: None,
|
||||
}]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -939,6 +939,7 @@ mod tests {
|
||||
path_to_skills_md: test_path_buf(&format!("/tmp/{name}/SKILL.md")).abs(),
|
||||
scope,
|
||||
plugin_id: None,
|
||||
remote_plugin_id: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -56,14 +56,14 @@ where
|
||||
.await
|
||||
.unwrap_or_else(|_| unreachable!());
|
||||
let cache_key = match (
|
||||
root.plugin_id.clone(),
|
||||
root.plugin_identity.clone(),
|
||||
root.plugin_namespace.clone(),
|
||||
root.plugin_root.clone(),
|
||||
) {
|
||||
(Some(plugin_id), Some(plugin_namespace), Some(plugin_root)) => {
|
||||
(Some(plugin_identity), Some(plugin_namespace), Some(plugin_root)) => {
|
||||
Some(PluginSkillRoot {
|
||||
path: root.path.clone(),
|
||||
plugin_id,
|
||||
plugin_identity,
|
||||
plugin_namespace,
|
||||
plugin_root,
|
||||
discovery_mode: root.discovery_mode,
|
||||
|
||||
@@ -8,6 +8,7 @@ use codex_exec_server::ExecutorFileSystem;
|
||||
use codex_protocol::protocol::Product;
|
||||
use codex_protocol::protocol::SkillScope;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use codex_utils_plugins::PluginIdentity;
|
||||
use codex_utils_plugins::PluginSkillRoot;
|
||||
use tokio::sync::Semaphore;
|
||||
use tracing::info;
|
||||
@@ -280,10 +281,18 @@ impl SkillsService {
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
struct ConfigSkillsCacheKey {
|
||||
roots: Vec<(AbsolutePathBuf, u8, Option<String>, Option<String>)>,
|
||||
roots: Vec<ConfigSkillRootCacheKey>,
|
||||
skill_config_rules: SkillConfigRules,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
struct ConfigSkillRootCacheKey {
|
||||
path: AbsolutePathBuf,
|
||||
scope_rank: u8,
|
||||
plugin_identity: Option<PluginIdentity>,
|
||||
plugin_namespace: Option<String>,
|
||||
}
|
||||
|
||||
pub fn bundled_skills_enabled_from_stack(
|
||||
config_layer_stack: &codex_config::ConfigLayerStack,
|
||||
) -> bool {
|
||||
@@ -320,12 +329,12 @@ fn config_skills_cache_key(
|
||||
SkillScope::System => 2,
|
||||
SkillScope::Admin => 3,
|
||||
};
|
||||
(
|
||||
root.path.clone(),
|
||||
ConfigSkillRootCacheKey {
|
||||
path: root.path.clone(),
|
||||
scope_rank,
|
||||
root.plugin_id.clone(),
|
||||
root.plugin_namespace.clone(),
|
||||
)
|
||||
plugin_identity: root.plugin_identity.clone(),
|
||||
plugin_namespace: root.plugin_namespace.clone(),
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
skill_config_rules: skill_config_rules.clone(),
|
||||
|
||||
@@ -11,6 +11,7 @@ use codex_exec_server::LOCAL_FS;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use codex_utils_absolute_path::test_support::PathBufExt;
|
||||
use codex_utils_absolute_path::test_support::PathExt;
|
||||
use codex_utils_plugins::PluginIdentity;
|
||||
use codex_utils_plugins::PluginSkillRoot;
|
||||
use codex_utils_plugins::SkillDiscoveryMode;
|
||||
use pretty_assertions::assert_eq;
|
||||
@@ -70,7 +71,10 @@ fn plugin_skill_root_for_skill_path(
|
||||
.expect("plugin skills root should live under a plugin root");
|
||||
PluginSkillRoot {
|
||||
path: skills_root.abs(),
|
||||
plugin_id: plugin_id.to_string(),
|
||||
plugin_identity: PluginIdentity {
|
||||
plugin_id: plugin_id.to_string(),
|
||||
remote_plugin_id: None,
|
||||
},
|
||||
plugin_namespace: plugin_namespace.to_string(),
|
||||
plugin_root: plugin_root.abs(),
|
||||
discovery_mode: SkillDiscoveryMode::Recursive,
|
||||
@@ -91,6 +95,7 @@ fn test_skill(name: &str, path: PathBuf) -> SkillMetadata {
|
||||
.expect("skill path should canonicalize"),
|
||||
scope: SkillScope::User,
|
||||
plugin_id: None,
|
||||
remote_plugin_id: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,6 +235,53 @@ async fn skills_for_config_reuses_cache_for_same_effective_config() {
|
||||
assert_eq!(outcome2.skills, outcome1.skills);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn skills_for_config_refreshes_cache_when_remote_plugin_id_changes() {
|
||||
let codex_home = tempfile::tempdir().expect("tempdir");
|
||||
let cwd = tempfile::tempdir().expect("tempdir");
|
||||
let skill_path = write_plugin_skill(
|
||||
&codex_home,
|
||||
"test",
|
||||
"sample",
|
||||
"sample-search",
|
||||
"sample-search",
|
||||
"search sample data",
|
||||
);
|
||||
let config_layer_stack = config_stack(&codex_home, "");
|
||||
let mut plugin_skill_root =
|
||||
plugin_skill_root_for_skill_path(&skill_path, "sample@test", "sample");
|
||||
let skills_service = SkillsService::new(
|
||||
codex_home.path().abs(),
|
||||
/*bundled_skills_enabled*/ true,
|
||||
);
|
||||
|
||||
skills_for_config_with_stack(
|
||||
&skills_service,
|
||||
&cwd,
|
||||
&config_layer_stack,
|
||||
&[plugin_skill_root.clone()],
|
||||
)
|
||||
.await;
|
||||
|
||||
plugin_skill_root.plugin_identity.remote_plugin_id = Some("plugins~Plugin_sample".to_string());
|
||||
let refreshed = skills_for_config_with_stack(
|
||||
&skills_service,
|
||||
&cwd,
|
||||
&config_layer_stack,
|
||||
&[plugin_skill_root],
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(
|
||||
refreshed
|
||||
.skills
|
||||
.iter()
|
||||
.find(|skill| skill.name == "sample:sample-search")
|
||||
.and_then(|skill| skill.remote_plugin_id.as_deref()),
|
||||
Some("plugins~Plugin_sample")
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn set_extra_roots_replaces_runtime_roots_and_clears_cache() {
|
||||
let codex_home = tempfile::tempdir().expect("tempdir");
|
||||
|
||||
Reference in new issue
Block a user