acp: Fix the spawn directory for agent servers in remote environments (#52308)

Ben Brandt created

We can't add a current_dir when in remote scenarios in the same place,
it gets added elsewhere.

Reused the same worktree selection logic, and make sure this gets passed
to the RPC call instead.

This fixes something that hasn't made it to preview yet.

Release Notes:

- N/A

Change summary

crates/agent_servers/src/acp.rs          | 14 +++++++----
crates/project/src/agent_server_store.rs | 31 ++++++++++++++++++++++---
crates/project/src/project.rs            | 23 +++++++++++++++----
3 files changed, 54 insertions(+), 14 deletions(-)

Detailed changes

crates/agent_servers/src/acp.rs 🔗

@@ -203,11 +203,15 @@ impl AcpConnection {
             builder.build_std_command(Some(command.path.display().to_string()), &command.args);
         child.envs(command.env.iter().flatten());
         if let Some(cwd) = project.update(cx, |project, cx| {
-            project
-                .default_path_list(cx)
-                .ordered_paths()
-                .next()
-                .cloned()
+            if project.is_local() {
+                project
+                    .default_path_list(cx)
+                    .ordered_paths()
+                    .next()
+                    .cloned()
+            } else {
+                None
+            }
         }) {
             child.current_dir(cwd);
         }

crates/project/src/agent_server_store.rs 🔗

@@ -27,6 +27,8 @@ use util::{ResultExt as _, debug_panic};
 use crate::ProjectEnvironment;
 use crate::agent_registry_store::{AgentRegistryStore, RegistryAgent, RegistryTargetConfig};
 
+use crate::worktree_store::WorktreeStore;
+
 #[derive(Deserialize, Serialize, Clone, PartialEq, Eq, JsonSchema)]
 pub struct AgentServerCommand {
     #[serde(rename = "command")]
@@ -149,6 +151,7 @@ enum AgentServerStoreState {
     Remote {
         project_id: u64,
         upstream_client: Entity<RemoteClient>,
+        worktree_store: Entity<WorktreeStore>,
     },
     Collab,
 }
@@ -231,6 +234,7 @@ impl AgentServerStore {
             AgentServerStoreState::Remote {
                 project_id,
                 upstream_client,
+                worktree_store,
             } => {
                 let mut agents = vec![];
                 for (ext_id, manifest) in manifests {
@@ -256,6 +260,7 @@ impl AgentServerStore {
                                     Box::new(RemoteExternalAgentServer {
                                         project_id: *project_id,
                                         upstream_client: upstream_client.clone(),
+                                        worktree_store: worktree_store.clone(),
                                         name: agent_server_name.clone(),
                                         new_version_available_tx: None,
                                     })
@@ -612,11 +617,16 @@ impl AgentServerStore {
         this
     }
 
-    pub(crate) fn remote(project_id: u64, upstream_client: Entity<RemoteClient>) -> Self {
+    pub(crate) fn remote(
+        project_id: u64,
+        upstream_client: Entity<RemoteClient>,
+        worktree_store: Entity<WorktreeStore>,
+    ) -> Self {
         Self {
             state: AgentServerStoreState::Remote {
                 project_id,
                 upstream_client,
+                worktree_store,
             },
             external_agents: HashMap::default(),
         }
@@ -751,8 +761,10 @@ impl AgentServerStore {
                 .env
                 .map(|env| env.into_iter().collect())
                 .unwrap_or_default(),
-            // root_dir and login are no longer used, but returned for backwards compatibility
-            root_dir: paths::home_dir().to_string_lossy().to_string(),
+            root_dir: envelope
+                .payload
+                .root_dir
+                .unwrap_or_else(|| paths::home_dir().to_string_lossy().to_string()),
             login: None,
         })
     }
@@ -766,6 +778,7 @@ impl AgentServerStore {
             let AgentServerStoreState::Remote {
                 project_id,
                 upstream_client,
+                worktree_store,
             } = &this.state
             else {
                 debug_panic!(
@@ -810,6 +823,7 @@ impl AgentServerStore {
                     let agent = RemoteExternalAgentServer {
                         project_id: *project_id,
                         upstream_client: upstream_client.clone(),
+                        worktree_store: worktree_store.clone(),
                         name: agent_id.clone(),
                         new_version_available_tx: new_version_available_txs
                             .remove(&agent_id)
@@ -906,6 +920,7 @@ impl AgentServerStore {
 struct RemoteExternalAgentServer {
     project_id: u64,
     upstream_client: Entity<RemoteClient>,
+    worktree_store: Entity<WorktreeStore>,
     name: AgentId,
     new_version_available_tx: Option<watch::Sender<Option<String>>>,
 }
@@ -920,8 +935,16 @@ impl ExternalAgentServer for RemoteExternalAgentServer {
         let project_id = self.project_id;
         let name = self.name.to_string();
         let upstream_client = self.upstream_client.downgrade();
+        let worktree_store = self.worktree_store.clone();
         self.new_version_available_tx = new_version_available_tx;
         cx.spawn(async move |cx| {
+            let root_dir = worktree_store.read_with(cx, |worktree_store, cx| {
+                crate::Project::default_visible_worktree_paths(worktree_store, cx)
+                    .into_iter()
+                    .next()
+                    .map(|path| path.display().to_string())
+            });
+
             let mut response = upstream_client
                 .update(cx, |upstream_client, _| {
                     upstream_client
@@ -929,7 +952,7 @@ impl ExternalAgentServer for RemoteExternalAgentServer {
                         .request(proto::GetAgentServerCommand {
                             project_id,
                             name,
-                            root_dir: None,
+                            root_dir,
                         })
                 })?
                 .await?;

crates/project/src/project.rs 🔗

@@ -1500,8 +1500,13 @@ impl Project {
                 )
             });
 
-            let agent_server_store =
-                cx.new(|_| AgentServerStore::remote(REMOTE_SERVER_PROJECT_ID, remote.clone()));
+            let agent_server_store = cx.new(|_| {
+                AgentServerStore::remote(
+                    REMOTE_SERVER_PROJECT_ID,
+                    remote.clone(),
+                    worktree_store.clone(),
+                )
+            });
 
             cx.subscribe(&remote, Self::on_remote_client_event).detach();
 
@@ -2294,8 +2299,11 @@ impl Project {
         self.worktree_store.read(cx).visible_worktrees(cx)
     }
 
-    pub fn default_path_list(&self, cx: &App) -> PathList {
-        let worktree_roots = self
+    pub(crate) fn default_visible_worktree_paths(
+        worktree_store: &WorktreeStore,
+        cx: &App,
+    ) -> Vec<PathBuf> {
+        worktree_store
             .visible_worktrees(cx)
             .sorted_by(|left, right| {
                 left.read(cx)
@@ -2311,7 +2319,12 @@ impl Project {
                     Some(path.to_path_buf())
                 }
             })
-            .collect::<Vec<_>>();
+            .collect()
+    }
+
+    pub fn default_path_list(&self, cx: &App) -> PathList {
+        let worktree_roots =
+            Self::default_visible_worktree_paths(&self.worktree_store.read(cx), cx);
 
         if worktree_roots.is_empty() {
             PathList::new(&[paths::home_dir().as_path()])