use anyhow::{Context, Result};
use std::path::PathBuf;
pub async fn run(session_id: Option<String>) -> Result<()> {
let store = perspt_store::SessionStore::new().context("Failed to open session store")?;
match session_id {
Some(id) => resume_session(&store, &id).await,
None => list_sessions(&store).await,
}
}
async fn list_sessions(store: &perspt_store::SessionStore) -> Result<()> {
let sessions = store.list_recent_sessions(10)?;
if sessions.is_empty() {
println!("No sessions found.");
println!();
println!("Start a new session with: perspt agent \"<task>\"");
return Ok(());
}
println!("Recent Sessions:");
println!("{}", "β".repeat(80));
println!("{:<12} {:<12} {:<50}", "SESSION ID", "STATUS", "TASK");
println!("{}", "β".repeat(80));
for session in &sessions {
let task_display = if session.task.len() > 48 {
format!("{}...", &session.task[..45])
} else {
session.task.clone()
};
let id_short = if session.session_id.len() > 10 {
format!("{}...", &session.session_id[..8])
} else {
session.session_id.clone()
};
let status_emoji = match session.status.as_str() {
"COMPLETED" => "β
",
"RUNNING" => "π",
"PAUSED" => "βΈοΈ",
"FAILED" => "β",
_ => "β",
};
println!(
"{:<12} {} {:<10} {:<50}",
id_short, status_emoji, session.status, task_display
);
}
println!("{}", "β".repeat(80));
println!();
println!("Resume with: perspt resume <session_id>");
println!("Resume last: perspt resume --last");
Ok(())
}
async fn resume_session(store: &perspt_store::SessionStore, session_id: &str) -> Result<()> {
let actual_id = if session_id == "--last" {
let sessions = store.list_recent_sessions(1)?;
if sessions.is_empty() {
anyhow::bail!("No sessions found to resume");
}
sessions[0].session_id.clone()
} else {
session_id.to_string()
};
let session = store
.get_session(&actual_id)?
.context(format!("Session not found: {}", actual_id))?;
println!("π Resuming session: {}", session.session_id);
println!("π Task: {}", session.task);
println!("π Working dir: {}", session.working_dir);
println!("π Status: {}", session.status);
let node_states = store.get_node_states(&actual_id)?;
let completed_count = node_states
.iter()
.filter(|n| n.state == "COMPLETED" || n.state == "STABLE")
.count();
println!(
"β
Completed nodes: {}/{}",
completed_count,
node_states.len()
);
let branches = store.get_provisional_branches(&actual_id)?;
if !branches.is_empty() {
let active = branches.iter().filter(|b| b.state == "active").count();
let flushed = branches.iter().filter(|b| b.state == "flushed").count();
if active > 0 || flushed > 0 {
println!(
"πΏ Provisional: {} active, {} flushed (of {} total)",
active,
flushed,
branches.len()
);
}
}
let escalations = store.get_escalation_reports(&actual_id)?;
if !escalations.is_empty() {
println!("β οΈ Escalations: {} recorded", escalations.len());
}
if let Some(latest) = node_states.last() {
if let Ok(energy_history) = store.get_energy_history(&actual_id, &latest.node_id) {
if let Some(last_energy) = energy_history.last() {
println!(
"β‘ Last energy: V(x)={:.3} (syn={:.2} str={:.2} log={:.2})",
last_energy.v_total, last_energy.v_syn, last_energy.v_str, last_energy.v_log
);
}
}
}
let total_retries: i32 = node_states.iter().map(|n| n.attempt_count.max(0)).sum();
if total_retries > 0 {
println!("β» Total retries: {}", total_retries);
}
if session.status == "COMPLETED" {
println!();
println!("βΉοΈ This session is already completed.");
println!(" Start a new session with: perspt agent \"<task>\"");
return Ok(());
}
store.update_session_status(&actual_id, "RUNNING")?;
let working_dir = PathBuf::from(&session.working_dir);
if !working_dir.exists() {
anyhow::bail!(
"Working directory no longer exists: {}",
session.working_dir
);
}
println!();
println!("π Resuming orchestration...");
println!();
let mut orchestrator = perspt_agent::SRBNOrchestrator::new(
working_dir.clone(),
false, );
let rehydrated = match orchestrator.rehydrate_session(&actual_id) {
Ok(snapshot) => {
let total = snapshot.node_details.len();
let terminal = snapshot
.node_details
.iter()
.filter(|d| {
matches!(
d.record.state.as_str(),
"Completed"
| "COMPLETED"
| "STABLE"
| "Failed"
| "FAILED"
| "Aborted"
| "ABORTED"
)
})
.count();
println!(
"π¦ Rehydrated {} nodes ({} terminal, {} to resume)",
total,
terminal,
total - terminal
);
let missing_goals = snapshot
.node_details
.iter()
.filter(|d| d.record.goal.is_none())
.count();
if missing_goals > 0 {
println!(
"β οΈ Degraded: {} nodes missing goal metadata (older session)",
missing_goals
);
}
true
}
Err(e) => {
println!(
"β οΈ Cannot rehydrate session ({}), falling back to fresh run",
e
);
false
}
};
let result = if rehydrated {
orchestrator.run_resumed().await
} else {
orchestrator.run(session.task.clone()).await
};
match result {
Ok(()) => {
store.update_session_status(&actual_id, "COMPLETED")?;
println!("β
Session completed successfully!");
}
Err(e) => {
store.update_session_status(&actual_id, "FAILED")?;
println!("β Session failed: {}", e);
return Err(e);
}
}
Ok(())
}