oy-cli 0.8.1

Local AI coding CLI for inspecting, editing, running commands, and auditing repositories
Documentation
use anyhow::{Context, Result, bail};
use std::path::Path;

use super::{Session, Transcript};
use crate::config::{self, SafetyMode};
use crate::tools::ToolPolicy;

pub fn load_saved(
    name: Option<&str>,
    interactive: bool,
    mode: SafetyMode,
    policy: ToolPolicy,
) -> Result<Option<Session>> {
    let Some(path) = config::resolve_saved_session(name)? else {
        return Ok(None);
    };
    let saved = config::load_session_file(&path)?;
    let transcript: Transcript = serde_json::from_value(saved.transcript)
        .with_context(|| format!("invalid saved transcript in {}", path.display()))?;
    let root = config::oy_root()?;
    ensure_saved_workspace_matches(&path, saved.workspace_root.as_deref(), &root)?;
    let mode = saved.mode.unwrap_or(mode);
    let system_prompt = config::system_prompt(interactive, mode);
    Ok(Some(Session {
        root,
        model: saved.model,
        system_prompt,
        interactive,
        policy,
        mode,
        transcript,
        todos: saved.todos,
    }))
}

fn ensure_saved_workspace_matches(
    session_path: &Path,
    saved_root: Option<&Path>,
    current_root: &Path,
) -> Result<()> {
    let Some(saved_root) = saved_root else {
        return Ok(());
    };
    if saved_root == current_root {
        return Ok(());
    }
    bail!(
        "saved session {} belongs to workspace {}; current workspace is {}",
        session_path.display(),
        saved_root.display(),
        current_root.display()
    )
}

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

    #[test]
    fn saved_workspace_mismatch_fails_closed() {
        let dir = tempfile::tempdir().unwrap();
        let current = tempfile::tempdir().unwrap();
        let session_path = dir.path().join("session.json");
        let saved_root = dir.path().to_path_buf();

        let err = ensure_saved_workspace_matches(&session_path, Some(&saved_root), current.path())
            .unwrap_err();

        assert!(err.to_string().contains("belongs to workspace"));
    }

    #[test]
    fn saved_workspace_allows_legacy_without_root() {
        let dir = tempfile::tempdir().unwrap();
        assert!(
            ensure_saved_workspace_matches(&dir.path().join("session.json"), None, dir.path())
                .is_ok()
        );
    }
}