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
This commit is contained in:
@@ -512,6 +512,9 @@ pub async fn run() -> Result<()> {
|
|||||||
// Then load README for project context
|
// Then load README for project context
|
||||||
let readme_content = read_project_readme(&workspace_dir);
|
let readme_content = read_project_readme(&workspace_dir);
|
||||||
|
|
||||||
|
// Load project memory if available
|
||||||
|
let memory_content = read_project_memory(&workspace_dir);
|
||||||
|
|
||||||
// Create project model
|
// Create project model
|
||||||
let project = if cli.autonomous {
|
let project = if cli.autonomous {
|
||||||
if let Some(requirements_text) = &cli.requirements {
|
if let Some(requirements_text) = &cli.requirements {
|
||||||
@@ -587,12 +590,23 @@ pub async fn run() -> Result<()> {
|
|||||||
// Initialize agent
|
// Initialize agent
|
||||||
// ui_writer will be created conditionally based on machine mode
|
// ui_writer will be created conditionally based on machine mode
|
||||||
|
|
||||||
// Combine AGENTS.md and README content if both exist
|
// Combine AGENTS.md, README, and memory content if they exist
|
||||||
let combined_content = match (agents_content.clone(), readme_content.clone()) {
|
let combined_content = {
|
||||||
(Some(agents), Some(readme)) => Some(format!("{}\n\n{}", agents, readme)),
|
let mut parts = Vec::new();
|
||||||
(Some(agents), None) => Some(agents),
|
if let Some(agents) = agents_content.clone() {
|
||||||
(None, Some(readme)) => Some(readme),
|
parts.push(agents);
|
||||||
(None, None) => None,
|
}
|
||||||
|
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
|
// 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
|
// Run autonomous mode with the accumulated requirements
|
||||||
let autonomous_result = tokio::select! {
|
let autonomous_result = tokio::select! {
|
||||||
result = run_autonomous(
|
result = run_autonomous(
|
||||||
agent,
|
agent,
|
||||||
project,
|
project,
|
||||||
cli.show_prompt,
|
cli.show_prompt,
|
||||||
cli.show_code,
|
cli.show_code,
|
||||||
cli.max_turns,
|
cli.max_turns,
|
||||||
cli.quiet,
|
cli.quiet,
|
||||||
cli.codebase_fast_start.clone(),
|
cli.codebase_fast_start.clone(),
|
||||||
) => result,
|
) => result.map(Some),
|
||||||
_ = tokio::signal::ctrl_c() => {
|
_ = tokio::signal::ctrl_c() => {
|
||||||
output.print("\n⚠️ Autonomous run cancelled by user (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 {
|
match autonomous_result {
|
||||||
Ok(_) => {
|
Ok(Some(_returned_agent)) => {
|
||||||
|
// Session continuation was already saved by run_autonomous
|
||||||
output.print("");
|
output.print("");
|
||||||
output.print("✅ Autonomous run completed");
|
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) => {
|
Err(e) => {
|
||||||
output.print("");
|
output.print("");
|
||||||
output.print(&format!("❌ Autonomous run failed: {}", e));
|
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
|
// Execute task, autonomous mode, or start interactive mode
|
||||||
if cli.autonomous {
|
if cli.autonomous {
|
||||||
// Autonomous mode with coach-player feedback loop
|
// Autonomous mode with coach-player feedback loop
|
||||||
run_autonomous(
|
let _agent = run_autonomous(
|
||||||
agent,
|
agent,
|
||||||
project,
|
project,
|
||||||
cli.show_prompt,
|
cli.show_prompt,
|
||||||
@@ -1447,6 +1468,30 @@ fn read_project_readme(workspace_dir: &Path) -> Option<String> {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Read project memory if available
|
||||||
|
fn read_project_memory(workspace_dir: &Path) -> Option<String> {
|
||||||
|
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
|
/// Extract the main heading or title from README content
|
||||||
fn extract_readme_heading(readme_content: &str) -> Option<String> {
|
fn extract_readme_heading(readme_content: &str) -> Option<String> {
|
||||||
// Find the README section in the combined content
|
// Find the README section in the combined content
|
||||||
@@ -1594,6 +1639,7 @@ async fn run_interactive<W: UiWriter>(
|
|||||||
// Check what was loaded
|
// Check what was loaded
|
||||||
let has_agents = content.contains("Agent Configuration");
|
let has_agents = content.contains("Agent Configuration");
|
||||||
let has_readme = content.contains("Project README");
|
let has_readme = content.contains("Project README");
|
||||||
|
let has_memory = content.contains("Project Memory");
|
||||||
|
|
||||||
if has_agents {
|
if has_agents {
|
||||||
print!(
|
print!(
|
||||||
@@ -1615,6 +1661,14 @@ async fn run_interactive<W: UiWriter>(
|
|||||||
ResetColor
|
ResetColor
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if has_memory {
|
||||||
|
print!(
|
||||||
|
"{}🧠 Project memory loaded{}\n",
|
||||||
|
SetForegroundColor(Color::DarkGrey),
|
||||||
|
ResetColor
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Display workspace path
|
// Display workspace path
|
||||||
@@ -2354,7 +2408,7 @@ async fn run_autonomous(
|
|||||||
max_turns: usize,
|
max_turns: usize,
|
||||||
quiet: bool,
|
quiet: bool,
|
||||||
codebase_fast_start: Option<PathBuf>,
|
codebase_fast_start: Option<PathBuf>,
|
||||||
) -> Result<()> {
|
) -> Result<Agent<ConsoleUiWriter>> {
|
||||||
let start_time = std::time::Instant::now();
|
let start_time = std::time::Instant::now();
|
||||||
let output = SimpleOutput::new();
|
let output = SimpleOutput::new();
|
||||||
let mut turn_metrics: Vec<TurnMetrics> = Vec::new();
|
let mut turn_metrics: Vec<TurnMetrics> = Vec::new();
|
||||||
@@ -2411,7 +2465,7 @@ async fn run_autonomous(
|
|||||||
output.print(&generate_turn_histogram(&turn_metrics));
|
output.print(&generate_turn_histogram(&turn_metrics));
|
||||||
output.print(&"=".repeat(60));
|
output.print(&"=".repeat(60));
|
||||||
|
|
||||||
return Ok(());
|
return Ok(agent);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read requirements
|
// Read requirements
|
||||||
@@ -2453,7 +2507,7 @@ async fn run_autonomous(
|
|||||||
output.print(&generate_turn_histogram(&turn_metrics));
|
output.print(&generate_turn_histogram(&turn_metrics));
|
||||||
output.print(&"=".repeat(60));
|
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
|
// Save session continuation for resume capability
|
||||||
agent.save_session_continuation(None);
|
agent.save_session_continuation(None);
|
||||||
|
|
||||||
Ok(())
|
Ok(agent)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -434,7 +434,32 @@ pub fn format_session_time(created_at: &str) -> String {
|
|||||||
match chrono::DateTime::parse_from_rfc3339(created_at) {
|
match chrono::DateTime::parse_from_rfc3339(created_at) {
|
||||||
Ok(dt) => {
|
Ok(dt) => {
|
||||||
let local: chrono::DateTime<chrono::Local> = dt.into();
|
let local: chrono::DateTime<chrono::Local> = 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(),
|
Err(_) => created_at.to_string(),
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user