use std::sync::Arc;
use std::time::Duration;
use tokio::time::interval;
use crate::acp::client::TurnEndEvent;
use crate::acp::supervisor::AcpSupervisor;
pub const IDLE_RECYCLE_SECS: u64 = 300;
pub const REQUIRES_ACTION_RECYCLE_SECS: u64 = 1800;
pub const PROMPT_STALE_SECS: u64 = 600;
const TICK_SECS: u64 = 30;
pub async fn run_reaper(supervisor: AcpSupervisor) {
let mut ticker = interval(Duration::from_secs(TICK_SECS));
loop {
ticker.tick().await;
let mut to_reap: Vec<(String, bool /*perm_stale*/)> = Vec::new();
for (sid, client) in supervisor.snapshot().await {
if client.is_idle_stale(IDLE_RECYCLE_SECS).await {
to_reap.push((sid, false));
} else if client.is_permission_stale(REQUIRES_ACTION_RECYCLE_SECS).await {
to_reap.push((sid, true));
} else if client.is_prompt_stale(PROMPT_STALE_SECS) {
tracing::warn!(
session_id = %sid,
"prompt active but no agent activity for {}s; force-finalizing turn",
PROMPT_STALE_SECS
);
client.mark_prompt_idle();
client.notify_turn_end(TurnEndEvent::Done {
stop_reason: "InactivityTimeout".into(),
});
}
}
for (sid, perm_stale) in to_reap {
if let Some(client) = supervisor.dispose(&sid).await {
if perm_stale {
let _ = client.cancel();
}
if let Ok(c) = Arc::try_unwrap(client) {
c.disconnect().await;
}
}
}
}
}