From 03e0581ee87d58cf77b9ecdc6523900d1ca256b1 Mon Sep 17 00:00:00 2001 From: Charles McLaughlin Date: Sat, 1 Nov 2025 07:14:12 -0700 Subject: [PATCH] agent_ui: Show notifications also when the panel is hidden (#40942) Currently Zed only displays agent notifications (e.g. when the agent completes a task) if the user has switched apps and Zed is not in the foreground. This adds PR supports the scenario where the agent finishes a long-running task and the user is busy coding within Zed on something else. Releases Note: - If agent notifications are turned on, they will now also be displayed when the agent panel is hidden, in complement to them showing when the Zed window is in the background. --------- Co-authored-by: Danilo Leal --- crates/agent_ui/src/acp/thread_view.rs | 120 ++++++++++++++++++++++++- crates/agent_ui/src/agent_panel.rs | 19 ++++ 2 files changed, 137 insertions(+), 2 deletions(-) diff --git a/crates/agent_ui/src/acp/thread_view.rs b/crates/agent_ui/src/acp/thread_view.rs index 3638faf9336f79d692f820df39266ab7b85360a8..0ae60ebe0df91c61eb5c968d5ee23ec18ef87187 100644 --- a/crates/agent_ui/src/acp/thread_view.rs +++ b/crates/agent_ui/src/acp/thread_view.rs @@ -4571,14 +4571,29 @@ impl AcpThreadView { window: &mut Window, cx: &mut Context, ) { - if window.is_window_active() || !self.notifications.is_empty() { + if !self.notifications.is_empty() { + return; + } + + let settings = AgentSettings::get_global(cx); + + let window_is_inactive = !window.is_window_active(); + let panel_is_hidden = self + .workspace + .upgrade() + .map(|workspace| AgentPanel::is_hidden(&workspace, cx)) + .unwrap_or(true); + + let should_notify = window_is_inactive || panel_is_hidden; + + if !should_notify { return; } // TODO: Change this once we have title summarization for external agents. let title = self.agent.name(); - match AgentSettings::get_global(cx).notify_when_agent_waiting { + match settings.notify_when_agent_waiting { NotifyWhenAgentWaiting::PrimaryScreen => { if let Some(primary) = cx.primary_display() { self.pop_up(icon, caption.into(), title, window, primary, cx); @@ -5892,6 +5907,107 @@ pub(crate) mod tests { ); } + #[gpui::test] + async fn test_notification_when_panel_hidden(cx: &mut TestAppContext) { + init_test(cx); + + let (thread_view, cx) = setup_thread_view(StubAgentServer::default_response(), cx).await; + + add_to_workspace(thread_view.clone(), cx); + + let message_editor = cx.read(|cx| thread_view.read(cx).message_editor.clone()); + + message_editor.update_in(cx, |editor, window, cx| { + editor.set_text("Hello", window, cx); + }); + + // Window is active (don't deactivate), but panel will be hidden + // Note: In the test environment, the panel is not actually added to the dock, + // so is_agent_panel_hidden will return true + + thread_view.update_in(cx, |thread_view, window, cx| { + thread_view.send(window, cx); + }); + + cx.run_until_parked(); + + // Should show notification because window is active but panel is hidden + assert!( + cx.windows() + .iter() + .any(|window| window.downcast::().is_some()), + "Expected notification when panel is hidden" + ); + } + + #[gpui::test] + async fn test_notification_still_works_when_window_inactive(cx: &mut TestAppContext) { + init_test(cx); + + let (thread_view, cx) = setup_thread_view(StubAgentServer::default_response(), cx).await; + + let message_editor = cx.read(|cx| thread_view.read(cx).message_editor.clone()); + message_editor.update_in(cx, |editor, window, cx| { + editor.set_text("Hello", window, cx); + }); + + // Deactivate window - should show notification regardless of setting + cx.deactivate_window(); + + thread_view.update_in(cx, |thread_view, window, cx| { + thread_view.send(window, cx); + }); + + cx.run_until_parked(); + + // Should still show notification when window is inactive (existing behavior) + assert!( + cx.windows() + .iter() + .any(|window| window.downcast::().is_some()), + "Expected notification when window is inactive" + ); + } + + #[gpui::test] + async fn test_notification_respects_never_setting(cx: &mut TestAppContext) { + init_test(cx); + + // Set notify_when_agent_waiting to Never + cx.update(|cx| { + AgentSettings::override_global( + AgentSettings { + notify_when_agent_waiting: NotifyWhenAgentWaiting::Never, + ..AgentSettings::get_global(cx).clone() + }, + cx, + ); + }); + + let (thread_view, cx) = setup_thread_view(StubAgentServer::default_response(), cx).await; + + let message_editor = cx.read(|cx| thread_view.read(cx).message_editor.clone()); + message_editor.update_in(cx, |editor, window, cx| { + editor.set_text("Hello", window, cx); + }); + + // Window is active + + thread_view.update_in(cx, |thread_view, window, cx| { + thread_view.send(window, cx); + }); + + cx.run_until_parked(); + + // Should NOT show notification because notify_when_agent_waiting is Never + assert!( + !cx.windows() + .iter() + .any(|window| window.downcast::().is_some()), + "Expected no notification when notify_when_agent_waiting is Never" + ); + } + async fn setup_thread_view( agent: impl AgentServer + 'static, cx: &mut TestAppContext, diff --git a/crates/agent_ui/src/agent_panel.rs b/crates/agent_ui/src/agent_panel.rs index 997a2bec09aa2a0ae39909c909c7de80771c5055..173059ee535d4417cd0ff493842d889559b85ef4 100644 --- a/crates/agent_ui/src/agent_panel.rs +++ b/crates/agent_ui/src/agent_panel.rs @@ -729,6 +729,25 @@ impl AgentPanel { &self.context_server_registry } + pub fn is_hidden(workspace: &Entity, cx: &App) -> bool { + let workspace_read = workspace.read(cx); + + workspace_read + .panel::(cx) + .map(|panel| { + let panel_id = Entity::entity_id(&panel); + + let is_visible = workspace_read.all_docks().iter().any(|dock| { + dock.read(cx) + .visible_panel() + .is_some_and(|visible_panel| visible_panel.panel_id() == panel_id) + }); + + !is_visible + }) + .unwrap_or(true) + } + fn active_thread_view(&self) -> Option<&Entity> { match &self.active_view { ActiveView::ExternalAgentThread { thread_view, .. } => Some(thread_view),