Skip to main content

capo_agent/session/
paths.rs

1#![cfg_attr(test, allow(clippy::expect_used, clippy::unwrap_used))]
2
3//! Per-cwd session bucket paths.
4
5use std::path::{Path, PathBuf};
6
7use crate::error::{AppError, Result};
8use crate::session::encode_cwd;
9
10/// Resolved storage locations for a session bucket.
11#[derive(Debug, Clone)]
12pub struct SessionPaths {
13    /// `~/.capo/agent` (or `$CAPO_AGENT_DIR`).
14    pub agent_dir: PathBuf,
15    /// `<agent_dir>/sessions/<encoded-cwd>` — one motosan `FileSessionStore`
16    /// per directory; each session id is a file inside.
17    pub bucket_dir: PathBuf,
18}
19
20impl SessionPaths {
21    /// Compute the bucket dir for `cwd`. Does NOT create the directory.
22    pub fn for_cwd(agent_dir: PathBuf, cwd: &Path) -> Self {
23        let bucket_dir = agent_dir.join("sessions").join(encode_cwd(cwd));
24        Self {
25            agent_dir,
26            bucket_dir,
27        }
28    }
29
30    /// `mkdir -p` on the bucket dir. Idempotent.
31    pub fn ensure_bucket(&self) -> Result<()> {
32        std::fs::create_dir_all(&self.bucket_dir).map_err(|err| {
33            AppError::Config(format!(
34                "failed to create {}: {err}",
35                self.bucket_dir.display()
36            ))
37        })
38    }
39
40    /// Root of the `sessions/` tree (all buckets sit beneath this).
41    pub fn sessions_root(&self) -> PathBuf {
42        self.agent_dir.join("sessions")
43    }
44}
45
46#[cfg(test)]
47mod tests {
48    use super::*;
49    use tempfile::tempdir;
50
51    #[test]
52    fn for_cwd_produces_encoded_bucket() {
53        let agent = PathBuf::from("/x/agent");
54        let cwd = PathBuf::from("/Users/wade/proj/capo");
55        let p = SessionPaths::for_cwd(agent.clone(), &cwd);
56        assert_eq!(p.agent_dir, agent);
57        assert_eq!(
58            p.bucket_dir,
59            PathBuf::from("/x/agent/sessions/--Users-wade-proj-capo--")
60        );
61    }
62
63    #[test]
64    fn ensure_bucket_creates_nested_dirs() {
65        let dir = tempdir().unwrap();
66        let p = SessionPaths::for_cwd(dir.path().to_path_buf(), &PathBuf::from("/tmp/x"));
67        assert!(!p.bucket_dir.exists());
68        p.ensure_bucket().unwrap();
69        assert!(p.bucket_dir.exists());
70    }
71}