Skip to main content

ai_session/
session_persistence.rs

1//! Persistent session manager for the CLI
2//!
3//! This module provides a SessionManager wrapper that persists sessions
4//! across CLI invocations using the ai-session library's PersistenceManager.
5
6use crate::core::{SessionId, SessionStatus};
7use crate::persistence::{PersistenceManager, SessionMetadata, SessionState};
8use crate::{AISession, SessionConfig, SessionManager as InnerSessionManager};
9use anyhow::Result;
10use std::path::PathBuf;
11use std::sync::Arc;
12
13/// Global session manager instance
14static MANAGER_INSTANCE: tokio::sync::OnceCell<Arc<PersistentSessionManager>> =
15    tokio::sync::OnceCell::const_new();
16
17/// Get the global persistent session manager
18pub async fn get_session_manager() -> Result<Arc<PersistentSessionManager>> {
19    Ok(MANAGER_INSTANCE
20        .get_or_init(|| async { Arc::new(PersistentSessionManager::new().await.unwrap()) })
21        .await
22        .clone())
23}
24
25/// Persistent session manager that wraps the inner SessionManager
26/// and provides persistence across CLI invocations
27pub struct PersistentSessionManager {
28    /// Inner session manager
29    inner: InnerSessionManager,
30    /// Persistence manager for saving/loading sessions
31    persistence: PersistenceManager,
32    /// Cache directory for session data
33    #[allow(dead_code)]
34    storage_path: PathBuf,
35}
36
37impl PersistentSessionManager {
38    /// Create a new persistent session manager
39    pub async fn new() -> Result<Self> {
40        let storage_path = dirs::data_dir()
41            .ok_or_else(|| anyhow::anyhow!("Could not determine data directory"))?
42            .join("ai-session")
43            .join("sessions");
44
45        std::fs::create_dir_all(&storage_path)?;
46        eprintln!("Using storage path: {}", storage_path.display());
47
48        let persistence = PersistenceManager::new(storage_path.clone());
49        let inner = InnerSessionManager::new();
50
51        let mut manager = Self {
52            inner,
53            persistence,
54            storage_path,
55        };
56
57        // Load existing sessions
58        manager.restore_sessions().await?;
59
60        Ok(manager)
61    }
62
63    /// Restore sessions from persistent storage
64    async fn restore_sessions(&mut self) -> Result<()> {
65        eprintln!("Restoring sessions from persistence...");
66        let session_ids = self.persistence.list_sessions().await?;
67        eprintln!("Found {} sessions to restore", session_ids.len());
68
69        for session_id in session_ids {
70            match self.persistence.load_session(&session_id).await {
71                Ok(state) => {
72                    // Only restore sessions that were running or paused
73                    match state.status {
74                        SessionStatus::Running | SessionStatus::Paused => {
75                            // Restore the session with its original ID
76                            match self
77                                .inner
78                                .restore_session(
79                                    state.session_id.clone(),
80                                    state.config.clone(),
81                                    state.metadata.created_at,
82                                )
83                                .await
84                            {
85                                Ok(_session) => {
86                                    eprintln!("Restored session: {}", session_id);
87                                }
88                                Err(e) => {
89                                    eprintln!(
90                                        "Warning: Failed to restore session {}: {}",
91                                        session_id, e
92                                    );
93                                }
94                            }
95                        }
96                        _ => {
97                            // Skip terminated or error sessions
98                            eprintln!("Skipping terminated session: {}", session_id);
99                        }
100                    }
101                }
102                Err(e) => {
103                    eprintln!("Warning: Failed to restore session {}: {}", session_id, e);
104                }
105            }
106        }
107
108        Ok(())
109    }
110
111    /// Create a new session with config and persist it
112    pub async fn create_session_with_config(
113        &self,
114        config: SessionConfig,
115    ) -> Result<Arc<AISession>> {
116        let session = self.inner.create_session_with_config(config).await?;
117
118        // Start the session first
119        session.start().await?;
120
121        // Save the session state after starting
122        let state = SessionState {
123            session_id: session.id.clone(),
124            config: session.config.clone(),
125            status: session.status().await,
126            context: session.context.read().await.clone(),
127            command_history: vec![],
128            metadata: SessionMetadata::default(),
129        };
130
131        eprintln!(
132            "Saving session {} with status {:?}",
133            session.id, state.status
134        );
135        self.persistence.save_session(&session.id, &state).await?;
136        eprintln!("Session {} saved successfully", session.id);
137
138        Ok(session)
139    }
140
141    /// Get a session by ID, starting it if necessary
142    pub async fn get_session(&self, id: &SessionId) -> Option<Arc<AISession>> {
143        if let Some(session) = self.inner.get_session(id) {
144            // Check if session needs to be started
145            if session.status().await == SessionStatus::Initializing
146                && let Err(e) = session.start().await
147            {
148                eprintln!("Error: Failed to start session {id}: {e}");
149                return None;
150            }
151            Some(session)
152        } else {
153            // Try to load from persistence
154            match self.persistence.load_session(id).await {
155                Ok(state) => {
156                    // Recreate the session
157                    match self
158                        .inner
159                        .create_session_with_config(state.config.clone())
160                        .await
161                    {
162                        Ok(session) => {
163                            // Start the session
164                            if let Err(e) = session.start().await {
165                                eprintln!("Error: Failed to start restored session {}: {}", id, e);
166                                return None;
167                            }
168                            Some(session)
169                        }
170                        Err(e) => {
171                            eprintln!("Error: Failed to recreate session {}: {}", id, e);
172                            None
173                        }
174                    }
175                }
176                Err(e) => {
177                    eprintln!("Debug: Session {} not found in persistence: {}", id, e);
178                    None
179                }
180            }
181        }
182    }
183
184    /// List all sessions (both active and persisted)
185    pub async fn list_all_sessions(&self) -> Result<Vec<SessionId>> {
186        let mut all_sessions = self.inner.list_sessions();
187        let persisted = self.persistence.list_sessions().await?;
188
189        // Add persisted sessions that aren't already active
190        for session_id in persisted {
191            if !all_sessions.contains(&session_id) {
192                all_sessions.push(session_id);
193            }
194        }
195
196        Ok(all_sessions)
197    }
198
199    /// Remove a session and delete its persistent data
200    pub async fn remove_session(&self, id: &SessionId) -> Result<()> {
201        // Remove from active sessions
202        self.inner.remove_session(id).await?;
203
204        // Delete persistent data
205        self.persistence.delete_session(id).await?;
206
207        Ok(())
208    }
209
210    /// Update session state in persistence
211    pub async fn update_session_state(&self, session: &AISession) -> Result<()> {
212        let state = SessionState {
213            session_id: session.id.clone(),
214            config: session.config.clone(),
215            status: session.status().await,
216            context: session.context.read().await.clone(),
217            command_history: session.get_command_history().await,
218            metadata: SessionMetadata {
219                created_at: session.created_at,
220                last_accessed: *session.last_activity.read().await,
221                command_count: session.get_command_count().await,
222                total_tokens: session.get_total_tokens().await,
223                custom: session.metadata.read().await.clone(),
224            },
225        };
226
227        self.persistence.save_session(&session.id, &state).await?;
228        Ok(())
229    }
230}