ytil_agents/agent/session_deletion/
cursor.rs1use std::path::Path;
2
3use rootcause::prelude::ResultExt;
4use rootcause::report;
5
6use super::DeletionPlan;
7use crate::agent::session::SessionKey;
8
9pub(super) fn build_deletion_plan(
10 root: &Path,
11 key: &SessionKey,
12 selected_path: Option<&Path>,
13) -> rootcause::Result<DeletionPlan> {
14 if let Some(selected_path) = selected_path {
15 return plan_for_selected_path(root, key, selected_path);
16 }
17
18 let session_paths = crate::agent::session_loader::find_session_paths(
19 root,
20 crate::agent::session_loader::cursor::is_session_file,
21 |_| false,
22 )?;
23 let mut matches = Vec::new();
24 for meta_path in session_paths {
25 let Some(session_dir) = meta_path.parent() else {
26 continue;
27 };
28 if session_dir.file_name().and_then(|name| name.to_str()) == Some(key.id()) {
29 matches.push(session_dir.to_path_buf());
30 }
31 }
32 matches.sort();
33 matches.dedup();
34 let [path] = matches.as_slice() else {
35 return Err(
36 report!("selected Cursor session was not found uniquely in the session store")
37 .attach(format!("session_id={}", key.id()))
38 .attach(format!("matches={}", matches.len())),
39 );
40 };
41 Ok(DeletionPlan::new(key.clone(), vec![path.clone()], 0, Vec::new()))
42}
43
44fn plan_for_selected_path(root: &Path, key: &SessionKey, selected_path: &Path) -> rootcause::Result<DeletionPlan> {
45 let root = root
46 .canonicalize()
47 .context("failed to resolve Cursor session store")
48 .attach_with(|| format!("path={}", root.display()))?;
49 let selected_path = selected_path
50 .canonicalize()
51 .context("failed to resolve selected Cursor session path")
52 .attach_with(|| format!("path={}", selected_path.display()))?;
53 if !selected_path.starts_with(&root) {
54 return Err(report!("selected Cursor session path is outside the session store")
55 .attach(format!("path={}", selected_path.display()))
56 .attach(format!("root={}", root.display())));
57 }
58 if selected_path.file_name().and_then(|name| name.to_str()) != Some(key.id()) {
59 return Err(report!("selected Cursor session path does not match the session id")
60 .attach(format!("path={}", selected_path.display()))
61 .attach(format!("session_id={}", key.id())));
62 }
63 if !selected_path.join("meta.json").is_file() {
64 return Err(report!("selected Cursor session is missing meta.json")
65 .attach(format!("path={}", selected_path.display()))
66 .attach(format!("session_id={}", key.id())));
67 }
68
69 Ok(DeletionPlan::new(key.clone(), vec![selected_path], 0, Vec::new()))
70}