use std::path::Path;
use crate::error::SessionStoreError;
use crate::sessions_root;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
pub struct SessionSummary {
pub session_id: String,
pub turn_count: u64,
pub event_count: u64,
pub status: String,
pub updated_at: String,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
pub struct FactRecord {
pub fact: String,
pub session_id: String,
}
#[must_use]
pub fn recent_sessions(workspace: &Path, n: usize) -> Vec<SessionSummary> {
let root = sessions_root(workspace);
if !root.exists() {
return Vec::new();
}
let mut out = Vec::new();
let entries = match std::fs::read_dir(&root) {
Ok(e) => e,
Err(_) => return Vec::new(),
};
for entry in entries.filter_map(Result::ok) {
let manifest = entry.path().join("manifest.json");
if let Ok(bytes) = std::fs::read(&manifest) {
if let Ok(s) = serde_json::from_slice::<SessionSummary>(&bytes) {
out.push(s);
}
}
}
out.sort_by(|a, b| b.updated_at.cmp(&a.updated_at));
out.truncate(n);
out
}
pub fn query_facts(workspace: &Path, limit: usize) -> Result<Vec<FactRecord>, SessionStoreError> {
let root = sessions_root(workspace);
if !root.exists() {
return Ok(Vec::new());
}
let mut facts: Vec<FactRecord> = Vec::new();
let entries = std::fs::read_dir(&root).map_err(|e| SessionStoreError::io(root.clone(), e))?;
for entry in entries.filter_map(Result::ok) {
let memory = entry.path().join(crate::DERIVED_DIR).join("memory.json");
let Ok(bytes) = std::fs::read(&memory) else {
continue;
};
let Ok(value) = serde_json::from_slice::<serde_json::Value>(&bytes) else {
continue;
};
let session_id = entry.file_name().to_string_lossy().to_string();
if let Some(arr) = value.get("grounded_facts").and_then(|v| v.as_array()) {
for item in arr {
if let Some(fact) = item.get("fact").and_then(|f| f.as_str()) {
facts.push(FactRecord {
fact: fact.to_string(),
session_id: session_id.clone(),
});
}
}
}
}
facts.truncate(limit);
Ok(facts)
}