From ed0060a274624bac32c06c3b8272db34d4961ab6 Mon Sep 17 00:00:00 2001 From: Alex Clarke Date: Fri, 5 Jun 2026 15:11:22 -0600 Subject: [PATCH] fix: disable skills for specific built-in roles --- assets/roles/create-prompt.md | 3 + assets/roles/create-title.md | 3 + assets/roles/explain-shell.md | 3 + assets/roles/shell.md | 3 + src/config/request_context.rs | 130 +++++++++++++++++++++++++++++++++- 5 files changed, 139 insertions(+), 3 deletions(-) diff --git a/assets/roles/create-prompt.md b/assets/roles/create-prompt.md index 06775a6..7dc1927 100644 --- a/assets/roles/create-prompt.md +++ b/assets/roles/create-prompt.md @@ -1,3 +1,6 @@ +--- +skills_enabled: false +--- As a professional Prompt Engineer, your role is to create effective and innovative prompts for interacting with AI models. Your core skills include: diff --git a/assets/roles/create-title.md b/assets/roles/create-title.md index e6f97ff..5f831f6 100644 --- a/assets/roles/create-title.md +++ b/assets/roles/create-title.md @@ -1,3 +1,6 @@ +--- +skills_enabled: false +--- Create a concise, 3-6 word title. **Notes**: diff --git a/assets/roles/explain-shell.md b/assets/roles/explain-shell.md index 90d5df0..093ee23 100644 --- a/assets/roles/explain-shell.md +++ b/assets/roles/explain-shell.md @@ -1,3 +1,6 @@ +--- +skills_enabled: false +--- Provide a terse, single sentence description of the given shell command. Describe each argument and option of the command. Provide short responses in about 80 words. diff --git a/assets/roles/shell.md b/assets/roles/shell.md index f00ce5b..4e044d3 100644 --- a/assets/roles/shell.md +++ b/assets/roles/shell.md @@ -1,3 +1,6 @@ +--- +skills_enabled: false +--- Provide only {{__shell__}} commands for {{__os_distro__}} without any description. Ensure the output is a valid {{__shell__}} command. If there is a lack of details, provide most logical solution. diff --git a/src/config/request_context.rs b/src/config/request_context.rs index 84af488..d068765 100644 --- a/src/config/request_context.rs +++ b/src/config/request_context.rs @@ -652,10 +652,10 @@ impl RequestContext { if should_inject_skill_instructions(app, &policy) { let config = self.skill_instructions_config(); - + if config.inject { let separator = if role.is_empty_prompt() { "" } else { "\n\n" }; - + role.append_to_prompt(separator); role.append_to_prompt( config @@ -1275,7 +1275,8 @@ impl RequestContext { .iter() .filter(|v| { (v.name.starts_with(USER_FUNCTION_PREFIX) - || v.name.starts_with(SKILL_FUNCTION_PREFIX)) + || (!matches!(role.skills_enabled(), Some(false)) + && v.name.starts_with(SKILL_FUNCTION_PREFIX))) && !existing.contains(&v.name) }) .cloned() @@ -3641,6 +3642,129 @@ mod tests { assert!(!names.contains(&"todo__done")); } + #[test] + fn select_functions_re_adds_skill_tools_when_role_skills_enabled_unset() { + let mut ctx = create_test_ctx(); + ctx.tool_scope.functions.append_skill_functions(); + + let mut role = Role::new("r", "p"); + role.set_enabled_tools(Some(vec!["foo".to_string()])); + + let fns = ctx.select_functions(&role).unwrap(); + let names: Vec<&str> = fns.iter().map(|f| f.name.as_str()).collect(); + assert!(names.contains(&"skill__list")); + assert!(names.contains(&"skill__load")); + assert!(names.contains(&"skill__unload")); + } + + #[test] + fn select_functions_suppresses_skill_tools_when_role_skills_enabled_false() { + let mut ctx = create_test_ctx(); + ctx.tool_scope.functions.append_skill_functions(); + + let mut role = Role::new("r", "---\nskills_enabled: false\n---\np"); + role.set_enabled_tools(Some(vec!["foo".to_string()])); + + let fns = ctx.select_functions(&role).unwrap(); + let names: Vec<&str> = fns.iter().map(|f| f.name.as_str()).collect(); + assert!(!names.contains(&"skill__list")); + assert!(!names.contains(&"skill__load")); + assert!(!names.contains(&"skill__unload")); + } + + #[test] + fn select_functions_still_re_adds_user_tools_when_role_skills_enabled_false() { + let mut ctx = create_test_ctx(); + ctx.tool_scope.functions.append_user_interaction_functions(); + ctx.tool_scope.functions.append_skill_functions(); + + let mut role = Role::new("r", "---\nskills_enabled: false\n---\np"); + role.set_enabled_tools(Some(vec!["foo".to_string()])); + + let fns = ctx.select_functions(&role).unwrap(); + let names: Vec<&str> = fns.iter().map(|f| f.name.as_str()).collect(); + assert!(names.contains(&"user__ask")); + assert!(!names.contains(&"skill__list")); + } + + #[test] + #[serial] + fn select_functions_re_adds_skill_tools_when_agent_skills_enabled_not_false() { + let _guard = TestConfigDirGuard::new(); + let mut ctx = create_test_ctx(); + let app = ctx.app.config.clone(); + let agent_name = format!( + "test_skill_agent_{}", + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_nanos() + ); + let agent_dir = paths::agent_data_dir(&agent_name); + create_dir_all(&agent_dir).unwrap(); + write( + agent_dir.join("graph.yaml"), + format!( + "name: {agent_name}\nversion: \"1.0\"\nstart: done\nnodes:\n done:\n type: end\n output: ok\n" + ), + ) + .unwrap(); + + let abort = utils::create_abort_signal(); + run_async(ctx.use_agent(&app, &agent_name, None, abort)).unwrap(); + ctx.tool_scope.functions.append_skill_functions(); + + let mut role = Role::new("r", "p"); + role.set_enabled_tools(Some(vec!["foo".to_string()])); + + let fns = ctx.select_functions(&role).unwrap(); + let names: Vec<&str> = fns.iter().map(|f| f.name.as_str()).collect(); + assert!(names.contains(&"skill__list")); + assert!(names.contains(&"skill__load")); + assert!(names.contains(&"skill__unload")); + } + + #[test] + #[serial] + fn select_functions_suppresses_skill_tools_when_agent_skills_enabled_false() { + let _guard = TestConfigDirGuard::new(); + let mut ctx = create_test_ctx(); + let app = ctx.app.config.clone(); + let agent_name = format!( + "test_skill_agent_off_{}", + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_nanos() + ); + let agent_dir = paths::agent_data_dir(&agent_name); + create_dir_all(&agent_dir).unwrap(); + write( + agent_dir.join("graph.yaml"), + format!( + "name: {agent_name}\nversion: \"1.0\"\nstart: done\nnodes:\n done:\n type: end\n output: ok\n" + ), + ) + .unwrap(); + + let abort = utils::create_abort_signal(); + run_async(ctx.use_agent(&app, &agent_name, None, abort)).unwrap(); + ctx.agent + .as_mut() + .expect("agent loaded") + .set_skills_enabled(Some(false)); + ctx.tool_scope.functions.append_skill_functions(); + + let mut role = Role::new("r", "p"); + role.set_enabled_tools(Some(vec!["foo".to_string()])); + + let fns = ctx.select_functions(&role).unwrap(); + let names: Vec<&str> = fns.iter().map(|f| f.name.as_str()).collect(); + assert!(!names.contains(&"skill__list")); + assert!(!names.contains(&"skill__load")); + assert!(!names.contains(&"skill__unload")); + } + #[test] fn select_enabled_mcp_servers_returns_empty_when_mcp_disabled() { let app_state = {