Add public skills + improve repo skill discovery and error UX (#8098)

1. Adds SkillScope::Public end-to-end (core + protocol) and loads skills
from the public cache directory
2. Improves repo skill discovery by searching upward for the nearest
.codex/skills within a git repo
3. Deduplicates skills by name with deterministic ordering to avoid
duplicates across sources
4. Fixes garbled “Skill errors” overlay rendering by preventing pending
history lines from being injected during the modal
5. Updates the project docs “Skills” intro wording to avoid hardcoded
paths
This commit is contained in:
xl-openai authored and GitHub committed 2025-12-17 01:35:49 -08:00
1 parent 2041b72da7
commit 4897efcced
20 files changed
+1050 -73

No files matched your search

+10 -1
View File
@@ -410,7 +410,10 @@ impl ChatWidget {
}
// Ask codex-core to enumerate custom prompts for this session.
self.submit_op(Op::ListCustomPrompts);
self.submit_op(Op::ListSkills { cwds: Vec::new() });
self.submit_op(Op::ListSkills {
cwds: Vec::new(),
force_reload: false,
});
if let Some(user_message) = self.initial_user_message.take() {
self.submit_user_message(user_message);
}
@@ -1886,6 +1889,12 @@ impl ChatWidget {
EventMsg::McpListToolsResponse(ev) => self.on_list_mcp_tools(ev),
EventMsg::ListCustomPromptsResponse(ev) => self.on_list_custom_prompts(ev),
EventMsg::ListSkillsResponse(ev) => self.on_list_skills(ev),
EventMsg::SkillsUpdateAvailable => {
self.submit_op(Op::ListSkills {
cwds: Vec::new(),
force_reload: true,
});
}
EventMsg::ShutdownComplete => self.on_shutdown_complete(),
EventMsg::TurnDiff(TurnDiffEvent { unified_diff }) => self.on_turn_diff(unified_diff),
EventMsg::DeprecationNotice(ev) => self.on_deprecation_notice(ev),
+49 -20
View File
@@ -1,6 +1,8 @@
use crate::tui::FrameRequester;
use crate::tui::Tui;
use crate::tui::TuiEvent;
use crate::wrapping::RtOptions;
use crate::wrapping::word_wrap_line;
use codex_core::skills::SkillError;
use crossterm::event::KeyCode;
use crossterm::event::KeyEvent;
@@ -16,7 +18,6 @@ use ratatui::widgets::Clear;
use ratatui::widgets::Paragraph;
use ratatui::widgets::Widget;
use ratatui::widgets::WidgetRef;
use ratatui::widgets::Wrap;
use tokio_stream::StreamExt;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
@@ -31,16 +32,24 @@ pub(crate) async fn run_skill_error_prompt(
) -> SkillErrorPromptOutcome {
struct AltScreenGuard<'a> {
tui: &'a mut Tui,
stashed_history_lines: Vec<Line<'static>>,
}
impl<'a> AltScreenGuard<'a> {
fn enter(tui: &'a mut Tui) -> Self {
let _ = tui.enter_alt_screen();
Self { tui }
let stashed_history_lines = tui.stash_pending_history_lines();
Self {
tui,
stashed_history_lines,
}
}
}
impl Drop for AltScreenGuard<'_> {
fn drop(&mut self) {
let _ = self.tui.leave_alt_screen();
let stashed_history_lines = std::mem::take(&mut self.stashed_history_lines);
self.tui
.restore_pending_history_lines(stashed_history_lines);
}
}
@@ -77,28 +86,16 @@ pub(crate) async fn run_skill_error_prompt(
struct SkillErrorScreen {
request_frame: FrameRequester,
lines: Vec<Line<'static>>,
errors: Vec<SkillError>,
done: bool,
exit: bool,
}
impl SkillErrorScreen {
fn new(request_frame: FrameRequester, errors: &[SkillError]) -> Self {
let mut lines: Vec<Line<'static>> = Vec::new();
lines.push(Line::from("Skill validation errors detected".bold()));
lines.push(Line::from(
"Fix these SKILL.md files and restart. Invalid skills are ignored until resolved. Press enter or esc to continue, Ctrl+C or Ctrl+D to exit.",
));
lines.push(Line::from(""));
for error in errors {
let message = format!("- {}: {}", error.path.display(), error.message);
lines.push(Line::from(message));
}
Self {
request_frame,
lines,
errors: errors.to_vec(),
done: false,
exit: false,
}
@@ -154,12 +151,44 @@ impl SkillErrorScreen {
impl WidgetRef for &SkillErrorScreen {
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
Clear.render(area, buf);
let block = Block::default()
.title("Skill errors".bold())
.borders(Borders::ALL);
Paragraph::new(self.lines.clone())
.block(block)
.wrap(Wrap { trim: true })
.render(area, buf);
let inner = block.inner(area);
let width = usize::from(inner.width).max(1);
let mut base_lines: Vec<Line<'static>> = vec![
Line::from("Skill validation errors detected".bold()),
Line::from("Fix these SKILL.md files and restart."),
Line::from("Invalid skills are ignored until resolved."),
Line::from("Press enter or esc to continue. Ctrl+C or Ctrl+D to exit."),
Line::from(""),
];
let error_start = base_lines.len();
for error in &self.errors {
base_lines.push(Line::from(vec![
error.path.display().to_string().dim(),
": ".into(),
error.message.clone().red(),
]));
}
let error_wrap_opts = RtOptions::new(width)
.initial_indent(Line::from("- "))
.subsequent_indent(Line::from(" "));
let mut lines: Vec<Line<'_>> = Vec::new();
for (idx, line) in base_lines.iter().enumerate() {
if idx < error_start {
lines.extend(word_wrap_line(line, width));
} else {
lines.extend(word_wrap_line(line, error_wrap_opts.clone()));
}
}
Paragraph::new(lines).block(block).render(area, buf);
}
}
+8
View File
@@ -335,6 +335,14 @@ impl Tui {
self.frame_requester().schedule_frame();
}
pub(crate) fn stash_pending_history_lines(&mut self) -> Vec<Line<'static>> {
std::mem::take(&mut self.pending_history_lines)
}
pub(crate) fn restore_pending_history_lines(&mut self, lines: Vec<Line<'static>>) {
self.pending_history_lines = lines;
}
pub fn draw(
&mut self,
height: u16,