From 3fcef587e8efe4821543b7ac5040824025a62e87 Mon Sep 17 00:00:00 2001 From: "Dhanji R. Prasanna" Date: Sun, 11 Jan 2026 06:13:27 +0800 Subject: [PATCH] Fix /resume to show all sessions and use human-readable timestamps - Change run_autonomous to return Agent instead of () so session continuation is properly saved in accumulative mode - Update format_session_time to show relative times ("2 hours ago", "yesterday") for recent sessions and dates for older ones - Handle Ctrl+C cancellation gracefully with informative message --- crates/g3-cli/src/lib.rs | 96 +++++++++++++++++----- crates/g3-core/src/session_continuation.rs | 27 +++++- 2 files changed, 101 insertions(+), 22 deletions(-) diff --git a/crates/g3-cli/src/lib.rs b/crates/g3-cli/src/lib.rs index 9dd8875..418063e 100644 --- a/crates/g3-cli/src/lib.rs +++ b/crates/g3-cli/src/lib.rs @@ -512,6 +512,9 @@ pub async fn run() -> Result<()> { // Then load README for project context let readme_content = read_project_readme(&workspace_dir); + // Load project memory if available + let memory_content = read_project_memory(&workspace_dir); + // Create project model let project = if cli.autonomous { if let Some(requirements_text) = &cli.requirements { @@ -587,12 +590,23 @@ pub async fn run() -> Result<()> { // Initialize agent // ui_writer will be created conditionally based on machine mode - // Combine AGENTS.md and README content if both exist - let combined_content = match (agents_content.clone(), readme_content.clone()) { - (Some(agents), Some(readme)) => Some(format!("{}\n\n{}", agents, readme)), - (Some(agents), None) => Some(agents), - (None, Some(readme)) => Some(readme), - (None, None) => None, + // Combine AGENTS.md, README, and memory content if they exist + let combined_content = { + let mut parts = Vec::new(); + if let Some(agents) = agents_content.clone() { + parts.push(agents); + } + if let Some(readme) = readme_content.clone() { + parts.push(readme); + } + if let Some(memory) = memory_content.clone() { + parts.push(memory); + } + if parts.is_empty() { + None + } else { + Some(parts.join("\n\n")) + } }; // Execute task, autonomous mode, or start interactive mode based on machine mode @@ -1168,25 +1182,32 @@ async fn run_accumulative_mode( // Run autonomous mode with the accumulated requirements let autonomous_result = tokio::select! { result = run_autonomous( - agent, - project, - cli.show_prompt, - cli.show_code, - cli.max_turns, - cli.quiet, - cli.codebase_fast_start.clone(), - ) => result, + agent, + project, + cli.show_prompt, + cli.show_code, + cli.max_turns, + cli.quiet, + cli.codebase_fast_start.clone(), + ) => result.map(Some), _ = tokio::signal::ctrl_c() => { output.print("\n⚠️ Autonomous run cancelled by user (Ctrl+C)"); - Ok(()) + // Agent was moved into run_autonomous and is now dropped + // We can't save continuation here, but the next iteration will create a new agent + Ok(None) } }; match autonomous_result { - Ok(_) => { + Ok(Some(_returned_agent)) => { + // Session continuation was already saved by run_autonomous output.print(""); output.print("✅ Autonomous run completed"); } + Ok(None) => { + // Ctrl+C case - agent was dropped, continuation not saved + output.print(" (session continuation not saved due to cancellation)"); + } Err(e) => { output.print(""); output.print(&format!("❌ Autonomous run failed: {}", e)); @@ -1280,7 +1301,7 @@ async fn run_with_console_mode( // Execute task, autonomous mode, or start interactive mode if cli.autonomous { // Autonomous mode with coach-player feedback loop - run_autonomous( + let _agent = run_autonomous( agent, project, cli.show_prompt, @@ -1447,6 +1468,30 @@ fn read_project_readme(workspace_dir: &Path) -> Option { None } +/// Read project memory if available +fn read_project_memory(workspace_dir: &Path) -> Option { + let memory_path = workspace_dir.join(".g3").join("memory.md"); + + if memory_path.exists() { + match std::fs::read_to_string(&memory_path) { + Ok(content) => { + let size = if content.len() < 1000 { + format!("{} chars", content.len()) + } else { + format!("{:.1}k chars", content.len() as f64 / 1000.0) + }; + Some(format!( + "🧠 Project Memory ({}):\n\n{}", + size, content + )) + } + Err(_) => None, + } + } else { + None + } +} + /// Extract the main heading or title from README content fn extract_readme_heading(readme_content: &str) -> Option { // Find the README section in the combined content @@ -1594,6 +1639,7 @@ async fn run_interactive( // Check what was loaded let has_agents = content.contains("Agent Configuration"); let has_readme = content.contains("Project README"); + let has_memory = content.contains("Project Memory"); if has_agents { print!( @@ -1615,6 +1661,14 @@ async fn run_interactive( ResetColor ); } + + if has_memory { + print!( + "{}🧠 Project memory loaded{}\n", + SetForegroundColor(Color::DarkGrey), + ResetColor + ); + } } // Display workspace path @@ -2354,7 +2408,7 @@ async fn run_autonomous( max_turns: usize, quiet: bool, codebase_fast_start: Option, -) -> Result<()> { +) -> Result> { let start_time = std::time::Instant::now(); let output = SimpleOutput::new(); let mut turn_metrics: Vec = Vec::new(); @@ -2411,7 +2465,7 @@ async fn run_autonomous( output.print(&generate_turn_histogram(&turn_metrics)); output.print(&"=".repeat(60)); - return Ok(()); + return Ok(agent); } // Read requirements @@ -2453,7 +2507,7 @@ async fn run_autonomous( output.print(&generate_turn_histogram(&turn_metrics)); output.print(&"=".repeat(60)); - return Ok(()); + return Ok(agent); } }; @@ -3076,5 +3130,5 @@ Remember: Be clear in your review and concise in your feedback. APPROVE iff the // Save session continuation for resume capability agent.save_session_continuation(None); - Ok(()) + Ok(agent) } diff --git a/crates/g3-core/src/session_continuation.rs b/crates/g3-core/src/session_continuation.rs index ae0f1c1..f293ab0 100644 --- a/crates/g3-core/src/session_continuation.rs +++ b/crates/g3-core/src/session_continuation.rs @@ -434,7 +434,32 @@ pub fn format_session_time(created_at: &str) -> String { match chrono::DateTime::parse_from_rfc3339(created_at) { Ok(dt) => { let local: chrono::DateTime = dt.into(); - local.format("%Y-%m-%d %H:%M").to_string() + let now = chrono::Local::now(); + let duration = now.signed_duration_since(local); + + // Show relative time for recent sessions, absolute for older ones + if duration.num_minutes() < 1 { + "just now".to_string() + } else if duration.num_minutes() < 60 { + format!("{} min ago", duration.num_minutes()) + } else if duration.num_hours() < 24 { + let hours = duration.num_hours(); + if hours == 1 { + "1 hour ago".to_string() + } else { + format!("{} hours ago", hours) + } + } else if duration.num_days() < 7 { + let days = duration.num_days(); + if days == 1 { + "yesterday".to_string() + } else { + format!("{} days ago", days) + } + } else { + // For older sessions, show the date + local.format("%b %d, %Y").to_string() + } } Err(_) => created_at.to_string(), }