schwab_cli/agent/
paths.rs1use std::path::{Path, PathBuf};
2
3pub 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
38pub fn append_agent_log(rules_path: &Path, line: &str) -> std::io::Result<()> {
40 use std::io::Write;
41 let log = log_path(rules_path);
42 if let Some(parent) = log.parent() {
43 std::fs::create_dir_all(parent)?;
44 }
45 let mut file = std::fs::OpenOptions::new()
46 .create(true)
47 .append(true)
48 .open(&log)?;
49 writeln!(file, "{line}")?;
50 Ok(())
51}
52
53pub fn load_agent_state(rules_path: &Path, agent_id: &str) -> super::state::AgentState {
55 use super::state::{load_state, save_state};
56
57 let state_path = default_state_path(rules_path);
58 if state_path.exists() {
59 return load_state(&state_path).unwrap_or_default();
60 }
61
62 let legacy = rules_runtime_dir(rules_path).join("agent-state.json");
63 if legacy.exists() {
64 if let Ok(state) = load_state(&legacy) {
65 if state.agent_id.is_empty() || state.agent_id == agent_id {
66 let _ = save_state(&state_path, &state);
67 return state;
68 }
69 }
70 }
71
72 load_state(&state_path).unwrap_or_default()
73}
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78 use std::path::Path;
79
80 #[test]
81 fn runtime_paths_derive_from_rules_filename() {
82 let rules = Path::new("rules/options-pilot-8709.yaml");
83 assert_eq!(
84 default_state_path(rules),
85 Path::new("rules/agent-state-options-pilot-8709.json")
86 );
87 assert_eq!(
88 pid_path(rules),
89 Path::new("rules/agent-options-pilot-8709.pid")
90 );
91 assert_eq!(
92 log_path(rules),
93 Path::new("rules/agent-options-pilot-8709.log")
94 );
95 }
96
97 #[test]
98 fn distinct_rules_files_get_distinct_state_paths() {
99 let a = default_state_path(Path::new("rules/options-pilot-8709.yaml"));
100 let b = default_state_path(Path::new("rules/options-pilot-9947.yaml"));
101 assert_ne!(a, b);
102 }
103}