use crate::session::metadata::*;
use crate::session::persistence::*;
use crate::session::recovery::*;
use crate::task::manager::{TaskManager, TaskManagerConfig};
use crate::task::tree::TaskTree;
use crate::task::types::*;
use anyhow::{Context, Result};
use chrono::{DateTime, Duration, Utc};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use std::sync::Arc;
use tokio::sync::{Mutex, RwLock};
use tokio::time::{Duration as TokioDuration, interval};
use tracing::{debug, error, info, warn};
pub struct SessionManager {
session_id: SessionId,
metadata: Arc<RwLock<SessionMetadata>>,
task_manager: Arc<TaskManager>,
persistence: Arc<PersistenceManager>,
recovery: Arc<RecoveryManager>,
config: SessionManagerConfig,
auto_save_enabled: Arc<Mutex<bool>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionManagerConfig {
pub auto_save_interval_minutes: u32,
pub auto_checkpoint_interval_minutes: u32,
pub checkpoint_on_significant_progress: bool,
pub significant_progress_threshold: f32,
pub max_session_duration_hours: u32,
pub enable_crash_recovery: bool,
pub validate_on_save: bool,
pub compress_checkpoints: bool,
}
#[derive(Debug, Clone)]
pub struct SessionInitOptions {
pub name: String,
pub description: Option<String>,
pub workspace_root: PathBuf,
pub task_manager_config: TaskManagerConfig,
pub persistence_config: PersistenceConfig,
pub recovery_config: RecoveryConfig,
pub enable_auto_save: bool,
pub restore_from_checkpoint: Option<String>,
}
#[derive(Debug, Clone)]
pub struct SessionStatus {
pub id: SessionId,
pub name: String,
pub uptime: Duration,
pub total_tasks: u32,
pub completed_tasks: u32,
pub failed_tasks: u32,
pub active_tasks: u32,
pub last_checkpoint: Option<DateTime<Utc>>,
pub last_save: Option<DateTime<Utc>>,
pub memory_usage_mb: u64,
pub is_auto_save_active: bool,
}
impl SessionManager {
pub async fn new(
session_dir: PathBuf,
config: SessionManagerConfig,
init_options: SessionInitOptions,
) -> Result<Self> {
info!("Creating new session: {}", init_options.name);
let session_id = SessionId::new_v4();
let persistence = Arc::new(
PersistenceManager::new(
session_dir.clone(),
&session_id.to_string(),
init_options.persistence_config.clone(),
)
.context("Failed to create persistence manager")?,
);
let recovery = Arc::new(RecoveryManager::new(
PersistenceManager::new(
session_dir,
&session_id.to_string(),
init_options.persistence_config.clone(),
)?,
init_options.recovery_config.clone(),
));
let task_manager = Arc::new(TaskManager::new(init_options.task_manager_config));
let metadata = Arc::new(RwLock::new(SessionMetadata::new(
init_options.name,
init_options.workspace_root,
)));
let session_manager = Self {
session_id,
metadata,
task_manager,
persistence,
recovery,
config: config.clone(),
auto_save_enabled: Arc::new(Mutex::new(init_options.enable_auto_save)),
};
if let Some(checkpoint_id) = init_options.restore_from_checkpoint {
session_manager
.restore_from_checkpoint(&checkpoint_id)
.await?;
} else if config.enable_crash_recovery {
if let Ok(recovery_result) = session_manager.recovery.auto_recover().await
&& recovery_result.success
&& let Some(state) = recovery_result.recovered_state
{
session_manager.restore_session_state(state).await?;
info!("Automatically recovered session from previous state");
}
}
if init_options.enable_auto_save {
session_manager.start_auto_save().await?;
}
if session_manager.config.auto_checkpoint_interval_minutes > 0 {
session_manager.start_auto_checkpoint().await?;
}
info!("Session manager initialized: {}", session_id);
Ok(session_manager)
}
pub fn task_manager(&self) -> &Arc<TaskManager> {
&self.task_manager
}
pub async fn get_status(&self) -> Result<SessionStatus> {
let metadata = self.metadata.read().await;
let uptime = Utc::now().signed_duration_since(metadata.created_at);
let _statistics = self.task_manager.get_statistics().await?;
let active_tasks = self
.task_manager
.get_tasks_by_status(|status| matches!(status, TaskStatus::InProgress { .. }))
.await?
.len() as u32;
let auto_save_active = *self.auto_save_enabled.lock().await;
Ok(SessionStatus {
id: self.session_id,
name: metadata.name.clone(),
uptime,
total_tasks: metadata.total_tasks,
completed_tasks: metadata.completed_tasks,
failed_tasks: metadata.failed_tasks,
active_tasks,
last_checkpoint: metadata.latest_checkpoint().map(|c| c.created_at),
last_save: Some(metadata.last_updated),
memory_usage_mb: 0, is_auto_save_active: auto_save_active,
})
}
pub async fn save_session(&self) -> Result<PersistenceResult> {
debug!("Saving session state");
let session_state = self.capture_session_state().await?;
if self.config.validate_on_save {
let validation = self.recovery.validate_session_state(&session_state).await?;
if !validation.is_valid {
warn!(
"Session validation failed before save: {:?}",
validation.errors
);
}
}
let result = self.persistence.save_session_state(&session_state).await?;
{
let mut metadata = self.metadata.write().await;
metadata.last_updated = Utc::now();
}
info!("Session saved successfully: {} bytes", result.bytes_written);
Ok(result)
}
pub async fn create_checkpoint(&self, description: String) -> Result<CheckpointInfo> {
info!("Creating manual checkpoint: {}", description);
let session_state = self.capture_session_state().await?;
let checkpoint_info = self
.persistence
.create_checkpoint(
&session_state,
description,
CheckpointTrigger::Manual {
reason: "User requested".to_string(),
},
)
.await?;
{
let mut metadata = self.metadata.write().await;
metadata.add_checkpoint(checkpoint_info.clone());
}
info!("Checkpoint created: {}", checkpoint_info.id);
Ok(checkpoint_info)
}
pub async fn restore_from_checkpoint(&self, checkpoint_id: &str) -> Result<()> {
info!("Restoring session from checkpoint: {}", checkpoint_id);
if self.recovery.config.create_recovery_checkpoint {
let current_state = self.capture_session_state().await?;
let _emergency_checkpoint = self
.recovery
.create_emergency_checkpoint(¤t_state)
.await?;
}
let recovery_result = self.recovery.recover_from_checkpoint(checkpoint_id).await?;
if recovery_result.success {
if let Some(state) = recovery_result.recovered_state {
self.restore_session_state(state).await?;
info!("Successfully restored from checkpoint: {}", checkpoint_id);
}
} else {
return Err(anyhow::anyhow!(
"Failed to restore from checkpoint: {:?}",
recovery_result.errors
));
}
Ok(())
}
pub async fn list_checkpoints(
&self,
include_all_sessions: bool,
) -> Result<Vec<CheckpointInfo>> {
if include_all_sessions {
let metadata = self.metadata.read().await;
let workspace_root = &metadata.workspace_root;
Self::list_all_checkpoints_in_workspace(workspace_root).await
} else {
let checkpoint_ids = self.persistence.list_checkpoints().await?;
let metadata = self.metadata.read().await;
let available_checkpoints: Vec<CheckpointInfo> = metadata
.checkpoints
.iter()
.filter(|checkpoint| checkpoint_ids.contains(&checkpoint.id))
.cloned()
.collect();
Ok(available_checkpoints)
}
}
pub async fn create_checkpoint_in_latest_session_of_workspace(
&self,
description: String,
) -> Result<CheckpointInfo> {
let metadata = self.metadata.read().await;
let workspace_root = &metadata.workspace_root;
Self::create_checkpoint_in_latest_session(workspace_root, description).await
}
pub async fn start_auto_save(&self) -> Result<()> {
let interval_duration =
TokioDuration::from_secs(self.config.auto_save_interval_minutes as u64 * 60);
let persistence = self.persistence.clone();
let metadata = self.metadata.clone();
let task_manager = self.task_manager.clone();
let auto_save_enabled = self.auto_save_enabled.clone();
let validate_on_save = self.config.validate_on_save;
tokio::spawn(async move {
let mut interval_timer = interval(interval_duration);
loop {
interval_timer.tick().await;
let is_enabled = *auto_save_enabled.lock().await;
if !is_enabled {
continue;
}
match Self::capture_session_state_static(&metadata, &task_manager).await {
Ok(session_state) => {
if validate_on_save {
}
if let Err(e) = persistence.save_session_state(&session_state).await {
error!("Auto-save failed: {}", e);
} else {
debug!("Auto-save completed successfully");
}
}
Err(e) => {
error!("Failed to capture session state for auto-save: {}", e);
}
}
}
});
info!(
"Auto-save started with interval: {} minutes",
self.config.auto_save_interval_minutes
);
Ok(())
}
pub async fn start_auto_checkpoint(&self) -> Result<()> {
let interval_duration =
TokioDuration::from_secs(self.config.auto_checkpoint_interval_minutes as u64 * 60);
let persistence = self.persistence.clone();
let metadata = self.metadata.clone();
let task_manager = self.task_manager.clone();
tokio::spawn(async move {
let mut interval_timer = interval(interval_duration);
loop {
interval_timer.tick().await;
match Self::capture_session_state_static(&metadata, &task_manager).await {
Ok(session_state) => {
let description = format!(
"Automatic checkpoint at {}",
Utc::now().format("%Y-%m-%d %H:%M:%S")
);
if let Err(e) = persistence
.create_checkpoint(
&session_state,
description,
CheckpointTrigger::Automatic {
trigger: AutoTrigger::TimeInterval {
minutes: interval_duration.as_secs() as u32 / 60,
},
},
)
.await
{
error!("Auto-checkpoint failed: {}", e);
} else {
debug!("Auto-checkpoint created successfully");
}
}
Err(e) => {
error!("Failed to capture session state for auto-checkpoint: {}", e);
}
}
}
});
info!(
"Auto-checkpoint started with interval: {} minutes",
self.config.auto_checkpoint_interval_minutes
);
Ok(())
}
pub async fn set_auto_save_enabled(&self, enabled: bool) -> Result<()> {
let mut auto_save_enabled = self.auto_save_enabled.lock().await;
*auto_save_enabled = enabled;
info!("Auto-save {}", if enabled { "enabled" } else { "disabled" });
Ok(())
}
async fn capture_session_state(&self) -> Result<SessionState> {
Self::capture_session_state_static(&self.metadata, &self.task_manager).await
}
async fn capture_session_state_static(
metadata: &Arc<RwLock<SessionMetadata>>,
task_manager: &Arc<TaskManager>,
) -> Result<SessionState> {
let task_tree_json = task_manager.export_to_json().await?;
let task_tree: TaskTree = serde_json::from_str(&task_tree_json)?;
let current_metadata = {
let metadata_lock = metadata.read().await;
metadata_lock.clone()
};
let execution_context = ExecutionContext::default();
let file_system_state = FileSystemState::default();
Ok(SessionState {
metadata: current_metadata,
task_tree,
execution_context,
file_system_state,
})
}
async fn restore_session_state(&self, state: SessionState) -> Result<()> {
info!("Restoring session state");
let task_tree_json = serde_json::to_string(&state.task_tree)?;
self.task_manager.import_from_json(&task_tree_json).await?;
{
let mut metadata = self.metadata.write().await;
*metadata = state.metadata;
}
info!("Session state restored successfully");
Ok(())
}
pub async fn cleanup_old_checkpoints(&self) -> Result<u32> {
let cleaned_count = self.persistence.cleanup_old_checkpoints().await?;
if cleaned_count > 0 {
let remaining_checkpoints = self.persistence.list_checkpoints().await?;
let mut metadata = self.metadata.write().await;
metadata
.checkpoints
.retain(|checkpoint| remaining_checkpoints.contains(&checkpoint.id));
info!("Cleaned up {} old checkpoints", cleaned_count);
}
Ok(cleaned_count)
}
pub async fn validate_session(&self) -> Result<ValidationResult> {
let session_state = self.capture_session_state().await?;
self.recovery.validate_session_state(&session_state).await
}
pub async fn get_session_statistics(&self) -> Result<SessionStatistics> {
let metadata = self.metadata.read().await;
let uptime = Utc::now().signed_duration_since(metadata.created_at);
Ok(SessionStatistics {
total_execution_time: uptime,
total_checkpoints: metadata.checkpoints.len() as u32,
data_size_mb: 0.0, average_checkpoint_size_mb: 0.0,
recovery_count: 0,
last_recovery_at: None,
performance_metrics: PerformanceMetrics::default(),
})
}
pub async fn shutdown(&self) -> Result<()> {
info!("Shutting down session: {}", self.session_id);
self.set_auto_save_enabled(false).await?;
let _final_checkpoint = self
.create_checkpoint("session_shutdown".to_string())
.await?;
self.save_session().await?;
if self.persistence.config.auto_cleanup {
self.cleanup_old_checkpoints().await?;
}
info!("Session shutdown completed");
Ok(())
}
async fn list_all_checkpoints_in_workspace(
workspace_root: &std::path::Path,
) -> Result<Vec<CheckpointInfo>> {
use crate::env;
use std::collections::BTreeMap;
let sessions_dir = env::sessions_dir_path(workspace_root);
if !sessions_dir.exists() {
return Ok(Vec::new());
}
let mut all_checkpoints: BTreeMap<DateTime<Utc>, CheckpointInfo> = BTreeMap::new();
if let Ok(entries) = std::fs::read_dir(&sessions_dir) {
for entry in entries.flatten() {
let session_path = entry.path();
if session_path.is_dir() {
let checkpoints_dir = session_path.join("checkpoints");
if checkpoints_dir.exists()
&& let Ok(checkpoint_entries) = std::fs::read_dir(&checkpoints_dir)
{
for checkpoint_entry in checkpoint_entries.flatten() {
let checkpoint_path = checkpoint_entry.path();
if checkpoint_path.extension().and_then(|s| s.to_str()) == Some("json")
&& let Ok(content) = std::fs::read_to_string(&checkpoint_path)
{
if let Ok(session_data) =
serde_json::from_str::<serde_json::Value>(&content)
&& let Some(metadata) = session_data.get("metadata")
&& let Some(checkpoints_array) = metadata.get("checkpoints")
&& let Some(checkpoints) = checkpoints_array.as_array()
{
for checkpoint_value in checkpoints {
if let Ok(checkpoint_info) =
serde_json::from_value::<CheckpointInfo>(
checkpoint_value.clone(),
)
{
all_checkpoints.insert(
checkpoint_info.created_at,
checkpoint_info,
);
}
}
}
}
}
}
}
}
}
let mut checkpoints: Vec<_> = all_checkpoints.into_values().collect();
checkpoints.sort_by(|a, b| b.created_at.cmp(&a.created_at));
Ok(checkpoints)
}
async fn create_checkpoint_in_latest_session(
workspace_root: &std::path::Path,
description: String,
) -> Result<CheckpointInfo> {
use crate::env;
use uuid::Uuid;
let sessions_dir = env::sessions_dir_path(workspace_root);
if !sessions_dir.exists() {
return Err(anyhow::anyhow!("No sessions found in workspace"));
}
let mut latest_session_dir = None;
let mut latest_time = None;
if let Ok(entries) = std::fs::read_dir(&sessions_dir) {
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir()
&& let Ok(metadata) = entry.metadata()
&& let Ok(modified) = metadata.modified()
&& (latest_time.is_none() || Some(modified) > latest_time)
{
latest_time = Some(modified);
latest_session_dir = Some(path);
}
}
}
let session_dir =
latest_session_dir.ok_or_else(|| anyhow::anyhow!("No session directories found"))?;
let _session_config = SessionManagerConfig::default();
let _init_options = SessionInitOptions {
name: "Temporary Session for Manual Checkpoint".to_string(),
description: Some("Temporary session for creating manual checkpoint".to_string()),
workspace_root: workspace_root.to_path_buf(),
task_manager_config: TaskManagerConfig::default(),
persistence_config: PersistenceConfig::default(),
recovery_config: RecoveryConfig::default(),
enable_auto_save: false,
restore_from_checkpoint: None,
};
let checkpoint_id = format!("checkpoint_{}", Uuid::new_v4());
let now = Utc::now();
let checkpoint_info = CheckpointInfo {
id: checkpoint_id.clone(),
created_at: now,
description: description.clone(),
task_count: 0,
size_bytes: 0,
is_automatic: false,
trigger_reason: CheckpointTrigger::Manual {
reason: "User requested manual checkpoint".to_string(),
},
};
let checkpoints_dir = session_dir.join("checkpoints");
if !checkpoints_dir.exists() {
std::fs::create_dir_all(&checkpoints_dir)?;
}
let checkpoint_file = checkpoints_dir.join(format!("{}.json", checkpoint_id));
let session_data = serde_json::json!({
"metadata": {
"id": format!("{}", Uuid::new_v4()),
"name": "Manual Checkpoint",
"description": format!("Manual checkpoint: {}", description),
"created_at": now,
"last_updated": now,
"version": {
"major": 1,
"minor": 0,
"patch": 0,
"agent_version": "0.1.0",
"format_version": "1.0"
},
"checkpoints": [&checkpoint_info],
"total_tasks": 0,
"completed_tasks": 0,
"failed_tasks": 0,
"session_tags": [],
"workspace_root": workspace_root.to_string_lossy(),
"custom_properties": {}
},
"task_tree": {
"tasks": {},
"roots": [],
"metadata": {
"created_at": now,
"updated_at": now,
"version": 1,
"total_tasks_created": 0,
"statistics": {
"total_tasks": 0,
"pending_tasks": 0,
"in_progress_tasks": 0,
"completed_tasks": 0,
"failed_tasks": 0,
"blocked_tasks": 0,
"skipped_tasks": 0,
"average_completion_time": null,
"success_rate": 0.0
}
}
},
"execution_context": {
"current_working_directory": workspace_root.to_string_lossy(),
"environment_variables": {},
"active_file_watchers": [],
"resource_usage": {
"memory_usage_mb": 0,
"cpu_usage_percent": 0.0,
"disk_usage_mb": 0,
"open_file_handles": 0,
"network_connections": 0
}
},
"file_system_state": {
"tracked_files": {},
"workspace_files": [],
"temp_files": [],
"created_directories": []
}
});
std::fs::write(
&checkpoint_file,
serde_json::to_string_pretty(&session_data)?,
)?;
info!(
"Manual checkpoint created: {} in session: {:?}",
checkpoint_id,
session_dir.file_name()
);
Ok(checkpoint_info)
}
pub async fn get_session_dir(&self) -> PathBuf {
let metadata = self.metadata.read().await;
crate::env::session_dir_path(&metadata.workspace_root, &self.session_id.to_string())
}
}
impl Default for SessionManagerConfig {
fn default() -> Self {
Self {
auto_save_interval_minutes: 5,
auto_checkpoint_interval_minutes: 30,
checkpoint_on_significant_progress: true,
significant_progress_threshold: 25.0, max_session_duration_hours: 24,
enable_crash_recovery: true,
validate_on_save: true,
compress_checkpoints: false, }
}
}
impl Default for SessionInitOptions {
fn default() -> Self {
Self {
name: "Default Session".to_string(),
description: None,
workspace_root: PathBuf::from("/tmp/claude-agent-workspace"),
task_manager_config: TaskManagerConfig::default(),
persistence_config: PersistenceConfig::default(),
recovery_config: RecoveryConfig::default(),
enable_auto_save: true,
restore_from_checkpoint: None,
}
}
}