mirror of
https://github.com/openai/codex.git
synced 2026-09-14 11:57:03 +00:00
238 lines
7.6 KiB
Rust
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}");
|
|
}
|
|
}
|
|
}
|