agent_ui: Replace stale draft thread when selected agent changes (#54023)

## Problem

When creating a new agent thread from the sidebar's **+** button (or via
the `NewThread` / `NewThreadInGroup` actions), the panel always returned
the cached draft thread — even if the user had since switched to a
different agent. This meant the new thread would use the wrong agent
(typically the default Zed Agent instead of the last-used external
agent).

The root cause was in `ensure_draft()`: it cached the draft and returned
it unconditionally on subsequent calls without checking whether its
agent
matched the current `selected_agent`.

Meanwhile, **cmd-n from within the agent panel** dispatched
`NewExternalAgentThread`, which calls `create_thread()` — this always
creates a fresh thread with the correct `selected_agent`, so it worked
fine.

## Fix

`ensure_draft()` now compares the cached draft's agent against the
desired agent. When they differ and the draft's editor is empty, the
stale draft is discarded and a new one is created with the correct
agent.
If the draft has user-typed content, it is preserved to avoid losing
work.

## Test

Added `test_draft_replaced_when_selected_agent_changes` which verifies:
1. A draft created with `NativeAgent` gets replaced when
`selected_agent`
   changes to a custom agent
2. The replacement draft uses the correct agent
3. Calling `activate_draft` again with the same agent reuses the cached
   draft (no unnecessary replacement)

The test fails without the fix (returns the stale `NativeAgent` draft)
and passes with it.

Release Notes:

- N/A
This commit is contained in:
Eric Holk authored and GitHub committed 2026-04-15 22:47:25 -07:00
1 parent 8d45a9f9de
commit a0d562d786
1 file changed
+116 -6
+116 -6
View File
@@ -1282,16 +1282,37 @@ impl AgentPanel {
window: &mut Window,
cx: &mut Context<Self>,
) -> Entity<ConversationView> {
if let Some(draft) = &self.draft_thread {
return draft.clone();
}
let agent = if self.project.read(cx).is_via_collab() {
let desired_agent = if self.project.read(cx).is_via_collab() {
Agent::NativeAgent
} else {
self.selected_agent.clone()
};
let thread =
self.create_agent_thread(agent, None, None, None, None, "agent_panel", window, cx);
if let Some(draft) = &self.draft_thread {
let agent_matches = *draft.read(cx).agent_key() == desired_agent;
let has_editor_content = draft.read(cx).active_thread().is_some_and(|tv| {
!tv.read(cx)
.message_editor
.read(cx)
.text(cx)
.trim()
.is_empty()
});
if agent_matches || has_editor_content {
return draft.clone();
}
self.draft_thread = None;
self._draft_editor_observation = None;
}
let thread = self.create_agent_thread(
desired_agent,
None,
None,
None,
None,
"agent_panel",
window,
cx,
);
self.draft_thread = Some(thread.conversation_view.clone());
self.observe_draft_editor(&thread.conversation_view, cx);
thread.conversation_view
@@ -7104,6 +7125,95 @@ mod tests {
});
}
#[gpui::test]
async fn test_draft_replaced_when_selected_agent_changes(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);
});
let project = Project::test(fs.clone(), [], 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();
workspace.update(cx, |workspace, _cx| {
workspace.set_random_database_id();
});
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, None, window, cx));
workspace.add_panel(panel.clone(), window, cx);
panel
});
// Create a draft with the default NativeAgent.
panel.update_in(cx, |panel, window, cx| {
panel.activate_draft(true, window, cx);
});
let first_draft_id = panel.read_with(cx, |panel, cx| {
assert!(panel.draft_thread.is_some());
assert_eq!(panel.selected_agent, Agent::NativeAgent);
let draft = panel.draft_thread.as_ref().unwrap();
assert_eq!(*draft.read(cx).agent_key(), Agent::NativeAgent);
draft.entity_id()
});
// Switch selected_agent to a custom agent, then activate_draft again.
// The stale NativeAgent draft should be replaced.
let custom_agent = Agent::Custom {
id: "my-custom-agent".into(),
};
panel.update_in(cx, |panel, window, cx| {
panel.selected_agent = custom_agent.clone();
panel.activate_draft(true, window, cx);
});
panel.read_with(cx, |panel, cx| {
let draft = panel.draft_thread.as_ref().expect("draft should exist");
assert_ne!(
draft.entity_id(),
first_draft_id,
"a new draft should have been created"
);
assert_eq!(
*draft.read(cx).agent_key(),
custom_agent,
"the new draft should use the custom agent"
);
});
// Calling activate_draft again with the same agent should return the
// cached draft (no replacement).
let second_draft_id = panel.read_with(cx, |panel, _cx| {
panel.draft_thread.as_ref().unwrap().entity_id()
});
panel.update_in(cx, |panel, window, cx| {
panel.activate_draft(true, window, cx);
});
panel.read_with(cx, |panel, _cx| {
assert_eq!(
panel.draft_thread.as_ref().unwrap().entity_id(),
second_draft_id,
"draft should be reused when the agent has not changed"
);
});
}
#[gpui::test]
async fn test_rollback_all_succeed_returns_ok(cx: &mut TestAppContext) {
init_test(cx);