agent2: Add new "new thread" selector in the toolbar (#36133)

Danilo Leal created

Release Notes:

- N/A

Change summary

assets/keymaps/default-linux.json           |   1 
assets/keymaps/default-macos.json           |   1 
crates/agent_ui/src/agent_panel.rs          | 812 ++++++++++++++--------
crates/agent_ui/src/agent_ui.rs             |   2 
crates/agent_ui/src/ui.rs                   |   4 
crates/agent_ui/src/ui/new_thread_button.rs |   6 
crates/zed/src/zed/component_preview.rs     |   2 
7 files changed, 513 insertions(+), 315 deletions(-)

Detailed changes

assets/keymaps/default-linux.json 🔗

@@ -239,6 +239,7 @@
       "ctrl-shift-a": "agent::ToggleContextPicker",
       "ctrl-shift-j": "agent::ToggleNavigationMenu",
       "ctrl-shift-i": "agent::ToggleOptionsMenu",
+      "ctrl-alt-shift-n": "agent::ToggleNewThreadMenu",
       "shift-alt-escape": "agent::ExpandMessageEditor",
       "ctrl->": "assistant::QuoteSelection",
       "ctrl-alt-e": "agent::RemoveAllContext",

assets/keymaps/default-macos.json 🔗

@@ -279,6 +279,7 @@
       "cmd-shift-a": "agent::ToggleContextPicker",
       "cmd-shift-j": "agent::ToggleNavigationMenu",
       "cmd-shift-i": "agent::ToggleOptionsMenu",
+      "cmd-alt-shift-n": "agent::ToggleNewThreadMenu",
       "shift-alt-escape": "agent::ExpandMessageEditor",
       "cmd->": "assistant::QuoteSelection",
       "cmd-alt-e": "agent::RemoveAllContext",

crates/agent_ui/src/agent_panel.rs 🔗

@@ -12,12 +12,12 @@ use serde::{Deserialize, Serialize};
 use crate::NewExternalAgentThread;
 use crate::agent_diff::AgentDiffThread;
 use crate::message_editor::{MAX_EDITOR_LINES, MIN_EDITOR_LINES};
-use crate::ui::NewThreadButton;
 use crate::{
     AddContextServer, AgentDiffPane, ContinueThread, ContinueWithBurnMode,
     DeleteRecentlyOpenThread, ExpandMessageEditor, Follow, InlineAssistant, NewTextThread,
     NewThread, OpenActiveThreadAsMarkdown, OpenAgentDiff, OpenHistory, ResetTrialEndUpsell,
-    ResetTrialUpsell, ToggleBurnMode, ToggleContextPicker, ToggleNavigationMenu, ToggleOptionsMenu,
+    ResetTrialUpsell, ToggleBurnMode, ToggleContextPicker, ToggleNavigationMenu,
+    ToggleNewThreadMenu, ToggleOptionsMenu,
     acp::AcpThreadView,
     active_thread::{self, ActiveThread, ActiveThreadEvent},
     agent_configuration::{AgentConfiguration, AssistantConfigurationEvent},
@@ -67,8 +67,8 @@ use theme::ThemeSettings;
 use time::UtcOffset;
 use ui::utils::WithRemSize;
 use ui::{
-    Banner, Callout, ContextMenu, ContextMenuEntry, ElevationIndex, KeyBinding, PopoverMenu,
-    PopoverMenuHandle, ProgressBar, Tab, Tooltip, prelude::*,
+    Banner, ButtonLike, Callout, ContextMenu, ContextMenuEntry, ElevationIndex, KeyBinding,
+    PopoverMenu, PopoverMenuHandle, ProgressBar, Tab, Tooltip, prelude::*,
 };
 use util::ResultExt as _;
 use workspace::{
@@ -86,6 +86,7 @@ const AGENT_PANEL_KEY: &str = "agent_panel";
 #[derive(Serialize, Deserialize)]
 struct SerializedAgentPanel {
     width: Option<Pixels>,
+    selected_agent: Option<AgentType>,
 }
 
 pub fn init(cx: &mut App) {
@@ -179,6 +180,14 @@ pub fn init(cx: &mut App) {
                         });
                     }
                 })
+                .register_action(|workspace, _: &ToggleNewThreadMenu, window, cx| {
+                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
+                        workspace.focus_panel::<AgentPanel>(window, cx);
+                        panel.update(cx, |panel, cx| {
+                            panel.toggle_new_thread_menu(&ToggleNewThreadMenu, window, cx);
+                        });
+                    }
+                })
                 .register_action(|workspace, _: &OpenOnboardingModal, window, cx| {
                     AgentOnboardingModal::toggle(workspace, window, cx)
                 })
@@ -223,6 +232,36 @@ enum WhichFontSize {
     None,
 }
 
+#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
+pub enum AgentType {
+    #[default]
+    Zed,
+    TextThread,
+    Gemini,
+    ClaudeCode,
+    NativeAgent,
+}
+
+impl AgentType {
+    fn label(self) -> impl Into<SharedString> {
+        match self {
+            Self::Zed | Self::TextThread => "Zed",
+            Self::NativeAgent => "Agent 2",
+            Self::Gemini => "Gemini",
+            Self::ClaudeCode => "Claude Code",
+        }
+    }
+
+    fn icon(self) -> IconName {
+        match self {
+            Self::Zed | Self::TextThread => IconName::AiZed,
+            Self::NativeAgent => IconName::ZedAssistant,
+            Self::Gemini => IconName::AiGemini,
+            Self::ClaudeCode => IconName::AiClaude,
+        }
+    }
+}
+
 impl ActiveView {
     pub fn which_font_size_used(&self) -> WhichFontSize {
         match self {
@@ -453,16 +492,21 @@ pub struct AgentPanel {
     zoomed: bool,
     pending_serialization: Option<Task<Result<()>>>,
     onboarding: Entity<AgentPanelOnboarding>,
+    selected_agent: AgentType,
 }
 
 impl AgentPanel {
     fn serialize(&mut self, cx: &mut Context<Self>) {
         let width = self.width;
+        let selected_agent = self.selected_agent;
         self.pending_serialization = Some(cx.background_spawn(async move {
             KEY_VALUE_STORE
                 .write_kvp(
                     AGENT_PANEL_KEY.into(),
-                    serde_json::to_string(&SerializedAgentPanel { width })?,
+                    serde_json::to_string(&SerializedAgentPanel {
+                        width,
+                        selected_agent: Some(selected_agent),
+                    })?,
                 )
                 .await?;
             anyhow::Ok(())
@@ -531,6 +575,9 @@ impl AgentPanel {
                 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;
+                        }
                         cx.notify();
                     });
                 }
@@ -732,6 +779,7 @@ impl AgentPanel {
             zoomed: false,
             pending_serialization: None,
             onboarding,
+            selected_agent: AgentType::default(),
         }
     }
 
@@ -1174,6 +1222,15 @@ impl AgentPanel {
         self.agent_panel_menu_handle.toggle(window, cx);
     }
 
+    pub fn toggle_new_thread_menu(
+        &mut self,
+        _: &ToggleNewThreadMenu,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        self.new_thread_menu_handle.toggle(window, cx);
+    }
+
     pub fn increase_font_size(
         &mut self,
         action: &IncreaseBufferFontSize,
@@ -1581,6 +1638,17 @@ impl AgentPanel {
 
         menu
     }
+
+    pub fn set_selected_agent(&mut self, agent: AgentType, cx: &mut Context<Self>) {
+        if self.selected_agent != agent {
+            self.selected_agent = agent;
+            self.serialize(cx);
+        }
+    }
+
+    pub fn selected_agent(&self) -> AgentType {
+        self.selected_agent
+    }
 }
 
 impl Focusable for AgentPanel {
@@ -1811,75 +1879,170 @@ impl AgentPanel {
             .into_any()
     }
 
-    fn render_toolbar(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
+    fn render_panel_options_menu(
+        &self,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) -> impl IntoElement {
         let user_store = self.user_store.read(cx);
         let usage = user_store.model_request_usage();
-
         let account_url = zed_urls::account_url(cx);
 
         let focus_handle = self.focus_handle(cx);
 
-        let go_back_button = div().child(
-            IconButton::new("go-back", IconName::ArrowLeft)
-                .icon_size(IconSize::Small)
-                .on_click(cx.listener(|this, _, window, cx| {
-                    this.go_back(&workspace::GoBack, window, cx);
-                }))
-                .tooltip({
+        let full_screen_label = if self.is_zoomed(window, cx) {
+            "Disable Full Screen"
+        } else {
+            "Enable Full Screen"
+        };
+
+        PopoverMenu::new("agent-options-menu")
+            .trigger_with_tooltip(
+                IconButton::new("agent-options-menu", IconName::Ellipsis)
+                    .icon_size(IconSize::Small),
+                {
                     let focus_handle = focus_handle.clone();
                     move |window, cx| {
                         Tooltip::for_action_in(
-                            "Go Back",
-                            &workspace::GoBack,
+                            "Toggle Agent Menu",
+                            &ToggleOptionsMenu,
                             &focus_handle,
                             window,
                             cx,
                         )
                     }
-                }),
-        );
+                },
+            )
+            .anchor(Corner::TopRight)
+            .with_handle(self.agent_panel_menu_handle.clone())
+            .menu({
+                let focus_handle = focus_handle.clone();
+                move |window, cx| {
+                    Some(ContextMenu::build(window, cx, |mut menu, _window, _| {
+                        menu = menu.context(focus_handle.clone());
+                        if let Some(usage) = usage {
+                            menu = menu
+                                .header_with_link("Prompt Usage", "Manage", account_url.clone())
+                                .custom_entry(
+                                    move |_window, cx| {
+                                        let used_percentage = match usage.limit {
+                                            UsageLimit::Limited(limit) => {
+                                                Some((usage.amount as f32 / limit as f32) * 100.)
+                                            }
+                                            UsageLimit::Unlimited => None,
+                                        };
 
-        let recent_entries_menu = div().child(
-            PopoverMenu::new("agent-nav-menu")
-                .trigger_with_tooltip(
-                    IconButton::new("agent-nav-menu", IconName::MenuAlt)
-                        .icon_size(IconSize::Small)
-                        .style(ui::ButtonStyle::Subtle),
-                    {
-                        let focus_handle = focus_handle.clone();
-                        move |window, cx| {
-                            Tooltip::for_action_in(
-                                "Toggle Panel Menu",
-                                &ToggleNavigationMenu,
-                                &focus_handle,
-                                window,
-                                cx,
-                            )
+                                        h_flex()
+                                            .flex_1()
+                                            .gap_1p5()
+                                            .children(used_percentage.map(|percent| {
+                                                ProgressBar::new("usage", percent, 100., cx)
+                                            }))
+                                            .child(
+                                                Label::new(match usage.limit {
+                                                    UsageLimit::Limited(limit) => {
+                                                        format!("{} / {limit}", usage.amount)
+                                                    }
+                                                    UsageLimit::Unlimited => {
+                                                        format!("{} / ∞", usage.amount)
+                                                    }
+                                                })
+                                                .size(LabelSize::Small)
+                                                .color(Color::Muted),
+                                            )
+                                            .into_any_element()
+                                    },
+                                    move |_, cx| cx.open_url(&zed_urls::account_url(cx)),
+                                )
+                                .separator()
                         }
-                    },
-                )
-                .anchor(Corner::TopLeft)
-                .with_handle(self.assistant_navigation_menu_handle.clone())
-                .menu({
-                    let menu = self.assistant_navigation_menu.clone();
+
+                        menu = menu
+                            .header("MCP Servers")
+                            .action(
+                                "View Server Extensions",
+                                Box::new(zed_actions::Extensions {
+                                    category_filter: Some(
+                                        zed_actions::ExtensionCategoryFilter::ContextServers,
+                                    ),
+                                    id: None,
+                                }),
+                            )
+                            .action("Add Custom Server…", Box::new(AddContextServer))
+                            .separator();
+
+                        menu = menu
+                            .action("Rules…", Box::new(OpenRulesLibrary::default()))
+                            .action("Settings", Box::new(OpenSettings))
+                            .separator()
+                            .action(full_screen_label, Box::new(ToggleZoom));
+                        menu
+                    }))
+                }
+            })
+    }
+
+    fn render_recent_entries_menu(
+        &self,
+        icon: IconName,
+        cx: &mut Context<Self>,
+    ) -> impl IntoElement {
+        let focus_handle = self.focus_handle(cx);
+
+        PopoverMenu::new("agent-nav-menu")
+            .trigger_with_tooltip(
+                IconButton::new("agent-nav-menu", icon)
+                    .icon_size(IconSize::Small)
+                    .style(ui::ButtonStyle::Subtle),
+                {
+                    let focus_handle = focus_handle.clone();
                     move |window, cx| {
-                        if let Some(menu) = menu.as_ref() {
-                            menu.update(cx, |_, cx| {
-                                cx.defer_in(window, |menu, window, cx| {
-                                    menu.rebuild(window, cx);
-                                });
-                            })
-                        }
-                        menu.clone()
+                        Tooltip::for_action_in(
+                            "Toggle Panel Menu",
+                            &ToggleNavigationMenu,
+                            &focus_handle,
+                            window,
+                            cx,
+                        )
                     }
-                }),
-        );
+                },
+            )
+            .anchor(Corner::TopLeft)
+            .with_handle(self.assistant_navigation_menu_handle.clone())
+            .menu({
+                let menu = self.assistant_navigation_menu.clone();
+                move |window, cx| {
+                    if let Some(menu) = menu.as_ref() {
+                        menu.update(cx, |_, cx| {
+                            cx.defer_in(window, |menu, window, cx| {
+                                menu.rebuild(window, cx);
+                            });
+                        })
+                    }
+                    menu.clone()
+                }
+            })
+    }
 
-        let full_screen_label = if self.is_zoomed(window, cx) {
-            "Disable Full Screen"
-        } else {
-            "Enable Full Screen"
-        };
+    fn render_toolbar_back_button(&self, cx: &mut Context<Self>) -> impl IntoElement {
+        let focus_handle = self.focus_handle(cx);
+
+        IconButton::new("go-back", IconName::ArrowLeft)
+            .icon_size(IconSize::Small)
+            .on_click(cx.listener(|this, _, window, cx| {
+                this.go_back(&workspace::GoBack, window, cx);
+            }))
+            .tooltip({
+                let focus_handle = focus_handle.clone();
+
+                move |window, cx| {
+                    Tooltip::for_action_in("Go Back", &workspace::GoBack, &focus_handle, window, cx)
+                }
+            })
+    }
+
+    fn render_toolbar_old(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
+        let focus_handle = self.focus_handle(cx);
 
         let active_thread = match &self.active_view {
             ActiveView::Thread { thread, .. } => Some(thread.read(cx).thread().clone()),
@@ -1903,9 +2066,6 @@ impl AgentPanel {
                     Some(ContextMenu::build(window, cx, |mut menu, _window, cx| {
                         menu = menu
                             .context(focus_handle.clone())
-                            .when(cx.has_flag::<feature_flags::AcpFeatureFlag>(), |this| {
-                                this.header("Zed Agent")
-                            })
                             .when_some(active_thread, |this, active_thread| {
                                 let thread = active_thread.read(cx);
 
@@ -1948,72 +2108,101 @@ impl AgentPanel {
                                     .handler(move |window, cx| {
                                         window.dispatch_action(NewTextThread.boxed_clone(), cx);
                                     }),
-                            )
-                            .when(cx.has_flag::<feature_flags::AcpFeatureFlag>(), |this| {
-                                this.separator()
-                                    .header("External Agents")
-                                    .item(
-                                        ContextMenuEntry::new("New Gemini Thread")
-                                            .icon(IconName::AiGemini)
-                                            .icon_color(Color::Muted)
-                                            .handler(move |window, cx| {
-                                                window.dispatch_action(
-                                                    NewExternalAgentThread {
-                                                        agent: Some(crate::ExternalAgent::Gemini),
-                                                    }
-                                                    .boxed_clone(),
-                                                    cx,
-                                                );
-                                            }),
-                                    )
-                                    .item(
-                                        ContextMenuEntry::new("New Claude Code Thread")
-                                            .icon(IconName::AiClaude)
-                                            .icon_color(Color::Muted)
-                                            .handler(move |window, cx| {
-                                                window.dispatch_action(
-                                                    NewExternalAgentThread {
-                                                        agent: Some(
-                                                            crate::ExternalAgent::ClaudeCode,
-                                                        ),
-                                                    }
-                                                    .boxed_clone(),
-                                                    cx,
-                                                );
-                                            }),
-                                    )
-                                    .item(
-                                        ContextMenuEntry::new("New Native Agent Thread")
-                                            .icon(IconName::ZedAssistant)
-                                            .icon_color(Color::Muted)
-                                            .handler(move |window, cx| {
-                                                window.dispatch_action(
-                                                    NewExternalAgentThread {
-                                                        agent: Some(
-                                                            crate::ExternalAgent::NativeAgent,
-                                                        ),
-                                                    }
-                                                    .boxed_clone(),
-                                                    cx,
-                                                );
-                                            }),
-                                    )
-                            });
+                            );
                         menu
                     }))
                 }
             });
 
-        let agent_panel_menu = PopoverMenu::new("agent-options-menu")
+        h_flex()
+            .id("assistant-toolbar")
+            .h(Tab::container_height(cx))
+            .max_w_full()
+            .flex_none()
+            .justify_between()
+            .gap_2()
+            .bg(cx.theme().colors().tab_bar_background)
+            .border_b_1()
+            .border_color(cx.theme().colors().border)
+            .child(
+                h_flex()
+                    .size_full()
+                    .pl_1()
+                    .gap_1()
+                    .child(match &self.active_view {
+                        ActiveView::History | ActiveView::Configuration => {
+                            self.render_toolbar_back_button(cx).into_any_element()
+                        }
+                        _ => self
+                            .render_recent_entries_menu(IconName::MenuAlt, cx)
+                            .into_any_element(),
+                    })
+                    .child(self.render_title_view(window, cx)),
+            )
+            .child(
+                h_flex()
+                    .h_full()
+                    .gap_2()
+                    .children(self.render_token_count(cx))
+                    .child(
+                        h_flex()
+                            .h_full()
+                            .gap(DynamicSpacing::Base02.rems(cx))
+                            .px(DynamicSpacing::Base08.rems(cx))
+                            .border_l_1()
+                            .border_color(cx.theme().colors().border)
+                            .child(new_thread_menu)
+                            .child(self.render_panel_options_menu(window, cx)),
+                    ),
+            )
+    }
+
+    fn render_toolbar_new(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
+        let focus_handle = self.focus_handle(cx);
+
+        let active_thread = match &self.active_view {
+            ActiveView::Thread { thread, .. } => Some(thread.read(cx).thread().clone()),
+            ActiveView::ExternalAgentThread { .. }
+            | ActiveView::TextThread { .. }
+            | ActiveView::History
+            | ActiveView::Configuration => None,
+        };
+
+        let new_thread_menu = PopoverMenu::new("new_thread_menu")
             .trigger_with_tooltip(
-                IconButton::new("agent-options-menu", IconName::Ellipsis)
-                    .icon_size(IconSize::Small),
+                ButtonLike::new("new_thread_menu_btn").child(
+                    h_flex()
+                        .group("agent-selector")
+                        .gap_1p5()
+                        .child(
+                            h_flex()
+                                .relative()
+                                .size_4()
+                                .justify_center()
+                                .child(
+                                    h_flex()
+                                        .group_hover("agent-selector", |s| s.invisible())
+                                        .child(
+                                            Icon::new(self.selected_agent.icon())
+                                                .color(Color::Muted),
+                                        ),
+                                )
+                                .child(
+                                    h_flex()
+                                        .absolute()
+                                        .invisible()
+                                        .group_hover("agent-selector", |s| s.visible())
+                                        .child(Icon::new(IconName::Plus)),
+                                ),
+                        )
+                        .child(Label::new(self.selected_agent.label())),
+                ),
                 {
                     let focus_handle = focus_handle.clone();
                     move |window, cx| {
                         Tooltip::for_action_in(
-                            "Toggle Agent Menu",
-                            &ToggleOptionsMenu,
+                            "New…",
+                            &ToggleNewThreadMenu,
                             &focus_handle,
                             window,
                             cx,
@@ -2021,76 +2210,197 @@ impl AgentPanel {
                     }
                 },
             )
-            .anchor(Corner::TopRight)
-            .with_handle(self.agent_panel_menu_handle.clone())
+            .anchor(Corner::TopLeft)
+            .with_handle(self.new_thread_menu_handle.clone())
             .menu({
                 let focus_handle = focus_handle.clone();
+                let workspace = self.workspace.clone();
+
                 move |window, cx| {
-                    Some(ContextMenu::build(window, cx, |mut menu, _window, _| {
-                        menu = menu.context(focus_handle.clone());
-                        if let Some(usage) = usage {
-                            menu = menu
-                                .header_with_link("Prompt Usage", "Manage", account_url.clone())
-                                .custom_entry(
-                                    move |_window, cx| {
-                                        let used_percentage = match usage.limit {
-                                            UsageLimit::Limited(limit) => {
-                                                Some((usage.amount as f32 / limit as f32) * 100.)
-                                            }
-                                            UsageLimit::Unlimited => None,
-                                        };
+                    let active_thread = active_thread.clone();
+                    Some(ContextMenu::build(window, cx, |mut menu, _window, cx| {
+                        menu = menu
+                            .context(focus_handle.clone())
+                            .header("Zed Agent")
+                            .when_some(active_thread, |this, active_thread| {
+                                let thread = active_thread.read(cx);
 
-                                        h_flex()
-                                            .flex_1()
-                                            .gap_1p5()
-                                            .children(used_percentage.map(|percent| {
-                                                ProgressBar::new("usage", percent, 100., cx)
-                                            }))
-                                            .child(
-                                                Label::new(match usage.limit {
-                                                    UsageLimit::Limited(limit) => {
-                                                        format!("{} / {limit}", usage.amount)
+                                if !thread.is_empty() {
+                                    let thread_id = thread.id().clone();
+                                    this.item(
+                                        ContextMenuEntry::new("New From Summary")
+                                            .icon(IconName::ThreadFromSummary)
+                                            .icon_color(Color::Muted)
+                                            .handler(move |window, cx| {
+                                                window.dispatch_action(
+                                                    Box::new(NewThread {
+                                                        from_thread_id: Some(thread_id.clone()),
+                                                    }),
+                                                    cx,
+                                                );
+                                            }),
+                                    )
+                                } else {
+                                    this
+                                }
+                            })
+                            .item(
+                                ContextMenuEntry::new("New Thread")
+                                    .icon(IconName::Thread)
+                                    .icon_color(Color::Muted)
+                                    .action(NewThread::default().boxed_clone())
+                                    .handler({
+                                        let workspace = workspace.clone();
+                                        move |window, cx| {
+                                            if let Some(workspace) = workspace.upgrade() {
+                                                workspace.update(cx, |workspace, cx| {
+                                                    if let Some(panel) =
+                                                        workspace.panel::<AgentPanel>(cx)
+                                                    {
+                                                        panel.update(cx, |panel, cx| {
+                                                            panel.set_selected_agent(
+                                                                AgentType::Zed,
+                                                                cx,
+                                                            );
+                                                        });
                                                     }
-                                                    UsageLimit::Unlimited => {
-                                                        format!("{} / ∞", usage.amount)
+                                                });
+                                            }
+                                            window.dispatch_action(
+                                                NewThread::default().boxed_clone(),
+                                                cx,
+                                            );
+                                        }
+                                    }),
+                            )
+                            .item(
+                                ContextMenuEntry::new("New Text Thread")
+                                    .icon(IconName::TextThread)
+                                    .icon_color(Color::Muted)
+                                    .action(NewTextThread.boxed_clone())
+                                    .handler({
+                                        let workspace = workspace.clone();
+                                        move |window, cx| {
+                                            if let Some(workspace) = workspace.upgrade() {
+                                                workspace.update(cx, |workspace, cx| {
+                                                    if let Some(panel) =
+                                                        workspace.panel::<AgentPanel>(cx)
+                                                    {
+                                                        panel.update(cx, |panel, cx| {
+                                                            panel.set_selected_agent(
+                                                                AgentType::TextThread,
+                                                                cx,
+                                                            );
+                                                        });
                                                     }
-                                                })
-                                                .size(LabelSize::Small)
-                                                .color(Color::Muted),
-                                            )
-                                            .into_any_element()
-                                    },
-                                    move |_, cx| cx.open_url(&zed_urls::account_url(cx)),
-                                )
-                                .separator()
-                        }
-
-                        menu = menu
-                            .header("MCP Servers")
-                            .action(
-                                "View Server Extensions",
-                                Box::new(zed_actions::Extensions {
-                                    category_filter: Some(
-                                        zed_actions::ExtensionCategoryFilter::ContextServers,
-                                    ),
-                                    id: None,
-                                }),
+                                                });
+                                            }
+                                            window.dispatch_action(NewTextThread.boxed_clone(), cx);
+                                        }
+                                    }),
+                            )
+                            .item(
+                                ContextMenuEntry::new("New Native Agent Thread")
+                                    .icon(IconName::ZedAssistant)
+                                    .icon_color(Color::Muted)
+                                    .handler({
+                                        let workspace = workspace.clone();
+                                        move |window, cx| {
+                                            if let Some(workspace) = workspace.upgrade() {
+                                                workspace.update(cx, |workspace, cx| {
+                                                    if let Some(panel) =
+                                                        workspace.panel::<AgentPanel>(cx)
+                                                    {
+                                                        panel.update(cx, |panel, cx| {
+                                                            panel.set_selected_agent(
+                                                                AgentType::NativeAgent,
+                                                                cx,
+                                                            );
+                                                        });
+                                                    }
+                                                });
+                                            }
+                                            window.dispatch_action(
+                                                NewExternalAgentThread {
+                                                    agent: Some(crate::ExternalAgent::NativeAgent),
+                                                }
+                                                .boxed_clone(),
+                                                cx,
+                                            );
+                                        }
+                                    }),
                             )
-                            .action("Add Custom Server…", Box::new(AddContextServer))
-                            .separator();
-
-                        menu = menu
-                            .action("Rules…", Box::new(OpenRulesLibrary::default()))
-                            .action("Settings", Box::new(OpenSettings))
                             .separator()
-                            .action(full_screen_label, Box::new(ToggleZoom));
+                            .header("External Agents")
+                            .item(
+                                ContextMenuEntry::new("New Gemini Thread")
+                                    .icon(IconName::AiGemini)
+                                    .icon_color(Color::Muted)
+                                    .handler({
+                                        let workspace = workspace.clone();
+                                        move |window, cx| {
+                                            if let Some(workspace) = workspace.upgrade() {
+                                                workspace.update(cx, |workspace, cx| {
+                                                    if let Some(panel) =
+                                                        workspace.panel::<AgentPanel>(cx)
+                                                    {
+                                                        panel.update(cx, |panel, cx| {
+                                                            panel.set_selected_agent(
+                                                                AgentType::Gemini,
+                                                                cx,
+                                                            );
+                                                        });
+                                                    }
+                                                });
+                                            }
+                                            window.dispatch_action(
+                                                NewExternalAgentThread {
+                                                    agent: Some(crate::ExternalAgent::Gemini),
+                                                }
+                                                .boxed_clone(),
+                                                cx,
+                                            );
+                                        }
+                                    }),
+                            )
+                            .item(
+                                ContextMenuEntry::new("New Claude Code Thread")
+                                    .icon(IconName::AiClaude)
+                                    .icon_color(Color::Muted)
+                                    .handler({
+                                        let workspace = workspace.clone();
+                                        move |window, cx| {
+                                            if let Some(workspace) = workspace.upgrade() {
+                                                workspace.update(cx, |workspace, cx| {
+                                                    if let Some(panel) =
+                                                        workspace.panel::<AgentPanel>(cx)
+                                                    {
+                                                        panel.update(cx, |panel, cx| {
+                                                            panel.set_selected_agent(
+                                                                AgentType::ClaudeCode,
+                                                                cx,
+                                                            );
+                                                        });
+                                                    }
+                                                });
+                                            }
+                                            window.dispatch_action(
+                                                NewExternalAgentThread {
+                                                    agent: Some(crate::ExternalAgent::ClaudeCode),
+                                                }
+                                                .boxed_clone(),
+                                                cx,
+                                            );
+                                        }
+                                    }),
+                            );
                         menu
                     }))
                 }
             });
 
         h_flex()
-            .id("assistant-toolbar")
+            .id("agent-panel-toolbar")
             .h(Tab::container_height(cx))
             .max_w_full()
             .flex_none()
@@ -2102,11 +2412,18 @@ impl AgentPanel {
             .child(
                 h_flex()
                     .size_full()
-                    .pl_1()
-                    .gap_1()
+                    .gap(DynamicSpacing::Base08.rems(cx))
                     .child(match &self.active_view {
-                        ActiveView::History | ActiveView::Configuration => go_back_button,
-                        _ => recent_entries_menu,
+                        ActiveView::History | ActiveView::Configuration => {
+                            self.render_toolbar_back_button(cx).into_any_element()
+                        }
+                        _ => h_flex()
+                            .h_full()
+                            .px(DynamicSpacing::Base04.rems(cx))
+                            .border_r_1()
+                            .border_color(cx.theme().colors().border)
+                            .child(new_thread_menu)
+                            .into_any_element(),
                     })
                     .child(self.render_title_view(window, cx)),
             )
@@ -2119,15 +2436,24 @@ impl AgentPanel {
                         h_flex()
                             .h_full()
                             .gap(DynamicSpacing::Base02.rems(cx))
-                            .px(DynamicSpacing::Base08.rems(cx))
+                            .pl(DynamicSpacing::Base04.rems(cx))
+                            .pr(DynamicSpacing::Base06.rems(cx))
                             .border_l_1()
                             .border_color(cx.theme().colors().border)
-                            .child(new_thread_menu)
-                            .child(agent_panel_menu),
+                            .child(self.render_recent_entries_menu(IconName::HistoryRerun, cx))
+                            .child(self.render_panel_options_menu(window, cx)),
                     ),
             )
     }
 
+    fn render_toolbar(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
+        if cx.has_flag::<feature_flags::AcpFeatureFlag>() {
+            self.render_toolbar_new(window, cx).into_any_element()
+        } else {
+            self.render_toolbar_old(window, cx).into_any_element()
+        }
+    }
+
     fn render_token_count(&self, cx: &App) -> Option<AnyElement> {
         match &self.active_view {
             ActiveView::Thread {
@@ -2576,138 +2902,6 @@ impl AgentPanel {
                                 },
                             )),
                     )
-                    .child(self.render_empty_state_section_header("Start", None, cx))
-                    .child(
-                        v_flex()
-                            .p_1()
-                            .gap_2()
-                            .child(
-                                h_flex()
-                                    .w_full()
-                                    .gap_2()
-                                    .child(
-                                        NewThreadButton::new(
-                                            "new-thread-btn",
-                                            "New Thread",
-                                            IconName::Thread,
-                                        )
-                                        .keybinding(KeyBinding::for_action_in(
-                                            &NewThread::default(),
-                                            &self.focus_handle(cx),
-                                            window,
-                                            cx,
-                                        ))
-                                        .on_click(
-                                            |window, cx| {
-                                                window.dispatch_action(
-                                                    NewThread::default().boxed_clone(),
-                                                    cx,
-                                                )
-                                            },
-                                        ),
-                                    )
-                                    .child(
-                                        NewThreadButton::new(
-                                            "new-text-thread-btn",
-                                            "New Text Thread",
-                                            IconName::TextThread,
-                                        )
-                                        .keybinding(KeyBinding::for_action_in(
-                                            &NewTextThread,
-                                            &self.focus_handle(cx),
-                                            window,
-                                            cx,
-                                        ))
-                                        .on_click(
-                                            |window, cx| {
-                                                window.dispatch_action(Box::new(NewTextThread), cx)
-                                            },
-                                        ),
-                                    ),
-                            )
-                            .when(cx.has_flag::<feature_flags::AcpFeatureFlag>(), |this| {
-                                this.child(
-                                    h_flex()
-                                        .w_full()
-                                        .gap_2()
-                                        .child(
-                                            NewThreadButton::new(
-                                                "new-gemini-thread-btn",
-                                                "New Gemini Thread",
-                                                IconName::AiGemini,
-                                            )
-                                            // .keybinding(KeyBinding::for_action_in(
-                                            //     &OpenHistory,
-                                            //     &self.focus_handle(cx),
-                                            //     window,
-                                            //     cx,
-                                            // ))
-                                            .on_click(
-                                                |window, cx| {
-                                                    window.dispatch_action(
-                                                        Box::new(NewExternalAgentThread {
-                                                            agent: Some(
-                                                                crate::ExternalAgent::Gemini,
-                                                            ),
-                                                        }),
-                                                        cx,
-                                                    )
-                                                },
-                                            ),
-                                        )
-                                        .child(
-                                            NewThreadButton::new(
-                                                "new-claude-thread-btn",
-                                                "New Claude Code Thread",
-                                                IconName::AiClaude,
-                                            )
-                                            // .keybinding(KeyBinding::for_action_in(
-                                            //     &OpenHistory,
-                                            //     &self.focus_handle(cx),
-                                            //     window,
-                                            //     cx,
-                                            // ))
-                                            .on_click(
-                                                |window, cx| {
-                                                    window.dispatch_action(
-                                                        Box::new(NewExternalAgentThread {
-                                                            agent: Some(
-                                                                crate::ExternalAgent::ClaudeCode,
-                                                            ),
-                                                        }),
-                                                        cx,
-                                                    )
-                                                },
-                                            ),
-                                        )
-                                        .child(
-                                            NewThreadButton::new(
-                                                "new-native-agent-thread-btn",
-                                                "New Native Agent Thread",
-                                                IconName::ZedAssistant,
-                                            )
-                                            // .keybinding(KeyBinding::for_action_in(
-                                            //     &OpenHistory,
-                                            //     &self.focus_handle(cx),
-                                            //     window,
-                                            //     cx,
-                                            // ))
-                                            .on_click(
-                                                |window, cx| {
-                                                    window.dispatch_action(
-                                                        Box::new(NewExternalAgentThread {
-                                                            agent: Some(
-                                                                crate::ExternalAgent::NativeAgent,
-                                                            ),
-                                                        }),
-                                                        cx,
-                                                    )
-                                                },
-                                            ),
-                                        ),
-                                )
-                            }),
-                    )
                     .when_some(configuration_error.as_ref(), |this, err| {
                         this.child(self.render_configuration_error(err, &focus_handle, window, cx))
                     })

crates/agent_ui/src/agent_ui.rs 🔗

@@ -64,6 +64,8 @@ actions!(
         NewTextThread,
         /// Toggles the context picker interface for adding files, symbols, or other context.
         ToggleContextPicker,
+        /// Toggles the menu to create new agent threads.
+        ToggleNewThreadMenu,
         /// Toggles the navigation menu for switching between threads and views.
         ToggleNavigationMenu,
         /// Toggles the options menu for agent settings and preferences.

crates/agent_ui/src/ui.rs 🔗

@@ -2,7 +2,7 @@ mod agent_notification;
 mod burn_mode_tooltip;
 mod context_pill;
 mod end_trial_upsell;
-mod new_thread_button;
+// mod new_thread_button;
 mod onboarding_modal;
 pub mod preview;
 
@@ -10,5 +10,5 @@ pub use agent_notification::*;
 pub use burn_mode_tooltip::*;
 pub use context_pill::*;
 pub use end_trial_upsell::*;
-pub use new_thread_button::*;
+// pub use new_thread_button::*;
 pub use onboarding_modal::*;

crates/agent_ui/src/ui/new_thread_button.rs 🔗

@@ -11,7 +11,7 @@ pub struct NewThreadButton {
 }
 
 impl NewThreadButton {
-    pub fn new(id: impl Into<ElementId>, label: impl Into<SharedString>, icon: IconName) -> Self {
+    fn new(id: impl Into<ElementId>, label: impl Into<SharedString>, icon: IconName) -> Self {
         Self {
             id: id.into(),
             label: label.into(),
@@ -21,12 +21,12 @@ impl NewThreadButton {
         }
     }
 
-    pub fn keybinding(mut self, keybinding: Option<ui::KeyBinding>) -> Self {
+    fn keybinding(mut self, keybinding: Option<ui::KeyBinding>) -> Self {
         self.keybinding = keybinding;
         self
     }
 
-    pub fn on_click<F>(mut self, handler: F) -> Self
+    fn on_click<F>(mut self, handler: F) -> Self
     where
         F: Fn(&mut Window, &mut App) + 'static,
     {

crates/zed/src/zed/component_preview.rs 🔗

@@ -761,7 +761,7 @@ impl Render for ComponentPreview {
                         )
                         .track_scroll(self.nav_scroll_handle.clone())
                         .p_2p5()
-                        .w(px(229.))
+                        .w(px(231.)) // Matches perfectly with the size of the "Component Preview" tab, if that's the first one in the pane
                         .h_full()
                         .flex_1(),
                     )