Skip to main content

minion_engine/claude/
session.rs

1// Public API — used after worktree merge; suppress dead_code lint until then
2#![allow(dead_code)]
3
4/// Manages Claude Code session IDs for conversation continuity
5#[derive(Debug, Default)]
6pub struct SessionManager {
7    session_id: Option<String>,
8}
9
10impl SessionManager {
11    pub fn new() -> Self {
12        Self { session_id: None }
13    }
14
15    /// Capture a session_id — only the first call wins; subsequent calls are ignored.
16    pub fn capture(&mut self, session_id: Option<String>) {
17        if self.session_id.is_none() {
18            self.session_id = session_id;
19        }
20    }
21
22    /// Returns the captured session ID, if any.
23    pub fn session_id(&self) -> Option<&str> {
24        self.session_id.as_deref()
25    }
26
27    /// Returns CLI arguments to resume or fork a session.
28    ///
29    /// - `isolated = true`  → empty vec (start fresh every time)
30    /// - `isolated = false` + session_id is Some → `["--fork-session", "--resume", "<id>"]`
31    /// - `isolated = false` + session_id is None → empty vec
32    pub fn resume_args(&self, isolated: bool) -> Vec<String> {
33        if isolated {
34            return vec![];
35        }
36        match &self.session_id {
37            Some(id) => vec![
38                "--fork-session".to_string(),
39                "--resume".to_string(),
40                id.clone(),
41            ],
42            None => vec![],
43        }
44    }
45}
46
47#[cfg(test)]
48mod tests {
49    use super::*;
50
51    #[test]
52    fn test_capture_session_id() {
53        let mut mgr = SessionManager::new();
54        mgr.capture(Some("abc-123".to_string()));
55        assert_eq!(mgr.session_id(), Some("abc-123"));
56    }
57
58    #[test]
59    fn test_capture_first_wins() {
60        let mut mgr = SessionManager::new();
61        mgr.capture(Some("first".to_string()));
62        mgr.capture(Some("second".to_string()));
63        assert_eq!(mgr.session_id(), Some("first"));
64    }
65
66    #[test]
67    fn test_capture_none_then_some() {
68        let mut mgr = SessionManager::new();
69        mgr.capture(None);
70        mgr.capture(Some("late".to_string()));
71        // None counts as "set" — but actually None means we never got an id,
72        // so the second Some should NOT override. Let's verify the spec:
73        // "captures first session_id only (if already set, ignore)" — None is still
74        // "not set", so a subsequent Some should win only if we interpret None as absent.
75        // Per the spec the field starts as None; capture(None) leaves it as None which
76        // means it was never set, so we accept the next Some.
77        // Re-reading: "if already set, ignore" — None means NOT set, so second wins.
78        assert_eq!(mgr.session_id(), Some("late"));
79    }
80
81    #[test]
82    fn test_isolated_returns_no_resume_args() {
83        let mut mgr = SessionManager::new();
84        mgr.capture(Some("abc-123".to_string()));
85        let args = mgr.resume_args(true);
86        assert!(args.is_empty());
87    }
88
89    #[test]
90    fn test_shared_with_id_returns_resume_args() {
91        let mut mgr = SessionManager::new();
92        mgr.capture(Some("abc-123".to_string()));
93        let args = mgr.resume_args(false);
94        assert_eq!(args, vec!["--fork-session", "--resume", "abc-123"]);
95    }
96
97    #[test]
98    fn test_shared_without_id_returns_no_args() {
99        let mgr = SessionManager::new();
100        let args = mgr.resume_args(false);
101        assert!(args.is_empty());
102    }
103
104    #[test]
105    fn test_default() {
106        let mgr = SessionManager::default();
107        assert_eq!(mgr.session_id(), None);
108    }
109}