diff --git a/crates/tui/src/tui/history.rs b/crates/tui/src/tui/history.rs index 9ca7fb54..252e4288 100644 --- a/crates/tui/src/tui/history.rs +++ b/crates/tui/src/tui/history.rs @@ -957,7 +957,7 @@ impl ExecCell { )); } else if self.status == ToolStatus::Running && self.source == ExecSource::Assistant { lines.extend(wrap_plain_line( - " Ctrl+B opens shell controls.", + " Ctrl+B backgrounds this command.", Style::default().fg(palette::TEXT_MUTED), width, )); @@ -5075,7 +5075,7 @@ mod tests { assert!(text.contains("running line 1")); assert!(text.contains("running line 2")); - assert!(!text.contains("Ctrl+B opens shell controls")); + assert!(!text.contains("Ctrl+B backgrounds this command")); } #[test] diff --git a/crates/tui/src/tui/ui.rs b/crates/tui/src/tui/ui.rs index be17a2d9..ba25afb9 100644 --- a/crates/tui/src/tui/ui.rs +++ b/crates/tui/src/tui/ui.rs @@ -8771,10 +8771,30 @@ fn render_toast_stack_overlay( } pub(crate) fn request_foreground_shell_background(app: &mut App) { - if !app.is_loading || !active_foreground_shell_running(app) { + if !app.is_loading { app.status_message = Some("No foreground shell command to background".to_string()); return; } + if !active_foreground_shell_running(app) { + // #3032 AC3: name the reason backgrounding is unavailable — + // interactive execs and non-shell blocking tools are visibly running + // but cannot be detached, and a generic shrug reads like a bug. + let reason = if terminal_pause_has_live_owner(app) { + "the running command is interactive" + } else if app + .active_cell + .as_ref() + .is_some_and(|active| !active.is_empty()) + { + "the running tool is not a foreground shell command" + } else { + "no foreground shell command is running" + }; + app.status_message = Some(format!( + "Cannot background: {reason}. Press Ctrl+C to cancel the turn, or wait for completion." + )); + return; + } let Some(shell_manager) = app.runtime_services.shell_manager.clone() else { app.status_message = Some("Shell manager is not attached".to_string()); diff --git a/docs/KEYBINDINGS.md b/docs/KEYBINDINGS.md index 6782e4ee..e3710bc7 100644 --- a/docs/KEYBINDINGS.md +++ b/docs/KEYBINDINGS.md @@ -11,6 +11,7 @@ Bindings are not (yet) user-configurable — tracked for a future release (#436, | `F1` or `Ctrl-/` | Toggle the help overlay | | `Ctrl-K` | Open the command palette (slash-command finder) | | `Ctrl-C` | Cancel current turn / dismiss modal / arm-then-confirm quit | +| `Ctrl-B` | Background the running foreground shell command (turn continues; the command becomes a `/jobs` background job) | | `Ctrl-D` | Quit (only when the composer is empty) | | `Tab` | Cycle TUI mode: Plan → Agent → YOLO → Plan | | `Shift-Tab` | Cycle reasoning effort: off → high → max → off | diff --git a/docs/OPERATIONS_RUNBOOK.md b/docs/OPERATIONS_RUNBOOK.md index d53a8a45..8e98ac8a 100644 --- a/docs/OPERATIONS_RUNBOOK.md +++ b/docs/OPERATIONS_RUNBOOK.md @@ -28,7 +28,7 @@ Checks: 3. Confirm no local sandbox/permission deadlock in tool output Actions: -1. If a foreground shell command is running, press `Ctrl+B` and choose whether to background it or cancel the current turn. +1. If a foreground shell command is running, press `Ctrl+B` to move it to the background (the turn keeps running and the command becomes a background job under `/jobs`); use `Ctrl+C` instead if you want to cancel the turn. 2. If the command was started in the background, ask the assistant to cancel it with `exec_shell_cancel` and the returned task id. 3. Use `Esc` or `Ctrl+C` to interrupt the current turn when you want to stop the request itself. 4. Retry prompt; if still failing, restart TUI.