11use std:: collections:: HashMap ;
2- use std:: collections:: HashSet ;
32use std:: fmt:: Debug ;
4- use std:: path:: Path ;
53use std:: path:: PathBuf ;
64use std:: sync:: Arc ;
75use std:: sync:: atomic:: AtomicU64 ;
@@ -57,7 +55,6 @@ use mcp_types::ReadResourceResult;
5755use mcp_types:: RequestId ;
5856use serde_json;
5957use serde_json:: Value ;
60- use tokio:: fs;
6158use tokio:: sync:: Mutex ;
6259use tokio:: sync:: RwLock ;
6360use tokio:: sync:: oneshot;
@@ -116,8 +113,9 @@ use crate::rollout::RolloutRecorderParams;
116113use crate :: rollout:: map_session_init_error;
117114use crate :: shell;
118115use crate :: shell_snapshot:: ShellSnapshot ;
116+ use crate :: skills:: SkillInjections ;
119117use crate :: skills:: SkillLoadOutcome ;
120- use crate :: skills:: SkillMetadata ;
118+ use crate :: skills:: build_skill_injections ;
121119use crate :: skills:: load_skills;
122120use crate :: state:: ActiveTurn ;
123121use crate :: state:: SessionServices ;
@@ -135,7 +133,6 @@ use crate::tools::spec::ToolsConfigParams;
135133use crate :: turn_diff_tracker:: TurnDiffTracker ;
136134use crate :: unified_exec:: UnifiedExecSessionManager ;
137135use crate :: user_instructions:: DeveloperInstructions ;
138- use crate :: user_instructions:: SkillInstructions ;
139136use crate :: user_instructions:: UserInstructions ;
140137use crate :: user_notification:: UserNotification ;
141138use crate :: util:: backoff;
@@ -621,7 +618,7 @@ impl Session {
621618 . await
622619 . map ( Arc :: new) ;
623620 }
624- let state = SessionState :: new ( session_configuration. clone ( ) , skills . clone ( ) ) ;
621+ let state = SessionState :: new ( session_configuration. clone ( ) ) ;
625622
626623 let services = SessionServices {
627624 mcp_connection_manager : Arc :: new ( RwLock :: new ( McpConnectionManager :: default ( ) ) ) ,
@@ -635,6 +632,7 @@ impl Session {
635632 otel_event_manager,
636633 models_manager : Arc :: clone ( & models_manager) ,
637634 tool_approvals : Mutex :: new ( ApprovalStore :: default ( ) ) ,
635+ skills : skills. clone ( ) ,
638636 } ;
639637
640638 let sess = Arc :: new ( Session {
@@ -1345,54 +1343,6 @@ impl Session {
13451343 }
13461344 }
13471345
1348- async fn inject_skills (
1349- & self ,
1350- turn_context : & TurnContext ,
1351- user_input : & [ UserInput ] ,
1352- ) -> Vec < ResponseItem > {
1353- if user_input. is_empty ( ) || !self . enabled ( Feature :: Skills ) {
1354- return Vec :: new ( ) ;
1355- }
1356-
1357- let skills = {
1358- let state = self . state . lock ( ) . await ;
1359- state
1360- . skills
1361- . as_ref ( )
1362- . map ( |outcome| outcome. skills . clone ( ) )
1363- . unwrap_or_default ( )
1364- } ;
1365-
1366- let mentioned_skills = collect_explicit_skill_mentions ( user_input, & skills) ;
1367- if mentioned_skills. is_empty ( ) {
1368- return Vec :: new ( ) ;
1369- }
1370-
1371- let mut injections: Vec < ResponseItem > = Vec :: with_capacity ( mentioned_skills. len ( ) ) ;
1372- for skill in mentioned_skills {
1373- match fs:: read_to_string ( & skill. path ) . await {
1374- Ok ( contents) => {
1375- injections. push ( ResponseItem :: from ( SkillInstructions {
1376- name : skill. name ,
1377- path : skill. path . to_string_lossy ( ) . into_owned ( ) ,
1378- contents,
1379- } ) ) ;
1380- }
1381- Err ( err) => {
1382- let message = format ! (
1383- "Failed to load skill {} at {}: {err:#}" ,
1384- skill. name,
1385- skill. path. display( )
1386- ) ;
1387- self . send_event ( turn_context, EventMsg :: Warning ( WarningEvent { message } ) )
1388- . await ;
1389- }
1390- }
1391- }
1392-
1393- injections
1394- }
1395-
13961346 pub ( crate ) async fn notify_background_event (
13971347 & self ,
13981348 turn_context : & TurnContext ,
@@ -2108,42 +2058,6 @@ async fn spawn_review_thread(
21082058 . await ;
21092059}
21102060
2111- fn collect_explicit_skill_mentions (
2112- inputs : & [ UserInput ] ,
2113- skills : & [ SkillMetadata ] ,
2114- ) -> Vec < SkillMetadata > {
2115- let mut selected: Vec < SkillMetadata > = Vec :: new ( ) ;
2116- let mut seen: HashSet < String > = HashSet :: new ( ) ;
2117-
2118- for input in inputs {
2119- if let UserInput :: Skill { name, path } = input
2120- && seen. insert ( name. clone ( ) )
2121- && let Some ( skill) = skills
2122- . iter ( )
2123- . find ( |s| s. name == * name && paths_match ( & s. path , path) )
2124- {
2125- selected. push ( skill. clone ( ) ) ;
2126- }
2127- }
2128-
2129- selected
2130- }
2131-
2132- fn paths_match ( a : & Path , b : & Path ) -> bool {
2133- if a == b {
2134- return true ;
2135- }
2136-
2137- let Ok ( ca) = std:: fs:: canonicalize ( a) else {
2138- return false ;
2139- } ;
2140- let Ok ( cb) = std:: fs:: canonicalize ( b) else {
2141- return false ;
2142- } ;
2143-
2144- ca == cb
2145- }
2146-
21472061fn skill_load_outcome_for_client (
21482062 outcome : Option < & SkillLoadOutcome > ,
21492063) -> Option < SkillLoadOutcomeInfo > {
@@ -2196,16 +2110,21 @@ pub(crate) async fn run_task(
21962110 } ) ;
21972111 sess. send_event ( & turn_context, event) . await ;
21982112
2199- let skill_injections = sess. inject_skills ( & turn_context, & input) . await ;
2113+ let SkillInjections { items, warnings } =
2114+ build_skill_injections ( & input, sess. services . skills . as_ref ( ) ) . await ;
2115+
2116+ for message in warnings {
2117+ sess. send_event ( & turn_context, EventMsg :: Warning ( WarningEvent { message } ) )
2118+ . await ;
2119+ }
22002120
22012121 let initial_input_for_turn: ResponseInputItem = ResponseInputItem :: from ( input) ;
22022122 let response_item: ResponseItem = initial_input_for_turn. clone ( ) . into ( ) ;
22032123 sess. record_response_item_and_emit_turn_item ( turn_context. as_ref ( ) , response_item)
22042124 . await ;
22052125
2206- if !skill_injections. is_empty ( ) {
2207- sess. record_conversation_items ( & turn_context, & skill_injections)
2208- . await ;
2126+ if !items. is_empty ( ) {
2127+ sess. record_conversation_items ( & turn_context, & items) . await ;
22092128 }
22102129
22112130 sess. maybe_start_ghost_snapshot ( Arc :: clone ( & turn_context) , cancellation_token. child_token ( ) )
@@ -2761,7 +2680,7 @@ mod tests {
27612680 session_source : SessionSource :: Exec ,
27622681 } ;
27632682
2764- let mut state = SessionState :: new ( session_configuration, None ) ;
2683+ let mut state = SessionState :: new ( session_configuration) ;
27652684 let initial = RateLimitSnapshot {
27662685 primary : Some ( RateLimitWindow {
27672686 used_percent : 10.0 ,
@@ -2832,7 +2751,7 @@ mod tests {
28322751 session_source : SessionSource :: Exec ,
28332752 } ;
28342753
2835- let mut state = SessionState :: new ( session_configuration, None ) ;
2754+ let mut state = SessionState :: new ( session_configuration) ;
28362755 let initial = RateLimitSnapshot {
28372756 primary : Some ( RateLimitWindow {
28382757 used_percent : 15.0 ,
@@ -3038,7 +2957,7 @@ mod tests {
30382957 let otel_event_manager =
30392958 otel_event_manager ( conversation_id, config. as_ref ( ) , & model_family) ;
30402959
3041- let state = SessionState :: new ( session_configuration. clone ( ) , None ) ;
2960+ let state = SessionState :: new ( session_configuration. clone ( ) ) ;
30422961
30432962 let services = SessionServices {
30442963 mcp_connection_manager : Arc :: new ( RwLock :: new ( McpConnectionManager :: default ( ) ) ) ,
@@ -3052,6 +2971,7 @@ mod tests {
30522971 otel_event_manager : otel_event_manager. clone ( ) ,
30532972 models_manager,
30542973 tool_approvals : Mutex :: new ( ApprovalStore :: default ( ) ) ,
2974+ skills : None ,
30552975 } ;
30562976
30572977 let turn_context = Session :: make_turn_context (
@@ -3120,7 +3040,7 @@ mod tests {
31203040 let otel_event_manager =
31213041 otel_event_manager ( conversation_id, config. as_ref ( ) , & model_family) ;
31223042
3123- let state = SessionState :: new ( session_configuration. clone ( ) , None ) ;
3043+ let state = SessionState :: new ( session_configuration. clone ( ) ) ;
31243044
31253045 let services = SessionServices {
31263046 mcp_connection_manager : Arc :: new ( RwLock :: new ( McpConnectionManager :: default ( ) ) ) ,
@@ -3134,6 +3054,7 @@ mod tests {
31343054 otel_event_manager : otel_event_manager. clone ( ) ,
31353055 models_manager,
31363056 tool_approvals : Mutex :: new ( ApprovalStore :: default ( ) ) ,
3057+ skills : None ,
31373058 } ;
31383059
31393060 let turn_context = Arc :: new ( Session :: make_turn_context (
0 commit comments