@@ -4571,14 +4571,29 @@ impl AcpThreadView {
window: &mut Window,
cx: &mut Context<Self>,
) {
- 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::<AgentNotification>().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::<AgentNotification>().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::<AgentNotification>().is_some()),
+ "Expected no notification when notify_when_agent_waiting is Never"
+ );
+ }
+
async fn setup_thread_view(
agent: impl AgentServer + 'static,
cx: &mut TestAppContext,
@@ -729,6 +729,25 @@ impl AgentPanel {
&self.context_server_registry
}
+ pub fn is_hidden(workspace: &Entity<Workspace>, cx: &App) -> bool {
+ let workspace_read = workspace.read(cx);
+
+ workspace_read
+ .panel::<AgentPanel>(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<AcpThreadView>> {
match &self.active_view {
ActiveView::ExternalAgentThread { thread_view, .. } => Some(thread_view),