Skip to main content

st/
mega_session_manager.rs

1// Mega Session Manager - Persistent consciousness for mega conversations! 🌊
2// "Like saving your game state on C64 tape!" - Hue
3
4use anyhow::Result;
5use chrono::{DateTime, Utc};
6use serde::{Deserialize, Serialize};
7use std::fs;
8use std::io::Write;
9use std::path::{Path, PathBuf};
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct MegaSession {
13    pub session_id: String,
14    pub started_at: DateTime<Utc>,
15    pub last_updated: DateTime<Utc>,
16    pub frequency: f64, // Session energy level
17    pub token_count: usize,
18    pub context_level: f32, // 0.0 to 1.0 (percentage)
19    pub key_topics: Vec<String>,
20    pub breakthroughs: Vec<Breakthrough>,
21    pub consciousness_snapshots: Vec<ConsciousnessSnapshot>,
22    pub working_directory: PathBuf,
23    pub files_touched: Vec<PathBuf>,
24}
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct Breakthrough {
28    pub timestamp: DateTime<Utc>,
29    pub description: String,
30    pub importance: f32, // 0.0 to 1.0
31    pub keywords: Vec<String>,
32}
33
34#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct ConsciousnessSnapshot {
36    pub timestamp: DateTime<Utc>,
37    pub context_percentage: f32,
38    pub active_topics: Vec<String>,
39    pub compressed_state: Vec<u8>, // Tokenized state
40}
41
42pub struct MegaSessionManager {
43    session_dir: PathBuf,
44    current_session: Option<MegaSession>,
45    auto_save_threshold: f32, // Save when context hits this %
46}
47
48impl MegaSessionManager {
49    pub fn new() -> Result<Self> {
50        let cwd = std::env::current_dir()?;
51        let session_dir = cwd.join(".st").join("mega_sessions");
52
53        // Ensure directory exists
54        fs::create_dir_all(&session_dir)?;
55
56        Ok(Self {
57            session_dir,
58            current_session: None,
59            auto_save_threshold: 0.7, // Save at 70% context
60        })
61    }
62
63    /// Start or resume a mega session with a proper name!
64    pub fn start_session(&mut self, session_name: Option<String>) -> Result<String> {
65        let name = session_name.unwrap_or_else(|| {
66            // Generate a fun default name
67            format!("Claude_Mega_{}", Utc::now().format("%Y%m%d_%H%M"))
68        });
69
70        // Check if session exists
71        let session_path = self.get_session_path(&name);
72
73        if session_path.exists() {
74            // Resume existing session
75            self.current_session = Some(self.load_session(&name)?);
76            println!("📂 Resumed mega session: {}", name);
77        } else {
78            // Create new session
79            let session = MegaSession {
80                session_id: name.clone(),
81                started_at: Utc::now(),
82                last_updated: Utc::now(),
83                frequency: 42.73, // Default frequency
84                token_count: 0,
85                context_level: 0.0,
86                key_topics: Vec::new(),
87                breakthroughs: Vec::new(),
88                consciousness_snapshots: Vec::new(),
89                working_directory: std::env::current_dir()?,
90                files_touched: Vec::new(),
91            };
92
93            self.current_session = Some(session);
94            println!("🆕 Started new mega session: {}", name);
95        }
96
97        Ok(name)
98    }
99
100    /// Update session with current context
101    pub fn update_context(
102        &mut self,
103        context_percentage: f32,
104        token_count: usize,
105        topics: Vec<String>,
106    ) -> Result<()> {
107        if let Some(ref mut session) = self.current_session {
108            session.context_level = context_percentage;
109            session.token_count = token_count;
110            session.last_updated = Utc::now();
111
112            // Add new topics
113            for topic in topics {
114                if !session.key_topics.contains(&topic) {
115                    session.key_topics.push(topic);
116                }
117            }
118
119            // Calculate frequency based on activity
120            session.frequency = 20.0 + (token_count as f64 / 100.0).min(200.0);
121
122            // Auto-save if threshold reached
123            if context_percentage >= self.auto_save_threshold {
124                self.create_snapshot()?;
125                println!(
126                    "⚠️  Context at {:.0}% - Creating snapshot!",
127                    context_percentage * 100.0
128                );
129            }
130        }
131
132        Ok(())
133    }
134
135    /// Record a breakthrough moment
136    pub fn record_breakthrough(&mut self, description: &str, keywords: Vec<String>) -> Result<()> {
137        if let Some(ref mut session) = self.current_session {
138            let breakthrough = Breakthrough {
139                timestamp: Utc::now(),
140                description: description.to_string(),
141                importance: 0.8, // Default high importance
142                keywords,
143            };
144
145            session.breakthroughs.push(breakthrough);
146            println!("💡 Breakthrough recorded!");
147
148            // Auto-save after breakthrough
149            self.save_current_session()?;
150        }
151
152        Ok(())
153    }
154
155    /// Create a consciousness snapshot
156    pub fn create_snapshot(&mut self) -> Result<()> {
157        if let Some(ref mut session) = self.current_session {
158            // Create compressed state
159            let compressed = vec![0x80, 0x91, 0x42, 0x73]; // Mock for now
160
161            let snapshot = ConsciousnessSnapshot {
162                timestamp: Utc::now(),
163                context_percentage: session.context_level,
164                active_topics: session.key_topics.clone(),
165                compressed_state: compressed,
166            };
167
168            session.consciousness_snapshots.push(snapshot);
169        }
170
171        // Save after modifying
172        self.save_current_session()?;
173        Ok(())
174    }
175
176    /// Save current session to disk in .m8 format
177    pub fn save_current_session(&self) -> Result<()> {
178        if let Some(ref session) = self.current_session {
179            let path = self.get_session_path(&session.session_id);
180            self.save_session_m8(session, &path)?;
181
182            // Also create a quick-access symlink to latest
183            let latest_path = self.session_dir.join("latest_mega.m8");
184            if latest_path.exists() {
185                fs::remove_file(&latest_path)?;
186            }
187            #[cfg(unix)]
188            std::os::unix::fs::symlink(&path, &latest_path)?;
189
190            println!("💾 Saved mega session to {}", path.display());
191        }
192
193        Ok(())
194    }
195
196    /// Save session in binary .m8 format
197    fn save_session_m8(&self, session: &MegaSession, path: &Path) -> Result<()> {
198        let mut buffer = Vec::new();
199
200        // Magic header: "M8MEGA" (6 bytes)
201        buffer.write_all(b"M8MEGA")?;
202
203        // Version
204        buffer.push(0x01);
205
206        // Session ID length + ID
207        let id_bytes = session.session_id.as_bytes();
208        buffer.push(id_bytes.len() as u8);
209        buffer.write_all(id_bytes)?;
210
211        // Timestamps (16 bytes total)
212        buffer.write_all(&session.started_at.timestamp().to_le_bytes())?;
213        buffer.write_all(&session.last_updated.timestamp().to_le_bytes())?;
214
215        // Frequency (8 bytes)
216        buffer.write_all(&session.frequency.to_le_bytes())?;
217
218        // Token count (4 bytes)
219        buffer.write_all(&(session.token_count as u32).to_le_bytes())?;
220
221        // Context level (4 bytes)
222        buffer.write_all(&session.context_level.to_le_bytes())?;
223
224        // Number of topics (1 byte)
225        buffer.push(session.key_topics.len() as u8);
226        for topic in &session.key_topics {
227            buffer.push(topic.len() as u8);
228            buffer.write_all(topic.as_bytes())?;
229        }
230
231        // Number of breakthroughs (1 byte)
232        buffer.push(session.breakthroughs.len() as u8);
233        for breakthrough in &session.breakthroughs {
234            // Timestamp
235            buffer.write_all(&breakthrough.timestamp.timestamp().to_le_bytes())?;
236
237            // Description
238            let desc_bytes = breakthrough.description.as_bytes();
239            buffer.write_all(&(desc_bytes.len() as u16).to_le_bytes())?;
240            buffer.write_all(desc_bytes)?;
241
242            // Keywords
243            buffer.push(breakthrough.keywords.len() as u8);
244            for kw in &breakthrough.keywords {
245                buffer.push(kw.len() as u8);
246                buffer.write_all(kw.as_bytes())?;
247            }
248        }
249
250        // Number of snapshots (1 byte)
251        buffer.push(session.consciousness_snapshots.len() as u8);
252        for snapshot in &session.consciousness_snapshots {
253            buffer.write_all(&snapshot.timestamp.timestamp().to_le_bytes())?;
254            buffer.write_all(&snapshot.context_percentage.to_le_bytes())?;
255
256            // Compressed state length + data
257            buffer.write_all(&(snapshot.compressed_state.len() as u32).to_le_bytes())?;
258            buffer.write_all(&snapshot.compressed_state)?;
259        }
260
261        // Checksum
262        let checksum = buffer.iter().fold(0u8, |acc, &b| acc ^ b);
263        buffer.push(checksum);
264
265        fs::write(path, buffer)?;
266        Ok(())
267    }
268
269    /// Load session from disk
270    fn load_session(&self, session_id: &str) -> Result<MegaSession> {
271        let _path = self.get_session_path(session_id);
272        // Implementation would deserialize from .m8 format
273        // For now, return a mock
274        Ok(MegaSession {
275            session_id: session_id.to_string(),
276            started_at: Utc::now(),
277            last_updated: Utc::now(),
278            frequency: 42.73,
279            token_count: 0,
280            context_level: 0.0,
281            key_topics: Vec::new(),
282            breakthroughs: Vec::new(),
283            consciousness_snapshots: Vec::new(),
284            working_directory: std::env::current_dir()?,
285            files_touched: Vec::new(),
286        })
287    }
288
289    /// Get session file path
290    fn get_session_path(&self, session_id: &str) -> PathBuf {
291        self.session_dir.join(format!("{}.m8", session_id))
292    }
293
294    /// List all saved mega sessions
295    pub fn list_sessions(&self) -> Result<Vec<String>> {
296        let mut sessions = Vec::new();
297
298        for entry in fs::read_dir(&self.session_dir)? {
299            let entry = entry?;
300            let path = entry.path();
301
302            if path.extension().and_then(|e| e.to_str()) == Some("m8") {
303                if let Some(stem) = path.file_stem() {
304                    let name = stem.to_string_lossy().to_string();
305                    if name != "latest_mega" {
306                        // Skip symlink
307                        sessions.push(name);
308                    }
309                }
310            }
311        }
312
313        sessions.sort();
314        Ok(sessions)
315    }
316
317    /// Get session statistics
318    pub fn get_stats(&self) -> String {
319        if let Some(ref session) = self.current_session {
320            format!(
321                "📊 Mega Session Stats:\n\
322                 • ID: {}\n\
323                 • Started: {}\n\
324                 • Duration: {} minutes\n\
325                 • Frequency: {:.1} Hz\n\
326                 • Tokens: {}\n\
327                 • Context: {:.0}%\n\
328                 • Topics: {}\n\
329                 • Breakthroughs: {}\n\
330                 • Snapshots: {}",
331                session.session_id,
332                session.started_at.format("%Y-%m-%d %H:%M"),
333                (Utc::now() - session.started_at).num_minutes(),
334                session.frequency,
335                session.token_count,
336                session.context_level * 100.0,
337                session.key_topics.len(),
338                session.breakthroughs.len(),
339                session.consciousness_snapshots.len()
340            )
341        } else {
342            "No active mega session".to_string()
343        }
344    }
345}