@@ -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<ConnectionView>, &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<ConnectionView>,
+ Entity<ThreadHistory>,
+ &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<ConnectionView>, cx: &mut VisualTestContext) {
@@ -3648,6 +3687,102 @@ pub(crate) mod tests {
) -> Task<anyhow::Result<AgentSessionListResponse>> {
Task::ready(Ok(AgentSessionListResponse::new(self.sessions.clone())))
}
+
+ fn into_any(self: Rc<Self>) -> Rc<dyn Any> {
+ self
+ }
+ }
+
+ #[derive(Clone)]
+ struct SessionHistoryConnection {
+ sessions: Vec<AgentSessionInfo>,
+ }
+
+ impl SessionHistoryConnection {
+ fn new(sessions: Vec<AgentSessionInfo>) -> Self {
+ Self { sessions }
+ }
+ }
+
+ fn build_test_thread(
+ connection: Rc<dyn AgentConnection>,
+ project: Entity<Project>,
+ name: &'static str,
+ session_id: SessionId,
+ cx: &mut App,
+ ) -> Entity<AcpThread> {
+ 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<Self>,
+ project: Entity<Project>,
+ _cwd: &Path,
+ cx: &mut App,
+ ) -> Task<anyhow::Result<Entity<AcpThread>>> {
+ 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<Rc<dyn AgentSessionList>> {
+ 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<anyhow::Result<()>> {
+ Task::ready(Ok(()))
+ }
+
+ fn prompt(
+ &self,
+ _id: Option<acp_thread::UserMessageId>,
+ _params: acp::PromptRequest,
+ _cx: &mut App,
+ ) -> Task<anyhow::Result<acp::PromptResponse>> {
+ Task::ready(Ok(acp::PromptResponse::new(acp::StopReason::EndTurn)))
+ }
+
+ fn cancel(&self, _session_id: &acp::SessionId, _cx: &mut App) {}
+
fn into_any(self: Rc<Self>) -> Rc<dyn Any> {
self
}
@@ -3667,24 +3802,13 @@ pub(crate) mod tests {
_cwd: &Path,
cx: &mut gpui::App,
) -> Task<gpui::Result<Entity<AcpThread>>> {
- 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<gpui::Result<Entity<AcpThread>>> {
- 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))
}