From d704eca073d414f989ab5ef833170130d64eacf8 Mon Sep 17 00:00:00 2001 From: Paul Walker Date: Fri, 28 Feb 2025 03:01:05 +0000 Subject: [PATCH 1/3] feat: implement global command history storage Store command history in ~/.config/goose/history.txt instead of per-session. This allows command history to persist across different chat sessions. Add save_history helper function to ensure history is saved after each command. --- crates/goose-cli/src/session/input.rs | 2 +- crates/goose-cli/src/session/mod.rs | 53 ++++++++++++++++++++------- 2 files changed, 41 insertions(+), 14 deletions(-) diff --git a/crates/goose-cli/src/session/input.rs b/crates/goose-cli/src/session/input.rs index 38fd2ea586..b7215d8e11 100644 --- a/crates/goose-cli/src/session/input.rs +++ b/crates/goose-cli/src/session/input.rs @@ -40,7 +40,7 @@ pub fn get_input( }, }; - // Add valid input to history + // Add valid input to history (history saving to file is handled in the Session::interactive method) if !input.trim().is_empty() { editor.add_history_entry(input.as_str())?; } diff --git a/crates/goose-cli/src/session/mod.rs b/crates/goose-cli/src/session/mod.rs index b3826d2bef..0d32cea5b7 100644 --- a/crates/goose-cli/src/session/mod.rs +++ b/crates/goose-cli/src/session/mod.rs @@ -10,6 +10,7 @@ pub use storage::Identifier; use anyhow::Result; use etcetera::choose_app_strategy; +use etcetera::AppStrategy; use goose::agents::extension::{Envs, ExtensionConfig}; use goose::agents::Agent; use goose::message::{Message, MessageContent}; @@ -158,25 +159,41 @@ impl Session { } let mut editor = rustyline::Editor::<(), rustyline::history::DefaultHistory>::new()?; - - // Load history from messages - for msg in self - .messages - .iter() - .filter(|m| m.role == mcp_core::role::Role::User) - { - for content in msg.content.iter() { - if let Some(text) = content.as_text() { - if let Err(e) = editor.add_history_entry(text) { - eprintln!("Warning: Failed to add history entry: {}", e); - } - } + + // Create and use a global history file in ~/.config/goose directory + // This allows command history to persist across different chat sessions + // instead of being tied to each individual session's messages + let history_file = choose_app_strategy(crate::APP_STRATEGY.clone()) + .expect("goose requires a home dir") + .in_config_dir("history.txt"); + + // Ensure config directory exists + if let Some(parent) = history_file.parent() { + if !parent.exists() { + std::fs::create_dir_all(parent)?; } } + + // Load history from the global file + if history_file.exists() { + if let Err(err) = editor.load_history(&history_file) { + eprintln!("Warning: Failed to load command history: {}", err); + } + } + + // Helper function to save history after commands + let save_history = |editor: &mut rustyline::Editor<(), rustyline::history::DefaultHistory>| { + if let Err(err) = editor.save_history(&history_file) { + eprintln!("Warning: Failed to save command history: {}", err); + } + }; + output::display_greeting(); loop { match input::get_input(&mut editor)? { input::InputResult::Message(content) => { + save_history(&mut editor); + self.messages.push(Message::user().with_text(&content)); storage::persist_messages(&self.session_file, &self.messages)?; @@ -186,18 +203,24 @@ impl Session { } input::InputResult::Exit => break, input::InputResult::AddExtension(cmd) => { + save_history(&mut editor); + match self.add_extension(cmd.clone()).await { Ok(_) => output::render_extension_success(&cmd), Err(e) => output::render_extension_error(&cmd, &e.to_string()), } } input::InputResult::AddBuiltin(names) => { + save_history(&mut editor); + match self.add_builtin(names.clone()).await { Ok(_) => output::render_builtin_success(&names), Err(e) => output::render_builtin_error(&names, &e.to_string()), } } input::InputResult::ToggleTheme => { + save_history(&mut editor); + let current = output::get_theme(); let new_theme = match current { output::Theme::Light => { @@ -218,9 +241,13 @@ impl Session { } input::InputResult::Retry => continue, input::InputResult::ListPrompts => { + save_history(&mut editor); + output::render_prompts(&self.list_prompts().await) } input::InputResult::PromptCommand(opts) => { + save_history(&mut editor); + // name is required if opts.name.is_empty() { output::render_error("Prompt name argument is required"); From 8a716e89c5204b8d33a9cd659436fc41546d0671 Mon Sep 17 00:00:00 2001 From: Paul Walker Date: Fri, 28 Feb 2025 16:55:39 +0000 Subject: [PATCH 2/3] cargo fmt --- crates/goose-cli/src/session/mod.rs | 31 +++++++++++++++-------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/crates/goose-cli/src/session/mod.rs b/crates/goose-cli/src/session/mod.rs index 0d32cea5b7..0125ce2fc7 100644 --- a/crates/goose-cli/src/session/mod.rs +++ b/crates/goose-cli/src/session/mod.rs @@ -159,41 +159,42 @@ impl Session { } let mut editor = rustyline::Editor::<(), rustyline::history::DefaultHistory>::new()?; - + // Create and use a global history file in ~/.config/goose directory // This allows command history to persist across different chat sessions // instead of being tied to each individual session's messages let history_file = choose_app_strategy(crate::APP_STRATEGY.clone()) .expect("goose requires a home dir") .in_config_dir("history.txt"); - + // Ensure config directory exists if let Some(parent) = history_file.parent() { if !parent.exists() { std::fs::create_dir_all(parent)?; } } - + // Load history from the global file if history_file.exists() { if let Err(err) = editor.load_history(&history_file) { eprintln!("Warning: Failed to load command history: {}", err); } } - + // Helper function to save history after commands - let save_history = |editor: &mut rustyline::Editor<(), rustyline::history::DefaultHistory>| { - if let Err(err) = editor.save_history(&history_file) { - eprintln!("Warning: Failed to save command history: {}", err); - } - }; + let save_history = + |editor: &mut rustyline::Editor<(), rustyline::history::DefaultHistory>| { + if let Err(err) = editor.save_history(&history_file) { + eprintln!("Warning: Failed to save command history: {}", err); + } + }; output::display_greeting(); loop { match input::get_input(&mut editor)? { input::InputResult::Message(content) => { save_history(&mut editor); - + self.messages.push(Message::user().with_text(&content)); storage::persist_messages(&self.session_file, &self.messages)?; @@ -204,7 +205,7 @@ impl Session { input::InputResult::Exit => break, input::InputResult::AddExtension(cmd) => { save_history(&mut editor); - + match self.add_extension(cmd.clone()).await { Ok(_) => output::render_extension_success(&cmd), Err(e) => output::render_extension_error(&cmd, &e.to_string()), @@ -212,7 +213,7 @@ impl Session { } input::InputResult::AddBuiltin(names) => { save_history(&mut editor); - + match self.add_builtin(names.clone()).await { Ok(_) => output::render_builtin_success(&names), Err(e) => output::render_builtin_error(&names, &e.to_string()), @@ -220,7 +221,7 @@ impl Session { } input::InputResult::ToggleTheme => { save_history(&mut editor); - + let current = output::get_theme(); let new_theme = match current { output::Theme::Light => { @@ -242,12 +243,12 @@ impl Session { input::InputResult::Retry => continue, input::InputResult::ListPrompts => { save_history(&mut editor); - + output::render_prompts(&self.list_prompts().await) } input::InputResult::PromptCommand(opts) => { save_history(&mut editor); - + // name is required if opts.name.is_empty() { output::render_error("Prompt name argument is required"); From 1167d86a33be348491110cafc537bacc4225c8c7 Mon Sep 17 00:00:00 2001 From: Paul Walker Date: Fri, 28 Feb 2025 19:55:33 +0000 Subject: [PATCH 3/3] Update session module --- crates/goose-cli/src/session/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/goose-cli/src/session/mod.rs b/crates/goose-cli/src/session/mod.rs index 10deb22e6e..89b07c5d2a 100644 --- a/crates/goose-cli/src/session/mod.rs +++ b/crates/goose-cli/src/session/mod.rs @@ -243,7 +243,7 @@ impl Session { // Helper function to save history after commands let save_history = - |editor: &mut rustyline::Editor<(), rustyline::history::DefaultHistory>| { + |editor: &mut rustyline::Editor| { if let Err(err) = editor.save_history(&history_file) { eprintln!("Warning: Failed to save command history: {}", err); } @@ -303,7 +303,7 @@ impl Session { input::InputResult::Retry => continue, input::InputResult::ListPrompts(extension) => { save_history(&mut editor); - + match self.list_prompts(extension).await { Ok(prompts) => output::render_prompts(&prompts), Err(e) => output::render_error(&e.to_string()),