use anyhow::Result;
use navi_core::memory::{
DreamOptions, HistoryEvent, MemoryManager, build_rebuild_context, run_checkpoint_writer,
run_distill_maintenance, run_dream_maintenance_with_options,
};
use navi_core::{LoadedConfig, ModelMessage, ModelRole, effective_context_window};
use navi_sdk::build_provider_for_config;
use std::path::Path;
pub async fn handle_memory_command(
action: crate::MemoryAction,
loaded_config: &LoadedConfig,
cwd: &Path,
) -> Result<()> {
let memory_config = &loaded_config.config.memory;
let manager = MemoryManager::new(
cwd.to_path_buf(),
loaded_config.data_dir.clone(),
memory_config,
)?;
match action {
crate::MemoryAction::Status => {
println!("Memory System Status:");
println!(" Memory Root: {:?}", manager.store.memory_root);
println!(
" Global Memory Path: {:?}",
manager.store.global_memory_path
);
println!(" History DB Path: {:?}", manager.history.db_path);
let sessions = manager.history.list_sessions()?;
if let Some(session) = sessions.first() {
let rebuild_count = manager.history.get_rebuild_count(&session.id)?;
let checkpoint_count = manager.history.get_checkpoint_count(&session.id)?;
let last_checkpoint = manager.history.get_last_checkpoint_time(&session.id)?;
let event_count = manager.history.get_event_count(&session.id, "message")?;
println!(" Last Active Session ID: {}", session.id);
println!(" Current Cycle (Rebuilds + 1): {}", rebuild_count + 1);
println!(
" Checkpoint Thresholds Crossed: {} (Configured: {:?})",
checkpoint_count, memory_config.checkpoint_thresholds
);
println!(
" Rebuild Threshold: {}%",
memory_config.rebuild_threshold * 100.0
);
println!(
" Last Checkpoint Time: {}",
last_checkpoint.unwrap_or_else(|| "None".to_string())
);
println!(" Total Messages Logged in Session: {}", event_count);
} else {
println!(" Last Active Session ID: None");
println!(" Current Cycle: 1");
println!(
" Checkpoint Thresholds Crossed: 0 (Configured: {:?})",
memory_config.checkpoint_thresholds
);
println!(
" Rebuild Threshold: {}%",
memory_config.rebuild_threshold * 100.0
);
println!(" Last Checkpoint Time: None");
println!(" Total Messages Logged in Session: 0");
}
}
crate::MemoryAction::Checkpoint => {
let sessions = manager.history.list_sessions()?;
let session_id = if let Some(s) = sessions.first() {
s.id.clone()
} else {
let fallback_id =
format!("manual-{}", navi_core::session::current_unix_timestamp());
println!(
"No active session found. Running manual checkpoint under temporary session ID: {}",
fallback_id
);
fallback_id
};
let events = manager.history.get_recent_events(&session_id, None)?;
let messages = to_model_messages(&events);
let provider = build_provider_for_config(loaded_config)?;
let model_name = &loaded_config.config.model.name;
println!(
"Running manual checkpoint writer for session '{}' using model '{}'...",
session_id, model_name
);
run_checkpoint_writer(
&session_id,
&messages,
&manager.store,
provider.as_ref(),
model_name,
)
.await?;
println!("Checkpoint writer finished successfully.");
}
crate::MemoryAction::RebuildPreview => {
let sessions = manager.history.list_sessions()?;
let session_id = if let Some(s) = sessions.first() {
s.id.clone()
} else {
anyhow::bail!(
"No active session found in history database. Cannot generate rebuild preview."
);
};
let events = manager.history.get_recent_events(&session_id, None)?;
let messages = to_model_messages(&events);
let context_window = effective_context_window(&loaded_config.config);
let boot_context = build_rebuild_context(
&messages,
&manager.store,
context_window,
memory_config.injected_context_token_budget,
);
println!("=== REBUILD CONTEXT PREVIEW ===");
println!("{}", boot_context);
}
crate::MemoryAction::History {
query,
limit,
session_id,
} => {
let results = manager
.history
.search_history(&query, session_id.as_deref(), limit)?;
println!("History Search Results for '{}':", query);
if results.is_empty() {
println!(" No matching events found.");
} else {
for event in results {
println!(
"--- Event ID: {} (Session: {}, Seq: {}, Type: {}) ---",
event.id, event.session_id, event.sequence, event.event_type
);
println!(" Created: {}", event.created_at);
if let Some(role) = event.role {
println!(" Role: {}", role);
}
if let Some(tool) = event.tool_name {
println!(" Tool: {}", tool);
}
if let Some(content) = event.content {
println!(" Content: {}", content);
}
if let Some(output) = event.tool_output {
println!(" Tool Output: {}", output);
}
}
}
}
crate::MemoryAction::Dream {
apply,
sessions,
instructions,
} => {
let provider = build_provider_for_config(loaded_config)?;
let model_name = &loaded_config.config.model.name;
println!(
"Running memory dream maintenance using model '{}'...",
model_name
);
let result = run_dream_maintenance_with_options(
&manager.store,
&manager.history,
provider.as_ref(),
model_name,
DreamOptions {
session_limit: sessions,
instructions,
apply,
},
)
.await?;
println!("Dream maintenance finished successfully.");
println!(" Output directory: {}", result.output_dir.display());
println!(
" Project memory candidate: {}",
result.project_memory_path.display()
);
println!(
" Global memory candidate: {}",
result.global_memory_path.display()
);
println!(" Report: {}", result.report_path.display());
println!(" Applied to active memory: {}", result.applied);
}
crate::MemoryAction::Distill => {
let provider = build_provider_for_config(loaded_config)?;
let model_name = &loaded_config.config.model.name;
println!(
"Running process distillation maintenance using model '{}'...",
model_name
);
run_distill_maintenance(
&manager.store,
&manager.history,
provider.as_ref(),
model_name,
)
.await?;
println!("Distill maintenance finished successfully.");
}
crate::MemoryAction::Doctor => {
println!("Memory Doctor diagnostics:");
println!(
" [OK] Memory config loaded. Enabled: {}",
memory_config.enabled
);
let root = &manager.store.memory_root;
println!(" Memory Root Directory: {:?}", root);
if root.exists() {
println!(" [OK] Root path exists and is readable.");
} else {
println!(
" [WARN] Root path does not exist yet. It will be created when needed."
);
}
let cp_path = manager.store.checkpoint_path();
println!(" Checkpoint File: {:?}", cp_path);
if cp_path.exists() {
match std::fs::read_to_string(&cp_path) {
Ok(content) => println!(" [OK] Readable ({} bytes)", content.len()),
Err(e) => println!(" [ERROR] Failed to read checkpoint: {}", e),
}
} else {
println!(" [WARN] Does not exist yet.");
}
let notes_path = manager.store.notes_path();
println!(" Notes File: {:?}", notes_path);
if notes_path.exists() {
match std::fs::read_to_string(¬es_path) {
Ok(content) => println!(" [OK] Readable ({} bytes)", content.len()),
Err(e) => println!(" [ERROR] Failed to read notes: {}", e),
}
} else {
println!(" [WARN] Does not exist yet.");
}
let pm_path = manager.store.project_memory_path();
println!(" Project Memory File: {:?}", pm_path);
if pm_path.exists() {
match std::fs::read_to_string(&pm_path) {
Ok(content) => println!(" [OK] Readable ({} bytes)", content.len()),
Err(e) => println!(" [ERROR] Failed to read project memory: {}", e),
}
} else {
println!(" [WARN] Does not exist yet.");
}
let gm_path = manager.store.global_memory_path;
println!(" Global Memory File: {:?}", gm_path);
if gm_path.exists() {
match std::fs::read_to_string(&gm_path) {
Ok(content) => println!(" [OK] Readable ({} bytes)", content.len()),
Err(e) => println!(" [ERROR] Failed to read global memory: {}", e),
}
} else {
println!(" [WARN] Does not exist yet.");
}
let db_path = &manager.history.db_path;
println!(" History DB: {:?}", db_path);
if db_path.exists() {
match manager.history.doctor_check() {
Ok(logs) => {
for log in logs {
if log.starts_with("ERROR:") {
println!(" [ERROR] {}", log);
} else {
println!(" [OK] {}", log);
}
}
}
Err(e) => println!(" [ERROR] Failed database check: {}", e),
}
} else {
println!(" [WARN] DB file does not exist yet.");
}
println!("Doctor diagnostics completed.");
}
}
Ok(())
}
fn to_model_messages(events: &[HistoryEvent]) -> Vec<ModelMessage> {
events
.iter()
.filter(|e| e.event_type == "message")
.map(|e| {
let role = match e.role.as_deref() {
Some("system") => ModelRole::System,
Some("assistant") => ModelRole::Assistant,
Some("tool") => ModelRole::Tool,
_ => ModelRole::User,
};
ModelMessage {
role,
content: e.content.clone().unwrap_or_default(),
content_parts: Vec::new(),
tool_call_id: None,
tool_name: e.tool_name.clone(),
tool_calls: Vec::new(),
created_at: None,
thinking_content: None,
}
})
.collect()
}