magi-code 0.64.0

Repository-aware CLI coding agent for terminal work
Documentation
use super::metadata::SessionMetadataSummary;
use super::read::validate_session_id;
use super::store::{prepare_session_root, primary_path};
use std::path::{Path, PathBuf};
use uuid::Uuid;

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct SessionInternalDiagnostic {
    pub(crate) session_id: Option<String>,
    pub(crate) message: String,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct SessionListReport {
    pub(crate) summaries: Vec<SessionMetadataSummary>,
    pub(crate) diagnostics: Vec<SessionInternalDiagnostic>,
}

#[derive(Debug, Clone)]
pub struct SessionManager {
    pub(in crate::sessions) root: PathBuf,
}

impl SessionManager {
    #[must_use]
    pub fn new(root: PathBuf) -> Self {
        Self { root }
    }

    pub fn create(&self) -> anyhow::Result<Session> {
        prepare_session_root(&self.root)?;
        let id = Uuid::new_v4().to_string();
        let path = self.path_for_valid_id(&id)?;
        // Do not create JSONL until first event append; avoids orphan session files.
        Ok(Session { id, path })
    }

    pub fn open(&self, id: impl Into<String>) -> anyhow::Result<Session> {
        let id = validate_session_id(id.into())?;
        Ok(Session {
            path: self.path_for_valid_id(&id)?,
            id,
        })
    }

    pub(crate) fn open_existing(&self, id: impl Into<String>) -> anyhow::Result<Session> {
        let session = self.open(id)?;
        super::store::open_existing_primary(&self.root, &session.id)?
            .ok_or_else(|| anyhow::anyhow!("session JSONL is missing"))?;
        Ok(session)
    }

    pub fn list(&self) -> anyhow::Result<Vec<Session>> {
        Ok(self
            .list_metadata_summaries()?
            .into_iter()
            .map(|summary| summary.session)
            .collect())
    }

    pub fn most_recent(&self) -> anyhow::Result<Option<Session>> {
        let report = self.list_metadata_report()?;
        if !report.diagnostics.is_empty() {
            anyhow::bail!("session discovery found unreadable or unsafe history");
        }
        Ok(report
            .summaries
            .into_iter()
            .last()
            .map(|summary| summary.session))
    }

    pub(crate) fn path_for_valid_id(&self, id: &str) -> anyhow::Result<PathBuf> {
        primary_path(&self.root, id)
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Session {
    pub(in crate::sessions) id: String,
    pub(in crate::sessions) path: PathBuf,
}

impl Session {
    pub fn id(&self) -> &str {
        &self.id
    }

    pub fn path(&self) -> &Path {
        &self.path
    }

    #[cfg(test)]
    pub(crate) fn unchecked_for_test(id: String, path: PathBuf) -> Self {
        Self { id, path }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use proptest::prelude::*;
    use std::path::Component;
    use tempfile::TempDir;

    fn valid_session_id_strategy() -> impl Strategy<Value = String> {
        proptest::string::string_regex("[A-Za-z0-9_-]{1,64}").unwrap()
    }

    fn invalid_session_id_strategy() -> impl Strategy<Value = String> {
        prop_oneof![
            Just(String::new()),
            Just(".".to_string()),
            any::<String>().prop_map(|value| format!("{value}..")),
            any::<String>().prop_map(|value| format!("{value}/{value}")),
            any::<String>().prop_map(|value| format!("{value}\\{value}")),
            any::<String>().prop_map(|value| format!("{value}.jsonl")),
            any::<String>().prop_map(|value| format!("{value}é")),
        ]
    }

    fn lexical_normalize(path: &Path) -> PathBuf {
        let mut normalized = PathBuf::new();
        for component in path.components() {
            match component {
                Component::CurDir => {}
                Component::ParentDir => {
                    normalized.pop();
                }
                Component::Prefix(_) | Component::RootDir | Component::Normal(_) => {
                    normalized.push(component.as_os_str());
                }
            }
        }
        normalized
    }

    proptest! {
        #[test]
        fn path_for_valid_id_keeps_valid_ids_under_session_root(id in valid_session_id_strategy()) {
            let temp = TempDir::new().unwrap();
            let root = temp.path().join("sessions");
            let manager = SessionManager::new(root.clone());
            let path = manager.path_for_valid_id(&id).unwrap();
            let normalized_root = lexical_normalize(&root);
            let normalized_path = lexical_normalize(&path);
            let expected_file_name = format!("{id}.jsonl");

            prop_assert!(normalized_path.starts_with(&normalized_root));
            prop_assert_eq!(normalized_path.parent(), Some(normalized_root.as_path()));
            prop_assert_eq!(
                normalized_path.file_name().and_then(|file_name| file_name.to_str()),
                Some(expected_file_name.as_str())
            );

            let session = manager.open(id.clone()).unwrap();
            prop_assert_eq!(session.id(), id);
            prop_assert_eq!(session.path(), path.as_path());
        }

        #[test]
        fn open_and_path_for_valid_id_reject_generated_unsafe_ids(id in invalid_session_id_strategy()) {
            let temp = TempDir::new().unwrap();
            let manager = SessionManager::new(temp.path().join("sessions"));

            prop_assert!(manager.open(id.clone()).is_err());
            prop_assert!(manager.path_for_valid_id(&id).is_err());
        }
    }

    #[test]
    fn session_open_rejects_unsafe_ids_before_joining_paths() {
        let temp = TempDir::new().unwrap();
        let manager = SessionManager::new(temp.path().join("sessions"));
        for id in [
            "",
            "..",
            "../escape",
            "nested/id",
            "nested\\id",
            "/absolute",
            "bad.jsonl",
        ] {
            assert!(manager.open(id).is_err(), "accepted unsafe id {id:?}");
        }
        assert!(manager.open("safe_ID-123").is_ok());
    }
}