Refactor: improve readability in CLI modules

- project_files.rs: Fix UTF-8 safety in truncate_for_display (use char
  boundaries instead of byte slicing), add test for multi-byte chars
- task_execution.rs: Extract recoverable_error_name() helper, use shared
  calculate_retry_delay() from error_handling.rs to eliminate duplication
- ui_writer_impl.rs: Extract duration_color() helper for timing display,
  add clear_tool_state() to consolidate repeated mutex clearing patterns

Agent: carmack
This commit is contained in:
Dhanji R. Prasanna
2026-01-13 05:58:54 +05:30
parent f30f145c85
commit 5c9404e292
3 changed files with 77 additions and 78 deletions

View File

@@ -1,6 +1,6 @@
//! Task execution with retry logic for G3 CLI.
use g3_core::error_handling::{classify_error, ErrorType, RecoverableError};
use g3_core::error_handling::{calculate_retry_delay, classify_error, ErrorType, RecoverableError};
use g3_core::ui_writer::UiWriter;
use g3_core::Agent;
use tokio_util::sync::CancellationToken;
@@ -11,6 +11,19 @@ use crate::simple_output::SimpleOutput;
/// Maximum number of retry attempts for recoverable errors
const MAX_RETRIES: u32 = 3;
/// Get a human-readable name for a recoverable error type.
fn recoverable_error_name(err: &RecoverableError) -> &'static str {
match err {
RecoverableError::RateLimit => "Rate limit",
RecoverableError::ServerError => "Server error",
RecoverableError::NetworkError => "Network error",
RecoverableError::Timeout => "Timeout",
RecoverableError::ModelBusy => "Model busy",
RecoverableError::TokenLimit => "Token limit",
RecoverableError::ContextLengthExceeded => "Context length",
}
}
/// Execute a task with retry logic for recoverable errors.
pub async fn execute_task_with_retry<W: UiWriter>(
agent: &mut Agent<W>,
@@ -66,35 +79,16 @@ pub async fn execute_task_with_retry<W: UiWriter>(
if let ErrorType::Recoverable(recoverable_error) = error_type {
if attempt < MAX_RETRIES {
// Calculate retry delay with exponential backoff + jitter
let base_delay_ms = match recoverable_error {
RecoverableError::RateLimit => 5000, // Rate limits need longer waits
RecoverableError::ServerError => 2000,
RecoverableError::NetworkError => 1000,
RecoverableError::Timeout => 1000,
RecoverableError::ModelBusy => 3000,
RecoverableError::TokenLimit => 1000,
RecoverableError::ContextLengthExceeded => 1000,
};
let delay_ms = base_delay_ms * (2_u64.pow(attempt - 1));
// Add jitter (±20%)
let jitter = (delay_ms as f64 * 0.2 * (rand::random::<f64>() - 0.5)) as i64;
let delay_ms = (delay_ms as i64 + jitter).max(100) as u64;
let delay = std::time::Duration::from_millis(delay_ms);
let error_name = match recoverable_error {
RecoverableError::RateLimit => "Rate limit",
RecoverableError::ServerError => "Server error",
RecoverableError::NetworkError => "Network error",
RecoverableError::Timeout => "Timeout",
RecoverableError::ModelBusy => "Model busy",
RecoverableError::TokenLimit => "Token limit",
RecoverableError::ContextLengthExceeded => "Context length",
};
// Use shared retry delay calculation (non-autonomous mode)
let delay = calculate_retry_delay(attempt, false);
let delay_ms = delay.as_millis();
output.print(&format!(
"⚠️ {} detected (attempt {}/{}). Retrying in {:.1}s...",
error_name, attempt, MAX_RETRIES, delay_ms as f64 / 1000.0
recoverable_error_name(&recoverable_error),
attempt,
MAX_RETRIES,
delay_ms as f64 / 1000.0
));
// Wait before retrying