Skip to main content

acp_cli/session/
scoping.rs

1use sha2::{Digest, Sha256};
2use std::path::{Path, PathBuf};
3
4use super::persistence::SessionRecord;
5
6/// Compute a deterministic session key from agent, directory, and optional name.
7///
8/// Uses SHA-256 of `"agent\0dir\0name"` (hex encoded). The null byte separator
9/// prevents collisions between e.g. `("a","b\0c","")` and `("a\0b","c","")`.
10pub fn session_key(agent: &str, dir: &str, name: &str) -> String {
11    let mut hasher = Sha256::new();
12    hasher.update(agent.as_bytes());
13    hasher.update(b"\0");
14    hasher.update(dir.as_bytes());
15    hasher.update(b"\0");
16    hasher.update(name.as_bytes());
17    let result = hasher.finalize();
18    result.iter().map(|b| format!("{b:02x}")).collect()
19}
20
21/// Walk up from `from` looking for a `.git` directory, returning the git root.
22pub fn find_git_root(from: &Path) -> Option<PathBuf> {
23    let mut current = from.to_path_buf();
24    loop {
25        if current.join(".git").exists() {
26            return Some(current);
27        }
28        if !current.pop() {
29            return None;
30        }
31    }
32}
33
34/// Return the default session storage directory: `~/.acp-cli/sessions/`.
35pub fn session_dir() -> PathBuf {
36    let home = dirs::home_dir().expect("could not determine home directory");
37    home.join(".acp-cli").join("sessions")
38}
39
40/// Find an existing session record for the given agent, working directory, and name.
41///
42/// Resolves the directory to a git root (if possible), computes the session key,
43/// and attempts to load the corresponding session file.
44pub fn find_session(agent: &str, cwd: &Path, name: &str) -> Option<SessionRecord> {
45    let resolved_dir = find_git_root(cwd).unwrap_or_else(|| cwd.to_path_buf());
46    let dir_str = resolved_dir.to_string_lossy();
47    let key = session_key(agent, &dir_str, name);
48    let path = session_dir().join(format!("{key}.json"));
49    SessionRecord::load(&path).ok().flatten()
50}