acp: Set agent as default when installing it from registry (#60452)
This PR makes it so that when you install an agent from the registry we set it as the default agent. We apply the same behaviour when installing an ACP agent from the onboarding page, which should make it less confusing for new users (they won't see the Zed agent when they see a new thread, but instead see the agent they installed) https://github.com/user-attachments/assets/81f4711f-4c9a-4606-882a-9f944c90dfa3 Release Notes: - agent: Improved onboarding experience when installing ACP agents
This commit is contained in:
1 parent
0d789ded0e
commit
54bf918329
4 files changed
+153
-50
No files matched your search
@@ -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::<AgentPanel>(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::<AgentPanel>(cx) {
|
||||
workspace.focus_panel::<AgentPanel>(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<Self>) {
|
||||
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<Self>) {
|
||||
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);
|
||||
<dyn fs::Fs>::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);
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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)]
|
||||
|
||||
Reference in new issue
Block a user