From 6fb806f0938a695e48ac39efcda920aeee7e1554 Mon Sep 17 00:00:00 2001 From: Jakob Stender Guldberg Date: Mon, 5 Oct 2026 09:36:06 +0200 Subject: [PATCH] feat(agent-mode): ask the agent to fix the last failed command Alt+Shift+Enter (or Alt+Enter on an empty prompt) sends the last command in flyline's history, plus $?, to the configured agent. Commands excluded from history never reach the agent. On a non-empty buffer it asks the agent to fix the buffer text instead. --- README.md | 8 ++++++ src/app/actions/keyboard.rs | 49 +++++++++++++++++++++++++++++++++++++ src/history/mod.rs | 1 - 3 files changed, 57 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c860e6b6..6d87f8c8 100644 --- a/README.md +++ b/README.md @@ -428,6 +428,14 @@ After setting up your agent with flyline, you can pass the buffer to your agent Flyline will syntax highlight the suggested commands and render markdown output. +To fix a failed command, press Alt+Enter on an empty prompt. Flyline sends your previous command and its exit status to your agent. + +Alt+Shift+Enter does the same on an empty prompt. On a non-empty buffer it asks the agent to fix what you typed (without an exit status), where Alt+Enter would send it as a regular question. Alt+Shift+Enter needs a terminal that supports extended key codes. If yours doesn't, bind the fix to a single key combination: + +```bash +flyline key bind Alt+z always=fixLastCommand +``` + # Mouse support Click to move your cursor, select suggestions, and hover for tooltips. diff --git a/src/app/actions/keyboard.rs b/src/app/actions/keyboard.rs index fa616221..f6f51f27 100644 --- a/src/app/actions/keyboard.rs +++ b/src/app/actions/keyboard.rs @@ -109,6 +109,10 @@ pub enum KeyEventAction { RunAgentMode, #[strum(message = "Run the agent mode help command")] AgentModeRunHelpCommand, + #[strum( + message = "Ask the agent to fix the buffer, or the previous command if the buffer is empty" + )] + FixLastCommand, #[strum( message = "Submit the current command or insert a newline if the buffer is an incomplete expression" )] @@ -541,6 +545,38 @@ impl KeyEventAction { app.show_agent_mode_not_configured_error(); } } + KeyEventAction::FixLastCommand => { + let last_command = app + .long_lived + .history_manager + .entries() + .last() + .map(|entry| entry.command.clone()); + let Some((agent_cmd, buffer)) = app.resolve_agent_command(false) else { + if app.buffer.buffer().trim().is_empty() + && let Some(last) = last_command + { + app.buffer.replace_buffer(&last); + } + app.show_agent_mode_not_configured_error(); + return; + }; + let prompt = if buffer.trim().is_empty() { + let Some(last) = last_command else { + return; + }; + app.long_lived + .agent_prompt_history_manager + .push_entry(last.clone()); + format!( + "This Bash command exited with status {}. Fix it:\n{last}", + crate::shell::backend().last_command_exit_status() + ) + } else { + format!("Fix this Bash command:\n{}", buffer.trim()) + }; + app.start_agent_mode(agent_cmd, &prompt); + } KeyEventAction::AgentModeRunHelpCommand => { if let ContentMode::AgentError { suggested_setup_command, @@ -2336,11 +2372,24 @@ pub static DEFAULT_BINDINGS: LazyLock> = LazyLock::new(|| { ContextVar::BufferHasAgentModePrefix + ContextVar::EditingBufferMode, &[KeyEventAction::RunAgentMode], ), + Binding::new( + &expand_variations![M::ALT + KC::Enter.into()], + ContextVar::BufferIsEmpty.into(), + &[KeyEventAction::FixLastCommand], + ), Binding::new( &expand_variations![M::ALT + KC::Enter.into()], ContextVar::Always.into(), &[KeyEventAction::RunAgentMode], ), + Binding::new( + &[ + (M::ALT | M::SHIFT) + KC::Enter.into(), + (M::META | M::SHIFT) + KC::Enter.into(), + ], + ContextVar::Always.into(), + &[KeyEventAction::FixLastCommand], + ), Binding::new( &expand_variations![KC::Enter.into()], ContextVar::FuzzyHistorySearchAgentCommands.into(), diff --git a/src/history/mod.rs b/src/history/mod.rs index 01eff171..1d4e109d 100644 --- a/src/history/mod.rs +++ b/src/history/mod.rs @@ -696,7 +696,6 @@ impl HistoryManager { } } - #[allow(dead_code)] pub fn entries(&self) -> &[HistoryEntry] { &self.entries }