diff --git a/crates/agent_ui/src/agent_panel.rs b/crates/agent_ui/src/agent_panel.rs index 7097e5be156eb33382a1a0f47c1b4256c84ce9b1..65f47279fc9fe710406ef7173f4b85a1a8e4e547 100644 --- a/crates/agent_ui/src/agent_panel.rs +++ b/crates/agent_ui/src/agent_panel.rs @@ -9,7 +9,7 @@ use std::{ time::Duration, }; -use acp_thread::{AcpThread, AgentSessionInfo, MentionUri}; +use acp_thread::{AcpThread, AgentSessionInfo, MentionUri, ThreadStatus}; use agent::{ContextServerRegistry, SharedThread, ThreadStore}; use agent_client_protocol as acp; use agent_servers::AgentServer; @@ -50,6 +50,7 @@ use assistant_slash_command::SlashCommandWorkingSet; use assistant_text_thread::{TextThread, TextThreadEvent, TextThreadSummary}; use client::UserStore; use cloud_api_types::Plan; +use collections::HashMap; use editor::{Anchor, AnchorRangeExt as _, Editor, EditorEvent, MultiBuffer}; use extension::ExtensionEvents; use extension_host::ExtensionStore; @@ -513,7 +514,7 @@ pub struct AgentPanel { focus_handle: FocusHandle, active_view: ActiveView, previous_view: Option, - _active_view_observation: Option, + background_views: HashMap>, new_thread_menu_handle: PopoverMenuHandle, agent_panel_menu_handle: PopoverMenuHandle, agent_navigation_menu_handle: PopoverMenuHandle, @@ -798,7 +799,7 @@ impl AgentPanel { focus_handle: cx.focus_handle(), context_server_registry, previous_view: None, - _active_view_observation: None, + background_views: HashMap::default(), new_thread_menu_handle: PopoverMenuHandle::default(), agent_panel_menu_handle: PopoverMenuHandle::default(), agent_navigation_menu_handle: PopoverMenuHandle::default(), @@ -1566,6 +1567,49 @@ impl AgentPanel { } } + /// Returns the primary thread views for all retained connections: the + /// active thread plus any background threads that are still running or + /// completed but unseen. + pub fn parent_threads(&self, cx: &App) -> Vec> { + let mut views = Vec::new(); + + if let Some(server_view) = self.as_active_server_view() { + if let Some(thread_view) = server_view.read(cx).parent_thread(cx) { + views.push(thread_view); + } + } + + for server_view in self.background_views.values() { + if let Some(thread_view) = server_view.read(cx).parent_thread(cx) { + views.push(thread_view); + } + } + + views + } + + fn retain_running_thread(&mut self, old_view: ActiveView, cx: &mut Context) { + let ActiveView::AgentThread { server_view } = old_view else { + return; + }; + + let Some(thread_view) = server_view.read(cx).parent_thread(cx) else { + return; + }; + + let thread = &thread_view.read(cx).thread; + let (status, session_id) = { + let thread = thread.read(cx); + (thread.status(), thread.session_id().clone()) + }; + + if status != ThreadStatus::Generating { + return; + } + + self.background_views.insert(session_id, server_view); + } + pub(crate) fn active_native_agent_thread(&self, cx: &App) -> Option> { match &self.active_view { ActiveView::AgentThread { server_view, .. } => { @@ -1604,31 +1648,23 @@ impl AgentPanel { let current_is_config = matches!(self.active_view, ActiveView::Configuration); let new_is_config = matches!(new_view, ActiveView::Configuration); - let current_is_special = current_is_history || current_is_config; - let new_is_special = new_is_history || new_is_config; + let current_is_overlay = current_is_history || current_is_config; + let new_is_overlay = new_is_history || new_is_config; - if current_is_uninitialized || (current_is_special && !new_is_special) { + if current_is_uninitialized || (current_is_overlay && !new_is_overlay) { self.active_view = new_view; - } else if !current_is_special && new_is_special { + } else if !current_is_overlay && new_is_overlay { self.previous_view = Some(std::mem::replace(&mut self.active_view, new_view)); } else { - if !new_is_special { - self.previous_view = None; + let old_view = std::mem::replace(&mut self.active_view, new_view); + if !new_is_overlay { + if let Some(previous) = self.previous_view.take() { + self.retain_running_thread(previous, cx); + } } - self.active_view = new_view; + self.retain_running_thread(old_view, cx); } - self._active_view_observation = match &self.active_view { - ActiveView::AgentThread { server_view } => { - Some(cx.observe(server_view, |this, _, cx| { - cx.emit(AgentPanelEvent::ActiveViewChanged); - this.serialize(cx); - cx.notify(); - })) - } - _ => None, - }; - let is_in_agent_history = matches!( self.active_view, ActiveView::History { @@ -1824,6 +1860,11 @@ impl AgentPanel { window: &mut Window, cx: &mut Context, ) { + if let Some(server_view) = self.background_views.remove(&thread.session_id) { + self.set_active_view(ActiveView::AgentThread { server_view }, true, window, cx); + return; + } + let Some(agent) = self.selected_external_agent() else { return; }; @@ -1867,6 +1908,20 @@ impl AgentPanel { ) }); + cx.observe(&server_view, |this, server_view, cx| { + let is_active = this + .as_active_server_view() + .is_some_and(|active| active.entity_id() == server_view.entity_id()); + if is_active { + cx.emit(AgentPanelEvent::ActiveViewChanged); + this.serialize(cx); + } else { + cx.emit(AgentPanelEvent::BackgroundThreadChanged); + } + cx.notify(); + }) + .detach(); + self.set_active_view(ActiveView::AgentThread { server_view }, true, window, cx); } } @@ -1900,6 +1955,7 @@ fn agent_panel_dock_position(cx: &App) -> DockPosition { pub enum AgentPanelEvent { ActiveViewChanged, + BackgroundThreadChanged, } impl EventEmitter for AgentPanel {}