feat: add enabled_macros config field at global, role, agent, and session levels
Mirrors the enabled_skills plumbing per plans/custom-commands-design.md §5: - global: Config + AppConfig structs, from_config copy, and the COYOTE_ENABLED_MACROS env-override arm (csv_to_vec parsing) - role: frontmatter via parse_string_or_array (list or csv string), plus the export() mirror so Role::save round-trips the field - agent (non-graph): plain serde on AgentConfig; graph.yaml silently ignores the key (pinned by test, no field on Graph by design) - session: plain serde with csv-or-vec deserializer Empty list/string deserializes to Some([]) (explicit zero), distinct from absent/null (None) — pinned by tests at every level, including the env arm (serial-fenced against the from_config tests, which read the process env via load_envs).
This commit is contained in:
@@ -43,6 +43,8 @@ pub struct AppConfig {
|
||||
#[serde(default, deserialize_with = "super::deserialize_csv_or_vec")]
|
||||
pub enabled_skills: Option<Vec<String>>,
|
||||
pub visible_skills: Option<Vec<String>>,
|
||||
#[serde(default, deserialize_with = "super::deserialize_csv_or_vec")]
|
||||
pub enabled_macros: Option<Vec<String>>,
|
||||
|
||||
pub mcp_server_support: bool,
|
||||
pub mapping_mcp_servers: IndexMap<String, String>,
|
||||
@@ -127,6 +129,7 @@ impl Default for AppConfig {
|
||||
skills_enabled: true,
|
||||
enabled_skills: None,
|
||||
visible_skills: None,
|
||||
enabled_macros: None,
|
||||
|
||||
mcp_server_support: true,
|
||||
mapping_mcp_servers: Default::default(),
|
||||
@@ -211,6 +214,7 @@ impl AppConfig {
|
||||
skills_enabled: config.skills_enabled,
|
||||
enabled_skills: config.enabled_skills,
|
||||
visible_skills: config.visible_skills,
|
||||
enabled_macros: config.enabled_macros,
|
||||
|
||||
mcp_server_support: config.mcp_server_support,
|
||||
mapping_mcp_servers: config.mapping_mcp_servers,
|
||||
@@ -533,6 +537,10 @@ impl AppConfig {
|
||||
self.enabled_skills = v.map(|raw| super::csv_to_vec(&raw));
|
||||
}
|
||||
|
||||
if let Some(v) = super::read_env_value::<String>(&get_env_name("enabled_macros")) {
|
||||
self.enabled_macros = v.map(|raw| super::csv_to_vec(&raw));
|
||||
}
|
||||
|
||||
if let Some(Some(v)) = super::read_env_bool(&get_env_name("mcp_server_support")) {
|
||||
self.mcp_server_support = v;
|
||||
}
|
||||
@@ -769,6 +777,70 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial_test::serial]
|
||||
fn from_config_copies_enabled_macros() {
|
||||
let cfg = Config {
|
||||
model_id: "provider:test".to_string(),
|
||||
enabled_macros: Some(vec!["a".to_string()]),
|
||||
..Config::default()
|
||||
};
|
||||
|
||||
let app = AppConfig::from_config(cfg).unwrap();
|
||||
|
||||
assert_eq!(app.enabled_macros, Some(vec!["a".to_string()]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial_test::serial]
|
||||
fn from_config_preserves_explicit_empty_enabled_macros() {
|
||||
let cfg = Config {
|
||||
model_id: "provider:test".to_string(),
|
||||
enabled_macros: Some(vec![]),
|
||||
..Config::default()
|
||||
};
|
||||
|
||||
let app = AppConfig::from_config(cfg).unwrap();
|
||||
|
||||
assert_eq!(app.enabled_macros, Some(vec![]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial_test::serial]
|
||||
fn load_envs_overrides_enabled_macros() {
|
||||
let env_name = get_env_name("enabled_macros");
|
||||
let prev = std::env::var_os(&env_name);
|
||||
|
||||
let mut app = AppConfig::default();
|
||||
|
||||
unsafe { std::env::set_var(&env_name, "a,b") };
|
||||
app.load_envs();
|
||||
assert_eq!(
|
||||
app.enabled_macros,
|
||||
Some(vec!["a".to_string(), "b".to_string()])
|
||||
);
|
||||
|
||||
unsafe { std::env::set_var(&env_name, "") };
|
||||
app.load_envs();
|
||||
assert_eq!(app.enabled_macros, Some(vec![]));
|
||||
|
||||
unsafe { std::env::set_var(&env_name, "null") };
|
||||
app.load_envs();
|
||||
assert_eq!(app.enabled_macros, None);
|
||||
|
||||
unsafe { std::env::remove_var(&env_name) };
|
||||
app.enabled_macros = Some(vec!["keep".to_string()]);
|
||||
app.load_envs();
|
||||
assert_eq!(app.enabled_macros, Some(vec!["keep".to_string()]));
|
||||
|
||||
unsafe {
|
||||
match prev {
|
||||
Some(v) => std::env::set_var(&env_name, v),
|
||||
None => std::env::remove_var(&env_name),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn editor_returns_configured_value() {
|
||||
let configured = cached_editor()
|
||||
|
||||
Reference in New Issue
Block a user