Fix UTF-8 panics and inconsistent retry logic

- Fix 7 UTF-8 byte slicing panics that crash on multi-byte characters:
  - acd.rs: extract_topic_from_text() [..50] slice
  - streaming.rs: log_stream_error() [..500] slice
  - tools/acd.rs: rehydrate message truncation [..2000] slice
  - history.rs: git commit message truncation [..69] slice
  - planner.rs: commit summary/description truncation [..69] slices
  - llm.rs: requirements summary line truncation [..117] slice

- All now use chars().count() and chars().take(N).collect() for
  UTF-8 safe truncation

- Fix inconsistent retry logic in task_execution.rs:
  - Previously only retried on Timeout errors
  - Now retries on ALL recoverable errors (rate limits, network,
    server errors, model busy, token limits, context length)
  - Added error-specific base delays (rate limit: 5s, server: 2s, etc.)
  - Added exponential backoff with ±20% jitter
  - Consistent with autonomous mode retry behavior
This commit is contained in:
Dhanji R. Prasanna
2026-01-13 05:49:45 +05:30
parent 6f50d01ab6
commit f30f145c85
9 changed files with 64 additions and 35 deletions

View File

@@ -335,12 +335,13 @@ fn extract_topic_from_text(text: &str) -> String {
let first_line = text.lines().next().unwrap_or("");
let cleaned = first_line.trim();
if cleaned.len() <= 50 {
if cleaned.chars().count() <= 50 {
cleaned.to_string()
} else {
// Find a good break point
let truncated = &cleaned[..50];
// Find a good break point (UTF-8 safe)
let truncated: String = cleaned.chars().take(50).collect();
if let Some(last_space) = truncated.rfind(' ') {
// last_space is a byte index into truncated, which is safe since truncated is a new String
format!("{}...", &truncated[..last_space])
} else {
format!("{}...", truncated)

View File

@@ -200,8 +200,9 @@ pub fn log_stream_error(
.rev()
.find(|m| matches!(m.role, MessageRole::User))
{
let truncated = if last_user_msg.content.len() > 500 {
format!("{}... (truncated)", &last_user_msg.content[..500])
let truncated = if last_user_msg.content.chars().count() > 500 {
let chars: String = last_user_msg.content.chars().take(500).collect();
format!("{}... (truncated)", chars)
} else {
last_user_msg.content.clone()
};

View File

@@ -93,8 +93,9 @@ pub async fn execute_rehydrate<W: UiWriter>(
};
// Truncate very long messages for readability
let content = if msg.content.len() > 2000 {
format!("{}... [truncated, {} chars total]", &msg.content[..2000], msg.content.len())
let content = if msg.content.chars().count() > 2000 {
let chars: String = msg.content.chars().take(2000).collect();
format!("{}... [truncated, {} chars total]", chars, msg.content.chars().count())
} else {
msg.content.clone()
};