thread_view: Add fallback error handling for connect failures (#50063)

Kunall Banerjee created

Following up from https://github.com/zed-industries/zed/pull/50061: when
connecting to an ACP adapter fails before any thread is active, errors
would not display in the Agent Panel. Falling back to
`handle_load_error` to show the error UI properly as it already handles
this.

Before you mark this PR as ready for review, make sure that you have:
- [x] Added a solid test coverage and/or screenshots from doing manual
testing
- [x] Done a self-review taking into account security and performance
aspects
- [x] Aligned any UI changes with the [UI
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)

Release Notes:

- Added fallback error handling for connect failures in the Agent Panel

Change summary

crates/agent_ui/src/acp/thread_view.rs | 64 ++++++++++++++++++++++++++++
1 file changed, 64 insertions(+)

Detailed changes

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

@@ -609,6 +609,8 @@ impl AcpServerView {
                             this.handle_load_error(err, window, cx);
                         } else if let Some(active) = this.active_thread() {
                             active.update(cx, |active, cx| active.handle_any_thread_error(err, cx));
+                        } else {
+                            this.handle_load_error(err, window, cx);
                         }
                         cx.notify();
                     })
@@ -3092,6 +3094,38 @@ pub(crate) mod tests {
         });
     }
 
+    #[gpui::test]
+    async fn test_connect_failure_transitions_to_load_error(cx: &mut TestAppContext) {
+        init_test(cx);
+
+        let (thread_view, cx) = setup_thread_view(FailingAgentServer, cx).await;
+
+        thread_view.read_with(cx, |view, cx| {
+            let title = view.title(cx);
+            assert_eq!(
+                title.as_ref(),
+                "Error Loading Codex CLI",
+                "Tab title should show the agent name with an error prefix"
+            );
+            match &view.server_state {
+                ServerState::LoadError(LoadError::Other(msg)) => {
+                    assert!(
+                        msg.contains("Invalid gzip header"),
+                        "Error callout should contain the underlying extraction error, got: {msg}"
+                    );
+                }
+                other => panic!(
+                    "Expected LoadError::Other, got: {}",
+                    match other {
+                        ServerState::Loading(_) => "Loading (stuck!)",
+                        ServerState::LoadError(_) => "LoadError (wrong variant)",
+                        ServerState::Connected(_) => "Connected",
+                    }
+                ),
+            }
+        });
+    }
+
     #[gpui::test]
     async fn test_auth_required_on_initial_connect(cx: &mut TestAppContext) {
         init_test(cx);
@@ -3602,6 +3636,36 @@ pub(crate) mod tests {
         }
     }
 
+    struct FailingAgentServer;
+
+    impl AgentServer for FailingAgentServer {
+        fn logo(&self) -> ui::IconName {
+            ui::IconName::AiOpenAi
+        }
+
+        fn name(&self) -> SharedString {
+            "Codex CLI".into()
+        }
+
+        fn connect(
+            &self,
+            _root_dir: Option<&Path>,
+            _delegate: AgentServerDelegate,
+            _cx: &mut App,
+        ) -> Task<gpui::Result<(Rc<dyn AgentConnection>, Option<task::SpawnInTerminal>)>> {
+            Task::ready(Err(anyhow!(
+                "extracting downloaded asset for \
+                 https://github.com/zed-industries/codex-acp/releases/download/v0.9.4/\
+                 codex-acp-0.9.4-aarch64-pc-windows-msvc.zip: \
+                 failed to iterate over archive: Invalid gzip header"
+            )))
+        }
+
+        fn into_any(self: Rc<Self>) -> Rc<dyn Any> {
+            self
+        }
+    }
+
     #[derive(Clone)]
     struct StubSessionList {
         sessions: Vec<AgentSessionInfo>,