Disable external agents over collab (#37377)

Conrad Irwin created

Release Notes:

- Disable UI to boot external agents in collab projects (as they don't
work)

Change summary

Cargo.toml                             |  3 ++
crates/agent_ui/src/acp/thread_view.rs |  9 ++++++
crates/agent_ui/src/agent_panel.rs     | 35 +++++++++++++++++++--------
3 files changed, 35 insertions(+), 12 deletions(-)

Detailed changes

Cargo.toml 🔗

@@ -848,6 +848,9 @@ too_many_arguments = "allow"
 # We often have large enum variants yet we rarely actually bother with splitting them up.
 large_enum_variant = "allow"
 
+# Boolean expressions can be hard to read, requiring only the minimal form gets in the way
+nonminimal_bol = "allow"
+
 [workspace.metadata.cargo-machete]
 ignored = [
     "bindgen",

crates/agent_ui/src/acp/thread_view.rs 🔗

@@ -8,7 +8,7 @@ use action_log::ActionLog;
 use agent_client_protocol::{self as acp, PromptCapabilities};
 use agent_servers::{AgentServer, AgentServerDelegate, ClaudeCode};
 use agent_settings::{AgentProfileId, AgentSettings, CompletionMode, NotifyWhenAgentWaiting};
-use agent2::{DbThreadMetadata, HistoryEntry, HistoryEntryId, HistoryStore};
+use agent2::{DbThreadMetadata, HistoryEntry, HistoryEntryId, HistoryStore, NativeAgentServer};
 use anyhow::{Context as _, Result, anyhow, bail};
 use audio::{Audio, Sound};
 use buffer_diff::BufferDiff;
@@ -418,6 +418,13 @@ impl AcpThreadView {
         window: &mut Window,
         cx: &mut Context<Self>,
     ) -> ThreadState {
+        if project.read(cx).is_via_collab()
+            && agent.clone().downcast::<NativeAgentServer>().is_none()
+        {
+            return ThreadState::LoadError(LoadError::Other(
+                "External agents are not yet supported for remote projects.".into(),
+            ));
+        }
         let root_dir = project
             .read(cx)
             .visible_worktrees(cx)

crates/agent_ui/src/agent_panel.rs 🔗

@@ -1091,6 +1091,7 @@ impl AgentPanel {
         let workspace = self.workspace.clone();
         let project = self.project.clone();
         let fs = self.fs.clone();
+        let is_via_collab = self.project.read(cx).is_via_collab();
 
         const LAST_USED_EXTERNAL_AGENT_KEY: &str = "agent_panel__last_used_external_agent";
 
@@ -1122,17 +1123,21 @@ impl AgentPanel {
                     agent
                 }
                 None => {
-                    cx.background_spawn(async move {
-                        KEY_VALUE_STORE.read_kvp(LAST_USED_EXTERNAL_AGENT_KEY)
-                    })
-                    .await
-                    .log_err()
-                    .flatten()
-                    .and_then(|value| {
-                        serde_json::from_str::<LastUsedExternalAgent>(&value).log_err()
-                    })
-                    .unwrap_or_default()
-                    .agent
+                    if is_via_collab {
+                        ExternalAgent::NativeAgent
+                    } else {
+                        cx.background_spawn(async move {
+                            KEY_VALUE_STORE.read_kvp(LAST_USED_EXTERNAL_AGENT_KEY)
+                        })
+                        .await
+                        .log_err()
+                        .flatten()
+                        .and_then(|value| {
+                            serde_json::from_str::<LastUsedExternalAgent>(&value).log_err()
+                        })
+                        .unwrap_or_default()
+                        .agent
+                    }
                 }
             };
 
@@ -2527,6 +2532,11 @@ impl AgentPanel {
             .with_handle(self.new_thread_menu_handle.clone())
             .menu({
                 let workspace = self.workspace.clone();
+                let is_via_collab = workspace
+                    .update(cx, |workspace, cx| {
+                        workspace.project().read(cx).is_via_collab()
+                    })
+                    .unwrap_or_default();
 
                 move |window, cx| {
                     telemetry::event!("New Thread Clicked");
@@ -2617,6 +2627,7 @@ impl AgentPanel {
                                     ContextMenuEntry::new("New Gemini CLI Thread")
                                         .icon(IconName::AiGemini)
                                         .icon_color(Color::Muted)
+                                        .disabled(is_via_collab)
                                         .handler({
                                             let workspace = workspace.clone();
                                             move |window, cx| {
@@ -2643,6 +2654,7 @@ impl AgentPanel {
                                 menu.item(
                                     ContextMenuEntry::new("New Claude Code Thread")
                                         .icon(IconName::AiClaude)
+                                        .disabled(is_via_collab)
                                         .icon_color(Color::Muted)
                                         .handler({
                                             let workspace = workspace.clone();
@@ -2675,6 +2687,7 @@ impl AgentPanel {
                                         ContextMenuEntry::new(format!("New {} Thread", agent_name))
                                             .icon(IconName::Terminal)
                                             .icon_color(Color::Muted)
+                                            .disabled(is_via_collab)
                                             .handler({
                                                 let workspace = workspace.clone();
                                                 let agent_name = agent_name.clone();