fix(tui): throttle AgentProgress redraws to prevent freeze under subagent load (#3033)
When 4+ sub-agents run concurrently, each AgentProgress event triggers a full terminal redraw via received_engine_event → needs_redraw. The render loop saturates, sidebar recomputation dominates the frame budget, and terminal input events (including Ctrl+C) are starved. Limit progress-driven redraws to at most one per 100ms per agent. The status-animation timer (80ms cadence) still guarantees sidebar updates. Agent state is recorded immediately; the sidebar picks it up on the next permitted redraw. Adds last_agent_progress_redraw field to App to track throttle state.
This commit is contained in:
@@ -1445,6 +1445,9 @@ pub struct App {
|
||||
pub pending_subagent_dispatch: Option<String>,
|
||||
/// Animation anchor for status-strip active sub-agent spinner.
|
||||
pub agent_activity_started_at: Option<Instant>,
|
||||
/// Last time a sub-agent progress event triggered a redraw.
|
||||
/// Used to throttle redraws under high sub-agent concurrency (#3033).
|
||||
pub last_agent_progress_redraw: Option<Instant>,
|
||||
pub ui_theme: UiTheme,
|
||||
/// Active named theme. Drives the cell-level color remap in
|
||||
/// `tui::color_compat::ColorCompatBackend` so community presets
|
||||
@@ -2174,6 +2177,7 @@ impl App {
|
||||
last_fanout_card_index: None,
|
||||
pending_subagent_dispatch: None,
|
||||
agent_activity_started_at: None,
|
||||
last_agent_progress_redraw: None,
|
||||
ui_theme,
|
||||
theme_id,
|
||||
onboarding,
|
||||
|
||||
@@ -2318,6 +2318,24 @@ async fn run_event_loop(
|
||||
app.agent_activity_started_at = Some(Instant::now());
|
||||
}
|
||||
app.status_message = Some(format!("Sub-agent {id}: {display}"));
|
||||
// #3033: Throttle redraws from rapid AgentProgress events.
|
||||
// When 4+ sub-agents are running concurrently, each firing
|
||||
// progress events, the per-event `needs_redraw = true` saturates
|
||||
// the render loop and starves terminal input. Limit
|
||||
// progress-driven repaints to at most one per 100ms; the
|
||||
// status-animation timer (80ms cadence) provides a guaranteed
|
||||
// floor for sidebar updates. Data is still recorded immediately;
|
||||
// the sidebar picks it up on the next permitted redraw.
|
||||
let now = Instant::now();
|
||||
if let Some(last) = app.last_agent_progress_redraw {
|
||||
if now.duration_since(last) < Duration::from_millis(100) {
|
||||
received_engine_event = false;
|
||||
} else {
|
||||
app.last_agent_progress_redraw = Some(now);
|
||||
}
|
||||
} else {
|
||||
app.last_agent_progress_redraw = Some(now);
|
||||
}
|
||||
}
|
||||
EngineEvent::AgentComplete { id, result } => {
|
||||
execute_subagent_observer_hook(
|
||||
|
||||
Reference in New Issue
Block a user