@@ -159,6 +159,11 @@ impl ThreadMetadataStore {
let weak_store = cx.weak_entity();
cx.observe_new::<acp_thread::AcpThread>(move |thread, _window, cx| {
+ // Don't track subagent threads in the sidebar.
+ if thread.parent_session_id().is_some() {
+ return;
+ }
+
let thread_entity = cx.entity();
cx.on_release({
@@ -195,6 +200,11 @@ impl ThreadMetadataStore {
event: &acp_thread::AcpThreadEvent,
cx: &mut Context<Self>,
) {
+ // Don't track subagent threads in the sidebar.
+ if thread.read(cx).parent_session_id().is_some() {
+ return;
+ }
+
match event {
acp_thread::AcpThreadEvent::NewEntry
| acp_thread::AcpThreadEvent::EntryUpdated(_)
@@ -365,8 +375,17 @@ impl Column for ThreadMetadata {
#[cfg(test)]
mod tests {
use super::*;
+ use acp_thread::{AgentConnection, StubAgentConnection};
+ use action_log::ActionLog;
use agent::DbThread;
+ use agent_client_protocol as acp;
+ use feature_flags::FeatureFlagAppExt;
use gpui::TestAppContext;
+ use project::FakeFs;
+ use project::Project;
+ use std::path::Path;
+ use std::rc::Rc;
+ use util::path_list::PathList;
fn make_db_thread(title: &str, updated_at: DateTime<Utc>) -> DbThread {
DbThread {
@@ -525,4 +544,88 @@ mod tests {
assert_eq!(list.len(), 1);
assert_eq!(list[0].session_id.0.as_ref(), "existing-session");
}
+
+ #[gpui::test]
+ async fn test_subagent_threads_excluded_from_sidebar_metadata(cx: &mut TestAppContext) {
+ cx.update(|cx| {
+ let settings_store = settings::SettingsStore::test(cx);
+ cx.set_global(settings_store);
+ cx.update_flags(true, vec!["agent-v2".to_string()]);
+ ThreadStore::init_global(cx);
+ ThreadMetadataStore::init_global(cx);
+ });
+
+ let fs = FakeFs::new(cx.executor());
+ let project = Project::test(fs, None::<&Path>, cx).await;
+ let connection = Rc::new(StubAgentConnection::new());
+
+ // Create a regular (non-subagent) AcpThread.
+ let regular_thread = cx
+ .update(|cx| {
+ connection
+ .clone()
+ .new_session(project.clone(), PathList::default(), cx)
+ })
+ .await
+ .unwrap();
+
+ let regular_session_id = cx.read(|cx| regular_thread.read(cx).session_id().clone());
+
+ // Set a title on the regular thread to trigger a save via handle_thread_update.
+ cx.update(|cx| {
+ regular_thread.update(cx, |thread, cx| {
+ thread.set_title("Regular Thread".into(), cx).detach();
+ });
+ });
+ cx.run_until_parked();
+
+ // Create a subagent AcpThread
+ let subagent_session_id = acp::SessionId::new("subagent-session");
+ let subagent_thread = cx.update(|cx| {
+ let action_log = cx.new(|_| ActionLog::new(project.clone()));
+ cx.new(|cx| {
+ acp_thread::AcpThread::new(
+ Some(regular_session_id.clone()),
+ "Subagent Thread",
+ None,
+ connection.clone(),
+ project.clone(),
+ action_log,
+ subagent_session_id.clone(),
+ watch::Receiver::constant(acp::PromptCapabilities::new()),
+ cx,
+ )
+ })
+ });
+
+ // Set a title on the subagent thread to trigger handle_thread_update.
+ cx.update(|cx| {
+ subagent_thread.update(cx, |thread, cx| {
+ thread
+ .set_title("Subagent Thread Title".into(), cx)
+ .detach();
+ });
+ });
+ cx.run_until_parked();
+
+ // List all metadata from the store.
+ let metadata_list = cx.update(|cx| {
+ let store = ThreadMetadataStore::global(cx);
+ store.read(cx).list(cx)
+ });
+
+ let list = metadata_list.await.unwrap();
+
+ // The subagent thread should NOT appear in the sidebar metadata.
+ // Only the regular thread should be listed.
+ assert_eq!(
+ list.len(),
+ 1,
+ "Expected only the regular thread in sidebar metadata, \
+ but found {} entries (subagent threads are leaking into the sidebar)",
+ list.len(),
+ );
+ assert_eq!(list[0].session_id, regular_session_id);
+ assert_eq!(list[0].title.as_ref(), "Regular Thread");
+ }
}