Skip to main content

roboticus_core/config/
agent_paths.rs

1#[derive(Debug, Clone, Serialize, Deserialize)]
2pub struct AgentConfig {
3    pub name: String,
4    pub id: String,
5    #[serde(default = "default_workspace")]
6    pub workspace: PathBuf,
7    #[serde(default = "default_log_level")]
8    pub log_level: String,
9    #[serde(default = "default_true")]
10    pub delegation_enabled: bool,
11    #[serde(default = "default_min_decomposition_complexity")]
12    pub delegation_min_complexity: f64,
13    #[serde(default = "default_min_delegation_utility_margin")]
14    pub delegation_min_utility_margin: f64,
15    #[serde(default = "default_true")]
16    pub specialist_creation_requires_approval: bool,
17    #[serde(default = "default_autonomy_max_react_turns")]
18    pub autonomy_max_react_turns: usize,
19    #[serde(default = "default_autonomy_max_turn_duration_seconds")]
20    pub autonomy_max_turn_duration_seconds: u64,
21}
22
23fn default_workspace() -> PathBuf {
24    dirs_next().join("workspace")
25}
26
27fn default_log_level() -> String {
28    "info".into()
29}
30
31fn default_min_decomposition_complexity() -> f64 {
32    0.35
33}
34
35fn default_min_delegation_utility_margin() -> f64 {
36    0.15
37}
38
39fn default_autonomy_max_react_turns() -> usize {
40    10
41}
42
43fn default_autonomy_max_turn_duration_seconds() -> u64 {
44    90
45}
46
47fn default_log_dir() -> PathBuf {
48    let home = std::env::var("HOME").unwrap_or_else(|_| "/tmp".into());
49    PathBuf::from(home).join(".roboticus").join("logs")
50}
51
52fn default_log_max_days() -> u32 {
53    7
54}
55
56fn dirs_next() -> PathBuf {
57    let home = home_dir();
58    let legacy = home.join(".ironclad");
59    let new_dir = home.join(".roboticus");
60    if legacy.exists() && !new_dir.exists() {
61        if let Err(e) = std::fs::rename(&legacy, &new_dir) {
62            tracing::warn!(error = %e, "failed to migrate ~/.ironclad to ~/.roboticus");
63        } else {
64            tracing::info!("Migrated config directory from ~/.ironclad to ~/.roboticus");
65        }
66    }
67    new_dir
68}
69
70/// Returns the user's home directory, checking `HOME` first (Unix / MSYS2 / Git Bash)
71/// then `USERPROFILE` (native Windows). Falls back to the platform temp directory.
72pub fn home_dir() -> PathBuf {
73    std::env::var("HOME")
74        .or_else(|_| std::env::var("USERPROFILE"))
75        .map(PathBuf::from)
76        .unwrap_or_else(|_| std::env::temp_dir())
77}
78
79/// Resolves the configuration file path using a standard precedence chain:
80///
81/// 1. Explicit path (from `--config` flag or `ROBOTICUS_CONFIG` env var)
82/// 2. `~/.roboticus/roboticus.toml` (if it exists)
83/// 3. `./roboticus.toml` in the current working directory (if it exists)
84/// 4. `None` — caller decides the fallback (e.g., built-in defaults or error)
85pub fn resolve_config_path(explicit: Option<&str>) -> Option<PathBuf> {
86    if let Some(p) = explicit {
87        return Some(expand_tilde(Path::new(p)));
88    }
89    let home_config = home_dir().join(".roboticus").join("roboticus.toml");
90    if home_config.exists() {
91        return Some(home_config);
92    }
93    let cwd_config = PathBuf::from("roboticus.toml");
94    if cwd_config.exists() {
95        return Some(cwd_config);
96    }
97    None
98}
99
100/// Expands a leading `~` in `path` to the user's home directory; otherwise returns the path unchanged.
101fn expand_tilde(path: &Path) -> PathBuf {
102    if let Ok(stripped) = path.strip_prefix("~") {
103        home_dir().join(stripped)
104    } else {
105        path.to_path_buf()
106    }
107}
108