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

Release Notes:

- N/A *or* Added/Fixed/Improved ...
This commit is contained in:
Lukas Wirth 2026-01-22 11:18:13 +01:00 committed by GitHub
parent 2be94ce893
commit 8db954a492
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 41 additions and 111 deletions

View file

@ -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,
)

View file

@ -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,
);
}

View file

@ -121,7 +121,6 @@ impl AgentThreadPane {
Some(thread_store),
prompt_store,
history,
true,
window,
cx,
)