fix: disable skills for specific built-in roles

This commit is contained in:
2026-06-05 15:11:22 -06:00
parent 165d0d113d
commit 46685cb641
5 changed files with 139 additions and 3 deletions
+127 -3
View File
@@ -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 = {