Files
zesdex/apps/domain/src/cms/memory.rs
T
asepharyana da2ed6da25 feat(tui): add usage overlay and sidebar for displaying usage statistics and tasks
feat(tui): implement status bar with connection and turn state indicators
feat(tui): create workflow panel for agent status and progress visualization
feat(web): introduce web frontend interface with static file serving
feat(ws): add WebSocket interface for real-time communication and session management
2026-07-20 09:04:57 +07:00

110 lines
3.9 KiB
Rust

//! Pure domain entity for long-term agent memory.
//!
//! A `Memory` entry stores a named, kinded piece of information (lesson,
//! reference, fact) with frontmatter metadata and free-form markdown
//! content. Memories are persisted as individual `.md` files with YAML
//! frontmatter.
//!
//! # Architecture
//! This is a pure data structure with **no I/O logic**. Load/save
//! responsibilities live in `MemoryRepository` (domain::repository).
//!
//! ## Utility Functions
//! - `slugify()` — converts a name string into a filesystem-safe slug
//! - `path()` — computes the on-disk path for a given memory name
//!
//! Both are pure computations that take parameters and perform no I/O.
use std::path::{Path, PathBuf};
use serde::{Deserialize, Serialize};
/// A single memory entry with frontmatter metadata and markdown content.
///
/// ## Fields
/// - `name` — unique identifier / title for this memory
/// - `description` — short summary of what this memory contains
/// - `content` — free-form markdown body
/// - `kind` — category/tag (e.g. "lesson", "reference", "fact")
/// - `created_at` — Unix timestamp of creation
/// - `updated_at` — Unix timestamp of last modification
/// - `outcome` — optional outcome of applying this memory
/// - `lifecycle` — lifecycle stage (e.g. "active", "archived")
/// - `scope` — optional scope qualifier (which session/context this applies to)
/// - `before_snippet` — optional context snapshot before memory was applied
/// - `after_snippet` — optional context snapshot after memory was applied
/// - `provenances` — list of origin identifiers that created or confirmed this memory
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Memory {
pub name: String,
pub description: String,
pub content: String,
pub kind: String,
pub created_at: i64,
pub updated_at: i64,
pub outcome: Option<String>,
pub lifecycle: String,
pub scope: Option<String>,
pub before_snippet: Option<String>,
pub after_snippet: Option<String>,
pub provenances: Vec<String>,
}
impl Memory {
/// Convert an arbitrary string into a filesystem-safe slug.
///
/// Flow: lowercase → replace non-alphanumeric chars with `-` →
/// collapse/trim repeated `-`.
///
/// Returns `None` if the result is empty or exceeds 80 characters.
pub fn slugify(s: &str) -> Option<String> {
// Phase 1: replace every non-alphanumeric character with '-'
let slug: String = s
.to_lowercase()
.chars()
.map(|c| if c.is_ascii_alphanumeric() { c } else { '-' })
.collect();
// Phase 2: collapse consecutive '-' separators
let slug: String = slug
.split('-')
.filter(|s| !s.is_empty())
.collect::<Vec<_>>()
.join("-");
if slug.is_empty() || slug.len() > 80 {
return None;
}
Some(slug)
}
/// Compute the on-disk path for a memory of the given name.
///
/// ## Parameters
/// - `memory_dir` — the base directory for memory storage
/// - `name` — the memory name (will be slugified internally)
///
/// Falls back to `"memory.md"` when the name slugifies to an empty
/// or invalid string.
///
/// ## Pure Computation
/// This function performs **no I/O** — it only computes a path.
pub fn path(memory_dir: &Path, name: &str) -> PathBuf {
let slug = Self::slugify(name).unwrap_or_else(|| "memory".to_string());
let clean: String = format!("{slug}.md")
.chars()
.map(|c| {
if c.is_ascii_alphanumeric() || c == '.' || c == '-' {
c
} else {
'-'
}
})
.collect();
let clean = clean.trim_start_matches('.').to_string();
memory_dir.join(if clean.is_empty() {
"memory.md".to_string()
} else {
clean
})
}
}