Skip to main content

microclaw_engine/config/
defaults.rs

1use super::*;
2
3pub(crate) fn default_telegram_bot_token() -> String {
4    String::new()
5}
6
7pub(crate) fn default_bot_username() -> String {
8    String::new()
9}
10
11pub(crate) fn default_llm_provider() -> String {
12    "anthropic".into()
13}
14
15pub(crate) fn default_api_key() -> String {
16    String::new()
17}
18
19pub fn default_model_for_provider_name(provider: &str) -> &'static str {
20    match provider.trim().to_ascii_lowercase().as_str() {
21        "anthropic" => "claude-sonnet-4-5-20250929",
22        "ollama" => "llama3.2",
23        "openai-codex" => "gpt-5.3-codex",
24        _ => "gpt-5.2",
25    }
26}
27
28pub(crate) fn default_llm_user_agent() -> String {
29    crate::http_client::default_llm_user_agent()
30}
31
32pub(crate) fn default_max_tokens() -> u32 {
33    8192
34}
35
36pub(crate) fn default_max_tool_iterations() -> usize {
37    100
38}
39
40pub(crate) fn default_chat_turn_queue_max_pending() -> usize {
41    20
42}
43
44pub(crate) fn default_parallel_tool_max_concurrency() -> usize {
45    8
46}
47
48pub(crate) fn default_compaction_timeout_secs() -> u64 {
49    180
50}
51
52pub(crate) fn default_max_history_messages() -> usize {
53    50
54}
55
56pub(crate) fn default_max_document_size_mb() -> u64 {
57    100
58}
59
60pub(crate) fn default_memory_token_budget() -> usize {
61    1500
62}
63
64pub(crate) fn default_memory_l0_identity_pct() -> usize {
65    20
66}
67
68pub(crate) fn default_memory_l1_essential_pct() -> usize {
69    30
70}
71
72pub(crate) fn default_memory_max_entries_per_chat() -> usize {
73    200
74}
75
76pub(crate) fn default_memory_max_global_entries() -> usize {
77    500
78}
79
80pub(crate) fn default_kg_max_triples_per_chat() -> usize {
81    1000
82}
83
84pub(crate) fn default_tool_result_truncation_threshold_chars() -> usize {
85    4000
86}
87
88pub(crate) fn default_tool_result_truncation_head_chars() -> usize {
89    1500
90}
91
92pub(crate) fn default_tool_result_truncation_tail_chars() -> usize {
93    500
94}
95
96pub(crate) fn default_tool_result_artifact_ttl_hours() -> u64 {
97    24
98}
99
100pub(crate) fn default_memory_recency_half_life_days() -> f64 {
101    30.0
102}
103
104pub(crate) fn default_memory_graph_recall_enabled() -> bool {
105    true
106}
107
108pub(crate) fn default_memory_graph_max_hops() -> usize {
109    2
110}
111
112pub(crate) fn default_memory_graph_max_triples() -> usize {
113    10
114}
115
116pub(crate) fn default_tool_repeat_window() -> usize {
117    10
118}
119
120pub(crate) fn default_tool_repeat_limit() -> usize {
121    3
122}
123
124pub(crate) fn default_anthropic_prompt_cache_enabled() -> bool {
125    true
126}
127
128pub(crate) fn default_anthropic_prompt_cache_ttl() -> String {
129    "5m".to_string()
130}
131
132pub(crate) fn default_checkpoints_enabled() -> bool {
133    false
134}
135
136pub(crate) fn default_skill_archive_after_days() -> u64 {
137    30
138}
139
140pub(crate) fn default_skills_catalog_top_k() -> usize {
141    3
142}
143
144pub(crate) fn default_skill_review_min_tool_calls() -> usize {
145    5
146}
147
148pub(crate) fn default_data_dir() -> String {
149    default_data_root().to_string_lossy().to_string()
150}
151
152pub(crate) fn default_data_root() -> PathBuf {
153    if std::env::var("SNAP").is_ok() {
154        if let Ok(snap_user_common) = std::env::var("SNAP_USER_COMMON") {
155            return PathBuf::from(snap_user_common);
156        }
157    }
158    expand_path("~/.microclaw")
159}
160
161pub(crate) fn default_working_dir() -> String {
162    default_data_root()
163        .join("working_dir")
164        .to_string_lossy()
165        .to_string()
166}
167
168pub(crate) fn default_working_dir_isolation() -> WorkingDirIsolation {
169    WorkingDirIsolation::Chat
170}
171
172pub(crate) fn default_bash_dangerous_patterns() -> Vec<String> {
173    vec![
174        // Destructive recursive deletes against root or wildcards.
175        r"\brm\s+(-[a-zA-Z]*[rfRF][a-zA-Z]*\s+)+(/|\*|~|\$HOME)".into(),
176        // Pipe-to-shell installer pattern.
177        r"\b(curl|wget|fetch)\b[^|]*\|\s*(sudo\s+)?(sh|bash|zsh|fish)\b".into(),
178        // Privilege escalation.
179        r"\bsudo\b".into(),
180        // Disk-overwrite.
181        r"\bdd\s+if=".into(),
182        // Forkbomb.
183        r":\(\)\s*\{\s*:\s*\|\s*:&\s*\}\s*;\s*:".into(),
184        // Filesystem format.
185        r"\bmkfs(\.[a-z0-9]+)?\b".into(),
186        // Recursive chmod/chown on root.
187        r"\bch(mod|own)\s+-R\s+[^/]*\s+/(\s|$)".into(),
188    ]
189}
190
191pub(crate) fn default_high_risk_tool_user_confirmation_required() -> bool {
192    true
193}
194
195pub(crate) fn default_sandbox_image() -> String {
196    "ubuntu:25.10".into()
197}
198
199pub(crate) fn default_sandbox_container_prefix() -> String {
200    "microclaw-sandbox".into()
201}
202
203pub(crate) fn default_timezone() -> String {
204    "auto".into()
205}
206
207pub(crate) fn default_max_session_messages() -> usize {
208    40
209}
210
211pub(crate) fn default_diff_max_lines() -> usize {
212    microclaw_core::diff::DEFAULT_DIFF_MAX_LINES
213}
214
215pub(crate) fn default_model_context_window() -> usize {
216    200_000
217}
218
219pub(crate) fn default_context_pressure_compact_pct() -> usize {
220    85
221}
222
223pub(crate) fn default_file_diffs_in_chat() -> bool {
224    true
225}
226
227pub(crate) fn default_compact_keep_recent() -> usize {
228    20
229}
230
231pub(crate) fn default_tool_timeout_secs() -> u64 {
232    30
233}
234
235pub(crate) fn default_mcp_request_timeout_secs() -> u64 {
236    120
237}
238
239pub(crate) fn default_control_chat_ids() -> Vec<i64> {
240    Vec::new()
241}
242
243pub(crate) fn default_web_enabled() -> bool {
244    true
245}
246
247pub(crate) fn default_web_host() -> String {
248    "127.0.0.1".into()
249}
250
251pub(crate) fn default_web_port() -> u16 {
252    10961
253}
254
255pub(crate) fn default_web_max_inflight_per_session() -> usize {
256    10
257}
258
259pub(crate) fn default_web_max_requests_per_window() -> usize {
260    8
261}
262
263pub(crate) fn default_web_rate_window_seconds() -> u64 {
264    10
265}
266
267pub(crate) fn default_web_run_history_limit() -> usize {
268    512
269}
270
271pub(crate) fn default_web_session_idle_ttl_seconds() -> u64 {
272    300
273}
274
275pub(crate) fn default_allow_group_slash_without_mention() -> bool {
276    false
277}
278
279pub(crate) fn default_model_prices() -> Vec<ModelPrice> {
280    Vec::new()
281}
282
283pub(crate) fn default_reflector_enabled() -> bool {
284    true
285}
286
287pub(crate) fn default_reflector_interval_mins() -> u64 {
288    15
289}
290
291pub(crate) fn default_dlq_replay_enabled() -> bool {
292    true
293}
294
295pub(crate) fn default_dlq_replay_interval_secs() -> u64 {
296    300
297}
298
299pub(crate) fn default_dlq_max_replay_attempts() -> u32 {
300    3
301}
302
303pub(crate) fn default_soul_path() -> Option<String> {
304    None
305}
306
307pub(crate) fn default_souls_dir() -> Option<String> {
308    None
309}
310
311pub(crate) fn default_context_max_chars() -> usize {
312    8000
313}
314
315pub(crate) fn default_user_model_max_chars() -> usize {
316    1500
317}
318
319pub(crate) fn default_voice_provider() -> String {
320    "openai".into()
321}
322
323#[cfg(test)]
324mod tests {
325    use super::*;
326    #[allow(unused_imports)]
327    use crate::config::test_prelude::*;
328
329    #[test]
330    fn test_config_struct_clone_and_debug() {
331        let config = test_config();
332        let cloned = config.clone();
333        assert_eq!(cloned.telegram_bot_token, "tok");
334        assert_eq!(cloned.max_tokens, 8192);
335        assert_eq!(cloned.max_tool_iterations, 100);
336        assert_eq!(cloned.max_history_messages, 50);
337        assert_eq!(cloned.max_document_size_mb, 100);
338        assert_eq!(cloned.memory_token_budget, 1500);
339        assert!(cloned.openai_api_key.is_none());
340        assert_eq!(cloned.timezone, "UTC");
341        assert!(cloned.allowed_groups.is_empty());
342        assert!(cloned.control_chat_ids.is_empty());
343        assert_eq!(cloned.max_session_messages, 40);
344        assert_eq!(cloned.compact_keep_recent, 20);
345        assert_eq!(cloned.default_tool_timeout_secs, 30);
346        assert!(cloned.tool_timeout_overrides.is_empty());
347        assert_eq!(cloned.default_mcp_request_timeout_secs, 120);
348        assert!(cloned.discord_bot_token.is_none());
349        assert!(cloned.discord_allowed_channels.is_empty());
350        let _ = format!("{:?}", config);
351    }
352
353    #[test]
354    fn test_config_yaml_defaults() {
355        let yaml = "telegram_bot_token: tok\nbot_username: bot\napi_key: key\n";
356        let config: Config = serde_yaml::from_str(yaml).unwrap();
357        assert_eq!(config.llm_provider, "anthropic");
358        assert_eq!(config.max_tokens, 8192);
359        assert_eq!(config.max_tool_iterations, 100);
360        assert!(config.data_dir.ends_with(".microclaw"));
361        assert!(std::path::PathBuf::from(&config.working_dir)
362            .ends_with(std::path::Path::new(".microclaw").join("working_dir")));
363        assert_eq!(config.memory_token_budget, 1500);
364        assert!(matches!(
365            config.working_dir_isolation,
366            WorkingDirIsolation::Chat
367        ));
368        assert!(matches!(config.sandbox.mode, SandboxMode::Off));
369        assert_eq!(config.max_document_size_mb, 100);
370        assert_eq!(config.timezone, "auto");
371        assert_eq!(config.default_tool_timeout_secs, 30);
372        assert!(config.tool_timeout_overrides.is_empty());
373        assert_eq!(config.default_mcp_request_timeout_secs, 120);
374        assert!(config.web_fetch_validation.enabled);
375        assert!(config.web_fetch_validation.strict_mode);
376        assert_eq!(config.web_fetch_validation.max_scan_bytes, 100_000);
377        assert!(config.web_fetch_url_validation.enabled);
378        assert_eq!(
379            config.web_fetch_url_validation.allowed_schemes,
380            vec!["https".to_string(), "http".to_string()]
381        );
382        assert!(config.web_fetch_url_validation.allowlist_hosts.is_empty());
383        assert!(config.web_fetch_url_validation.denylist_hosts.is_empty());
384    }
385
386    #[test]
387    fn test_post_deserialize_timeout_defaults_and_overrides() {
388        let mut config = test_config();
389        config.default_tool_timeout_secs = 0;
390        config.default_mcp_request_timeout_secs = 0;
391        config.web_fetch_validation.max_scan_bytes = 0;
392        config.web_fetch_url_validation.allowed_schemes.clear();
393        config.web_fetch_url_validation.allowlist_hosts = vec!["  Example.COM  ".into()];
394        config.web_fetch_url_validation.denylist_hosts = vec![" .Bad.EXAMPLE ".into()];
395        config.tool_timeout_overrides = HashMap::from([
396            ("  bash ".to_string(), 90),
397            ("".to_string(), 5),
398            ("browser".to_string(), 0),
399        ]);
400        config.post_deserialize().unwrap();
401
402        assert_eq!(config.default_tool_timeout_secs, 30);
403        assert_eq!(config.default_mcp_request_timeout_secs, 120);
404        assert_eq!(config.web_fetch_validation.max_scan_bytes, 100_000);
405        assert_eq!(
406            config.web_fetch_url_validation.allowed_schemes,
407            vec!["https".to_string(), "http".to_string()]
408        );
409        assert_eq!(
410            config.web_fetch_url_validation.allowlist_hosts,
411            vec!["example.com".to_string()]
412        );
413        assert_eq!(
414            config.web_fetch_url_validation.denylist_hosts,
415            vec!["bad.example".to_string()]
416        );
417        assert_eq!(config.tool_timeout_overrides.len(), 1);
418        assert_eq!(config.tool_timeout_overrides.get("bash"), Some(&90));
419    }
420
421    #[test]
422    fn test_tool_timeout_lookup_prefers_override_then_default() {
423        let mut config = test_config();
424        config.default_tool_timeout_secs = 45;
425        config.tool_timeout_overrides.insert("bash".to_string(), 75);
426
427        assert_eq!(config.tool_timeout_secs("bash", 120), 75);
428        assert_eq!(config.tool_timeout_secs("browser", 120), 45);
429    }
430}