aboutsummaryrefslogtreecommitdiff
path: root/src/server
diff options
context:
space:
mode:
authorJJ Liebig <jonathan.liebig@gmail.com>2026-08-11 02:24:02 +0200
committerGitHub <noreply@github.com>2026-08-11 03:24:02 +0300
commit7ae4b056a0ca478e584fa282c45b528134cc80c9 (patch)
tree5eb8da51353327f2bb1b7ef35f76cafe9b471dee /src/server
parent350f001320ec4a119f09c38157973126962a6121 (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.rs85
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");