From f44eb29181ad5a3425f5d187558064a2fa56a22a Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Mon, 6 Apr 2026 08:51:34 -0700 Subject: [PATCH] Annotate skill doc reads with skill names (#16813) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses #16303 Problem: Skill doc reads render as plain `Read SKILL.md`, so the TUI hides which skill was opened. Solution: Best-effort annotate exact `SKILL.md` reads with the matching loaded skill name from `skills_all` before rendering exec cells. Before: ``` • Explored └ Read SKILL.md ``` After: ``` • Explored └ Read SKILL.md (pr-babysitter skill) ``` --- codex-rs/tui/src/chatwidget.rs | 8 +++++--- codex-rs/tui/src/chatwidget/skills.rs | 26 ++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index 454c9053b..34c0df7b4 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -4206,6 +4206,7 @@ impl ChatWidget { Some(rc) => (rc.command, rc.parsed_cmd, rc.source), None => (ev.command.clone(), ev.parsed_cmd.clone(), ev.source), }; + let parsed = self.annotate_skill_reads_in_parsed_cmd(parsed); let is_unified_exec_interaction = matches!(source, ExecCommandSource::UnifiedExecInteraction); let end_target = match self.active_cell.as_ref() { @@ -4425,11 +4426,12 @@ impl ChatWidget { pub(crate) fn handle_exec_begin_now(&mut self, ev: ExecCommandBeginEvent) { // Ensure the status indicator is visible while the command runs. self.bottom_pane.ensure_status_indicator(); + let parsed_cmd = self.annotate_skill_reads_in_parsed_cmd(ev.parsed_cmd.clone()); self.running_commands.insert( ev.call_id.clone(), RunningCommand { command: ev.command.clone(), - parsed_cmd: ev.parsed_cmd.clone(), + parsed_cmd: parsed_cmd.clone(), source: ev.source, }, ); @@ -4462,7 +4464,7 @@ impl ChatWidget { && let Some(new_exec) = cell.with_added_call( ev.call_id.clone(), ev.command.clone(), - ev.parsed_cmd.clone(), + parsed_cmd.clone(), ev.source, interaction_input.clone(), ) @@ -4475,7 +4477,7 @@ impl ChatWidget { self.active_cell = Some(Box::new(new_active_exec_command( ev.call_id.clone(), ev.command.clone(), - ev.parsed_cmd, + parsed_cmd, ev.source, interaction_input, self.config.animations, diff --git a/codex-rs/tui/src/chatwidget/skills.rs b/codex-rs/tui/src/chatwidget/skills.rs index 6e161087c..53dd7e8b8 100644 --- a/codex-rs/tui/src/chatwidget/skills.rs +++ b/codex-rs/tui/src/chatwidget/skills.rs @@ -19,6 +19,7 @@ use codex_core::skills::model::SkillDependencies; use codex_core::skills::model::SkillInterface; use codex_core::skills::model::SkillMetadata; use codex_core::skills::model::SkillToolDependency; +use codex_protocol::parse_command::ParsedCommand; use codex_protocol::protocol::ListSkillsResponseEvent; use codex_protocol::protocol::SkillMetadata as ProtocolSkillMetadata; use codex_protocol::protocol::SkillsListEntry; @@ -142,6 +143,31 @@ impl ChatWidget { self.skills_all = skills; self.set_skills(Some(enabled_skills_for_mentions(&self.skills_all))); } + + pub(crate) fn annotate_skill_reads_in_parsed_cmd( + &self, + mut parsed_cmd: Vec, + ) -> Vec { + if self.skills_all.is_empty() { + return parsed_cmd; + } + + for parsed in &mut parsed_cmd { + let ParsedCommand::Read { name, path, .. } = parsed else { + continue; + }; + if name != "SKILL.md" { + continue; + } + + // Best effort only: annotate exact SKILL.md path matches from the loaded skills list. + if let Some(skill) = self.skills_all.iter().find(|skill| skill.path == *path) { + *name = format!("{name} ({} skill)", skill.name); + } + } + + parsed_cmd + } } fn skills_for_cwd(cwd: &Path, skills_entries: &[SkillsListEntry]) -> Vec {