ytil_agents/agent/
session_loader.rs1use std::collections::HashSet;
2use std::path::Path;
3use std::path::PathBuf;
4
5use rootcause::prelude::ResultExt;
6
7use crate::agent::Agent;
8use crate::agent::session::Session;
9use crate::agent::session::SessionKey;
10
11pub mod claude;
12pub mod codex;
13pub mod cursor;
14
15pub fn load_sessions() -> rootcause::Result<Vec<Session>> {
20 let mut sessions = Vec::new();
21 sessions.extend(claude::load_sessions()?);
22 sessions.extend(codex::load_sessions()?);
23 sessions.extend(cursor::load_sessions()?);
24 Ok(sessions)
25}
26
27pub fn load_sessions_by_key(keys: &[SessionKey]) -> rootcause::Result<Vec<Session>> {
32 let mut sessions = Vec::new();
33 sessions.extend(claude::load_sessions_by_key(keys)?);
34 sessions.extend(codex::load_sessions_by_key(keys)?);
35 sessions.extend(cursor::load_sessions_by_key(keys)?);
36 Ok(sessions)
37}
38
39fn requested_ids(keys: &[SessionKey], agent: Agent) -> HashSet<&str> {
40 keys.iter()
41 .filter(|key| key.agent() == agent)
42 .map(SessionKey::id)
43 .collect()
44}
45
46fn find_session_paths(
47 root: &Path,
48 matching_file_fn: impl Fn(&std::fs::DirEntry) -> bool,
49 skip_dir_fn: impl Fn(&std::fs::DirEntry) -> bool,
50) -> rootcause::Result<Vec<PathBuf>> {
51 if !root.exists() {
52 return Ok(Vec::new());
53 }
54
55 ytil_sys::file::find_matching_recursively_in_dir(root, matching_file_fn, skip_dir_fn)
56}
57
58fn file_updated_at(path: &Path) -> rootcause::Result<Option<chrono::DateTime<chrono::Utc>>> {
59 let modified = std::fs::metadata(path)
60 .context("failed to read session metadata")
61 .attach_with(|| format!("path={}", path.display()))?
62 .modified()
63 .context("failed to read session modified time")
64 .attach_with(|| format!("path={}", path.display()))?;
65 Ok(Some(chrono::DateTime::<chrono::Utc>::from(modified)))
66}
67
68#[cfg(test)]
69mod tests {
70 #[test]
71 fn test_find_session_paths_missing_root_returns_empty_paths() {
72 let dir = tempfile::tempdir().unwrap();
73 let missing_root = dir.path().join("missing");
74
75 let res = crate::agent::session_loader::find_session_paths(&missing_root, |_| true, |_| false);
76
77 assert2::assert!(let Ok(paths) = res);
78 pretty_assertions::assert_eq!(paths, Vec::<std::path::PathBuf>::new());
79 }
80
81 #[test]
82 fn test_find_session_paths_existing_file_root_returns_error() {
83 let dir = tempfile::tempdir().unwrap();
84 let file_root = dir.path().join("file");
85 std::fs::write(&file_root, b"not a directory").unwrap();
86
87 let res = crate::agent::session_loader::find_session_paths(&file_root, |_| true, |_| false);
88
89 assert2::assert!(let Err(err) = res);
90 assert!(err.to_string().contains("error reading directory"));
91 }
92}