Detailed changes
@@ -9,42 +9,51 @@ use gpui::{AppContext, Context, Entity, EventEmitter, SharedString, Subscription
use project::{AgentServerStore, AgentServersUpdated, Project};
use watch::Receiver;
-use crate::ExternalAgent;
+use crate::{ExternalAgent, ThreadHistory};
use project::ExternalAgentServerName;
-pub enum ConnectionEntry {
+pub enum AgentConnectionEntry {
Connecting {
- connect_task: Shared<Task<Result<Rc<dyn AgentConnection>, LoadError>>>,
- },
- Connected {
- connection: Rc<dyn AgentConnection>,
+ connect_task: Shared<Task<Result<AgentConnectedState, LoadError>>>,
},
+ Connected(AgentConnectedState),
Error {
error: LoadError,
},
}
-impl ConnectionEntry {
- pub fn wait_for_connection(&self) -> Shared<Task<Result<Rc<dyn AgentConnection>, LoadError>>> {
+#[derive(Clone)]
+pub struct AgentConnectedState {
+ pub connection: Rc<dyn AgentConnection>,
+ pub history: Entity<ThreadHistory>,
+}
+
+impl AgentConnectionEntry {
+ pub fn wait_for_connection(&self) -> Shared<Task<Result<AgentConnectedState, LoadError>>> {
match self {
- ConnectionEntry::Connecting { connect_task } => connect_task.clone(),
- ConnectionEntry::Connected { connection } => {
- Task::ready(Ok(connection.clone())).shared()
- }
- ConnectionEntry::Error { error } => Task::ready(Err(error.clone())).shared(),
+ AgentConnectionEntry::Connecting { connect_task } => connect_task.clone(),
+ AgentConnectionEntry::Connected(state) => Task::ready(Ok(state.clone())).shared(),
+ AgentConnectionEntry::Error { error } => Task::ready(Err(error.clone())).shared(),
+ }
+ }
+
+ pub fn history(&self) -> Option<&Entity<ThreadHistory>> {
+ match self {
+ AgentConnectionEntry::Connected(state) => Some(&state.history),
+ _ => None,
}
}
}
-pub enum ConnectionEntryEvent {
+pub enum AgentConnectionEntryEvent {
NewVersionAvailable(SharedString),
}
-impl EventEmitter<ConnectionEntryEvent> for ConnectionEntry {}
+impl EventEmitter<AgentConnectionEntryEvent> for AgentConnectionEntry {}
pub struct AgentConnectionStore {
project: Entity<Project>,
- entries: HashMap<ExternalAgent, Entity<ConnectionEntry>>,
+ entries: HashMap<ExternalAgent, Entity<AgentConnectionEntry>>,
_subscriptions: Vec<Subscription>,
}
@@ -59,17 +68,21 @@ impl AgentConnectionStore {
}
}
+ pub fn entry(&self, key: &ExternalAgent) -> Option<&Entity<AgentConnectionEntry>> {
+ self.entries.get(key)
+ }
+
pub fn request_connection(
&mut self,
key: ExternalAgent,
server: Rc<dyn AgentServer>,
cx: &mut Context<Self>,
- ) -> Entity<ConnectionEntry> {
+ ) -> Entity<AgentConnectionEntry> {
self.entries.get(&key).cloned().unwrap_or_else(|| {
let (mut new_version_rx, connect_task) = self.start_connection(server.clone(), cx);
let connect_task = connect_task.shared();
- let entry = cx.new(|_cx| ConnectionEntry::Connecting {
+ let entry = cx.new(|_cx| AgentConnectionEntry::Connecting {
connect_task: connect_task.clone(),
});
@@ -79,18 +92,18 @@ impl AgentConnectionStore {
let key = key.clone();
let entry = entry.clone();
async move |this, cx| match connect_task.await {
- Ok(connection) => {
+ Ok(connected_state) => {
entry.update(cx, |entry, cx| {
- if let ConnectionEntry::Connecting { .. } = entry {
- *entry = ConnectionEntry::Connected { connection };
+ if let AgentConnectionEntry::Connecting { .. } = entry {
+ *entry = AgentConnectionEntry::Connected(connected_state);
cx.notify();
}
});
}
Err(error) => {
entry.update(cx, |entry, cx| {
- if let ConnectionEntry::Connecting { .. } = entry {
- *entry = ConnectionEntry::Error { error };
+ if let AgentConnectionEntry::Connecting { .. } = entry {
+ *entry = AgentConnectionEntry::Error { error };
cx.notify();
}
});
@@ -106,7 +119,7 @@ impl AgentConnectionStore {
while let Ok(version) = new_version_rx.recv().await {
if let Some(version) = version {
entry.update(cx, |_entry, cx| {
- cx.emit(ConnectionEntryEvent::NewVersionAvailable(
+ cx.emit(AgentConnectionEntryEvent::NewVersionAvailable(
version.clone().into(),
));
});
@@ -143,7 +156,7 @@ impl AgentConnectionStore {
cx: &mut Context<Self>,
) -> (
Receiver<Option<String>>,
- Task<Result<Rc<dyn AgentConnection>, LoadError>>,
+ Task<Result<AgentConnectedState, LoadError>>,
) {
let (new_version_tx, new_version_rx) = watch::channel::<Option<String>>(None);
@@ -151,8 +164,14 @@ impl AgentConnectionStore {
let delegate = AgentServerDelegate::new(agent_server_store, Some(new_version_tx));
let connect_task = server.connect(delegate, cx);
- let connect_task = cx.spawn(async move |_this, _cx| match connect_task.await {
- Ok(connection) => Ok(connection),
+ let connect_task = cx.spawn(async move |_this, cx| match connect_task.await {
+ Ok(connection) => cx.update(|cx| {
+ let history = cx.new(|cx| ThreadHistory::new(connection.session_list(cx), cx));
+ Ok(AgentConnectedState {
+ connection,
+ history,
+ })
+ }),
Err(err) => match err.downcast::<LoadError>() {
Ok(load_error) => Err(load_error),
Err(err) => Err(LoadError::Other(SharedString::from(err.to_string()))),
@@ -29,8 +29,6 @@ use zed_actions::agent::{
ResolveConflictedFilesWithAgent, ResolveConflictsWithAgent, ReviewBranchDiff,
};
-use crate::ManageProfiles;
-use crate::agent_connection_store::AgentConnectionStore;
use crate::ui::{AcpOnboardingModal, ClaudeCodeOnboardingModal};
use crate::{
AddContextServer, AgentDiffPane, ConnectionView, CopyThreadToClipboard, Follow,
@@ -48,12 +46,14 @@ use crate::{
NewNativeAgentThreadFromSummary,
};
use crate::{
- ExpandMessageEditor, ThreadHistory, ThreadHistoryView, ThreadHistoryViewEvent,
+ ExpandMessageEditor, ThreadHistoryView,
text_thread_history::{TextThreadHistory, TextThreadHistoryEvent},
};
+use crate::{ManageProfiles, ThreadHistoryViewEvent};
+use crate::{ThreadHistory, agent_connection_store::AgentConnectionStore};
use agent_settings::AgentSettings;
use ai_onboarding::AgentPanelOnboarding;
-use anyhow::{Result, anyhow};
+use anyhow::{Context as _, Result, anyhow};
use assistant_slash_command::SlashCommandWorkingSet;
use assistant_text_thread::{TextThread, TextThreadEvent, TextThreadSummary};
use client::UserStore;
@@ -621,9 +621,9 @@ fn build_conflicted_files_resolution_prompt(
content
}
-#[derive(Clone, Copy, Debug, PartialEq, Eq)]
-enum HistoryKind {
- AgentThreads,
+#[derive(Clone, Debug, PartialEq, Eq)]
+enum History {
+ AgentThreads { view: Entity<ThreadHistoryView> },
TextThreads,
}
@@ -639,7 +639,7 @@ enum ActiveView {
_subscriptions: Vec<gpui::Subscription>,
},
History {
- kind: HistoryKind,
+ history: History,
},
Configuration,
}
@@ -870,8 +870,6 @@ pub struct AgentPanel {
project: Entity<Project>,
fs: Arc<dyn Fs>,
language_registry: Arc<LanguageRegistry>,
- acp_history: Entity<ThreadHistory>,
- acp_history_view: Entity<ThreadHistoryView>,
text_thread_history: Entity<TextThreadHistory>,
thread_store: Entity<ThreadStore>,
text_thread_store: Entity<assistant_text_thread::TextThreadStore>,
@@ -1081,26 +1079,9 @@ impl AgentPanel {
cx.new(|cx| ContextServerRegistry::new(project.read(cx).context_server_store(), cx));
let thread_store = ThreadStore::global(cx);
- let acp_history = cx.new(|cx| ThreadHistory::new(None, cx));
- let acp_history_view = cx.new(|cx| ThreadHistoryView::new(acp_history.clone(), window, cx));
let text_thread_history =
cx.new(|cx| TextThreadHistory::new(text_thread_store.clone(), window, cx));
- cx.subscribe_in(
- &acp_history_view,
- window,
- |this, _, event, window, cx| match event {
- ThreadHistoryViewEvent::Open(thread) => {
- this.load_agent_thread(
- thread.session_id.clone(),
- thread.cwd.clone(),
- thread.title.clone(),
- window,
- cx,
- );
- }
- },
- )
- .detach();
+
cx.subscribe_in(
&text_thread_history,
window,
@@ -1120,15 +1101,18 @@ impl AgentPanel {
window.defer(cx, move |window, cx| {
let panel = weak_panel.clone();
let agent_navigation_menu =
- ContextMenu::build_persistent(window, cx, move |mut menu, _window, cx| {
+ ContextMenu::build_persistent(window, cx, move |mut menu, window, cx| {
if let Some(panel) = panel.upgrade() {
- if let Some(kind) = panel.read(cx).history_kind_for_selected_agent(cx) {
- menu =
- Self::populate_recently_updated_menu_section(menu, panel, kind, cx);
- let view_all_label = match kind {
- HistoryKind::AgentThreads => "View All",
- HistoryKind::TextThreads => "View All Text Threads",
+ if let Some(history) = panel
+ .update(cx, |panel, cx| panel.history_for_selected_agent(window, cx))
+ {
+ let view_all_label = match history {
+ History::AgentThreads { .. } => "View All",
+ History::TextThreads => "View All Text Threads",
};
+ menu = Self::populate_recently_updated_menu_section(
+ menu, panel, history, cx,
+ );
menu = menu.action(view_all_label, Box::new(OpenHistory));
}
}
@@ -1222,8 +1206,6 @@ impl AgentPanel {
zoomed: false,
pending_serialization: None,
onboarding,
- acp_history,
- acp_history_view,
text_thread_history,
thread_store,
selected_agent: AgentType::default(),
@@ -1288,8 +1270,8 @@ impl AgentPanel {
&self.thread_store
}
- pub fn history(&self) -> &Entity<ThreadHistory> {
- &self.acp_history
+ pub fn connection_store(&self) -> &Entity<AgentConnectionStore> {
+ &self.connection_store
}
pub fn open_thread(
@@ -1353,27 +1335,41 @@ impl AgentPanel {
window: &mut Window,
cx: &mut Context<Self>,
) {
- let Some(thread) = self
- .acp_history
- .read(cx)
- .session_for_id(&action.from_session_id)
- else {
- return;
- };
+ let agent = ExternalAgent::NativeAgent;
- self.external_thread(
- Some(ExternalAgent::NativeAgent),
- None,
- None,
- None,
- Some(AgentInitialContent::ThreadSummary {
- session_id: thread.session_id,
- title: thread.title,
- }),
- true,
- window,
- cx,
- );
+ let server = agent.server(self.fs.clone(), self.thread_store.clone());
+ let session_id = action.from_session_id.clone();
+
+ let entry = self.connection_store.update(cx, |store, cx| {
+ store.request_connection(agent.clone(), server, cx)
+ });
+ let connect_task = entry.read(cx).wait_for_connection();
+
+ cx.spawn_in(window, async move |this, cx| {
+ let history = connect_task.await?.history;
+ this.update_in(cx, |this, window, cx| {
+ let thread = history
+ .read(cx)
+ .session_for_id(&session_id)
+ .context("Session not found")?;
+
+ this.external_thread(
+ Some(agent),
+ None,
+ None,
+ None,
+ Some(AgentInitialContent::ThreadSummary {
+ session_id: thread.session_id,
+ title: thread.title,
+ }),
+ true,
+ window,
+ cx,
+ );
+ anyhow::Ok(())
+ })
+ })
+ .detach_and_log_err(cx);
}
fn new_text_thread(&mut self, window: &mut Window, cx: &mut Context<Self>) {
@@ -1554,13 +1550,52 @@ impl AgentPanel {
})
}
- fn history_kind_for_selected_agent(&self, cx: &App) -> Option<HistoryKind> {
- match self.selected_agent {
- AgentType::NativeAgent => Some(HistoryKind::AgentThreads),
- AgentType::TextThread => Some(HistoryKind::TextThreads),
- AgentType::Custom { .. } => {
- if self.acp_history.read(cx).has_session_list() {
- Some(HistoryKind::AgentThreads)
+ fn has_history_for_selected_agent(&self, cx: &App) -> bool {
+ match &self.selected_agent {
+ AgentType::TextThread | AgentType::NativeAgent => true,
+ AgentType::Custom { name } => {
+ let agent = ExternalAgent::Custom { name: name.clone() };
+ self.connection_store
+ .read(cx)
+ .entry(&agent)
+ .map_or(false, |entry| entry.read(cx).history().is_some())
+ }
+ }
+ }
+
+ fn history_for_selected_agent(
+ &self,
+ window: &mut Window,
+ cx: &mut Context<Self>,
+ ) -> Option<History> {
+ match &self.selected_agent {
+ AgentType::TextThread => Some(History::TextThreads),
+ AgentType::NativeAgent => {
+ let history = self
+ .connection_store
+ .read(cx)
+ .entry(&ExternalAgent::NativeAgent)?
+ .read(cx)
+ .history()?
+ .clone();
+
+ Some(History::AgentThreads {
+ view: self.create_thread_history_view(history, window, cx),
+ })
+ }
+ AgentType::Custom { name } => {
+ let agent = ExternalAgent::Custom { name: name.clone() };
+ let history = self
+ .connection_store
+ .read(cx)
+ .entry(&agent)?
+ .read(cx)
+ .history()?
+ .clone();
+ if history.read(cx).has_session_list() {
+ Some(History::AgentThreads {
+ view: self.create_thread_history_view(history, window, cx),
+ })
} else {
None
}
@@ -1568,13 +1603,38 @@ impl AgentPanel {
}
}
+ fn create_thread_history_view(
+ &self,
+ history: Entity<ThreadHistory>,
+ window: &mut Window,
+ cx: &mut Context<Self>,
+ ) -> Entity<ThreadHistoryView> {
+ let view = cx.new(|cx| ThreadHistoryView::new(history.clone(), window, cx));
+ cx.subscribe_in(&view, window, |this, _, event, window, cx| match event {
+ ThreadHistoryViewEvent::Open(thread) => {
+ this.load_agent_thread(
+ thread.session_id.clone(),
+ thread.cwd.clone(),
+ thread.title.clone(),
+ window,
+ cx,
+ );
+ }
+ })
+ .detach();
+ view
+ }
+
fn open_history(&mut self, window: &mut Window, cx: &mut Context<Self>) {
- let Some(kind) = self.history_kind_for_selected_agent(cx) else {
+ let Some(history) = self.history_for_selected_agent(window, cx) else {
return;
};
- if let ActiveView::History { kind: active_kind } = self.active_view {
- if active_kind == kind {
+ if let ActiveView::History {
+ history: active_history,
+ } = &self.active_view
+ {
+ if active_history == &history {
if let Some(previous_view) = self.previous_view.take() {
self.set_active_view(previous_view, true, window, cx);
}
@@ -1582,7 +1642,7 @@ impl AgentPanel {
}
}
- self.set_active_view(ActiveView::History { kind }, true, window, cx);
+ self.set_active_view(ActiveView::History { history }, true, window, cx);
cx.notify();
}
@@ -1655,7 +1715,7 @@ impl AgentPanel {
window: &mut Window,
cx: &mut Context<Self>,
) {
- if self.history_kind_for_selected_agent(cx).is_none() {
+ if !self.has_history_for_selected_agent(cx) {
return;
}
self.agent_navigation_menu_handle.toggle(window, cx);
@@ -2096,7 +2156,7 @@ impl AgentPanel {
let was_in_agent_history = matches!(
self.active_view,
ActiveView::History {
- kind: HistoryKind::AgentThreads
+ history: History::AgentThreads { .. }
}
);
let current_is_uninitialized = matches!(self.active_view, ActiveView::Uninitialized);
@@ -2154,16 +2214,13 @@ impl AgentPanel {
}
};
- let is_in_agent_history = matches!(
- self.active_view,
- ActiveView::History {
- kind: HistoryKind::AgentThreads
+ if let ActiveView::History { history } = &self.active_view {
+ if !was_in_agent_history && let History::AgentThreads { view } = history {
+ view.update(cx, |view, cx| {
+ view.history()
+ .update(cx, |history, cx| history.refresh_full_history(cx))
+ });
}
- );
-
- if !was_in_agent_history && is_in_agent_history {
- self.acp_history
- .update(cx, |history, cx| history.refresh_full_history(cx));
}
if focus {
@@ -2175,14 +2232,14 @@ impl AgentPanel {
fn populate_recently_updated_menu_section(
mut menu: ContextMenu,
panel: Entity<Self>,
- kind: HistoryKind,
+ history: History,
cx: &mut Context<ContextMenu>,
) -> ContextMenu {
- match kind {
- HistoryKind::AgentThreads => {
- let entries = panel
+ match history {
+ History::AgentThreads { view } => {
+ let entries = view
.read(cx)
- .acp_history
+ .history()
.read(cx)
.sessions()
.iter()
@@ -2224,7 +2281,7 @@ impl AgentPanel {
});
}
}
- HistoryKind::TextThreads => {
+ History::TextThreads => {
let entries = panel
.read(cx)
.text_thread_store
@@ -2518,7 +2575,6 @@ impl AgentPanel {
project,
thread_store,
self.prompt_store.clone(),
- self.acp_history.clone(),
window,
cx,
)
@@ -3056,9 +3112,9 @@ impl Focusable for AgentPanel {
match &self.active_view {
ActiveView::Uninitialized => self.focus_handle.clone(),
ActiveView::AgentThread { server_view, .. } => server_view.focus_handle(cx),
- ActiveView::History { kind } => match kind {
- HistoryKind::AgentThreads => self.acp_history_view.focus_handle(cx),
- HistoryKind::TextThreads => self.text_thread_history.focus_handle(cx),
+ ActiveView::History { history: kind } => match kind {
+ History::AgentThreads { view } => view.read(cx).focus_handle(cx),
+ History::TextThreads => self.text_thread_history.focus_handle(cx),
},
ActiveView::TextThread {
text_thread_editor, ..
@@ -3292,10 +3348,10 @@ impl AgentPanel {
.into_any_element(),
}
}
- ActiveView::History { kind } => {
+ ActiveView::History { history: kind } => {
let title = match kind {
- HistoryKind::AgentThreads => "History",
- HistoryKind::TextThreads => "Text Thread History",
+ History::AgentThreads { .. } => "History",
+ History::TextThreads => "Text Thread History",
};
Label::new(title).truncate().into_any_element()
}
@@ -4122,7 +4178,7 @@ impl AgentPanel {
selected_agent.into_any_element()
};
- let show_history_menu = self.history_kind_for_selected_agent(cx).is_some();
+ let show_history_menu = self.has_history_for_selected_agent(cx);
let has_v2_flag = cx.has_flag::<AgentV2FeatureFlag>();
let is_empty_state = !self.active_thread_has_messages(cx);
@@ -4402,6 +4458,14 @@ impl AgentPanel {
return false;
}
+ let has_configured_non_zed_providers = LanguageModelRegistry::read_global(cx)
+ .visible_providers()
+ .iter()
+ .any(|provider| {
+ provider.is_authenticated(cx)
+ && provider.id() != language_model::ZED_CLOUD_PROVIDER_ID
+ });
+
match &self.active_view {
ActiveView::Uninitialized | ActiveView::History { .. } | ActiveView::Configuration => {
false
@@ -4411,17 +4475,15 @@ impl AgentPanel {
{
false
}
- _ => {
- let history_is_empty = self.acp_history.read(cx).is_empty();
-
- let has_configured_non_zed_providers = LanguageModelRegistry::read_global(cx)
- .visible_providers()
- .iter()
- .any(|provider| {
- provider.is_authenticated(cx)
- && provider.id() != language_model::ZED_CLOUD_PROVIDER_ID
- });
-
+ ActiveView::AgentThread { server_view } => {
+ let history_is_empty = server_view
+ .read(cx)
+ .history()
+ .is_none_or(|h| h.read(cx).is_empty());
+ history_is_empty || !has_configured_non_zed_providers
+ }
+ ActiveView::TextThread { .. } => {
+ let history_is_empty = self.text_thread_history.read(cx).is_empty();
history_is_empty || !has_configured_non_zed_providers
}
}
@@ -4803,9 +4865,9 @@ impl Render for AgentPanel {
ActiveView::AgentThread { server_view, .. } => parent
.child(server_view.clone())
.child(self.render_drag_target(cx)),
- ActiveView::History { kind } => match kind {
- HistoryKind::AgentThreads => parent.child(self.acp_history_view.clone()),
- HistoryKind::TextThreads => parent.child(self.text_thread_history.clone()),
+ ActiveView::History { history: kind } => match kind {
+ History::AgentThreads { view } => parent.child(view.clone()),
+ History::TextThreads => parent.child(self.text_thread_history.clone()),
},
ActiveView::TextThread {
text_thread_editor,
@@ -4910,17 +4972,26 @@ impl rules_library::InlineAssistDelegate for PromptLibraryInlineAssist {
let Some(panel) = workspace.read(cx).panel::<AgentPanel>(cx) else {
return;
};
+ let Some(history) = panel
+ .read(cx)
+ .connection_store()
+ .read(cx)
+ .entry(&crate::ExternalAgent::NativeAgent)
+ .and_then(|s| s.read(cx).history())
+ else {
+ log::error!("No connection entry found for native agent");
+ return;
+ };
let project = workspace.read(cx).project().downgrade();
let panel = panel.read(cx);
let thread_store = panel.thread_store().clone();
- let history = panel.history().downgrade();
assistant.assist(
prompt_editor,
self.workspace.clone(),
project,
thread_store,
None,
- history,
+ history.downgrade(),
initial_prompt,
window,
cx,
@@ -67,7 +67,9 @@ use super::entry_view_state::EntryViewState;
use super::thread_history::ThreadHistory;
use crate::ModeSelector;
use crate::ModelSelectorPopover;
-use crate::agent_connection_store::{AgentConnectionStore, ConnectionEntryEvent};
+use crate::agent_connection_store::{
+ AgentConnectedState, AgentConnectionEntryEvent, AgentConnectionStore,
+};
use crate::agent_diff::AgentDiff;
use crate::entry_view_state::{EntryViewEvent, ViewEvent};
use crate::message_editor::{MessageEditor, MessageEditorEvent};
@@ -314,7 +316,6 @@ pub struct ConnectionView {
thread_store: Option<Entity<ThreadStore>>,
prompt_store: Option<Entity<PromptStore>>,
server_state: ServerState,
- history: Entity<ThreadHistory>,
focus_handle: FocusHandle,
notifications: Vec<WindowHandle<AgentNotification>>,
notification_subscriptions: HashMap<WindowHandle<AgentNotification>, Vec<Subscription>>,
@@ -418,6 +419,7 @@ pub struct ConnectedServerState {
active_id: Option<acp::SessionId>,
threads: HashMap<acp::SessionId, Entity<ThreadView>>,
connection: Rc<dyn AgentConnection>,
+ history: Entity<ThreadHistory>,
conversation: Entity<Conversation>,
_connection_entry_subscription: Subscription,
}
@@ -484,7 +486,6 @@ impl ConnectionView {
project: Entity<Project>,
thread_store: Option<Entity<ThreadStore>>,
prompt_store: Option<Entity<PromptStore>>,
- history: Entity<ThreadHistory>,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
@@ -537,7 +538,6 @@ impl ConnectionView {
notifications: Vec::new(),
notification_subscriptions: HashMap::default(),
auth_task: None,
- history,
_subscriptions: subscriptions,
focus_handle: cx.focus_handle(),
}
@@ -660,7 +660,7 @@ impl ConnectionView {
let connection_entry_subscription =
cx.subscribe(&connection_entry, |this, _entry, event, cx| match event {
- ConnectionEntryEvent::NewVersionAvailable(version) => {
+ AgentConnectionEntryEvent::NewVersionAvailable(version) => {
if let Some(thread) = this.active_thread() {
thread.update(cx, |thread, cx| {
thread.new_server_version_available = Some(version.clone());
@@ -674,8 +674,11 @@ impl ConnectionView {
let load_session_id = resume_session_id.clone();
let load_task = cx.spawn_in(window, async move |this, cx| {
- let connection = match connect_result.await {
- Ok(connection) => connection,
+ let (connection, history) = match connect_result.await {
+ Ok(AgentConnectedState {
+ connection,
+ history,
+ }) => (connection, history),
Err(err) => {
this.update_in(cx, |this, window, cx| {
this.handle_load_error(load_session_id.clone(), err, window, cx);
@@ -764,6 +767,7 @@ impl ConnectionView {
conversation.clone(),
resumed_without_history,
initial_content,
+ history.clone(),
window,
cx,
);
@@ -777,14 +781,6 @@ impl ConnectionView {
}
let id = current.read(cx).thread.read(cx).session_id().clone();
- let session_list = if connection.supports_session_history() {
- connection.session_list(cx)
- } else {
- None
- };
- this.history.update(cx, |history, cx| {
- history.set_session_list(session_list, cx);
- });
this.set_server_state(
ServerState::Connected(ConnectedServerState {
connection,
@@ -792,6 +788,7 @@ impl ConnectionView {
active_id: Some(id.clone()),
threads: HashMap::from_iter([(id, current)]),
conversation,
+ history,
_connection_entry_subscription: connection_entry_subscription,
}),
cx,
@@ -825,6 +822,7 @@ impl ConnectionView {
conversation: Entity<Conversation>,
resumed_without_history: bool,
initial_content: Option<AgentInitialContent>,
+ history: Entity<ThreadHistory>,
window: &mut Window,
cx: &mut Context<Self>,
) -> Entity<ThreadView> {
@@ -841,7 +839,7 @@ impl ConnectionView {
self.workspace.clone(),
self.project.downgrade(),
self.thread_store.clone(),
- self.history.downgrade(),
+ history.downgrade(),
self.prompt_store.clone(),
prompt_capabilities.clone(),
available_commands.clone(),
@@ -1008,7 +1006,7 @@ impl ConnectionView {
resumed_without_history,
self.project.downgrade(),
self.thread_store.clone(),
- self.history.clone(),
+ history,
self.prompt_store.clone(),
initial_content,
subscriptions,
@@ -1090,6 +1088,7 @@ impl ConnectionView {
threads: HashMap::default(),
connection,
conversation: cx.new(|_cx| Conversation::default()),
+ history: cx.new(|cx| ThreadHistory::new(None, cx)),
_connection_entry_subscription: Subscription::new(|| {}),
}),
cx,
@@ -1694,10 +1693,10 @@ impl ConnectionView {
cx.spawn_in(window, async move |this, cx| {
let subagent_thread = subagent_thread_task.await?;
this.update_in(cx, |this, window, cx| {
- let conversation = this
+ let Some((conversation, history)) = this
.as_connected()
- .map(|connected| connected.conversation.clone());
- let Some(conversation) = conversation else {
+ .map(|connected| (connected.conversation.clone(), connected.history.clone()))
+ else {
return;
};
conversation.update(cx, |conversation, cx| {
@@ -1709,6 +1708,7 @@ impl ConnectionView {
conversation,
false,
None,
+ history,
window,
cx,
);
@@ -2215,9 +2215,11 @@ impl ConnectionView {
let agent_name = self.agent.name();
let workspace = self.workspace.clone();
let project = self.project.downgrade();
- let history = self.history.downgrade();
-
- let Some(thread) = self.active_thread() else {
+ let Some(connected) = self.as_connected() else {
+ return;
+ };
+ let history = connected.history.downgrade();
+ let Some(thread) = connected.active_view() else {
return;
};
let prompt_capabilities = thread.read(cx).prompt_capabilities.clone();
@@ -2610,8 +2612,16 @@ impl ConnectionView {
})
}
+ pub fn history(&self) -> Option<&Entity<ThreadHistory>> {
+ self.as_connected().map(|c| &c.history)
+ }
+
pub fn delete_history_entry(&mut self, session_id: &acp::SessionId, cx: &mut Context<Self>) {
- let task = self
+ let Some(connected) = self.as_connected() else {
+ return;
+ };
+
+ let task = connected
.history
.update(cx, |history, cx| history.delete_session(&session_id, cx));
task.detach_and_log_err(cx);
@@ -2900,8 +2910,6 @@ pub(crate) mod tests {
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
let thread_store = cx.update(|_window, cx| cx.new(|cx| ThreadStore::new(cx)));
- // Create history without an initial session list - it will be set after connection
- let history = cx.update(|_window, cx| cx.new(|cx| ThreadHistory::new(None, cx)));
let connection_store =
cx.update(|_window, cx| cx.new(|cx| AgentConnectionStore::new(project.clone(), cx)));
@@ -2921,7 +2929,6 @@ pub(crate) mod tests {
project,
Some(thread_store),
None,
- history.clone(),
window,
cx,
)
@@ -2931,6 +2938,14 @@ pub(crate) mod tests {
// Wait for connection to establish
cx.run_until_parked();
+ let history = cx.update(|_window, cx| {
+ thread_view
+ .read(cx)
+ .history()
+ .expect("Missing history")
+ .clone()
+ });
+
// Initially empty because StubAgentConnection.session_list() returns None
active_thread(&thread_view, cx).read_with(cx, |view, _cx| {
assert_eq!(view.recent_history_entries.len(), 0);
@@ -3007,7 +3022,6 @@ pub(crate) mod tests {
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
let thread_store = cx.update(|_window, cx| cx.new(|cx| ThreadStore::new(cx)));
- let history = cx.update(|_window, cx| cx.new(|cx| ThreadHistory::new(None, cx)));
let connection_store =
cx.update(|_window, cx| cx.new(|cx| AgentConnectionStore::new(project.clone(), cx)));
@@ -3027,7 +3041,6 @@ pub(crate) mod tests {
project,
Some(thread_store),
None,
- history,
window,
cx,
)
@@ -3066,7 +3079,6 @@ pub(crate) mod tests {
let captured_cwd = connection.captured_cwd.clone();
let thread_store = cx.update(|_window, cx| cx.new(|cx| ThreadStore::new(cx)));
- let history = cx.update(|_window, cx| cx.new(|cx| ThreadHistory::new(None, cx)));
let connection_store =
cx.update(|_window, cx| cx.new(|cx| AgentConnectionStore::new(project.clone(), cx)));
@@ -3086,7 +3098,6 @@ pub(crate) mod tests {
project,
Some(thread_store),
None,
- history,
window,
cx,
)
@@ -3123,7 +3134,6 @@ pub(crate) mod tests {
let captured_cwd = connection.captured_cwd.clone();
let thread_store = cx.update(|_window, cx| cx.new(|cx| ThreadStore::new(cx)));
- let history = cx.update(|_window, cx| cx.new(|cx| ThreadHistory::new(None, cx)));
let connection_store =
cx.update(|_window, cx| cx.new(|cx| AgentConnectionStore::new(project.clone(), cx)));
@@ -3143,7 +3153,6 @@ pub(crate) mod tests {
project,
Some(thread_store),
None,
- history,
window,
cx,
)
@@ -3180,7 +3189,6 @@ pub(crate) mod tests {
let captured_cwd = connection.captured_cwd.clone();
let thread_store = cx.update(|_window, cx| cx.new(|cx| ThreadStore::new(cx)));
- let history = cx.update(|_window, cx| cx.new(|cx| ThreadHistory::new(None, cx)));
let connection_store =
cx.update(|_window, cx| cx.new(|cx| AgentConnectionStore::new(project.clone(), cx)));
@@ -3200,7 +3208,6 @@ pub(crate) mod tests {
project,
Some(thread_store),
None,
- history,
window,
cx,
)
@@ -3498,7 +3505,6 @@ pub(crate) mod tests {
// Set up thread view in workspace 1
let thread_store = cx.update(|_window, cx| cx.new(|cx| ThreadStore::new(cx)));
- let history = cx.update(|_window, cx| cx.new(|cx| ThreadHistory::new(None, cx)));
let connection_store =
cx.update(|_window, cx| cx.new(|cx| AgentConnectionStore::new(project1.clone(), cx)));
@@ -3519,7 +3525,6 @@ pub(crate) mod tests {
project1.clone(),
Some(thread_store),
None,
- history,
window,
cx,
)
@@ -3676,7 +3681,8 @@ pub(crate) mod tests {
agent: impl AgentServer + 'static,
cx: &mut TestAppContext,
) -> (Entity<ConnectionView>, &mut VisualTestContext) {
- let (thread_view, _history, cx) = setup_thread_view_with_history(agent, cx).await;
+ let (thread_view, _history, cx) =
+ setup_thread_view_with_history_and_initial_content(agent, None, cx).await;
(thread_view, cx)
}
@@ -3688,7 +3694,9 @@ pub(crate) mod tests {
Entity<ThreadHistory>,
&mut VisualTestContext,
) {
- setup_thread_view_with_history_and_initial_content(agent, None, cx).await
+ let (thread_view, history, cx) =
+ setup_thread_view_with_history_and_initial_content(agent, None, cx).await;
+ (thread_view, history.expect("Missing history"), cx)
}
async fn setup_thread_view_with_initial_content(
@@ -3708,7 +3716,7 @@ pub(crate) mod tests {
cx: &mut TestAppContext,
) -> (
Entity<ConnectionView>,
- Entity<ThreadHistory>,
+ Option<Entity<ThreadHistory>>,
&mut VisualTestContext,
) {
let fs = FakeFs::new(cx.executor());
@@ -3718,18 +3726,19 @@ pub(crate) mod tests {
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
let thread_store = cx.update(|_window, cx| cx.new(|cx| ThreadStore::new(cx)));
- let history = cx.update(|_window, cx| cx.new(|cx| ThreadHistory::new(None, cx)));
let connection_store =
cx.update(|_window, cx| cx.new(|cx| AgentConnectionStore::new(project.clone(), cx)));
+ let agent_key = ExternalAgent::Custom {
+ name: "Test".into(),
+ };
+
let thread_view = cx.update(|window, cx| {
cx.new(|cx| {
ConnectionView::new(
Rc::new(agent),
- connection_store,
- ExternalAgent::Custom {
- name: "Test".into(),
- },
+ connection_store.clone(),
+ agent_key.clone(),
None,
None,
None,
@@ -3738,13 +3747,20 @@ pub(crate) mod tests {
project,
Some(thread_store),
None,
- history.clone(),
window,
cx,
)
})
});
cx.run_until_parked();
+
+ let history = cx.update(|_window, cx| {
+ connection_store
+ .read(cx)
+ .entry(&agent_key)
+ .and_then(|e| e.read(cx).history().cloned())
+ });
+
(thread_view, history, cx)
}
@@ -4454,7 +4470,6 @@ pub(crate) mod tests {
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
let thread_store = cx.update(|_window, cx| cx.new(|cx| ThreadStore::new(cx)));
- let history = cx.update(|_window, cx| cx.new(|cx| ThreadHistory::new(None, cx)));
let connection_store =
cx.update(|_window, cx| cx.new(|cx| AgentConnectionStore::new(project.clone(), cx)));
@@ -4475,7 +4490,6 @@ pub(crate) mod tests {
project.clone(),
Some(thread_store.clone()),
None,
- history,
window,
cx,
)
@@ -266,7 +266,7 @@ impl InlineAssistant {
return;
};
- let configuration_error = || {
+ let configuration_error = |cx| {
let model_registry = LanguageModelRegistry::read_global(cx);
model_registry.configuration_error(model_registry.inline_assistant_model(), cx)
};
@@ -278,7 +278,15 @@ impl InlineAssistant {
let prompt_store = agent_panel.prompt_store().as_ref().cloned();
let thread_store = agent_panel.thread_store().clone();
- let history = agent_panel.history().downgrade();
+ let Some(history) = agent_panel
+ .connection_store()
+ .read(cx)
+ .entry(&crate::ExternalAgent::NativeAgent)
+ .and_then(|s| s.read(cx).history().cloned())
+ else {
+ log::error!("No connection entry found for native agent");
+ return;
+ };
let handle_assist =
|window: &mut Window, cx: &mut Context<Workspace>| match inline_assist_target {
@@ -290,7 +298,7 @@ impl InlineAssistant {
workspace.project().downgrade(),
thread_store,
prompt_store,
- history,
+ history.downgrade(),
action.prompt.clone(),
window,
cx,
@@ -305,7 +313,7 @@ impl InlineAssistant {
workspace.project().downgrade(),
thread_store,
prompt_store,
- history,
+ history.downgrade(),
action.prompt.clone(),
window,
cx,
@@ -314,7 +322,7 @@ impl InlineAssistant {
}
};
- if let Some(error) = configuration_error() {
+ if let Some(error) = configuration_error(cx) {
if let ConfigurationError::ProviderNotAuthenticated(provider) = error {
cx.spawn(async move |_, cx| {
cx.update(|cx| provider.authenticate(cx)).await?;
@@ -322,7 +330,7 @@ impl InlineAssistant {
})
.detach_and_log_err(cx);
- if configuration_error().is_none() {
+ if configuration_error(cx).is_none() {
handle_assist(window, cx);
}
} else {
@@ -1969,7 +1977,16 @@ impl CodeActionProvider for AssistantCodeActionProvider {
.panel::<AgentPanel>(cx)
.context("missing agent panel")?
.read(cx);
- anyhow::Ok((panel.thread_store().clone(), panel.history().downgrade()))
+
+ let history = panel
+ .connection_store()
+ .read(cx)
+ .entry(&crate::ExternalAgent::NativeAgent)
+ .and_then(|e| e.read(cx).history())
+ .context("no history found for native agent")?
+ .downgrade();
+
+ anyhow::Ok((panel.thread_store().clone(), history))
})??;
let editor = editor.upgrade().context("editor was released")?;
let range = editor
@@ -116,6 +116,10 @@ impl TextThreadHistory {
this
}
+ pub fn is_empty(&self) -> bool {
+ self.visible_items.is_empty()
+ }
+
fn update_visible_items(&mut self, preserve_selected_item: bool, cx: &mut Context<Self>) {
let entries = self.text_thread_store.update(cx, |store, _| {
store.ordered_text_threads().cloned().collect::<Vec<_>>()
@@ -19,14 +19,23 @@ impl ThreadHistory {
_refresh_task: Task::ready(()),
_watch_task: None,
};
- this.set_session_list(session_list, cx);
+ this.set_session_list_impl(session_list, cx);
this
}
+ #[cfg(any(test, feature = "test-support"))]
pub fn set_session_list(
&mut self,
session_list: Option<Rc<dyn AgentSessionList>>,
cx: &mut Context<Self>,
+ ) {
+ self.set_session_list_impl(session_list, cx);
+ }
+
+ fn set_session_list_impl(
+ &mut self,
+ session_list: Option<Rc<dyn AgentSessionList>>,
+ cx: &mut Context<Self>,
) {
if let (Some(current), Some(next)) = (&self.session_list, &session_list)
&& Rc::ptr_eq(current, next)
@@ -117,6 +117,10 @@ impl ThreadHistoryView {
this
}
+ pub fn history(&self) -> &Entity<ThreadHistory> {
+ &self.history
+ }
+
fn update_visible_items(&mut self, preserve_selected_item: bool, cx: &mut Context<Self>) {
let entries = self.history.read(cx).sessions().to_vec();
let new_list_items = if self.search_query.is_empty() {