capo-agent 0.5.0

Coding-agent library built on motosan-agent-loop. Composable, embeddable.
Documentation
#![cfg_attr(test, allow(clippy::expect_used, clippy::unwrap_used))]

//! Per-cwd session bucket paths.

use std::path::{Path, PathBuf};

use crate::error::{AppError, Result};
use crate::session::encode_cwd;

/// Resolved storage locations for a session bucket.
#[derive(Debug, Clone)]
pub struct SessionPaths {
    /// `~/.capo/agent` (or `$CAPO_AGENT_DIR`).
    pub agent_dir: PathBuf,
    /// `<agent_dir>/sessions/<encoded-cwd>` — one motosan `FileSessionStore`
    /// per directory; each session id is a file inside.
    pub bucket_dir: PathBuf,
}

impl SessionPaths {
    /// Compute the bucket dir for `cwd`. Does NOT create the directory.
    pub fn for_cwd(agent_dir: PathBuf, cwd: &Path) -> Self {
        let bucket_dir = agent_dir.join("sessions").join(encode_cwd(cwd));
        Self {
            agent_dir,
            bucket_dir,
        }
    }

    /// `mkdir -p` on the bucket dir. Idempotent.
    pub fn ensure_bucket(&self) -> Result<()> {
        std::fs::create_dir_all(&self.bucket_dir).map_err(|err| {
            AppError::Config(format!(
                "failed to create {}: {err}",
                self.bucket_dir.display()
            ))
        })
    }

    /// Root of the `sessions/` tree (all buckets sit beneath this).
    pub fn sessions_root(&self) -> PathBuf {
        self.agent_dir.join("sessions")
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use tempfile::tempdir;

    #[test]
    fn for_cwd_produces_encoded_bucket() {
        let agent = PathBuf::from("/x/agent");
        let cwd = PathBuf::from("/Users/wade/proj/capo");
        let p = SessionPaths::for_cwd(agent.clone(), &cwd);
        assert_eq!(p.agent_dir, agent);
        assert_eq!(
            p.bucket_dir,
            PathBuf::from("/x/agent/sessions/--Users-wade-proj-capo--")
        );
    }

    #[test]
    fn ensure_bucket_creates_nested_dirs() {
        let dir = tempdir().unwrap();
        let p = SessionPaths::for_cwd(dir.path().to_path_buf(), &PathBuf::from("/tmp/x"));
        assert!(!p.bucket_dir.exists());
        p.ensure_bucket().unwrap();
        assert!(p.bucket_dir.exists());
    }
}