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
This commit is contained in:
asepharyana
2026-07-20 09:04:57 +07:00
parent bceba665c0
commit da2ed6da25
454 changed files with 13979 additions and 29539 deletions
@@ -0,0 +1,183 @@
//! Markdown filebacked `MemoryRepository`.
//! Each memory is stored as a `.md` file with YAML-ish frontmatter.
use std::collections::HashMap;
use std::io::Write;
use std::path::Path;
use zesdex_domain::cms::{Memory, MemoryRepository, RepositoryError};
/// File-based `MemoryRepository` that stores memories as `.md` files with
/// YAML-ish frontmatter.
#[derive(Debug, Clone, Default)]
pub struct MarkdownMemoryRepository;
impl MarkdownMemoryRepository {
pub fn new() -> Self {
Self
}
fn build_frontmatter(memory: &Memory) -> String {
let outcome_line = memory
.outcome
.as_ref()
.map(|o| format!("outcome: {o}\n"))
.unwrap_or_default();
let scope_line = memory
.scope
.as_ref()
.map(|s| format!("scope: {s}\n"))
.unwrap_or_default();
let before_line = memory
.before_snippet
.as_ref()
.map(|s| format!("before: {s}\n"))
.unwrap_or_default();
let after_line = memory
.after_snippet
.as_ref()
.map(|s| format!("after: {s}\n"))
.unwrap_or_default();
let prov_line = if memory.provenances.is_empty() {
String::new()
} else {
format!("provenances: {}\n", memory.provenances.join(", "))
};
format!(
"name: {name}\ndescription: {desc}\nkind: {kind}\n\
created_at: {created}\nupdated_at: {updated}\nlifecycle: {lifecycle}\n\
{outcome}{scope}{before}{after}{prov}",
name = memory.name,
desc = memory.description,
kind = memory.kind,
created = memory.created_at,
updated = memory.updated_at,
lifecycle = memory.lifecycle,
outcome = outcome_line,
scope = scope_line,
before = before_line,
after = after_line,
prov = prov_line,
)
}
fn parse_frontmatter(front: &str) -> HashMap<String, String> {
front
.lines()
.filter_map(|l| {
let mut it = l.splitn(2, ':');
Some((
it.next()?.trim().to_string(),
it.next()?.trim().to_string(),
))
})
.collect()
}
fn parse(content: &str) -> std::io::Result<Memory> {
let content = content.strip_prefix("---\n").unwrap_or(content);
let parts: Vec<&str> = content.splitn(2, "\n---\n").collect();
if parts.len() < 2 {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"missing frontmatter",
));
}
let front = Self::parse_frontmatter(parts[0]);
let body = parts.get(1).unwrap_or(&"").trim().to_string();
Ok(Memory {
name: front.get("name").cloned().unwrap_or_default(),
description: front.get("description").cloned().unwrap_or_default(),
content: body,
kind: front
.get("kind")
.cloned()
.unwrap_or_else(|| "reference".to_string()),
created_at: front
.get("created_at")
.and_then(|v| v.parse().ok())
.unwrap_or(0),
updated_at: front
.get("updated_at")
.and_then(|v| v.parse().ok())
.unwrap_or(0),
outcome: front.get("outcome").cloned().filter(|s| !s.is_empty()),
lifecycle: front
.get("lifecycle")
.cloned()
.unwrap_or_else(|| "new".to_string()),
scope: front.get("scope").cloned().filter(|s| !s.is_empty()),
before_snippet: front.get("before").cloned().filter(|s| !s.is_empty()),
after_snippet: front.get("after").cloned().filter(|s| !s.is_empty()),
provenances: front
.get("provenances")
.cloned()
.map(|s| s.split(", ").map(String::from).collect())
.unwrap_or_default(),
})
}
}
impl MemoryRepository for MarkdownMemoryRepository {
fn list(&self, memory_dir: &Path) -> Result<Vec<String>, RepositoryError> {
let Ok(entries) = std::fs::read_dir(memory_dir) else {
return Ok(Vec::new());
};
let slugs: Vec<String> = entries
.filter_map(std::result::Result::ok)
.filter(|e| e.path().extension().is_some_and(|x| x == "md"))
.filter_map(|e| {
let name = e.file_name().to_string_lossy().to_string();
if name == "MEMORY.md" {
return None;
}
name.strip_suffix(".md")
.map(std::string::ToString::to_string)
})
.collect();
Ok(slugs)
}
fn load(&self, memory_dir: &Path, name: &str) -> Result<Memory, RepositoryError> {
let path = Memory::path(memory_dir, name);
let content = std::fs::read_to_string(&path)?;
let memory = Self::parse(&content)
.map_err(|e| RepositoryError::Other(format!("failed to parse memory '{name}': {e}")))?;
Ok(memory)
}
fn save(&self, memory_dir: &Path, memory: &Memory) -> Result<(), RepositoryError> {
let path = Memory::path(memory_dir, &memory.name);
let parent = path.parent().unwrap();
std::fs::create_dir_all(parent)?;
let frontmatter = Self::build_frontmatter(memory);
let content = format!("---\n{frontmatter}---\n\n{}", memory.content);
let tmp = parent.join(format!(".{}.tmp", uuid::Uuid::new_v4()));
{
let mut f = std::fs::OpenOptions::new()
.create(true)
.truncate(true)
.write(true)
.open(&tmp)?;
f.write_all(content.as_bytes())?;
f.sync_all()?;
}
std::fs::rename(&tmp, &path)?;
if let Some(p) = path.parent() {
if let Ok(d) = std::fs::File::open(p) {
let _ = d.sync_all();
}
}
Ok(())
}
fn delete(&self, memory_dir: &Path, name: &str) -> Result<(), RepositoryError> {
let path = Memory::path(memory_dir, name);
if path.exists() {
std::fs::remove_file(&path)?;
}
Ok(())
}
}