diff --git a/codex-rs/app-server/src/codex_message_processor.rs b/codex-rs/app-server/src/codex_message_processor.rs index b2f9f528b..276b4ff4a 100644 --- a/codex-rs/app-server/src/codex_message_processor.rs +++ b/codex-rs/app-server/src/codex_message_processor.rs @@ -6526,7 +6526,7 @@ fn skills_to_info( skills .iter() .map(|skill| { - let enabled = !disabled_paths.contains(&skill.path); + let enabled = !disabled_paths.contains(&skill.path_to_skills_md); codex_app_server_protocol::SkillMetadata { name: skill.name.clone(), description: skill.description.clone(), @@ -6557,7 +6557,7 @@ fn skills_to_info( .collect(), } }), - path: skill.path.clone(), + path: skill.path_to_skills_md.clone(), scope: skill.scope.into(), enabled, } diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 397318c8e..d3d789c4e 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -4734,9 +4734,9 @@ fn skills_to_info( .collect(), } }), - path: skill.path.clone(), + path: skill.path_to_skills_md.clone(), scope: skill.scope, - enabled: !disabled_paths.contains(&skill.path), + enabled: !disabled_paths.contains(&skill.path_to_skills_md), }) .collect() } diff --git a/codex-rs/core/src/mcp/skill_dependencies.rs b/codex-rs/core/src/mcp/skill_dependencies.rs index f2138cc29..7ea040aed 100644 --- a/codex-rs/core/src/mcp/skill_dependencies.rs +++ b/codex-rs/core/src/mcp/skill_dependencies.rs @@ -434,7 +434,7 @@ mod tests { dependencies: Some(SkillDependencies { tools }), policy: None, permissions: None, - path: PathBuf::from("skill"), + path_to_skills_md: PathBuf::from("skill"), scope: SkillScope::User, } } diff --git a/codex-rs/core/src/mentions.rs b/codex-rs/core/src/mentions.rs index edbcf0161..008456d61 100644 --- a/codex-rs/core/src/mentions.rs +++ b/codex-rs/core/src/mentions.rs @@ -55,7 +55,7 @@ pub(crate) fn build_skill_name_counts( let mut exact_counts: HashMap = HashMap::new(); let mut lower_counts: HashMap = HashMap::new(); for skill in skills { - if disabled_paths.contains(&skill.path) { + if disabled_paths.contains(&skill.path_to_skills_md) { continue; } *exact_counts.entry(skill.name.clone()).or_insert(0) += 1; diff --git a/codex-rs/core/src/skills/injection.rs b/codex-rs/core/src/skills/injection.rs index 475ea6c12..22767d6dc 100644 --- a/codex-rs/core/src/skills/injection.rs +++ b/codex-rs/core/src/skills/injection.rs @@ -37,18 +37,18 @@ pub(crate) async fn build_skill_injections( let mut invocations = Vec::new(); for skill in mentioned_skills { - match fs::read_to_string(&skill.path).await { + match fs::read_to_string(&skill.path_to_skills_md).await { Ok(contents) => { emit_skill_injected_metric(otel, skill, "ok"); invocations.push(SkillInvocation { skill_name: skill.name.clone(), skill_scope: skill.scope, - skill_path: skill.path.clone(), + skill_path: skill.path_to_skills_md.clone(), invocation_type: InvocationType::Explicit, }); result.items.push(ResponseItem::from(SkillInstructions { name: skill.name.clone(), - path: skill.path.to_string_lossy().into_owned(), + path: skill.path_to_skills_md.to_string_lossy().into_owned(), contents, })); } @@ -57,7 +57,7 @@ pub(crate) async fn build_skill_injections( let message = format!( "Failed to load skill {name} at {path}: {err:#}", name = skill.name, - path = skill.path.display() + path = skill.path_to_skills_md.display() ); result.warnings.push(message); } @@ -121,9 +121,9 @@ pub(crate) fn collect_explicit_skill_mentions( if let Some(skill) = selection_context .skills .iter() - .find(|skill| skill.path.as_path() == path.as_path()) + .find(|skill| skill.path_to_skills_md.as_path() == path.as_path()) { - seen_paths.insert(skill.path.clone()); + seen_paths.insert(skill.path_to_skills_md.clone()); seen_names.insert(skill.name.clone()); selected.push(skill.clone()); } @@ -304,23 +304,27 @@ fn select_skills_from_mentions( .collect(); for skill in selection_context.skills { - if selection_context.disabled_paths.contains(&skill.path) - || seen_paths.contains(&skill.path) + if selection_context + .disabled_paths + .contains(&skill.path_to_skills_md) + || seen_paths.contains(&skill.path_to_skills_md) { continue; } - let path_str = skill.path.to_string_lossy(); + let path_str = skill.path_to_skills_md.to_string_lossy(); if mention_skill_paths.contains(path_str.as_ref()) { - seen_paths.insert(skill.path.clone()); + seen_paths.insert(skill.path_to_skills_md.clone()); seen_names.insert(skill.name.clone()); selected.push(skill.clone()); } } for skill in selection_context.skills { - if selection_context.disabled_paths.contains(&skill.path) - || seen_paths.contains(&skill.path) + if selection_context + .disabled_paths + .contains(&skill.path_to_skills_md) + || seen_paths.contains(&skill.path_to_skills_md) { continue; } @@ -347,7 +351,7 @@ fn select_skills_from_mentions( } if seen_names.insert(skill.name.clone()) { - seen_paths.insert(skill.path.clone()); + seen_paths.insert(skill.path_to_skills_md.clone()); selected.push(skill.clone()); } } @@ -479,7 +483,7 @@ mod tests { dependencies: None, policy: None, permissions: None, - path: PathBuf::from(path), + path_to_skills_md: PathBuf::from(path), scope: codex_protocol::protocol::SkillScope::User, } } diff --git a/codex-rs/core/src/skills/invocation_utils.rs b/codex-rs/core/src/skills/invocation_utils.rs index fd847091d..65f077f96 100644 --- a/codex-rs/core/src/skills/invocation_utils.rs +++ b/codex-rs/core/src/skills/invocation_utils.rs @@ -32,10 +32,10 @@ pub(crate) fn build_implicit_skill_path_indexes( let mut by_scripts_dir = HashMap::new(); let mut by_skill_doc_path = HashMap::new(); for skill in skills { - let skill_doc_path = normalize_path(skill.path.as_path()); + let skill_doc_path = normalize_path(skill.path_to_skills_md.as_path()); by_skill_doc_path.insert(skill_doc_path, skill.clone()); - if let Some(skill_dir) = skill.path.parent() { + if let Some(skill_dir) = skill.path_to_skills_md.parent() { let scripts_dir = normalize_path(&skill_dir.join("scripts")); by_scripts_dir.insert(scripts_dir, skill); } @@ -118,7 +118,7 @@ pub(crate) async fn ensure_skill_approval_for_command( let cache_key = SkillApprovalCacheKey { skill_name: skill.name.clone(), - skill_path: skill.path.clone(), + skill_path: skill.path_to_skills_md.clone(), skill_scope: skill.scope, }; let already_approved = { @@ -162,7 +162,7 @@ pub(crate) async fn maybe_emit_implicit_skill_invocation( let invocation = SkillInvocation { skill_name: candidate.name, skill_scope: candidate.scope, - skill_path: candidate.path, + skill_path: candidate.path_to_skills_md, invocation_type: InvocationType::Implicit, }; let skill_scope = match invocation.skill_scope { @@ -337,7 +337,7 @@ mod tests { dependencies: None, policy: None, permissions: None, - path: skill_doc_path, + path_to_skills_md: skill_doc_path, scope: codex_protocol::protocol::SkillScope::User, } } diff --git a/codex-rs/core/src/skills/loader.rs b/codex-rs/core/src/skills/loader.rs index 6508d4fd1..960830649 100644 --- a/codex-rs/core/src/skills/loader.rs +++ b/codex-rs/core/src/skills/loader.rs @@ -167,7 +167,7 @@ where let mut seen: HashSet = HashSet::new(); outcome .skills - .retain(|skill| seen.insert(skill.path.clone())); + .retain(|skill| seen.insert(skill.path_to_skills_md.clone())); fn scope_rank(scope: SkillScope) -> u8 { // Higher-priority scopes first (matches root scan order for dedupe). @@ -183,7 +183,7 @@ where scope_rank(a.scope) .cmp(&scope_rank(b.scope)) .then_with(|| a.name.cmp(&b.name)) - .then_with(|| a.path.cmp(&b.path)) + .then_with(|| a.path_to_skills_md.cmp(&b.path_to_skills_md)) }); outcome @@ -528,7 +528,7 @@ fn parse_skill_file(path: &Path, scope: SkillScope) -> Result, // This is an experimental field. pub permissions: Option, - pub path: PathBuf, + /// Path to the SKILLS.md file that declares this skill. + pub path_to_skills_md: PathBuf, pub scope: SkillScope, } @@ -76,7 +77,7 @@ pub struct SkillLoadOutcome { impl SkillLoadOutcome { pub fn is_skill_enabled(&self, skill: &SkillMetadata) -> bool { - !self.disabled_paths.contains(&skill.path) + !self.disabled_paths.contains(&skill.path_to_skills_md) } pub fn is_skill_allowed_for_implicit_invocation(&self, skill: &SkillMetadata) -> bool { diff --git a/codex-rs/core/src/skills/render.rs b/codex-rs/core/src/skills/render.rs index 9627778dc..e6243f169 100644 --- a/codex-rs/core/src/skills/render.rs +++ b/codex-rs/core/src/skills/render.rs @@ -11,7 +11,7 @@ pub fn render_skills_section(skills: &[SkillMetadata]) -> Option { lines.push("### Available skills".to_string()); for skill in skills { - let path_str = skill.path.to_string_lossy().replace('\\', "/"); + let path_str = skill.path_to_skills_md.to_string_lossy().replace('\\', "/"); let name = skill.name.as_str(); let description = skill.description.as_str(); lines.push(format!("- {name}: {description} (file: {path_str})")); diff --git a/codex-rs/tui/src/bottom_pane/chat_composer.rs b/codex-rs/tui/src/bottom_pane/chat_composer.rs index 8d9e4b250..789211996 100644 --- a/codex-rs/tui/src/bottom_pane/chat_composer.rs +++ b/codex-rs/tui/src/bottom_pane/chat_composer.rs @@ -3559,7 +3559,7 @@ impl ChatComposer { description, insert_text: format!("${skill_name}"), search_terms, - path: Some(skill.path.to_string_lossy().into_owned()), + path: Some(skill.path_to_skills_md.to_string_lossy().into_owned()), category_tag: (skill.scope == codex_protocol::protocol::SkillScope::Repo) .then(|| "[Repo]".to_string()), }); diff --git a/codex-rs/tui/src/bottom_pane/mod.rs b/codex-rs/tui/src/bottom_pane/mod.rs index 680129236..5781e055d 100644 --- a/codex-rs/tui/src/bottom_pane/mod.rs +++ b/codex-rs/tui/src/bottom_pane/mod.rs @@ -1495,7 +1495,7 @@ mod tests { dependencies: None, policy: None, permissions: None, - path: PathBuf::from("test-skill"), + path_to_skills_md: PathBuf::from("test-skill"), scope: SkillScope::User, }]), }); diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index ac8da4d55..15a6a58d6 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -4066,12 +4066,14 @@ impl ChatWidget { .strip_prefix("skill://") .unwrap_or(binding.path.as_str()); let path = Path::new(path); - if let Some(skill) = skills.iter().find(|skill| skill.path.as_path() == path) - && selected_skill_paths.insert(skill.path.clone()) + if let Some(skill) = skills + .iter() + .find(|skill| skill.path_to_skills_md.as_path() == path) + && selected_skill_paths.insert(skill.path_to_skills_md.clone()) { items.push(UserInput::Skill { name: skill.name.clone(), - path: skill.path.clone(), + path: skill.path_to_skills_md.clone(), }); } } @@ -4079,13 +4081,13 @@ impl ChatWidget { let skill_mentions = find_skill_mentions_with_tool_mentions(&mentions, skills); for skill in skill_mentions { if bound_names.contains(skill.name.as_str()) - || !selected_skill_paths.insert(skill.path.clone()) + || !selected_skill_paths.insert(skill.path_to_skills_md.clone()) { continue; } items.push(UserInput::Skill { name: skill.name.clone(), - path: skill.path.clone(), + path: skill.path_to_skills_md.clone(), }); } } diff --git a/codex-rs/tui/src/chatwidget/skills.rs b/codex-rs/tui/src/chatwidget/skills.rs index 1d71cc61e..72af6b89b 100644 --- a/codex-rs/tui/src/chatwidget/skills.rs +++ b/codex-rs/tui/src/chatwidget/skills.rs @@ -78,7 +78,7 @@ impl ChatWidget { let display_name = skill_display_name(&core_skill).to_string(); let description = skill_description(&core_skill).to_string(); let name = core_skill.name.clone(); - let path = core_skill.path; + let path = core_skill.path_to_skills_md; SkillsToggleItem { name: display_name, skill_name: name, @@ -191,7 +191,7 @@ fn protocol_skill_to_core(skill: &ProtocolSkillMetadata) -> SkillMetadata { }), policy: None, permissions: None, - path: skill.path.clone(), + path_to_skills_md: skill.path.clone(), scope: skill.scope, } } @@ -229,23 +229,23 @@ pub(crate) fn find_skill_mentions_with_tool_mentions( let mut matches: Vec = Vec::new(); for skill in skills { - if seen_paths.contains(&skill.path) { + if seen_paths.contains(&skill.path_to_skills_md) { continue; } - let path_str = skill.path.to_string_lossy(); + let path_str = skill.path_to_skills_md.to_string_lossy(); if mention_skill_paths.contains(path_str.as_ref()) { - seen_paths.insert(skill.path.clone()); + seen_paths.insert(skill.path_to_skills_md.clone()); seen_names.insert(skill.name.clone()); matches.push(skill.clone()); } } for skill in skills { - if seen_paths.contains(&skill.path) { + if seen_paths.contains(&skill.path_to_skills_md) { continue; } if mentions.names.contains(&skill.name) && seen_names.insert(skill.name.clone()) { - seen_paths.insert(skill.path.clone()); + seen_paths.insert(skill.path_to_skills_md.clone()); matches.push(skill.clone()); } } diff --git a/codex-rs/tui/src/chatwidget/tests.rs b/codex-rs/tui/src/chatwidget/tests.rs index 5fa985413..1e938e2d4 100644 --- a/codex-rs/tui/src/chatwidget/tests.rs +++ b/codex-rs/tui/src/chatwidget/tests.rs @@ -931,7 +931,7 @@ async fn submission_prefers_selected_duplicate_skill_path() { dependencies: None, policy: None, permissions: None, - path: repo_skill_path, + path_to_skills_md: repo_skill_path, scope: SkillScope::Repo, }, SkillMetadata { @@ -942,7 +942,7 @@ async fn submission_prefers_selected_duplicate_skill_path() { dependencies: None, policy: None, permissions: None, - path: user_skill_path.clone(), + path_to_skills_md: user_skill_path.clone(), scope: SkillScope::User, }, ]));