Skip to main content

skilllite_executor/
session.rs

1//! Session store: sessions.json
2//!
3//! Schema aligned with OpenClaw: sessionId, sessionKey, token counts, compaction state.
4
5use anyhow::{Context, Result};
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8use std::fs;
9use std::path::Path;
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct SessionEntry {
13    pub session_id: String,
14    pub session_key: String,
15    pub updated_at: String,
16    #[serde(default)]
17    pub input_tokens: u64,
18    #[serde(default)]
19    pub output_tokens: u64,
20    #[serde(default)]
21    pub total_tokens: u64,
22    #[serde(default)]
23    pub context_tokens: u64,
24    #[serde(default)]
25    pub compaction_count: u32,
26    #[serde(default)]
27    pub memory_flush_at: Option<String>,
28    #[serde(default)]
29    pub memory_flush_compaction_count: Option<u32>,
30    #[serde(flatten)]
31    pub extra: HashMap<String, serde_json::Value>,
32}
33
34#[derive(Debug, Clone, Serialize, Deserialize, Default)]
35pub struct SessionStore {
36    pub sessions: HashMap<String, SessionEntry>,
37}
38
39impl SessionStore {
40    pub fn load(path: &Path) -> Result<Self> {
41        if !path.exists() {
42            return Ok(Self::default());
43        }
44        let content = fs::read_to_string(path)
45            .with_context(|| format!("Failed to read session store: {}", path.display()))?;
46        serde_json::from_str(&content).with_context(|| "Invalid sessions.json")
47    }
48
49    pub fn save(&self, path: &Path) -> Result<()> {
50        if let Some(parent) = path.parent() {
51            fs::create_dir_all(parent)?;
52        }
53        let content = serde_json::to_string_pretty(self)?;
54        fs::write(path, content)
55            .with_context(|| format!("Failed to write session store: {}", path.display()))
56    }
57
58    pub fn get(&self, session_key: &str) -> Option<&SessionEntry> {
59        self.sessions.get(session_key)
60    }
61
62    pub fn create_or_get(&mut self, session_key: &str) -> &mut SessionEntry {
63        let now = chrono_now();
64        let entry = self
65            .sessions
66            .entry(session_key.to_string())
67            .or_insert_with(|| SessionEntry {
68                session_id: format!("tx-{}", uuid_short()),
69                session_key: session_key.to_string(),
70                updated_at: now.clone(),
71                input_tokens: 0,
72                output_tokens: 0,
73                total_tokens: 0,
74                context_tokens: 0,
75                compaction_count: 0,
76                memory_flush_at: None,
77                memory_flush_compaction_count: None,
78                extra: HashMap::new(),
79            });
80        entry.updated_at = now;
81        entry
82    }
83
84    pub fn update(&mut self, session_key: &str, f: impl FnOnce(&mut SessionEntry)) -> Result<()> {
85        let entry = self
86            .sessions
87            .get_mut(session_key)
88            .context("Session not found")?;
89        f(entry);
90        entry.updated_at = chrono_now();
91        Ok(())
92    }
93
94    /// Reset compaction-related fields for a fresh session (e.g. after /new or clear).
95    pub fn reset_compaction_state(&mut self, session_key: &str) {
96        if let Some(entry) = self.sessions.get_mut(session_key) {
97            entry.compaction_count = 0;
98            entry.memory_flush_at = None;
99            entry.memory_flush_compaction_count = None;
100            entry.updated_at = chrono_now();
101        }
102    }
103}
104
105fn chrono_now() -> String {
106    use std::time::{SystemTime, UNIX_EPOCH};
107    let secs = SystemTime::now()
108        .duration_since(UNIX_EPOCH)
109        .map(|d| d.as_secs())
110        .unwrap_or(0);
111    format!("{}", secs)
112}
113
114fn uuid_short() -> String {
115    use std::time::{SystemTime, UNIX_EPOCH};
116    let t = SystemTime::now()
117        .duration_since(UNIX_EPOCH)
118        .map(|d| d.as_nanos())
119        .unwrap_or(0);
120    format!("{:x}", t % 0xFFFF_FFFF)
121}