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:
@@ -0,0 +1,108 @@
|
||||
//! Database migration binary.
|
||||
//!
|
||||
//! Scans all session directories and initializes or upgrades the SQLite
|
||||
//! schema for each one. Standalone CLI tool invoked as `cargo run --bin migrate`.
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
let store = zesdex_domain::core::Store::new();
|
||||
let sessions_dir = store.base_dir.join("sessions");
|
||||
|
||||
if !sessions_dir.exists() {
|
||||
eprintln!("No sessions directory found, nothing to migrate");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut migrated = 0u32;
|
||||
let mut failed = 0u32;
|
||||
|
||||
for entry in std::fs::read_dir(&sessions_dir)? {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
if !path.is_dir() {
|
||||
continue;
|
||||
}
|
||||
|
||||
match migrate_session_msglog(&path) {
|
||||
Ok(_) => {
|
||||
migrated += 1;
|
||||
eprintln!("Migrated session: {:?}", path.file_name());
|
||||
}
|
||||
Err(e) => {
|
||||
failed += 1;
|
||||
eprintln!("Failed to migrate session {:?}: {e}", path.file_name());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
eprintln!("Migration complete: {migrated} succeeded, {failed} failed");
|
||||
if failed > 0 {
|
||||
anyhow::bail!("{failed} session(s) failed to migrate");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn migrate_session_msglog(session_dir: &Path) -> anyhow::Result<()> {
|
||||
let msglog_path = session_dir.join("messages.sqlite");
|
||||
|
||||
if let Some(parent) = msglog_path.parent() {
|
||||
std::fs::create_dir_all(parent)?;
|
||||
}
|
||||
|
||||
let conn = rusqlite::Connection::open(&msglog_path)?;
|
||||
conn.execute_batch("PRAGMA journal_mode = WAL;")?;
|
||||
conn.execute_batch("PRAGMA busy_timeout = 5000;")?;
|
||||
conn.execute_batch("PRAGMA foreign_keys = ON;")?;
|
||||
conn.execute_batch(
|
||||
"CREATE TABLE IF NOT EXISTS messages (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
session_id TEXT NOT NULL,
|
||||
role TEXT NOT NULL,
|
||||
content TEXT,
|
||||
tool_call_id TEXT,
|
||||
tool_name TEXT,
|
||||
tool_arguments TEXT,
|
||||
created_at INTEGER NOT NULL
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS archives (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
session_id TEXT NOT NULL UNIQUE,
|
||||
title TEXT,
|
||||
model TEXT,
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL,
|
||||
message_count INTEGER DEFAULT 0,
|
||||
token_count INTEGER DEFAULT 0,
|
||||
summary TEXT
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_messages_session_id ON messages(session_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_messages_created_at ON messages(created_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_archives_created_at ON archives(created_at);
|
||||
CREATE TABLE IF NOT EXISTS blobs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
session_id TEXT NOT NULL,
|
||||
blob_key TEXT NOT NULL,
|
||||
data BLOB NOT NULL,
|
||||
mime_type TEXT,
|
||||
created_at INTEGER NOT NULL,
|
||||
UNIQUE(session_id, blob_key)
|
||||
);",
|
||||
)?;
|
||||
|
||||
let version: i32 = conn
|
||||
.pragma_query_value(None, "user_version", |row| row.get(0))
|
||||
.unwrap_or(0);
|
||||
|
||||
if version < 1 {
|
||||
conn.pragma_update(None, "user_version", 1)?;
|
||||
}
|
||||
if version < 2 {
|
||||
conn.execute_batch(
|
||||
"CREATE INDEX IF NOT EXISTS idx_messages_session_role ON messages(session_id, role);",
|
||||
)?;
|
||||
conn.pragma_update(None, "user_version", 2)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user