Files
codex/codex-rs/cli/src/infty/progress.rs
jif-oai f073bc5ccf V17
2025-10-14 14:03:15 +01:00

238 lines
7.6 KiB
Rust

use std::path::Path;
use codex_core::protocol::AgentMessageEvent;
use codex_core::protocol::EventMsg;
use codex_infty::AggregatedVerifierVerdict;
use codex_infty::DirectiveResponse;
use codex_infty::ProgressReporter;
use codex_infty::VerifierDecision;
use codex_infty::VerifierVerdict;
use owo_colors::OwoColorize;
use supports_color::Stream;
pub(crate) struct TerminalProgressReporter {
color_enabled: bool,
}
impl TerminalProgressReporter {
pub(crate) fn with_color(color_enabled: bool) -> Self {
Self { color_enabled }
}
fn format_decision(&self, decision: VerifierDecision) -> String {
let label = match decision {
VerifierDecision::Pass => "pass",
VerifierDecision::Fail => "fail",
};
if !self.color_enabled {
return label.to_string();
}
match decision {
VerifierDecision::Pass => format!("{}", label.green().bold()),
VerifierDecision::Fail => format!("{}", label.red().bold()),
}
}
}
impl Default for TerminalProgressReporter {
fn default() -> Self {
Self::with_color(supports_color::on(Stream::Stdout).is_some())
}
}
impl ProgressReporter for TerminalProgressReporter {
fn objective_posted(&self, objective: &str) {
let line = format!("→ objective sent to solver: {objective}");
if self.color_enabled {
println!("{}", line.cyan());
} else {
println!("{line}");
}
}
fn waiting_for_solver(&self) {
if self.color_enabled {
println!("{}", "Waiting for solver response...".dimmed());
} else {
println!("Waiting for solver response...");
}
}
fn solver_event(&self, event: &EventMsg) {
match serde_json::to_string_pretty(event) {
Ok(json) => {
tracing::debug!("[solver:event]\n{json}");
}
Err(err) => {
tracing::warn!("[solver:event] (failed to serialize: {err}) {event:?}");
}
}
}
fn role_event(&self, role: &str, event: &EventMsg) {
match serde_json::to_string_pretty(event) {
Ok(json) => {
tracing::debug!("[{role}:event]\n{json}");
}
Err(err) => {
tracing::warn!("[{role}:event] (failed to serialize: {err}) {event:?}");
}
}
}
fn solver_agent_message(&self, agent_msg: &AgentMessageEvent) {
let prefix = if self.color_enabled {
format!("{}", "[solver]".magenta().bold())
} else {
"[solver]".to_string()
};
println!("{prefix} {}", agent_msg.message);
}
fn direction_request(&self, prompt: &str) {
let line = format!("→ solver requested direction: {prompt}");
if self.color_enabled {
println!("{}", line.yellow().bold());
} else {
println!("{line}");
}
}
fn director_response(&self, directive: &DirectiveResponse) {
match directive.rationale.as_deref() {
Some(rationale) if !rationale.is_empty() => {
let line = format!(
"[director] directive: {} (rationale: {rationale})",
directive.directive
);
if self.color_enabled {
println!("{}", line.blue());
} else {
println!("{line}");
}
}
_ => {
let line = format!("[director] directive: {}", directive.directive);
if self.color_enabled {
println!("{}", line.blue());
} else {
println!("{line}");
}
}
}
}
fn verification_request(&self, claim_path: &str, notes: Option<&str>) {
let line = format!("→ solver requested verification for {claim_path}");
if self.color_enabled {
println!("{}", line.yellow().bold());
} else {
println!("{line}");
}
if let Some(notes) = notes
&& !notes.is_empty()
{
let notes_line = format!(" notes: {notes}");
if self.color_enabled {
println!("{}", notes_line.dimmed());
} else {
println!("{notes_line}");
}
}
}
fn verifier_verdict(&self, role: &str, verdict: &VerifierVerdict) {
let decision = self.format_decision(verdict.verdict);
let prefix = if self.color_enabled {
format!("{}", format!("[{role}]").magenta().bold())
} else {
format!("[{role}]")
};
println!("{prefix} verdict: {decision}");
if !verdict.reasons.is_empty() {
let reasons = verdict.reasons.join("; ");
let line = format!(" reasons: {reasons}");
if self.color_enabled {
println!("{}", line.dimmed());
} else {
println!("{line}");
}
}
if !verdict.suggestions.is_empty() {
let suggestions = verdict.suggestions.join("; ");
let line = format!(" suggestions: {suggestions}");
if self.color_enabled {
println!("{}", line.dimmed());
} else {
println!("{line}");
}
}
}
fn verification_summary(&self, summary: &AggregatedVerifierVerdict) {
println!();
let decision = self.format_decision(summary.overall);
let heading = if self.color_enabled {
format!("{}", "Verification summary".bold())
} else {
"Verification summary".to_string()
};
println!("{heading}: {decision}");
for report in &summary.verdicts {
let report_decision = self.format_decision(report.verdict);
let line = format!(" {}{report_decision}", report.role);
println!("{line}");
if !report.reasons.is_empty() {
let reasons = report.reasons.join("; ");
let reason_line = format!(" reasons: {reasons}");
if self.color_enabled {
println!("{}", reason_line.dimmed());
} else {
println!("{reason_line}");
}
}
if !report.suggestions.is_empty() {
let suggestions = report.suggestions.join("; ");
let suggestion_line = format!(" suggestions: {suggestions}");
if self.color_enabled {
println!("{}", suggestion_line.dimmed());
} else {
println!("{suggestion_line}");
}
}
}
}
fn final_delivery(&self, deliverable_path: &Path, summary: Option<&str>) {
println!();
let line = format!(
"✓ solver reported final delivery at {}",
deliverable_path.display()
);
if self.color_enabled {
println!("{}", line.green().bold());
} else {
println!("{line}");
}
if let Some(summary) = summary
&& !summary.is_empty()
{
let hint = " (final summary will be shown below)";
if self.color_enabled {
println!("{}", hint.dimmed());
} else {
println!("{hint}");
}
}
}
fn run_interrupted(&self) {
let line = "Run interrupted by Ctrl+C. Shutting down sessions…";
if self.color_enabled {
println!("{}", line.red().bold());
} else {
println!("{line}");
}
}
}