Skip to main content

schwab_cli/agent/
paths.rs

1use std::path::{Path, PathBuf};
2
3/// Stable filename stem from the rules YAML path (e.g. `options-pilot-8709`).
4pub fn rules_runtime_stem(rules_path: &Path) -> String {
5    rules_path
6        .file_stem()
7        .and_then(|s| s.to_str())
8        .filter(|s| !s.is_empty())
9        .unwrap_or("agent")
10        .to_string()
11}
12
13fn rules_runtime_dir(rules_path: &Path) -> PathBuf {
14    rules_path
15        .parent()
16        .map(Path::to_path_buf)
17        .unwrap_or_else(|| PathBuf::from("."))
18}
19
20pub fn default_state_path(rules_path: &Path) -> PathBuf {
21    let dir = rules_runtime_dir(rules_path);
22    let stem = rules_runtime_stem(rules_path);
23    dir.join(format!("agent-state-{stem}.json"))
24}
25
26pub fn pid_path(rules_path: &Path) -> PathBuf {
27    let dir = rules_runtime_dir(rules_path);
28    let stem = rules_runtime_stem(rules_path);
29    dir.join(format!("agent-{stem}.pid"))
30}
31
32pub fn log_path(rules_path: &Path) -> PathBuf {
33    let dir = rules_runtime_dir(rules_path);
34    let stem = rules_runtime_stem(rules_path);
35    dir.join(format!("agent-{stem}.log"))
36}
37
38/// Load persisted state for a rules file, migrating legacy `agent-state.json` when agent_id matches.
39pub fn load_agent_state(rules_path: &Path, agent_id: &str) -> super::state::AgentState {
40    use super::state::{load_state, save_state};
41
42    let state_path = default_state_path(rules_path);
43    if state_path.exists() {
44        return load_state(&state_path).unwrap_or_default();
45    }
46
47    let legacy = rules_runtime_dir(rules_path).join("agent-state.json");
48    if legacy.exists() {
49        if let Ok(state) = load_state(&legacy) {
50            if state.agent_id.is_empty() || state.agent_id == agent_id {
51                let _ = save_state(&state_path, &state);
52                return state;
53            }
54        }
55    }
56
57    load_state(&state_path).unwrap_or_default()
58}
59
60#[cfg(test)]
61mod tests {
62    use super::*;
63    use std::path::Path;
64
65    #[test]
66    fn runtime_paths_derive_from_rules_filename() {
67        let rules = Path::new("rules/options-pilot-8709.yaml");
68        assert_eq!(
69            default_state_path(rules),
70            Path::new("rules/agent-state-options-pilot-8709.json")
71        );
72        assert_eq!(
73            pid_path(rules),
74            Path::new("rules/agent-options-pilot-8709.pid")
75        );
76        assert_eq!(
77            log_path(rules),
78            Path::new("rules/agent-options-pilot-8709.log")
79        );
80    }
81
82    #[test]
83    fn distinct_rules_files_get_distinct_state_paths() {
84        let a = default_state_path(Path::new("rules/options-pilot-8709.yaml"));
85        let b = default_state_path(Path::new("rules/options-pilot-9947.yaml"));
86        assert_ne!(a, b);
87    }
88}