@@ -100,6 +100,9 @@ use crate::protocol::ReviewDecision;
100100use crate :: protocol:: SandboxCommandAssessment ;
101101use crate :: protocol:: SandboxPolicy ;
102102use crate :: protocol:: SessionConfiguredEvent ;
103+ use crate :: protocol:: SkillErrorInfo ;
104+ use crate :: protocol:: SkillInfo ;
105+ use crate :: protocol:: SkillLoadOutcomeInfo ;
103106use crate :: protocol:: StreamErrorEvent ;
104107use crate :: protocol:: Submission ;
105108use crate :: protocol:: TokenCountEvent ;
@@ -111,9 +114,10 @@ use crate::rollout::RolloutRecorder;
111114use crate :: rollout:: RolloutRecorderParams ;
112115use crate :: rollout:: map_session_init_error;
113116use crate :: shell;
117+ use crate :: shell_snapshot:: ShellSnapshot ;
118+ use crate :: skills:: SkillLoadOutcome ;
114119use crate :: skills:: SkillMetadata ;
115120use crate :: skills:: load_skills;
116- use crate :: shell_snapshot:: ShellSnapshot ;
117121use crate :: state:: ActiveTurn ;
118122use crate :: state:: SessionServices ;
119123use crate :: state:: SessionState ;
@@ -195,9 +199,18 @@ impl Codex {
195199 }
196200 }
197201
198- let user_instructions =
199- get_user_instructions ( & config, loaded_skills. as_ref ( ) . map ( |o| o. skills . as_slice ( ) ) )
200- . await ;
202+ let skills_outcome = loaded_skills. clone ( ) ;
203+
204+ let user_instructions = get_user_instructions (
205+ & config,
206+ skills_outcome. as_ref ( ) . and_then ( |outcome| {
207+ outcome
208+ . errors
209+ . is_empty ( )
210+ . then_some ( outcome. skills . as_slice ( ) )
211+ } ) ,
212+ )
213+ . await ;
201214
202215 let exec_policy = load_exec_policy_for_features ( & config. features , & config. codex_home )
203216 . await
@@ -225,7 +238,6 @@ impl Codex {
225238
226239 // Generate a unique ID for the lifetime of this Codex session.
227240 let session_source_clone = session_configuration. session_source . clone ( ) ;
228- let skills_cache = loaded_skills. as_ref ( ) . map ( |o| o. skills . clone ( ) ) ;
229241
230242 let session = Session :: new (
231243 session_configuration,
@@ -235,7 +247,7 @@ impl Codex {
235247 tx_event. clone ( ) ,
236248 conversation_history,
237249 session_source_clone,
238- skills_cache ,
250+ skills_outcome . clone ( ) ,
239251 )
240252 . await
241253 . map_err ( |e| {
@@ -501,7 +513,7 @@ impl Session {
501513 tx_event : Sender < Event > ,
502514 initial_history : InitialHistory ,
503515 session_source : SessionSource ,
504- loaded_skills : Option < Vec < SkillMetadata > > ,
516+ skills : Option < SkillLoadOutcome > ,
505517 ) -> anyhow:: Result < Arc < Self > > {
506518 debug ! (
507519 "Configuring session: model={}; provider={:?}" ,
@@ -608,7 +620,7 @@ impl Session {
608620 . await
609621 . map ( Arc :: new) ;
610622 }
611- let state = SessionState :: new ( session_configuration. clone ( ) , loaded_skills ) ;
623+ let state = SessionState :: new ( session_configuration. clone ( ) , skills . clone ( ) ) ;
612624
613625 let services = SessionServices {
614626 mcp_connection_manager : Arc :: new ( RwLock :: new ( McpConnectionManager :: default ( ) ) ) ,
@@ -637,6 +649,7 @@ impl Session {
637649 // Dispatch the SessionConfiguredEvent first and then report any errors.
638650 // If resuming, include converted initial messages in the payload so UIs can render them immediately.
639651 let initial_messages = initial_history. get_event_msgs ( ) ;
652+ let skill_load_outcome = skill_load_outcome_for_client ( skills. as_ref ( ) ) ;
640653
641654 let events = std:: iter:: once ( Event {
642655 id : INITIAL_SUBMIT_ID . to_owned ( ) ,
@@ -651,6 +664,7 @@ impl Session {
651664 history_log_id,
652665 history_entry_count,
653666 initial_messages,
667+ skill_load_outcome,
654668 rollout_path,
655669 } ) ,
656670 } )
@@ -1341,7 +1355,11 @@ impl Session {
13411355
13421356 let skills = {
13431357 let state = self . state . lock ( ) . await ;
1344- state. skills . clone ( ) . unwrap_or_default ( )
1358+ state
1359+ . skills
1360+ . as_ref ( )
1361+ . map ( |outcome| outcome. skills . clone ( ) )
1362+ . unwrap_or_default ( )
13451363 } ;
13461364
13471365 let mentioned_skills = collect_explicit_skill_mentions ( user_input, & skills) ;
@@ -2093,30 +2111,43 @@ fn collect_explicit_skill_mentions(
20932111 inputs : & [ UserInput ] ,
20942112 skills : & [ SkillMetadata ] ,
20952113) -> Vec < SkillMetadata > {
2096- let mut full_text: Vec < String > = Vec :: new ( ) ;
2097- for input in inputs {
2098- if let UserInput :: Text { text } = input {
2099- full_text. push ( text. clone ( ) ) ;
2100- }
2101- }
2102- let combined = full_text. join ( " " ) ;
2114+ let mut selected: Vec < SkillMetadata > = Vec :: new ( ) ;
21032115 let mut seen: HashSet < String > = HashSet :: new ( ) ;
2104- let mut matches: Vec < SkillMetadata > = Vec :: new ( ) ;
21052116
2106- for skill in skills {
2107- let name = skill. name . clone ( ) ;
2108- if seen. contains ( & name) {
2109- continue ;
2110- }
2111- let needle = format ! ( "${name}" ) ;
2112- let hit = combined. contains ( & needle) ;
2113- if hit {
2114- seen. insert ( name) ;
2115- matches. push ( skill. clone ( ) ) ;
2117+ for input in inputs {
2118+ if let UserInput :: Skill { name, path } = input
2119+ && seen. insert ( name. clone ( ) )
2120+ && let Some ( skill) = skills. iter ( ) . find ( |s| s. name == * name && s. path == * path)
2121+ {
2122+ selected. push ( skill. clone ( ) ) ;
21162123 }
21172124 }
21182125
2119- matches
2126+ selected
2127+ }
2128+
2129+ fn skill_load_outcome_for_client (
2130+ outcome : Option < & SkillLoadOutcome > ,
2131+ ) -> Option < SkillLoadOutcomeInfo > {
2132+ outcome. map ( |outcome| SkillLoadOutcomeInfo {
2133+ skills : outcome
2134+ . skills
2135+ . iter ( )
2136+ . map ( |skill| SkillInfo {
2137+ name : skill. name . clone ( ) ,
2138+ description : skill. description . clone ( ) ,
2139+ path : skill. path . clone ( ) ,
2140+ } )
2141+ . collect ( ) ,
2142+ errors : outcome
2143+ . errors
2144+ . iter ( )
2145+ . map ( |err| SkillErrorInfo {
2146+ path : err. path . clone ( ) ,
2147+ message : err. message . clone ( ) ,
2148+ } )
2149+ . collect ( ) ,
2150+ } )
21202151}
21212152
21222153/// Takes a user message as input and runs a loop where, at each turn, the model
0 commit comments