Skip to content

Commit 9a50a04

Browse files
authored
feat: Support listing and selecting skills via $ or /skills (#7506)
List/Select skills with $-mention or /skills
1 parent 231ff19 commit 9a50a04

File tree

11 files changed

+505
-97
lines changed

11 files changed

+505
-97
lines changed

codex-rs/core/src/project_doc.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -516,8 +516,9 @@ mod tests {
516516
)
517517
.unwrap_or_else(|_| cfg.codex_home.join("skills/pdf-processing/SKILL.md"));
518518
let expected_path_str = expected_path.to_string_lossy().replace('\\', "/");
519+
let usage_rules = "- Discovery: Available skills are listed in project docs and may also appear in a runtime \"## Skills\" section (name + description + file path). These are the sources of truth; skill bodies live on disk at the listed paths.\n- Trigger rules: If the user names a skill (with `$SkillName` or plain text) OR the task clearly matches a skill's description, you must use that skill for that turn. Multiple mentions mean use them all. Do not carry skills across turns unless re-mentioned.\n- Missing/blocked: If a named skill isn't in the list or the path can't be read, say so briefly and continue with the best fallback.\n- How to use a skill (progressive disclosure):\n 1) After deciding to use a skill, open its `SKILL.md`. Read only enough to follow the workflow.\n 2) If `SKILL.md` points to extra folders such as `references/`, load only the specific files needed for the request; don't bulk-load everything.\n 3) If `scripts/` exist, prefer running or patching them instead of retyping large code blocks.\n 4) If `assets/` or templates exist, reuse them instead of recreating from scratch.\n- Description as trigger: The YAML `description` in `SKILL.md` is the primary trigger signal; rely on it to decide applicability. If unsure, ask a brief clarification before proceeding.\n- Coordination and sequencing:\n - If multiple skills apply, choose the minimal set that covers the request and state the order you'll use them.\n - Announce which skill(s) you're using and why (one short line). If you skip an obvious skill, say why.\n- Context hygiene:\n - Keep context small: summarize long sections instead of pasting them; only load extra files when needed.\n - Avoid deeply nested references; prefer one-hop files explicitly linked from `SKILL.md`.\n - When variants exist (frameworks, providers, domains), pick only the relevant reference file(s) and note that choice.\n- Safety and fallback: If a skill can't be applied cleanly (missing files, unclear instructions), state the issue, pick the next-best approach, and continue.";
519520
let expected = format!(
520-
"base doc\n\n## Skills\nThese skills are discovered at startup from ~/.codex/skills; each entry shows name, description, and file path so you can open the source for full instructions. Content is not inlined to keep context lean.\n- pdf-processing: extract from pdfs (file: {expected_path_str})"
521+
"base doc\n\n## Skills\nThese skills are discovered at startup from ~/.codex/skills; each entry shows name, description, and file path so you can open the source for full instructions. Content is not inlined to keep context lean.\n- pdf-processing: extract from pdfs (file: {expected_path_str})\n{usage_rules}"
521522
);
522523
assert_eq!(res, expected);
523524
}
@@ -535,8 +536,9 @@ mod tests {
535536
dunce::canonicalize(cfg.codex_home.join("skills/linting/SKILL.md").as_path())
536537
.unwrap_or_else(|_| cfg.codex_home.join("skills/linting/SKILL.md"));
537538
let expected_path_str = expected_path.to_string_lossy().replace('\\', "/");
539+
let usage_rules = "- Discovery: Available skills are listed in project docs and may also appear in a runtime \"## Skills\" section (name + description + file path). These are the sources of truth; skill bodies live on disk at the listed paths.\n- Trigger rules: If the user names a skill (with `$SkillName` or plain text) OR the task clearly matches a skill's description, you must use that skill for that turn. Multiple mentions mean use them all. Do not carry skills across turns unless re-mentioned.\n- Missing/blocked: If a named skill isn't in the list or the path can't be read, say so briefly and continue with the best fallback.\n- How to use a skill (progressive disclosure):\n 1) After deciding to use a skill, open its `SKILL.md`. Read only enough to follow the workflow.\n 2) If `SKILL.md` points to extra folders such as `references/`, load only the specific files needed for the request; don't bulk-load everything.\n 3) If `scripts/` exist, prefer running or patching them instead of retyping large code blocks.\n 4) If `assets/` or templates exist, reuse them instead of recreating from scratch.\n- Description as trigger: The YAML `description` in `SKILL.md` is the primary trigger signal; rely on it to decide applicability. If unsure, ask a brief clarification before proceeding.\n- Coordination and sequencing:\n - If multiple skills apply, choose the minimal set that covers the request and state the order you'll use them.\n - Announce which skill(s) you're using and why (one short line). If you skip an obvious skill, say why.\n- Context hygiene:\n - Keep context small: summarize long sections instead of pasting them; only load extra files when needed.\n - Avoid deeply nested references; prefer one-hop files explicitly linked from `SKILL.md`.\n - When variants exist (frameworks, providers, domains), pick only the relevant reference file(s) and note that choice.\n- Safety and fallback: If a skill can't be applied cleanly (missing files, unclear instructions), state the issue, pick the next-best approach, and continue.";
538540
let expected = format!(
539-
"## Skills\nThese skills are discovered at startup from ~/.codex/skills; each entry shows name, description, and file path so you can open the source for full instructions. Content is not inlined to keep context lean.\n- linting: run clippy (file: {expected_path_str})"
541+
"## Skills\nThese skills are discovered at startup from ~/.codex/skills; each entry shows name, description, and file path so you can open the source for full instructions. Content is not inlined to keep context lean.\n- linting: run clippy (file: {expected_path_str})\n{usage_rules}"
540542
);
541543
assert_eq!(res, expected);
542544
}

codex-rs/core/src/skills/render.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,26 @@ pub fn render_skills_section(skills: &[SkillMetadata]) -> Option<String> {
1717
));
1818
}
1919

20+
lines.push(
21+
r###"- Discovery: Available skills are listed in project docs and may also appear in a runtime "## Skills" section (name + description + file path). These are the sources of truth; skill bodies live on disk at the listed paths.
22+
- Trigger rules: If the user names a skill (with `$SkillName` or plain text) OR the task clearly matches a skill's description, you must use that skill for that turn. Multiple mentions mean use them all. Do not carry skills across turns unless re-mentioned.
23+
- Missing/blocked: If a named skill isn't in the list or the path can't be read, say so briefly and continue with the best fallback.
24+
- How to use a skill (progressive disclosure):
25+
1) After deciding to use a skill, open its `SKILL.md`. Read only enough to follow the workflow.
26+
2) If `SKILL.md` points to extra folders such as `references/`, load only the specific files needed for the request; don't bulk-load everything.
27+
3) If `scripts/` exist, prefer running or patching them instead of retyping large code blocks.
28+
4) If `assets/` or templates exist, reuse them instead of recreating from scratch.
29+
- Description as trigger: The YAML `description` in `SKILL.md` is the primary trigger signal; rely on it to decide applicability. If unsure, ask a brief clarification before proceeding.
30+
- Coordination and sequencing:
31+
- If multiple skills apply, choose the minimal set that covers the request and state the order you'll use them.
32+
- Announce which skill(s) you're using and why (one short line). If you skip an obvious skill, say why.
33+
- Context hygiene:
34+
- Keep context small: summarize long sections instead of pasting them; only load extra files when needed.
35+
- Avoid deeply nested references; prefer one-hop files explicitly linked from `SKILL.md`.
36+
- When variants exist (frameworks, providers, domains), pick only the relevant reference file(s) and note that choice.
37+
- Safety and fallback: If a skill can't be applied cleanly (missing files, unclear instructions), state the issue, pick the next-best approach, and continue."###
38+
.to_string(),
39+
);
40+
2041
Some(lines.join("\n"))
2142
}

codex-rs/tui/src/app.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ use codex_core::AuthManager;
2525
use codex_core::ConversationManager;
2626
use codex_core::config::Config;
2727
use codex_core::config::edit::ConfigEditsBuilder;
28-
#[cfg(target_os = "windows")]
2928
use codex_core::features::Feature;
3029
use codex_core::model_family::find_family_for_model;
3130
use codex_core::openai_models::model_presets::HIDE_GPT_5_1_CODEX_MAX_MIGRATION_PROMPT_CONFIG;
@@ -37,6 +36,7 @@ use codex_core::protocol::Op;
3736
use codex_core::protocol::SessionSource;
3837
use codex_core::protocol::TokenUsage;
3938
use codex_core::skills::load_skills;
39+
use codex_core::skills::model::SkillMetadata;
4040
use codex_protocol::ConversationId;
4141
use codex_protocol::openai_models::ModelUpgrade;
4242
use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig;
@@ -231,6 +231,8 @@ pub(crate) struct App {
231231

232232
// One-shot suppression of the next world-writable scan after user confirmation.
233233
skip_world_writable_scan_once: bool,
234+
235+
pub(crate) skills: Option<Vec<SkillMetadata>>,
234236
}
235237

236238
impl App {
@@ -285,6 +287,12 @@ impl App {
285287
}
286288
}
287289

290+
let skills = if config.features.enabled(Feature::Skills) {
291+
Some(skills_outcome.skills.clone())
292+
} else {
293+
None
294+
};
295+
288296
let enhanced_keys_supported = tui.enhanced_keys_supported();
289297

290298
let mut chat_widget = match resume_selection {
@@ -298,6 +306,7 @@ impl App {
298306
enhanced_keys_supported,
299307
auth_manager: auth_manager.clone(),
300308
feedback: feedback.clone(),
309+
skills: skills.clone(),
301310
is_first_run,
302311
};
303312
ChatWidget::new(init, conversation_manager.clone())
@@ -322,6 +331,7 @@ impl App {
322331
enhanced_keys_supported,
323332
auth_manager: auth_manager.clone(),
324333
feedback: feedback.clone(),
334+
skills: skills.clone(),
325335
is_first_run,
326336
};
327337
ChatWidget::new_from_existing(
@@ -357,6 +367,7 @@ impl App {
357367
pending_update_action: None,
358368
suppress_shutdown_complete: false,
359369
skip_world_writable_scan_once: false,
370+
skills,
360371
};
361372

362373
// On startup, if Agent mode (workspace-write) or ReadOnly is active, warn about world-writable dirs on Windows.
@@ -476,6 +487,7 @@ impl App {
476487
enhanced_keys_supported: self.enhanced_keys_supported,
477488
auth_manager: self.auth_manager.clone(),
478489
feedback: self.feedback.clone(),
490+
skills: self.skills.clone(),
479491
is_first_run: false,
480492
};
481493
self.chat_widget = ChatWidget::new(init, self.server.clone());
@@ -523,6 +535,7 @@ impl App {
523535
enhanced_keys_supported: self.enhanced_keys_supported,
524536
auth_manager: self.auth_manager.clone(),
525537
feedback: self.feedback.clone(),
538+
skills: self.skills.clone(),
526539
is_first_run: false,
527540
};
528541
self.chat_widget = ChatWidget::new_from_existing(
@@ -1147,6 +1160,7 @@ mod tests {
11471160
pending_update_action: None,
11481161
suppress_shutdown_complete: false,
11491162
skip_world_writable_scan_once: false,
1163+
skills: None,
11501164
}
11511165
}
11521166

@@ -1184,6 +1198,7 @@ mod tests {
11841198
pending_update_action: None,
11851199
suppress_shutdown_complete: false,
11861200
skip_world_writable_scan_once: false,
1201+
skills: None,
11871202
},
11881203
rx,
11891204
op_rx,

codex-rs/tui/src/app_backtrack.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,7 @@ impl App {
347347
enhanced_keys_supported: self.enhanced_keys_supported,
348348
auth_manager: self.auth_manager.clone(),
349349
feedback: self.feedback.clone(),
350+
skills: self.skills.clone(),
350351
is_first_run: false,
351352
};
352353
self.chat_widget =

0 commit comments

Comments
 (0)