feat: implement global command history storage (#1440)

This commit is contained in:
Paul Walker
2025-02-28 15:36:09 -05:00
committed by GitHub
parent 36726cfb80
commit 469228dee2
2 changed files with 41 additions and 13 deletions
+1 -1
View File
@@ -42,7 +42,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() { if !input.trim().is_empty() {
editor.add_history_entry(input.as_str())?; editor.add_history_entry(input.as_str())?;
} }
+40 -12
View File
@@ -12,6 +12,7 @@ pub use storage::Identifier;
use anyhow::Result; use anyhow::Result;
use completion::GooseCompleter; use completion::GooseCompleter;
use etcetera::choose_app_strategy; use etcetera::choose_app_strategy;
use etcetera::AppStrategy;
use goose::agents::extension::{Envs, ExtensionConfig}; use goose::agents::extension::{Envs, ExtensionConfig};
use goose::agents::Agent; use goose::agents::Agent;
use goose::message::{Message, MessageContent}; use goose::message::{Message, MessageContent};
@@ -223,24 +224,41 @@ impl Session {
let completer = GooseCompleter::new(self.completion_cache.clone()); let completer = GooseCompleter::new(self.completion_cache.clone());
editor.set_helper(Some(completer)); editor.set_helper(Some(completer));
// Load history from messages // Create and use a global history file in ~/.config/goose directory
for msg in self // This allows command history to persist across different chat sessions
.messages // instead of being tied to each individual session's messages
.iter() let history_file = choose_app_strategy(crate::APP_STRATEGY.clone())
.filter(|m| m.role == mcp_core::role::Role::User) .expect("goose requires a home dir")
{ .in_config_dir("history.txt");
for content in msg.content.iter() {
if let Some(text) = content.as_text() { // Ensure config directory exists
if let Err(e) = editor.add_history_entry(text) { if let Some(parent) = history_file.parent() {
eprintln!("Warning: Failed to add history entry: {}", e); 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<GooseCompleter, rustyline::history::DefaultHistory>| {
if let Err(err) = editor.save_history(&history_file) {
eprintln!("Warning: Failed to save command history: {}", err);
}
};
output::display_greeting(); output::display_greeting();
loop { loop {
match input::get_input(&mut editor)? { match input::get_input(&mut editor)? {
input::InputResult::Message(content) => { input::InputResult::Message(content) => {
save_history(&mut editor);
self.messages.push(Message::user().with_text(&content)); self.messages.push(Message::user().with_text(&content));
storage::persist_messages(&self.session_file, &self.messages)?; storage::persist_messages(&self.session_file, &self.messages)?;
@@ -250,18 +268,24 @@ impl Session {
} }
input::InputResult::Exit => break, input::InputResult::Exit => break,
input::InputResult::AddExtension(cmd) => { input::InputResult::AddExtension(cmd) => {
save_history(&mut editor);
match self.add_extension(cmd.clone()).await { match self.add_extension(cmd.clone()).await {
Ok(_) => output::render_extension_success(&cmd), Ok(_) => output::render_extension_success(&cmd),
Err(e) => output::render_extension_error(&cmd, &e.to_string()), Err(e) => output::render_extension_error(&cmd, &e.to_string()),
} }
} }
input::InputResult::AddBuiltin(names) => { input::InputResult::AddBuiltin(names) => {
save_history(&mut editor);
match self.add_builtin(names.clone()).await { match self.add_builtin(names.clone()).await {
Ok(_) => output::render_builtin_success(&names), Ok(_) => output::render_builtin_success(&names),
Err(e) => output::render_builtin_error(&names, &e.to_string()), Err(e) => output::render_builtin_error(&names, &e.to_string()),
} }
} }
input::InputResult::ToggleTheme => { input::InputResult::ToggleTheme => {
save_history(&mut editor);
let current = output::get_theme(); let current = output::get_theme();
let new_theme = match current { let new_theme = match current {
output::Theme::Light => { output::Theme::Light => {
@@ -282,12 +306,16 @@ impl Session {
} }
input::InputResult::Retry => continue, input::InputResult::Retry => continue,
input::InputResult::ListPrompts(extension) => { input::InputResult::ListPrompts(extension) => {
save_history(&mut editor);
match self.list_prompts(extension).await { match self.list_prompts(extension).await {
Ok(prompts) => output::render_prompts(&prompts), Ok(prompts) => output::render_prompts(&prompts),
Err(e) => output::render_error(&e.to_string()), Err(e) => output::render_error(&e.to_string()),
} }
} }
input::InputResult::PromptCommand(opts) => { input::InputResult::PromptCommand(opts) => {
save_history(&mut editor);
// name is required // name is required
if opts.name.is_empty() { if opts.name.is_empty() {
output::render_error("Prompt name argument is required"); output::render_error("Prompt name argument is required");