diff --git a/crates/agent_ui/src/agent_panel.rs b/crates/agent_ui/src/agent_panel.rs index b363d6a42a..8a09f93999 100644 --- a/crates/agent_ui/src/agent_panel.rs +++ b/crates/agent_ui/src/agent_panel.rs @@ -33,7 +33,7 @@ use crate::DEFAULT_THREAD_TITLE; use crate::ExpandMessageEditor; use crate::ManageProfiles; use crate::agent_connection_store::AgentConnectionStore; -use crate::thread_metadata_store::{ThreadId, ThreadMetadataStore}; +use crate::thread_metadata_store::{ThreadId, ThreadMetadataStore, ThreadMetadataStoreEvent}; use crate::{ AddContextServer, AgentDiffPane, ConversationView, CopyThreadToClipboard, Follow, InlineAssistant, LoadThreadFromClipboard, NewThread, OpenActiveThreadAsMarkdown, OpenAgentDiff, @@ -708,6 +708,7 @@ pub struct AgentPanel { show_trust_workspace_message: bool, _base_view_observation: Option, _draft_editor_observation: Option, + _thread_metadata_store_subscription: Subscription, } impl AgentPanel { @@ -1022,6 +1023,17 @@ impl AgentPanel { } _ => {} }); + + let _thread_metadata_store_subscription = cx.subscribe( + &ThreadMetadataStore::global(cx), + |this, _store, event, cx| { + let ThreadMetadataStoreEvent::ThreadArchived(thread_id) = event; + if this.retained_threads.remove(thread_id).is_some() { + cx.notify(); + } + }, + ); + let mut panel = Self { workspace_id, base_view, @@ -1055,6 +1067,7 @@ impl AgentPanel { new_user_onboarding_upsell_dismissed: AtomicBool::new(OnboardingUpsell::dismissed(cx)), _base_view_observation: None, _draft_editor_observation: None, + _thread_metadata_store_subscription, }; // Initial sync of agent servers from extensions diff --git a/crates/agent_ui/src/thread_metadata_store.rs b/crates/agent_ui/src/thread_metadata_store.rs index 21ac2af099..0b7e023e2c 100644 --- a/crates/agent_ui/src/thread_metadata_store.rs +++ b/crates/agent_ui/src/thread_metadata_store.rs @@ -785,6 +785,8 @@ impl ThreadMetadataStore { if let Some(job) = archive_job { self.in_flight_archives.insert(thread_id, job); } + + cx.emit(ThreadMetadataStoreEvent::ThreadArchived(thread_id)); } pub fn unarchive(&mut self, thread_id: ThreadId, cx: &mut Context) { @@ -1228,6 +1230,13 @@ impl ThreadMetadataStore { impl Global for ThreadMetadataStore {} +#[derive(Clone, Debug)] +pub enum ThreadMetadataStoreEvent { + ThreadArchived(ThreadId), +} + +impl gpui::EventEmitter for ThreadMetadataStore {} + struct ThreadMetadataDb(ThreadSafeConnection); impl Domain for ThreadMetadataDb { diff --git a/crates/sidebar/src/sidebar_tests.rs b/crates/sidebar/src/sidebar_tests.rs index 2de895114d..9224edc3be 100644 --- a/crates/sidebar/src/sidebar_tests.rs +++ b/crates/sidebar/src/sidebar_tests.rs @@ -6141,6 +6141,45 @@ async fn test_archive_thread_keeps_metadata_but_hides_from_sidebar(cx: &mut Test }); } +#[gpui::test] +async fn test_archive_thread_drops_retained_conversation_view(cx: &mut TestAppContext) { + let project = init_test_project_with_agent_panel("/project-a", cx).await; + let (multi_workspace, cx) = + cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx)); + let (sidebar, panel) = setup_sidebar_with_agent_panel(&multi_workspace, cx); + cx.run_until_parked(); + + let connection = acp_thread::StubAgentConnection::new(); + connection.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk( + acp::ContentChunk::new("Done".into()), + )]); + open_thread_with_connection(&panel, connection, cx); + send_message(&panel, cx); + let session_id = active_session_id(&panel, cx); + let thread_id = active_thread_id(&panel, cx); + cx.run_until_parked(); + + sidebar.read_with(cx, |sidebar, _| { + assert!( + is_active_session(sidebar, &session_id), + "expected the newly created thread to be active before archiving", + ); + }); + + sidebar.update_in(cx, |sidebar, window, cx| { + sidebar.archive_thread(&session_id, window, cx); + }); + cx.run_until_parked(); + + panel.read_with(cx, |panel, _| { + assert!( + !panel.is_retained_thread(&thread_id), + "archiving a thread must drop its ConversationView from retained_threads, \ + but the archived thread id {thread_id:?} is still retained", + ); + }); +} + #[gpui::test] async fn test_archive_thread_active_entry_management(cx: &mut TestAppContext) { // Tests two archive scenarios: