diff --git a/rust/crates/rusty-claude-cli/src/main.rs b/rust/crates/rusty-claude-cli/src/main.rs index 5e6aa0e..e0b836c 100644 --- a/rust/crates/rusty-claude-cli/src/main.rs +++ b/rust/crates/rusty-claude-cli/src/main.rs @@ -1338,12 +1338,17 @@ fn run_resume_command( ), }); } + let backup_path = write_session_clear_backup(session, session_path)?; + let previous_session_id = session.session_id.clone(); let cleared = Session::new(); + let new_session_id = cleared.session_id.clone(); cleared.save_to_path(session_path)?; Ok(ResumeCommandOutcome { session: cleared, message: Some(format!( - "Cleared resumed session file {}.", + "Session cleared\n Mode resumed session reset\n Previous session {previous_session_id}\n Backup {}\n Resume previous claw --resume {}\n New session {new_session_id}\n Session file {}", + backup_path.display(), + backup_path.display(), session_path.display() )), }) @@ -2111,6 +2116,7 @@ impl LiveCli { return Ok(false); } + let previous_session = self.session.clone(); let session_state = Session::new(); self.session = create_managed_session_handle(&session_state.session_id)?; let runtime = build_runtime( @@ -2126,10 +2132,13 @@ impl LiveCli { )?; self.replace_runtime(runtime)?; println!( - "Session cleared\n Mode fresh session\n Preserved model {}\n Permission mode {}\n Session {}", + "Session cleared\n Mode fresh session\n Previous session {}\n Resume previous /resume {}\n Preserved model {}\n Permission mode {}\n New session {}\n Session file {}", + previous_session.id, + previous_session.id, self.model, self.permission_mode.as_str(), self.session.id, + self.session.path.display(), ); Ok(true) } @@ -2665,6 +2674,27 @@ fn format_session_modified_age(modified_epoch_millis: u128) -> String { } } +fn write_session_clear_backup( + session: &Session, + session_path: &Path, +) -> Result> { + let backup_path = session_clear_backup_path(session_path); + session.save_to_path(&backup_path)?; + Ok(backup_path) +} + +fn session_clear_backup_path(session_path: &Path) -> PathBuf { + let timestamp = std::time::SystemTime::now() + .duration_since(UNIX_EPOCH) + .ok() + .map_or(0, |duration| duration.as_millis()); + let file_name = session_path + .file_name() + .and_then(|value| value.to_str()) + .unwrap_or("session.jsonl"); + session_path.with_file_name(format!("{file_name}.before-clear-{timestamp}.bak")) +} + fn render_repl_help() -> String { [ "REPL".to_string(), diff --git a/rust/crates/rusty-claude-cli/tests/resume_slash_commands.rs b/rust/crates/rusty-claude-cli/tests/resume_slash_commands.rs index 99ebce9..ccef95f 100644 --- a/rust/crates/rusty-claude-cli/tests/resume_slash_commands.rs +++ b/rust/crates/rusty-claude-cli/tests/resume_slash_commands.rs @@ -5,6 +5,7 @@ use std::process::{Command, Output}; use std::sync::atomic::{AtomicU64, Ordering}; use std::time::{SystemTime, UNIX_EPOCH}; +use runtime::ContentBlock; use runtime::Session; static TEMP_COUNTER: AtomicU64 = AtomicU64::new(0); @@ -51,7 +52,12 @@ fn resumed_binary_accepts_slash_commands_with_arguments() { assert!(stdout.contains("Export")); assert!(stdout.contains("wrote transcript")); assert!(stdout.contains(export_path.to_str().expect("utf8 path"))); - assert!(stdout.contains("Cleared resumed session file")); + assert!(stdout.contains("Session cleared")); + assert!(stdout.contains("Mode resumed session reset")); + assert!(stdout.contains("Previous session")); + assert!(stdout.contains("Resume previous claw --resume")); + assert!(stdout.contains("Backup ")); + assert!(stdout.contains("Session file ")); let export = fs::read_to_string(&export_path).expect("export file should exist"); assert!(export.contains("# Conversation Export")); @@ -59,6 +65,18 @@ fn resumed_binary_accepts_slash_commands_with_arguments() { let restored = Session::load_from_path(&session_path).expect("cleared session should load"); assert!(restored.messages.is_empty()); + + let backup_path = stdout + .lines() + .find_map(|line| line.strip_prefix(" Backup ")) + .map(PathBuf::from) + .expect("clear output should include backup path"); + let backup = Session::load_from_path(&backup_path).expect("backup session should load"); + assert_eq!(backup.messages.len(), 1); + assert!(matches!( + backup.messages[0].blocks.first(), + Some(ContentBlock::Text { text }) if text == "ship the slash command harness" + )); } #[test]