Skip to main content

microclaw_engine/config/
integrations.rs

1use super::*;
2
3pub(crate) fn default_rtk_binary_path() -> String {
4    "rtk".to_string()
5}
6
7/// Opt-in RTK (Rust Token Killer) integration for the bash tool.
8/// When enabled, commands are passed through `rtk rewrite` before execution;
9/// commands RTK recognizes run as `rtk <command>` and return compressed
10/// output, reducing token consumption. Commands without an RTK equivalent
11/// (or any rtk failure) run unchanged.
12#[derive(Clone, Debug, Serialize, Deserialize)]
13pub struct RtkConfig {
14    #[serde(default)]
15    pub enabled: bool,
16    /// Path to the rtk binary. Defaults to `rtk` resolved from PATH.
17    #[serde(default = "default_rtk_binary_path")]
18    pub binary_path: String,
19}
20
21impl Default for RtkConfig {
22    fn default() -> Self {
23        Self {
24            enabled: false,
25            binary_path: default_rtk_binary_path(),
26        }
27    }
28}
29
30pub(crate) fn default_a2a_enabled() -> bool {
31    false
32}
33
34pub(crate) fn default_clawhub_registry() -> String {
35    "https://clawhub.ai".into()
36}
37
38pub(crate) fn default_true() -> bool {
39    true
40}
41
42/// Load-time verification policy for ClawHub-managed skill trees.
43///
44/// `block` (default) makes a skill whose on-disk tree no longer matches the
45/// lockfile hash unavailable until it is reinstalled; `warn` only logs;
46/// `off` disables the check. Entries installed before tree hashing existed
47/// (no recorded hash) always warn instead of blocking.
48#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
49#[serde(rename_all = "lowercase")]
50pub enum ClawHubVerifyMode {
51    Off,
52    Warn,
53    #[default]
54    Block,
55}
56
57#[derive(Clone, Debug, Serialize, Deserialize)]
58pub struct ClawHubConfig {
59    /// ClawHub registry URL
60    #[serde(default = "default_clawhub_registry", rename = "clawhub_registry")]
61    pub registry: String,
62    /// ClawHub API token (optional)
63    #[serde(default, rename = "clawhub_token")]
64    pub token: Option<String>,
65    /// Enable agent tools for ClawHub (search, install)
66    #[serde(default = "default_true", rename = "clawhub_agent_tools_enabled")]
67    pub agent_tools_enabled: bool,
68    /// Skip security warnings for ClawHub installs
69    #[serde(default, rename = "clawhub_skip_security_warnings")]
70    pub skip_security_warnings: bool,
71    /// Verify installed skill trees against the lockfile whenever skills load
72    #[serde(default, rename = "clawhub_verify_on_load")]
73    pub verify_on_load: ClawHubVerifyMode,
74}
75
76impl Default for ClawHubConfig {
77    fn default() -> Self {
78        Self {
79            registry: default_clawhub_registry(),
80            token: None,
81            agent_tools_enabled: default_true(),
82            skip_security_warnings: false,
83            verify_on_load: ClawHubVerifyMode::default(),
84        }
85    }
86}
87
88/// Named per-peer trust tier for outbound A2A sends. `limited` (default)
89/// keeps the historical behavior; `sandboxed` treats a send to this peer
90/// as high-risk — on web/control chats it requires the explicit approval
91/// flow before the message leaves. `trusted` is currently equivalent to
92/// `limited` and reserved for future capability widening; tiers never
93/// lower existing guardrails.
94#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
95#[serde(rename_all = "lowercase")]
96pub enum A2ATrust {
97    Trusted,
98    #[default]
99    Limited,
100    Sandboxed,
101}
102
103#[derive(Clone, Debug, Serialize, Deserialize, Default)]
104pub struct A2APeerConfig {
105    #[serde(default = "default_true")]
106    pub enabled: bool,
107    #[serde(default)]
108    pub base_url: String,
109    #[serde(default)]
110    pub bearer_token: Option<String>,
111    #[serde(default)]
112    pub description: Option<String>,
113    #[serde(default)]
114    pub default_session_key: Option<String>,
115    /// Trust tier for sends to this peer. Default: limited.
116    #[serde(default)]
117    pub trust: A2ATrust,
118}
119
120#[derive(Clone, Debug, Serialize, Deserialize)]
121pub struct A2AConfig {
122    #[serde(default = "default_a2a_enabled")]
123    pub enabled: bool,
124    #[serde(default)]
125    pub public_base_url: Option<String>,
126    #[serde(default)]
127    pub agent_name: Option<String>,
128    #[serde(default)]
129    pub agent_description: Option<String>,
130    #[serde(default)]
131    pub shared_tokens: Vec<String>,
132    #[serde(default)]
133    pub peers: HashMap<String, A2APeerConfig>,
134}
135
136impl Default for A2AConfig {
137    fn default() -> Self {
138        Self {
139            enabled: default_a2a_enabled(),
140            public_base_url: None,
141            agent_name: None,
142            agent_description: None,
143            shared_tokens: Vec::new(),
144            peers: HashMap::new(),
145        }
146    }
147}
148
149#[cfg(test)]
150mod tests {
151    use super::*;
152    #[allow(unused_imports)]
153    use crate::config::test_prelude::*;
154
155    #[test]
156    fn test_post_deserialize_normalizes_a2a_config() {
157        let mut config = Config::test_defaults();
158        config.a2a.enabled = true;
159        config.a2a.public_base_url = Some(" https://mc.example.com/ ".into());
160        config.a2a.agent_name = Some(" Planner ".into());
161        config.a2a.agent_description = Some(" Plans ".into());
162        config.a2a.shared_tokens = vec!["  ".into(), " secret ".into()];
163        config.a2a.peers.insert(
164            " Worker ".into(),
165            A2APeerConfig {
166                enabled: true,
167                base_url: " https://worker.example.com/ ".into(),
168                bearer_token: Some(" token ".into()),
169                description: Some(" executes ".into()),
170                default_session_key: Some(" team/work ".into()),
171                trust: Default::default(),
172            },
173        );
174
175        config.post_deserialize().unwrap();
176
177        assert_eq!(
178            config.a2a.public_base_url.as_deref(),
179            Some("https://mc.example.com")
180        );
181        assert_eq!(config.a2a.agent_name.as_deref(), Some("Planner"));
182        assert_eq!(config.a2a.agent_description.as_deref(), Some("Plans"));
183        assert_eq!(config.a2a.shared_tokens, vec!["secret".to_string()]);
184        let peer = config.a2a.peers.get("worker").unwrap();
185        assert_eq!(peer.base_url, "https://worker.example.com");
186        assert_eq!(peer.bearer_token.as_deref(), Some("token"));
187        assert_eq!(peer.description.as_deref(), Some("executes"));
188        assert_eq!(peer.default_session_key.as_deref(), Some("team/work"));
189    }
190}