diff --git a/crates/agent_ui/src/agent_panel.rs b/crates/agent_ui/src/agent_panel.rs index 40c15c7179..5575046e25 100644 --- a/crates/agent_ui/src/agent_panel.rs +++ b/crates/agent_ui/src/agent_panel.rs @@ -26,7 +26,7 @@ use zed_actions::{ agent::{ AddSelectionToThread, ConflictContent, LogoutAgent, OpenSettings, ReauthenticateAgent, ResetAgentZoom, ResetOnboarding, ResolveConflictedFilesWithAgent, - ResolveConflictsWithAgent, ReviewBranchDiff, + ResolveConflictsWithAgent, ReviewBranchDiff, SelectAgent, }, assistant::{ FocusAgent, ManageSkills, OpenGlobalAgentsMdRules, OpenProjectAgentsMdRules, Toggle, @@ -423,6 +423,14 @@ pub fn init(cx: &mut App) { }); } }) + .register_action(|workspace, action: &SelectAgent, window, cx| { + if let Some(panel) = workspace.panel::(cx) { + panel.update(cx, |panel, cx| { + let agent = AgentId::new(action.agent.clone()).into(); + panel.select_agent(agent, window, cx); + }); + } + }) .register_action(|workspace, action: &ManageSkills, window, cx| { if let Some(panel) = workspace.panel::(cx) { workspace.focus_panel::(window, cx); @@ -1913,6 +1921,43 @@ impl AgentPanel { self.activate_new_thread(true, AgentThreadSource::AgentPanel, window, cx); } + fn set_selected_agent_and_persist(&mut self, agent: Agent, cx: &mut Context) { + if self.selected_agent != agent { + self.selected_agent = agent.clone(); + self.serialize(cx); + } + + cx.background_spawn({ + let kvp = KeyValueStore::global(cx); + async move { + write_global_last_used_agent(kvp, agent).await; + } + }) + .detach(); + } + + /// Sets the panel's selected agent without opening the panel or focusing + /// it, so the agent is launched the next time the panel is opened (or + /// right away, if the panel is already showing the empty new-thread + /// draft). + pub fn select_agent(&mut self, agent: Agent, window: &mut Window, cx: &mut Context) { + if self.project.read(cx).is_via_collab() && !agent.is_native() { + return; + } + + let showing_new_draft = matches!( + (&self.base_view, &self.draft_thread), + (BaseView::AgentThread { conversation_view }, Some(draft)) + if conversation_view.entity_id() == draft.entity_id() + ); + + if matches!(self.base_view, BaseView::AgentThread { .. }) && showing_new_draft { + self.set_selected_agent_and_persist(agent, cx); + self.activate_draft(false, AgentThreadSource::AgentPanel, window, cx); + cx.notify(); + } + } + pub fn new_terminal( &mut self, workspace: Option<&Workspace>, @@ -3180,18 +3225,7 @@ impl AgentPanel { cx, ); if let Some(original) = saved_selected_agent { - if self.selected_agent != original { - self.selected_agent = original.clone(); - self.serialize(cx); - // Restore the last-used-agent in persistent storage as well. - cx.background_spawn({ - let kvp = KeyValueStore::global(cx); - async move { - write_global_last_used_agent(kvp, original).await; - } - }) - .detach(); - } + self.set_selected_agent_and_persist(original, cx); } let thread_id = thread.conversation_view.read(cx).thread_id; self.retained_threads @@ -4501,19 +4535,7 @@ impl AgentPanel { let workspace = self.workspace.clone(); let project = self.project.clone(); - if self.selected_agent != agent { - self.selected_agent = agent.clone(); - self.serialize(cx); - } - - cx.background_spawn({ - let kvp = KeyValueStore::global(cx); - let agent = agent.clone(); - async move { - write_global_last_used_agent(kvp, agent).await; - } - }) - .detach(); + self.set_selected_agent_and_persist(agent.clone(), cx); let server = server_override .unwrap_or_else(|| agent.server(self.fs.clone(), self.thread_store.clone())); @@ -11318,6 +11340,60 @@ mod tests { }); } + #[gpui::test] + async fn test_select_agent_action_updates_visible_draft(cx: &mut TestAppContext) { + init_test(cx); + let fs = FakeFs::new(cx.executor()); + cx.update(|cx| { + agent::ThreadStore::init_global(cx); + language_model::LanguageModelRegistry::test(cx); + ::set_global(fs.clone(), cx); + }); + + fs.insert_tree("/project", json!({ "file.txt": "" })).await; + let project = Project::test(fs.clone(), [Path::new("/project")], cx).await; + let multi_workspace = + cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx)); + let workspace = multi_workspace + .read_with(cx, |multi_workspace, _cx| { + multi_workspace.workspace().clone() + }) + .unwrap(); + let cx = &mut VisualTestContext::from_window(multi_workspace.into(), cx); + + let panel = workspace.update_in(cx, |workspace, window, cx| { + let panel = cx.new(|cx| AgentPanel::new(workspace, window, cx)); + workspace.add_panel(panel.clone(), window, cx); + panel + }); + + panel.update_in(cx, |panel, window, cx| { + panel.activate_draft(false, AgentThreadSource::AgentPanel, window, cx); + }); + + cx.dispatch_action(SelectAgent { + agent: "my-configured-agent".to_string(), + }); + cx.run_until_parked(); + + let expected_agent = Agent::Custom { + id: "my-configured-agent".into(), + }; + + panel.read_with(cx, |panel, cx| { + let draft = panel.draft_thread.as_ref().expect("draft should exist"); + assert_eq!(panel.selected_agent, expected_agent); + assert_eq!(*draft.read(cx).agent_key(), expected_agent); + }); + + let kvp = cx.update(|_, cx| KeyValueStore::global(cx)); + assert_eq!( + read_global_last_used_agent(&kvp), + Some(expected_agent), + "the selection should be persisted as the global last-used agent" + ); + } + #[gpui::test] async fn test_workspaces_maintain_independent_agent_selection(cx: &mut TestAppContext) { init_test(cx); diff --git a/crates/agent_ui/src/agent_registry_ui.rs b/crates/agent_ui/src/agent_registry_ui.rs index 897a853662..a4fb6bfc60 100644 --- a/crates/agent_ui/src/agent_registry_ui.rs +++ b/crates/agent_ui/src/agent_registry_ui.rs @@ -514,19 +514,27 @@ impl AgentRegistryPage { .size(IconSize::Small) .color(Color::Muted), ) - .on_click(move |_, _, cx| { - let agent_id = agent_id.clone(); - update_settings_file(fs.clone(), cx, move |settings, _| { - let agent_servers = settings.agent_servers.get_or_insert_default(); - agent_servers.entry(agent_id).or_insert_with(|| { - settings::CustomAgentServerSettings::Registry { - default_mode: None, - env: Default::default(), - default_config_options: HashMap::default(), - favorite_config_option_values: HashMap::default(), - } - }); + .on_click(move |_, window, cx| { + update_settings_file(fs.clone(), cx, { + let agent_id = agent_id.clone(); + move |settings, _| { + let agent_servers = settings.agent_servers.get_or_insert_default(); + agent_servers.entry(agent_id).or_insert_with(|| { + settings::CustomAgentServerSettings::Registry { + default_mode: None, + env: Default::default(), + default_config_options: HashMap::default(), + favorite_config_option_values: HashMap::default(), + } + }); + } }); + window.dispatch_action( + Box::new(zed_actions::agent::SelectAgent { + agent: agent_id.clone(), + }), + cx, + ); }) } RegistryInstallStatus::InstalledRegistry => { diff --git a/crates/onboarding/src/basics_page.rs b/crates/onboarding/src/basics_page.rs index a4b17a0c09..40fe15c089 100644 --- a/crates/onboarding/src/basics_page.rs +++ b/crates/onboarding/src/basics_page.rs @@ -565,20 +565,28 @@ fn render_registry_agent_button( .name(agent.name().clone()) .state(state_element) .disabled(installed) - .on_click(move |_, _, cx| { + .on_click(move |_, window, cx| { telemetry::event!("Welcome Agent Install Clicked", agent = agent_id.as_str()); - let agent_id = agent_id.clone(); - update_settings_file(fs.clone(), cx, move |settings, _| { - let agent_servers = settings.agent_servers.get_or_insert_default(); - agent_servers.entry(agent_id).or_insert_with(|| { - CustomAgentServerSettings::Registry { - env: Default::default(), - default_mode: None, - default_config_options: HashMap::default(), - favorite_config_option_values: HashMap::default(), - } - }); + update_settings_file(fs.clone(), cx, { + let agent_id = agent_id.clone(); + move |settings, _| { + let agent_servers = settings.agent_servers.get_or_insert_default(); + agent_servers.entry(agent_id).or_insert_with(|| { + CustomAgentServerSettings::Registry { + env: Default::default(), + default_mode: None, + default_config_options: HashMap::default(), + favorite_config_option_values: HashMap::default(), + } + }); + } }); + window.dispatch_action( + Box::new(zed_actions::agent::SelectAgent { + agent: agent_id.clone(), + }), + cx, + ); }) } diff --git a/crates/zed_actions/src/lib.rs b/crates/zed_actions/src/lib.rs index a11eca78e0..7eb78ca0ec 100644 --- a/crates/zed_actions/src/lib.rs +++ b/crates/zed_actions/src/lib.rs @@ -567,6 +567,17 @@ pub mod agent { ] ); + /// Selects the agent used for new threads in the agent panel, without + /// opening the panel. The selected agent is launched the next time the + /// panel is opened. + #[derive(Clone, PartialEq, Deserialize, JsonSchema, Action)] + #[action(namespace = agent)] + #[serde(deny_unknown_fields)] + pub struct SelectAgent { + /// The id of the agent to select. + pub agent: String, + } + /// Opens a new agent thread with the provided branch diff for review. #[derive(Clone, PartialEq, Deserialize, JsonSchema, Action)] #[action(namespace = agent)]