diff options
| author | JJ Liebig <jonathan.liebig@gmail.com> | 2026-08-11 02:24:02 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-08-11 03:24:02 +0300 |
| commit | 7ae4b056a0ca478e584fa282c45b528134cc80c9 (patch) | |
| tree | 5eb8da51353327f2bb1b7ef35f76cafe9b471dee /src/server | |
| parent | 350f001320ec4a119f09c38157973126962a6121 (diff) | |
fix: wait for agent prompt readiness (#2537)
* fix: wait for agent prompt readiness
refs #2410
* fix: return pane updates after tab bar command
refs #2410
---------
Co-authored-by: Can Celik <ogulcancelik@gmail.com>
Diffstat (limited to 'src/server')
| -rw-r--r-- | src/server/headless.rs | 85 |
1 files changed, 78 insertions, 7 deletions
diff --git a/src/server/headless.rs b/src/server/headless.rs index 3425225c..ff6f367d 100644 --- a/src/server/headless.rs +++ b/src/server/headless.rs @@ -1963,7 +1963,7 @@ impl HeadlessServer { let suppress_active_tab_notifications = self.active_tab_suppresses_notifications(is_active_tab); - if self.app.state.sound.allows(update.known_agent) { + if !update.suppress_completion && self.app.state.sound.allows(update.known_agent) { if let Some(sound) = crate::app::actions::notification_sound_for_state_change_with_agent_labels( suppress_active_tab_notifications, @@ -2325,7 +2325,11 @@ impl HeadlessServer { // Headless mode disables local sound playback separately from the // sound policy so reloads can keep server-side notification policy live. self.sync_foreground_client_state(); - self.app.handle_internal_event(ev); + let suppress_completion = self + .app + .handle_internal_event_with_pane_updates(ev) + .iter() + .any(|update| update.pane_id == pane_id_val && update.suppress_completion); // Forward sound notification to clients when server-side sound policy allows it. let is_active_tab = self @@ -2344,7 +2348,8 @@ impl HeadlessServer { let next_state = self.pane_effective_state(pane_id_val); let next_agent_label = self.pane_effective_agent_label(pane_id_val); - if self.app.state.toast_config.delay_seconds == 0 + if !suppress_completion + && self.app.state.toast_config.delay_seconds == 0 && self.app.state.sound.allows(agent_val) { if let Some(sound) = @@ -2364,7 +2369,8 @@ impl HeadlessServer { } } - let toast_msg = if self.app.state.toast_config.delay_seconds == 0 + let toast_msg = if !suppress_completion + && self.app.state.toast_config.delay_seconds == 0 && should_forward_toast_to_clients(self.app.state.toast_config.delivery) { if self.app.state.toast.is_some() && self.app.state.toast != toast_before { @@ -2416,7 +2422,11 @@ impl HeadlessServer { let prev_agent_label = self.pane_effective_agent_label(pane_id_val); self.sync_foreground_client_state(); - self.app.handle_internal_event(ev); + let suppress_completion = self + .app + .handle_internal_event_with_pane_updates(ev) + .iter() + .any(|update| update.pane_id == pane_id_val && update.suppress_completion); // Forward sound notification based on the effective transition when // server-side sound policy allows it. @@ -2436,7 +2446,8 @@ impl HeadlessServer { let next_state = self.pane_effective_state(pane_id_val); let next_agent_label = self.pane_effective_agent_label(pane_id_val); - if self.app.state.toast_config.delay_seconds == 0 + if !suppress_completion + && self.app.state.toast_config.delay_seconds == 0 && self.app.state.sound.allows(agent_val) { if let Some(sound) = @@ -2456,7 +2467,8 @@ impl HeadlessServer { } } - let toast_msg = if self.app.state.toast_config.delay_seconds == 0 + let toast_msg = if !suppress_completion + && self.app.state.toast_config.delay_seconds == 0 && should_forward_toast_to_clients(self.app.state.toast_config.delivery) { if self.app.state.toast.is_some() && self.app.state.toast != toast_before { @@ -11128,6 +11140,65 @@ next_tab = "" } #[test] + fn startup_idle_does_not_forward_completion() { + let mut server = test_headless_server(); + let workspace = crate::workspace::Workspace::test_new("active"); + let pane_id = workspace.tabs[0].root_pane; + server.app.state.workspaces = vec![workspace]; + server.app.state.ensure_test_terminals(); + server.app.state.active = Some(0); + server.app.state.toast_config.delivery = crate::config::ToastDelivery::System; + server.app.state.toast_config.delay_seconds = 0; + server.app.state.sound.enabled = true; + + assert!( + server.handle_internal_event_with_forwarding(AppEvent::AgentProcessDetected { + pane_id, + agent: crate::detect::Agent::Pi, + observed_at: Instant::now(), + }) + ); + + let (client_tx, client_control_rx, _client_rx) = test_client_writer(); + server.clients.insert( + 1, + ClientConnection::new( + (80, 24), + crate::kitty_graphics::HostCellSize::default(), + crate::terminal_theme::TerminalTheme::default(), + Some(false), + 1, + RenderEncoding::SemanticFrame, + Some(client_tx), + ), + ); + server.foreground_client_id = Some(1); + server.sync_foreground_client_state(); + while client_control_rx + .recv_timeout(Duration::from_millis(20)) + .is_ok() + {} + + assert!( + server.handle_internal_event_with_forwarding(AppEvent::StateChanged { + pane_id, + agent: Some(crate::detect::Agent::Pi), + state: crate::detect::AgentState::Idle, + visible_blocker: false, + visible_working: false, + process_exited: false, + observed_at: Instant::now(), + }) + ); + assert!( + client_control_rx + .recv_timeout(Duration::from_millis(50)) + .is_err(), + "startup readiness should not forward a completion notification" + ); + } + + #[test] fn delayed_agent_notification_forwards_after_deadline() { let mut server = test_headless_server(); let background = crate::workspace::Workspace::test_new("background"); |
