From 10efbd5eb4258bb6d157e352bb1b5e7669ed6aca Mon Sep 17 00:00:00 2001
From: Danilo Leal <67129314+danilo-leal@users.noreply.github.com>
Date: Tue, 18 Nov 2025 01:53:37 -0300
Subject: [PATCH] agent_ui: Show the "new thread" keybinding for the currently
active agent (#42939)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
This PR's goal is to improve discoverability of how Zed "remembers" the
currently selected agent when hitting `cmd-n` (or `ctrl-n`). Hitting
that binding starts a new thread with whatever agent is currently
selected.
In the example below, I am in a Claude Code thread and if I hit `cmd-n`,
a new, fresh CC thread will be started:
Release Notes:
- agent: Improved discoverability of the `cmd-n` keybinding to create a
new thread with the currently selected agent.
---
assets/keymaps/default-macos.json | 2 +-
crates/agent_ui/src/agent_panel.rs | 50 ++++++++++++++++++++++++------
2 files changed, 42 insertions(+), 10 deletions(-)
diff --git a/assets/keymaps/default-macos.json b/assets/keymaps/default-macos.json
index 9d23eeb8cde071e20e5d3e4d7f873b1f668501b2..fe65a53aa70522ff48728d3eaded16ac3312f2e0 100644
--- a/assets/keymaps/default-macos.json
+++ b/assets/keymaps/default-macos.json
@@ -313,7 +313,7 @@
"use_key_equivalents": true,
"bindings": {
"cmd-n": "agent::NewTextThread",
- "cmd-alt-t": "agent::NewThread"
+ "cmd-alt-n": "agent::NewExternalAgentThread"
}
},
{
diff --git a/crates/agent_ui/src/agent_panel.rs b/crates/agent_ui/src/agent_panel.rs
index d4138aa5bdf8f3731df7508ab8d6476455aca11b..dfc4d27e1153c61dc07c00a807c958f69db77b5a 100644
--- a/crates/agent_ui/src/agent_panel.rs
+++ b/crates/agent_ui/src/agent_panel.rs
@@ -1892,6 +1892,9 @@ impl AgentPanel {
.anchor(Corner::TopRight)
.with_handle(self.new_thread_menu_handle.clone())
.menu({
+ let selected_agent = self.selected_agent.clone();
+ let is_agent_selected = move |agent_type: AgentType| selected_agent == agent_type;
+
let workspace = self.workspace.clone();
let is_via_collab = workspace
.update(cx, |workspace, cx| {
@@ -1929,7 +1932,9 @@ impl AgentPanel {
})
.item(
ContextMenuEntry::new("Zed Agent")
- .action(NewThread.boxed_clone())
+ .when(is_agent_selected(AgentType::NativeAgent) | is_agent_selected(AgentType::TextThread) , |this| {
+ this.action(Box::new(NewExternalAgentThread { agent: None }))
+ })
.icon(IconName::ZedAgent)
.icon_color(Color::Muted)
.handler({
@@ -1955,9 +1960,9 @@ impl AgentPanel {
)
.item(
ContextMenuEntry::new("Text Thread")
+ .action(NewTextThread.boxed_clone())
.icon(IconName::TextThread)
.icon_color(Color::Muted)
- .action(NewTextThread.boxed_clone())
.handler({
let workspace = workspace.clone();
move |window, cx| {
@@ -1983,6 +1988,9 @@ impl AgentPanel {
.header("External Agents")
.item(
ContextMenuEntry::new("Claude Code")
+ .when(is_agent_selected(AgentType::ClaudeCode), |this| {
+ this.action(Box::new(NewExternalAgentThread { agent: None }))
+ })
.icon(IconName::AiClaude)
.disabled(is_via_collab)
.icon_color(Color::Muted)
@@ -2009,6 +2017,9 @@ impl AgentPanel {
)
.item(
ContextMenuEntry::new("Codex CLI")
+ .when(is_agent_selected(AgentType::Codex), |this| {
+ this.action(Box::new(NewExternalAgentThread { agent: None }))
+ })
.icon(IconName::AiOpenAi)
.disabled(is_via_collab)
.icon_color(Color::Muted)
@@ -2035,6 +2046,9 @@ impl AgentPanel {
)
.item(
ContextMenuEntry::new("Gemini CLI")
+ .when(is_agent_selected(AgentType::Gemini), |this| {
+ this.action(Box::new(NewExternalAgentThread { agent: None }))
+ })
.icon(IconName::AiGemini)
.icon_color(Color::Muted)
.disabled(is_via_collab)
@@ -2060,8 +2074,8 @@ impl AgentPanel {
}),
)
.map(|mut menu| {
- let agent_server_store_read = agent_server_store.read(cx);
- let agent_names = agent_server_store_read
+ let agent_server_store = agent_server_store.read(cx);
+ let agent_names = agent_server_store
.external_agents()
.filter(|name| {
name.0 != GEMINI_NAME
@@ -2070,21 +2084,38 @@ impl AgentPanel {
})
.cloned()
.collect::>();
+
let custom_settings = cx
.global::()
.get::(None)
.custom
.clone();
+
for agent_name in agent_names {
- let icon_path = agent_server_store_read.agent_icon(&agent_name);
- let mut entry =
- ContextMenuEntry::new(format!("{}", agent_name));
+ let icon_path = agent_server_store.agent_icon(&agent_name);
+
+ let mut entry = ContextMenuEntry::new(agent_name.clone());
+
+ let command = custom_settings
+ .get(&agent_name.0)
+ .map(|settings| settings.command.clone())
+ .unwrap_or(placeholder_command());
+
if let Some(icon_path) = icon_path {
entry = entry.custom_icon_svg(icon_path);
} else {
entry = entry.icon(IconName::Terminal);
}
entry = entry
+ .when(
+ is_agent_selected(AgentType::Custom {
+ name: agent_name.0.clone(),
+ command: command.clone(),
+ }),
+ |this| {
+ this.action(Box::new(NewExternalAgentThread { agent: None }))
+ },
+ )
.icon_color(Color::Muted)
.disabled(is_via_collab)
.handler({
@@ -2124,6 +2155,7 @@ impl AgentPanel {
}
}
});
+
menu = menu.item(entry);
}
@@ -2156,7 +2188,7 @@ impl AgentPanel {
.id("selected_agent_icon")
.when_some(selected_agent_custom_icon, |this, icon_path| {
let label = selected_agent_label.clone();
- this.px(DynamicSpacing::Base02.rems(cx))
+ this.px_1()
.child(Icon::from_external_svg(icon_path).color(Color::Muted))
.tooltip(move |_window, cx| {
Tooltip::with_meta(label.clone(), None, "Selected Agent", cx)
@@ -2165,7 +2197,7 @@ impl AgentPanel {
.when(!has_custom_icon, |this| {
this.when_some(self.selected_agent.icon(), |this, icon| {
let label = selected_agent_label.clone();
- this.px(DynamicSpacing::Base02.rems(cx))
+ this.px_1()
.child(Icon::new(icon).color(Color::Muted))
.tooltip(move |_window, cx| {
Tooltip::with_meta(label.clone(), None, "Selected Agent", cx)