Render error state when agent binary exits unexpectedly (#35651)
This PR adds handling for the case where an agent binary exits unexpectedly after successfully establishing a connection. Release Notes: - N/A --------- Co-authored-by: Agus <agus@zed.dev>
This commit is contained in:
parent
142efbac0d
commit
bc2108cbba
5 changed files with 116 additions and 35 deletions
|
@ -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)]
|
||||
|
|
|
@ -61,11 +61,24 @@ impl AcpConnection {
|
|||
}
|
||||
});
|
||||
|
||||
let io_task = cx.background_spawn(async move {
|
||||
io_task.await?;
|
||||
drop(child);
|
||||
Ok(())
|
||||
});
|
||||
let io_task = cx.background_spawn(io_task);
|
||||
|
||||
cx.spawn({
|
||||
let sessions = sessions.clone();
|
||||
async move |cx| {
|
||||
let status = child.status().await?;
|
||||
|
||||
for session in sessions.borrow().values() {
|
||||
session
|
||||
.thread
|
||||
.update(cx, |thread, cx| thread.emit_server_exited(status, cx))
|
||||
.ok();
|
||||
}
|
||||
|
||||
anyhow::Ok(())
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
let response = connection
|
||||
.initialize(acp::InitializeRequest {
|
||||
|
|
|
@ -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 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);
|
||||
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?;
|
||||
cx.background_spawn(async move {
|
||||
let mut outgoing_rx = Some(outgoing_rx);
|
||||
|
||||
log::trace!("Stopped (pid: {})", pid);
|
||||
ClaudeAgentSession::handle_io(
|
||||
outgoing_rx.take().unwrap(),
|
||||
incoming_message_tx.clone(),
|
||||
stdin,
|
||||
stdout,
|
||||
)
|
||||
.await?;
|
||||
|
||||
drop(mcp_config_path);
|
||||
anyhow::Ok(())
|
||||
}
|
||||
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();
|
||||
|
||||
|
|
|
@ -1523,7 +1523,8 @@ impl AgentDiff {
|
|||
}
|
||||
AcpThreadEvent::Stopped
|
||||
| AcpThreadEvent::ToolAuthorizationRequired
|
||||
| AcpThreadEvent::Error => {}
|
||||
| AcpThreadEvent::Error
|
||||
| AcpThreadEvent::ServerExited(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue