From 197cf60d05ea7d2b3e9126c70b05e9554691aa12 Mon Sep 17 00:00:00 2001 From: Rabi Mishra Date: Tue, 3 Mar 2026 15:51:40 +0530 Subject: [PATCH] agent_ui: Refresh ACP history after thread create/load (#49796) Moves loading of the history connection to once we have a new connection, not on every thread view. Release Notes: - N/A --------- Signed-off-by: rabi Co-authored-by: Ben Brandt --- crates/agent_ui/src/connection_view.rs | 205 +++++++++++++++++++------ 1 file changed, 159 insertions(+), 46 deletions(-) diff --git a/crates/agent_ui/src/connection_view.rs b/crates/agent_ui/src/connection_view.rs index 93bf7c98098530b23522c60f987f9e341ebc69ca..bc58120a964b7cb10eb4c779eb24fa8507030bc6 100644 --- a/crates/agent_ui/src/connection_view.rs +++ b/crates/agent_ui/src/connection_view.rs @@ -728,6 +728,14 @@ 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, @@ -833,14 +841,6 @@ impl ConnectionView { let connection = thread.read(cx).connection().clone(); let session_id = thread.read(cx).session_id().clone(); - let session_list = if connection.supports_session_history() { - connection.session_list(cx) - } else { - None - }; - self.history.update(cx, |history, cx| { - history.set_session_list(session_list, cx); - }); // Check for config options first // Config options take precedence over legacy mode/model selectors @@ -2835,6 +2835,33 @@ pub(crate) mod tests { }); } + #[gpui::test] + async fn test_new_thread_creation_triggers_session_list_refresh(cx: &mut TestAppContext) { + init_test(cx); + + let session = AgentSessionInfo::new(SessionId::new("history-session")); + let (thread_view, history, cx) = setup_thread_view_with_history( + StubAgentServer::new(SessionHistoryConnection::new(vec![session.clone()])), + cx, + ) + .await; + + history.read_with(cx, |history, _cx| { + assert!( + history.has_session_list(), + "session list should be attached after thread creation" + ); + }); + + active_thread(&thread_view, cx).read_with(cx, |view, _cx| { + assert_eq!(view.recent_history_entries.len(), 1); + assert_eq!( + view.recent_history_entries[0].session_id, + session.session_id + ); + }); + } + #[gpui::test] async fn test_resume_without_history_adds_notice(cx: &mut TestAppContext) { init_test(cx); @@ -3482,6 +3509,18 @@ pub(crate) mod tests { agent: impl AgentServer + 'static, cx: &mut TestAppContext, ) -> (Entity, &mut VisualTestContext) { + let (thread_view, _history, cx) = setup_thread_view_with_history(agent, cx).await; + (thread_view, cx) + } + + async fn setup_thread_view_with_history( + agent: impl AgentServer + 'static, + cx: &mut TestAppContext, + ) -> ( + Entity, + Entity, + &mut VisualTestContext, + ) { let fs = FakeFs::new(cx.executor()); let project = Project::test(fs, [], cx).await; let (multi_workspace, cx) = @@ -3501,14 +3540,14 @@ pub(crate) mod tests { project, Some(thread_store), None, - history, + history.clone(), window, cx, ) }) }); cx.run_until_parked(); - (thread_view, cx) + (thread_view, history, cx) } fn add_to_workspace(thread_view: Entity, cx: &mut VisualTestContext) { @@ -3648,6 +3687,102 @@ pub(crate) mod tests { ) -> Task> { Task::ready(Ok(AgentSessionListResponse::new(self.sessions.clone()))) } + + fn into_any(self: Rc) -> Rc { + self + } + } + + #[derive(Clone)] + struct SessionHistoryConnection { + sessions: Vec, + } + + impl SessionHistoryConnection { + fn new(sessions: Vec) -> Self { + Self { sessions } + } + } + + fn build_test_thread( + connection: Rc, + project: Entity, + name: &'static str, + session_id: SessionId, + cx: &mut App, + ) -> Entity { + let action_log = cx.new(|_| ActionLog::new(project.clone())); + cx.new(|cx| { + AcpThread::new( + None, + name, + connection, + project, + action_log, + session_id, + watch::Receiver::constant( + acp::PromptCapabilities::new() + .image(true) + .audio(true) + .embedded_context(true), + ), + cx, + ) + }) + } + + impl AgentConnection for SessionHistoryConnection { + fn telemetry_id(&self) -> SharedString { + "history-connection".into() + } + + fn new_session( + self: Rc, + project: Entity, + _cwd: &Path, + cx: &mut App, + ) -> Task>> { + let thread = build_test_thread( + self, + project, + "SessionHistoryConnection", + SessionId::new("history-session"), + cx, + ); + Task::ready(Ok(thread)) + } + + fn supports_load_session(&self) -> bool { + true + } + + fn session_list(&self, _cx: &mut App) -> Option> { + Some(Rc::new(StubSessionList::new(self.sessions.clone()))) + } + + fn auth_methods(&self) -> &[acp::AuthMethod] { + &[] + } + + fn authenticate( + &self, + _method_id: acp::AuthMethodId, + _cx: &mut App, + ) -> Task> { + Task::ready(Ok(())) + } + + fn prompt( + &self, + _id: Option, + _params: acp::PromptRequest, + _cx: &mut App, + ) -> Task> { + Task::ready(Ok(acp::PromptResponse::new(acp::StopReason::EndTurn))) + } + + fn cancel(&self, _session_id: &acp::SessionId, _cx: &mut App) {} + fn into_any(self: Rc) -> Rc { self } @@ -3667,24 +3802,13 @@ pub(crate) mod tests { _cwd: &Path, cx: &mut gpui::App, ) -> Task>> { - let action_log = cx.new(|_| ActionLog::new(project.clone())); - let thread = cx.new(|cx| { - AcpThread::new( - None, - "ResumeOnlyAgentConnection", - self.clone(), - project, - action_log, - SessionId::new("new-session"), - watch::Receiver::constant( - acp::PromptCapabilities::new() - .image(true) - .audio(true) - .embedded_context(true), - ), - cx, - ) - }); + let thread = build_test_thread( + self, + project, + "ResumeOnlyAgentConnection", + SessionId::new("new-session"), + cx, + ); Task::ready(Ok(thread)) } @@ -3699,24 +3823,13 @@ pub(crate) mod tests { _cwd: &Path, cx: &mut App, ) -> Task>> { - let action_log = cx.new(|_| ActionLog::new(project.clone())); - let thread = cx.new(|cx| { - AcpThread::new( - None, - "ResumeOnlyAgentConnection", - self.clone(), - project, - action_log, - session.session_id, - watch::Receiver::constant( - acp::PromptCapabilities::new() - .image(true) - .audio(true) - .embedded_context(true), - ), - cx, - ) - }); + let thread = build_test_thread( + self, + project, + "ResumeOnlyAgentConnection", + session.session_id, + cx, + ); Task::ready(Ok(thread)) }