use compact_str::CompactString;
use tokio::sync::mpsc;
use crate::agent::tools::background::BackgroundStore;
use crate::agent::tools::plan::PlanSwitchSender;
use crate::agent::tools::question::QuestionSender;
use crate::cli::Cli;
use crate::config::Config;
use crate::context::ContextFiles;
use crate::event::AgentEvent;
#[cfg(feature = "mcp")]
use crate::extras::mcp::McpClientManager;
use crate::permission::ask::AskSender;
use crate::permission::checker::PermCheck;
use crate::provider::{AnyAgent, AnyClient};
use crate::sandbox::Sandbox;
#[cfg(feature = "semantic")]
use crate::semantic::SemanticManager;
use crate::ui::agent_io::{persist_turn_to_db, render_agent_stream};
use crate::ui::colors::{c_agent, c_error};
use crate::ui::events::sanitize_output;
use crate::ui::renderer::Renderer;
use crate::ui::run_handlers::{AgentBuildDeps, RunCtx};
use crate::ui::slash::{CompressOutcome, handle_compress};
use crate::ui::theme;
use crate::ui::tool_display::close_tool_chamber_if_open;
#[allow(clippy::too_many_arguments)]
pub(crate) async fn handle_context_overflow(
ctx: &mut RunCtx<'_>,
prompt: CompactString,
error: CompactString,
was_reasoning: &mut bool,
is_running: &mut bool,
agent: &mut AnyAgent,
context: &mut ContextFiles,
deps: &AgentBuildDeps<'_>,
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>>>,
) -> anyhow::Result<()> {
let client = deps.client;
let permission = deps.permission;
let ask_tx = deps.ask_tx;
let question_tx = deps.question_tx;
let plan_tx = deps.plan_tx;
let bg_store = deps.bg_store;
let sandbox = deps.sandbox;
let user_tx = deps.user_tx;
#[cfg(feature = "mcp")]
let mcp_manager = deps.mcp_manager;
#[cfg(feature = "semantic")]
let semantic_manager = deps.semantic_manager;
#[cfg(feature = "lsp")]
let lsp_manager = deps.lsp_manager;
*was_reasoning = false;
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,
)?;
}
let safe = sanitize_output(&error);
ctx.renderer
.write_line(&format!("context overflow: {}", safe), c_error())?;
persist_turn_to_db(
ctx.session,
ctx.last_user_prompt,
ctx.response_buf,
ctx.tool_calls_buf,
);
if let Some(h) = agent_abort.take() {
h.abort();
}
*agent_rx = None;
*agent_interject = None;
*agent_cancel = None;
*ctx.agent_line_started = false;
ctx.response_buf.clear();
*ctx.response_start_line = None;
ctx.reasoning_buf.clear();
*ctx.reasoning_start_line = None;
ctx.renderer
.write_line("▒░ auto-compacting then retrying ░▒", theme::accent())?;
let compress_result = compress(
agent,
client,
ctx.renderer,
ctx.session,
ctx.cli,
ctx.cfg,
context,
permission,
ask_tx,
question_tx,
plan_tx,
user_tx,
bg_store,
sandbox,
#[cfg(feature = "mcp")]
mcp_manager,
#[cfg(feature = "semantic")]
semantic_manager,
#[cfg(feature = "lsp")]
lsp_manager,
)
.await;
let tools_already_ran = *ctx.tool_calls_this_run > 0;
*ctx.tool_calls_this_run = 0;
match compress_result {
Ok(CompressOutcome::Compacted) if !tools_already_ran => {
let mut history = crate::agent::runner::convert_history(ctx.session);
if let Some(last) = history.last()
&& matches!(last, rig::completion::Message::User { .. })
{
history.pop();
}
let prompt_owned = prompt.to_string();
ctx.last_user_prompt.clone_from(&prompt_owned);
let prepared_prompt = crate::agent::tools::background::prepend_pending_notifications(
&prompt_owned,
bg_store.as_ref(),
);
let runner = agent.clone().spawn_runner(
prepared_prompt,
history,
Some(interjection_queue.clone()),
);
runner.install_into(
agent_rx,
agent_abort,
agent_interject,
agent_cancel,
is_running,
);
*ctx.last_collapsed = None;
ctx.renderer
.write_line(" ↳ resumed run with compacted history", theme::dim())?;
}
Ok(CompressOutcome::Compacted) => {
ctx.renderer.write_line(
" ↳ context compacted, but the failed run already invoked tools — not auto-retrying. Re-issue your prompt manually if you want to continue.",
c_error(),
)?;
*is_running = false;
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 tool-side-effect safety",
dropped,
if dropped == 1 { "" } else { "s" }
),
c_error(),
)?;
}
}
Ok(CompressOutcome::NoOp { reason }) => {
ctx.renderer.write_line(
&format!(
"auto-compact made no progress ({reason}); leaving session as-is. Try /compress with stricter instructions, lower keep_recent_tokens, or /clear."
),
c_error(),
)?;
*is_running = false;
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 compact no-op",
dropped,
if dropped == 1 { "" } else { "s" }
),
c_error(),
)?;
}
}
Err(ce) => {
ctx.renderer.write_line(
&format!(
"auto-compact failed ({}); leaving session as-is. Try /compress manually or /clear.",
ce
),
c_error(),
)?;
*is_running = false;
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 compact failure",
dropped,
if dropped == 1 { "" } else { "s" }
),
c_error(),
)?;
}
}
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
async fn compress(
agent: &mut AnyAgent,
client: &AnyClient,
renderer: &mut Renderer,
session: &mut crate::session::Session,
cli: &Cli,
cfg: &Config,
context: &mut ContextFiles,
permission: &Option<PermCheck>,
ask_tx: &Option<AskSender>,
question_tx: &Option<QuestionSender>,
plan_tx: &Option<PlanSwitchSender>,
user_tx: &tokio::sync::mpsc::UnboundedSender<crate::event::UserEvent>,
bg_store: &Option<BackgroundStore>,
sandbox: &Sandbox,
#[cfg(feature = "mcp")] mcp_manager: Option<&McpClientManager>,
#[cfg(feature = "semantic")] semantic_manager: Option<&SemanticManager>,
#[cfg(feature = "lsp")] lsp_manager: Option<&std::sync::Arc<crate::lsp::manager::LspManager>>,
) -> anyhow::Result<CompressOutcome> {
handle_compress(
None,
false, agent,
client,
renderer,
session,
cli,
cfg,
context,
permission,
ask_tx,
question_tx,
plan_tx,
user_tx,
bg_store,
sandbox,
#[cfg(feature = "mcp")]
mcp_manager,
#[cfg(feature = "semantic")]
semantic_manager,
#[cfg(feature = "lsp")]
lsp_manager,
)
.await
}