From 6803b9ad9aa3e1ecb406bbac988f7d8babc60e80 Mon Sep 17 00:00:00 2001 From: Bryan Helmkamp Date: Sun, 8 Mar 2026 00:39:30 -0500 Subject: [PATCH] Show compaction progress in non-verbose `arc run` output Compaction events were previously only shown with `-v`. Since compaction takes time and changes agent behavior, display a spinning progress bar on CompactionStarted and a summary line on CompactionCompleted regardless of verbose mode. Co-Authored-By: Claude Opus 4.6 (1M context) --- crates/arc-workflows/src/cli/progress.rs | 75 +++++++++++++++++++++--- 1 file changed, 67 insertions(+), 8 deletions(-) diff --git a/crates/arc-workflows/src/cli/progress.rs b/crates/arc-workflows/src/cli/progress.rs index 007d6cca5..6cd30fd54 100644 --- a/crates/arc-workflows/src/cli/progress.rs +++ b/crates/arc-workflows/src/cli/progress.rs @@ -188,6 +188,7 @@ pub struct ProgressUI { sandbox_bar: Option, setup_bar: Option, cli_ensure_bar: Option, + compaction_bar: Option, any_stage_started: bool, parallel_parent: Option, } @@ -210,6 +211,7 @@ impl ProgressUI { sandbox_bar: None, setup_bar: None, cli_ensure_bar: None, + compaction_bar: None, any_stage_started: false, parallel_parent: None, } @@ -732,20 +734,49 @@ impl ProgressUI { ), ); } + AgentEvent::CompactionStarted { .. } => { + match &self.renderer { + ProgressRenderer::Tty(tty) => { + let after = self + .active_stages + .get(stage_node_id) + .map(|s| s.tool_calls.back().map_or(&s.spinner, |e| &e.bar)); + let bar = if let Some(after_bar) = after { + tty.multi + .insert_after(after_bar, ProgressBar::new_spinner()) + } else { + tty.multi.add(ProgressBar::new_spinner()) + }; + bar.set_style(style_tool_running()); + bar.set_message("\u{27f3} compacting context\u{2026}"); + bar.enable_steady_tick(Duration::from_millis(100)); + self.compaction_bar = Some(bar); + } + ProgressRenderer::Plain => {} + } + } AgentEvent::CompactionCompleted { original_turn_count, preserved_turn_count, tracked_file_count, .. - } if self.verbose => { - let dim = Style::new().dim(); - self.insert_info_line_for_stage( - stage_node_id, - &dim.apply_to(format!( - "\u{27f3} compaction: {original_turn_count} \u{2192} {preserved_turn_count} turns, {tracked_file_count} files" - )) - .to_string(), + } => { + let msg = format!( + "\u{27f3} compaction: {original_turn_count} \u{2192} {preserved_turn_count} turns, {tracked_file_count} files" ); + match &self.renderer { + ProgressRenderer::Tty(_) => { + if let Some(bar) = self.compaction_bar.take() { + bar.set_style(style_tool_done()); + bar.finish_with_message(msg); + } else { + self.insert_info_line_for_stage(stage_node_id, &msg); + } + } + ProgressRenderer::Plain => { + eprintln!(" {msg}"); + } + } } AgentEvent::LlmRetry { model, @@ -1130,6 +1161,34 @@ mod tests { )); } + #[test] + fn compaction_sets_and_clears_bar() { + let mut ui = ProgressUI::new(true, false); + + ui.handle_event(&stage_started("s1", "Build")); + assert!(ui.compaction_bar.is_none()); + + ui.handle_event(&WorkflowRunEvent::Agent { + stage: "s1".into(), + event: AgentEvent::CompactionStarted { + estimated_tokens: 5000, + context_window_size: 8000, + }, + }); + assert!(ui.compaction_bar.is_some()); + + ui.handle_event(&WorkflowRunEvent::Agent { + stage: "s1".into(), + event: AgentEvent::CompactionCompleted { + original_turn_count: 20, + preserved_turn_count: 6, + summary_token_estimate: 500, + tracked_file_count: 3, + }, + }); + assert!(ui.compaction_bar.is_none()); + } + #[test] fn plain_mode_sets_parallel_parent() { let mut ui = ProgressUI::new(false, false);