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:
jameswt-oai authored and copyberry committed 2026-07-24 23:36:11 +00:00
1 parent 32329b289d
commit 07fd04abb1
28 files changed
+550 -170

No files matched your search

+73 -15
View File
@@ -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,
}]
);
}