Skip to main content

agentic_workflow_mcp/session/
mod.rs

1use std::path::{Path, PathBuf};
2use std::sync::Arc;
3use std::time::{Duration, Instant};
4
5use tokio::sync::Mutex;
6
7use agentic_workflow::engine::store::WorkflowStore;
8
9/// Autonomic profile for session behavior.
10#[derive(Debug, Clone)]
11pub enum AutonomicProfile {
12    /// Desktop: 30s autosave, moderate maintenance
13    Desktop,
14    /// Server: 15s autosave, aggressive maintenance
15    Server,
16    /// Terminal: 60s autosave, minimal maintenance
17    Terminal,
18}
19
20impl AutonomicProfile {
21    pub fn autosave_interval(&self) -> Duration {
22        match self {
23            Self::Desktop => Duration::from_secs(30),
24            Self::Server => Duration::from_secs(15),
25            Self::Terminal => Duration::from_secs(60),
26        }
27    }
28
29    pub fn maintenance_interval(&self) -> Duration {
30        match self {
31            Self::Desktop => Duration::from_secs(300),
32            Self::Server => Duration::from_secs(120),
33            Self::Terminal => Duration::from_secs(600),
34        }
35    }
36}
37
38/// Session manager — wraps WorkflowStore with lifecycle management.
39pub struct SessionManager {
40    store: WorkflowStore,
41    session_id: String,
42    profile: AutonomicProfile,
43    started_at: Instant,
44    last_save: Instant,
45    mutation_count: u64,
46    data_path: PathBuf,
47}
48
49impl SessionManager {
50    /// Open a session with a workflow store at the given path.
51    pub fn open(path: impl AsRef<Path>) -> anyhow::Result<Self> {
52        let path = path.as_ref().to_path_buf();
53        let store = WorkflowStore::open(&path)
54            .map_err(|e| anyhow::anyhow!("Failed to open store: {}", e))?;
55
56        let session_id = uuid::Uuid::new_v4().to_string();
57        let now = Instant::now();
58
59        eprintln!("SessionManager: opened session {} at {}", session_id, path.display());
60
61        Ok(Self {
62            store,
63            session_id,
64            profile: AutonomicProfile::Desktop,
65            started_at: now,
66            last_save: now,
67            mutation_count: 0,
68            data_path: path,
69        })
70    }
71
72    /// Open in-memory session (testing).
73    pub fn open_memory() -> Self {
74        Self {
75            store: WorkflowStore::open_memory(),
76            session_id: uuid::Uuid::new_v4().to_string(),
77            profile: AutonomicProfile::Desktop,
78            started_at: Instant::now(),
79            last_save: Instant::now(),
80            mutation_count: 0,
81            data_path: PathBuf::new(),
82        }
83    }
84
85    /// Get mutable access to the store.
86    pub fn store_mut(&mut self) -> &mut WorkflowStore {
87        self.mutation_count += 1;
88        &mut self.store
89    }
90
91    /// Get read access to the store.
92    pub fn store(&self) -> &WorkflowStore {
93        &self.store
94    }
95
96    /// Get session ID.
97    pub fn session_id(&self) -> &str {
98        &self.session_id
99    }
100
101    /// Set autonomic profile.
102    pub fn set_profile(&mut self, profile: AutonomicProfile) {
103        self.profile = profile;
104    }
105
106    /// Get mutation count since session start.
107    pub fn mutation_count(&self) -> u64 {
108        self.mutation_count
109    }
110
111    /// Get session uptime.
112    pub fn uptime(&self) -> Duration {
113        self.started_at.elapsed()
114    }
115
116    /// Run periodic maintenance tick (autosave if needed).
117    pub fn maintenance_tick(&mut self) -> anyhow::Result<()> {
118        let since_save = self.last_save.elapsed();
119
120        if since_save >= self.profile.autosave_interval() && self.store.is_dirty() {
121            self.store.save()
122                .map_err(|e| anyhow::anyhow!("Autosave failed: {}", e))?;
123            self.last_save = Instant::now();
124            eprintln!(
125                "SessionManager: autosave ({} mutations, {}s since last save)",
126                self.mutation_count,
127                since_save.as_secs()
128            );
129        }
130
131        Ok(())
132    }
133
134    /// Force save.
135    pub fn force_save(&mut self) -> anyhow::Result<()> {
136        self.store.save()
137            .map_err(|e| anyhow::anyhow!("Save failed: {}", e))?;
138        self.last_save = Instant::now();
139        Ok(())
140    }
141
142    /// Get session stats.
143    pub fn stats(&self) -> serde_json::Value {
144        serde_json::json!({
145            "session_id": self.session_id,
146            "workflow_count": self.store.count(),
147            "mutation_count": self.mutation_count,
148            "uptime_secs": self.uptime().as_secs(),
149            "data_path": self.data_path.display().to_string(),
150            "profile": format!("{:?}", self.profile),
151            "is_dirty": self.store.is_dirty(),
152        })
153    }
154
155    /// Shutdown session gracefully.
156    pub fn shutdown(&mut self) -> anyhow::Result<()> {
157        if self.store.is_dirty() {
158            self.force_save()?;
159        }
160        eprintln!(
161            "SessionManager: shutdown session {} ({} mutations, {}s uptime)",
162            self.session_id, self.mutation_count, self.uptime().as_secs()
163        );
164        Ok(())
165    }
166}
167
168impl Drop for SessionManager {
169    fn drop(&mut self) {
170        if let Err(e) = self.shutdown() {
171            eprintln!("SessionManager: shutdown error on drop: {}", e);
172        }
173    }
174}
175
176/// Create a shared session manager for use in MCP server.
177pub fn create_shared_session(path: impl AsRef<Path>) -> anyhow::Result<Arc<Mutex<SessionManager>>> {
178    let manager = SessionManager::open(path)?;
179    Ok(Arc::new(Mutex::new(manager)))
180}
181
182/// Spawn autosave background task.
183pub async fn spawn_autosave(session: Arc<Mutex<SessionManager>>) {
184    tokio::spawn(async move {
185        let mut interval = tokio::time::interval(Duration::from_secs(10));
186        loop {
187            interval.tick().await;
188            let mut mgr = session.lock().await;
189            if let Err(e) = mgr.maintenance_tick() {
190                eprintln!("Autosave tick error: {}", e);
191            }
192        }
193    });
194}