@@ -18,6 +18,7 @@ use project::{AgentLocation, Project};
use std::collections::HashMap;
use std::error::Error;
use std::fmt::Formatter;
+use std::process::ExitStatus;
use std::rc::Rc;
use std::{
fmt::Display,
@@ -581,6 +582,7 @@ pub enum AcpThreadEvent {
ToolAuthorizationRequired,
Stopped,
Error,
+ ServerExited(ExitStatus),
}
impl EventEmitter<AcpThreadEvent> for AcpThread {}
@@ -1229,6 +1231,10 @@ impl AcpThread {
pub fn to_markdown(&self, cx: &App) -> String {
self.entries.iter().map(|e| e.to_markdown(cx)).collect()
}
+
+ pub fn emit_server_exited(&mut self, status: ExitStatus, cx: &mut Context<Self>) {
+ cx.emit(AcpThreadEvent::ServerExited(status));
+ }
}
#[cfg(test)]
@@ -114,42 +114,42 @@ impl AgentConnection for ClaudeAgentConnection {
log::trace!("Starting session with id: {}", session_id);
- cx.background_spawn({
- let session_id = session_id.clone();
- async move {
- let mut outgoing_rx = Some(outgoing_rx);
-
- let mut child = spawn_claude(
- &command,
- ClaudeSessionMode::Start,
- session_id.clone(),
- &mcp_config_path,
- &cwd,
- )?;
-
- let pid = child.id();
- log::trace!("Spawned (pid: {})", pid);
-
- ClaudeAgentSession::handle_io(
- outgoing_rx.take().unwrap(),
- incoming_message_tx.clone(),
- child.stdin.take().unwrap(),
- child.stdout.take().unwrap(),
- )
- .await?;
-
- log::trace!("Stopped (pid: {})", pid);
-
- drop(mcp_config_path);
- anyhow::Ok(())
- }
+ let mut child = spawn_claude(
+ &command,
+ ClaudeSessionMode::Start,
+ session_id.clone(),
+ &mcp_config_path,
+ &cwd,
+ )?;
+
+ let stdin = child.stdin.take().unwrap();
+ let stdout = child.stdout.take().unwrap();
+
+ let pid = child.id();
+ log::trace!("Spawned (pid: {})", pid);
+
+ cx.background_spawn(async move {
+ let mut outgoing_rx = Some(outgoing_rx);
+
+ ClaudeAgentSession::handle_io(
+ outgoing_rx.take().unwrap(),
+ incoming_message_tx.clone(),
+ stdin,
+ stdout,
+ )
+ .await?;
+
+ log::trace!("Stopped (pid: {})", pid);
+
+ drop(mcp_config_path);
+ anyhow::Ok(())
})
.detach();
let end_turn_tx = Rc::new(RefCell::new(None));
let handler_task = cx.spawn({
let end_turn_tx = end_turn_tx.clone();
- let thread_rx = thread_rx.clone();
+ let mut thread_rx = thread_rx.clone();
async move |cx| {
while let Some(message) = incoming_message_rx.next().await {
ClaudeAgentSession::handle_message(
@@ -160,6 +160,16 @@ impl AgentConnection for ClaudeAgentConnection {
)
.await
}
+
+ if let Some(status) = child.status().await.log_err() {
+ if let Some(thread) = thread_rx.recv().await.ok() {
+ thread
+ .update(cx, |thread, cx| {
+ thread.emit_server_exited(status, cx);
+ })
+ .ok();
+ }
+ }
}
});
@@ -5,6 +5,7 @@ use audio::{Audio, Sound};
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::path::Path;
+use std::process::ExitStatus;
use std::rc::Rc;
use std::sync::Arc;
use std::time::Duration;
@@ -90,6 +91,9 @@ enum ThreadState {
Unauthenticated {
connection: Rc<dyn AgentConnection>,
},
+ ServerExited {
+ status: ExitStatus,
+ },
}
impl AcpThreadView {
@@ -229,7 +233,7 @@ impl AcpThreadView {
let connect_task = agent.connect(&root_dir, &project, cx);
let load_task = cx.spawn_in(window, async move |this, cx| {
let connection = match connect_task.await {
- Ok(thread) => thread,
+ Ok(connection) => connection,
Err(err) => {
this.update(cx, |this, cx| {
this.handle_load_error(err, cx);
@@ -240,6 +244,20 @@ impl AcpThreadView {
}
};
+ // this.update_in(cx, |_this, _window, cx| {
+ // let status = connection.exit_status(cx);
+ // cx.spawn(async move |this, cx| {
+ // let status = status.await.ok();
+ // this.update(cx, |this, cx| {
+ // this.thread_state = ThreadState::ServerExited { status };
+ // cx.notify();
+ // })
+ // .ok();
+ // })
+ // .detach();
+ // })
+ // .ok();
+
let result = match connection
.clone()
.new_thread(project.clone(), &root_dir, cx)
@@ -308,7 +326,8 @@ impl AcpThreadView {
ThreadState::Ready { thread, .. } => Some(thread),
ThreadState::Unauthenticated { .. }
| ThreadState::Loading { .. }
- | ThreadState::LoadError(..) => None,
+ | ThreadState::LoadError(..)
+ | ThreadState::ServerExited { .. } => None,
}
}
@@ -318,6 +337,7 @@ impl AcpThreadView {
ThreadState::Loading { .. } => "Loadingβ¦".into(),
ThreadState::LoadError(_) => "Failed to load".into(),
ThreadState::Unauthenticated { .. } => "Not authenticated".into(),
+ ThreadState::ServerExited { .. } => "Server exited unexpectedly".into(),
}
}
@@ -647,6 +667,9 @@ impl AcpThreadView {
cx,
);
}
+ AcpThreadEvent::ServerExited(status) => {
+ self.thread_state = ThreadState::ServerExited { status: *status };
+ }
}
cx.notify();
}
@@ -1383,7 +1406,29 @@ impl AcpThreadView {
.into_any()
}
- fn render_error_state(&self, e: &LoadError, cx: &Context<Self>) -> AnyElement {
+ fn render_server_exited(&self, status: ExitStatus, _cx: &Context<Self>) -> AnyElement {
+ v_flex()
+ .items_center()
+ .justify_center()
+ .child(self.render_error_agent_logo())
+ .child(
+ v_flex()
+ .mt_4()
+ .mb_2()
+ .gap_0p5()
+ .text_center()
+ .items_center()
+ .child(Headline::new("Server exited unexpectedly").size(HeadlineSize::Medium))
+ .child(
+ Label::new(format!("Exit status: {}", status.code().unwrap_or(-127)))
+ .size(LabelSize::Small)
+ .color(Color::Muted),
+ ),
+ )
+ .into_any_element()
+ }
+
+ fn render_load_error(&self, e: &LoadError, cx: &Context<Self>) -> AnyElement {
let mut container = v_flex()
.items_center()
.justify_center()
@@ -2494,7 +2539,13 @@ impl Render for AcpThreadView {
.flex_1()
.items_center()
.justify_center()
- .child(self.render_error_state(e, cx)),
+ .child(self.render_load_error(e, cx)),
+ ThreadState::ServerExited { status } => v_flex()
+ .p_2()
+ .flex_1()
+ .items_center()
+ .justify_center()
+ .child(self.render_server_exited(*status, cx)),
ThreadState::Ready { thread, .. } => {
let thread_clone = thread.clone();