rsclaw 0.0.1-alpha.1

rsclaw: High-performance AI agent (BETA). Optimized for M4 Max and 2GB VPS. 100% compatible with openclaw
Documentation
use super::database::Store;
use crate::agent::AgentContext;
use crate::provider::ChatMessage;
use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use std::collections::VecDeque;
use std::sync::Arc;
use std::time::{Duration, SystemTime};

/// Persisted session data.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionData {
    pub id: Arc<str>,
    pub agent_name: Arc<str>,
    pub messages: Vec<ChatMessageData>,
    pub created_at: u64,
    pub last_active: u64,
}

/// Persisted chat message.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChatMessageData {
    pub role: Arc<str>,
    pub content: Arc<str>,
}

/// Session persistence manager.
pub struct SessionStore {
    store: Arc<Store>,
}

impl SessionStore {
    /// Create a new session store.
    pub fn new(store: Arc<Store>) -> Self {
        Self { store }
    }

    /// Save a session to disk.
    pub fn save(&self, session_id: &str, agent_name: &str, messages: &[ChatMessage]) -> Result<()> {
        let now = SystemTime::now()
            .duration_since(SystemTime::UNIX_EPOCH)?
            .as_secs();

        let message_data: Vec<ChatMessageData> = messages
            .iter()
            .map(|m| ChatMessageData {
                role: Arc::from(format!("{:?}", m.role).to_lowercase()),
                content: Arc::from(m.content.as_str()),
            })
            .collect();

        let session_data = SessionData {
            id: Arc::from(session_id),
            agent_name: Arc::from(agent_name),
            messages: message_data,
            created_at: now,
            last_active: now,
        };

        let serialized = serde_json::to_vec(&session_data)?;
        self.store.save_session(session_id, &serialized)?;

        tracing::info!("Session '{}' saved to disk", session_id);
        Ok(())
    }

    /// Load a session from disk.
    pub fn load(&self, session_id: &str) -> Result<Option<SessionData>> {
        match self.store.get_session(session_id)? {
            Some(data) => {
                let session: SessionData = serde_json::from_slice(&data)?;
                Ok(Some(session))
            }
            None => Ok(None),
        }
    }

    /// Delete a session.
    pub fn delete(&self, session_id: &str) -> Result<bool> {
        self.store.delete_session(session_id)
    }

    /// List all session IDs.
    pub fn list(&self) -> Result<Vec<String>> {
        self.store.list_sessions()
    }

    /// Clean up expired sessions.
    pub fn cleanup_expired(&self, max_age: Duration) -> Result<usize> {
        let sessions = self.list()?;
        let now = SystemTime::now()
            .duration_since(SystemTime::UNIX_EPOCH)?
            .as_secs();
        let max_age_secs = max_age.as_secs();

        let mut cleaned = 0;
        for session_id in sessions {
            if let Some(session) = self.load(&session_id)? {
                if now - session.last_active > max_age_secs {
                    self.delete(&session_id)?;
                    cleaned += 1;
                }
            }
        }

        if cleaned > 0 {
            tracing::info!("Cleaned up {} expired sessions", cleaned);
        }

        Ok(cleaned)
    }
}