use std::time::Instant;
use compact_str::CompactString;
use tokio::sync::mpsc;
use crate::event::AgentEvent;
use crate::ui::agent_io::{persist_turn_to_db, render_agent_stream};
use crate::ui::avatar;
use crate::ui::colors::{c_agent, c_error};
use crate::ui::events::sanitize_output;
use crate::ui::run_handlers::RunCtx;
use crate::ui::tool_display::close_tool_chamber_if_open;
#[cfg(feature = "plugin")]
use crate::plugin::PluginManager;
#[cfg(feature = "plugin")]
use std::sync::{Arc, Mutex};
#[allow(clippy::too_many_arguments)]
pub(crate) async fn handle_error(
ctx: &mut RunCtx<'_>,
error: CompactString,
was_reasoning: &mut bool,
is_running: &mut bool,
last_token_render: &mut Option<Instant>,
agent_rx: &mut Option<mpsc::Receiver<AgentEvent>>,
agent_abort: &mut Option<tokio::task::JoinHandle<()>>,
agent_interject: &mut Option<mpsc::Sender<()>>,
agent_cancel: &mut Option<mpsc::Sender<()>>,
interjection_queue: &std::sync::Arc<std::sync::Mutex<std::collections::VecDeque<String>>>,
#[cfg(feature = "plugin")] plugin_manager: Option<&Arc<Mutex<PluginManager>>>,
) -> anyhow::Result<()> {
*was_reasoning = false;
ctx.renderer.set_avatar_state(avatar::AvatarState::Error);
#[cfg(feature = "experimental-ui-terminal-tab")]
ctx.renderer.set_last_tool_name("");
close_tool_chamber_if_open(ctx.renderer, ctx.last_tool_name, ctx.tool_chamber_open)?;
if !ctx.response_buf.is_empty() {
render_agent_stream(
ctx.response_buf,
ctx.response_start_line,
c_agent(),
ctx.renderer,
)?;
*last_token_render = None;
}
let safe = sanitize_output(&error);
ctx.renderer
.write_line(&format!("error: {}", safe), c_error())?;
persist_turn_to_db(
ctx.session,
ctx.last_user_prompt,
ctx.response_buf,
ctx.tool_calls_buf,
);
#[cfg(feature = "plugin")]
if let Some(pm) = plugin_manager {
let mut mgr = pm.lock().unwrap_or_else(|err| err.into_inner());
if let Err(dispatch_err) = mgr.dispatch(
"on-error",
&format!(
"@{{:error \"{}\"}}",
crate::plugin::escape_janet_string(&error)
),
) {
ctx.renderer.write_line(
&format!("[plugin] on-error error: {dispatch_err}"),
c_error(),
)?;
}
}
*is_running = false;
if let Some(tx) = agent_cancel.take() {
let _ = tx.try_send(());
}
if let Some(h) = agent_abort.take() {
h.abort();
}
*agent_rx = None;
*agent_interject = None;
*ctx.agent_line_started = false;
ctx.response_buf.clear();
*ctx.response_start_line = None;
ctx.reasoning_buf.clear();
*ctx.reasoning_start_line = None;
let dropped = interjection_queue.lock().unwrap().len();
interjection_queue.lock().unwrap().clear();
if dropped > 0 {
ctx.renderer.write_line(
&format!(
"{} queued message{} dropped due to error",
dropped,
if dropped == 1 { "" } else { "s" }
),
c_error(),
)?;
}
Ok(())
}