Skip to main content

lean_ctx/tools/
ctx_session.rs

1use crate::core::session::SessionState;
2
3pub fn handle(
4    session: &mut SessionState,
5    action: &str,
6    value: Option<&str>,
7    session_id: Option<&str>,
8) -> String {
9    match action {
10        "status" => session.format_compact(),
11
12        "load" => {
13            let loaded = if let Some(id) = session_id {
14                SessionState::load_by_id(id)
15            } else {
16                SessionState::load_latest()
17            };
18
19            if let Some(prev) = loaded {
20                let summary = prev.format_compact();
21                *session = prev;
22                format!("Session loaded.\n{summary}")
23            } else {
24                let id_str = session_id.unwrap_or("latest");
25                format!("No session found (id: {id_str}). Starting fresh.")
26            }
27        }
28
29        "save" => {
30            match session.save() {
31                Ok(()) => format!("Session {} saved (v{}).", session.id, session.version),
32                Err(e) => format!("Save failed: {e}"),
33            }
34        }
35
36        "task" => {
37            let desc = value.unwrap_or("(no description)");
38            session.set_task(desc, None);
39            format!("Task set: {desc}")
40        }
41
42        "finding" => {
43            let summary = value.unwrap_or("(no summary)");
44            let (file, line, text) = parse_finding_value(summary);
45            session.add_finding(file.as_deref(), line, text);
46            format!("Finding added: {summary}")
47        }
48
49        "decision" => {
50            let desc = value.unwrap_or("(no description)");
51            session.add_decision(desc, None);
52            format!("Decision recorded: {desc}")
53        }
54
55        "reset" => {
56            let _ = session.save();
57            let old_id = session.id.clone();
58            *session = SessionState::new();
59            format!("Session reset. Previous: {old_id}. New: {}", session.id)
60        }
61
62        "list" => {
63            let sessions = SessionState::list_sessions();
64            if sessions.is_empty() {
65                return "No sessions found.".to_string();
66            }
67            let mut lines = vec![format!("Sessions ({}):", sessions.len())];
68            for s in sessions.iter().take(10) {
69                let task = s.task.as_deref().unwrap_or("(no task)");
70                let task_short: String = task.chars().take(40).collect();
71                lines.push(format!(
72                    "  {} v{} | {} calls | {} tok | {}",
73                    s.id, s.version, s.tool_calls, s.tokens_saved, task_short
74                ));
75            }
76            if sessions.len() > 10 {
77                lines.push(format!("  ... +{} more", sessions.len() - 10));
78            }
79            lines.join("\n")
80        }
81
82        "cleanup" => {
83            let removed = SessionState::cleanup_old_sessions(7);
84            format!("Cleaned up {removed} old session(s) (>7 days).")
85        }
86
87        "snapshot" => match session.save_compaction_snapshot() {
88            Ok(snapshot) => {
89                format!(
90                    "Compaction snapshot saved ({} bytes).\n{snapshot}",
91                    snapshot.len()
92                )
93            }
94            Err(e) => format!("Snapshot failed: {e}"),
95        },
96
97        "restore" => {
98            let snapshot = if let Some(id) = session_id {
99                SessionState::load_compaction_snapshot(id)
100            } else {
101                SessionState::load_latest_snapshot()
102            };
103            match snapshot {
104                Some(s) => format!("Session restored from compaction snapshot:\n{s}"),
105                None => "No compaction snapshot found. Session continues fresh.".to_string(),
106            }
107        }
108
109        "resume" => session.build_resume_block(),
110
111        _ => format!("Unknown action: {action}. Use: status, load, save, task, finding, decision, reset, list, cleanup, snapshot, restore, resume"),
112    }
113}
114
115fn parse_finding_value(value: &str) -> (Option<String>, Option<u32>, &str) {
116    // Format: "file.rs:42 — summary text" or just "summary text"
117    if let Some(dash_pos) = value.find(" \u{2014} ").or_else(|| value.find(" - ")) {
118        let location = &value[..dash_pos];
119        let sep_len = 3;
120        let text = &value[dash_pos + sep_len..];
121
122        if let Some(colon_pos) = location.rfind(':') {
123            let file = &location[..colon_pos];
124            if let Ok(line) = location[colon_pos + 1..].parse::<u32>() {
125                return (Some(file.to_string()), Some(line), text);
126            }
127        }
128        return (Some(location.to_string()), None, text);
129    }
130    (None, None, value)
131}