agent_ui: Defer spawning agents until the agent panel has been opened (#47355)

Lukas Wirth created

Release Notes:

- N/A *or* Added/Fixed/Improved ...

Change summary

crates/agent_ui/src/acp/thread_view.rs      |  13 -
crates/agent_ui/src/agent_panel.rs          | 138 ++++++----------------
crates/agent_ui_v2/src/agent_thread_pane.rs |   1 
3 files changed, 41 insertions(+), 111 deletions(-)

Detailed changes

crates/agent_ui/src/acp/thread_view.rs 🔗

@@ -413,7 +413,6 @@ impl AcpThreadView {
         thread_store: Option<Entity<ThreadStore>>,
         prompt_store: Option<Entity<PromptStore>>,
         history: Entity<AcpThreadHistory>,
-        track_load_event: bool,
         window: &mut Window,
         cx: &mut Context<Self>,
     ) -> Self {
@@ -559,7 +558,6 @@ impl AcpThreadView {
                 resume_thread.clone(),
                 workspace.clone(),
                 project.clone(),
-                track_load_event,
                 window,
                 cx,
             ),
@@ -570,7 +568,7 @@ impl AcpThreadView {
             profile_selector: None,
             notifications: Vec::new(),
             notification_subscriptions: HashMap::default(),
-            list_state: list_state,
+            list_state,
             thread_retry_status: None,
             thread_error: None,
             thread_error_markdown: None,
@@ -630,7 +628,6 @@ impl AcpThreadView {
             self.resume_thread_metadata.clone(),
             self.workspace.clone(),
             self.project.clone(),
-            true,
             window,
             cx,
         );
@@ -651,7 +648,6 @@ impl AcpThreadView {
         resume_thread: Option<AgentSessionInfo>,
         workspace: WeakEntity<Workspace>,
         project: Entity<Project>,
-        track_load_event: bool,
         window: &mut Window,
         cx: &mut Context<Self>,
     ) -> ThreadState {
@@ -713,9 +709,7 @@ impl AcpThreadView {
                 }
             };
 
-            if track_load_event {
-                telemetry::event!("Agent Thread Started", agent = connection.telemetry_id());
-            }
+            telemetry::event!("Agent Thread Started", agent = connection.telemetry_id());
 
             let result = if let Some(resume) = resume_thread.clone() {
                 cx.update(|_, cx| {
@@ -8703,7 +8697,6 @@ pub(crate) mod tests {
                     Some(thread_store),
                     None,
                     history.clone(),
-                    false,
                     window,
                     cx,
                 )
@@ -8995,7 +8988,6 @@ pub(crate) mod tests {
                     Some(thread_store),
                     None,
                     history,
-                    false,
                     window,
                     cx,
                 )
@@ -9288,7 +9280,6 @@ pub(crate) mod tests {
                     Some(thread_store.clone()),
                     None,
                     history,
-                    false,
                     window,
                     cx,
                 )

crates/agent_ui/src/agent_panel.rs 🔗

@@ -9,9 +9,7 @@ use project::{
     agent_server_store::{CLAUDE_CODE_NAME, CODEX_NAME, GEMINI_NAME},
 };
 use serde::{Deserialize, Serialize};
-use settings::{
-    DefaultAgentView as DefaultView, LanguageModelProviderSetting, LanguageModelSelection,
-};
+use settings::{LanguageModelProviderSetting, LanguageModelSelection};
 
 use zed_actions::agent::{OpenClaudeCodeOnboardingModal, ReauthenticateAgent};
 
@@ -219,6 +217,7 @@ enum HistoryKind {
 }
 
 enum ActiveView {
+    Uninitialized,
     ExternalAgentThread {
         thread_view: Entity<AcpThreadView>,
     },
@@ -291,43 +290,14 @@ impl From<ExternalAgent> for AgentType {
 impl ActiveView {
     pub fn which_font_size_used(&self) -> WhichFontSize {
         match self {
-            ActiveView::ExternalAgentThread { .. } | ActiveView::History { .. } => {
-                WhichFontSize::AgentFont
-            }
+            ActiveView::Uninitialized
+            | ActiveView::ExternalAgentThread { .. }
+            | ActiveView::History { .. } => WhichFontSize::AgentFont,
             ActiveView::TextThread { .. } => WhichFontSize::BufferFont,
             ActiveView::Configuration => WhichFontSize::None,
         }
     }
 
-    fn native_agent(
-        fs: Arc<dyn Fs>,
-        prompt_store: Option<Entity<PromptStore>>,
-        thread_store: Entity<ThreadStore>,
-        project: Entity<Project>,
-        workspace: WeakEntity<Workspace>,
-        history: Entity<AcpThreadHistory>,
-        window: &mut Window,
-        cx: &mut App,
-    ) -> Self {
-        let thread_view = cx.new(|cx| {
-            crate::acp::AcpThreadView::new(
-                ExternalAgent::NativeAgent.server(fs, thread_store.clone()),
-                None,
-                None,
-                workspace,
-                project,
-                Some(thread_store),
-                prompt_store,
-                history,
-                false,
-                window,
-                cx,
-            )
-        });
-
-        Self::ExternalAgentThread { thread_view }
-    }
-
     pub fn text_thread(
         text_thread_editor: Entity<TextThreadEditor>,
         language_registry: Arc<LanguageRegistry>,
@@ -418,7 +388,6 @@ impl ActiveView {
 
 pub struct AgentPanel {
     workspace: WeakEntity<Workspace>,
-    loading: bool,
     user_store: Entity<UserStore>,
     project: Entity<Project>,
     fs: Arc<dyn Fs>,
@@ -431,6 +400,7 @@ pub struct AgentPanel {
     context_server_registry: Entity<ContextServerRegistry>,
     configuration: Option<Entity<AgentConfiguration>>,
     configuration_subscription: Option<Subscription>,
+    focus_handle: FocusHandle,
     active_view: ActiveView,
     previous_view: Option<ActiveView>,
     new_thread_menu_handle: PopoverMenuHandle<ContextMenu>,
@@ -504,22 +474,15 @@ impl AgentPanel {
                 let panel =
                     cx.new(|cx| Self::new(workspace, text_thread_store, prompt_store, window, cx));
 
-                panel.as_mut(cx).loading = true;
                 if let Some(serialized_panel) = serialized_panel {
                     panel.update(cx, |panel, cx| {
                         panel.width = serialized_panel.width.map(|w| w.round());
                         if let Some(selected_agent) = serialized_panel.selected_agent {
-                            panel.selected_agent = selected_agent.clone();
-                            panel.new_agent_thread(selected_agent, window, cx);
+                            panel.selected_agent = selected_agent;
                         }
                         cx.notify();
                     });
-                } else {
-                    panel.update(cx, |panel, cx| {
-                        panel.new_agent_thread(AgentType::NativeAgent, window, cx);
-                    });
                 }
-                panel.as_mut(cx).loading = false;
                 panel
             })?;
 
@@ -570,37 +533,7 @@ impl AgentPanel {
         )
         .detach();
 
-        let panel_type = AgentSettings::get_global(cx).default_view;
-        let active_view = match panel_type {
-            DefaultView::Thread => ActiveView::native_agent(
-                fs.clone(),
-                prompt_store.clone(),
-                thread_store.clone(),
-                project.clone(),
-                workspace.clone(),
-                acp_history.clone(),
-                window,
-                cx,
-            ),
-            DefaultView::TextThread => {
-                let context = text_thread_store.update(cx, |store, cx| store.create(cx));
-                let lsp_adapter_delegate = make_lsp_adapter_delegate(&project.clone(), cx).unwrap();
-                let text_thread_editor = cx.new(|cx| {
-                    let mut editor = TextThreadEditor::for_text_thread(
-                        context,
-                        fs.clone(),
-                        workspace.clone(),
-                        project.clone(),
-                        lsp_adapter_delegate,
-                        window,
-                        cx,
-                    );
-                    editor.insert_default_prompt(window, cx);
-                    editor
-                });
-                ActiveView::text_thread(text_thread_editor, language_registry.clone(), window, cx)
-            }
-        };
+        let active_view = ActiveView::Uninitialized;
 
         let weak_panel = cx.entity().downgrade();
 
@@ -680,6 +613,7 @@ impl AgentPanel {
             prompt_store,
             configuration: None,
             configuration_subscription: None,
+            focus_handle: cx.focus_handle(),
             context_server_registry,
             previous_view: None,
             new_thread_menu_handle: PopoverMenuHandle::default(),
@@ -696,7 +630,6 @@ impl AgentPanel {
             text_thread_history,
             thread_store,
             selected_agent: AgentType::default(),
-            loading: false,
             show_trust_workspace_message: false,
         };
 
@@ -772,7 +705,8 @@ impl AgentPanel {
     pub(crate) fn active_thread_view(&self) -> Option<&Entity<AcpThreadView>> {
         match &self.active_view {
             ActiveView::ExternalAgentThread { thread_view, .. } => Some(thread_view),
-            ActiveView::TextThread { .. }
+            ActiveView::Uninitialized
+            | ActiveView::TextThread { .. }
             | ActiveView::History { .. }
             | ActiveView::Configuration => None,
         }
@@ -868,7 +802,6 @@ impl AgentPanel {
             agent: crate::ExternalAgent,
         }
 
-        let loading = self.loading;
         let thread_store = self.thread_store.clone();
 
         cx.spawn_in(window, async move |this, cx| {
@@ -918,7 +851,6 @@ impl AgentPanel {
                     summarize_thread,
                     workspace,
                     project,
-                    loading,
                     ext_agent,
                     window,
                     cx,
@@ -1064,7 +996,9 @@ impl AgentPanel {
                         } => {
                             text_thread_editor.focus_handle(cx).focus(window, cx);
                         }
-                        ActiveView::History { .. } | ActiveView::Configuration => {}
+                        ActiveView::Uninitialized
+                        | ActiveView::History { .. }
+                        | ActiveView::Configuration => {}
                     }
                 }
                 cx.notify();
@@ -1234,7 +1168,8 @@ impl AgentPanel {
                     })
                     .detach_and_log_err(cx);
             }
-            ActiveView::TextThread { .. }
+            ActiveView::Uninitialized
+            | ActiveView::TextThread { .. }
             | ActiveView::History { .. }
             | ActiveView::Configuration => {}
         }
@@ -1314,6 +1249,7 @@ impl AgentPanel {
         window: &mut Window,
         cx: &mut Context<Self>,
     ) {
+        let current_is_uninitialized = matches!(self.active_view, ActiveView::Uninitialized);
         let current_is_history = matches!(self.active_view, ActiveView::History { .. });
         let new_is_history = matches!(new_view, ActiveView::History { .. });
 
@@ -1323,13 +1259,7 @@ impl AgentPanel {
         let current_is_special = current_is_history || current_is_config;
         let new_is_special = new_is_history || new_is_config;
 
-        match &new_view {
-            ActiveView::TextThread { .. } => {}
-            ActiveView::ExternalAgentThread { .. } => {}
-            ActiveView::History { .. } | ActiveView::Configuration => {}
-        }
-
-        if current_is_special && !new_is_special {
+        if current_is_uninitialized || (current_is_special && !new_is_special) {
             self.active_view = new_view;
         } else if !current_is_special && new_is_special {
             self.previous_view = Some(std::mem::replace(&mut self.active_view, new_view));
@@ -1539,7 +1469,6 @@ impl AgentPanel {
         summarize_thread: Option<AgentSessionInfo>,
         workspace: WeakEntity<Workspace>,
         project: Entity<Project>,
-        loading: bool,
         ext_agent: ExternalAgent,
         window: &mut Window,
         cx: &mut Context<Self>,
@@ -1565,7 +1494,6 @@ impl AgentPanel {
                 thread_store,
                 self.prompt_store.clone(),
                 self.acp_history.clone(),
-                !loading,
                 window,
                 cx,
             )
@@ -1573,7 +1501,7 @@ impl AgentPanel {
 
         self.set_active_view(
             ActiveView::ExternalAgentThread { thread_view },
-            !loading,
+            true,
             window,
             cx,
         );
@@ -1583,6 +1511,7 @@ impl AgentPanel {
 impl Focusable for AgentPanel {
     fn focus_handle(&self, cx: &App) -> FocusHandle {
         match &self.active_view {
+            ActiveView::Uninitialized => self.focus_handle.clone(),
             ActiveView::ExternalAgentThread { thread_view, .. } => thread_view.focus_handle(cx),
             ActiveView::History { kind } => match kind {
                 HistoryKind::AgentThreads => self.acp_history.focus_handle(cx),
@@ -1595,7 +1524,7 @@ impl Focusable for AgentPanel {
                 if let Some(configuration) = self.configuration.as_ref() {
                     configuration.focus_handle(cx)
                 } else {
-                    cx.focus_handle()
+                    self.focus_handle.clone()
                 }
             }
         }
@@ -1653,7 +1582,12 @@ impl Panel for AgentPanel {
         cx.notify();
     }
 
-    fn set_active(&mut self, _active: bool, _window: &mut Window, _cx: &mut Context<Self>) {}
+    fn set_active(&mut self, active: bool, window: &mut Window, cx: &mut Context<Self>) {
+        if active && matches!(self.active_view, ActiveView::Uninitialized) {
+            let selected_agent = self.selected_agent.clone();
+            self.new_agent_thread(selected_agent, window, cx);
+        }
+    }
 
     fn remote_id() -> Option<proto::PanelId> {
         Some(proto::PanelId::AssistantPanel)
@@ -1806,6 +1740,7 @@ impl AgentPanel {
                 Label::new(title).truncate().into_any_element()
             }
             ActiveView::Configuration => Label::new("Settings").truncate().into_any_element(),
+            ActiveView::Uninitialized => Label::new("Agent").truncate().into_any_element(),
         };
 
         h_flex()
@@ -2043,7 +1978,8 @@ impl AgentPanel {
             ActiveView::ExternalAgentThread { thread_view } => {
                 thread_view.read(cx).as_native_thread(cx)
             }
-            ActiveView::TextThread { .. }
+            ActiveView::Uninitialized
+            | ActiveView::TextThread { .. }
             | ActiveView::History { .. }
             | ActiveView::Configuration => None,
         };
@@ -2430,7 +2366,8 @@ impl AgentPanel {
                     return false;
                 }
             }
-            ActiveView::ExternalAgentThread { .. }
+            ActiveView::Uninitialized
+            | ActiveView::ExternalAgentThread { .. }
             | ActiveView::History { .. }
             | ActiveView::Configuration => return false,
         }
@@ -2461,7 +2398,9 @@ impl AgentPanel {
         }
 
         match &self.active_view {
-            ActiveView::History { .. } | ActiveView::Configuration => false,
+            ActiveView::Uninitialized | ActiveView::History { .. } | ActiveView::Configuration => {
+                false
+            }
             ActiveView::ExternalAgentThread { thread_view, .. }
                 if thread_view.read(cx).as_native_thread(cx).is_none() =>
             {
@@ -2726,7 +2665,7 @@ impl AgentPanel {
                     );
                 });
             }
-            ActiveView::History { .. } | ActiveView::Configuration => {}
+            ActiveView::Uninitialized | ActiveView::History { .. } | ActiveView::Configuration => {}
         }
     }
 
@@ -2768,7 +2707,7 @@ impl AgentPanel {
         match &self.active_view {
             ActiveView::ExternalAgentThread { .. } => key_context.add("acp_thread"),
             ActiveView::TextThread { .. } => key_context.add("text_thread"),
-            ActiveView::History { .. } | ActiveView::Configuration => {}
+            ActiveView::Uninitialized | ActiveView::History { .. } | ActiveView::Configuration => {}
         }
         key_context
     }
@@ -2817,6 +2756,7 @@ impl Render for AgentPanel {
             .children(self.render_workspace_trust_message(cx))
             .children(self.render_onboarding(window, cx))
             .map(|parent| match &self.active_view {
+                ActiveView::Uninitialized => parent,
                 ActiveView::ExternalAgentThread { thread_view, .. } => parent
                     .child(thread_view.clone())
                     .child(self.render_drag_target(cx)),
@@ -3035,7 +2975,7 @@ impl AgentPanel {
         };
 
         self._external_thread(
-            server, None, None, workspace, project, false, ext_agent, window, cx,
+            server, None, None, workspace, project, ext_agent, window, cx,
         );
     }