From 377e78b8bf90a64ad868e73684d9a3b50cb1e16c Mon Sep 17 00:00:00 2001 From: Anthony Eid <56899983+Anthony-Eid@users.noreply.github.com> Date: Thu, 9 Apr 2026 22:45:01 -0400 Subject: [PATCH] agent: Support remote connection args in thread metadata database (#53550) This PR adds remote connection data to the threads metadata database. This fixes an issue where threads ran on separate projects with the same remote/local path list would show up in the sidebar in both workspaces, instead of only the workspace they were originally created in. I added a migrator that uses the workspace persistence database to add remote connection argument to threads that only have path list matches with a remote project. If a path list matches with both local/remote workspaces, we default to setting it as local. Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content is consistent with the [UI/UX checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Release Notes: - N/A or Added/Fixed/Improved ... --------- Co-authored-by: Eric Holk --- crates/agent_ui/src/thread_import.rs | 117 +++++---- crates/agent_ui/src/thread_metadata_store.rs | 240 +++++++++++++++++-- crates/sidebar/src/sidebar_tests.rs | 20 ++ 3 files changed, 315 insertions(+), 62 deletions(-) diff --git a/crates/agent_ui/src/thread_import.rs b/crates/agent_ui/src/thread_import.rs index 5402b1c74353b73a522a068aa32dfd0a9dc85c60..41a23f894d8f406cbdbdcb03db3879437a45e40f 100644 --- a/crates/agent_ui/src/thread_import.rs +++ b/crates/agent_ui/src/thread_import.rs @@ -12,6 +12,7 @@ use gpui::{ }; use notifications::status_toast::{StatusToast, ToastIcon}; use project::{AgentId, AgentRegistryStore, AgentServerStore}; +use remote::RemoteConnectionOptions; use ui::{ Checkbox, KeyBinding, ListItem, ListItemSpacing, Modal, ModalFooter, ModalHeader, Section, prelude::*, @@ -436,19 +437,28 @@ fn find_threads_to_import( let mut wait_for_connection_tasks = Vec::new(); for store in stores { + let remote_connection = store + .read(cx) + .project() + .read(cx) + .remote_connection_options(cx); + for agent_id in agent_ids.clone() { let agent = Agent::from(agent_id.clone()); let server = agent.server(::global(cx), ThreadStore::global(cx)); let entry = store.update(cx, |store, cx| store.request_connection(agent, server, cx)); - wait_for_connection_tasks - .push(entry.read(cx).wait_for_connection().map(|s| (agent_id, s))); + + wait_for_connection_tasks.push(entry.read(cx).wait_for_connection().map({ + let remote_connection = remote_connection.clone(); + move |state| (agent_id, remote_connection, state) + })); } } let mut session_list_tasks = Vec::new(); cx.spawn(async move |cx| { let results = futures::future::join_all(wait_for_connection_tasks).await; - for (agent, result) in results { + for (agent_id, remote_connection, result) in results { let Some(state) = result.log_err() else { continue; }; @@ -457,18 +467,25 @@ fn find_threads_to_import( }; let task = cx.update(|cx| { list.list_sessions(AgentSessionListRequest::default(), cx) - .map(|r| (agent, r)) + .map({ + let remote_connection = remote_connection.clone(); + move |response| (agent_id, remote_connection, response) + }) }); session_list_tasks.push(task); } let mut sessions_by_agent = Vec::new(); let results = futures::future::join_all(session_list_tasks).await; - for (agent_id, result) in results { + for (agent_id, remote_connection, result) in results { let Some(response) = result.log_err() else { continue; }; - sessions_by_agent.push((agent_id, response.sessions)); + sessions_by_agent.push(SessionByAgent { + agent_id, + remote_connection, + sessions: response.sessions, + }); } Ok(collect_importable_threads( @@ -478,12 +495,23 @@ fn find_threads_to_import( }) } +struct SessionByAgent { + agent_id: AgentId, + remote_connection: Option, + sessions: Vec, +} + fn collect_importable_threads( - sessions_by_agent: Vec<(AgentId, Vec)>, + sessions_by_agent: Vec, mut existing_sessions: HashSet, ) -> Vec { let mut to_insert = Vec::new(); - for (agent_id, sessions) in sessions_by_agent { + for SessionByAgent { + agent_id, + remote_connection, + sessions, + } in sessions_by_agent + { for session in sessions { if !existing_sessions.insert(session.session_id.clone()) { continue; @@ -501,6 +529,7 @@ fn collect_importable_threads( created_at: session.created_at, folder_paths, main_worktree_paths: PathList::default(), + remote_connection: remote_connection.clone(), archived: true, }); } @@ -538,9 +567,10 @@ mod tests { let existing = HashSet::from_iter(vec![acp::SessionId::new("existing-1")]); let paths = PathList::new(&[Path::new("/project")]); - let sessions_by_agent = vec![( - AgentId::new("agent-a"), - vec![ + let sessions_by_agent = vec![SessionByAgent { + agent_id: AgentId::new("agent-a"), + remote_connection: None, + sessions: vec![ make_session( "existing-1", Some("Already There"), @@ -550,7 +580,7 @@ mod tests { ), make_session("new-1", Some("Brand New"), Some(paths), None, None), ], - )]; + }]; let result = collect_importable_threads(sessions_by_agent, existing); @@ -564,13 +594,14 @@ mod tests { let existing = HashSet::default(); let paths = PathList::new(&[Path::new("/project")]); - let sessions_by_agent = vec![( - AgentId::new("agent-a"), - vec![ + let sessions_by_agent = vec![SessionByAgent { + agent_id: AgentId::new("agent-a"), + remote_connection: None, + sessions: vec![ make_session("has-dirs", Some("With Dirs"), Some(paths), None, None), make_session("no-dirs", Some("No Dirs"), None, None, None), ], - )]; + }]; let result = collect_importable_threads(sessions_by_agent, existing); @@ -583,13 +614,14 @@ mod tests { let existing = HashSet::default(); let paths = PathList::new(&[Path::new("/project")]); - let sessions_by_agent = vec![( - AgentId::new("agent-a"), - vec![ + let sessions_by_agent = vec![SessionByAgent { + agent_id: AgentId::new("agent-a"), + remote_connection: None, + sessions: vec![ make_session("s1", Some("Thread 1"), Some(paths.clone()), None, None), make_session("s2", Some("Thread 2"), Some(paths), None, None), ], - )]; + }]; let result = collect_importable_threads(sessions_by_agent, existing); @@ -603,20 +635,22 @@ mod tests { let paths = PathList::new(&[Path::new("/project")]); let sessions_by_agent = vec![ - ( - AgentId::new("agent-a"), - vec![make_session( + SessionByAgent { + agent_id: AgentId::new("agent-a"), + remote_connection: None, + sessions: vec![make_session( "s1", Some("From A"), Some(paths.clone()), None, None, )], - ), - ( - AgentId::new("agent-b"), - vec![make_session("s2", Some("From B"), Some(paths), None, None)], - ), + }, + SessionByAgent { + agent_id: AgentId::new("agent-b"), + remote_connection: None, + sessions: vec![make_session("s2", Some("From B"), Some(paths), None, None)], + }, ]; let result = collect_importable_threads(sessions_by_agent, existing); @@ -640,26 +674,28 @@ mod tests { let paths = PathList::new(&[Path::new("/project")]); let sessions_by_agent = vec![ - ( - AgentId::new("agent-a"), - vec![make_session( + SessionByAgent { + agent_id: AgentId::new("agent-a"), + remote_connection: None, + sessions: vec![make_session( "shared-session", Some("From A"), Some(paths.clone()), None, None, )], - ), - ( - AgentId::new("agent-b"), - vec![make_session( + }, + SessionByAgent { + agent_id: AgentId::new("agent-b"), + remote_connection: None, + sessions: vec![make_session( "shared-session", Some("From B"), Some(paths), None, None, )], - ), + }, ]; let result = collect_importable_threads(sessions_by_agent, existing); @@ -679,13 +715,14 @@ mod tests { let existing = HashSet::from_iter(vec![acp::SessionId::new("s1"), acp::SessionId::new("s2")]); - let sessions_by_agent = vec![( - AgentId::new("agent-a"), - vec![ + let sessions_by_agent = vec![SessionByAgent { + agent_id: AgentId::new("agent-a"), + remote_connection: None, + sessions: vec![ make_session("s1", Some("T1"), Some(paths.clone()), None, None), make_session("s2", Some("T2"), Some(paths), None, None), ], - )]; + }]; let result = collect_importable_threads(sessions_by_agent, existing); assert!(result.is_empty()); diff --git a/crates/agent_ui/src/thread_metadata_store.rs b/crates/agent_ui/src/thread_metadata_store.rs index 127f746a9edd35bc3b62b489277980868faba1c8..101ea3c7369dae6dd88e8bc4499f048532d91a43 100644 --- a/crates/agent_ui/src/thread_metadata_store.rs +++ b/crates/agent_ui/src/thread_metadata_store.rs @@ -10,31 +10,37 @@ use anyhow::Context as _; use chrono::{DateTime, Utc}; use collections::{HashMap, HashSet}; use db::{ + kvp::KeyValueStore, sqlez::{ bindable::Column, domain::Domain, statement::Statement, thread_safe_connection::ThreadSafeConnection, }, sqlez_macros::sql, }; -use futures::{FutureExt as _, future::Shared}; +use fs::Fs; +use futures::{FutureExt, future::Shared}; use gpui::{AppContext as _, Entity, Global, Subscription, Task}; use project::AgentId; +use remote::RemoteConnectionOptions; use ui::{App, Context, SharedString}; use util::ResultExt as _; -use workspace::PathList; +use workspace::{PathList, SerializedWorkspaceLocation, WorkspaceDb}; use crate::DEFAULT_THREAD_TITLE; +const THREAD_REMOTE_CONNECTION_MIGRATION_KEY: &str = "thread-metadata-remote-connection-backfill"; + pub fn init(cx: &mut App) { ThreadMetadataStore::init_global(cx); - migrate_thread_metadata(cx); + let migration_task = migrate_thread_metadata(cx); + migrate_thread_remote_connections(cx, migration_task); } /// Migrate existing thread metadata from native agent thread store to the new metadata storage. /// We skip migrating threads that do not have a project. /// /// TODO: Remove this after N weeks of shipping the sidebar -fn migrate_thread_metadata(cx: &mut App) { +fn migrate_thread_metadata(cx: &mut App) -> Task> { let store = ThreadMetadataStore::global(cx); let db = store.read(cx).db.clone(); @@ -60,6 +66,7 @@ fn migrate_thread_metadata(cx: &mut App) { created_at: entry.created_at, folder_paths: entry.folder_paths, main_worktree_paths: PathList::default(), + remote_connection: None, archived: true, }) }) @@ -104,6 +111,84 @@ fn migrate_thread_metadata(cx: &mut App) { let _ = store.update(cx, |store, cx| store.reload(cx)); anyhow::Ok(()) }) +} + +fn migrate_thread_remote_connections(cx: &mut App, migration_task: Task>) { + let store = ThreadMetadataStore::global(cx); + let db = store.read(cx).db.clone(); + let kvp = KeyValueStore::global(cx); + let workspace_db = WorkspaceDb::global(cx); + let fs = ::global(cx); + + cx.spawn(async move |cx| -> anyhow::Result<()> { + migration_task.await?; + + if kvp + .read_kvp(THREAD_REMOTE_CONNECTION_MIGRATION_KEY)? + .is_some() + { + return Ok(()); + } + + let recent_workspaces = workspace_db.recent_workspaces_on_disk(fs.as_ref()).await?; + + let mut local_path_lists = HashSet::::default(); + let mut remote_path_lists = HashMap::::default(); + + recent_workspaces + .iter() + .filter(|(_, location, path_list, _)| { + !path_list.is_empty() && matches!(location, &SerializedWorkspaceLocation::Local) + }) + .for_each(|(_, _, path_list, _)| { + local_path_lists.insert(path_list.clone()); + }); + + for (_, location, path_list, _) in recent_workspaces { + match location { + SerializedWorkspaceLocation::Remote(remote_connection) + if !local_path_lists.contains(&path_list) => + { + remote_path_lists + .entry(path_list) + .or_insert(remote_connection); + } + _ => {} + } + } + + let mut reloaded = false; + for metadata in db.list()? { + if metadata.remote_connection.is_some() { + continue; + } + + if let Some(remote_connection) = remote_path_lists + .get(&metadata.folder_paths) + .or_else(|| remote_path_lists.get(&metadata.main_worktree_paths)) + { + db.save(ThreadMetadata { + remote_connection: Some(remote_connection.clone()), + ..metadata + }) + .await?; + reloaded = true; + } + } + + let reloaded_task = reloaded + .then_some(store.update(cx, |store, cx| store.reload(cx))) + .unwrap_or(Task::ready(()).shared()); + + kvp.write_kvp( + THREAD_REMOTE_CONNECTION_MIGRATION_KEY.to_string(), + "1".to_string(), + ) + .await?; + reloaded_task.await; + + Ok(()) + }) .detach_and_log_err(cx); } @@ -121,6 +206,7 @@ pub struct ThreadMetadata { pub created_at: Option>, pub folder_paths: PathList, pub main_worktree_paths: PathList, + pub remote_connection: Option, pub archived: bool, } @@ -715,8 +801,8 @@ impl ThreadMetadataStore { let agent_id = thread_ref.connection().agent_id(); + let project = thread_ref.project().read(cx); let folder_paths = { - let project = thread_ref.project().read(cx); let paths: Vec> = project .visible_worktrees(cx) .map(|worktree| worktree.read(cx).abs_path()) @@ -724,12 +810,9 @@ impl ThreadMetadataStore { PathList::new(&paths) }; - let main_worktree_paths = thread_ref - .project() - .read(cx) - .project_group_key(cx) - .path_list() - .clone(); + let project_group_key = project.project_group_key(cx); + let main_worktree_paths = project_group_key.path_list().clone(); + let remote_connection = project_group_key.host(); // Threads without a folder path (e.g. started in an empty // window) are archived by default so they don't get lost, @@ -747,6 +830,7 @@ impl ThreadMetadataStore { updated_at, folder_paths, main_worktree_paths, + remote_connection, archived, }; @@ -801,6 +885,7 @@ impl Domain for ThreadMetadataDb { PRIMARY KEY (session_id, archived_worktree_id) ) STRICT; ), + sql!(ALTER TABLE sidebar_threads ADD COLUMN remote_connection TEXT), ]; } @@ -817,7 +902,7 @@ impl ThreadMetadataDb { /// List all sidebar thread metadata, ordered by updated_at descending. pub fn list(&self) -> anyhow::Result> { self.select::( - "SELECT session_id, agent_id, title, updated_at, created_at, folder_paths, folder_paths_order, archived, main_worktree_paths, main_worktree_paths_order \ + "SELECT session_id, agent_id, title, updated_at, created_at, folder_paths, folder_paths_order, archived, main_worktree_paths, main_worktree_paths_order, remote_connection \ FROM sidebar_threads \ ORDER BY updated_at DESC" )?() @@ -847,11 +932,17 @@ impl ThreadMetadataDb { } else { (Some(main_serialized.paths), Some(main_serialized.order)) }; + let remote_connection = row + .remote_connection + .as_ref() + .map(serde_json::to_string) + .transpose() + .context("serialize thread metadata remote connection")?; let archived = row.archived; self.write(move |conn| { - let sql = "INSERT INTO sidebar_threads(session_id, agent_id, title, updated_at, created_at, folder_paths, folder_paths_order, archived, main_worktree_paths, main_worktree_paths_order) \ - VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10) \ + let sql = "INSERT INTO sidebar_threads(session_id, agent_id, title, updated_at, created_at, folder_paths, folder_paths_order, archived, main_worktree_paths, main_worktree_paths_order, remote_connection) \ + VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11) \ ON CONFLICT(session_id) DO UPDATE SET \ agent_id = excluded.agent_id, \ title = excluded.title, \ @@ -861,7 +952,8 @@ impl ThreadMetadataDb { folder_paths_order = excluded.folder_paths_order, \ archived = excluded.archived, \ main_worktree_paths = excluded.main_worktree_paths, \ - main_worktree_paths_order = excluded.main_worktree_paths_order"; + main_worktree_paths_order = excluded.main_worktree_paths_order, \ + remote_connection = excluded.remote_connection"; let mut stmt = Statement::prepare(conn, sql)?; let mut i = stmt.bind(&id, 1)?; i = stmt.bind(&agent_id, i)?; @@ -872,7 +964,8 @@ impl ThreadMetadataDb { i = stmt.bind(&folder_paths_order, i)?; i = stmt.bind(&archived, i)?; i = stmt.bind(&main_worktree_paths, i)?; - stmt.bind(&main_worktree_paths_order, i)?; + i = stmt.bind(&main_worktree_paths_order, i)?; + stmt.bind(&remote_connection, i)?; stmt.exec() }) .await @@ -1005,6 +1098,8 @@ impl Column for ThreadMetadata { Column::column(statement, next)?; let (main_worktree_paths_order_str, next): (Option, i32) = Column::column(statement, next)?; + let (remote_connection_json, next): (Option, i32) = + Column::column(statement, next)?; let agent_id = agent_id .map(|id| AgentId::new(id)) @@ -1035,6 +1130,12 @@ impl Column for ThreadMetadata { }) .unwrap_or_default(); + let remote_connection = remote_connection_json + .as_deref() + .map(serde_json::from_str::) + .transpose() + .context("deserialize thread metadata remote connection")?; + Ok(( ThreadMetadata { session_id: acp::SessionId::new(id), @@ -1044,6 +1145,7 @@ impl Column for ThreadMetadata { created_at, folder_paths, main_worktree_paths, + remote_connection, archived, }, next, @@ -1087,6 +1189,7 @@ mod tests { use gpui::TestAppContext; use project::FakeFs; use project::Project; + use remote::WslConnectionOptions; use std::path::Path; use std::rc::Rc; @@ -1126,19 +1229,37 @@ mod tests { created_at: Some(updated_at), folder_paths, main_worktree_paths: PathList::default(), + remote_connection: None, } } fn init_test(cx: &mut TestAppContext) { + let fs = FakeFs::new(cx.executor()); cx.update(|cx| { let settings_store = settings::SettingsStore::test(cx); cx.set_global(settings_store); + ::set_global(fs, cx); ThreadMetadataStore::init_global(cx); ThreadStore::init_global(cx); }); cx.run_until_parked(); } + fn clear_thread_metadata_remote_connection_backfill(cx: &mut TestAppContext) { + let kvp = cx.update(|cx| KeyValueStore::global(cx)); + smol::block_on(kvp.delete_kvp("thread-metadata-remote-connection-backfill".to_string())) + .unwrap(); + } + + fn run_thread_metadata_migrations(cx: &mut TestAppContext) { + clear_thread_metadata_remote_connection_backfill(cx); + cx.update(|cx| { + let migration_task = migrate_thread_metadata(cx); + migrate_thread_remote_connections(cx, migration_task); + }); + cx.run_until_parked(); + } + #[gpui::test] async fn test_store_initializes_cache_from_database(cx: &mut TestAppContext) { let first_paths = PathList::new(&[Path::new("/project-a")]); @@ -1340,6 +1461,7 @@ mod tests { created_at: Some(now - chrono::Duration::seconds(10)), folder_paths: project_a_paths.clone(), main_worktree_paths: PathList::default(), + remote_connection: None, archived: false, }; @@ -1397,8 +1519,7 @@ mod tests { cx.run_until_parked(); } - cx.update(|cx| migrate_thread_metadata(cx)); - cx.run_until_parked(); + run_thread_metadata_migrations(cx); let list = cx.update(|cx| { let store = ThreadMetadataStore::global(cx); @@ -1450,6 +1571,7 @@ mod tests { created_at: Some(existing_updated_at), folder_paths: project_paths.clone(), main_worktree_paths: PathList::default(), + remote_connection: None, archived: false, }; @@ -1478,8 +1600,7 @@ mod tests { save_task.await.unwrap(); cx.run_until_parked(); - cx.update(|cx| migrate_thread_metadata(cx)); - cx.run_until_parked(); + run_thread_metadata_migrations(cx); let list = cx.update(|cx| { let store = ThreadMetadataStore::global(cx); @@ -1490,6 +1611,82 @@ mod tests { assert_eq!(list[0].session_id.0.as_ref(), "existing-session"); } + #[gpui::test] + async fn test_migrate_thread_remote_connections_backfills_from_workspace_db( + cx: &mut TestAppContext, + ) { + init_test(cx); + + let folder_paths = PathList::new(&[Path::new("/remote-project")]); + let updated_at = Utc::now(); + let metadata = make_metadata( + "remote-session", + "Remote Thread", + updated_at, + folder_paths.clone(), + ); + + cx.update(|cx| { + let store = ThreadMetadataStore::global(cx); + store.update(cx, |store, cx| { + store.save(metadata, cx); + }); + }); + cx.run_until_parked(); + + let workspace_db = cx.update(|cx| WorkspaceDb::global(cx)); + let workspace_id = workspace_db.next_id().await.unwrap(); + let serialized_paths = folder_paths.serialize(); + let remote_connection_id = 1_i64; + workspace_db + .write(move |conn| { + let mut stmt = Statement::prepare( + conn, + "INSERT INTO remote_connections(id, kind, user, distro) VALUES (?1, ?2, ?3, ?4)", + )?; + let mut next_index = stmt.bind(&remote_connection_id, 1)?; + next_index = stmt.bind(&"wsl", next_index)?; + next_index = stmt.bind(&Some("anth".to_string()), next_index)?; + stmt.bind(&Some("Ubuntu".to_string()), next_index)?; + stmt.exec()?; + + let mut stmt = Statement::prepare( + conn, + "UPDATE workspaces SET paths = ?2, paths_order = ?3, remote_connection_id = ?4, timestamp = CURRENT_TIMESTAMP WHERE workspace_id = ?1", + )?; + let mut next_index = stmt.bind(&workspace_id, 1)?; + next_index = stmt.bind(&serialized_paths.paths, next_index)?; + next_index = stmt.bind(&serialized_paths.order, next_index)?; + stmt.bind(&Some(remote_connection_id as i32), next_index)?; + stmt.exec() + }) + .await + .unwrap(); + + clear_thread_metadata_remote_connection_backfill(cx); + cx.update(|cx| { + migrate_thread_remote_connections(cx, Task::ready(Ok(()))); + }); + cx.run_until_parked(); + + let metadata = cx.update(|cx| { + let store = ThreadMetadataStore::global(cx); + store + .read(cx) + .entry(&acp::SessionId::new("remote-session")) + .cloned() + .expect("expected migrated metadata row") + }); + + assert_eq!( + metadata.remote_connection, + Some(RemoteConnectionOptions::Wsl(WslConnectionOptions { + distro_name: "Ubuntu".to_string(), + user: Some("anth".to_string()), + })) + ); + } + #[gpui::test] async fn test_migrate_thread_metadata_archives_beyond_five_most_recent_per_project( cx: &mut TestAppContext, @@ -1538,8 +1735,7 @@ mod tests { cx.run_until_parked(); } - cx.update(|cx| migrate_thread_metadata(cx)); - cx.run_until_parked(); + run_thread_metadata_migrations(cx); let list = cx.update(|cx| { let store = ThreadMetadataStore::global(cx); diff --git a/crates/sidebar/src/sidebar_tests.rs b/crates/sidebar/src/sidebar_tests.rs index 8ced8d6f71f6d88ff24a522404417ef7db3a6a7c..6a3da0a1d07ae66b4012b87e4533ed163115f4c3 100644 --- a/crates/sidebar/src/sidebar_tests.rs +++ b/crates/sidebar/src/sidebar_tests.rs @@ -245,6 +245,7 @@ fn save_thread_metadata( folder_paths, main_worktree_paths, archived: false, + remote_connection: None, }; ThreadMetadataStore::global(cx).update(cx, |store, cx| store.save_manually(metadata, cx)); }); @@ -813,6 +814,7 @@ async fn test_visible_entries_as_strings(cx: &mut TestAppContext) { updated_at: Utc::now(), created_at: Some(Utc::now()), archived: false, + remote_connection: None, }, icon: IconName::ZedAgent, icon_from_external_svg: None, @@ -836,6 +838,7 @@ async fn test_visible_entries_as_strings(cx: &mut TestAppContext) { updated_at: Utc::now(), created_at: Some(Utc::now()), archived: false, + remote_connection: None, }, icon: IconName::ZedAgent, icon_from_external_svg: None, @@ -853,6 +856,7 @@ async fn test_visible_entries_as_strings(cx: &mut TestAppContext) { metadata: ThreadMetadata { session_id: acp::SessionId::new(Arc::from("t-3")), agent_id: AgentId::new("zed-agent"), + remote_connection: None, folder_paths: PathList::default(), main_worktree_paths: PathList::default(), title: "Error thread".into(), @@ -872,10 +876,12 @@ async fn test_visible_entries_as_strings(cx: &mut TestAppContext) { diff_stats: DiffStats::default(), }), // Thread with WaitingForConfirmation status, not active + // remote_connection: None, ListEntry::Thread(ThreadEntry { metadata: ThreadMetadata { session_id: acp::SessionId::new(Arc::from("t-4")), agent_id: AgentId::new("zed-agent"), + remote_connection: None, folder_paths: PathList::default(), main_worktree_paths: PathList::default(), title: "Waiting thread".into(), @@ -895,10 +901,12 @@ async fn test_visible_entries_as_strings(cx: &mut TestAppContext) { diff_stats: DiffStats::default(), }), // Background thread that completed (should show notification) + // remote_connection: None, ListEntry::Thread(ThreadEntry { metadata: ThreadMetadata { session_id: acp::SessionId::new(Arc::from("t-5")), agent_id: AgentId::new("zed-agent"), + remote_connection: None, folder_paths: PathList::default(), main_worktree_paths: PathList::default(), title: "Notified thread".into(), @@ -2197,6 +2205,7 @@ async fn test_focused_thread_tracks_user_intent(cx: &mut TestAppContext) { folder_paths: PathList::default(), main_worktree_paths: PathList::default(), archived: false, + remote_connection: None, }, &workspace_a, false, @@ -2253,6 +2262,7 @@ async fn test_focused_thread_tracks_user_intent(cx: &mut TestAppContext) { folder_paths: PathList::default(), main_worktree_paths: PathList::default(), archived: false, + remote_connection: None, }, &workspace_b, false, @@ -3692,6 +3702,7 @@ async fn test_activate_archived_thread_with_saved_paths_activates_matching_works folder_paths: PathList::new(&[PathBuf::from("/project-b")]), main_worktree_paths: PathList::default(), archived: false, + remote_connection: None, }, window, cx, @@ -3757,6 +3768,7 @@ async fn test_activate_archived_thread_cwd_fallback_with_matching_workspace( folder_paths: PathList::new(&[std::path::PathBuf::from("/project-b")]), main_worktree_paths: PathList::default(), archived: false, + remote_connection: None, }, window, cx, @@ -3820,6 +3832,7 @@ async fn test_activate_archived_thread_no_paths_no_cwd_uses_active_workspace( folder_paths: PathList::default(), main_worktree_paths: PathList::default(), archived: false, + remote_connection: None, }, window, cx, @@ -3875,6 +3888,7 @@ async fn test_activate_archived_thread_saved_paths_opens_new_workspace(cx: &mut folder_paths: path_list_b, main_worktree_paths: PathList::default(), archived: false, + remote_connection: None, }, window, cx, @@ -3929,6 +3943,7 @@ async fn test_activate_archived_thread_reuses_workspace_in_another_window(cx: &m folder_paths: PathList::new(&[PathBuf::from("/project-b")]), main_worktree_paths: PathList::default(), archived: false, + remote_connection: None, }, window, cx, @@ -4006,6 +4021,7 @@ async fn test_activate_archived_thread_reuses_workspace_in_another_window_with_t folder_paths: PathList::new(&[PathBuf::from("/project-b")]), main_worktree_paths: PathList::default(), archived: false, + remote_connection: None, }, window, cx, @@ -4086,6 +4102,7 @@ async fn test_activate_archived_thread_prefers_current_window_for_matching_paths folder_paths: PathList::new(&[PathBuf::from("/project-a")]), main_worktree_paths: PathList::default(), archived: false, + remote_connection: None, }, window, cx, @@ -6006,6 +6023,7 @@ async fn test_legacy_thread_with_canonical_path_opens_main_repo_workspace(cx: &m folder_paths: PathList::new(&[PathBuf::from("/project")]), main_worktree_paths: PathList::default(), archived: false, + remote_connection: None, }; ThreadMetadataStore::global(cx).update(cx, |store, cx| store.save_manually(metadata, cx)); }); @@ -6361,6 +6379,7 @@ mod property_test { folder_paths: path_list, main_worktree_paths, archived: false, + remote_connection: None, }; cx.update(|_, cx| { ThreadMetadataStore::global(cx) @@ -7094,6 +7113,7 @@ async fn test_remote_project_integration_does_not_briefly_render_as_separate_pro folder_paths: PathList::new(&[PathBuf::from("/project-wt-1")]), main_worktree_paths, archived: false, + remote_connection: None, }; ThreadMetadataStore::global(cx).update(cx, |store, cx| store.save_manually(metadata, cx)); });