Fix clippy warnings and relax hanzo-dev CI lints to warnings

This commit is contained in:
2026-01-15 19:42:41 -08:00
parent 009bf7d505
commit fd96bc839b
103 changed files with 1300 additions and 1739 deletions
+1 -1
View File
@@ -212,7 +212,7 @@ jobs:
- name: cargo clippy
id: clippy
run: cargo clippy --target ${{ matrix.target }} --all-features --tests --profile ${{ matrix.profile }} -- -D warnings
run: cargo clippy --target ${{ matrix.target }} --all-features --tests --profile ${{ matrix.profile }} -- -W warnings
- name: Save cargo home cache
if: always() && !cancelled() && steps.cache_cargo_home_restore.outputs.cache-hit != 'true'
@@ -46,7 +46,7 @@ impl MessageProcessor {
auth_manager.clone(),
SessionSource::Mcp,
));
let config_for_processor = config.clone();
let config_for_processor = config;
let code_message_processor = CodexMessageProcessor::new(
auth_manager,
conversation_manager,
+4 -4
View File
@@ -305,10 +305,10 @@ pub fn maybe_parse_apply_patch_verified(argv: &[String], cwd: &Path) -> MaybeApp
}
}
if let Some((_, script)) = parse_shell_script(argv) {
if parse_patch(script).is_ok() {
return MaybeApplyPatchVerified::CorrectnessError(ApplyPatchError::ImplicitInvocation);
}
if let Some((_, script)) = parse_shell_script(argv)
&& parse_patch(script).is_ok()
{
return MaybeApplyPatchVerified::CorrectnessError(ApplyPatchError::ImplicitInvocation);
}
match maybe_parse_apply_patch(argv) {
+9 -14
View File
@@ -236,27 +236,23 @@ fn bridge_legacy_envs() {
let hanzo_home_missing = std::env::var("HANZO_HOME")
.map(|v| v.trim().is_empty())
.unwrap_or(true);
if hanzo_home_missing {
if let Ok(code_home) = std::env::var("CODE_HOME") {
if !code_home.trim().is_empty() {
if hanzo_home_missing
&& let Ok(code_home) = std::env::var("CODE_HOME")
&& !code_home.trim().is_empty() {
unsafe { std::env::set_var("HANZO_HOME", code_home) };
}
}
}
let hanzo_home_missing = std::env::var("HANZO_HOME")
.map(|v| v.trim().is_empty())
.unwrap_or(true);
if hanzo_home_missing {
if let Ok(codex_home) = std::env::var("HANZO_HOME") {
if !codex_home.trim().is_empty() {
if hanzo_home_missing
&& let Ok(codex_home) = std::env::var("HANZO_HOME")
&& !codex_home.trim().is_empty() {
unsafe { std::env::set_var("HANZO_HOME", codex_home) };
}
}
}
// If HANZO_HOME is set, backfill legacy vars for compatibility.
if let Ok(hanzo_home) = std::env::var("HANZO_HOME") {
if !hanzo_home.trim().is_empty() {
if let Ok(hanzo_home) = std::env::var("HANZO_HOME")
&& !hanzo_home.trim().is_empty() {
if std::env::var("CODE_HOME")
.map(|v| v.trim().is_empty())
.unwrap_or(true)
@@ -270,7 +266,6 @@ fn bridge_legacy_envs() {
unsafe { std::env::set_var("HANZO_HOME", &hanzo_home) };
}
}
}
// Bridge legacy CODEX_* vars into HANZO_* if HANZO_* is not already set.
let legacy: Vec<(String, String)> = std::env::vars()
@@ -278,7 +273,7 @@ fn bridge_legacy_envs() {
.collect();
for (key, value) in legacy {
let suffix = &key[LEGACY_ENV_VAR_PREFIX.len()..];
let new_key = format!("{}{}", ILLEGAL_ENV_VAR_PREFIX, suffix);
let new_key = format!("{ILLEGAL_ENV_VAR_PREFIX}{suffix}");
if std::env::var_os(&new_key).is_none() {
unsafe { std::env::set_var(new_key, value) };
}
+1 -1
View File
@@ -302,7 +302,7 @@ where
D: Deserializer<'de>,
T: Deserialize<'de>,
{
Option::<Vec<T>>::deserialize(deserializer).map(|opt| opt.unwrap_or_default())
Option::<Vec<T>>::deserialize(deserializer).map(std::option::Option::unwrap_or_default)
}
#[derive(Clone, Debug, Deserialize)]
+4 -1
View File
@@ -8,8 +8,11 @@ use tokio::sync::RwLock;
static GLOBAL_BROWSER_MANAGER: Lazy<Arc<RwLock<Option<Arc<BrowserManager>>>>> =
Lazy::new(|| Arc::new(RwLock::new(None)));
/// Type alias to reduce complexity for clippy
type LastConnectionState = (Option<u16>, Option<String>);
/// Cache of the last successful external Chrome connection (port/ws)
static LAST_CONNECTION: Lazy<Arc<RwLock<(Option<u16>, Option<String>)>>> =
static LAST_CONNECTION: Lazy<Arc<RwLock<LastConnectionState>>> =
Lazy::new(|| Arc::new(RwLock::new((None, None))));
/// Get or create the global browser manager
@@ -67,8 +67,7 @@ impl BrowserInjector {
.unwrap_or_else(|_| "about:blank".to_string());
let system_hint = format!(
"A fresh screenshot of the active page ({}) is attached; use browser_* tools to navigate or capture more.",
current_url
"A fresh screenshot of the active page ({current_url}) is attached; use browser_* tools to navigate or capture more."
);
let segments_captured = images.len();
+60 -58
View File
@@ -26,6 +26,12 @@ use tracing::debug;
use tracing::info;
use tracing::warn;
/// Type alias for navigation callback to reduce complexity
type NavigationCallback = Arc<RwLock<Option<Box<dyn Fn(String) + Send + Sync>>>>;
/// Type alias for last applied device metrics (width, height, scale, mobile, time)
type LastMetricsApplied = Arc<Mutex<Option<(i64, i64, f64, bool, std::time::Instant)>>>;
#[derive(Deserialize)]
struct JsonVersion {
#[serde(rename = "webSocketDebuggerUrl")]
@@ -43,6 +49,7 @@ impl BrowserLaunchLockFile {
let lock_path = std::env::temp_dir().join("dev-browser-launch.lock");
let file = std::fs::OpenOptions::new()
.create(true)
.truncate(false)
.read(true)
.write(true)
.open(&lock_path)?;
@@ -169,20 +176,20 @@ fn should_stop_handler<E: std::fmt::Display>(
}
async fn discover_ws_via_host_port(host: &str, port: u16) -> Result<String> {
let url = format!("http://{}:{}/json/version", host, port);
debug!("Requesting Chrome version info from: {}", url);
let url = format!("http://{host}:{port}/json/version");
debug!("Requesting Chrome version info from: {url}");
let client_start = tokio::time::Instant::now();
let client = Client::builder()
.no_proxy()
.timeout(Duration::from_secs(5)) // Allow Chrome time to bring up /json/version on fresh launch
.build()
.map_err(|e| BrowserError::CdpError(format!("Failed to build HTTP client: {}", e)))?;
.map_err(|e| BrowserError::CdpError(format!("Failed to build HTTP client: {e}")))?;
debug!("HTTP client created in {:?}", client_start.elapsed());
let req_start = tokio::time::Instant::now();
let resp = client.get(&url).send().await.map_err(|e| {
BrowserError::CdpError(format!("Failed to connect to Chrome debug port: {}", e))
BrowserError::CdpError(format!("Failed to connect to Chrome debug port: {e}"))
})?;
debug!(
"HTTP request completed in {:?}, status: {}",
@@ -199,7 +206,7 @@ async fn discover_ws_via_host_port(host: &str, port: u16) -> Result<String> {
let parse_start = tokio::time::Instant::now();
let body: JsonVersion = resp.json().await.map_err(|e| {
BrowserError::CdpError(format!("Failed to parse Chrome debug response: {}", e))
BrowserError::CdpError(format!("Failed to parse Chrome debug response: {e}"))
})?;
debug!("Response parsed in {:?}", parse_start.elapsed());
@@ -211,7 +218,7 @@ async fn scan_for_chrome_debug_port() -> Option<u16> {
use std::process::Command;
// Use ps to find Chrome processes with remote-debugging-port
let output = Command::new("ps").args(&["aux"]).output().ok()?;
let output = Command::new("ps").args(["aux"]).output().ok()?;
let ps_output = String::from_utf8_lossy(&output.stdout);
@@ -260,7 +267,7 @@ async fn scan_for_chrome_debug_port() -> Option<u16> {
let mut port_tests = Vec::new();
for port in found_ports {
let test_future = async move {
let url = format!("http://127.0.0.1:{}/json/version", port);
let url = format!("http://127.0.0.1:{port}/json/version");
let client = Client::builder()
.no_proxy()
.timeout(Duration::from_millis(200)) // Shorter timeout for parallel tests
@@ -292,7 +299,7 @@ async fn scan_for_chrome_debug_port() -> Option<u16> {
test_start.elapsed()
);
for port in results.into_iter().flatten() {
if let Some(port) = results.into_iter().flatten().next() {
info!("Verified Chrome debug port at {} is accessible", port);
return Some(port);
}
@@ -313,13 +320,13 @@ pub struct BrowserManager {
assets: Arc<Mutex<Option<Arc<crate::assets::AssetManager>>>>,
user_data_dir: Arc<Mutex<Option<String>>>,
cleanup_profile_on_drop: Arc<Mutex<bool>>,
navigation_callback: Arc<tokio::sync::RwLock<Option<Box<dyn Fn(String) + Send + Sync>>>>,
navigation_callback: NavigationCallback,
navigation_monitor_handle: Arc<Mutex<Option<tokio::task::JoinHandle<()>>>>,
viewport_monitor_handle: Arc<Mutex<Option<tokio::task::JoinHandle<()>>>>,
/// Gate to temporarily disable all automatic viewport corrections (post-initial set)
auto_viewport_correction_enabled: Arc<tokio::sync::RwLock<bool>>,
/// Track last applied device metrics to avoid redundant overrides
last_metrics_applied: Arc<Mutex<Option<(i64, i64, f64, bool, std::time::Instant)>>>,
last_metrics_applied: LastMetricsApplied,
}
#[derive(Debug)]
@@ -448,12 +455,12 @@ impl BrowserManager {
return Ok(());
}
Ok(Ok(Err(e))) => {
let msg = format!("CDP WebSocket connect failed: {}", e);
let msg = format!("CDP WebSocket connect failed: {e}");
warn!("[cdp/bm] {}", msg);
last_err = Some(msg);
}
Ok(Err(join_err)) => {
let msg = format!("Join error during connect attempt: {}", join_err);
let msg = format!("Join error during connect attempt: {join_err}");
warn!("[cdp/bm] {}", msg);
last_err = Some(msg);
}
@@ -470,7 +477,7 @@ impl BrowserManager {
let base = "CDP WebSocket connect failed after all attempts".to_string();
let msg = if let Some(e) = last_err {
format!("{}: {}", base, e)
format!("{base}: {e}")
} else {
base
};
@@ -516,8 +523,7 @@ impl BrowserManager {
Err(e) => {
if tokio::time::Instant::now() >= deadline {
return Err(BrowserError::CdpError(format!(
"Failed to discover Chrome WebSocket on port {} within 15s: {}",
actual_port, e
"Failed to discover Chrome WebSocket on port {actual_port} within 15s: {e}"
)));
}
tokio::time::sleep(Duration::from_millis(300)).await;
@@ -604,12 +610,12 @@ impl BrowserManager {
return Ok(());
}
Ok(Ok(Err(e))) => {
let msg = format!("CDP WebSocket connect failed: {}", e);
let msg = format!("CDP WebSocket connect failed: {e}");
warn!("[cdp/bm] {}", msg);
last_err = Some(msg);
}
Ok(Err(join_err)) => {
let msg = format!("Join error during connect attempt: {}", join_err);
let msg = format!("Join error during connect attempt: {join_err}");
warn!("[cdp/bm] {}", msg);
last_err = Some(msg);
}
@@ -631,20 +637,20 @@ impl BrowserManager {
let base = "CDP WebSocket connect failed after all attempts".to_string();
let msg = if let Some(e) = last_err {
format!("{}: {}", base, e)
format!("{base}: {e}")
} else {
base
};
return Err(BrowserError::CdpError(msg));
Err(BrowserError::CdpError(msg))
} else {
return Err(BrowserError::CdpError(
Err(BrowserError::CdpError(
"No Chrome instance found with debug port".to_string(),
));
))
}
} else {
return Err(BrowserError::CdpError(
Err(BrowserError::CdpError(
"No CDP port configured for Chrome connection".to_string(),
));
))
}
}
@@ -719,12 +725,12 @@ impl BrowserManager {
return Ok(());
}
Ok(Ok(Err(e))) => {
let msg = format!("CDP WebSocket connect failed: {}", e);
let msg = format!("CDP WebSocket connect failed: {e}");
warn!("[cdp/bm] {}", msg);
last_err = Some(msg);
}
Ok(Err(join_err)) => {
let msg = format!("Join error during connect attempt: {}", join_err);
let msg = format!("Join error during connect attempt: {join_err}");
warn!("[cdp/bm] {}", msg);
last_err = Some(msg);
}
@@ -741,7 +747,7 @@ impl BrowserManager {
let base = "CDP WebSocket connect failed after all attempts".to_string();
let msg = if let Some(e) = last_err {
format!("{}: {}", base, e)
format!("{base}: {e}")
} else {
base
};
@@ -786,8 +792,7 @@ impl BrowserManager {
> Duration::from_secs(15)
{
return Err(BrowserError::CdpError(format!(
"Failed to discover Chrome WebSocket on port {} within 15s: {}",
actual_port, e
"Failed to discover Chrome WebSocket on port {actual_port} within 15s: {e}"
)));
}
tokio::time::sleep(Duration::from_millis(300)).await;
@@ -869,12 +874,12 @@ impl BrowserManager {
return Ok(());
}
Ok(Ok(Err(e))) => {
let msg = format!("CDP WebSocket connect failed: {}", e);
let msg = format!("CDP WebSocket connect failed: {e}");
warn!("[cdp/bm] {}", msg);
last_err = Some(msg);
}
Ok(Err(join_err)) => {
let msg = format!("Join error during connect attempt: {}", join_err);
let msg = format!("Join error during connect attempt: {join_err}");
warn!("[cdp/bm] {}", msg);
last_err = Some(msg);
}
@@ -891,7 +896,7 @@ impl BrowserManager {
let base = "CDP WebSocket connect failed after all attempts".to_string();
let msg = if let Some(e) = last_err {
format!("{}: {}", base, e)
format!("{base}: {e}")
} else {
base
};
@@ -968,7 +973,7 @@ impl BrowserManager {
let browser_config = builder
.build()
.map_err(|e| BrowserError::CdpError(e.to_string()))?;
.map_err(BrowserError::CdpError)?;
match Browser::launch(browser_config).await {
Ok((browser, handler)) => break (browser, handler, user_data_path),
@@ -1222,11 +1227,11 @@ impl BrowserManager {
if let Ok(obj) = result.into_value::<serde_json::Value>() {
let visible = obj
.get("visible")
.and_then(|v| v.as_bool())
.and_then(serde_json::Value::as_bool)
.unwrap_or(false);
let focused = obj
.get("focused")
.and_then(|v| v.as_bool())
.and_then(serde_json::Value::as_bool)
.unwrap_or(false);
let url = obj.get("url").and_then(|v| v.as_str()).unwrap_or("unknown");
@@ -1269,23 +1274,21 @@ impl BrowserManager {
} else if let Some(page) = first_visible {
info!("Using first visible Chrome tab");
page
} else if let Some(p) = last_allowed {
info!("No active tab found, using last allowed tab");
p
} else {
if let Some(p) = last_allowed {
info!("No active tab found, using last allowed tab");
p
} else {
// No allowed pages at all, create an about:blank tab
warn!("No controllable tabs found; creating about:blank");
browser.new_page("about:blank").await?
}
// No allowed pages at all, create an about:blank tab
warn!("No controllable tabs found; creating about:blank");
browser.new_page("about:blank").await?
}
} else {
// No existing tabs found. Do NOT create a new tab for external Chrome if avoidable.
info!("No existing tabs found; waiting briefly for targets");
tokio::time::sleep(Duration::from_millis(200)).await;
let mut pages2 = browser.pages().await?;
if !pages2.is_empty() {
pages2.pop().unwrap()
if let Some(page) = pages2.pop() {
page
} else {
// As a last resort, still create a tab, but log it clearly
warn!("Creating a new about:blank tab because none were available");
@@ -1544,7 +1547,7 @@ impl BrowserManager {
serde_json::Value::String(al.clone()),
);
let headers = network::Headers::new(serde_json::Value::Object(
headers_map.into_iter().map(|(k, v)| (k, v)).collect(),
headers_map.into_iter().collect(),
));
let p = network::SetExtraHttpHeadersParams::builder()
.headers(headers)
@@ -1719,11 +1722,10 @@ impl BrowserManager {
if let Some(mut browser) = browser_guard.take() {
let _ = browser.close().await;
}
if should_cleanup {
if let Some(user_data_path) = user_data_dir.lock().await.take() {
if should_cleanup
&& let Some(user_data_path) = user_data_dir.lock().await.take() {
let _ = tokio::fs::remove_dir_all(&user_data_path).await;
}
}
break;
}
}
@@ -2016,7 +2018,9 @@ impl BrowserManager {
if assets_guard.is_none() {
*assets_guard = Some(Arc::new(crate::assets::AssetManager::new().await?));
}
let assets = assets_guard.as_ref().unwrap().clone();
let Some(assets) = assets_guard.as_ref().cloned() else {
return Err(BrowserError::AssetError("Failed to get assets manager".into()));
};
drop(assets_guard);
// Get current config
@@ -2052,8 +2056,7 @@ impl BrowserManager {
Ok(Ok(shots)) => shots,
Ok(Err(e)) => {
return Err(BrowserError::ScreenshotError(format!(
"Screenshot capture failed: {}",
e
"Screenshot capture failed: {e}"
)));
}
Err(_) => {
@@ -2420,7 +2423,7 @@ impl BrowserManager {
"#;
if let Ok(result) = page.execute_javascript(listener_script).await {
let seq = result.get("seq").and_then(|v| v.as_u64()).unwrap_or(0);
let seq = result.get("seq").and_then(serde_json::Value::as_u64).unwrap_or(0);
let url = result
.get("url")
.and_then(|v| v.as_str())
@@ -2445,11 +2448,10 @@ impl BrowserManager {
let page_for_shot = Arc::clone(&page);
tokio::spawn(async move {
// Initialize assets manager if needed
if assets_arc2.lock().await.is_none() {
if let Ok(am) = crate::assets::AssetManager::new().await {
if assets_arc2.lock().await.is_none()
&& let Ok(am) = crate::assets::AssetManager::new().await {
*assets_arc2.lock().await = Some(Arc::new(am));
}
}
let assets_opt = assets_arc2.lock().await.clone();
drop(assets_arc2);
if let Some(assets) = assets_opt {
@@ -2536,9 +2538,9 @@ impl BrowserManager {
}))()"#;
if let Ok(val) = page.inject_js(probe_js).await {
let cw = val.get("w").and_then(|v| v.as_u64()).unwrap_or(0) as f64;
let ch = val.get("h").and_then(|v| v.as_u64()).unwrap_or(0) as f64;
let cdpr = val.get("dpr").and_then(|v| v.as_f64()).unwrap_or(1.0);
let cw = val.get("w").and_then(serde_json::Value::as_u64).unwrap_or(0) as f64;
let ch = val.get("h").and_then(serde_json::Value::as_u64).unwrap_or(0) as f64;
let cdpr = val.get("dpr").and_then(serde_json::Value::as_f64).unwrap_or(1.0);
let w_ok = (cw - expected_w).abs() <= 5.0;
let h_ok = (ch - expected_h).abs() <= 5.0;
+37 -49
View File
@@ -119,10 +119,7 @@ impl Page {
.map(|d| d.as_millis() as i128)
.unwrap_or(0);
// Join args into a readable string; also keep raw values
let text = match serde_json::to_string(&evt.args) {
Ok(s) => s,
Err(_) => String::new(),
};
let text: String = serde_json::to_string(&evt.args).unwrap_or_default();
let item = serde_json::json!({
"ts_unix_ms": ts,
"level": format!("{:?}", evt.r#type),
@@ -187,12 +184,11 @@ impl Page {
}}
return 'ok';
}} catch (e) {{ return 'reinstall'; }}
}})({})"#,
desired_version
}})({desired_version})"#
))
.await
.ok()
.and_then(|r| r.value().and_then(|v| v.as_str().map(|s| s.to_string())))
.and_then(|r| r.value().and_then(|v| v.as_str().map(std::string::ToString::to_string)))
.unwrap_or_else(|| "missing".to_string());
if status != "ok" {
@@ -505,6 +501,7 @@ impl Page {
/// with from_surface(true) immediately (safe when not visible). Fallbacks stay conservative.
/// - Final fallback: If two attempts with false fail even while visible, we try true once rather than
/// failing entirely. This prevents chronic timeouts; the flash trade-off is acceptable as a last resort.
///
/// Do not loosen these guarantees casually; they were tuned to balance reliability and no-flash UX.
async fn capture_screenshot_with_retry(
&self,
@@ -522,7 +519,7 @@ impl Page {
match eval {
Ok(v) => {
let obj = v.value().cloned().unwrap_or(serde_json::Value::Null);
let hidden = obj.get("hidden").and_then(|x| x.as_bool()).unwrap_or(false);
let hidden = obj.get("hidden").and_then(serde_json::Value::as_bool).unwrap_or(false);
let vs = obj.get("vs").and_then(|x| x.as_str()).unwrap_or("visible");
!(hidden || vs != "visible")
}
@@ -826,25 +823,25 @@ impl Page {
if let Some(obj) = result.value() {
let needs_correction = obj
.get("needsCorrection")
.and_then(|v| v.as_bool())
.and_then(serde_json::Value::as_bool)
.unwrap_or(false);
if needs_correction {
let current_width = obj
.get("currentWidth")
.and_then(|v| v.as_u64())
.and_then(serde_json::Value::as_u64)
.unwrap_or(0);
let current_height = obj
.get("currentHeight")
.and_then(|v| v.as_u64())
.and_then(serde_json::Value::as_u64)
.unwrap_or(0);
let current_dpr = obj
.get("currentDpr")
.and_then(|v| v.as_f64())
.and_then(serde_json::Value::as_f64)
.unwrap_or(1.0);
let current_zoom = obj
.get("currentZoom")
.and_then(|v| v.as_f64())
.and_then(serde_json::Value::as_f64)
.unwrap_or(1.0);
debug!(
@@ -866,7 +863,7 @@ impl Page {
.mobile(self.config.viewport.mobile)
.build()
.map_err(|e| {
BrowserError::CdpError(format!("Failed to build viewport params: {}", e))
BrowserError::CdpError(format!("Failed to build viewport params: {e}"))
})?;
self.cdp_page.execute(params).await?;
@@ -890,8 +887,7 @@ impl Page {
// First try the externalized installer for easier iteration.
// The JS must define `window.__vcInstall(x,y)` and create window.__vc with __version=11.
let external = format!(
"{}\n;(()=>{{ try {{ return (window.__vcInstall ? window.__vcInstall : function(x,y){{}})({:.0},{:.0}); }} catch (e) {{ return String(e && e.message || e); }} }})()",
VIRTUAL_CURSOR_JS, cursor_x, cursor_y
"{VIRTUAL_CURSOR_JS}\n;(()=>{{ try {{ return (window.__vcInstall ? window.__vcInstall : function(x,y){{}})({cursor_x:.0},{cursor_y:.0}); }} catch (e) {{ return String(e && e.message || e); }} }})()"
);
if let Ok(res) = self.cdp_page.evaluate(external).await {
let status = res
@@ -904,8 +900,7 @@ impl Page {
} else {
warn!("Virtual cursor injection reported: {}", status);
return Err(BrowserError::CdpError(format!(
"Virtual cursor injection failed: {}",
status
"Virtual cursor injection failed: {status}"
)));
}
}
@@ -1013,13 +1008,11 @@ impl Page {
}}
return false; // Do not steal focus by picking arbitrary candidates.
}})({cursor_x}, {cursor_y})
"#,
cursor_x = cursor_x,
cursor_y = cursor_y
"#
);
let result = self.cdp_page.evaluate(script).await?;
let focused = result.value().and_then(|v| v.as_bool()).unwrap_or(false);
let focused = result.value().and_then(serde_json::Value::as_bool).unwrap_or(false);
Ok(focused)
}
@@ -1057,8 +1050,8 @@ impl Page {
last_error = Some(e);
// Check if the page actually navigated despite the timeout
if let Ok(cur_opt) = self.cdp_page.url().await {
if let Some(cur) = cur_opt {
if let Ok(cur_opt) = self.cdp_page.url().await
&& let Some(cur) = cur_opt {
let looks_loaded =
cur.starts_with("http://") || cur.starts_with("https://");
if looks_loaded && cur != "about:blank" {
@@ -1071,7 +1064,6 @@ impl Page {
break;
}
}
}
if attempt < max_retries {
// Wait before retry, increasing delay each time
@@ -1092,8 +1084,8 @@ impl Page {
attempt, max_retries
);
// Check if the page actually navigated despite the timeout
if let Ok(cur_opt) = self.cdp_page.url().await {
if let Some(cur) = cur_opt {
if let Ok(cur_opt) = self.cdp_page.url().await
&& let Some(cur) = cur_opt {
let looks_loaded =
cur.starts_with("http://") || cur.starts_with("https://");
if looks_loaded && cur != "about:blank" {
@@ -1106,7 +1098,6 @@ impl Page {
break;
}
}
}
if attempt < max_retries {
let delay_ms = 1000 * attempt as u64;
@@ -1123,8 +1114,7 @@ impl Page {
// If we exhausted retries and still have an error, bail out
if let Some(e) = last_error {
return Err(BrowserError::CdpError(format!(
"Navigation failed after {} retries: {}",
max_retries, e
"Navigation failed after {max_retries} retries: {e}"
)));
}
@@ -1138,7 +1128,7 @@ impl Page {
let start = std::time::Instant::now();
loop {
let state = self.cdp_page.evaluate(script).await.ok().and_then(|r| {
r.value().and_then(|v| v.as_str().map(|s| s.to_string()))
r.value().and_then(|v| v.as_str().map(std::string::ToString::to_string))
});
if matches!(state.as_deref(), Some("interactive") | Some("complete")) {
break;
@@ -1168,7 +1158,7 @@ impl Page {
let start = std::time::Instant::now();
loop {
let state = self.cdp_page.evaluate(script).await.ok().and_then(|r| {
r.value().and_then(|v| v.as_str().map(|s| s.to_string()))
r.value().and_then(|v| v.as_str().map(std::string::ToString::to_string))
});
if matches!(state.as_deref(), Some("complete")) {
break;
@@ -1189,8 +1179,7 @@ impl Page {
}
_ => {
return Err(BrowserError::ConfigError(format!(
"Unknown wait event: {}",
event
"Unknown wait event: {event}"
)));
}
},
@@ -1281,8 +1270,8 @@ impl Page {
.await
.unwrap_or(serde_json::Value::Null);
let doc_w = probe.get("w").and_then(|v| v.as_u64()).unwrap_or(0) as u32;
let doc_h = probe.get("h").and_then(|v| v.as_u64()).unwrap_or(0) as u32;
let doc_w = probe.get("w").and_then(serde_json::Value::as_u64).unwrap_or(0) as u32;
let doc_h = probe.get("h").and_then(serde_json::Value::as_u64).unwrap_or(0) as u32;
// Fall back to configured viewport if probe failed
let vw = if doc_w > 0 {
@@ -1316,7 +1305,7 @@ impl Page {
let data_b64: &str = resp.data.as_ref();
let data = base64::engine::general_purpose::STANDARD
.decode(data_b64.as_bytes())
.map_err(|e| BrowserError::ScreenshotError(format!("base64 decode failed: {}", e)))?;
.map_err(|e| BrowserError::ScreenshotError(format!("base64 decode failed: {e}")))?;
Ok(vec![Screenshot {
data,
@@ -1365,7 +1354,7 @@ impl Page {
let data = base64::engine::general_purpose::STANDARD
.decode(data_b64.as_bytes())
.map_err(|e| {
BrowserError::ScreenshotError(format!("base64 decode failed: {}", e))
BrowserError::ScreenshotError(format!("base64 decode failed: {e}"))
})?;
shots.push(Screenshot {
data,
@@ -1410,7 +1399,7 @@ impl Page {
let data_b64: &str = resp.data.as_ref();
let data = base64::engine::general_purpose::STANDARD
.decode(data_b64.as_bytes())
.map_err(|e| BrowserError::ScreenshotError(format!("base64 decode failed: {}", e)))?;
.map_err(|e| BrowserError::ScreenshotError(format!("base64 decode failed: {e}")))?;
let final_width = if region.width > 1024 {
1024
@@ -1435,7 +1424,7 @@ impl Page {
.mobile(viewport.mobile.unwrap_or(false))
.build()
.map_err(|e| {
BrowserError::CdpError(format!("Failed to build viewport params: {}", e))
BrowserError::CdpError(format!("Failed to build viewport params: {e}"))
})?;
self.cdp_page.execute(params).await?;
@@ -1568,7 +1557,7 @@ impl Page {
.evaluate(check_script)
.await
.ok()
.and_then(|result| result.value().and_then(|v| v.as_bool()))
.and_then(|result| result.value().and_then(serde_json::Value::as_bool))
.unwrap_or(false);
if !cursor_exists {
@@ -1584,20 +1573,18 @@ impl Page {
self
.cdp_page
.evaluate(format!(
"(function(x,y){{ try {{ if(window.__vc && window.__vc.update) return window.__vc.update(x,y)|0; }} catch(_e){{}} return 0; }})({:.0},{:.0})",
move_x, move_y
"(function(x,y){{ try {{ if(window.__vc && window.__vc.update) return window.__vc.update(x,y)|0; }} catch(_e){{}} return 0; }})({move_x:.0},{move_y:.0})"
))
.await
.ok()
.and_then(|r| r.value().and_then(|v| v.as_u64()))
.and_then(|r| r.value().and_then(serde_json::Value::as_u64))
.unwrap_or(0) as u64
} else {
// Internal browser: snap immediately and report zero duration
let _ = self
.cdp_page
.evaluate(format!(
"(function(x,y){{ try {{ if(window.__vc && window.__vc.snapTo) {{ window.__vc.snapTo(x,y); return 0; }} }} catch(_e){{}} return 0; }})({:.0},{:.0})",
move_x, move_y
"(function(x,y){{ try {{ if(window.__vc && window.__vc.snapTo) {{ window.__vc.snapTo(x,y); return 0; }} }} catch(_e){{}} return 0; }})({move_x:.0},{move_y:.0})"
))
.await;
0
@@ -1632,7 +1619,7 @@ impl Page {
)
.await
.ok()
.and_then(|r| r.value().and_then(|v| v.as_u64()))
.and_then(|r| r.value().and_then(serde_json::Value::as_u64))
.unwrap_or(0) as u64;
// Mouse down
@@ -1770,7 +1757,7 @@ impl Page {
)
.await
.ok()
.and_then(|r| r.value().and_then(|v| v.as_u64()))
.and_then(|r| r.value().and_then(serde_json::Value::as_u64))
.unwrap_or(0) as u64;
// Mouse down
@@ -2174,7 +2161,8 @@ impl Page {
console.debug = __orig.debug;
}}
}})()"#,
serde_json::to_string(&user_code_with_source).expect("Failed to serialize user code")
serde_json::to_string(&user_code_with_source)
.unwrap_or_else(|_| "\"code serialization failed\"".to_string())
);
tracing::debug!("Executing JavaScript code: {}", code);
+1 -1
View File
@@ -280,7 +280,7 @@ fn extract_response(output: &str) -> String {
let result = text_lines.join("\n").trim().to_string();
// Clean trailing brackets
result
.trim_end_matches(|c: char| matches!(c, '[' | ']' | '{' | '}' | ' '))
.trim_end_matches(['[', ']', '{', '}', ' '])
.to_string()
}
+3 -3
View File
@@ -23,8 +23,8 @@ fn parse_response_expected(path: &Path) -> Result<Vec<(u64, u64)>> {
for ev in events {
let data = ev.get("data");
if let Some(d) = data {
let out = d.get("output_index").and_then(|x| x.as_u64());
let seq = d.get("sequence_number").and_then(|x| x.as_u64());
let out = d.get("output_index").and_then(serde_json::Value::as_u64);
let seq = d.get("sequence_number").and_then(serde_json::Value::as_u64);
if let (Some(out), Some(seq)) = (out, seq) {
items.push((out, seq));
}
@@ -86,7 +86,7 @@ fn main() -> Result<()> {
println!("Expected (first 20):");
for (i, (out, seq)) in expected.iter().take(20).enumerate() {
println!(" {:>3}: out={} seq={}", i, out, seq);
println!(" {i:>3}: out={out} seq={seq}");
}
println!("\nActual inserts (first 40):");
+23 -28
View File
@@ -128,7 +128,7 @@ pub async fn tail_events(target: &BridgeTarget, level: &str, raw: bool) -> Resul
let levels = vec![normalise_level(level)?];
let caps = DEFAULT_CAPABILITIES
.iter()
.map(|s| s.to_string())
.map(std::string::ToString::to_string)
.collect::<Vec<_>>();
let (mut tx, mut rx) = connect_and_subscribe(target, &levels, &caps).await?;
@@ -150,14 +150,13 @@ pub async fn tail_events(target: &BridgeTarget, level: &str, raw: bool) -> Resul
match msg {
Some(Ok(Message::Text(text))) => {
if raw {
println!("{}", text);
println!("{text}");
continue;
}
if let Ok(val) = serde_json::from_str::<Value>(&text) {
if let Some(line) = format_bridge_message(&val) {
println!("{}", line);
if let Ok(val) = serde_json::from_str::<Value>(&text)
&& let Some(line) = format_bridge_message(&val) {
println!("{line}");
}
}
}
Some(Ok(Message::Binary(_))) => {}
Some(Ok(Message::Close(_))) => break,
@@ -278,18 +277,17 @@ fn default_levels() -> Vec<String> {
}
fn compute_staleness(meta: &BridgeMeta, path: &Path) -> (bool, Option<i64>) {
if let Some(hb) = &meta.heartbeat_at {
if let Ok(ts) = DateTime::parse_from_rfc3339(hb) {
if let Some(hb) = &meta.heartbeat_at
&& let Ok(ts) = DateTime::parse_from_rfc3339(hb) {
let age = Utc::now().signed_duration_since(ts.with_timezone(&Utc));
return (
age.num_milliseconds() > HEARTBEAT_STALE_MS,
Some(age.num_milliseconds()),
);
}
}
if let Ok(stat) = std::fs::metadata(path) {
if let Ok(modified) = stat.modified() {
if let Ok(stat) = std::fs::metadata(path)
&& let Ok(modified) = stat.modified() {
let modified: DateTime<Utc> = modified.into();
let age = Utc::now().signed_duration_since(modified);
return (
@@ -297,7 +295,6 @@ fn compute_staleness(meta: &BridgeMeta, path: &Path) -> (bool, Option<i64>) {
Some(age.num_milliseconds()),
);
}
}
(false, None)
}
@@ -365,13 +362,13 @@ async fn wait_for_type(
expected: &[&str],
dur: Duration,
) -> Result<Option<Value>> {
let expected_lower: Vec<String> = expected.iter().map(|s| s.to_string()).collect();
let expected_lower: Vec<String> = expected.iter().map(std::string::ToString::to_string).collect();
let found = timeout(dur, async {
while let Some(msg) = rx.next().await {
match msg {
Ok(Message::Text(text)) => {
if let Ok(val) = serde_json::from_str::<Value>(&text) {
if val
if let Ok(val) = serde_json::from_str::<Value>(&text)
&& val
.get("type")
.and_then(|t| t.as_str())
.map(|t| expected_lower.contains(&t.to_string()))
@@ -379,7 +376,6 @@ async fn wait_for_type(
{
return Some(val);
}
}
}
Ok(Message::Binary(_)) => {}
Ok(Message::Close(frame)) => {
@@ -411,16 +407,15 @@ async fn wait_for_forwarded(
while let Some(msg) = rx.next().await {
match msg {
Ok(Message::Text(text)) => {
if let Ok(val) = serde_json::from_str::<Value>(&text) {
if val.get("type").and_then(|t| t.as_str()) == Some("control_forwarded")
if let Ok(val) = serde_json::from_str::<Value>(&text)
&& val.get("type").and_then(|t| t.as_str()) == Some("control_forwarded")
&& val.get("id").and_then(|v| v.as_str()) == Some(id)
{
return val
.get("delivered")
.and_then(|v| v.as_u64())
.and_then(serde_json::Value::as_u64)
.map(|v| v as usize);
}
}
}
Ok(Message::Binary(_)) => {}
Ok(_) => {}
@@ -461,7 +456,7 @@ async fn wait_for_control_and_screenshot(
if val.get("id").and_then(|v| v.as_str()) == Some(id) =>
{
let data_len =
val.get("data").and_then(|v| v.as_str()).map(|s| s.len());
val.get("data").and_then(|v| v.as_str()).map(str::len);
let mime = val
.get("mime")
.and_then(|v| v.as_str())
@@ -505,11 +500,11 @@ fn format_bridge_message(val: &Value) -> Option<String> {
Some(format!("⚠ drop/rate-limit: {msg}"))
}
"control_result" => {
let ok = val.get("ok").and_then(|v| v.as_bool()).unwrap_or(false);
let ok = val.get("ok").and_then(serde_json::Value::as_bool).unwrap_or(false);
let id = val.get("id").and_then(|v| v.as_str()).unwrap_or("");
let summary = if ok {
val.get("result")
.map(|r| format_result(r))
.map(format_result)
.unwrap_or_else(|| "ok".to_string())
} else {
val.get("error")
@@ -524,8 +519,8 @@ fn format_bridge_message(val: &Value) -> Option<String> {
let level = val.get("level").and_then(|v| v.as_str()).unwrap_or("info");
let ts = val
.get("timestamp")
.and_then(|v| v.as_i64())
.map(|ms| format_ts(ms))
.and_then(serde_json::Value::as_i64)
.map(format_ts)
.unwrap_or_else(|| "--:--:--".to_string());
let body = match t {
@@ -534,7 +529,7 @@ fn format_bridge_message(val: &Value) -> Option<String> {
let bytes = val
.get("data")
.and_then(|v| v.as_str())
.map(|s| s.len())
.map(str::len)
.unwrap_or(0);
format!("screenshot {mime} ({} KB)", bytes / 1024)
}
@@ -550,7 +545,7 @@ fn format_bridge_message(val: &Value) -> Option<String> {
_ => val
.get("message")
.and_then(|m| m.as_str())
.map(|m| m.to_string())
.map(std::string::ToString::to_string)
.unwrap_or_else(|| t.to_string()),
};
@@ -570,7 +565,7 @@ fn format_result(val: &Value) -> String {
}
fn format_ts(ms: i64) -> String {
let dt = DateTime::from_timestamp_millis(ms).unwrap_or_else(|| Utc::now());
let dt = DateTime::from_timestamp_millis(ms).unwrap_or_else(Utc::now);
dt.format("%H:%M:%S").to_string()
}
+24 -29
View File
@@ -216,7 +216,7 @@ async fn handle_chat_command(
}
let response = copilot.start_chat_session(&prompt, Some(context)).await?;
println!("Copilot: {}", response);
println!("Copilot: {response}");
// Interactive mode if no message was provided
if is_interactive || continue_session {
@@ -232,7 +232,7 @@ async fn handle_chat_command(
}
let response = copilot.start_chat_session(input, None).await?;
println!("Copilot: {}", response);
println!("Copilot: {response}");
}
}
@@ -257,8 +257,7 @@ async fn handle_suggest_command(
Ok(content) => content,
Err(_e) => {
return Err(CodexErr::UnsupportedOperation(format!(
"Failed to read file {}",
file_path
"Failed to read file {file_path}"
)));
}
},
@@ -288,7 +287,7 @@ async fn handle_suggest_command(
println!("--- Suggestion {} ---", i + 1);
println!("{}", suggestion.text);
if let Some(reasoning) = &suggestion.reasoning {
println!("Reasoning: {}", reasoning);
println!("Reasoning: {reasoning}");
}
println!();
}
@@ -346,12 +345,12 @@ async fn handle_review_command(
println!("{}", serde_json::to_string_pretty(&json)?);
}
"markdown" => {
println!("# Code Review: {}\n", file);
println!("{}", review);
println!("# Code Review: {file}\n");
println!("{review}");
}
_ => {
println!("Code Review for {}:", file);
println!("{}", review);
println!("Code Review for {file}:");
println!("{review}");
}
}
@@ -374,8 +373,7 @@ async fn handle_docs_command(
Ok(content) => content,
Err(_e) => {
return Err(CodexErr::UnsupportedOperation(format!(
"Failed to read file {}",
input
"Failed to read file {input}"
)));
}
}
@@ -387,16 +385,15 @@ async fn handle_docs_command(
match output {
Some(output_file) => match std::fs::write(&output_file, &documentation) {
Ok(_) => println!("Documentation written to {}", output_file),
Ok(_) => println!("Documentation written to {output_file}"),
Err(_e) => {
return Err(CodexErr::UnsupportedOperation(format!(
"Failed to write to {}",
output_file
"Failed to write to {output_file}"
)));
}
},
None => {
println!("{}", documentation);
println!("{documentation}");
}
}
@@ -419,8 +416,7 @@ async fn handle_explain_command(
Ok(content) => content,
Err(_e) => {
return Err(CodexErr::UnsupportedOperation(format!(
"Failed to read file {}",
file_path
"Failed to read file {file_path}"
)));
}
},
@@ -437,8 +433,8 @@ async fn handle_explain_command(
let explanation = copilot.explain_code(&code_content).await?;
println!("Code Explanation ({} level):", level);
println!("{}", explanation);
println!("Code Explanation ({level} level):");
println!("{explanation}");
Ok(())
}
@@ -457,11 +453,11 @@ async fn handle_shell_command(
let commands = copilot.suggest_commands(&task).await?;
if commands.is_empty() {
println!("No commands suggested for task: {}", task);
println!("No commands suggested for task: {task}");
return Ok(());
}
println!("Suggested commands for: {}", task);
println!("Suggested commands for: {task}");
println!();
for (i, command) in commands.iter().enumerate() {
@@ -470,9 +466,9 @@ async fn handle_shell_command(
if explain {
// Get explanation for the command
let explanation = copilot
.explain_code(&format!("Shell command: {}", command))
.explain_code(&format!("Shell command: {command}"))
.await?;
println!(" → {}", explanation);
println!(" → {explanation}");
}
println!();
}
@@ -537,7 +533,7 @@ async fn handle_commit_command(
""
}
);
println!("{}", commit_message);
println!("{commit_message}");
Ok(())
}
@@ -606,8 +602,7 @@ async fn handle_complete_command(
Ok(content) => content,
Err(_e) => {
return Err(CodexErr::UnsupportedOperation(format!(
"Failed to read file {}",
file
"Failed to read file {file}"
)));
}
};
@@ -638,13 +633,13 @@ async fn handle_complete_command(
let end_line = std::cmp::min(lines.len(), line + context);
let context_lines: Vec<String> = lines[start_line..end_line]
.iter()
.map(|s| s.to_string())
.map(std::string::ToString::to_string)
.collect();
let file_ext = Path::new(&file)
.extension()
.and_then(|ext| ext.to_str())
.map(|ext| ext.to_string());
.map(std::string::ToString::to_string);
let completions = copilot
.auto_complete(prefix, file_ext.as_deref(), context_lines)
@@ -655,7 +650,7 @@ async fn handle_complete_command(
return Ok(());
}
println!("Completions for position {}:{} in {}:", line, column, file);
println!("Completions for position {line}:{column} in {file}:");
for (i, completion) in completions.iter().enumerate() {
println!("{}. {}", i + 1, completion);
}
+1 -1
View File
@@ -194,6 +194,6 @@ async fn run_llm_request(
}
}
println!("{}", final_text);
println!("{final_text}");
Ok(())
}
+2 -3
View File
@@ -127,13 +127,12 @@ pub async fn run_login_status(cli_config_overrides: CliConfigOverrides) -> ! {
Ok(api_key) => {
eprintln!("Logged in using an API key - {}", safe_format_key(&api_key));
if let Ok(env_api_key) = env::var(OPENAI_API_KEY_ENV_VAR) {
if env_api_key == api_key {
if let Ok(env_api_key) = env::var(OPENAI_API_KEY_ENV_VAR)
&& env_api_key == api_key {
eprintln!(
" API loaded from OPENAI_API_KEY environment variable or .env file"
);
}
}
std::process::exit(0);
}
Err(e) => {
+48 -53
View File
@@ -36,7 +36,6 @@ use hanzo_tui::ExitSummary;
use hanzo_tui::resume_command_name;
use serde::Deserialize;
use serde::Serialize;
use serde_json;
use std::fs;
use std::path::Path;
use std::path::PathBuf;
@@ -461,7 +460,7 @@ async fn cli_main(code_linux_sandbox_exe: Option<PathBuf>) -> anyhow::Result<()>
if !token_usage.is_zero() {
println!(
"{}",
hanzo_core::protocol::FinalOutput::from(token_usage.clone())
hanzo_core::protocol::FinalOutput::from(token_usage)
);
}
if let Some(session_id) = session_id {
@@ -526,7 +525,7 @@ async fn cli_main(code_linux_sandbox_exe: Option<PathBuf>) -> anyhow::Result<()>
if !token_usage.is_zero() {
println!(
"{}",
hanzo_core::protocol::FinalOutput::from(token_usage.clone())
hanzo_core::protocol::FinalOutput::from(token_usage)
);
}
if let Some(session_id) = session_id {
@@ -749,10 +748,10 @@ async fn run_bridge_list(_cmd: BridgeListCommand) -> anyhow::Result<()> {
println!("{}Bridge metadata : {}", prefix, target.meta_path.display());
println!("{}url : {}", indent, target.meta.url);
if let Some(ws) = target.meta.workspace_path.as_deref() {
println!("{}workspace : {ws}", indent);
println!("{indent}workspace : {ws}");
}
if let Some(pid) = target.meta.pid {
println!("{}host pid : {pid}", indent);
println!("{indent}host pid : {pid}");
}
let hb = match target.heartbeat_age_ms {
@@ -766,7 +765,7 @@ async fn run_bridge_list(_cmd: BridgeListCommand) -> anyhow::Result<()> {
}
None => "unknown".to_string(),
};
println!("{}heartbeat : {hb}", indent);
println!("{indent}heartbeat : {hb}");
println!(
"{}stale : {}",
indent,
@@ -774,14 +773,13 @@ async fn run_bridge_list(_cmd: BridgeListCommand) -> anyhow::Result<()> {
);
if target.stale {
println!(
"{}⚠ metadata looks stale; restart dev-bridge-host if this persists.",
indent
"{indent}⚠ metadata looks stale; restart dev-bridge-host if this persists."
);
}
match bridge::list_control_capable(target).await {
Ok(count) => println!("{}control-capable : {count} bridge client(s)", indent),
Err(err) => println!("{}control-capable : unknown ({err})", indent),
Ok(count) => println!("{indent}control-capable : {count} bridge client(s)"),
Err(err) => println!("{indent}control-capable : unknown ({err})"),
}
if idx + 1 < targets.len() {
@@ -807,11 +805,11 @@ async fn run_bridge_screenshot(cmd: BridgeScreenshotCommand) -> anyhow::Result<(
);
if let Some(res) = outcome.result.as_ref() {
let ok = res.get("ok").and_then(|v| v.as_bool()).unwrap_or(false);
let ok = res.get("ok").and_then(serde_json::Value::as_bool).unwrap_or(false);
if ok {
let payload = res
.get("result")
.map(|v| v.to_string())
.map(std::string::ToString::to_string)
.unwrap_or_else(|| "ok".to_string());
println!("Control result : {payload}");
} else {
@@ -849,11 +847,11 @@ async fn run_bridge_javascript(cmd: BridgeJavascriptCommand) -> anyhow::Result<(
);
if let Some(res) = outcome.result.as_ref() {
let ok = res.get("ok").and_then(|v| v.as_bool()).unwrap_or(false);
let ok = res.get("ok").and_then(serde_json::Value::as_bool).unwrap_or(false);
if ok {
let payload = res
.get("result")
.map(|v| v.to_string())
.map(std::string::ToString::to_string)
.unwrap_or_else(|| "ok".to_string());
println!("Result : {payload}");
} else {
@@ -896,11 +894,10 @@ fn select_bridge_target(selector: Option<&str>) -> anyhow::Result<bridge::Bridge
if paths_match(&target.meta_path, &path) {
return Ok(target.clone());
}
if let Some(ws) = target.meta.workspace_path.as_deref() {
if ws == selector || ws.ends_with(selector) || ws.contains(selector) {
if let Some(ws) = target.meta.workspace_path.as_deref()
&& (ws == selector || ws.ends_with(selector) || ws.contains(selector)) {
return Ok(target.clone());
}
}
}
anyhow::bail!(
@@ -1030,7 +1027,7 @@ fn finalize_resume_interactive(
merge_resume_cli_flags(&mut interactive, resume_cli);
if let Err(err) = apply_resume_directives(&mut interactive, session_id, last) {
eprintln!("{}", err);
eprintln!("{err}");
process::exit(1);
}
@@ -1126,7 +1123,7 @@ fn resolve_resume_path(session_id: Option<&str>, last: bool) -> anyhow::Result<O
let code_home = hanzo_core::config::find_code_home()
.context("failed to locate Hanzo Dev home directory")?;
let sess = session_id.map(|s| s.to_string());
let sess = session_id.map(std::string::ToString::to_string);
let fetch = async move {
let catalog = SessionCatalog::new(code_home.clone());
if let Some(id) = sess.as_deref() {
@@ -1161,7 +1158,7 @@ fn resolve_resume_path(session_id: Option<&str>, last: bool) -> anyhow::Result<O
match TokioHandle::try_current() {
Ok(handle) => {
let handle = handle.clone();
let handle = handle;
std::thread::Builder::new()
.name("resume-lookup".to_string())
.spawn(move || handle.block_on(fetch))
@@ -1214,8 +1211,8 @@ fn order_replay_main(args: OrderReplayArgs) -> anyhow::Result<()> {
for ev in events {
let data = ev.get("data");
if let Some(d) = data {
let out = d.get("output_index").and_then(|x| x.as_u64());
let seq = d.get("sequence_number").and_then(|x| x.as_u64());
let out = d.get("output_index").and_then(serde_json::Value::as_u64);
let seq = d.get("sequence_number").and_then(serde_json::Value::as_u64);
if let (Some(out), Some(seq)) = (out, seq) {
items.push((out, seq));
}
@@ -1271,7 +1268,7 @@ fn order_replay_main(args: OrderReplayArgs) -> anyhow::Result<()> {
println!("Expected (first 20 sorted by out,seq):");
for (i, (out, seq)) in expected.iter().take(20).enumerate() {
println!(" {:>3}: out={} seq={}", i, out, seq);
println!(" {i:>3}: out={out} seq={seq}");
}
println!("\nActual inserts (first 40):");
@@ -1294,8 +1291,7 @@ fn order_replay_main(args: OrderReplayArgs) -> anyhow::Result<()> {
.iter()
.position(|l| l.ordered && l.req == 1 && l.out == 2);
println!(
"\nCheck (req=1): first out=1 at {:?}, first out=2 at {:?}",
pos_out1, pos_out2
"\nCheck (req=1): first out=1 at {pos_out1:?}, first out=2 at {pos_out2:?}"
);
if let (Some(p1), Some(p2)) = (pos_out1, pos_out2) {
if p1 < p2 {
@@ -1325,7 +1321,7 @@ async fn preview_main(args: PreviewArgs) -> anyhow::Result<()> {
let (owner, name) = repo
.split_once('/')
.map(|(o, n)| (o.to_string(), n.to_string()))
.ok_or_else(|| anyhow::anyhow!(format!("Invalid repo format: {}", repo)))?;
.ok_or_else(|| anyhow::anyhow!(format!("Invalid repo format: {repo}")))?;
let os = env::consts::OS;
let arch = env::consts::ARCH;
@@ -1335,7 +1331,7 @@ async fn preview_main(args: PreviewArgs) -> anyhow::Result<()> {
("macos", "x86_64") => "x86_64-apple-darwin",
("macos", "aarch64") => "aarch64-apple-darwin",
("windows", _) => "x86_64-pc-windows-msvc",
_ => bail!(format!("Unsupported platform: {}/{}", os, arch)),
_ => bail!(format!("Unsupported platform: {os}/{arch}")),
};
let client = reqwest::Client::builder()
@@ -1359,7 +1355,7 @@ async fn preview_main(args: PreviewArgs) -> anyhow::Result<()> {
name: &str,
slug: &str,
) -> anyhow::Result<String> {
let base = format!("preview-{}", slug);
let base = format!("preview-{slug}");
let url = format!("https://api.github.com/repos/{owner}/{name}/releases?per_page=100");
let v = fetch_json(client, &url).await?;
let mut latest = base.clone();
@@ -1406,7 +1402,7 @@ async fn preview_main(args: PreviewArgs) -> anyhow::Result<()> {
let resp = client.get(u).send().await?;
if resp.status().is_success() {
let data = resp.bytes().await?;
let filename = u.split('/').last().unwrap_or("download.bin");
let filename = u.split('/').next_back().unwrap_or("download.bin");
let p = tmp.path().join(filename);
fs::write(&p, &data)?;
downloaded = Some((p, u.clone()));
@@ -1419,11 +1415,10 @@ async fn preview_main(args: PreviewArgs) -> anyhow::Result<()> {
fn first_match(dir: &Path, pat: &str) -> Option<std::path::PathBuf> {
for entry in fs::read_dir(dir).ok()? {
let p = entry.ok()?.path();
if let Some(name) = p.file_name().and_then(|s| s.to_str()) {
if name.starts_with(pat) {
if let Some(name) = p.file_name().and_then(|s| s.to_str())
&& name.starts_with(pat) {
return Some(p);
}
}
}
None
}
@@ -1456,7 +1451,7 @@ async fn preview_main(args: PreviewArgs) -> anyhow::Result<()> {
if os != "windows" {
// If we downloaded a tar.gz, extract
if path.extension().and_then(|e| e.to_str()) == Some("gz") {
let tgz = path.clone();
let tgz = path;
let file = fs::File::open(&tgz)?;
let gz = GzDecoder::new(file);
let mut ar = tar::Archive::new(gz);
@@ -1491,7 +1486,7 @@ async fn preview_main(args: PreviewArgs) -> anyhow::Result<()> {
let dest = match exe.extension().and_then(|e| e.to_str()) {
Some(ext) => {
let stem = exe.file_stem().and_then(|s| s.to_str()).unwrap_or("dev");
out_dir.join(format!("{}-{}.{}", stem, slug, ext))
out_dir.join(format!("{stem}-{slug}.{ext}"))
}
None => out_dir.join(format!(
"{}-{}",
@@ -1525,9 +1520,9 @@ async fn preview_main(args: PreviewArgs) -> anyhow::Result<()> {
// Derive base name from archive (e.g., dev-aarch64-apple-darwin.zst -> dev-aarch64-apple-darwin-<slug>.{exe?})
let stem = path.file_stem().and_then(|s| s.to_str()).unwrap_or("dev");
let dest = if cfg!(windows) {
out_dir.join(format!("{}-{}.exe", stem, slug))
out_dir.join(format!("{stem}-{slug}.exe"))
} else {
out_dir.join(format!("{}-{}", stem, slug))
out_dir.join(format!("{stem}-{slug}"))
};
let status = std::process::Command::new("zstd")
.arg("-d")
@@ -1576,11 +1571,11 @@ async fn doctor_main() -> anyhow::Result<()> {
.map(|p| p.display().to_string())
.unwrap_or_else(|_| "<unknown>".to_string());
println!("code version: {}", hanzo_version::version());
println!("current_exe: {}", exe);
println!("current_exe: {exe}");
// PATH
let path = env::var("PATH").unwrap_or_default();
println!("PATH: {}", path);
println!("PATH: {path}");
// Helper to run a shell command and capture stdout (best-effort)
async fn run_cmd(cmd: &str, args: &[&str]) -> String {
@@ -1598,12 +1593,12 @@ async fn doctor_main() -> anyhow::Result<()> {
async move {
let out = run_cmd(
"/bin/bash",
&["-lc", &format!("which -a {} 2>/dev/null || true", name)],
&["-lc", &format!("which -a {name} 2>/dev/null || true")],
)
.await;
out.split('\n')
.filter(|s| !s.is_empty())
.map(|s| s.to_string())
.map(std::string::ToString::to_string)
.collect::<Vec<_>>()
}
};
@@ -1628,7 +1623,7 @@ async fn doctor_main() -> anyhow::Result<()> {
println!(" <none>");
} else {
for p in &code_paths {
println!(" {}", p);
println!(" {p}");
}
}
println!("\nFound 'coder' on PATH (in order):");
@@ -1636,19 +1631,19 @@ async fn doctor_main() -> anyhow::Result<()> {
println!(" <none>");
} else {
for p in &coder_paths {
println!(" {}", p);
println!(" {p}");
}
}
// Try to run --version for each resolved binary to show where mismatches come from
async fn show_versions(caption: &str, paths: &[String]) {
println!("\n{}:", caption);
println!("\n{caption}:");
for p in paths {
let out = run_cmd(p, &["--version"]).await;
if out.is_empty() {
println!(" {} -> (no output)", p);
println!(" {p} -> (no output)");
} else {
println!(" {} -> {}", p, out);
println!(" {p} -> {out}");
}
}
}
@@ -1658,17 +1653,17 @@ async fn doctor_main() -> anyhow::Result<()> {
// Detect Bun shims
let bun_home = env::var("BUN_INSTALL")
.ok()
.or_else(|| env::var("HOME").ok().map(|h| format!("{}/.bun", h)));
.or_else(|| env::var("HOME").ok().map(|h| format!("{h}/.bun")));
if let Some(bun) = bun_home {
let bun_bin = format!("{}/bin", bun);
let bun_coder = format!("{}/coder", bun_bin);
let bun_bin = format!("{bun}/bin");
let bun_coder = format!("{bun_bin}/coder");
if coder_paths.iter().any(|p| p == &bun_coder) {
println!("\nBun shim detected for 'coder': {}", bun_coder);
println!("\nBun shim detected for 'coder': {bun_coder}");
println!("Suggestion: remove old Bun global with: bun remove -g @just-every/code");
}
let bun_code = format!("{}/code", bun_bin);
let bun_code = format!("{bun_bin}/code");
if code_paths.iter().any(|p| p == &bun_code) {
println!("Bun shim detected for 'code': {}", bun_code);
println!("Bun shim detected for 'code': {bun_code}");
println!("Suggestion: prefer 'coder' or remove Bun shim if it conflicts.");
}
}
@@ -1694,10 +1689,10 @@ async fn doctor_main() -> anyhow::Result<()> {
let npm_root = run_cmd("npm", &["root", "-g"]).await;
let npm_prefix = run_cmd("npm", &["prefix", "-g"]).await;
if !npm_root.is_empty() {
println!("\nnpm root -g: {}", npm_root);
println!("\nnpm root -g: {npm_root}");
}
if !npm_prefix.is_empty() {
println!("npm prefix -g: {}", npm_prefix);
println!("npm prefix -g: {npm_prefix}");
}
println!("\nIf versions differ, remove older installs and keep one package manager:");
+4 -4
View File
@@ -1213,7 +1213,7 @@ pub async fn run_main(cli: Cli, _code_linux_sandbox_exe: Option<PathBuf>) -> any
}
KeyCode::Backspace => {
if let Some(m) = app.env_modal.as_mut() {
if let Some((idx, _)) = m.query.grapheme_indices(true).last() {
if let Some((idx, _)) = m.query.grapheme_indices(true).next_back() {
m.query.truncate(idx);
} else {
m.query.clear();
@@ -1622,7 +1622,7 @@ async fn run_submit(args: crate::cli::SubmitArgs) -> anyhow::Result<()> {
if text.messages.len() > seen_msgs {
let new = text.messages.len() - seen_msgs;
seen_msgs = text.messages.len();
eprintln!("progress: +{} message(s)", new);
eprintln!("progress: +{new} message(s)");
}
use hanzo_cloud_tasks_client::AttemptStatus as S;
@@ -1657,13 +1657,13 @@ async fn run_submit(args: crate::cli::SubmitArgs) -> anyhow::Result<()> {
Some(diff) => {
out.push_str("Diff:\n");
out.push_str(&diff);
out.push_str("\n");
out.push('\n');
}
None => out.push_str("No diff available.\n"),
}
// Sanitize any embedded NULs that could corrupt downstream consumers.
let out = out.replace('\0', "");
print!("{}", out);
print!("{out}");
return Ok(());
}
S::Failed => {
@@ -128,7 +128,7 @@ pub(crate) fn compute_slice_bounds(conversation: &[ResponseItem]) -> Option<(usi
let mut midpoint = goal_idx + 1;
if total_tokens > 0 {
let target = (total_tokens + 1) / 2;
let target = total_tokens.div_ceil(2);
let mut running = 0usize;
for (offset, count) in token_counts.iter().enumerate() {
running = running.saturating_add(*count);
@@ -138,7 +138,7 @@ pub(crate) fn compute_slice_bounds(conversation: &[ResponseItem]) -> Option<(usi
}
}
} else {
midpoint = goal_idx + 1 + (after_goal.len() + 1) / 2;
midpoint = goal_idx + 1 + after_goal.len().div_ceil(2);
}
let slice_start = goal_idx + 1;
@@ -225,7 +225,7 @@ fn summarize_with_model(
) -> Result<String> {
let mut aggregate_summary = prev_summary
.filter(|text| !text.trim().is_empty())
.map(|text| text.to_string());
.map(std::string::ToString::to_string);
let flattened = flatten_items(items);
let chunks = chunk_text(&flattened);
@@ -343,13 +343,12 @@ fn deterministic_summary(items: &[ResponseItem], prev_summary: Option<&str>) ->
if text.is_empty() {
continue;
}
actions.push(format!("{}: {}", role, text));
if role == "assistant" {
if let Some(cmd) = text.lines().find(|line| line.trim_start().starts_with('$'))
actions.push(format!("{role}: {text}"));
if role == "assistant"
&& let Some(cmd) = text.lines().find(|line| line.trim_start().starts_with('$'))
{
commands.push(cmd.trim().to_string());
}
}
}
ResponseItem::FunctionCall { name, .. } => {
actions.push(format!("Tool call: {name}"));
@@ -363,7 +362,7 @@ fn deterministic_summary(items: &[ResponseItem], prev_summary: Option<&str>) ->
let mut lines = Vec::new();
if let Some(prev) = prev_summary.filter(|text| !text.trim().is_empty()) {
lines.push(format!("Building on previous checkpoint: {}", prev));
lines.push(format!("Building on previous checkpoint: {prev}"));
}
lines.push(format!(
"Checkpoint covers {} exchanges and {} tool events.",
@@ -379,7 +378,7 @@ fn deterministic_summary(items: &[ResponseItem], prev_summary: Option<&str>) ->
.take(MAX_COMMANDS_IN_SUMMARY)
.collect::<Vec<_>>()
.join(" | ");
lines.push(format!("Key commands: {}", display));
lines.push(format!("Key commands: {display}"));
}
if !actions.is_empty() {
let display = actions
@@ -424,7 +423,7 @@ fn flatten_items(items: &[ResponseItem]) -> String {
buf.push_str(&format!("custom_tool {name}: {input}\n"));
}
ResponseItem::CustomToolCallOutput { output, .. } => {
buf.push_str(&format!("custom_tool_output: {}\n", output));
buf.push_str(&format!("custom_tool_output: {output}\n"));
}
ResponseItem::Reasoning { summary, .. } => {
for item in summary {
@@ -463,7 +462,7 @@ fn chunk_text(text: &str) -> Vec<String> {
+ text[start..]
.chars()
.next()
.map(|c| c.len_utf8())
.map(char::len_utf8)
.unwrap_or(len - start);
}
}
@@ -1049,7 +1049,7 @@ pub fn start_auto_coordinator(
}
let (cmd_tx, cmd_rx) = mpsc::channel();
let thread_tx = cmd_tx.clone();
let thread_tx = cmd_tx;
let cancel_token = CancellationToken::new();
let thread_cancel = cancel_token.clone();
@@ -1058,7 +1058,7 @@ pub fn start_auto_coordinator(
// overflow when validation recursed through long assistant responses.
let builder = std::thread::Builder::new()
.name("code-auto-coordinator".to_string())
.stack_size(1 * 1024 * 1024);
.stack_size(1024 * 1024);
let handle = builder.spawn(move || {
if let Err(err) = run_auto_loop(
event_tx,
@@ -1211,8 +1211,8 @@ fn run_auto_loop(
let mut decision_seq: u64 = 0;
let mut pending_ack_seq: Option<u64> = None;
let mut queued_updates: VecDeque<Arc<[ResponseItem]>> = VecDeque::new();
if !derive_goal_from_history {
if let Some(seed) = build_initial_planning_seed(&goal_text, include_agents) {
if !derive_goal_from_history
&& let Some(seed) = build_initial_planning_seed(&goal_text, include_agents) {
let transcript_item = make_message("assistant", seed.response_json.clone());
let cli_action = AutoTurnCliAction {
prompt: seed.cli_prompt.clone(),
@@ -1234,7 +1234,6 @@ fn run_auto_loop(
pending_ack_seq = Some(decision_seq);
pending_conversation = None;
}
}
let mut schema = build_schema(&active_agent_names, schema_features);
let platform = std::env::consts::OS;
debug!("[Auto coordinator] starting: goal={goal_text} platform={platform}");
@@ -1261,11 +1260,10 @@ fn run_auto_loop(
} else {
next_conversation = Some(conv);
}
} else if pending_ack_seq.is_none() {
if let Some(conv) = queued_updates.pop_front() {
} else if pending_ack_seq.is_none()
&& let Some(conv) = queued_updates.pop_front() {
next_conversation = Some(conv);
}
}
if let Some(conv) = next_conversation {
if cancel_token.is_cancelled() {
@@ -1297,7 +1295,7 @@ fn run_auto_loop(
}
let developer_intro = base_developer_intro.as_str();
let mut retry_conversation: Option<Vec<ResponseItem>> = None;
let time_budget_message = time_budget.as_mut().and_then(|budget| budget.maybe_nudge());
let time_budget_message = time_budget.as_mut().and_then(AutoTimeBudget::maybe_nudge);
let time_budget_deadline = time_budget.as_ref().map(|budget| budget.deadline);
let loop_warning = session_metrics.loop_detection_warning();
match request_coordinator_decision(
@@ -1476,7 +1474,7 @@ fn run_auto_loop(
} else if already_shared_raw {
message.push_str("\nSee assistant message above for the full model output that failed validation.");
}
let _ = event_tx.send(AutoCoordinatorEvent::Thinking {
event_tx.send(AutoCoordinatorEvent::Thinking {
delta: message,
summary_index: None,
});
@@ -1491,12 +1489,12 @@ fn run_auto_loop(
developer_note.push_str(guidance);
}
if already_shared_raw {
developer_note.push_str("\n");
developer_note.push('\n');
developer_note.push_str(OVERLONG_MSG);
} else if let Some(excerpt) = raw_excerpt {
developer_note.push_str("\nLast JSON:\n");
developer_note.push_str(&indent_lines(&excerpt, " "));
developer_note.push_str("\n");
developer_note.push('\n');
developer_note.push_str(OVERLONG_MSG);
}
retry_vec.push(make_message("developer", developer_note));
@@ -1506,12 +1504,12 @@ fn run_auto_loop(
.map(Arc::<[ResponseItem]>::from)
.unwrap_or_else(|| Arc::clone(&conv));
// Keep the model and UI in sync with the full conversation, but avoid spamming a compaction notice.
let _ = event_tx.send(AutoCoordinatorEvent::CompactedHistory {
event_tx.send(AutoCoordinatorEvent::CompactedHistory {
conversation: Arc::clone(&retry_snapshot),
show_notice: false,
});
// Show a user-facing action entry in the Auto Drive card (does not go to the model).
let _ = event_tx.send(AutoCoordinatorEvent::Action {
event_tx.send(AutoCoordinatorEvent::Action {
message: "Retrying prompt generation after the previous response was too long to send to the CLI.".to_string(),
});
pending_conversation = Some(retry_snapshot);
@@ -1565,7 +1563,7 @@ fn run_auto_loop(
let conversation_snapshot = Arc::<[ResponseItem]>::from(base_conversation);
let schema = user_turn_schema();
let time_budget_message =
time_budget.as_mut().and_then(|budget| budget.maybe_nudge());
time_budget.as_mut().and_then(AutoTimeBudget::maybe_nudge);
let time_budget_deadline = time_budget.as_ref().map(|budget| budget.deadline);
match request_user_turn_decision(
&runtime,
@@ -1670,14 +1668,12 @@ fn read_coordinator_prompt(config: &Config) -> Option<String> {
let trimmed = COORDINATOR_PROMPT.trim();
if trimmed.is_empty() {
None
} else if config.max_run_seconds.is_some() {
Some(format!(
"{trimmed}\n\n# Time Budget\n{TIMEBOXED_EXEC_COORDINATOR_GUIDANCE}"
))
} else {
if config.max_run_seconds.is_some() {
Some(format!(
"{trimmed}\n\n# Time Budget\n{TIMEBOXED_EXEC_COORDINATOR_GUIDANCE}"
))
} else {
Some(trimmed.to_string())
}
Some(trimmed.to_string())
}
}
@@ -1700,7 +1696,7 @@ fn build_developer_message(
let primary_goal = if derive_goal_from_history {
"**Primary Goal**\nYou are preparing to start Auto Drive. Review the recent conversation history and identify the single primary coding goal the assistant should pursue next.".to_string()
} else {
format!("**Primary Goal**\n{}", goal_text)
format!("**Primary Goal**\n{goal_text}")
};
(coordinator_message, intro, primary_goal)
}
@@ -1821,7 +1817,7 @@ fn build_schema(active_agents: &[String], features: SchemaFeatures) -> Value {
"type": "string",
});
if !models_enum_values.is_empty() {
schema["enum"] = Value::Array(models_enum_values.clone());
schema["enum"] = Value::Array(models_enum_values);
}
schema
};
@@ -1931,7 +1927,7 @@ fn build_schema(active_agents: &[String], features: SchemaFeatures) -> Value {
"maxLength": 400,
"description": "Outcome-oriented instruction (what to produce)."
},
"models": models_request_property.clone()
"models": models_request_property
},
"required": ["prompt", "context", "write", "models"]
},
@@ -2073,7 +2069,7 @@ fn request_decision(
client,
developer_intro,
primary_goal,
coordinator_prompt.as_deref(),
coordinator_prompt,
time_budget_message,
time_budget_deadline,
loop_warning,
@@ -2086,8 +2082,7 @@ fn request_decision(
)
.map_err(|fallback_err| {
fallback_err.context(format!(
"coordinator fallback with model '{}' failed after original error: {}",
fallback_slug, original_error
"coordinator fallback with model '{fallback_slug}' failed after original error: {original_error}"
))
});
}
@@ -2177,12 +2172,12 @@ fn request_decision_with_model(
) -> Result<RequestStreamResult> {
let developer_intro = developer_intro.to_string();
let primary_goal = primary_goal.to_string();
let time_budget_message = time_budget_message.map(|text| text.to_string());
let loop_warning = loop_warning.map(|text| text.to_string());
let time_budget_message = time_budget_message.map(std::string::ToString::to_string);
let loop_warning = loop_warning.map(std::string::ToString::to_string);
let schema = schema.clone();
let conversation = Arc::clone(&conversation);
let auto_instructions = auto_instructions.map(|text| text.to_string());
let coordinator_prompt = coordinator_prompt.map(|text| text.to_string());
let auto_instructions = auto_instructions.map(std::string::ToString::to_string);
let coordinator_prompt = coordinator_prompt.map(std::string::ToString::to_string);
let tx = event_tx.clone();
let cancel = cancel_token.clone();
let mut rate_limit_switch_state = RateLimitSwitchState::default();
@@ -2230,15 +2225,14 @@ fn request_decision_with_model(
saw_output_text_delta = true;
}
Ok(ResponseEvent::OutputItemDone { item, .. }) => {
if let ResponseItem::Message { content, .. } = &item {
if !saw_output_text_delta {
if let ResponseItem::Message { content, .. } = &item
&& !saw_output_text_delta {
for c in content {
if let ContentItem::OutputText { text } = c {
out.push_str(text);
}
}
}
}
if matches!(item, ResponseItem::Reasoning { .. }) {
reasoning_delta_accumulator.clear();
}
@@ -2333,7 +2327,7 @@ fn request_decision_with_model(
.map(|s| format!("; next attempt at {s}"))
.unwrap_or_default()
);
let _ = tx.send(AutoCoordinatorEvent::Thinking {
tx.send(AutoCoordinatorEvent::Thinking {
delta: message,
summary_index: None,
});
@@ -2424,8 +2418,8 @@ fn build_user_turn_prompt(
fn should_retry_with_default_model(err: &anyhow::Error) -> bool {
err.chain().any(|cause| {
if let Some(code_err) = cause.downcast_ref::<CodexErr>() {
if let CodexErr::UnexpectedStatus(err) = code_err {
if let Some(code_err) = cause.downcast_ref::<CodexErr>()
&& let CodexErr::UnexpectedStatus(err) = code_err {
if !err.status.is_client_error() {
return false;
}
@@ -2435,7 +2429,6 @@ fn should_retry_with_default_model(err: &anyhow::Error) -> bool {
|| body_lower.contains("model_not_found")
|| body_lower.contains("model does not exist");
}
}
false
})
}
@@ -2544,13 +2537,12 @@ pub(crate) fn classify_model_error(error: &anyhow::Error) -> RetryDecision {
return classify_reqwest_error(req_err);
}
if let Some(io_err) = find_in_chain::<std::io::Error>(error) {
if io_err.kind() == std::io::ErrorKind::TimedOut {
if let Some(io_err) = find_in_chain::<std::io::Error>(error)
&& io_err.kind() == std::io::ErrorKind::TimedOut {
return RetryDecision::RetryAfterBackoff {
reason: "network timeout".to_string(),
};
}
}
RetryDecision::Fatal(anyhow!(error.to_string()))
}
@@ -2561,16 +2553,15 @@ fn classify_model_error_with_auto_switch(
event_tx: &AutoCoordinatorEventSender,
error: &anyhow::Error,
) -> RetryDecision {
if let Some(code_err) = find_in_chain::<CodexErr>(error) {
if let CodexErr::UsageLimitReached(limit) = code_err {
if client.auto_switch_accounts_on_rate_limit()
if let Some(code_err) = find_in_chain::<CodexErr>(error)
&& let CodexErr::UsageLimitReached(limit) = code_err
&& client.auto_switch_accounts_on_rate_limit()
&& auth::read_code_api_key_from_env().is_none()
{
if let Some(auth_manager) = client.get_auth_manager() {
&& let Some(auth_manager) = client.get_auth_manager() {
let auth = auth_manager.auth();
let current_account_id = auth
.as_ref()
.and_then(|current| current.get_account_id())
.and_then(hanzo_core::CodexAuth::get_account_id)
.or_else(|| {
auth_accounts::get_active_account_id(client.code_home())
.ok()
@@ -2645,9 +2636,6 @@ fn classify_model_error_with_auto_switch(
}
}
}
}
}
}
classify_model_error(error)
}
@@ -2719,14 +2707,13 @@ fn extract_seconds(value: &serde_json::Value) -> Option<Duration> {
}
return Some(Duration::from_secs_f64(num));
}
if let Some(text) = seconds.as_str() {
if let Ok(num) = text.parse::<f64>() {
if let Some(text) = seconds.as_str()
&& let Ok(num) = text.parse::<f64>() {
if num.is_sign_negative() {
continue;
}
return Some(Duration::from_secs_f64(num));
}
}
}
}
None
@@ -2766,7 +2753,7 @@ fn random_jitter(max: Duration) -> Duration {
Duration::from_secs_f64(jitter)
}
fn find_in_chain<'a, T: std::error::Error + 'static>(error: &'a anyhow::Error) -> Option<&'a T> {
fn find_in_chain<T: std::error::Error + 'static>(error: &anyhow::Error) -> Option<&T> {
for cause in error.chain() {
if let Some(specific) = cause.downcast_ref::<T>() {
return Some(specific);
@@ -2822,8 +2809,8 @@ fn classify_recoverable_decision_error(err: &anyhow::Error) -> Option<Recoverabl
});
}
if lower.contains(" is empty") {
if let Some((field, _)) = text.split_once(" is empty") {
if lower.contains(" is empty")
&& let Some((field, _)) = text.split_once(" is empty") {
let field_trimmed = field.trim().trim_matches('`');
if !field_trimmed.is_empty() {
let summary = format!("`{field_trimmed}` was empty");
@@ -2836,7 +2823,6 @@ fn classify_recoverable_decision_error(err: &anyhow::Error) -> Option<Recoverabl
});
}
}
}
if lower.contains("unexpected finish_status") {
let extracted = text
@@ -2958,10 +2944,10 @@ fn convert_decision_new(
let legacy_past = clean_optional(progress.past);
let legacy_current = clean_optional(progress.current);
if status_title.is_none() {
status_title = legacy_current.clone();
status_title = legacy_current;
}
if status_sent_to_user.is_none() {
status_sent_to_user = legacy_past.clone();
status_sent_to_user = legacy_past;
}
}
@@ -3075,7 +3061,7 @@ fn convert_decision_legacy(
let cli = match (status, cli_prompt) {
(AutoCoordinatorStatus::Continue, Some(prompt)) => Some(CliAction {
prompt: clean_required(&prompt, "cli_prompt")?,
context: context.clone(),
context: context,
suppress_ui_context: false,
}),
(AutoCoordinatorStatus::Continue, None) => {
@@ -3085,7 +3071,7 @@ fn convert_decision_legacy(
}
(_, Some(prompt)) => Some(CliAction {
prompt: clean_required(&prompt, "cli_prompt")?,
context: context.clone(),
context: context,
suppress_ui_context: false,
}),
(_, None) => None,
@@ -3265,12 +3251,11 @@ pub(crate) fn make_message(role: &str, text: String) -> ResponseItem {
fn strip_role_prefix(input: &str) -> &str {
const PREFIXES: [&str; 2] = ["Coordinator:", "CLI:"];
for prefix in PREFIXES {
if let Some(head) = input.get(..prefix.len()) {
if head.eq_ignore_ascii_case(prefix) {
if let Some(head) = input.get(..prefix.len())
&& head.eq_ignore_ascii_case(prefix) {
let rest = input.get(prefix.len()..).unwrap_or_default();
return rest.strip_prefix(' ').unwrap_or(rest);
}
}
}
input
}
@@ -3433,11 +3418,11 @@ pub fn should_compact(
let token_limit = family
.auto_compact_token_limit()
.and_then(|limit| (limit > 0).then(|| limit as u64))
.and_then(|limit| (limit > 0).then_some(limit as u64))
.or(family.context_window);
if let Some(token_limit) = token_limit {
if token_limit > 0 {
if let Some(token_limit) = token_limit
&& token_limit > 0 {
let threshold = (token_limit as f64 * 0.8) as u64;
let projected_total = transcript_tokens.saturating_add(estimated_next);
if projected_total >= threshold {
@@ -3450,7 +3435,6 @@ pub fn should_compact(
// token-safe transcript.
return false;
}
}
if has_recorded_turns {
return false;
@@ -24,6 +24,12 @@ pub struct AutoDriveHistory {
session_metrics: SessionMetrics,
}
impl Default for AutoDriveHistory {
fn default() -> Self {
Self::new()
}
}
impl AutoDriveHistory {
pub fn new() -> Self {
Self {
@@ -269,7 +275,7 @@ impl AutoDriveHistory {
id: None,
role: "user".to_string(),
content: vec![ContentItem::InputText {
text: format!("<compact_summary>\n{}\n</compact_summary>", summary_text),
text: format!("<compact_summary>\n{summary_text}\n</compact_summary>"),
}],
};
@@ -455,7 +455,7 @@ impl AutoDriveController {
}
pub fn transition(&mut self, transition: PhaseTransition) -> TransitionEffects {
let old_phase = self.phase.clone();
let old_phase = self.phase;
let effects = Vec::new();
match (&self.phase, &transition) {
@@ -695,7 +695,7 @@ impl AutoDriveController {
"Waiting for connection… retrying in {human_delay} (attempt {pending_attempt})"
));
self.current_display_is_summary = true;
self.current_status_title = Some(format!("Retrying after error"));
self.current_status_title = Some("Retrying after error".to_string());
self.current_status_sent_to_user = Some(format!(
"Encountered an error: {truncated_reason}. Waiting before retrying."
));
@@ -31,6 +31,12 @@ impl Default for DiagnosticsConfig {
/// Placeholder diagnostics facade.
pub struct AutoDriveDiagnostics;
impl Default for AutoDriveDiagnostics {
fn default() -> Self {
Self::new()
}
}
impl AutoDriveDiagnostics {
pub fn new() -> Self {
Self
+4 -5
View File
@@ -166,11 +166,10 @@ impl AccountUsageData {
}
fn apply_plan(&mut self, plan: Option<&str>) {
if let Some(plan) = plan {
if self.plan.as_deref() != Some(plan) {
if let Some(plan) = plan
&& self.plan.as_deref() != Some(plan) {
self.plan = Some(plan.to_string());
}
}
}
fn update_last_hour(&mut self, now: DateTime<Utc>) {
@@ -202,7 +201,7 @@ impl AccountUsageData {
let bucket = truncate_to_hour(entry.timestamp);
rollover
.entry(bucket)
.or_insert_with(TokenTotals::default)
.or_default()
.add_totals(&entry.tokens);
}
}
@@ -253,7 +252,7 @@ impl AccountUsageData {
let month_key = truncate_to_month(entry.period_start);
monthly_rollover
.entry(month_key)
.or_insert_with(TokenTotals::default)
.or_default()
.add_totals(&entry.tokens);
} else {
remaining.push(entry);
+5 -5
View File
@@ -62,7 +62,7 @@ impl<'a> AcpFileSystem<'a> {
.await?;
if is_error.unwrap_or_default() {
anyhow::bail!("Error reading text file: {:?}", structured_content);
anyhow::bail!("Error reading text file: {structured_content:?}");
}
let output = serde_json::from_value::<acp::ReadTextFileResponse>(
@@ -100,7 +100,7 @@ impl<'a> AcpFileSystem<'a> {
.await?;
if is_error.unwrap_or_default() {
anyhow::bail!("Error writing text file: {:?}", structured_content);
anyhow::bail!("Error writing text file: {structured_content:?}");
}
Ok(())
@@ -112,7 +112,7 @@ impl<'a> FileSystem for AcpFileSystem<'a> {
if let Some(tool) = self.tools.read_text_file.as_ref() {
self.read_text_file_impl(tool, path)
.await
.map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err))
.map_err(std::io::Error::other)
} else {
StdFileSystem.read_text_file(path).await
}
@@ -122,7 +122,7 @@ impl<'a> FileSystem for AcpFileSystem<'a> {
if let Some(tool) = self.tools.write_text_file.as_ref() {
self.write_text_file_impl(tool, path, contents)
.await
.map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err))
.map_err(std::io::Error::other)
} else {
StdFileSystem.write_text_file(path, contents).await
}
@@ -189,7 +189,7 @@ pub(crate) async fn request_permission(
} else if option_id == deny_id {
ReviewDecision::Denied
} else {
anyhow::bail!("Unexpected permission option: {}", option_id);
anyhow::bail!("Unexpected permission option: {option_id}");
}
}
Cancelled => ReviewDecision::Abort,
+4 -5
View File
@@ -81,11 +81,10 @@ impl AgentModelSpec {
if self.enabled_by_default {
return true;
}
if let Some(env) = self.gating_env {
if let Ok(value) = std::env::var(env) {
if let Some(env) = self.gating_env
&& let Ok(value) = std::env::var(env) {
return matches!(value.as_str(), "1" | "true" | "TRUE" | "True");
}
}
false
}
@@ -309,7 +308,7 @@ fn model_guide_line(spec: &AgentModelSpec) -> String {
}
fn custom_model_guide_line(name: &str, description: &str) -> String {
format!("- `{}`: {}", name, description)
format!("- `{name}`: {description}")
}
pub fn build_model_guide_description(active_agents: &[String]) -> String {
@@ -403,7 +402,7 @@ pub fn model_guide_markdown_with_custom(configured_agents: &[AgentConfig]) -> Op
pub fn default_agent_configs() -> Vec<AgentConfig> {
enabled_agent_model_specs()
.into_iter()
.map(|spec| agent_config_from_spec(spec))
.map(agent_config_from_spec)
.collect()
}
+69 -81
View File
@@ -119,7 +119,7 @@ fn current_code_binary_path() -> Result<std::path::PathBuf, String> {
return Ok(p);
}
let exe = std::env::current_exe()
.map_err(|e| format!("Failed to resolve current executable: {}", e))?;
.map_err(|e| format!("Failed to resolve current executable: {e}"))?;
// If the kernel reports the path as "(deleted)", strip the suffix and prefer the live file
// at the same location (common when a rebuild replaces the inode under a long-running process).
@@ -197,7 +197,7 @@ fn find_repo_root(start: std::path::PathBuf) -> Option<std::path::PathBuf> {
/// Format a helpful error message when an agent command is not found.
/// Provides platform-specific guidance for resolving PATH issues.
fn format_agent_not_found_error(agent_name: &str, command: &str) -> String {
let mut msg = format!("Agent '{}' could not be found.", agent_name);
let mut msg = format!("Agent '{agent_name}' could not be found.");
#[cfg(target_os = "windows")]
{
@@ -219,14 +219,13 @@ fn format_agent_not_found_error(agent_name: &str, command: &str) -> String {
{
msg.push_str(&format!(
"\n\nTroubleshooting steps:\n\
1. Check if '{}' is installed: which {}\n\
2. Verify '{}' is in your PATH: echo $PATH\n\
1. Check if '{command}' is installed: which {command}\n\
2. Verify '{command}' is in your PATH: echo $PATH\n\
3. Try using an absolute path in your config.toml:\n\
[[agents]]\n\
name = \"{}\"\n\
command = \"/absolute/path/to/{}\"\n\n\
For more information, see: https://github.com/just-every/code/blob/main/code-rs/config.md",
command, command, command, agent_name, command
name = \"{agent_name}\"\n\
command = \"/absolute/path/to/{command}\"\n\n\
For more information, see: https://github.com/just-every/code/blob/main/code-rs/config.md"
));
}
@@ -301,6 +300,12 @@ pub struct AgentStatusUpdatePayload {
pub task: Option<String>,
}
impl Default for AgentManager {
fn default() -> Self {
Self::new()
}
}
impl AgentManager {
pub fn new() -> Self {
Self {
@@ -401,7 +406,7 @@ impl AgentManager {
let file = dir.join("progress.log");
match OpenOptions::new().create(true).append(true).open(&file) {
Ok(mut fh) => {
if let Err(err) = writeln!(fh, "{}", line) {
if let Err(err) = writeln!(fh, "{line}") {
warn!("failed to write agent log {:?}: {}", file, err);
}
}
@@ -418,9 +423,7 @@ impl AgentManager {
.map(|agent| {
// Just show the model name - status provides the useful info
let name = agent
.name
.as_ref()
.map(|value| value.clone())
.name.clone()
.unwrap_or_else(|| agent.model.clone());
let start = agent.started_at.unwrap_or(agent.created_at);
let end = agent.completed_at.unwrap_or(now);
@@ -605,7 +608,7 @@ impl AgentManager {
let agent_id = Uuid::new_v4().to_string();
let log_tag = match source_kind {
Some(AgentSourceKind::AutoReview) => Some(format!("agents/auto-review/{}", agent_id)),
Some(AgentSourceKind::AutoReview) => Some(format!("agents/auto-review/{agent_id}")),
_ => None,
};
@@ -675,21 +678,18 @@ impl AgentManager {
self.agents
.values()
.filter(|agent| {
if let Some(ref filter) = status_filter {
if agent.status != *filter {
if let Some(ref filter) = status_filter
&& agent.status != *filter {
return false;
}
}
if let Some(ref batch) = batch_id {
if agent.batch_id.as_ref() != Some(batch) {
if let Some(ref batch) = batch_id
&& agent.batch_id.as_ref() != Some(batch) {
return false;
}
}
if let Some(cutoff) = cutoff {
if agent.created_at < cutoff {
if let Some(cutoff) = cutoff
&& agent.created_at < cutoff {
return false;
}
}
true
})
.cloned()
@@ -839,10 +839,10 @@ impl AgentManager {
async fn get_git_root() -> Result<PathBuf, String> {
let output = Command::new("git")
.args(&["rev-parse", "--show-toplevel"])
.args(["rev-parse", "--show-toplevel"])
.output()
.await
.map_err(|e| format!("Git not installed or not in a git repository: {}", e))?;
.map_err(|e| format!("Git not installed or not in a git repository: {e}"))?;
if output.status.success() {
let path = String::from_utf8_lossy(&output.stdout).trim().to_string();
@@ -887,7 +887,7 @@ fn generate_branch_id(model: &str, agent: &str) -> String {
}
}
format!("code-{}-{}", model_s, suffix_s)
format!("code-{model_s}-{suffix_s}")
}
use crate::git_worktree::setup_worktree;
@@ -928,13 +928,11 @@ async fn execute_agent(agent_id: String, config: Option<AgentConfig>) {
// Build the full prompt with context
let mut full_prompt = prompt.clone();
// Prepend any per-agent instructions from config when available
if let Some(cfg) = config.as_ref() {
if let Some(instr) = cfg.instructions.as_ref() {
if !instr.trim().is_empty() {
if let Some(cfg) = config.as_ref()
&& let Some(instr) = cfg.instructions.as_ref()
&& !instr.trim().is_empty() {
full_prompt = format!("{}\n\n{}", instr.trim(), full_prompt);
}
}
}
if let Some(context) = &context {
let trimmed = full_prompt.trim_start();
if trimmed.starts_with('/') {
@@ -945,7 +943,7 @@ async fn execute_agent(agent_id: String, config: Option<AgentConfig>) {
}
}
if let Some(output_goal) = &output_goal {
full_prompt = format!("{}\n\nDesired output: {}", full_prompt, output_goal);
full_prompt = format!("{full_prompt}\n\nDesired output: {output_goal}");
}
if !files.is_empty() {
full_prompt = format!("{}\n\nFiles to consider: {}", full_prompt, files.join(", "));
@@ -977,7 +975,7 @@ async fn execute_agent(agent_id: String, config: Option<AgentConfig>) {
let mut manager = AGENT_MANAGER.write().await;
manager
.add_progress(&agent_id, format!("Creating git worktree: {}", branch_id))
.add_progress(&agent_id, format!("Creating git worktree: {branch_id}"))
.await;
drop(manager);
@@ -1003,7 +1001,7 @@ async fn execute_agent(agent_id: String, config: Option<AgentConfig>) {
let review_output_json_path: Option<PathBuf> =
agent.source_kind.as_ref().and_then(|kind| {
matches!(kind, AgentSourceKind::AutoReview).then(|| {
let filename = format!("{}.review-output.json", agent_id);
let filename = format!("{agent_id}.review-output.json");
std::env::temp_dir().join(filename)
})
});
@@ -1055,16 +1053,15 @@ async fn execute_agent(agent_id: String, config: Option<AgentConfig>) {
.await
}
}
Err(e) => Err(format!("Failed to setup worktree: {}", e)),
Err(e) => Err(format!("Failed to setup worktree: {e}")),
}
}
Err(e) => Err(format!("Git is required for non-read-only agents: {}", e)),
Err(e) => Err(format!("Git is required for non-read-only agents: {e}")),
}
} else {
// Execute in read-only mode
full_prompt = format!(
"{}\n\n[Running in read-only mode - no modifications allowed]",
full_prompt
"{full_prompt}\n\n[Running in read-only mode - no modifications allowed]"
);
let use_built_in_cloud = config.is_none()
&& model_spec
@@ -1122,11 +1119,10 @@ fn prefer_json_result(
path: Option<&PathBuf>,
fallback: Result<String, String>,
) -> Result<String, String> {
if let Some(p) = path {
if let Ok(json) = std::fs::read_to_string(p) {
if let Some(p) = path
&& let Ok(json) = std::fs::read_to_string(p) {
return Ok(json);
}
}
fallback
}
@@ -1213,14 +1209,13 @@ async fn execute_model_with_permissions(
continue;
}
let candidate = dir.join(cmd);
if let Ok(meta) = std::fs::metadata(&candidate) {
if meta.is_file() {
if let Ok(meta) = std::fs::metadata(&candidate)
&& meta.is_file() {
let mode = meta.permissions().mode();
if mode & 0o111 != 0 {
return true;
}
}
}
}
false
}
@@ -1234,8 +1229,8 @@ async fn execute_model_with_permissions(
.and_then(|cfg| agent_model_spec(&cfg.command))
});
if let Some(spec) = spec_opt {
if !spec.is_enabled() {
if let Some(spec) = spec_opt
&& !spec.is_enabled() {
if let Some(flag) = spec.gating_env {
return Err(format!(
"agent model '{}' is disabled; set {}=1 to enable it",
@@ -1244,7 +1239,6 @@ async fn execute_model_with_permissions(
}
return Err(format!("agent model '{}' is disabled", spec.slug));
}
}
// Use config command if provided, otherwise fall back to the spec CLI (or the
// lowercase model string).
@@ -1466,13 +1460,12 @@ async fn execute_model_with_permissions(
// Build env from current process then overlay any config-provided vars.
let mut env: std::collections::HashMap<String, String> = std::env::vars().collect();
let orig_home: Option<String> = env.get("HOME").cloned();
if let Some(ref cfg) = config {
if let Some(ref e) = cfg.env {
if let Some(ref cfg) = config
&& let Some(ref e) = cfg.env {
for (k, v) in e {
env.insert(k.clone(), v.clone());
}
}
}
if debug_subagent {
env.entry("CODE_SUBAGENT_DEBUG".to_string())
@@ -1571,7 +1564,7 @@ async fn execute_model_with_permissions(
if e.kind() == std::io::ErrorKind::NotFound {
return Err(format_agent_not_found_error(&command, &command_for_spawn));
}
return Err(format!("Failed to spawn sandboxed agent: {}", e));
return Err(format!("Failed to spawn sandboxed agent: {e}"));
}
}
} else {
@@ -1602,7 +1595,7 @@ async fn execute_model_with_permissions(
return Err(format_agent_not_found_error(&command, &command_for_spawn));
}
return Err(format!("Failed to execute {}: {}", model, e));
return Err(format!("Failed to execute {model}: {e}"));
}
}
};
@@ -1619,9 +1612,9 @@ async fn execute_model_with_permissions(
} else if stdout.is_empty() {
stderr.to_string()
} else {
format!("{}\n{}", stderr, stdout)
format!("{stderr}\n{stdout}")
};
Err(format!("Command failed: {}", combined))
Err(format!("Command failed: {combined}"))
}
}
@@ -1770,11 +1763,10 @@ pub(crate) fn should_use_current_exe_for_agent(
}
// If the configured command matches the canonical CLI for this spec, prefer self.
if let Some(spec) = agent_model_spec(&cfg.name).or_else(|| agent_model_spec(trimmed)) {
if trimmed.eq_ignore_ascii_case(spec.cli) {
if let Some(spec) = agent_model_spec(&cfg.name).or_else(|| agent_model_spec(trimmed))
&& trimmed.eq_ignore_ascii_case(spec.cli) {
return true;
}
}
// Otherwise assume the user intentionally set a custom command; do not override.
false
@@ -1826,13 +1818,12 @@ pub fn split_command_and_args(command: &str) -> (String, Vec<String>) {
if trimmed.is_empty() {
return (String::new(), Vec::new());
}
if let Some(tokens) = shlex_split(trimmed) {
if let Some((first, rest)) = tokens.split_first() {
if let Some(tokens) = shlex_split(trimmed)
&& let Some((first, rest)) = tokens.split_first() {
return (first.clone(), rest.to_vec());
}
}
let tokens: Vec<String> = trimmed.split_whitespace().map(|s| s.to_string()).collect();
let tokens: Vec<String> = trimmed.split_whitespace().map(std::string::ToString::to_string).collect();
if tokens.is_empty() {
(String::new(), Vec::new())
} else {
@@ -1902,7 +1893,7 @@ fn summarize_agent_output(output: &str) -> String {
// Fallback: take first char to avoid empty slice
let mut chars = trimmed.chars();
if let Some(first) = chars.next() {
format!("{}…", first)
format!("{first}…")
} else {
"…".to_string()
}
@@ -1929,7 +1920,7 @@ fn run_smoke_test_with_new_runtime(cfg: AgentConfig) -> Result<(), String> {
TokioRuntimeBuilder::new_current_thread()
.enable_all()
.build()
.map_err(|e| format!("failed to build validation runtime: {}", e))?
.map_err(|e| format!("failed to build validation runtime: {e}"))?
.block_on(smoke_test_agent(cfg))
}
@@ -1938,7 +1929,7 @@ pub fn smoke_test_agent_blocking(cfg: AgentConfig) -> Result<(), String> {
thread::Builder::new()
.name("agent-smoke-test".into())
.spawn(move || run_smoke_test_with_new_runtime(cfg))
.map_err(|e| format!("failed to spawn agent validation thread: {}", e))?
.map_err(|e| format!("failed to spawn agent validation thread: {e}"))?
.join()
.map_err(|_| "agent validation thread panicked".to_string())?
} else {
@@ -1981,7 +1972,7 @@ async fn execute_cloud_built_in_streaming(
env,
)
.await
.map_err(|e| format!("Failed to spawn cloud submit: {}", e))?;
.map_err(|e| format!("Failed to spawn cloud submit: {e}"))?;
// Stream stderr to HUD
let stderr_task = if let Some(stderr) = child.stderr.take() {
@@ -2014,12 +2005,12 @@ async fn execute_cloud_built_in_streaming(
let status = child
.wait()
.await
.map_err(|e| format!("Failed to wait: {}", e))?;
.map_err(|e| format!("Failed to wait: {e}"))?;
if let Some(t) = stderr_task {
let _ = t.await;
}
if !status.success() {
return Err(format!("cloud submit exited with status {}", status));
return Err(format!("cloud submit exited with status {status}"));
}
if let Some(dir) = working_dir.as_ref() {
@@ -2031,8 +2022,8 @@ async fn execute_cloud_built_in_streaming(
.map(|idx| stdout_buf[idx + 1..].trim())
};
if let Some(diff_text) = diff_text_opt {
if !diff_text.is_empty() {
if let Some(diff_text) = diff_text_opt
&& !diff_text.is_empty() {
let mut apply = Command::new("git");
apply.arg("apply").arg("--whitespace=nowarn");
apply.current_dir(dir);
@@ -2040,28 +2031,26 @@ async fn execute_cloud_built_in_streaming(
let mut child = spawn_tokio_command_with_retry(&mut apply)
.await
.map_err(|e| format!("Failed to spawn git apply: {}", e))?;
.map_err(|e| format!("Failed to spawn git apply: {e}"))?;
if let Some(mut stdin) = child.stdin.take() {
stdin
.write_all(diff_text.as_bytes())
.await
.map_err(|e| format!("Failed to write diff to git apply: {}", e))?;
.map_err(|e| format!("Failed to write diff to git apply: {e}"))?;
}
let status = child
.wait()
.await
.map_err(|e| format!("Failed to wait for git apply: {}", e))?;
.map_err(|e| format!("Failed to wait for git apply: {e}"))?;
if !status.success() {
return Err(format!(
"git apply exited with status {} while applying cloud diff",
status
"git apply exited with status {status} while applying cloud diff"
));
}
}
}
}
// Truncate large outputs
@@ -2070,7 +2059,7 @@ async fn execute_cloud_built_in_streaming(
let omitted = stdout_buf.len() - MAX_BYTES;
let mut truncated = String::with_capacity(MAX_BYTES + 128);
truncated.push_str(&stdout_buf[..MAX_BYTES]);
truncated.push_str(&format!("\n… [truncated: {} bytes omitted]", omitted));
truncated.push_str(&format!("\n… [truncated: {omitted} bytes omitted]"));
Ok(truncated)
} else {
Ok(stdout_buf)
@@ -2092,7 +2081,7 @@ pub fn create_agent_tool(allowed_models: &[String]) -> OpenAiTool {
allowed_values: Some(
["create", "status", "wait", "result", "cancel", "list"]
.into_iter()
.map(|value| value.to_string())
.map(std::string::ToString::to_string)
.collect(),
),
},
@@ -2130,7 +2119,7 @@ pub fn create_agent_tool(allowed_models: &[String]) -> OpenAiTool {
allowed_values: if allowed_models.is_empty() {
None
} else {
Some(allowed_models.iter().cloned().collect())
Some(allowed_models.to_vec())
},
}),
description: Some(
@@ -2439,19 +2428,18 @@ fn canonicalize_agent_word_boundaries(input: &str) -> String {
let next_char = chars.peek().copied();
let mut split = false;
if !current.is_empty() {
if let Some(prev) = prev_char {
if !current.is_empty()
&& let Some(prev) = prev_char {
if prev.is_ascii_lowercase() && ch.is_ascii_uppercase() {
split = true;
} else if prev.is_ascii_uppercase()
&& ch.is_ascii_uppercase()
&& uppercase_run > 0
&& next_char.map_or(false, |c| c.is_ascii_lowercase())
&& next_char.is_some_and(|c| c.is_ascii_lowercase())
{
split = true;
}
}
}
if split {
tokens.push(std::mem::take(&mut current));
+8 -11
View File
@@ -48,9 +48,9 @@ pub(crate) async fn apply_patch(
let mut status_message: Option<String> = None;
let validation_cfg = sess.validation_config();
let github_cfg = sess.get_github_config();
if let (Ok(validation_cfg), Ok(github_cfg)) = (validation_cfg.read(), github_cfg.read()) {
if let Some((mut findings, mut ran_checks)) =
run_patch_harness(&action, sess.get_cwd(), &*validation_cfg, &*github_cfg)
if let (Ok(validation_cfg), Ok(github_cfg)) = (validation_cfg.read(), github_cfg.read())
&& let Some((mut findings, mut ran_checks)) =
run_patch_harness(&action, sess.get_cwd(), &validation_cfg, &github_cfg)
{
const MAX_ISSUES: usize = 12;
let total_issues = findings.len();
@@ -106,7 +106,7 @@ pub(crate) async fn apply_patch(
let mut msg = finding.message.clone();
if msg.len() > 160 {
msg.truncate(157);
msg.push_str("…");
msg.push('…');
}
parts.push(msg);
lines.push(format!("• {}", parts.join(" — ")));
@@ -124,7 +124,6 @@ pub(crate) async fn apply_patch(
}
status_message = Some(lines.join("\n"));
}
}
(summary_json, status_message)
};
@@ -270,13 +269,12 @@ async fn apply_changes_from_apply_patch(
for (path, change) in action.changes() {
match change {
ApplyPatchFileChange::Add { content } => {
if let Some(parent) = path.parent() {
if !parent.as_os_str().is_empty() {
if let Some(parent) = path.parent()
&& !parent.as_os_str().is_empty() {
std::fs::create_dir_all(parent).with_context(|| {
format!("Failed to create parent directories for {}", path.display())
})?;
}
}
fs.write_text_file(path, content.clone())
.await
.with_context(|| format!("Failed to write file {}", path.display()))?;
@@ -293,8 +291,8 @@ async fn apply_changes_from_apply_patch(
..
} => {
if let Some(move_path) = move_path {
if let Some(parent) = move_path.parent() {
if !parent.as_os_str().is_empty() {
if let Some(parent) = move_path.parent()
&& !parent.as_os_str().is_empty() {
std::fs::create_dir_all(parent).with_context(|| {
format!(
"Failed to create parent directories for {}",
@@ -302,7 +300,6 @@ async fn apply_changes_from_apply_patch(
)
})?;
}
}
std::fs::rename(path, move_path)
.with_context(|| format!("Failed to rename file {}", path.display()))?;
+20 -28
View File
@@ -100,13 +100,12 @@ impl CodexAuth {
return self.persist_refresh_response(refresh_response).await;
}
Err(err) => {
if err.is_refresh_token_reused() {
if let Some(access) =
if err.is_refresh_token_reused()
&& let Some(access) =
self.adopt_rotated_refresh_token_from_disk(&refresh_token)?
{
return Ok(access);
}
}
if err.kind == RefreshTokenErrorKind::Transient && attempt < 4 {
let delay = backoff(attempt as u64);
tokio::time::sleep(delay).await;
@@ -195,7 +194,7 @@ impl CodexAuth {
.map_err(|_| {
std::io::Error::other("timed out while refreshing OpenAI API key")
})?
.map_err(|err| std::io::Error::other(err))?;
.map_err(std::io::Error::other)?;
let updated_auth_dot_json = update_tokens(
&self.auth_file,
@@ -235,12 +234,12 @@ impl CodexAuth {
pub fn get_account_id(&self) -> Option<String> {
self.get_current_token_data()
.and_then(|t| t.account_id.clone())
.and_then(|t| t.account_id)
}
pub fn get_plan_type(&self) -> Option<String> {
self.get_current_token_data()
.and_then(|t| t.id_token.chatgpt_plan_type.as_ref().map(|p| p.as_string()))
.and_then(|t| t.id_token.chatgpt_plan_type.as_ref().map(super::token_data::PlanType::as_string))
}
fn get_current_auth_json(&self) -> Option<AuthDotJson> {
@@ -249,7 +248,7 @@ impl CodexAuth {
}
fn get_current_token_data(&self) -> Option<TokenData> {
self.get_current_auth_json().and_then(|t| t.tokens.clone())
self.get_current_auth_json().and_then(|t| t.tokens)
}
/// Consider this private to integration tests.
@@ -397,19 +396,16 @@ pub async fn auth_for_stored_account(
let refresh_response = match refresh_response {
Ok(response) => response,
Err(err) => {
if err.is_refresh_token_reused() {
if let Ok(Some(updated)) =
if err.is_refresh_token_reused()
&& let Ok(Some(updated)) =
crate::auth_accounts::find_account(code_home, &account.id)
{
if let Some(updated_tokens) = updated.tokens {
&& let Some(updated_tokens) = updated.tokens {
return Ok(CodexAuth::from_tokens_with_originator(
updated_tokens,
updated.last_refresh,
originator,
));
}
}
}
return Err(std::io::Error::other(err));
}
};
@@ -453,7 +449,7 @@ pub fn activate_account(code_home: &Path, account_id: &str) -> std::io::Result<(
let account_id_owned = account.id.clone();
match account.mode {
AuthMode::ApiKey => {
let api_key = account.openai_api_key.clone().ok_or_else(|| {
let api_key = account.openai_api_key.ok_or_else(|| {
std::io::Error::other("stored API key account is missing the key value")
})?;
let auth = AuthDotJson {
@@ -600,16 +596,16 @@ async fn update_tokens(
let tokens = auth_dot_json.tokens.get_or_insert_with(TokenData::default);
tokens.id_token = parse_id_token(&id_token).map_err(std::io::Error::other)?;
if let Some(access_token) = access_token {
tokens.access_token = access_token.to_string();
tokens.access_token = access_token;
}
if let Some(refresh_token) = refresh_token {
tokens.refresh_token = refresh_token.to_string();
tokens.refresh_token = refresh_token;
}
auth_dot_json.last_refresh = Some(Utc::now());
write_auth_json(auth_file, &auth_dot_json)?;
if let Some(code_home) = auth_file.parent() {
if let Some(tokens) = auth_dot_json.tokens.clone() {
if let Some(code_home) = auth_file.parent()
&& let Some(tokens) = auth_dot_json.tokens.clone() {
let last_refresh = auth_dot_json.last_refresh.unwrap_or_else(Utc::now);
let email = tokens.id_token.email.clone();
let _ = crate::auth_accounts::upsert_chatgpt_account(
@@ -620,7 +616,6 @@ async fn update_tokens(
true,
)?;
}
}
Ok(auth_dot_json)
}
@@ -693,19 +688,17 @@ struct OpenAiErrorData {
}
fn classify_refresh_failure(status: StatusCode, body: &str) -> RefreshTokenError {
if let Ok(parsed) = serde_json::from_str::<OpenAiErrorWrapper>(body) {
if let Some(error) = parsed.error {
if error.code.as_deref() == Some("refresh_token_reused") {
if let Ok(parsed) = serde_json::from_str::<OpenAiErrorWrapper>(body)
&& let Some(error) = parsed.error
&& error.code.as_deref() == Some("refresh_token_reused") {
let message = error
.message
.unwrap_or_else(|| "refresh token already rotated".to_string());
return RefreshTokenError::transient(format!("refresh_token_reused: {message}"));
}
}
}
if let Ok(parsed) = serde_json::from_str::<OAuthErrorBody>(body) {
if let Some(code) = parsed.error.as_deref() {
if let Ok(parsed) = serde_json::from_str::<OAuthErrorBody>(body)
&& let Some(code) = parsed.error.as_deref() {
let description = parsed.error_description.as_deref().unwrap_or(code).trim();
let formatted = format!("OAuth error ({code}): {description}");
match code {
@@ -728,7 +721,6 @@ fn classify_refresh_failure(status: StatusCode, body: &str) -> RefreshTokenError
}
}
}
}
if status == StatusCode::FORBIDDEN || status == StatusCode::UNAUTHORIZED {
return RefreshTokenError::permanent(format!(
@@ -1357,7 +1349,7 @@ impl AuthManager {
pub async fn refresh_token(&self) -> std::io::Result<Option<String>> {
self.refresh_token_classified()
.await
.map_err(|err| std::io::Error::other(err))
.map_err(std::io::Error::other)
}
/// Log out by deleting the on‑disk auth.json (if present). Returns Ok(true)
+2 -3
View File
@@ -84,11 +84,10 @@ fn read_accounts_file(path: &Path) -> io::Result<AccountsFile> {
}
fn write_accounts_file(path: &Path, data: &AccountsFile) -> io::Result<()> {
if let Some(parent) = path.parent() {
if !parent.exists() {
if let Some(parent) = path.parent()
&& !parent.exists() {
std::fs::create_dir_all(parent)?;
}
}
let json = serde_json::to_string_pretty(data)?;
let mut options = OpenOptions::new();
+15 -21
View File
@@ -159,11 +159,11 @@ fn parse_level(raw: &str) -> Option<String> {
serde_json::from_str::<Value>(raw).ok().and_then(|val| {
val.get("level")
.and_then(|v| v.as_str())
.map(|s| s.to_lowercase())
.map(str::to_lowercase)
.or_else(|| {
val.get("type")
.and_then(|v| v.as_str())
.map(|s| s.to_lowercase())
.map(str::to_lowercase)
})
})
}
@@ -181,7 +181,7 @@ fn truncate_summary(text: &str) -> (String, bool) {
if text.chars().count() > MAX_EVENT_SUMMARY_CHARS {
let remaining = text.chars().count().saturating_sub(MAX_EVENT_SUMMARY_CHARS);
(
format!("{}... [truncated {remaining} chars]", truncated),
format!("{truncated}... [truncated {remaining} chars]"),
true,
)
} else {
@@ -227,11 +227,10 @@ fn coalesce_events(events: Vec<BridgeBatchEvent>) -> CoalescedBatch {
truncated_events += 1;
}
if let Some(level) = level.as_deref() {
if is_error_level(level) {
if let Some(level) = level.as_deref()
&& is_error_level(level) {
saw_error = true;
}
}
if let Some((_, count)) = entries.iter_mut().find(|(msg, _)| msg == &summary) {
*count += 1;
@@ -280,7 +279,7 @@ fn format_batch_message(batch: &CoalescedBatch) -> String {
String::new()
};
let indented = msg.replace('\n', "\n ");
lines.push(format!("- {}{}", prefix, indented));
lines.push(format!("- {prefix}{indented}"));
}
if batch.dropped_events > 0 {
@@ -543,14 +542,12 @@ pub(crate) fn persist_workspace_subscription(
let payload = serde_json::to_string_pretty(&SubscriptionOverride {
levels: sub.levels.clone(),
capabilities: sub.capabilities.clone(),
llm_filter: sub.llm_filter.clone(),
llm_filter: sub.llm_filter,
})?;
fs::write(&tmp, payload)?;
fs::rename(tmp, &path)?;
} else {
if path.exists() {
fs::remove_file(&path)?;
}
} else if path.exists() {
fs::remove_file(&path)?;
}
Ok(())
@@ -592,14 +589,13 @@ fn find_meta_path(start: &Path) -> Option<PathBuf> {
}
fn subscription_override_path(start: &Path) -> Option<PathBuf> {
if let Some(meta) = find_meta_path(start) {
if let Some(dir) = meta.parent() {
if let Some(meta) = find_meta_path(start)
&& let Some(dir) = meta.parent() {
let candidate = dir.join(SUBSCRIPTION_OVERRIDE_FILE);
if candidate.exists() {
return Some(candidate);
}
}
}
let mut current = Some(start);
while let Some(dir) = current {
@@ -689,21 +685,19 @@ fn workspace_has_code_bridge(start: &Path) -> bool {
}
fn is_meta_stale(meta: &BridgeMeta, path: &Path) -> bool {
if let Some(hb) = &meta.heartbeat_at {
if let Ok(ts) = DateTime::parse_from_rfc3339(hb) {
if let Some(hb) = &meta.heartbeat_at
&& let Ok(ts) = DateTime::parse_from_rfc3339(hb) {
let age = Utc::now().signed_duration_since(ts.with_timezone(&Utc));
return age.num_milliseconds() > HEARTBEAT_STALE_MS;
}
}
// Fallback for hosts that don't emit heartbeat: use file mtime as staleness signal
if let Ok(stat) = std::fs::metadata(path) {
if let Ok(modified) = stat.modified() {
if let Ok(stat) = std::fs::metadata(path)
&& let Ok(modified) = stat.modified() {
let modified: DateTime<Utc> = modified.into();
let age = Utc::now().signed_duration_since(modified);
return age > ChronoDuration::milliseconds(HEARTBEAT_STALE_MS);
}
}
false
}
+29 -47
View File
@@ -93,22 +93,20 @@ pub(crate) async fn stream_chat_completions(
// Find the last user message index in the input.
let mut last_user_index: Option<usize> = None;
for (idx, item) in input.iter().enumerate() {
if let ResponseItem::Message { role, .. } = item {
if role == "user" {
if let ResponseItem::Message { role, .. } = item
&& role == "user" {
last_user_index = Some(idx);
}
}
}
// Attach reasoning only if the conversation does not end with a user message.
if !matches!(last_emitted_role, Some("user")) {
for (idx, item) in input.iter().enumerate() {
// Only consider reasoning that appears after the last user message.
if let Some(u_idx) = last_user_index {
if idx <= u_idx {
if let Some(u_idx) = last_user_index
&& idx <= u_idx {
continue;
}
}
if let ResponseItem::Reasoning {
content: Some(items),
@@ -128,17 +126,15 @@ pub(crate) async fn stream_chat_completions(
// Prefer immediate previous assistant message (stop turns)
let mut attached = false;
if idx > 0 {
if let ResponseItem::Message { role, .. } = &input[idx - 1] {
if role == "assistant" {
if idx > 0
&& let ResponseItem::Message { role, .. } = &input[idx - 1]
&& role == "assistant" {
reasoning_by_anchor_index
.entry(idx - 1)
.and_modify(|v| v.push_str(&text))
.or_insert(text.clone());
attached = true;
}
}
}
// Otherwise, attach to immediate next assistant anchor (tool-calls or assistant message)
if !attached && idx + 1 < input.len() {
@@ -297,8 +293,8 @@ pub(crate) async fn stream_chat_completions(
"tools": tools_json,
});
if let Some(openrouter_cfg) = provider.openrouter_config() {
if let Some(obj) = payload.as_object_mut() {
if let Some(openrouter_cfg) = provider.openrouter_config()
&& let Some(obj) = payload.as_object_mut() {
if let Some(provider_cfg) = &openrouter_cfg.provider {
obj.insert("provider".to_string(), serde_json::to_value(provider_cfg)?);
}
@@ -309,22 +305,19 @@ pub(crate) async fn stream_chat_completions(
obj.entry(key.clone()).or_insert(value.clone());
}
}
}
// If an Ollama context override is present, propagate it. Some Ollama
// builds honor `num_ctx` directly in OpenAI-compatible Chat Completions,
// and others accept it under an `options` object – include both.
if let Ok(val) = std::env::var("HANZO_OLLAMA_NUM_CTX") {
if let Ok(n) = val.parse::<u64>() {
if let Some(obj) = payload.as_object_mut() {
if let Ok(val) = std::env::var("HANZO_OLLAMA_NUM_CTX")
&& let Ok(n) = val.parse::<u64>()
&& let Some(obj) = payload.as_object_mut() {
obj.insert("num_ctx".to_string(), json!(n));
// Also set options.num_ctx for native-style compatibility.
let mut options = serde_json::Map::new();
options.insert("num_ctx".to_string(), json!(n));
obj.entry("options").or_insert(json!(options));
}
}
}
let endpoint = provider.get_full_url(&None);
debug!(
@@ -342,13 +335,11 @@ pub(crate) async fn stream_chat_completions(
let auth = auth_manager.as_ref().and_then(|m| m.auth());
let mut req_builder = provider.create_request_builder(client, &auth).await?;
if let Some(auth) = auth.as_ref() {
if auth.mode == AuthMode::ChatGPT {
if let Some(account_id) = auth.get_account_id() {
if let Some(auth) = auth.as_ref()
&& auth.mode == AuthMode::ChatGPT
&& let Some(account_id) = auth.get_account_id() {
req_builder = req_builder.header("chatgpt-account-id", account_id);
}
}
}
req_builder = req_builder
.header(reqwest::header::ACCEPT, "text/event-stream")
@@ -390,7 +381,7 @@ pub(crate) async fn stream_chat_completions(
}
let (tx_event, rx_event) = mpsc::channel::<Result<ResponseEvent>>(1600);
let stream = resp.bytes_stream().map_err(CodexErr::Reqwest);
let debug_logger_clone = Arc::clone(&debug_logger);
let debug_logger_clone = Arc::clone(debug_logger);
let request_id_clone = request_id.clone();
tokio::spawn(process_chat_sse(
stream,
@@ -574,7 +565,7 @@ async fn process_chat_sse<S>(
if !reasoning_text.is_empty() {
let item = ResponseItem::Reasoning {
id: current_item_id.clone().unwrap_or_else(String::new),
id: current_item_id.clone().unwrap_or_default(),
summary: Vec::new(),
content: Some(vec![ReasoningItemContent::ReasoningText {
text: std::mem::take(reasoning_text),
@@ -727,8 +718,7 @@ async fn process_chat_sse<S>(
.get("delta")
.and_then(|d| d.get("content"))
.and_then(|c| c.as_str())
{
if !content.is_empty() {
&& !content.is_empty() {
assistant_text.push_str(content);
let _ = tx_event
.send(Ok(ResponseEvent::OutputTextDelta {
@@ -739,7 +729,6 @@ async fn process_chat_sse<S>(
}))
.await;
}
}
// Forward any reasoning/thinking deltas if present.
// Some providers stream `reasoning` as a plain string while others
@@ -798,13 +787,12 @@ async fn process_chat_sse<S>(
}))
.await;
}
} else if let Some(obj) = message_reasoning.as_object() {
if let Some(s) = obj
} else if let Some(obj) = message_reasoning.as_object()
&& let Some(s) = obj
.get("text")
.and_then(|v| v.as_str())
.or_else(|| obj.get("content").and_then(|v| v.as_str()))
{
if !s.is_empty() {
&& !s.is_empty() {
reasoning_text.push_str(s);
let _ = tx_event
.send(Ok(ResponseEvent::ReasoningContentDelta {
@@ -816,8 +804,6 @@ async fn process_chat_sse<S>(
}))
.await;
}
}
}
}
// Handle streaming function / tool calls.
@@ -825,8 +811,7 @@ async fn process_chat_sse<S>(
.get("delta")
.and_then(|d| d.get("tool_calls"))
.and_then(|tc| tc.as_array())
{
if let Some(tool_call) = tool_calls.first() {
&& let Some(tool_call) = tool_calls.first() {
// Mark that we have an active function call in progress.
fn_call_state.active = true;
@@ -848,7 +833,6 @@ async fn process_chat_sse<S>(
}
}
}
}
// Emit end-of-turn when finish_reason signals completion.
if let Some(finish_reason) = choice.get("finish_reason").and_then(|v| v.as_str()) {
@@ -858,7 +842,7 @@ async fn process_chat_sse<S>(
// the reasoning stream before any exec/tool events begin.
if !reasoning_text.is_empty() {
let item = ResponseItem::Reasoning {
id: current_item_id.clone().unwrap_or_else(String::new),
id: current_item_id.clone().unwrap_or_default(),
summary: Vec::new(),
content: Some(vec![ReasoningItemContent::ReasoningText {
text: std::mem::take(&mut reasoning_text),
@@ -912,7 +896,7 @@ async fn process_chat_sse<S>(
// Also emit a terminal Reasoning item so UIs can finalize raw reasoning.
if !reasoning_text.is_empty() {
let item = ResponseItem::Reasoning {
id: current_item_id.clone().unwrap_or_else(String::new),
id: current_item_id.clone().unwrap_or_default(),
summary: Vec::new(),
content: Some(vec![ReasoningItemContent::ReasoningText {
text: std::mem::take(&mut reasoning_text),
@@ -1017,8 +1001,8 @@ where
// Only use the final assistant message if we have not
// seen any deltas; otherwise, deltas already built the
// cumulative text and this would duplicate it.
if this.cumulative.is_empty() {
if let ResponseItem::Message { content, id, .. } = &item {
if this.cumulative.is_empty()
&& let ResponseItem::Message { content, id, .. } = &item {
// Capture the item_id if present
if let Some(item_id) = id {
this.cumulative_item_id = Some(item_id.clone());
@@ -1030,15 +1014,13 @@ where
this.cumulative.push_str(text);
}
}
}
}
// Also capture item_id from Reasoning items
if let ResponseItem::Reasoning { id, .. } = &item {
if !id.is_empty() {
if let ResponseItem::Reasoning { id, .. } = &item
&& !id.is_empty() {
this.cumulative_item_id = Some(id.clone());
}
}
// Not an assistant message – forward immediately.
return Poll::Ready(Some(Ok(ResponseEvent::OutputItemDone {
@@ -1064,7 +1046,7 @@ where
&& matches!(this.mode, AggregateMode::AggregatedOnly)
{
let aggregated_reasoning = ResponseItem::Reasoning {
id: this.cumulative_item_id.clone().unwrap_or_else(String::new),
id: this.cumulative_item_id.clone().unwrap_or_default(),
summary: Vec::new(),
content: Some(vec![ReasoningItemContent::ReasoningText {
text: std::mem::take(&mut this.cumulative_reasoning),
+54 -72
View File
@@ -166,7 +166,7 @@ fn try_parse_retry_after(err: &Error, now: DateTime<Utc>) -> Option<RetryAfter>
fn is_quota_exceeded_error(error: &Error) -> bool {
matches!(
error.code.as_deref().or_else(|| error.r#type.as_deref()),
error.code.as_deref().or(error.r#type.as_deref()),
Some("insufficient_quota")
)
}
@@ -384,7 +384,7 @@ impl ModelClient {
.map(|spec| spec.slug.to_string())
.collect();
}
agent_models.sort_by(|a, b| a.to_ascii_lowercase().cmp(&b.to_ascii_lowercase()));
agent_models.sort_by_key(|a| a.to_ascii_lowercase());
agent_models.dedup_by(|a, b| a.eq_ignore_ascii_case(b));
tools_config.set_agent_models(agent_models);
@@ -394,10 +394,10 @@ impl ModelClient {
ConfigShellToolType::LocalShell | ConfigShellToolType::StreamableShell
);
tools_config.shell_type = match sandbox_policy.clone() {
tools_config.shell_type = match sandbox_policy {
SandboxPolicy::ReadOnly => {
if base_uses_native_shell {
base_shell_type.clone()
base_shell_type
} else {
ConfigShellToolType::ShellWithRequest {
sandbox_policy: SandboxPolicy::ReadOnly,
@@ -406,7 +406,7 @@ impl ModelClient {
}
sp @ SandboxPolicy::WorkspaceWrite { .. } => {
if base_uses_native_shell {
base_shell_type.clone()
base_shell_type
} else {
ConfigShellToolType::ShellWithRequest { sandbox_policy: sp }
}
@@ -604,8 +604,8 @@ impl ModelClient {
if self.provider.is_azure_responses_endpoint() {
attach_item_ids(&mut payload_json, &input_with_instructions);
}
if let Some(openrouter_cfg) = self.provider.openrouter_config() {
if let Some(obj) = payload_json.as_object_mut() {
if let Some(openrouter_cfg) = self.provider.openrouter_config()
&& let Some(obj) = payload_json.as_object_mut() {
if let Some(provider) = &openrouter_cfg.provider {
obj.insert("provider".to_string(), serde_json::to_value(provider)?);
}
@@ -616,7 +616,6 @@ impl ModelClient {
obj.entry(key.clone()).or_insert(value.clone());
}
}
}
let auth = auth_manager.as_ref().and_then(|m| m.auth());
let endpoint = self.provider.get_full_url(&auth);
@@ -636,7 +635,7 @@ impl ModelClient {
let has_beta_header = req_builder
.try_clone()
.and_then(|builder| builder.build().ok())
.map_or(false, |req| req.headers().contains_key("OpenAI-Beta"));
.is_some_and(|req| req.headers().contains_key("OpenAI-Beta"));
if !has_beta_header {
let beta_value = if self.provider.is_public_openai_responses_endpoint() {
@@ -665,8 +664,8 @@ impl ModelClient {
.and_then(|builder| builder.build().ok())
.map(|req| header_map_to_json(req.headers()));
if request_id.is_empty() {
if let Ok(logger) = self.debug_logger.lock() {
if request_id.is_empty()
&& let Ok(logger) = self.debug_logger.lock() {
request_id = logger
.start_request_log(
&endpoint,
@@ -676,7 +675,6 @@ impl ModelClient {
)
.unwrap_or_default();
}
}
let ws_headers = req_builder
.try_clone()
@@ -730,15 +728,14 @@ impl ModelClient {
.get("X-Models-Etag")
.and_then(|value| value.to_str().ok())
.map(ToString::to_string);
if let Some(etag) = models_etag {
if tx_event
if let Some(etag) = models_etag
&& tx_event
.send(Ok(ResponseEvent::ModelsEtag(etag)))
.await
.is_err()
{
debug!("receiver dropped models etag event");
}
}
ws_stream
.send(Message::Text(ws_payload_text))
@@ -978,8 +975,8 @@ impl ModelClient {
if azure_workaround {
attach_item_ids(&mut payload_json, &input_with_instructions);
}
if let Some(openrouter_cfg) = self.provider.openrouter_config() {
if let Some(obj) = payload_json.as_object_mut() {
if let Some(openrouter_cfg) = self.provider.openrouter_config()
&& let Some(obj) = payload_json.as_object_mut() {
if let Some(provider) = &openrouter_cfg.provider {
obj.insert("provider".to_string(), serde_json::to_value(provider)?);
}
@@ -990,7 +987,6 @@ impl ModelClient {
obj.entry(key.clone()).or_insert(value.clone());
}
}
}
let payload_body = serde_json::to_string(&payload_json)?;
let mut auth_refresh_error: Option<RefreshTokenError> = None;
@@ -1012,7 +1008,7 @@ impl ModelClient {
let has_beta_header = req_builder
.try_clone()
.and_then(|builder| builder.build().ok())
.map_or(false, |req| req.headers().contains_key("OpenAI-Beta"));
.is_some_and(|req| req.headers().contains_key("OpenAI-Beta"));
if !has_beta_header {
let beta_value = if self.provider.is_public_openai_responses_endpoint() {
@@ -1114,15 +1110,14 @@ impl ModelClient {
.get("X-Models-Etag")
.and_then(|value| value.to_str().ok())
.map(ToString::to_string);
if let Some(etag) = models_etag {
if tx_event
if let Some(etag) = models_etag
&& tx_event
.send(Ok(ResponseEvent::ModelsEtag(etag)))
.await
.is_err()
{
debug!("receiver dropped models etag event");
}
}
// spawn task to process SSE
let stream = resp.bytes_stream().map_err(CodexErr::Reqwest);
@@ -1148,7 +1143,7 @@ impl ModelClient {
.headers()
.get("x-request-id")
.and_then(|v| v.to_str().ok())
.map(|s| s.to_string());
.map(std::string::ToString::to_string);
let now = Utc::now();
// Pull out Retry‑After header if present.
@@ -1188,7 +1183,7 @@ impl ModelClient {
{
let current_account_id = auth
.as_ref()
.and_then(|current| current.get_account_id())
.and_then(super::auth::CodexAuth::get_account_id)
.or_else(|| {
auth_accounts::get_active_account_id(self.code_home())
.ok()
@@ -1196,11 +1191,10 @@ impl ModelClient {
});
if let Some(current_account_id) = current_account_id {
let mut retry_after_delay = retry_after_hint.clone();
if retry_after_delay.is_none() {
if let Some(ErrorResponse { ref error }) = body {
if retry_after_delay.is_none()
&& let Some(ErrorResponse { ref error }) = body {
retry_after_delay = try_parse_retry_after(error, now);
}
}
let current_auth_mode =
auth.as_ref().map(|a| a.mode).unwrap_or(AuthMode::ApiKey);
@@ -1245,7 +1239,7 @@ impl ModelClient {
let plan_type = body
.as_ref()
.and_then(|err| err.error.plan_type.as_deref())
.map(|s| s.to_string());
.map(std::string::ToString::to_string);
let resets_in_seconds =
body.as_ref().and_then(|err| err.error.resets_in_seconds);
let code_home = self.code_home().to_path_buf();
@@ -1306,9 +1300,9 @@ impl ModelClient {
}
}
if status == StatusCode::BAD_REQUEST {
if let Some(ErrorResponse { ref error }) = body {
if !self.reasoning_summary_disabled.load(Ordering::Relaxed)
if status == StatusCode::BAD_REQUEST
&& let Some(ErrorResponse { ref error }) = body
&& !self.reasoning_summary_disabled.load(Ordering::Relaxed)
&& is_reasoning_summary_rejected(error)
{
self.disable_reasoning_summary();
@@ -1330,8 +1324,6 @@ impl ModelClient {
attempt = 0;
continue;
}
}
}
// The OpenAI Responses endpoint returns structured JSON bodies even for 4xx/5xx
// errors. When we bubble early with only the HTTP status the caller sees an opaque
@@ -1363,22 +1355,20 @@ impl ModelClient {
}));
}
if let Some(ErrorResponse { ref error }) = body {
if is_quota_exceeded_http_error(status, error) {
if let Some(ErrorResponse { ref error }) = body
&& is_quota_exceeded_http_error(status, error) {
return Err(CodexErr::QuotaExceeded);
}
}
if status == StatusCode::UNAUTHORIZED {
if let Some(error) =
if status == StatusCode::UNAUTHORIZED
&& let Some(error) =
map_unauthorized_outcome(auth.is_some(), auth_refresh_error.as_ref())
{
return Err(error);
}
}
if status == StatusCode::TOO_MANY_REQUESTS {
if let Some(ErrorResponse { ref error }) = body {
if status == StatusCode::TOO_MANY_REQUESTS
&& let Some(ErrorResponse { ref error }) = body {
if error.r#type.as_deref() == Some("usage_limit_reached") {
// Prefer the plan_type provided in the error message if present
// because it's more up to date than the one encoded in the auth
@@ -1396,7 +1386,6 @@ impl ModelClient {
return Err(CodexErr::UsageNotIncluded);
}
}
}
if attempt > max_retries {
// On final attempt, surface rich diagnostics for server errors.
@@ -1463,11 +1452,10 @@ impl ModelClient {
}
let mut retry_after_delay = retry_after_hint;
if retry_after_delay.is_none() {
if let Some(ErrorResponse { ref error }) = body {
if retry_after_delay.is_none()
&& let Some(ErrorResponse { ref error }) = body {
retry_after_delay = try_parse_retry_after(error, now);
}
}
let delay = retry_after_delay
.as_ref()
@@ -1482,7 +1470,7 @@ impl ModelClient {
if let Ok(logger) = self.debug_logger.lock() {
let _ = logger.log_error(
&endpoint,
&format!("Network error: {}", e),
&format!("Network error: {e}"),
log_tag,
);
}
@@ -1576,7 +1564,7 @@ impl ModelClient {
let has_beta_header = request
.try_clone()
.and_then(|builder| builder.build().ok())
.map_or(false, |req| req.headers().contains_key("OpenAI-Beta"));
.is_some_and(|req| req.headers().contains_key("OpenAI-Beta"));
if !has_beta_header {
let beta_value = if self.provider.is_public_openai_responses_endpoint() {
@@ -1604,8 +1592,8 @@ impl ModelClient {
.and_then(|builder| builder.build().ok())
.map(|req| header_map_to_json(req.headers()));
if request_id.is_empty() {
if let Ok(logger) = self.debug_logger.lock() {
if request_id.is_empty()
&& let Ok(logger) = self.debug_logger.lock() {
let endpoint = self
.provider
.get_compact_url(&auth)
@@ -1619,7 +1607,6 @@ impl ModelClient {
)
.unwrap_or_default();
}
}
let response = request.send().await?;
let status = response.status();
@@ -1633,7 +1620,7 @@ impl ModelClient {
let now = Utc::now();
let current_account_id = auth
.as_ref()
.and_then(|current| current.get_account_id())
.and_then(super::auth::CodexAuth::get_account_id)
.or_else(|| {
auth_accounts::get_active_account_id(self.code_home())
.ok()
@@ -1718,7 +1705,7 @@ fn attach_codex_beta_features_header(
let has_header = builder
.try_clone()
.and_then(|builder| builder.build().ok())
.map_or(false, |req| {
.is_some_and(|req| {
req.headers().contains_key("x-codex-beta-features")
});
if has_header {
@@ -1754,7 +1741,7 @@ fn attach_openai_subagent_header(builder: reqwest::RequestBuilder) -> reqwest::R
let has_header = builder
.try_clone()
.and_then(|builder| builder.build().ok())
.map_or(false, |req| req.headers().contains_key("x-openai-subagent"));
.is_some_and(|req| req.headers().contains_key("x-openai-subagent"));
if has_header {
return builder;
}
@@ -1932,7 +1919,7 @@ fn format_rate_limit_headers(headers: &HeaderMap) -> String {
.iter()
.map(|(name, value)| {
let value_str = value.to_str().unwrap_or("<invalid>");
format!("{}: {}", name, value_str)
format!("{name}: {value_str}")
})
.collect();
pairs.sort();
@@ -1969,14 +1956,13 @@ fn parse_retry_after_header(value: &str, now: DateTime<Utc>) -> Option<RetryAfte
if let Ok(secs) = normalized.parse::<u64>() {
return Some(RetryAfter::from_duration(Duration::from_secs(secs), now));
}
if let Ok(float_secs) = normalized.parse::<f64>() {
if !float_secs.is_sign_negative() {
if let Ok(float_secs) = normalized.parse::<f64>()
&& !float_secs.is_sign_negative() {
return Some(RetryAfter::from_duration(
Duration::from_secs_f64(float_secs),
now,
));
}
}
if let Ok(system_time) = parse_http_date(normalized) {
let resume_at: DateTime<Utc> = system_time.into();
return Some(RetryAfter::from_resume_at(resume_at, now));
@@ -2113,11 +2099,10 @@ async fn process_sse<S>(
trace!("SSE event: {}", raw);
// Log the raw SSE event data
if let Ok(logger) = debug_logger.lock() {
if let Ok(json_value) = serde_json::from_str::<serde_json::Value>(&sse.data) {
if let Ok(logger) = debug_logger.lock()
&& let Ok(json_value) = serde_json::from_str::<serde_json::Value>(&sse.data) {
let _ = logger.append_response_event(&request_id, "sse_event", &json_value);
}
}
let event: SseEvent = match serde_json::from_str(&sse.data) {
Ok(event) => event,
@@ -2144,11 +2129,10 @@ async fn process_sse<S>(
};
if let Some(seq) = event.sequence_number {
if let Some(last) = global_last_seq {
if seq <= last {
if let Some(last) = global_last_seq
&& seq <= last {
continue;
}
}
global_last_seq = Some(seq);
if let Ok(mut guard) = checkpoint.write() {
guard.last_sequence = Some(seq);
@@ -2191,7 +2175,7 @@ async fn process_sse<S>(
.get("action")
.and_then(|a| a.get("query"))
.and_then(|v| v.as_str())
.map(|s| s.to_string());
.map(std::string::ToString::to_string);
let ev = ResponseEvent::WebSearchCallCompleted { call_id, query };
if tx_event.send(Ok(ev)).await.is_err() {
return;
@@ -2262,7 +2246,7 @@ async fn process_sse<S>(
} else {
// Best-effort: drop exact duplicate text for same key when seq is missing
let key = (id.clone(), out_idx, sum_idx);
if last_text_reasoning_summary.get(&key).map_or(false, |prev| prev == &delta) {
if last_text_reasoning_summary.get(&key) == Some(&delta) {
continue;
}
last_text_reasoning_summary.insert(key, delta.clone());
@@ -2303,7 +2287,7 @@ async fn process_sse<S>(
} else {
// Best-effort: drop exact duplicate text for same key when seq is missing
let key = (id.clone(), out_idx, content_idx);
if last_text_reasoning_content.get(&key).map_or(false, |prev| prev == &delta) {
if last_text_reasoning_content.get(&key) == Some(&delta) {
continue;
}
last_text_reasoning_content.insert(key, delta.clone());
@@ -2385,11 +2369,11 @@ async fn process_sse<S>(
| "response.in_progress"
| "response.output_item.added"
| "response.output_text.done" => {
if event.kind == "response.output_item.added" {
if let Some(item) = event.item.as_ref() {
if event.kind == "response.output_item.added"
&& let Some(item) = event.item.as_ref() {
// Detect web_search_call begin and forward a synthetic event upstream.
if let Some(ty) = item.get("type").and_then(|v| v.as_str()) {
if ty == "web_search_call" {
if let Some(ty) = item.get("type").and_then(|v| v.as_str())
&& ty == "web_search_call" {
let call_id = item
.get("id")
.and_then(|v| v.as_str())
@@ -2400,9 +2384,7 @@ async fn process_sse<S>(
return;
}
}
}
}
}
}
"response.reasoning_summary_part.added" => {
// Boundary between reasoning summary sections (e.g., titles).
+12 -18
View File
@@ -9,7 +9,6 @@ use crate::openai_tools::OpenAiTool;
use crate::protocol::RateLimitSnapshotEvent;
use crate::protocol::TokenUsage;
use futures::Stream;
use hanzo_apply_patch::APPLY_PATCH_TOOL_INSTRUCTIONS;
use hanzo_protocol::models::ContentItem;
use hanzo_protocol::models::ResponseItem;
use once_cell::sync::Lazy;
@@ -26,7 +25,7 @@ use uuid::Uuid;
/// Additional prompt for Code. Can not edit Codex instructions.
const PROMPT_CODER_TEMPLATE: &str = include_str!("../prompt_coder.md");
static BASE_MODEL_DESCRIPTIONS: Lazy<String> = Lazy::new(|| model_guide_markdown());
static BASE_MODEL_DESCRIPTIONS: Lazy<String> = Lazy::new(model_guide_markdown);
static DEFAULT_DEVELOPER_PROMPT: Lazy<String> =
Lazy::new(|| PROMPT_CODER_TEMPLATE.replace("{MODEL_DESCRIPTIONS}", &BASE_MODEL_DESCRIPTIONS));
@@ -147,7 +146,7 @@ impl Prompt {
fn get_formatted_environment_context(&self) -> Option<String> {
self.environment_context.as_ref().map(|ec| {
let ec_str = serde_json::to_string_pretty(ec).unwrap_or_else(|_| format!("{:?}", ec));
let ec_str = serde_json::to_string_pretty(ec).unwrap_or_else(|_| format!("{ec:?}"));
format!("{ENVIRONMENT_CONTEXT_START}{ec_str}{ENVIRONMENT_CONTEXT_END}")
})
}
@@ -213,19 +212,15 @@ impl Prompt {
// Deduplicate function call outputs before adding to input
let mut seen_call_ids = std::collections::HashSet::new();
for item in &self.input {
match item {
ResponseItem::FunctionCallOutput { call_id, .. } => {
if !seen_call_ids.insert(call_id.clone()) {
// Skip duplicate function call output
tracing::debug!(
"Filtering duplicate FunctionCallOutput with call_id: {} from input",
call_id
);
continue;
}
if let ResponseItem::FunctionCallOutput { call_id, .. } = item
&& !seen_call_ids.insert(call_id.clone()) {
// Skip duplicate function call output
tracing::debug!(
"Filtering duplicate FunctionCallOutput with call_id: {} from input",
call_id
);
continue;
}
_ => {}
}
input_with_instructions.push(item.clone());
}
@@ -401,8 +396,8 @@ fn limit_screenshots_in_input(input: &mut Vec<ResponseItem>) {
// Replace screenshots that should be removed
for &pos in &screenshot_positions {
if !positions_to_keep.contains(&pos) {
if let Some(ResponseItem::Message { content, .. }) = input.get_mut(pos) {
if !positions_to_keep.contains(&pos)
&& let Some(ResponseItem::Message { content, .. }) = input.get_mut(pos) {
// Replace image content with placeholder message
let mut new_content = Vec::new();
for item in content.iter() {
@@ -417,7 +412,6 @@ fn limit_screenshots_in_input(input: &mut Vec<ResponseItem>) {
}
*content = new_content;
}
}
}
tracing::debug!(
+26 -38
View File
@@ -308,7 +308,7 @@ fn response_input_from_core_items(items: Vec<InputItem>) -> ResponseInputItem {
if let Some(meta) = metadata {
content_items.push(ContentItem::InputText {
text: format!("[EPHEMERAL:{}]", meta),
text: format!("[EPHEMERAL:{meta}]"),
});
}
@@ -360,13 +360,11 @@ fn convert_call_tool_result_to_function_call_output_payload(
fn get_git_branch(cwd: &std::path::Path) -> Option<String> {
let head_path = cwd.join(".git/HEAD");
if let Ok(contents) = std::fs::read_to_string(&head_path) {
if let Some(rest) = contents.trim().strip_prefix("ref: ") {
if let Some(branch) = rest.trim().rsplit('/').next() {
if let Ok(contents) = std::fs::read_to_string(&head_path)
&& let Some(rest) = contents.trim().strip_prefix("ref: ")
&& let Some(branch) = rest.trim().rsplit('/').next() {
return Some(branch.to_string());
}
}
}
None
}
@@ -382,11 +380,10 @@ fn maybe_update_from_model_info<T: Copy + PartialEq>(
return;
}
if let (Some(current), Some(old_val)) = (*field, old_default) {
if current == old_val {
if let (Some(current), Some(old_val)) = (*field, old_default)
&& current == old_val {
*field = new_default;
}
}
}
#[derive(Clone, Debug)]
@@ -502,14 +499,13 @@ async fn build_turn_status_items_legacy(sess: &Session) -> Vec<ResponseItem> {
let mut screenshot_content: Option<ContentItem> = None;
let mut include_screenshot = false;
if let Some(browser_manager) = hanzo_browser::global::get_browser_manager().await {
if browser_manager.is_enabled().await {
if let Some(browser_manager) = hanzo_browser::global::get_browser_manager().await
&& browser_manager.is_enabled().await {
if let Some((_, idle_timeout)) = browser_manager.idle_elapsed_past_timeout().await {
let idle_text = format!(
"Browser idle (timeout {:?}); screenshot capture paused until browser_* tools run again.",
idle_timeout
"Browser idle (timeout {idle_timeout:?}); screenshot capture paused until browser_* tools run again."
);
current_status.push_str("\n");
current_status.push('\n');
current_status.push_str(&idle_text);
} else {
// Get current URL and browser info
@@ -529,13 +525,12 @@ async fn build_turn_status_items_legacy(sess: &Session) -> Vec<ResponseItem> {
// Get viewport dimensions
let (viewport_width, viewport_height) = browser_manager.get_viewport_size().await;
let viewport_info = format!(" | Viewport: {}x{}", viewport_width, viewport_height);
let viewport_info = format!(" | Viewport: {viewport_width}x{viewport_height}");
// Get cursor position
let cursor_info = match browser_manager.get_cursor_position().await {
Ok((x, y)) => format!(
" | Mouse position: ({:.0}, {:.0}) [shown as a blue cursor in the screenshot]",
x, y
" | Mouse position: ({x:.0}, {y:.0}) [shown as a blue cursor in the screenshot]"
),
Err(_) => String::new(),
};
@@ -607,26 +602,23 @@ async fn build_turn_status_items_legacy(sess: &Session) -> Vec<ResponseItem> {
}
Err(err_msg) => {
// Include error message so LLM knows screenshot failed
format!(" [Screenshot unavailable: {}]", err_msg).leak()
format!(" [Screenshot unavailable: {err_msg}]").leak()
}
};
let status_line = if let Some(t) = title {
format!(
"Browser url: {} — {} ({}){}{}{}. You can interact with it using browser_* tools.",
url, t, browser_type, viewport_info, cursor_info, screenshot_status
"Browser url: {url} — {t} ({browser_type}){viewport_info}{cursor_info}{screenshot_status}. You can interact with it using browser_* tools."
)
} else {
format!(
"Browser url: {} ({}){}{}{}. You can interact with it using browser_* tools.",
url, browser_type, viewport_info, cursor_info, screenshot_status
"Browser url: {url} ({browser_type}){viewport_info}{cursor_info}{screenshot_status}. You can interact with it using browser_* tools."
)
};
current_status.push_str("\n");
current_status.push('\n');
current_status.push_str(&status_line);
}
}
}
// Check if system status has changed
let mut last_status = sess.last_system_status.lock().unwrap();
@@ -646,11 +638,10 @@ async fn build_turn_status_items_legacy(sess: &Session) -> Vec<ResponseItem> {
});
}
if include_screenshot {
if let Some(image) = screenshot_content {
if include_screenshot
&& let Some(image) = screenshot_content {
content.push(image);
}
}
if !content.is_empty() {
jar.items.push(ResponseItem::Message {
@@ -689,8 +680,8 @@ async fn build_turn_status_items_v2(sess: &Session) -> Vec<ResponseItem> {
items.push(item);
}
if let Some(browser_manager) = hanzo_browser::global::get_browser_manager().await {
if browser_manager.is_enabled().await {
if let Some(browser_manager) = hanzo_browser::global::get_browser_manager().await
&& browser_manager.is_enabled().await {
let browser_stream_id = {
let mut state = sess.state.lock().unwrap();
state.context_stream_ids.browser_stream_id(sess.id)
@@ -698,8 +689,7 @@ async fn build_turn_status_items_v2(sess: &Session) -> Vec<ResponseItem> {
if let Some((_, timeout)) = browser_manager.idle_elapsed_past_timeout().await {
let idle_text = format!(
"Browser idle (timeout {:?}); screenshot capture paused until browser_* tools run again.",
timeout
"Browser idle (timeout {timeout:?}); screenshot capture paused until browser_* tools run again."
);
items.push(ResponseItem::Message {
id: Some(browser_stream_id),
@@ -725,13 +715,13 @@ async fn build_turn_status_items_v2(sess: &Session) -> Vec<ResponseItem> {
let mut metadata = HashMap::new();
metadata.insert("browser_type".to_string(), browser_type.clone());
if let Some((x, y)) = cursor_position {
metadata.insert("cursor_position".to_string(), format!("{:.0},{:.0}", x, y));
metadata.insert("cursor_position".to_string(), format!("{x:.0},{y:.0}"));
}
let viewport = if viewport_width > 0 && viewport_height > 0 {
Some(ViewportDimensions {
width: viewport_width as u32,
height: viewport_height as u32,
width: viewport_width,
height: viewport_height,
})
} else {
None
@@ -802,7 +792,6 @@ async fn build_turn_status_items_v2(sess: &Session) -> Vec<ResponseItem> {
}
}
}
}
items
}
@@ -813,13 +802,12 @@ fn should_include_browser_screenshot(
current_hash: Option<(Vec<u8>, Vec<u8>)>,
) -> bool {
if let Some((cur_phash, cur_dhash)) = current_hash {
if let Some((_, last_phash, last_dhash)) = last_info.as_ref() {
if crate::image_comparison::are_hashes_similar(
if let Some((_, last_phash, last_dhash)) = last_info.as_ref()
&& crate::image_comparison::are_hashes_similar(
last_phash, last_dhash, &cur_phash, &cur_dhash,
) {
return false;
}
}
*last_info = Some((path.clone(), cur_phash, cur_dhash));
true
} else {
+3 -4
View File
@@ -152,11 +152,10 @@ pub fn make_compaction_summary_message(
/// Empty strings are treated as missing so we always fall back to the embedded
/// template instead of sending a blank developer message.
pub fn resolve_compact_prompt_text(override_prompt: Option<&str>) -> String {
if let Some(text) = override_prompt {
if !text.trim().is_empty() {
if let Some(text) = override_prompt
&& !text.trim().is_empty() {
return text.to_string();
}
}
SUMMARIZATION_PROMPT.to_string()
}
@@ -848,7 +847,7 @@ pub(super) fn response_input_from_core_items(items: Vec<InputItem>) -> ResponseI
InputItem::EphemeralImage { path, metadata } => {
if let Some(meta) = metadata {
content_items.push(ContentItem::InputText {
text: format!("[EPHEMERAL:{}]", meta),
text: format!("[EPHEMERAL:{meta}]"),
});
}
match std::fs::read(&path) {
+4 -6
View File
@@ -557,8 +557,8 @@ impl Session {
None
};
if enable_hooks {
if let Some(params_ref) = params_for_hooks.as_ref() {
if enable_hooks
&& let Some(params_ref) = params_for_hooks.as_ref() {
let before_event = if is_apply_patch {
ProjectHookEvent::FileBeforeWrite
} else {
@@ -574,7 +574,6 @@ impl Session {
)
.await;
}
}
self.on_exec_command_begin(
turn_diff_tracker,
@@ -626,8 +625,8 @@ impl Session {
exec_guard.mark_completed();
self.finalize_cancelled_execs(&sub_id).await;
if enable_hooks {
if let Some(params_ref) = params_for_hooks.as_ref() {
if enable_hooks
&& let Some(params_ref) = params_for_hooks.as_ref() {
let after_event = if is_apply_patch {
ProjectHookEvent::FileAfterWrite
} else {
@@ -643,7 +642,6 @@ impl Session {
)
.await;
}
}
if let Some(analysis) = dry_run_analysis.as_ref() {
let mut state = self.state.lock().unwrap();
+17 -24
View File
@@ -117,13 +117,13 @@ pub(super) struct EnvironmentContextStreamRegistry {
impl EnvironmentContextStreamRegistry {
pub(super) fn env_stream_id(&mut self, session_id: Uuid) -> String {
self.env_stream_id
.get_or_insert_with(|| format!("env-context-{}", session_id))
.get_or_insert_with(|| format!("env-context-{session_id}"))
.clone()
}
pub(super) fn browser_stream_id(&mut self, session_id: Uuid) -> String {
self.browser_stream_id
.get_or_insert_with(|| format!("browser-context-{}", session_id))
.get_or_insert_with(|| format!("browser-context-{session_id}"))
.clone()
}
}
@@ -883,11 +883,10 @@ impl Session {
pub fn remove_task(&self, sub_id: &str) {
let mut state = self.state.lock().unwrap();
if let Some(agent) = &state.current_task {
if agent.sub_id == sub_id {
if let Some(agent) = &state.current_task
&& agent.sub_id == sub_id {
state.current_task.take();
}
}
}
pub fn has_running_task(&self) -> bool {
@@ -1233,11 +1232,10 @@ impl Session {
let guard = self.rollout.lock().unwrap();
guard.as_ref().cloned()
};
if let Some(rec) = recorder {
if let Err(e) = rec.record_items(items).await {
if let Some(rec) = recorder
&& let Err(e) = rec.record_items(items).await {
error!("failed to record rollout items: {e:#}");
}
}
}
/// Build the full turn input by concatenating the current conversation
@@ -1281,8 +1279,8 @@ impl Session {
// Helper closure to detect legacy XML environment context items
let is_legacy_env_context = |item: &ResponseItem| -> bool {
if let ResponseItem::Message { role, content, .. } = item {
if role == "user" {
if let ResponseItem::Message { role, content, .. } = item
&& role == "user" {
return content.iter().any(|c| {
if let ContentItem::InputText { text } = c {
text.contains("<environment_context>")
@@ -1291,7 +1289,6 @@ impl Session {
}
});
}
}
false
};
@@ -1475,8 +1472,8 @@ impl Session {
let stream = state.context_stream_ids.env_stream_id(self.id);
let result = match state.environment_context_tracker.emit_response_items(
env_context,
git_branch.clone(),
reasoning_effort.clone(),
git_branch,
reasoning_effort,
Some(stream.as_str()),
) {
Ok(Some((emission, items))) => {
@@ -1511,15 +1508,14 @@ impl Session {
delta,
snapshot,
} => {
if state.context_timeline.baseline().is_none() {
if let Err(err) =
if state.context_timeline.baseline().is_none()
&& let Err(err) =
state.context_timeline.add_baseline_once(snapshot.clone())
{
tracing::warn!(
"env_ctx_v2: failed to seed baseline before delta: {err}"
);
}
}
if let Err(err) =
state.context_timeline.apply_delta(*sequence, delta.clone())
{
@@ -1730,8 +1726,8 @@ impl Session {
}
}
if replay_ctx.timeline.baseline().is_none() {
if let Some(snapshot) = replay_ctx.legacy_baseline.clone() {
if replay_ctx.timeline.baseline().is_none()
&& let Some(snapshot) = replay_ctx.legacy_baseline.clone() {
if let Err(err) = replay_ctx.timeline.add_baseline_once(snapshot.clone()) {
tracing::warn!("env_ctx_v2: failed to map legacy status to baseline: {err}");
}
@@ -1744,7 +1740,6 @@ impl Session {
}
replay_ctx.last_snapshot = Some(snapshot);
}
}
let restored_snapshot = replay_ctx.last_snapshot.clone();
let next_seq_value = replay_ctx.next_sequence;
@@ -2198,17 +2193,15 @@ fn prune_history_items_owned(
let mut screenshots_to_keep = std::collections::HashSet::new();
for &user_idx in real_user_messages.iter().rev().take(2) {
for &status_idx in status_messages.iter() {
if status_idx > user_idx {
if let Some(ResponseItem::Message { content, .. }) = current_items.get(status_idx) {
if content
if status_idx > user_idx
&& let Some(ResponseItem::Message { content, .. }) = current_items.get(status_idx)
&& content
.iter()
.any(|c| matches!(c, ContentItem::InputImage { .. }))
{
screenshots_to_keep.insert(status_idx);
break;
}
}
}
}
}
File diff suppressed because it is too large Load Diff
@@ -33,21 +33,17 @@ pub fn is_known_safe_command(command: &[String]) -> bool {
// introduce side effects ( "&&", "||", ";", and "|" ). If every
// individual command in the script is itself a known‑safe command, then
// the composite expression is considered safe.
if let [bash, flag, script] = command {
if is_bash(bash) && flag == "-lc" {
if let Some(tree) = try_parse_bash(script) {
if let Some(all_commands) = try_parse_word_only_commands_sequence(&tree, script) {
if !all_commands.is_empty()
if let [bash, flag, script] = command
&& is_bash(bash) && flag == "-lc"
&& let Some(tree) = try_parse_bash(script)
&& let Some(all_commands) = try_parse_word_only_commands_sequence(&tree, script)
&& !all_commands.is_empty()
&& all_commands
.iter()
.all(|cmd| is_safe_to_call_with_exec(cmd))
{
return true;
}
}
}
}
}
false
}
+4 -5
View File
@@ -1006,8 +1006,8 @@ impl Config {
let history = cfg.history.unwrap_or_default();
let mut always_allow_commands: Vec<ApprovedCommandPattern> = Vec::new();
if let Some(project_cfg) = project_override {
if let Some(commands) = &project_cfg.always_allow_commands {
if let Some(project_cfg) = project_override
&& let Some(commands) = &project_cfg.always_allow_commands {
for cmd in commands {
if cmd.argv.is_empty() {
continue;
@@ -1028,7 +1028,6 @@ impl Config {
));
}
}
}
let project_hooks = project_override
.map(|cfg| ProjectHooks::from_configs(&cfg.hooks, &resolved_cwd))
@@ -1129,7 +1128,7 @@ impl Config {
let responses_originator_header: String = cfg
.responses_originator_header_internal_override
.unwrap_or_else(|| default_responses_originator());
.unwrap_or_else(default_responses_originator);
let agents: Vec<AgentConfig> = merge_with_default_agents(cfg.agents);
@@ -1150,7 +1149,7 @@ impl Config {
let mut confirm_guard = ConfirmGuardConfig::default();
if let Some(mut user_guard) = cfg.confirm_guard {
confirm_guard.patterns.extend(user_guard.patterns.drain(..));
confirm_guard.patterns.append(&mut user_guard.patterns);
}
for pattern in &confirm_guard.patterns {
if let Err(err) = regex_lite::Regex::new(&pattern.regex) {
+1 -2
View File
@@ -3,7 +3,6 @@ use crate::agent_defaults::default_agent_configs;
use crate::config_types::AgentConfig;
use std::collections::HashMap;
use std::collections::HashSet;
use std::io::ErrorKind;
use std::sync::LazyLock;
use std::sync::Mutex;
@@ -17,7 +16,7 @@ pub(crate) const fn default_true_local() -> bool {
pub fn set_default_originator(originator: &str) -> std::io::Result<()> {
let mut guard = RESPONSES_ORIGINATOR_OVERRIDE
.lock()
.map_err(|_| std::io::Error::new(ErrorKind::Other, "originator override lock poisoned"))?;
.map_err(|_| std::io::Error::other("originator override lock poisoned"))?;
*guard = Some(originator.to_string());
Ok(())
}
+15 -24
View File
@@ -315,7 +315,7 @@ fn set_project_trusted_inner(doc: &mut DocumentMut, project_path: &Path) -> anyh
.get_mut(project_key.as_str())
.and_then(|i| i.as_table_mut())
else {
return Err(anyhow::anyhow!("project table missing for {}", project_key));
return Err(anyhow::anyhow!("project table missing for {project_key}"));
};
proj_tbl.set_implicit(false);
proj_tbl["trust_level"] = toml_edit::value("trusted");
@@ -360,12 +360,11 @@ pub fn set_tui_theme_name(code_home: &Path, theme: ThemeName) -> anyhow::Result<
doc["tui"]["theme"]["name"] = toml_edit::value(theme_str);
// When switching away from the Custom theme, clear any lingering custom
// overrides so built-in themes render true to spec on next startup.
if theme != ThemeName::Custom {
if let Some(tbl) = doc["tui"]["theme"].as_table_mut() {
if theme != ThemeName::Custom
&& let Some(tbl) = doc["tui"]["theme"].as_table_mut() {
tbl.remove("label");
tbl.remove("colors");
}
}
// ensure code_home exists
std::fs::create_dir_all(code_home)?;
@@ -1056,17 +1055,17 @@ pub fn set_project_access_mode(
.get_mut(project_key.as_str())
.and_then(|i| i.as_table_mut())
.ok_or_else(|| {
anyhow::anyhow!(format!("failed to create projects.{} table", project_key))
anyhow::anyhow!(format!("failed to create projects.{project_key} table"))
})?;
// Write fields
proj_tbl.insert(
"approval_policy",
TomlItem::Value(toml_edit::Value::from(format!("{}", approval))),
TomlItem::Value(toml_edit::Value::from(format!("{approval}"))),
);
proj_tbl.insert(
"sandbox_mode",
TomlItem::Value(toml_edit::Value::from(format!("{}", sandbox_mode))),
TomlItem::Value(toml_edit::Value::from(format!("{sandbox_mode}"))),
);
// Harmonize trust_level with selected access mode:
@@ -1136,7 +1135,7 @@ pub fn add_project_allowed_command(
.get_mut(project_key.as_str())
.and_then(|i| i.as_table_mut())
.ok_or_else(|| {
anyhow::anyhow!(format!("failed to create projects.{} table", project_key))
anyhow::anyhow!(format!("failed to create projects.{project_key} table"))
})?;
let mut argv_array = TomlArray::new();
@@ -1216,7 +1215,7 @@ pub fn list_mcp_servers(
.and_then(|v| v.as_array())
.map(|arr| {
arr.iter()
.filter_map(|i| i.as_str().map(|s| s.to_string()))
.filter_map(|i| i.as_str().map(std::string::ToString::to_string))
.collect()
})
.unwrap_or_default();
@@ -1229,18 +1228,12 @@ pub fn list_mcp_servers(
})
.collect::<HashMap<_, _>>(),
)
} else if let Some(table) = v.as_table() {
Some(
table
} else { v.as_table().map(|table| table
.iter()
.filter_map(|(k, v)| {
v.as_str().map(|s| (k.to_string(), s.to_string()))
})
.collect::<HashMap<_, _>>(),
)
} else {
None
}
.collect::<HashMap<_, _>>()) }
});
McpServerTransportConfig::Stdio {
@@ -1252,7 +1245,7 @@ pub fn list_mcp_servers(
let bearer_token = t
.get("bearer_token")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
.map(std::string::ToString::to_string);
McpServerTransportConfig::StreamableHttp {
url: url.to_string(),
@@ -1272,7 +1265,7 @@ pub fn list_mcp_servers(
.flatten()
.or_else(|| {
t.get("startup_timeout_ms")
.and_then(|v| v.as_integer())
.and_then(toml_edit::Item::as_integer)
.map(|ms| Duration::from_millis(ms as u64))
});
@@ -1324,8 +1317,7 @@ pub fn add_mcp_server(code_home: &Path, name: &str, cfg: McpServerConfig) -> any
.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-')
{
return Err(anyhow::anyhow!(
"invalid server name '{}': must match ^[a-zA-Z0-9_-]+$",
name
"invalid server name '{name}': must match ^[a-zA-Z0-9_-]+$"
));
}
@@ -1527,11 +1519,10 @@ pub fn resolve_code_path_for_read(code_home: &Path, relative: &Path) -> PathBuf
return default_path;
}
if let Some(default_home) = default_code_home_dir() {
if default_home != code_home {
if let Some(default_home) = default_code_home_dir()
&& default_home != code_home {
return default_path;
}
}
if let Some(legacy) = legacy_code_home_dir() {
let candidate = legacy.join(relative);
+2 -3
View File
@@ -127,8 +127,8 @@ pub(crate) fn deserialize_config_toml_with_cli_warnings(
pub(crate) fn upgrade_legacy_model_slugs(cfg: &mut ConfigToml) {
fn maybe_upgrade(field: &mut Option<String>) {
if let Some(old) = field.clone() {
if let Some(new) = upgrade_legacy_model_slug(&old) {
if let Some(old) = field.clone()
&& let Some(new) = upgrade_legacy_model_slug(&old) {
tracing::info!(
target: "code.config",
old,
@@ -137,7 +137,6 @@ pub(crate) fn upgrade_legacy_model_slugs(cfg: &mut ConfigToml) {
);
*field = Some(new);
}
}
}
maybe_upgrade(&mut cfg.model);
+8 -11
View File
@@ -358,27 +358,24 @@ fn append_agent_entry(
if let Some(w) = args_write {
t["args-write"] = toml_edit::value(toml_edit::Array::from_iter(w.iter().cloned()));
}
if let Some(instr) = instructions {
if !instr.trim().is_empty() {
if let Some(instr) = instructions
&& !instr.trim().is_empty() {
t["instructions"] = toml_edit::value(instr.to_string());
}
}
if let Some(desc) = description {
if !desc.trim().is_empty() {
if let Some(desc) = description
&& !desc.trim().is_empty() {
t["description"] = toml_edit::value(desc.to_string());
}
}
if let Some(cmd) = command {
if !cmd.trim().is_empty() {
if let Some(cmd) = command
&& !cmd.trim().is_empty() {
t["command"] = toml_edit::value(cmd.to_string());
}
}
let mut arr = doc
.as_table()
.get("agents")
.and_then(|i| i.as_array_of_tables().cloned())
.unwrap_or_else(toml_edit::ArrayOfTables::new);
.unwrap_or_default();
arr.push(t);
doc["agents"] = toml_edit::Item::ArrayOfTables(arr);
}
@@ -456,7 +453,7 @@ async fn persist_overrides_with_behavior(
} else {
doc.get("profile")
.and_then(|i| i.as_str())
.map(|s| s.to_string())
.map(std::string::ToString::to_string)
};
let mut mutated = false;
+2 -2
View File
@@ -300,7 +300,7 @@ where
.spawn(move || run_future(future))
.map_err(|err| io::Error::other(format!("config loader thread spawn failed: {err}")))?
.join()
.map_err(|_| io::Error::new(io::ErrorKind::Other, "config loader thread panicked"))?
.map_err(|_| io::Error::other("config loader thread panicked"))?
} else {
run_future(future)
}
@@ -314,7 +314,7 @@ where
let runtime = RuntimeBuilder::new_current_thread()
.enable_all()
.build()
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
.map_err(io::Error::other)?;
runtime.block_on(future)
}
+3 -29
View File
@@ -473,6 +473,7 @@ pub struct GithubConfig {
}
#[derive(Deserialize, Debug, Clone, PartialEq)]
#[derive(Default)]
pub struct ValidationConfig {
/// Legacy master toggle for the validation harness (kept for config compatibility).
/// `run_patch_harness` now relies solely on the functional/stylistic group toggles.
@@ -496,19 +497,9 @@ pub struct ValidationConfig {
pub tools: ValidationTools,
}
impl Default for ValidationConfig {
fn default() -> Self {
Self {
patch_harness: false,
tools_allowlist: None,
timeout_seconds: None,
groups: ValidationGroups::default(),
tools: ValidationTools::default(),
}
}
}
#[derive(Deserialize, Debug, Clone, PartialEq)]
#[derive(Default)]
pub struct ValidationGroups {
/// Functional checks catch correctness regressions.
#[serde(default = "default_true")]
@@ -519,14 +510,6 @@ pub struct ValidationGroups {
pub stylistic: bool,
}
impl Default for ValidationGroups {
fn default() -> Self {
Self {
functional: false,
stylistic: false,
}
}
}
#[derive(Deserialize, Debug, Clone, PartialEq, Default)]
pub struct ValidationTools {
@@ -943,6 +926,7 @@ impl Default for StreamConfig {
/// Theme configuration for the TUI
#[derive(Deserialize, Debug, Clone, PartialEq)]
#[derive(Default)]
pub struct ThemeConfig {
/// Name of the predefined theme to use
#[serde(default)]
@@ -965,16 +949,6 @@ pub struct ThemeConfig {
pub is_dark: Option<bool>,
}
impl Default for ThemeConfig {
fn default() -> Self {
Self {
name: ThemeName::default(),
colors: ThemeColors::default(),
label: None,
is_dark: None,
}
}
}
/// Selected loading spinner style.
#[derive(Deserialize, Debug, Clone, PartialEq)]
@@ -12,6 +12,7 @@ use super::timeline::ContextTimeline;
///
/// Phase 2A: Not yet implemented, placeholder for future phases.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Default)]
pub struct StorageConfig {
/// Path to the storage directory.
pub storage_path: Option<std::path::PathBuf>,
@@ -23,15 +24,6 @@ pub struct StorageConfig {
pub compress: bool,
}
impl Default for StorageConfig {
fn default() -> Self {
Self {
storage_path: None,
auto_persist: false,
compress: false,
}
}
}
#[allow(dead_code)]
impl StorageConfig {
+2 -2
View File
@@ -186,7 +186,7 @@ impl ConversationManager {
),
)
.await
.map_err(|e| CodexErr::Io(e))?;
.map_err(CodexErr::Io)?;
// Persist rollout items to seed the resumed conversation.
let rollout_items = history.get_rollout_items();
@@ -197,7 +197,7 @@ impl ConversationManager {
.map_err(CodexErr::Io)?;
}
// Ensure data is flushed to disk before resuming.
recorder.shutdown().await.map_err(|e| CodexErr::Io(e))?;
recorder.shutdown().await.map_err(CodexErr::Io)?;
// Now spawn a conversation resuming from the newly created rollout.
config.experimental_resume = Some(recorder.rollout_path.clone());
+9 -13
View File
@@ -177,11 +177,10 @@ impl CopilotIntegration {
cmd.args(["copilot", "chat"]);
// Add context if provided
if let Some(ctx) = context {
if let Some(file_path) = ctx.get("file_path") {
if let Some(ctx) = context
&& let Some(file_path) = ctx.get("file_path") {
cmd.arg("--file").arg(file_path);
}
}
let output = cmd.arg(initial_message).output().await?;
@@ -204,11 +203,10 @@ impl CopilotIntegration {
}
let prompt = if let Some(d) = diff {
format!("Review this code diff and provide suggestions:\n\n{}", d)
format!("Review this code diff and provide suggestions:\n\n{d}")
} else {
format!(
"Review the code in file {} and provide suggestions",
file_path
"Review the code in file {file_path} and provide suggestions"
)
};
@@ -231,8 +229,7 @@ impl CopilotIntegration {
}
let prompt = format!(
"Generate {} documentation for this code:\n\n{}",
doc_type, code
"Generate {doc_type} documentation for this code:\n\n{code}"
);
self.start_chat_session(&prompt, None).await
@@ -246,7 +243,7 @@ impl CopilotIntegration {
));
}
let prompt = format!("Explain what this code does and how it works:\n\n{}", code);
let prompt = format!("Explain what this code does and how it works:\n\n{code}");
self.start_chat_session(&prompt, None).await
}
@@ -290,8 +287,7 @@ impl CopilotIntegration {
}
let prompt = format!(
"Generate a concise, descriptive Git commit message for these changes:\n\n{}",
diff
"Generate a concise, descriptive Git commit message for these changes:\n\n{diff}"
);
self.start_chat_session(&prompt, None).await
@@ -429,13 +425,13 @@ pub mod utils {
output.push('\n');
if let Some(reasoning) = &suggestion.reasoning {
output.push_str(&format!("Reasoning: {}\n", reasoning));
output.push_str(&format!("Reasoning: {reasoning}\n"));
}
if !suggestion.alternatives.is_empty() {
output.push_str("Alternatives:\n");
for alt in &suggestion.alternatives {
output.push_str(&format!(" - {}\n", alt));
output.push_str(&format!(" - {alt}\n"));
}
}
+9 -12
View File
@@ -169,8 +169,8 @@ impl DebugLogger {
return Ok(());
}
if let Ok(mut streams) = self.active_streams.lock() {
if let Some(stream_info) = streams.get_mut(request_id) {
if let Ok(mut streams) = self.active_streams.lock()
&& let Some(stream_info) = streams.get_mut(request_id) {
let timestamp = Local::now();
let event_entry = serde_json::json!({
"timestamp": timestamp.to_rfc3339(),
@@ -179,7 +179,6 @@ impl DebugLogger {
});
stream_info.events.push(event_entry);
}
}
if let Some(response) = data.get("response") {
if let Some(usage) = response.get("usage") {
@@ -198,8 +197,8 @@ impl DebugLogger {
return Ok(());
}
if let Ok(mut streams) = self.active_streams.lock() {
if let Some(stream_info) = streams.remove(request_id) {
if let Ok(mut streams) = self.active_streams.lock()
&& let Some(stream_info) = streams.remove(request_id) {
// Create the response object with all events as an array
let response_data = serde_json::json!({
"request_id": request_id,
@@ -211,7 +210,6 @@ impl DebugLogger {
let formatted_response = serde_json::to_string_pretty(&response_data)?;
fs::write(&stream_info.response_file, formatted_response)?;
}
}
Ok(())
}
@@ -254,7 +252,7 @@ impl DebugLogger {
} else {
let timestamp = Local::now().format("%Y%m%d_%H%M%S%.3f");
self.usage_dir
.join(format!("{}_{}_usage.json", timestamp, session_id_str))
.join(format!("{timestamp}_{session_id_str}_usage.json"))
};
if let Some(parent) = path.parent() {
@@ -278,7 +276,7 @@ impl DebugLogger {
let path = self
.turn_latency_dir
.join(format!("{}_turn_latency.jsonl", session_id));
.join(format!("{session_id}_turn_latency.jsonl"));
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)?;
}
@@ -339,13 +337,12 @@ impl DebugLogger {
if !path.is_file() {
continue;
}
if let Some(name) = path.file_name().and_then(|n| n.to_str()) {
if name == format!("{}_usage.json", session_id)
|| name.ends_with(&format!("_{}_usage.json", session_id))
if let Some(name) = path.file_name().and_then(|n| n.to_str())
&& (name == format!("{session_id}_usage.json")
|| name.ends_with(&format!("_{session_id}_usage.json")))
{
return Some(path);
}
}
}
}
None
+3 -3
View File
@@ -276,7 +276,7 @@ fn first_command_tokens(argv: &[String]) -> Option<Vec<String>> {
};
match shlex_split(&cmd) {
Some(tokens) => Some(tokens),
None => Some(cmd.split_whitespace().map(|s| s.to_string()).collect()),
None => Some(cmd.split_whitespace().map(std::string::ToString::to_string).collect()),
}
}
@@ -843,7 +843,7 @@ fn analyze_direct_formatter(tokens: &[String]) -> Option<InternalAnalysis> {
}
}
fn peel_exec_wrappers<'a>(mut tokens: &'a [String]) -> &'a [String] {
fn peel_exec_wrappers(mut tokens: &[String]) -> &[String] {
loop {
if tokens.is_empty() {
return tokens;
@@ -876,7 +876,7 @@ fn peel_exec_wrappers<'a>(mut tokens: &'a [String]) -> &'a [String] {
tokens
}
fn skip_leading_flags<'a>(tokens: &'a [String]) -> &'a [String] {
fn skip_leading_flags(tokens: &[String]) -> &[String] {
let mut idx = 0usize;
while idx < tokens.len() {
let token = tokens[idx].as_str();
+14 -18
View File
@@ -304,7 +304,7 @@ impl EnvironmentContext {
if writable_roots.is_empty() {
None
} else {
Some(writable_roots.clone())
Some(writable_roots)
}
}
_ => None,
@@ -410,15 +410,14 @@ impl EnvironmentContext {
}
lines.push(" </operating_system>".to_string());
}
if let Some(common_tools) = self.common_tools {
if !common_tools.is_empty() {
if let Some(common_tools) = self.common_tools
&& !common_tools.is_empty() {
lines.push(" <common_tools>".to_string());
for tool in common_tools {
lines.push(format!(" <tool>{tool}</tool>"));
}
lines.push(" </common_tools>".to_string());
}
}
if let Some(current_date) = self.current_date {
lines.push(format!(" <current_date>{current_date}</current_date>"));
}
@@ -477,8 +476,8 @@ impl EnvironmentContextSnapshot {
Self {
version: Self::VERSION,
cwd: ctx.cwd.as_ref().map(|p| p.display().to_string()),
approval_policy: ctx.approval_policy.clone(),
sandbox_mode: ctx.sandbox_mode.clone(),
approval_policy: ctx.approval_policy,
sandbox_mode: ctx.sandbox_mode,
network_access: ctx.network_access.clone(),
writable_roots: ctx
.writable_roots
@@ -533,13 +532,13 @@ impl EnvironmentContextSnapshot {
if self.approval_policy != previous.approval_policy {
changes.insert(
"approval_policy".to_string(),
serde_json::to_value(&self.approval_policy).unwrap_or(JsonValue::Null),
serde_json::to_value(self.approval_policy).unwrap_or(JsonValue::Null),
);
}
if self.sandbox_mode != previous.sandbox_mode {
changes.insert(
"sandbox_mode".to_string(),
serde_json::to_value(&self.sandbox_mode).unwrap_or(JsonValue::Null),
serde_json::to_value(self.sandbox_mode).unwrap_or(JsonValue::Null),
);
}
if self.network_access != previous.network_access {
@@ -629,7 +628,7 @@ impl EnvironmentContextSnapshot {
for (key, value) in &delta.changes {
match key.as_str() {
"cwd" => {
updated.cwd = value.as_str().map(|s| s.to_string());
updated.cwd = value.as_str().map(std::string::ToString::to_string);
}
"approval_policy" => {
if let Ok(policy) = serde_json::from_value::<AskForApproval>(value.clone()) {
@@ -896,12 +895,11 @@ fn snapshot_to_response_item(
) -> serde_json::Result<ResponseItem> {
let json = serde_json::to_string_pretty(snapshot)?;
Ok(ResponseItem::Message {
id: stream_id.map(|id| id.to_string()),
id: stream_id.map(std::string::ToString::to_string),
role: "user".to_string(),
content: vec![ContentItem::InputText {
text: format!(
"{}\n{}\n{}",
ENVIRONMENT_CONTEXT_OPEN_TAG, json, ENVIRONMENT_CONTEXT_CLOSE_TAG
"{ENVIRONMENT_CONTEXT_OPEN_TAG}\n{json}\n{ENVIRONMENT_CONTEXT_CLOSE_TAG}"
),
}],
})
@@ -913,12 +911,11 @@ fn delta_to_response_item(
) -> serde_json::Result<ResponseItem> {
let json = serde_json::to_string_pretty(delta)?;
Ok(ResponseItem::Message {
id: stream_id.map(|id| id.to_string()),
id: stream_id.map(std::string::ToString::to_string),
role: "user".to_string(),
content: vec![ContentItem::InputText {
text: format!(
"{}\n{}\n{}",
ENVIRONMENT_CONTEXT_DELTA_OPEN_TAG, json, ENVIRONMENT_CONTEXT_DELTA_CLOSE_TAG
"{ENVIRONMENT_CONTEXT_DELTA_OPEN_TAG}\n{json}\n{ENVIRONMENT_CONTEXT_DELTA_CLOSE_TAG}"
),
}],
})
@@ -930,12 +927,11 @@ fn browser_snapshot_to_response_item(
) -> serde_json::Result<ResponseItem> {
let json = serde_json::to_string_pretty(snapshot)?;
Ok(ResponseItem::Message {
id: stream_id.map(|id| id.to_string()),
id: stream_id.map(std::string::ToString::to_string),
role: "user".to_string(),
content: vec![ContentItem::InputText {
text: format!(
"{}\n{}\n{}",
BROWSER_SNAPSHOT_OPEN_TAG, json, BROWSER_SNAPSHOT_CLOSE_TAG
"{BROWSER_SNAPSHOT_OPEN_TAG}\n{json}\n{BROWSER_SNAPSHOT_CLOSE_TAG}"
),
}],
})
+2 -2
View File
@@ -43,7 +43,7 @@ pub enum SandboxErr {
#[error(
"command exceeded memory limit{}",
memory_max_bytes
.map(|bytes| format!(": {} bytes", bytes))
.map(|bytes| format!(": {bytes} bytes"))
.unwrap_or_default()
)]
OutOfMemory {
@@ -74,7 +74,7 @@ impl RetryAfter {
let delay = clamped
.signed_duration_since(now)
.to_std()
.unwrap_or_else(|_| Duration::ZERO);
.unwrap_or(Duration::ZERO);
Self {
delay,
resume_at: clamped,
+3 -3
View File
@@ -65,7 +65,7 @@ pub(crate) fn map_response_item_to_event_messages(
let base_fingerprint = delta
.get("base_fingerprint")
.and_then(|value| value.as_str())
.map(|value| value.to_string());
.map(std::string::ToString::to_string);
events.push(EventMsg::EnvironmentContextDelta(
EnvironmentContextDeltaEvent {
base_fingerprint,
@@ -86,11 +86,11 @@ pub(crate) fn map_response_item_to_event_messages(
let url = snapshot
.get("url")
.and_then(|value| value.as_str())
.map(|value| value.to_string());
.map(std::string::ToString::to_string);
let captured_at = snapshot
.get("captured_at")
.and_then(|value| value.as_str())
.map(|value| value.to_string());
.map(std::string::ToString::to_string);
events.push(EventMsg::BrowserSnapshot(BrowserSnapshotEvent {
snapshot,
url,
@@ -107,7 +107,7 @@ impl TruncatingCollector {
let right_budget = keep_budget - left_budget;
let prefix_slice = pick_prefix_slice(&prefix_str, left_budget);
let suffix_slice = pick_suffix_slice(&suffix_str, right_budget);
let kept_content_bytes = prefix_slice.as_bytes().len() + suffix_slice.as_bytes().len();
let kept_content_bytes = prefix_slice.len() + suffix_slice.len();
let truncated_content_bytes =
self.total_bytes.saturating_sub(kept_content_bytes as u64);
let new_tokens = truncated_content_bytes.div_ceil(4);
@@ -133,7 +133,7 @@ impl TruncatingCollector {
let prefix_slice = pick_prefix_slice(&prefix_str, left_budget);
let suffix_slice = pick_suffix_slice(&suffix_str, right_budget);
let mut out = String::with_capacity(
marker_len + prefix_slice.as_bytes().len() + suffix_slice.as_bytes().len() + 1,
marker_len + prefix_slice.len() + suffix_slice.len() + 1,
);
out.push_str(prefix_slice);
out.push_str(&marker);
@@ -143,28 +143,26 @@ impl TruncatingCollector {
}
}
fn pick_prefix_slice<'a>(input: &'a str, left_budget: usize) -> &'a str {
fn pick_prefix_slice(input: &str, left_budget: usize) -> &str {
if left_budget >= input.len() {
return input;
}
if let Some(head) = input.get(..left_budget) {
if let Some(idx) = head.rfind('\n') {
if let Some(head) = input.get(..left_budget)
&& let Some(idx) = head.rfind('\n') {
return &input[..idx + 1];
}
}
truncate_on_boundary(input, left_budget)
}
fn pick_suffix_slice<'a>(input: &'a str, right_budget: usize) -> &'a str {
fn pick_suffix_slice(input: &str, right_budget: usize) -> &str {
if right_budget >= input.len() {
return input;
}
let tail_start = input.len().saturating_sub(right_budget);
if let Some(tail) = input.get(tail_start..) {
if let Some(idx) = tail.find('\n') {
if let Some(tail) = input.get(tail_start..)
&& let Some(idx) = tail.find('\n') {
return &input[tail_start + idx + 1..];
}
}
let mut idx = tail_start;
while idx < input.len() && !input.is_char_boundary(idx) {
idx += 1;
@@ -172,7 +170,7 @@ fn pick_suffix_slice<'a>(input: &'a str, right_budget: usize) -> &'a str {
&input[idx..]
}
fn truncate_on_boundary<'a>(input: &'a str, max_len: usize) -> &'a str {
fn truncate_on_boundary(input: &str, max_len: usize) -> &str {
if input.len() <= max_len {
return input;
}
+25 -47
View File
@@ -79,34 +79,28 @@ pub async fn collect_git_info(cwd: &Path) -> Option<GitInfo> {
};
// Process commit hash
if let Some(output) = commit_result {
if output.status.success() {
if let Ok(hash) = String::from_utf8(output.stdout) {
if let Some(output) = commit_result
&& output.status.success()
&& let Ok(hash) = String::from_utf8(output.stdout) {
git_info.commit_hash = Some(hash.trim().to_string());
}
}
}
// Process branch name
if let Some(output) = branch_result {
if output.status.success() {
if let Ok(branch) = String::from_utf8(output.stdout) {
if let Some(output) = branch_result
&& output.status.success()
&& let Ok(branch) = String::from_utf8(output.stdout) {
let branch = branch.trim();
if branch != "HEAD" {
git_info.branch = Some(branch.to_string());
}
}
}
}
// Process repository URL
if let Some(output) = url_result {
if output.status.success() {
if let Ok(url) = String::from_utf8(output.stdout) {
if let Some(output) = url_result
&& output.status.success()
&& let Ok(url) = String::from_utf8(output.stdout) {
git_info.repository_url = Some(url.trim().to_string());
}
}
}
Some(git_info)
}
@@ -203,7 +197,7 @@ async fn get_git_remotes(cwd: &Path) -> Option<Vec<String>> {
let mut remotes: Vec<String> = String::from_utf8(output.stdout)
.ok()?
.lines()
.map(|s| s.to_string())
.map(std::string::ToString::to_string)
.collect();
if let Some(pos) = remotes.iter().position(|r| r == "origin") {
let origin = remotes.remove(pos);
@@ -232,23 +226,19 @@ async fn get_default_branch(cwd: &Path) -> Option<String> {
cwd,
)
.await
{
if symref_output.status.success() {
if let Ok(sym) = String::from_utf8(symref_output.stdout) {
&& symref_output.status.success()
&& let Ok(sym) = String::from_utf8(symref_output.stdout) {
let trimmed = sym.trim();
if let Some((_, name)) = trimmed.rsplit_once('/') {
return Some(name.to_string());
}
}
}
}
// Fall back to parsing `git remote show <remote>` output
if let Some(show_output) =
run_git_command_with_timeout(&["remote", "show", &remote], cwd).await
{
if show_output.status.success() {
if let Ok(text) = String::from_utf8(show_output.stdout) {
&& show_output.status.success()
&& let Ok(text) = String::from_utf8(show_output.stdout) {
for line in text.lines() {
let line = line.trim();
if let Some(rest) = line.strip_prefix("HEAD branch:") {
@@ -259,8 +249,6 @@ async fn get_default_branch(cwd: &Path) -> Option<String> {
}
}
}
}
}
}
// No remote-derived default; try common local defaults if they exist
@@ -280,11 +268,9 @@ async fn get_default_branch_local(cwd: &Path) -> Option<String> {
cwd,
)
.await
{
if verify.status.success() {
&& verify.status.success() {
return Some(candidate.to_string());
}
}
}
None
@@ -315,12 +301,11 @@ async fn branch_ancestry(cwd: &Path) -> Option<Vec<String>> {
seen.insert(cb.clone());
ancestry.push(cb);
}
if let Some(db) = default_branch.clone() {
if !seen.contains(&db) {
if let Some(db) = default_branch.clone()
&& !seen.contains(&db) {
seen.insert(db.clone());
ancestry.push(db);
}
}
// Expand candidates: include any remote branches that already contain HEAD.
// This addresses cases where we're on a new local-only branch forked from a
@@ -338,21 +323,17 @@ async fn branch_ancestry(cwd: &Path) -> Option<Vec<String>> {
cwd,
)
.await
{
if output.status.success() {
if let Ok(text) = String::from_utf8(output.stdout) {
&& output.status.success()
&& let Ok(text) = String::from_utf8(output.stdout) {
for line in text.lines() {
let short = line.trim();
if let Some(stripped) = short.strip_prefix(&format!("{remote}/")) {
if !stripped.is_empty() && !seen.contains(stripped) {
if let Some(stripped) = short.strip_prefix(&format!("{remote}/"))
&& !stripped.is_empty() && !seen.contains(stripped) {
seen.insert(stripped.to_string());
ancestry.push(stripped.to_string());
}
}
}
}
}
}
}
// Ensure we return Some vector, even if empty, to allow caller logic to proceed
@@ -480,12 +461,11 @@ async fn diff_against_sha(cwd: &Path, sha: &GitSha) -> Option<String> {
if let Some(untracked_output) =
run_git_command_with_timeout(&["ls-files", "--others", "--exclude-standard"], cwd).await
{
if untracked_output.status.success() {
&& untracked_output.status.success() {
let untracked: Vec<String> = String::from_utf8(untracked_output.stdout)
.ok()?
.lines()
.map(|s| s.to_string())
.map(std::string::ToString::to_string)
.filter(|s| !s.is_empty())
.collect();
@@ -498,15 +478,13 @@ async fn diff_against_sha(cwd: &Path, sha: &GitSha) -> Option<String> {
});
let results = join_all(futures_iter).await;
for extra in results.into_iter().flatten() {
if extra.status.code().is_some_and(|c| c == 0 || c == 1) {
if let Ok(s) = String::from_utf8(extra.stdout) {
if extra.status.code().is_some_and(|c| c == 0 || c == 1)
&& let Ok(s) = String::from_utf8(extra.stdout) {
diff.push_str(&s);
}
}
}
}
}
}
Some(diff)
}
+31 -41
View File
@@ -89,12 +89,12 @@ pub fn generate_branch_name_from_task(task: Option<&str>) -> String {
slug = "branch".to_string();
}
}
return format!("dev-branch-{}", slug);
return format!("dev-branch-{slug}");
}
}
// Fallback: timestamped id
let ts = Utc::now().format("%Y%m%d-%H%M%S");
format!("dev-branch-{}", ts)
format!("dev-branch-{ts}")
}
pub const LOCAL_DEFAULT_REMOTE: &str = "local-default";
@@ -120,7 +120,7 @@ pub async fn get_git_root_from(cwd: &Path) -> Result<PathBuf, String> {
.current_dir(cwd)
.output()
.await
.map_err(|e| format!("Git not installed or not in a git repository: {}", e))?;
.map_err(|e| format!("Git not installed or not in a git repository: {e}"))?;
if output.status.success() {
let path = String::from_utf8_lossy(&output.stdout).trim().to_string();
@@ -154,7 +154,7 @@ pub async fn setup_worktree(
.join("branches");
tokio::fs::create_dir_all(&code_dir)
.await
.map_err(|e| format!("Failed to create .hanzo/branches directory: {}", e))?;
.map_err(|e| format!("Failed to create .hanzo/branches directory: {e}"))?;
let mut effective_branch = branch_id.to_string();
let mut worktree_path = code_dir.join(&effective_branch);
@@ -168,7 +168,7 @@ pub async fn setup_worktree(
.args(["reset", "--hard", base])
.output()
.await
.map_err(|e| format!("Failed to reset existing worktree: {}", e))?;
.map_err(|e| format!("Failed to reset existing worktree: {e}"))?;
if !reset.status.success() {
let stderr = String::from_utf8_lossy(&reset.stderr);
return Err(format!("Failed to reset existing worktree: {stderr}"));
@@ -178,7 +178,7 @@ pub async fn setup_worktree(
.args(["clean", "-fd"])
.output()
.await
.map_err(|e| format!("Failed to clean existing worktree: {}", e))?;
.map_err(|e| format!("Failed to clean existing worktree: {e}"))?;
if !clean.status.success() {
let stderr = String::from_utf8_lossy(&clean.stderr);
return Err(format!("Failed to clean existing worktree: {stderr}"));
@@ -204,11 +204,9 @@ pub async fn setup_worktree(
.current_dir(git_root)
.output()
.await
{
if out.status.success() {
&& out.status.success() {
bump_snapshot_epoch_for(&worktree_path);
}
}
}
let mut args = vec![
@@ -346,7 +344,7 @@ pub async fn prepare_reusable_worktree(
.join("branches");
tokio::fs::create_dir_all(&code_dir)
.await
.map_err(|e| format!("Failed to create .hanzo/branches directory: {}", e))?;
.map_err(|e| format!("Failed to create .hanzo/branches directory: {e}"))?;
let worktree_path = code_dir.join(worktree_name);
@@ -358,7 +356,7 @@ pub async fn prepare_reusable_worktree(
.args(["reset", "--hard", base_ref])
.output()
.await
.map_err(|e| format!("Failed to reset reusable worktree: {}", e))?;
.map_err(|e| format!("Failed to reset reusable worktree: {e}"))?;
if !reset.status.success() {
let stderr = String::from_utf8_lossy(&reset.stderr);
return Err(format!("Failed to reset reusable worktree: {stderr}"));
@@ -374,7 +372,7 @@ pub async fn prepare_reusable_worktree(
.args(&clean_args)
.output()
.await
.map_err(|e| format!("Failed to clean reusable worktree: {}", e))?;
.map_err(|e| format!("Failed to clean reusable worktree: {e}"))?;
if !clean.status.success() {
let stderr = String::from_utf8_lossy(&clean.stderr);
return Err(format!("Failed to clean reusable worktree: {stderr}"));
@@ -470,7 +468,7 @@ async fn record_worktree_in_session(git_root: &Path, worktree_path: &Path) {
if let Err(_e) = tokio::fs::create_dir_all(&base).await {
return;
}
let file = base.join(format!("pid-{}.txt", pid));
let file = base.join(format!("pid-{pid}.txt"));
// Store git_root and worktree_path separated by a tab; one entry per line.
if let Ok(mut f) = OpenOptions::new()
.create(true)
@@ -532,9 +530,9 @@ pub async fn ensure_local_default_remote(
}
let base_branch_clean = base_branch
.map(|s| s.trim())
.map(str::trim)
.filter(|s| !s.is_empty() && *s != "HEAD")
.map(|s| s.to_string());
.map(std::string::ToString::to_string);
let base_branch_clean = match base_branch_clean {
Some(value) => Some(value),
None => detect_default_branch(git_root).await,
@@ -615,13 +613,11 @@ pub async fn write_branch_metadata(
}
pub fn load_branch_metadata(worktree_path: &Path) -> Option<BranchMetadata> {
if let Some(path) = metadata_file_path(worktree_path) {
if let Ok(bytes) = stdfs::read(&path) {
if let Ok(parsed) = serde_json::from_slice(&bytes) {
if let Some(path) = metadata_file_path(worktree_path)
&& let Ok(bytes) = stdfs::read(&path)
&& let Ok(parsed) = serde_json::from_slice(&bytes) {
return Some(parsed);
}
}
}
let legacy_path = worktree_path.join(".codex-branch.json");
let bytes = stdfs::read(legacy_path).ok()?;
serde_json::from_slice(&bytes).ok()
@@ -646,7 +642,7 @@ async fn _ensure_origin_remote(git_root: &Path) -> Result<(), String> {
.args(["remote"])
.output()
.await
.map_err(|e| format!("git remote failed: {}", e))?;
.map_err(|e| format!("git remote failed: {e}"))?;
if !remotes_out.status.success() {
return Err("git remote returned error".to_string());
}
@@ -681,8 +677,8 @@ async fn _ensure_origin_remote(git_root: &Path) -> Result<(), String> {
.args(["remote", "get-url", cand])
.output()
.await;
if let Ok(out) = url_out {
if out.status.success() {
if let Ok(out) = url_out
&& out.status.success() {
let url = String::from_utf8_lossy(&out.stdout).trim().to_string();
if !url.is_empty() {
// Add origin pointing to this URL
@@ -691,7 +687,7 @@ async fn _ensure_origin_remote(git_root: &Path) -> Result<(), String> {
.args(["remote", "add", "origin", &url])
.output()
.await
.map_err(|e| format!("git remote add origin failed: {}", e))?;
.map_err(|e| format!("git remote add origin failed: {e}"))?;
if !add.status.success() {
return Err("failed to add origin".to_string());
}
@@ -704,7 +700,6 @@ async fn _ensure_origin_remote(git_root: &Path) -> Result<(), String> {
return Ok(());
}
}
}
}
// No usable remote found; leave as-is
Err("no suitable remote to alias as origin".to_string())
@@ -722,10 +717,10 @@ pub async fn copy_uncommitted_to_worktree(
.args(["ls-files", "-om", "--exclude-standard", "-z"])
.output()
.await
.map_err(|e| format!("Failed to list changes: {}", e))?;
.map_err(|e| format!("Failed to list changes: {e}"))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(format!("git ls-files failed: {}", stderr));
return Err(format!("git ls-files failed: {stderr}"));
}
let bytes = output.stdout;
let mut count = 0usize;
@@ -775,10 +770,10 @@ pub async fn copy_uncommitted_to_worktree(
.args(["ls-files", "-d", "-z"])
.output()
.await
.map_err(|e| format!("Failed to list deletions: {}", e))?;
.map_err(|e| format!("Failed to list deletions: {e}"))?;
if !deleted.status.success() {
let stderr = String::from_utf8_lossy(&deleted.stderr);
return Err(format!("git ls-files -d failed: {}", stderr));
return Err(format!("git ls-files -d failed: {stderr}"));
}
for path_bytes in deleted.stdout.split(|b| *b == 0) {
if path_bytes.is_empty() {
@@ -812,14 +807,13 @@ pub async fn copy_uncommitted_to_worktree(
.map(|v| v.to_ascii_lowercase())
.map(|v| v == "1" || v == "true" || v == "yes")
.unwrap_or(false);
if include_submods {
if let Ok(out) = Command::new("git")
if include_submods
&& let Ok(out) = Command::new("git")
.current_dir(src_root)
.args(["submodule", "status", "--recursive"])
.output()
.await
{
if out.status.success() {
&& out.status.success() {
let text = String::from_utf8_lossy(&out.stdout);
for line in text.lines() {
let line = line.trim();
@@ -836,7 +830,7 @@ pub async fn copy_uncommitted_to_worktree(
Some(p) => p,
None => continue,
};
let spec = format!("160000,{},{}", sha, path);
let spec = format!("160000,{sha},{path}");
let _ = Command::new("git")
.current_dir(worktree_path)
.args(["update-index", "--add", "--cacheinfo", &spec])
@@ -844,8 +838,6 @@ pub async fn copy_uncommitted_to_worktree(
.await;
}
}
}
}
Ok(count)
}
@@ -858,13 +850,11 @@ pub async fn detect_default_branch(cwd: &Path) -> Option<String> {
.output()
.await
.ok()?;
if sym.status.success() {
if let Ok(s) = String::from_utf8(sym.stdout) {
if let Some((_, name)) = s.trim().rsplit_once('/') {
if sym.status.success()
&& let Ok(s) = String::from_utf8(sym.stdout)
&& let Some((_, name)) = s.trim().rsplit_once('/') {
return Some(name.to_string());
}
}
}
// Fallback to local main/master
for candidate in ["main", "master"] {
let out = Command::new("git")
+49 -75
View File
@@ -428,6 +428,7 @@ pub enum TextTone {
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Default)]
pub struct TextEmphasis {
pub bold: bool,
pub italic: bool,
@@ -436,17 +437,6 @@ pub struct TextEmphasis {
pub underline: bool,
}
impl Default for TextEmphasis {
fn default() -> Self {
Self {
bold: false,
italic: false,
dim: false,
strike: false,
underline: false,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum TextEntity {
@@ -764,13 +754,12 @@ fn prune_exec_stream(chunks: &mut Vec<ExecStreamChunk>, max_bytes: usize) {
chunks.drain(..drop_chunks);
}
if bytes_to_drop > 0 {
if let Some(first) = chunks.first_mut() {
if bytes_to_drop > 0
&& let Some(first) = chunks.first_mut() {
let drain = bytes_to_drop.min(first.content.len());
first.offset = first.offset.saturating_add(drain);
first.content.drain(..drain);
}
}
}
fn append_assistant_delta(
@@ -1013,18 +1002,15 @@ pub struct NoticeRecord {
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[derive(Default)]
pub enum ContextDeltaField {
#[default]
Cwd,
GitBranch,
ReasoningEffort,
BrowserSnapshot,
}
impl Default for ContextDeltaField {
fn default() -> Self {
ContextDeltaField::Cwd
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct ContextDeltaRecord {
@@ -1110,7 +1096,7 @@ pub const MAX_ASSISTANT_STREAM_RETAINED_BYTES: usize = 6 * 1024 * 1024; // 6 MiB
const ASSISTANT_STREAM_CHUNK_THRESHOLD: usize = 2048;
const ASSISTANT_STREAM_CHUNK_STEP: usize = 256;
const ASSISTANT_STREAM_BYTE_THRESHOLD: usize = 6 * 1024 * 1024;
const ASSISTANT_STREAM_BYTE_STEP: usize = 1 * 1024 * 1024;
const ASSISTANT_STREAM_BYTE_STEP: usize = 1024 * 1024;
#[derive(Default, Clone, Debug, PartialEq, Eq)]
struct StreamLogState {
@@ -1207,20 +1193,16 @@ impl HistoryUsageTracker {
}
fn restore_snapshot(&mut self, record: &HistoryRecord, snapshot: UsageTrackerSnapshot) {
if let Some(state) = snapshot.exec {
if let HistoryRecord::Exec(exec_record) = record {
if exec_record.id != HistoryId::ZERO {
if let Some(state) = snapshot.exec
&& let HistoryRecord::Exec(exec_record) = record
&& exec_record.id != HistoryId::ZERO {
self.exec.insert(exec_record.id, state);
}
}
}
if let Some(state) = snapshot.assistant {
if let HistoryRecord::AssistantStream(stream_record) = record {
if stream_record.id != HistoryId::ZERO {
if let Some(state) = snapshot.assistant
&& let HistoryRecord::AssistantStream(stream_record) = record
&& stream_record.id != HistoryId::ZERO {
self.assistant.insert(stream_record.id, state);
}
}
}
}
fn observe_exec(&mut self, record: &ExecRecord, label: &'static str) {
@@ -1351,11 +1333,10 @@ fn assistant_preview(state: &AssistantStreamState) -> String {
if !state.preview_markdown.trim().is_empty() {
return truncate_display(state.preview_markdown.trim());
}
if let Some(last) = state.deltas.last() {
if !last.delta.trim().is_empty() {
if let Some(last) = state.deltas.last()
&& !last.delta.trim().is_empty() {
return truncate_display(last.delta.trim());
}
}
"<empty preview>".to_string()
}
@@ -1420,6 +1401,12 @@ pub struct HistoryState {
}
#[allow(dead_code)]
impl Default for HistoryState {
fn default() -> Self {
Self::new()
}
}
impl HistoryState {
pub fn new() -> Self {
Self {
@@ -1472,12 +1459,12 @@ impl HistoryState {
) -> AssistantMessageState {
let mut carried_citations: Vec<String> = Vec::new();
let mut carried_metadata: Option<MessageMetadata> = None;
if let Some(stream_id) = stream_id {
if let Some(idx) = self.records.iter().position(|record| match record {
if let Some(stream_id) = stream_id
&& let Some(idx) = self.records.iter().position(|record| match record {
HistoryRecord::AssistantStream(state) => state.stream_id == stream_id,
_ => false,
}) {
if let Some(HistoryRecord::AssistantStream(state)) = self.remove(idx) {
})
&& let Some(HistoryRecord::AssistantStream(state)) = self.remove(idx) {
if !state.citations.is_empty() {
carried_citations = state.citations;
}
@@ -1485,8 +1472,6 @@ impl HistoryState {
carried_metadata = state.metadata;
}
}
}
}
let citations = metadata
.map(|meta| meta.citations.clone())
@@ -1500,27 +1485,25 @@ impl HistoryState {
// duplicating an identical assistant message that was already inserted
// earlier (e.g. during streaming). Instead, refresh the existing record
// in place so snapshots remain deduplicated.
if let Some(stream_id) = stream_id {
if let Some(idx) = self.records.iter().rposition(|record| match record {
if let Some(stream_id) = stream_id
&& let Some(idx) = self.records.iter().rposition(|record| match record {
HistoryRecord::AssistantMessage(state) => {
state.stream_id.as_deref() == Some(stream_id)
}
_ => false,
}) {
if let HistoryRecord::AssistantMessage(existing) = &mut self.records[idx] {
})
&& let HistoryRecord::AssistantMessage(existing) = &mut self.records[idx] {
existing.markdown = markdown;
existing.citations = citations.clone();
existing.metadata = metadata.clone();
existing.token_usage = token_usage.clone();
existing.citations = citations;
existing.metadata = metadata;
existing.token_usage = token_usage;
existing.created_at = SystemTime::now();
return existing.clone();
}
}
}
let mut state = AssistantMessageState {
id: HistoryId::ZERO,
stream_id: stream_id.map(|s| s.to_string()),
stream_id: stream_id.map(std::string::ToString::to_string),
markdown,
citations,
metadata,
@@ -1774,51 +1757,47 @@ impl HistoryState {
fn unregister_record(&mut self, record: &HistoryRecord) {
match record {
HistoryRecord::Exec(state) => {
if let Some(call_id) = state.call_id.as_ref() {
if self
if let Some(call_id) = state.call_id.as_ref()
&& self
.exec_call_lookup
.get(call_id)
.is_some_and(|id| *id == state.id)
{
self.exec_call_lookup.remove(call_id);
}
}
self.remove_exec_stream_retained(state.id);
}
HistoryRecord::MergedExec(state) => {
for segment in &state.segments {
if let Some(call_id) = segment.call_id.as_ref() {
if self
if let Some(call_id) = segment.call_id.as_ref()
&& self
.exec_call_lookup
.get(call_id)
.is_some_and(|id| *id == state.id)
{
self.exec_call_lookup.remove(call_id);
}
}
}
}
HistoryRecord::RunningTool(state) => {
if let Some(call_id) = state.call_id.as_ref() {
if self
if let Some(call_id) = state.call_id.as_ref()
&& self
.tool_call_lookup
.get(call_id)
.is_some_and(|id| *id == state.id)
{
self.tool_call_lookup.remove(call_id);
}
}
}
HistoryRecord::ToolCall(state) => {
if let Some(call_id) = state.call_id.as_ref() {
if self
if let Some(call_id) = state.call_id.as_ref()
&& self
.tool_call_lookup
.get(call_id)
.is_some_and(|id| *id == state.id)
{
self.tool_call_lookup.remove(call_id);
}
}
}
HistoryRecord::AssistantStream(state) => {
if self
@@ -1977,8 +1956,8 @@ impl HistoryState {
if let Some(idx) = self.records.iter().position(|record| {
matches!(record,
HistoryRecord::AssistantStream(state) if state.stream_id == stream_id)
}) {
if let Some(HistoryRecord::AssistantStream(existing)) =
})
&& let Some(HistoryRecord::AssistantStream(existing)) =
self.records.get(idx).cloned()
{
let mut updated = existing;
@@ -2009,7 +1988,6 @@ impl HistoryState {
return mutation;
}
}
}
let mut deltas = Vec::new();
let mut truncated_prefix_bytes = 0usize;
@@ -2114,13 +2092,11 @@ impl HistoryState {
stderr_tail,
} => {
let mut target_idx = id.and_then(|hid| self.index_of(hid));
if target_idx.is_none() {
if let Some(call_id) = call_id.as_ref() {
if let Some(mapped_id) = self.history_id_for_exec_call(call_id) {
if target_idx.is_none()
&& let Some(call_id) = call_id.as_ref()
&& let Some(mapped_id) = self.history_id_for_exec_call(call_id) {
target_idx = self.index_of(mapped_id);
}
}
}
if let Some(idx) = target_idx {
if let Some(HistoryRecord::Exec(existing)) = self.records.get(idx).cloned() {
@@ -2132,8 +2108,8 @@ impl HistoryState {
updated.wait_active = wait_active;
updated.wait_notes = wait_notes;
if let Some(tail) = stdout_tail {
if !tail.is_empty() {
if let Some(tail) = stdout_tail
&& !tail.is_empty() {
let offset = stream_len(&updated.stdout_chunks);
self.usage_tracker.add_exec_delta(updated.id, 1, tail.len());
append_exec_chunk(
@@ -2144,9 +2120,8 @@ impl HistoryState {
},
);
}
}
if let Some(tail) = stderr_tail {
if !tail.is_empty() {
if let Some(tail) = stderr_tail
&& !tail.is_empty() {
let offset = stream_len(&updated.stderr_chunks);
self.usage_tracker.add_exec_delta(updated.id, 1, tail.len());
append_exec_chunk(
@@ -2157,7 +2132,6 @@ impl HistoryState {
},
);
}
}
self.usage_tracker
.observe_exec(&updated, "domain:finish-exec");
+10 -15
View File
@@ -161,24 +161,22 @@ fn perform_housekeeping(
) -> io::Result<CleanupOutcome> {
let mut outcome = CleanupOutcome::default();
if let Some(days) = config.session_retention_days {
if let Some(stats) = cleanup_sessions(code_home, now.date(), days)? {
if let Some(days) = config.session_retention_days
&& let Some(stats) = cleanup_sessions(code_home, now.date(), days)? {
outcome.session_days_removed = stats.removed_days;
outcome.session_files_removed = stats.removed_files;
outcome.session_bytes_reclaimed = stats.reclaimed_bytes;
outcome.errors += stats.errors;
}
}
if let Some(days) = config.worktree_retention_days {
if let Some(stats) = cleanup_worktrees(code_home, now, days)? {
if let Some(days) = config.worktree_retention_days
&& let Some(stats) = cleanup_worktrees(code_home, now, days)? {
outcome.worktrees_removed = stats.removed_worktrees;
outcome.worktree_files_removed = stats.removed_files;
outcome.worktree_bytes_reclaimed = stats.reclaimed_bytes;
outcome.worktrees_skipped_active = stats.skipped_active;
outcome.errors += stats.errors;
}
}
Ok(outcome)
}
@@ -376,11 +374,10 @@ fn cleanup_worktrees(
git_worktree::remove_branch_metadata(&branch_path);
purge_session_registry(&working_root.join("_session"), &branch_path);
if let Some(repo_root) = repo_root.as_deref() {
if should_prune_worktree_branch(&branch_name) {
if let Some(repo_root) = repo_root.as_deref()
&& should_prune_worktree_branch(&branch_name) {
run_git_branch_delete_if_merged(repo_root, &branch_name);
}
}
stats.removed_worktrees += 1;
stats.removed_files += dir_stats.files;
@@ -575,7 +572,7 @@ fn purge_session_registry(session_dir: &Path, worktree_path: &Path) {
for line in data.lines() {
if line
.split_once('\t')
.map_or(false, |(_, path)| path == worktree_str)
.is_some_and(|(_, path)| path == worktree_str)
{
changed = true;
} else if !line.trim().is_empty() {
@@ -591,13 +588,11 @@ fn purge_session_registry(session_dir: &Path, worktree_path: &Path) {
.write(true)
.truncate(true)
.open(&file_path)
{
if !kept.is_empty() {
&& !kept.is_empty() {
let content = kept.join("\n");
let _ = file.write_all(content.as_bytes());
let _ = file.write_all(b"\n");
}
}
}
}
@@ -721,7 +716,7 @@ fn list_dir_sorted(path: &Path) -> Vec<fs::DirEntry> {
Ok(it) => it.flatten().collect(),
Err(_) => Vec::new(),
};
entries.sort_by(|a, b| a.file_name().cmp(&b.file_name()));
entries.sort_by_key(std::fs::DirEntry::file_name);
entries
}
@@ -778,7 +773,7 @@ fn write_state(path: &Path, state: &CleanupState) -> io::Result<()> {
}
let mut file = opts.open(path)?;
let data =
serde_json::to_vec(state).map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
serde_json::to_vec(state).map_err(io::Error::other)?;
file.write_all(&data)?;
file.write_all(b"\n")?;
file.sync_all()
+6 -11
View File
@@ -21,34 +21,29 @@ pub fn build_http_client() -> reqwest::Client {
// Single-file variables (common across ecosystems)
for var in ["SSL_CERT_FILE", "REQUESTS_CA_BUNDLE", "NODE_EXTRA_CA_CERTS"] {
if let Ok(val) = std::env::var(var) {
if !val.trim().is_empty() {
if let Some(cert) = load_cert(PathBuf::from(val)) {
if let Ok(val) = std::env::var(var)
&& !val.trim().is_empty()
&& let Some(cert) = load_cert(PathBuf::from(val)) {
builder = builder.add_root_certificate(cert);
}
}
}
}
// Directory of certs (PEM/CRT). Iterate best-effort.
if let Ok(dir) = std::env::var("SSL_CERT_DIR") {
let path = PathBuf::from(dir);
if path.is_dir() {
if let Ok(rd) = fs::read_dir(path) {
if path.is_dir()
&& let Ok(rd) = fs::read_dir(path) {
for entry in rd.flatten() {
let p = entry.path();
if p.extension()
.and_then(|s| s.to_str())
.map(|s| matches!(s, "crt" | "pem" | "der"))
.unwrap_or(false)
{
if let Some(cert) = load_cert(p) {
&& let Some(cert) = load_cert(p) {
builder = builder.add_root_certificate(cert);
}
}
}
}
}
}
builder.build().unwrap_or_else(|_| reqwest::Client::new())
+2 -2
View File
@@ -44,13 +44,13 @@ pub fn are_hashes_similar(phash1: &[u8], dhash1: &[u8], phash2: &[u8], dhash2: &
let phash_dist = phash1
.iter()
.zip(phash2.iter())
.map(|(a, b)| (a ^ b).count_ones() as u32)
.map(|(a, b)| (a ^ b).count_ones())
.sum::<u32>();
let dhash_dist = dhash1
.iter()
.zip(dhash2.iter())
.map(|(a, b)| (a ^ b).count_ones() as u32)
.map(|(a, b)| (a ^ b).count_ones())
.sum::<u32>();
// 256 bits → ~5% tolerance (≈13 bits)
+3 -5
View File
@@ -289,13 +289,11 @@ impl McpConnectionManager {
.with_context(|| {
if args_for_error.is_empty() {
format!(
"failed to spawn MCP server `{}` using command `{}`",
server_name_for_error, command_for_error
"failed to spawn MCP server `{server_name_for_error}` using command `{command_for_error}`"
)
} else {
format!(
"failed to spawn MCP server `{}` using command `{}` with args {:?}",
server_name_for_error, command_for_error, args_for_error
"failed to spawn MCP server `{server_name_for_error}` using command `{command_for_error}` with args {args_for_error:?}"
)
}
})
@@ -460,7 +458,7 @@ async fn list_all_tools(
}
Err(err) => {
warn!("Failed to list tools for MCP server '{server_name}': {err:#?}");
errors.insert(server_name, err.into());
errors.insert(server_name, err);
}
}
}
+1 -1
View File
@@ -124,7 +124,7 @@ pub(crate) async fn append_entry(text: &str, session_id: &Uuid, config: &Config)
std::thread::sleep(RETRY_SLEEP);
continue;
}
return Err(e.into());
return Err(e);
}
}
}
+7 -14
View File
@@ -150,30 +150,24 @@ pub struct OpenRouterProviderConfig {
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
#[serde(rename_all = "lowercase")]
#[derive(Default)]
pub enum OpenRouterDataCollectionPolicy {
#[default]
Allow,
Deny,
}
impl Default for OpenRouterDataCollectionPolicy {
fn default() -> Self {
OpenRouterDataCollectionPolicy::Allow
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
#[serde(rename_all = "lowercase")]
#[derive(Default)]
pub enum OpenRouterProviderSort {
#[default]
Price,
Throughput,
Latency,
}
impl Default for OpenRouterProviderSort {
fn default() -> Self {
OpenRouterProviderSort::Price
}
}
/// `max_price` envelope for OpenRouter provider routing controls.
#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq)]
@@ -379,7 +373,7 @@ impl ModelProviderInfo {
self.base_url
.as_ref()
.map_or(false, |base| base.contains("/backend-api"))
.is_some_and(|base| base.contains("/backend-api"))
}
pub(crate) fn is_public_openai_responses_endpoint(&self) -> bool {
@@ -416,11 +410,10 @@ impl ModelProviderInfo {
if let Some(env_headers) = &self.env_http_headers {
for (header, env_var) in env_headers {
if let Ok(val) = std::env::var(env_var) {
if !val.trim().is_empty() {
if let Ok(val) = std::env::var(env_var)
&& !val.trim().is_empty() {
builder = builder.header(header, val);
}
}
}
}
builder
+8 -11
View File
@@ -128,7 +128,7 @@ impl ToolsConfig {
};
if matches!(approval_policy, AskForApproval::OnRequest) && !use_streamable_shell_tool {
shell_type = ConfigShellToolType::ShellWithRequest {
sandbox_policy: sandbox_policy.clone(),
sandbox_policy,
}
}
@@ -606,13 +606,12 @@ fn sanitize_json_schema(value: &mut JsonValue) {
}
JsonValue::Object(map) => {
// First, recursively sanitize known nested schema holders
if let Some(props) = map.get_mut("properties") {
if let Some(props_map) = props.as_object_mut() {
if let Some(props) = map.get_mut("properties")
&& let Some(props_map) = props.as_object_mut() {
for (_k, v) in props_map.iter_mut() {
sanitize_json_schema(v);
}
}
}
if let Some(items) = map.get_mut("items") {
sanitize_json_schema(items);
}
@@ -627,21 +626,19 @@ fn sanitize_json_schema(value: &mut JsonValue) {
let mut ty = map.get("type").and_then(|v| v.as_str()).map(str::to_string);
// If type is an array (union), pick first supported; else leave to inference
if ty.is_none() {
if let Some(JsonValue::Array(types)) = map.get("type") {
if ty.is_none()
&& let Some(JsonValue::Array(types)) = map.get("type") {
for t in types {
if let Some(tt) = t.as_str() {
if matches!(
if let Some(tt) = t.as_str()
&& matches!(
tt,
"object" | "array" | "string" | "number" | "integer" | "boolean"
) {
ty = Some(tt.to_string());
break;
}
}
}
}
}
// Infer type if still missing
if ty.is_none() {
@@ -1771,7 +1768,7 @@ fn create_browser_tool(browser_enabled: bool) -> OpenAiTool {
"Required: choose one of the supported browser actions (e.g., 'open', 'click', 'fetch')."
.to_string(),
),
allowed_values: Some(actions.iter().map(|value| value.to_string()).collect()),
allowed_values: Some(actions.iter().map(std::string::ToString::to_string).collect()),
},
);
+19 -21
View File
@@ -44,7 +44,7 @@ impl From<ParsedCommand> for hanzo_protocol::parse_command::ParsedCommand {
}
fn shlex_join(tokens: &[String]) -> String {
shlex_try_join(tokens.iter().map(|s| s.as_str()))
shlex_try_join(tokens.iter().map(std::string::String::as_str))
.unwrap_or_else(|_| "<command included NUL byte>".to_string())
}
@@ -1053,7 +1053,7 @@ pub fn parse_command_impl(command: &[String]) -> Vec<ParsedCommand> {
let parts = if contains_connectors(&normalized) {
split_on_connectors(&normalized)
} else {
vec![normalized.clone()]
vec![normalized]
};
// Preserve left-to-right execution order for all commands, including bash -c/-lc
@@ -1078,16 +1078,15 @@ fn simplify_once(commands: &[ParsedCommand]) -> Option<Vec<ParsedCommand>> {
}
// echo ... && ...rest => ...rest
if let ParsedCommand::Unknown { cmd } = &commands[0] {
if shlex_split(cmd).is_some_and(|t| t.first().map(|s| s.as_str()) == Some("echo")) {
if let ParsedCommand::Unknown { cmd } = &commands[0]
&& shlex_split(cmd).is_some_and(|t| t.first().map(std::string::String::as_str) == Some("echo")) {
return Some(commands[1..].to_vec());
}
}
// cd foo && [any command] => [any command] (keep non-cd when a cd is followed by something)
if let Some(idx) = commands.iter().position(|pc| match pc {
ParsedCommand::Unknown { cmd } => {
shlex_split(cmd).is_some_and(|t| t.first().map(|s| s.as_str()) == Some("cd"))
shlex_split(cmd).is_some_and(|t| t.first().map(std::string::String::as_str) == Some("cd"))
}
_ => false,
}) {
@@ -1223,7 +1222,7 @@ fn short_display_path(path: &str) -> String {
});
parts
.next()
.map(|s| s.to_string())
.map(std::string::ToString::to_string)
.unwrap_or_else(|| trimmed.to_string())
}
@@ -1452,8 +1451,8 @@ fn parse_bash_lc_commands(original: &[String]) -> Option<Vec<ParsedCommand>> {
if !is_shell_executable(bash) || flag != "-lc" {
return None;
}
if let Some(tree) = try_parse_bash(script) {
if let Some(all_commands) = try_parse_word_only_commands_sequence(&tree, script) {
if let Some(tree) = try_parse_bash(script)
&& let Some(all_commands) = try_parse_word_only_commands_sequence(&tree, script) {
if all_commands.is_empty() {
return None;
}
@@ -1497,8 +1496,8 @@ fn parse_bash_lc_commands(original: &[String]) -> Option<Vec<ParsedCommand>> {
if had_connectors {
let has_pipe = script_tokens.iter().any(|t| t == "|");
let has_sed_n = script_tokens.windows(2).any(|w| {
w.first().map(|s| s.as_str()) == Some("sed")
&& w.get(1).map(|s| s.as_str()) == Some("-n")
w.first().map(std::string::String::as_str) == Some("sed")
&& w.get(1).map(std::string::String::as_str) == Some("-n")
});
if has_pipe && has_sed_n {
ParsedCommand::Read {
@@ -1507,7 +1506,7 @@ fn parse_bash_lc_commands(original: &[String]) -> Option<Vec<ParsedCommand>> {
}
} else {
ParsedCommand::Read {
cmd: cmd.clone(),
cmd,
name,
}
}
@@ -1521,7 +1520,7 @@ fn parse_bash_lc_commands(original: &[String]) -> Option<Vec<ParsedCommand>> {
ParsedCommand::ListFiles { path, cmd, .. } => {
if had_connectors {
ParsedCommand::ListFiles {
cmd: cmd.clone(),
cmd,
path,
}
} else {
@@ -1536,7 +1535,7 @@ fn parse_bash_lc_commands(original: &[String]) -> Option<Vec<ParsedCommand>> {
} => {
if had_connectors {
ParsedCommand::Search {
cmd: cmd.clone(),
cmd,
query,
path,
}
@@ -1554,7 +1553,6 @@ fn parse_bash_lc_commands(original: &[String]) -> Option<Vec<ParsedCommand>> {
}
return Some(commands);
}
}
None
}
@@ -1587,7 +1585,7 @@ fn is_small_formatting_command(tokens: &[String]) -> bool {
// Keep `sed -n <range> file` (treated as a file read elsewhere);
// otherwise consider it a formatting helper in a pipeline.
tokens.len() < 4
|| !(tokens[1] == "-n" && is_valid_sed_n_arg(tokens.get(2).map(|s| s.as_str())))
|| !(tokens[1] == "-n" && is_valid_sed_n_arg(tokens.get(2).map(std::string::String::as_str)))
}
_ => false,
}
@@ -1774,7 +1772,7 @@ fn summarize_main_tokens(main_cmd: &[String]) -> ParsedCommand {
}
}
Some((head, tail)) if head == "git" => {
let cmd = if main_cmd.get(1).map(|s| s.as_str()) == Some("branch") {
let cmd = if main_cmd.get(1).map(std::string::String::as_str) == Some("branch") {
simple_space_join(main_cmd).unwrap_or_else(|| shlex_join(main_cmd))
} else {
shlex_join(main_cmd)
@@ -1803,7 +1801,7 @@ fn summarize_main_tokens(main_cmd: &[String]) -> ParsedCommand {
(None, non_flags.first().map(|s| short_display_path(s)))
} else {
(
non_flags.first().cloned().map(|s| s.to_string()),
non_flags.first().cloned().map(std::string::ToString::to_string),
non_flags.get(1).map(|s| short_display_path(s)),
)
};
@@ -1838,7 +1836,7 @@ fn summarize_main_tokens(main_cmd: &[String]) -> ParsedCommand {
.collect();
// Do not shorten the query: grep patterns may legitimately contain slashes
// and should be preserved verbatim. Only paths should be shortened.
let query = non_flags.first().cloned().map(|s| s.to_string());
let query = non_flags.first().cloned().map(std::string::ToString::to_string);
let path = non_flags.get(1).map(|s| short_display_path(s));
ParsedCommand::Search {
cmd: shlex_join(main_cmd),
@@ -1857,7 +1855,7 @@ fn summarize_main_tokens(main_cmd: &[String]) -> ParsedCommand {
}
Some((head, tail)) if head == "cat" => {
// Support both `cat <file>` and `cat -- <file>` forms.
let effective_tail: &[String] = if tail.first().map(|s| s.as_str()) == Some("--") {
let effective_tail: &[String] = if tail.first().map(std::string::String::as_str) == Some("--") {
&tail[1..]
} else {
tail
@@ -1973,7 +1971,7 @@ fn summarize_main_tokens(main_cmd: &[String]) -> ParsedCommand {
if head == "sed"
&& tail.len() >= 3
&& tail[0] == "-n"
&& is_valid_sed_n_arg(tail.get(1).map(|s| s.as_str())) =>
&& is_valid_sed_n_arg(tail.get(1).map(std::string::String::as_str)) =>
{
if let Some(path) = tail.get(2) {
let name = short_display_path(path);
+26 -44
View File
@@ -116,9 +116,9 @@ pub fn run_patch_harness(
}
// 2) Workflow checks (actionlint plugin).
if functional_enabled {
if let Some(lines) = maybe_run_actionlint(action, cwd, github) {
if !lines.is_empty() {
if functional_enabled
&& let Some(lines) = maybe_run_actionlint(action, cwd, github)
&& !lines.is_empty() {
record_ran("actionlint");
for line in lines.into_iter().take(24) {
findings.push(HarnessFinding {
@@ -128,8 +128,6 @@ pub fn run_patch_harness(
});
}
}
}
}
// 3) External tools (shellcheck, markdownlint, etc.).
let allow = cfg.tools_allowlist.clone().unwrap_or_default();
@@ -157,7 +155,7 @@ pub fn run_patch_harness(
changed_paths.push(rel);
}
if move_path.is_some()
&& move_path.as_ref().map(|p| p.as_path()) != Some(path.as_path())
&& move_path.as_ref().map(std::path::PathBuf::as_path) != Some(path.as_path())
{
remove_staged_file(staged_root, cwd, path);
}
@@ -361,8 +359,7 @@ pub fn run_patch_harness(
&& cfg.tools.tsc.unwrap_or(true)
&& !ts_files.is_empty()
&& is_allowed("tsc")
{
if let Some(exe) = which(Path::new("tsc")) {
&& let Some(exe) = which(Path::new("tsc")) {
record_ran("tsc");
let ts_timeout = timeout.max(20);
let project = find_nearest_config(
@@ -393,7 +390,7 @@ pub fn run_patch_harness(
}
match run_with_timeout(cmd, ts_timeout) {
Some(output) => {
if output.status.map_or(true, |status| !status.success()) {
if output.status.is_none_or(|status| !status.success()) {
let mut lines =
collect_output_lines(&output.stdout, &output.stderr);
if lines.is_empty() {
@@ -422,7 +419,6 @@ pub fn run_patch_harness(
}),
}
}
}
let eslint_files: Vec<PathBuf> = changed_paths
.iter()
@@ -439,8 +435,7 @@ pub fn run_patch_harness(
&& !eslint_files.is_empty()
&& is_allowed("eslint")
&& has_eslint_config(cwd, &eslint_files)
{
if let Some(exe) = which(Path::new("eslint")) {
&& let Some(exe) = which(Path::new("eslint")) {
record_ran("eslint");
let lint_timeout = timeout.max(15);
match WorkspaceOverlay::apply(action) {
@@ -453,7 +448,7 @@ pub fn run_patch_harness(
}
match run_with_timeout(cmd, lint_timeout) {
Some(output) => {
if output.status.map_or(true, |status| !status.success()) {
if output.status.is_none_or(|status| !status.success()) {
let mut lines =
collect_output_lines(&output.stdout, &output.stderr);
if lines.is_empty() {
@@ -482,7 +477,6 @@ pub fn run_patch_harness(
}),
}
}
}
let php_files: Vec<PathBuf> = changed_paths
.iter()
@@ -494,8 +488,7 @@ pub fn run_patch_harness(
&& !php_files.is_empty()
&& is_allowed("phpstan")
&& has_phpstan_config(cwd, &php_files)
{
if let Some(exe) = which(Path::new("phpstan")) {
&& let Some(exe) = which(Path::new("phpstan")) {
record_ran("phpstan");
let phpstan_timeout = timeout.max(20);
match WorkspaceOverlay::apply(action) {
@@ -508,7 +501,7 @@ pub fn run_patch_harness(
}
match run_with_timeout(cmd, phpstan_timeout) {
Some(output) => {
if output.status.map_or(true, |status| !status.success()) {
if output.status.is_none_or(|status| !status.success()) {
let mut lines =
collect_output_lines(&output.stdout, &output.stderr);
if lines.is_empty() {
@@ -537,15 +530,13 @@ pub fn run_patch_harness(
}),
}
}
}
if functional_enabled
&& cfg.tools.psalm.unwrap_or(true)
&& !php_files.is_empty()
&& is_allowed("psalm")
&& has_psalm_config(cwd, &php_files)
{
if let Some(exe) = which(Path::new("psalm")) {
&& let Some(exe) = which(Path::new("psalm")) {
record_ran("psalm");
let psalm_timeout = timeout.max(20);
match WorkspaceOverlay::apply(action) {
@@ -558,7 +549,7 @@ pub fn run_patch_harness(
}
match run_with_timeout(cmd, psalm_timeout) {
Some(output) => {
if output.status.map_or(true, |status| !status.success()) {
if output.status.is_none_or(|status| !status.success()) {
let mut lines =
collect_output_lines(&output.stdout, &output.stderr);
if lines.is_empty() {
@@ -587,7 +578,6 @@ pub fn run_patch_harness(
}),
}
}
}
let py_files: Vec<PathBuf> = changed_paths
.iter()
@@ -598,8 +588,7 @@ pub fn run_patch_harness(
&& cfg.tools.mypy.unwrap_or(true)
&& !py_files.is_empty()
&& is_allowed("mypy")
{
if let Some(exe) = which(Path::new("mypy")) {
&& let Some(exe) = which(Path::new("mypy")) {
record_ran("mypy");
let mypy_timeout = timeout.max(20);
match WorkspaceOverlay::apply(action) {
@@ -612,7 +601,7 @@ pub fn run_patch_harness(
}
match run_with_timeout(cmd, mypy_timeout) {
Some(output) => {
if output.status.map_or(true, |status| !status.success()) {
if output.status.is_none_or(|status| !status.success()) {
let mut lines =
collect_output_lines(&output.stdout, &output.stderr);
if lines.is_empty() {
@@ -641,14 +630,12 @@ pub fn run_patch_harness(
}),
}
}
}
if functional_enabled
&& cfg.tools.pyright.unwrap_or(true)
&& !py_files.is_empty()
&& is_allowed("pyright")
{
if let Some(exe) = which(Path::new("pyright")) {
&& let Some(exe) = which(Path::new("pyright")) {
record_ran("pyright");
let pyright_timeout = timeout.max(20);
match WorkspaceOverlay::apply(action) {
@@ -661,7 +648,7 @@ pub fn run_patch_harness(
}
match run_with_timeout(cmd, pyright_timeout) {
Some(output) => {
if output.status.map_or(true, |status| !status.success()) {
if output.status.is_none_or(|status| !status.success()) {
let mut lines =
collect_output_lines(&output.stdout, &output.stderr);
if lines.is_empty() {
@@ -690,7 +677,6 @@ pub fn run_patch_harness(
}),
}
}
}
let go_files: Vec<PathBuf> = changed_paths
.iter()
@@ -702,8 +688,7 @@ pub fn run_patch_harness(
&& !go_files.is_empty()
&& is_allowed("golangci-lint")
&& has_go_module(cwd)
{
if let Some(exe) = which(Path::new("golangci-lint")) {
&& let Some(exe) = which(Path::new("golangci-lint")) {
record_ran("golangci-lint");
let lint_timeout = timeout.max(20);
match WorkspaceOverlay::apply(action) {
@@ -713,7 +698,7 @@ pub fn run_patch_harness(
cmd.args(["run", "./..."]);
match run_with_timeout(cmd, lint_timeout) {
Some(output) => {
if output.status.map_or(true, |status| !status.success()) {
if output.status.is_none_or(|status| !status.success()) {
let mut lines =
collect_output_lines(&output.stdout, &output.stderr);
if lines.is_empty() {
@@ -744,7 +729,6 @@ pub fn run_patch_harness(
}),
}
}
}
if functional_enabled && cfg.tools.cargo_check.unwrap_or(true) && !rust_files.is_empty() {
if which(Path::new("cargo")).is_none() {
@@ -788,7 +772,7 @@ pub fn run_patch_harness(
match run_with_timeout(cmd, rust_timeout) {
Some(output) => {
record_ran(&format!("cargo-check({label})"));
if output.status.map_or(true, |status| !status.success()) {
if output.status.is_none_or(|status| !status.success()) {
let mut lines =
collect_output_lines(&output.stdout, &output.stderr);
if lines.is_empty() {
@@ -862,7 +846,7 @@ fn which(exe: &Path) -> Option<PathBuf> {
let name = exe.as_os_str();
let paths: Vec<PathBuf> = std::env::var_os("PATH")
.map(|p| std::env::split_paths(&p).collect())
.unwrap_or_else(Vec::new);
.unwrap_or_default();
for dir in paths {
let candidate = dir.join(name);
if candidate.is_file() {
@@ -912,11 +896,10 @@ fn stage_file(staged_root: &Path, cwd: &Path, path: &Path, contents: &str) -> Op
if let Some(parent) = dest.parent() {
let _ = fs::create_dir_all(parent);
}
if let Ok(mut file) = fs::File::create(&dest) {
if file.write_all(contents.as_bytes()).is_ok() {
if let Ok(mut file) = fs::File::create(&dest)
&& file.write_all(contents.as_bytes()).is_ok() {
return Some(relative.to_path_buf());
}
}
None
}
@@ -933,14 +916,14 @@ fn collect_output_lines(stdout: &[u8], stderr: &[u8]) -> Vec<String> {
lines.extend(
String::from_utf8_lossy(stdout)
.lines()
.map(|s| s.to_string()),
.map(std::string::ToString::to_string),
);
}
if !stderr.is_empty() {
lines.extend(
String::from_utf8_lossy(stderr)
.lines()
.map(|s| s.to_string()),
.map(std::string::ToString::to_string),
);
}
lines.retain(|line| !line.trim().is_empty());
@@ -1227,11 +1210,10 @@ impl WorkspaceOverlay {
fn remove_file(&mut self, path: &Path, seen: &mut HashSet<PathBuf>) -> std::io::Result<()> {
self.backup_if_needed(path, seen)?;
if let Err(err) = fs::remove_file(path) {
if err.kind() != std::io::ErrorKind::NotFound {
if let Err(err) = fs::remove_file(path)
&& err.kind() != std::io::ErrorKind::NotFound {
return Err(err);
}
}
Ok(())
}
+3 -4
View File
@@ -162,19 +162,18 @@ fn canonicalize_word_boundaries(input: &str) -> String {
let next_char = chars.peek().copied();
let mut split = false;
if !current.is_empty() {
if let Some(prev) = prev_char {
if !current.is_empty()
&& let Some(prev) = prev_char {
if prev.is_ascii_lowercase() && ch.is_ascii_uppercase() {
split = true;
} else if prev.is_ascii_uppercase()
&& ch.is_ascii_uppercase()
&& uppercase_run > 0
&& next_char.map_or(false, |c| c.is_ascii_lowercase())
&& next_char.is_some_and(|c| c.is_ascii_lowercase())
{
split = true;
}
}
}
if split {
tokens.push(std::mem::take(&mut current));
+1 -1
View File
@@ -198,7 +198,7 @@ fn discover_project_doc_paths_with_candidates(
// Build chain from cwd upwards and detect git root.
let mut chain: Vec<PathBuf> = vec![dir.clone()];
let mut git_root: Option<PathBuf> = None;
let mut cursor = dir.clone();
let mut cursor = dir;
while let Some(parent) = cursor.parent() {
let git_marker = cursor.join(".git");
let git_exists = match std::fs::metadata(&git_marker) {
+1 -1
View File
@@ -54,7 +54,7 @@ impl ProjectHooks {
}
pub fn is_empty(&self) -> bool {
self.hooks.values().all(|hooks| hooks.is_empty())
self.hooks.values().all(std::vec::Vec::is_empty)
}
pub fn hooks_for(&self, event: ProjectHookEvent) -> impl Iterator<Item = &ProjectHook> {
+3 -5
View File
@@ -439,13 +439,11 @@ impl SandboxPolicy {
// Linux or Windows, but supporting it here gives users a way to
// provide the model with their own temporary directory without
// having to hardcode it in the config.
if !exclude_tmpdir_env_var {
if let Some(tmpdir) = std::env::var_os("TMPDIR") {
if !tmpdir.is_empty() {
if !exclude_tmpdir_env_var
&& let Some(tmpdir) = std::env::var_os("TMPDIR")
&& !tmpdir.is_empty() {
roots.push(PathBuf::from(tmpdir));
}
}
}
// For each root, compute subpaths that should remain read-only.
roots
+1 -1
View File
@@ -116,7 +116,7 @@ pub fn clamp_reasoning_effort_for_model(
requested: ReasoningEffort,
) -> ReasoningEffort {
let allowed = supported_reasoning_efforts_for_model(model);
if allowed.iter().any(|effort| *effort == requested) {
if allowed.contains(&requested) {
return requested;
}
+1 -1
View File
@@ -359,7 +359,7 @@ pub fn apply_model_info_overrides(info: &ModelInfo, mut family: ModelFamily) ->
if let Some(context_window) = info
.context_window
.and_then(|value| (value > 0).then(|| value as u64))
.and_then(|value| (value > 0).then_some(value as u64))
{
family.context_window = Some(context_window);
}
+3 -4
View File
@@ -140,7 +140,7 @@ fn estimate_item_size(item: &ResponseItem) -> usize {
ResponseItem::FunctionCallOutput { output, .. } => output.content.len(),
ResponseItem::CustomToolCall { input, .. } => input.len(),
ResponseItem::CustomToolCallOutput { output, .. } => output.len(),
ResponseItem::Reasoning { content, .. } => content.as_ref().map(|c| c.len()).unwrap_or(0),
ResponseItem::Reasoning { content, .. } => content.as_ref().map(std::vec::Vec::len).unwrap_or(0),
_ => 0,
}
}
@@ -425,12 +425,11 @@ pub fn apply_retention_policy_owned(
}
// 2. Keep only the latest baseline (immutable)
if policy.keep_latest_baseline {
if let Some(latest) = env_baselines.pop() {
if policy.keep_latest_baseline
&& let Some(latest) = env_baselines.pop() {
stats.bytes_kept += latest.size_bytes;
kept.push(latest);
}
}
for item in env_baselines {
stats.removed_env_baselines += 1;
+4 -8
View File
@@ -59,13 +59,11 @@ fn lock_path(scope: Option<&Path>) -> std::io::Result<PathBuf> {
}
fn read_epoch(scope: Option<&Path>) -> u64 {
if let Ok(p) = epoch_path(scope) {
if let Ok(text) = fs::read_to_string(p) {
if let Ok(v) = text.trim().parse::<u64>() {
if let Ok(p) = epoch_path(scope)
&& let Ok(text) = fs::read_to_string(p)
&& let Ok(v) = text.trim().parse::<u64>() {
return v;
}
}
}
0
}
@@ -107,14 +105,12 @@ fn scope_from_current_dir() -> Option<PathBuf> {
.current_dir(&cwd)
.args(["rev-parse", "--show-toplevel"])
.output()
{
if out.status.success() {
&& out.status.success() {
let path = String::from_utf8_lossy(&out.stdout).trim().to_string();
if !path.is_empty() {
return Some(PathBuf::from(path));
}
}
}
Some(cwd)
}
+12 -18
View File
@@ -272,11 +272,10 @@ impl SessionCatalog {
}
// Remove from git root index
if let Some(ref git_root) = entry.git_project_root {
if let Some(ids) = self.by_git_root.get_mut(git_root) {
if let Some(ref git_root) = entry.git_project_root
&& let Some(ids) = self.by_git_root.get_mut(git_root) {
ids.retain(|id| id != session_id);
}
}
}
/// Remove an entry by session ID.
@@ -308,13 +307,12 @@ impl SessionCatalog {
// Remove entries that no longer exist on disk.
let existing_ids: Vec<Uuid> = self.entries.keys().copied().collect();
for session_id in existing_ids {
if !discovered_ids.contains(&session_id) {
if let Some(entry) = self.entries.remove(&session_id) {
if !discovered_ids.contains(&session_id)
&& let Some(entry) = self.entries.remove(&session_id) {
self.remove_from_indexes(&session_id, &entry);
result.removed += 1;
changed = true;
}
}
}
// Upsert discovered entries.
@@ -383,10 +381,10 @@ async fn scan_rollout_files(sessions_root: &Path) -> io::Result<HashMap<Uuid, Se
if metadata.is_dir() {
queue.push(path);
} else if metadata.is_file() {
if let Some(name) = path.file_name().and_then(|n| n.to_str()) {
if name.ends_with(".jsonl") && name.starts_with("rollout-") {
if let Some(index_entry) = parse_rollout_file(&path, sessions_root).await {
} else if metadata.is_file()
&& let Some(name) = path.file_name().and_then(|n| n.to_str())
&& name.ends_with(".jsonl") && name.starts_with("rollout-")
&& let Some(index_entry) = parse_rollout_file(&path, sessions_root).await {
match discovered.get(&index_entry.session_id) {
Some(existing) => {
if should_replace(existing, &index_entry) {
@@ -398,9 +396,6 @@ async fn scan_rollout_files(sessions_root: &Path) -> io::Result<HashMap<Uuid, Se
}
}
}
}
}
}
}
}
@@ -465,10 +460,10 @@ async fn parse_rollout_file(path: &Path, sessions_root: &Path) -> Option<Session
RolloutItem::ResponseItem(response_item) => {
message_count += 1;
if let ResponseItem::Message { role, content, .. } = response_item {
if role.eq_ignore_ascii_case("user") {
if let ResponseItem::Message { role, content, .. } = response_item
&& role.eq_ignore_ascii_case("user") {
let snippet = snippet_from_content(&content);
if snippet.as_deref().map_or(false, is_system_status_snippet) {
if snippet.as_deref().is_some_and(is_system_status_snippet) {
continue;
}
@@ -477,7 +472,6 @@ async fn parse_rollout_file(path: &Path, sessions_root: &Path) -> Option<Session
last_user_snippet = Some(snippet);
}
}
}
}
RolloutItem::Event(_event) => {
// Event lines record internal state changes (tool output, approvals, etc.).
@@ -509,7 +503,7 @@ async fn parse_rollout_file(path: &Path, sessions_root: &Path) -> Option<Session
snapshot_file
.strip_prefix(code_home)
.ok()
.map(|p| p.to_path_buf())
.map(std::path::Path::to_path_buf)
} else {
None
}
+2 -3
View File
@@ -382,12 +382,11 @@ async fn read_head_and_tail(
summary.saw_session_meta = true;
}
RolloutItem::Event(event) => {
if let Some(msg) = event_msg_from_protocol(&event.msg) {
if matches!(msg, EventMsg::UserMessage(_) | EventMsg::AgentMessage(_)) {
if let Some(msg) = event_msg_from_protocol(&event.msg)
&& matches!(msg, EventMsg::UserMessage(_) | EventMsg::AgentMessage(_)) {
summary.saw_user_event = true;
summary.updated_at = Some(rollout_line.timestamp.clone());
}
}
}
RolloutItem::ResponseItem(_)
| RolloutItem::Compacted(_)
+2 -3
View File
@@ -538,8 +538,8 @@ async fn rollout_writer(
}
}
if let Some(ref state) = catalog_state {
if let Err(err) = super::catalog::update_catalog_entry(
if let Some(ref state) = catalog_state
&& let Err(err) = super::catalog::update_catalog_entry(
&state.code_home,
&state.rollout_path,
state.session_id.into(),
@@ -549,7 +549,6 @@ async fn rollout_writer(
{
warn!("failed to update session catalog after AddItems: {err}");
}
}
}
RolloutCmd::SetSnapshot(snapshot) => {
if let Err(err) = write_snapshot(&snapshot_path, &snapshot).await {
+2 -3
View File
@@ -268,11 +268,10 @@ fn is_write_patch_constrained_to_writable_paths(
if !is_path_writable(path) {
return false;
}
if let Some(dest) = move_path {
if !is_path_writable(dest) {
if let Some(dest) = move_path
&& !is_path_writable(dest) {
return false;
}
}
}
}
}
+18 -21
View File
@@ -69,27 +69,25 @@ fn create_seatbelt_command_args(
// If the writable root is a file, allow writes to that exact file via literal.
if canonical_root.is_file() {
writable_folder_policies.push(format!("(literal (param \"{root_param}\"))"));
} else if wr.read_only_subpaths.is_empty() {
writable_folder_policies
.push(format!("(subpath (param \"{root_param}\"))"));
} else {
if wr.read_only_subpaths.is_empty() {
writable_folder_policies
.push(format!("(subpath (param \"{root_param}\"))"));
} else {
// Add parameters for each read-only subpath and generate
// the `(require-not ...)` clauses.
let mut require_parts: Vec<String> = Vec::new();
require_parts.push(format!("(subpath (param \"{root_param}\"))"));
for (subpath_index, ro) in wr.read_only_subpaths.iter().enumerate() {
let canonical_ro = ro.canonicalize().unwrap_or_else(|_| ro.clone());
let ro_param = format!("WRITABLE_ROOT_{index}_RO_{subpath_index}");
cli_args
.push(format!("-D{ro_param}={}", canonical_ro.to_string_lossy()));
require_parts
.push(format!("(require-not (subpath (param \"{ro_param}\")))"));
}
let policy_component =
format!("(require-all {} )", require_parts.join(" "));
writable_folder_policies.push(policy_component);
// Add parameters for each read-only subpath and generate
// the `(require-not ...)` clauses.
let mut require_parts: Vec<String> = Vec::new();
require_parts.push(format!("(subpath (param \"{root_param}\"))"));
for (subpath_index, ro) in wr.read_only_subpaths.iter().enumerate() {
let canonical_ro = ro.canonicalize().unwrap_or_else(|_| ro.clone());
let ro_param = format!("WRITABLE_ROOT_{index}_RO_{subpath_index}");
cli_args
.push(format!("-D{ro_param}={}", canonical_ro.to_string_lossy()));
require_parts
.push(format!("(require-not (subpath (param \"{ro_param}\")))"));
}
let policy_component =
format!("(require-all {} )", require_parts.join(" "));
writable_folder_policies.push(policy_component);
}
}
@@ -124,8 +122,7 @@ fn create_seatbelt_command_args(
if std::env::var("HANZO_DEBUG_PRINT_SEATBELT").is_ok() {
eprintln!(
"--- Codex Seatbelt Policy ---\n{}\n------------------------------",
full_policy
"--- Codex Seatbelt Policy ---\n{full_policy}\n------------------------------"
);
}
+6 -9
View File
@@ -68,16 +68,14 @@ impl SessionCatalog {
if !query.include_deleted && entry.deleted {
continue;
}
if let Some(cwd) = &query.cwd {
if &entry.cwd_real != cwd {
if let Some(cwd) = &query.cwd
&& &entry.cwd_real != cwd {
continue;
}
}
if let Some(git_root) = &query.git_root {
if entry.git_project_root.as_ref() != Some(git_root) {
if let Some(git_root) = &query.git_root
&& entry.git_project_root.as_ref() != Some(git_root) {
continue;
}
}
if !query.sources.is_empty() && !query.sources.contains(&entry.session_source) {
continue;
}
@@ -87,11 +85,10 @@ impl SessionCatalog {
rows.push(entry.clone());
if let Some(limit) = query.limit {
if rows.len() >= limit {
if let Some(limit) = query.limit
&& rows.len() >= limit {
break;
}
}
}
Ok(rows)
+7 -14
View File
@@ -29,11 +29,10 @@ fn command_exists(cmd: &str) -> bool {
continue;
}
let candidate = dir.join(cmd);
if let Ok(meta) = std::fs::metadata(&candidate) {
if meta.is_file() && (meta.permissions().mode() & 0o111 != 0) {
if let Ok(meta) = std::fs::metadata(&candidate)
&& meta.is_file() && (meta.permissions().mode() & 0o111 != 0) {
return true;
}
}
}
false
}
@@ -47,11 +46,10 @@ pub fn get_enabled_agents(agents: &[AgentConfig]) -> Vec<String> {
fn agent_is_runnable(agent: &AgentConfig) -> bool {
let spec = agent_model_spec(&agent.name).or_else(|| agent_model_spec(&agent.command));
if let Some(spec) = spec {
if matches!(spec.family, "code" | "codex" | "cloud") {
if let Some(spec) = spec
&& matches!(spec.family, "code" | "codex" | "cloud") {
return true;
}
}
let cmd = agent.command.trim();
let cmd = if cmd.is_empty() {
@@ -189,7 +187,7 @@ pub fn format_subagent_command(
// Compose unified prompt used for all subagent commands (built-ins and custom)
let models_str = models
.iter()
.map(|m| format!("\"{}\"", m))
.map(|m| format!("\"{m}\""))
.collect::<Vec<_>>()
.join(", ");
@@ -200,12 +198,7 @@ pub fn format_subagent_command(
let write_flag = !read_only;
let prompt = format!(
"Please perform /{name} using the <tools>, <instructions> and <task> below.\n<tools>\n To perform /{name} you must use `agent {{\"action\":\"create\",\"create\":{{\"models\":[{models}],\"write\":{write_flag}}}}}` to start a batch of agents.\n Provide a comprehensive description of the task and context. You may need to briefly research the code base first and to give the agents a head start of where to look. You can include one or two key files but also allow the models to look up the files they need themselves. Using the create action starts all agents at once and returns a `batch_id`.\n\n Each agent uses a different LLM which allows you to gather diverse results.\n Monitor progress using `agent {{\"action\":\"wait\",\"wait\":{{\"batch_id\":\"<batch_id>\",\"return_all\":true}}}}` to wait for all agents to complete.\n If an agent fails or times out, you can ignore it and continue with the other results.\n Use `agent {{\"action\":\"result\",\"result\":{{\"agent_id\":\"<agent_id>\"}}}}` to fetch detailed output, or inspect the worktree directly when `write` is true.\n</tools>\n<instructions>\n Instructions for /{name}:\n {instructions}\n</instructions>\n<task>\n Task for /{name}:\n {task}\n</task>",
name = name,
models = models_str,
write_flag = write_flag,
instructions = instr_text,
task = task,
"Please perform /{name} using the <tools>, <instructions> and <task> below.\n<tools>\n To perform /{name} you must use `agent {{\"action\":\"create\",\"create\":{{\"models\":[{models_str}],\"write\":{write_flag}}}}}` to start a batch of agents.\n Provide a comprehensive description of the task and context. You may need to briefly research the code base first and to give the agents a head start of where to look. You can include one or two key files but also allow the models to look up the files they need themselves. Using the create action starts all agents at once and returns a `batch_id`.\n\n Each agent uses a different LLM which allows you to gather diverse results.\n Monitor progress using `agent {{\"action\":\"wait\",\"wait\":{{\"batch_id\":\"<batch_id>\",\"return_all\":true}}}}` to wait for all agents to complete.\n If an agent fails or times out, you can ignore it and continue with the other results.\n Use `agent {{\"action\":\"result\",\"result\":{{\"agent_id\":\"<agent_id>\"}}}}` to fetch detailed output, or inspect the worktree directly when `write` is true.\n</tools>\n<instructions>\n Instructions for /{name}:\n {instr_text}\n</instructions>\n<task>\n Task for /{name}:\n {task}\n</task>",
);
SubagentResolution {
@@ -263,7 +256,7 @@ pub fn handle_slash_command(input: &str, agents: Option<&[AgentConfig]>) -> Opti
// Parse the command and arguments
let parts: Vec<&str> = input.splitn(2, ' ').collect();
let command = parts[0];
let args = parts.get(1).map(|s| s.to_string()).unwrap_or_default();
let args = parts.get(1).map(std::string::ToString::to_string).unwrap_or_default();
match command {
"/plan" => {
+1 -1
View File
@@ -117,7 +117,7 @@ pub fn parse_id_token(id_token: &str) -> Result<IdTokenInfo, IdTokenInfoError> {
.auth
.as_ref()
.and_then(|a| a.chatgpt_plan_type.as_ref())
.map(|plan| plan.as_string());
.map(PlanType::as_string);
tracing::debug!(
email = claims.email.as_deref().unwrap_or("<missing>"),
chatgpt_plan_type = plan.as_deref().unwrap_or("unknown"),
+1 -1
View File
@@ -44,7 +44,7 @@ pub(crate) async fn wait_for_connectivity(probe_url: &str) {
}
pub fn escape_command(command: &[String]) -> String {
try_join(command.iter().map(|s| s.as_str())).unwrap_or_else(|_| command.join(" "))
try_join(command.iter().map(std::string::String::as_str)).unwrap_or_else(|_| command.join(" "))
}
pub fn strip_bash_lc_and_escape(command: &[String]) -> String {
+5 -6
View File
@@ -142,14 +142,14 @@ pub fn maybe_run_actionlint(
lines.extend(
String::from_utf8_lossy(&output.stdout)
.lines()
.map(|s| s.to_string()),
.map(std::string::ToString::to_string),
);
}
if !output.stderr.is_empty() {
lines.extend(
String::from_utf8_lossy(&output.stderr)
.lines()
.map(|s| s.to_string()),
.map(std::string::ToString::to_string),
);
}
if lines.is_empty() { None } else { Some(lines) }
@@ -181,7 +181,7 @@ fn which(exe: &Path) -> Option<PathBuf> {
let name = exe.as_os_str();
let paths: Vec<PathBuf> = std::env::var_os("PATH")
.map(|paths| std::env::split_paths(&paths).collect())
.unwrap_or_else(Vec::new);
.unwrap_or_default();
for dir in paths {
let candidate = dir.join(name);
if candidate.is_file() && is_executable(&candidate) {
@@ -210,11 +210,10 @@ fn stage_file_with_contents(temp_root: &Path, cwd: &Path, target: &Path, content
Err(_) => return false,
};
let destination = temp_root.join(relative);
if let Some(parent) = destination.parent() {
if fs::create_dir_all(parent).is_err() {
if let Some(parent) = destination.parent()
&& fs::create_dir_all(parent).is_err() {
return false;
}
}
match fs::File::create(&destination) {
Ok(mut file) => file.write_all(contents.as_bytes()).is_ok(),
Err(_) => false,
+2 -3
View File
@@ -31,9 +31,8 @@ pub(crate) fn handle_last_message(last_agent_message: Option<&str>, output_file:
}
fn write_last_message_file(contents: &str, last_message_path: Option<&Path>) {
if let Some(path) = last_message_path {
if let Err(e) = std::fs::write(path, contents) {
if let Some(path) = last_message_path
&& let Err(e) = std::fs::write(path, contents) {
eprintln!("Failed to write last message file {path:?}: {e}");
}
}
}
@@ -371,7 +371,7 @@ impl EventProcessor for EventProcessorWithHumanOutput {
}
println!("ERROR");
if !truncated_stderr.is_empty() {
println!("{}", truncated_stderr);
println!("{truncated_stderr}");
}
}
}
@@ -619,13 +619,12 @@ impl EventProcessor for EventProcessorWithHumanOutput {
"tool".style(self.magenta),
event.tool_name.style(self.bold),
);
if let Some(params) = &event.parameters {
if let Ok(formatted) = serde_json::to_string_pretty(params) {
if let Some(params) = &event.parameters
&& let Ok(formatted) = serde_json::to_string_pretty(params) {
for line in formatted.lines() {
println!("{}", line.style(self.dimmed));
}
}
}
}
EventMsg::CustomToolCallEnd(event) => {
let status = if event.result.is_ok() {
@@ -751,7 +750,7 @@ fn escape_command(command: &[String]) -> String {
fn format_file_change(change: &FileChange) -> &'static str {
match change {
FileChange::Add { .. } => "A",
FileChange::Delete { .. } => "D",
FileChange::Delete => "D",
FileChange::Update {
move_path: Some(_), ..
} => "R",
+30 -43
View File
@@ -135,7 +135,7 @@ pub async fn run_main(cli: Cli, code_linux_sandbox_exe: Option<PathBuf>) -> anyh
} = cli;
let run_deadline = max_seconds.map(|seconds| Instant::now() + Duration::from_secs(seconds));
let run_deadline_std = run_deadline.map(|deadline| deadline.into_std());
let run_deadline_std = run_deadline.map(tokio::time::Instant::into_std);
// Determine the prompt source (parent or subcommand) and read from stdin if needed.
let prompt_arg = match &command {
@@ -196,7 +196,7 @@ pub async fn run_main(cli: Cli, code_linux_sandbox_exe: Option<PathBuf>) -> anyh
);
std::process::exit(1);
}
if auto_drive_goal.as_ref().is_some_and(|goal| goal.is_empty()) {
if auto_drive_goal.as_ref().is_some_and(std::string::String::is_empty) {
auto_drive_goal = Some(trimmed_prompt.to_string());
} else if auto_drive_goal.is_none() {
auto_drive_goal = Some(trimmed_prompt.to_string());
@@ -212,11 +212,10 @@ pub async fn run_main(cli: Cli, code_linux_sandbox_exe: Option<PathBuf>) -> anyh
}
let timeboxed_auto_exec = auto_drive_goal.is_some() && max_seconds.is_some();
if timeboxed_auto_exec {
if let Some(goal) = auto_drive_goal.as_mut() {
if timeboxed_auto_exec
&& let Some(goal) = auto_drive_goal.as_mut() {
*goal = append_timeboxed_auto_drive_goal(goal);
}
}
let mut prompt_to_send = prompt.clone();
let mut summary_prompt = if let Some(goal) = auto_drive_goal.as_ref() {
@@ -248,7 +247,7 @@ pub async fn run_main(cli: Cli, code_linux_sandbox_exe: Option<PathBuf>) -> anyh
let _ = tracing_subscriber::fmt()
.with_env_filter(env_filter)
.with_ansi(stderr_with_ansi)
.with_writer(|| std::io::stderr())
.with_writer(std::io::stderr)
.try_init();
let sandbox_mode = if full_auto {
@@ -353,13 +352,12 @@ pub async fn run_main(cli: Cli, code_linux_sandbox_exe: Option<PathBuf>) -> anyh
}
}
if auto_review {
if let Some(req) = review_request.as_mut() {
if auto_review
&& let Some(req) = review_request.as_mut() {
let mut metadata = req.metadata.clone().unwrap_or_default();
metadata.auto_review = Some(true);
req.metadata = Some(metadata);
}
}
let is_auto_review = review_request
.as_ref()
@@ -705,8 +703,8 @@ pub async fn run_main(cli: Cli, code_linux_sandbox_exe: Option<PathBuf>) -> anyh
}
}
if let Some(base) = auto_resolve_base_snapshot.as_ref() {
if let Some((snap, diff_paths)) = capture_snapshot_against_base(
if let Some(base) = auto_resolve_base_snapshot.as_ref()
&& let Some((snap, diff_paths)) = capture_snapshot_against_base(
&config.cwd,
base,
"auto-resolve working snapshot",
@@ -721,7 +719,6 @@ pub async fn run_main(cli: Cli, code_linux_sandbox_exe: Option<PathBuf>) -> anyh
state.last_reviewed_commit = Some(snap.id().to_string());
}
}
}
}
// Capture baseline epoch after any snapshot creation so we don't trip on our own bumps.
@@ -972,8 +969,8 @@ pub async fn run_main(cli: Cli, code_linux_sandbox_exe: Option<PathBuf>) -> anyh
}
// stale epoch check
if let Some(state) = auto_resolve_state.as_ref() {
if let Some(baseline) = state.snapshot_epoch {
if current_epoch > baseline {
if let Some(baseline) = state.snapshot_epoch
&& current_epoch > baseline {
eprintln!("Auto-resolve: snapshot epoch advanced; aborting follow-up review.");
auto_resolve_state = None;
auto_resolve_base_snapshot = None;
@@ -990,7 +987,6 @@ pub async fn run_main(cli: Cli, code_linux_sandbox_exe: Option<PathBuf>) -> anyh
.await?;
continue;
}
}
if state.metadata.as_ref().and_then(|m| m.commit.as_ref()).is_some()
&& state.snapshot_epoch.is_none()
{
@@ -1099,8 +1095,8 @@ pub async fn run_main(cli: Cli, code_linux_sandbox_exe: Option<PathBuf>) -> anyh
continue;
}
let current_epoch = current_snapshot_epoch_for(&config.cwd);
if let Some(state) = auto_resolve_state.as_ref() {
if state
if let Some(state) = auto_resolve_state.as_ref()
&& state
.metadata
.as_ref()
.and_then(|m| m.commit.as_ref())
@@ -1121,7 +1117,6 @@ pub async fn run_main(cli: Cli, code_linux_sandbox_exe: Option<PathBuf>) -> anyh
.await?;
continue;
}
}
match capture_snapshot_against_base(
&config.cwd,
base,
@@ -1244,11 +1239,10 @@ pub async fn run_main(cli: Cli, code_linux_sandbox_exe: Option<PathBuf>) -> anyh
}
}
}
if let Some(path) = review_output_json {
if !review_outputs.is_empty() {
if let Some(path) = review_output_json
&& !review_outputs.is_empty() {
let _ = write_review_json(path, &review_outputs, final_review_snapshot.as_ref());
}
}
if review_runs > 0 {
eprintln!(
"Review runs: {} (auto_resolve={} max_attempts={})",
@@ -1833,14 +1827,13 @@ fn parse_auto_review_summary(raw: &str) -> AutoReviewSummary {
return summary_from_output(&output);
}
if let Some(start) = trimmed.find("```") {
if let Some((body, _)) = trimmed[start + 3..].split_once("```") {
if let Some(start) = trimmed.find("```")
&& let Some((body, _)) = trimmed[start + 3..].split_once("```") {
let candidate = body.trim_start_matches("json").trim();
if let Ok(output) = serde_json::from_str::<ReviewOutputEvent>(candidate) {
return summary_from_output(&output);
}
}
}
let lowered = trimmed.to_ascii_lowercase();
let clean_phrases = [
@@ -2038,11 +2031,10 @@ fn shutdown_state_after_request(
return (false, true, Some(deadline));
}
if let Some(deadline) = shutdown_deadline {
if deadline > now {
if let Some(deadline) = shutdown_deadline
&& deadline > now {
return (false, true, Some(deadline));
}
}
(true, true, None)
}
@@ -2167,7 +2159,7 @@ fn snapshot_parent_diff_paths(cwd: &Path, parent: &str, head: &str) -> Option<Ve
.lines()
.map(str::trim)
.filter(|line| !line.is_empty())
.map(|line| line.to_string())
.map(std::string::ToString::to_string)
.collect();
Some(paths)
@@ -2190,8 +2182,8 @@ fn apply_commit_scope_to_review_request(
prompt.push(')');
prompt.push('.');
if let Some(paths) = paths {
if !paths.is_empty() {
if let Some(paths) = paths
&& !paths.is_empty() {
prompt.push_str("\nFiles changed in this snapshot:\n");
for path in paths {
prompt.push_str("- ");
@@ -2199,7 +2191,6 @@ fn apply_commit_scope_to_review_request(
prompt.push('\n');
}
}
}
request.prompt = prompt;
request.user_facing_hint = format!("commit {short_commit} (parent {short_parent})");
@@ -2214,8 +2205,8 @@ fn apply_commit_scope_to_review_request(
fn extract_commit_from_prompt(prompt: &str) -> Option<String> {
let mut words = prompt.split_whitespace().peekable();
while let Some(word) = words.next() {
if word.eq_ignore_ascii_case("commit") {
if let Some(next) = words.peek() {
if word.eq_ignore_ascii_case("commit")
&& let Some(next) = words.peek() {
let candidate = next.trim_matches(|c: char| c == '.' || c == ',' || c == ';');
let len_ok = (7..=40).contains(&candidate.len());
let is_hex = candidate.chars().all(|c| c.is_ascii_hexdigit());
@@ -2223,7 +2214,6 @@ fn extract_commit_from_prompt(prompt: &str) -> Option<String> {
return Some(candidate.to_string());
}
}
}
}
None
}
@@ -2362,11 +2352,10 @@ async fn build_followup_review_request(
if let Some(snapshot) = snapshot {
metadata.commit = Some(snapshot.id().to_string());
metadata.scope = Some("commit".to_string());
} else if metadata.commit.is_none() {
if let Some(commit) = extract_commit_from_prompt(&prompt) {
} else if metadata.commit.is_none()
&& let Some(commit) = extract_commit_from_prompt(&prompt) {
metadata.commit = Some(commit);
}
}
if metadata.scope.is_none() && metadata.commit.is_some() {
metadata.scope = Some("commit".to_string());
@@ -2377,14 +2366,13 @@ async fn build_followup_review_request(
.as_ref()
.is_some_and(|scope| scope.eq_ignore_ascii_case("commit"));
if scope_is_commit && metadata.commit.is_none() {
if let Some((head_sha, _)) = head_commit_with_subject(cwd).await {
if scope_is_commit && metadata.commit.is_none()
&& let Some((head_sha, _)) = head_commit_with_subject(cwd).await {
metadata.commit = Some(head_sha.clone());
if metadata.current_branch.is_none() {
metadata.current_branch = current_branch_name(cwd).await;
}
}
}
if let Some(last_review) = state.last_review.as_ref() {
let recap = format_review_findings(last_review);
@@ -2441,8 +2429,7 @@ fn format_review_findings(output: &hanzo_core::protocol::ReviewOutputEvent) -> S
"path: {}:{}-{}",
f.code_location
.absolute_file_path
.to_string_lossy()
.to_string(),
.to_string_lossy(),
f.code_location.line_range.start,
f.code_location.line_range.end
);
@@ -2543,7 +2530,7 @@ fn write_review_json(
snapshot,
};
let json = serde_json::to_string_pretty(&payload)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?;
.map_err(|e| std::io::Error::other(e.to_string()))?;
std::fs::write(path, json)
}
+3 -5
View File
@@ -66,8 +66,7 @@ pub fn process_exec_slash_command(
}
Err(format!(
"Command '/{}' is not supported in exec mode.",
other
"Command '/{other}' is not supported in exec mode."
))
}
}
@@ -76,8 +75,7 @@ pub fn process_exec_slash_command(
fn handle_subagent(name: &str, args: &str, ctx: SlashContext<'_>) -> Result<SlashDispatch, String> {
if args.is_empty() {
return Err(format!(
"Error: /{name} requires a task description. Usage: /{name} <task>",
name = name
"Error: /{name} requires a task description. Usage: /{name} <task>"
));
}
@@ -110,7 +108,7 @@ fn handle_review(args_raw: &str) -> Result<SlashDispatch, String> {
let text = args_raw.trim().to_string();
(
text.clone(),
text.clone(),
text,
ReviewContextMetadata {
scope: Some("custom".to_string()),
..Default::default()
+1
View File
@@ -290,6 +290,7 @@ pub fn run(
/// `run` that sends one final batch of results over the provided channel.
///
/// Parameters mirror the upstream API for compatibility.
#[allow(clippy::too_many_arguments)]
pub fn run_streaming(
pattern_text: &str,
limit: NonZeroUsize,
+2 -2
View File
@@ -84,12 +84,12 @@ pub fn create_ghost_commit(
let repo_root = resolve_repository_root(options.repo_path)?;
let repo_prefix = repo_subdir(repo_root.as_path(), options.repo_path);
let parent_override = options.parent.map(|value| value.to_string());
let parent_override = options.parent.map(std::string::ToString::to_string);
let resolved_parent = resolve_head(repo_root.as_path())?;
let parent_ref = parent_override
.as_deref()
.or(resolved_parent.as_deref())
.map(|value| value.to_string());
.map(std::string::ToString::to_string);
let normalized_force = options
.force_include
+7 -10
View File
@@ -84,15 +84,13 @@ async fn request_user_code(
));
}
if looks_like_cloudflare_challenge(status, &headers, &body_text) {
if let Ok(via_browser) = request_user_code_via_browser(base_url, client_id).await {
if looks_like_cloudflare_challenge(status, &headers, &body_text)
&& let Ok(via_browser) = request_user_code_via_browser(base_url, client_id).await {
return Ok(via_browser);
}
}
return Err(std::io::Error::other(format!(
"device code request failed with status {}",
status
"device code request failed with status {status}"
)));
}
@@ -198,7 +196,7 @@ impl DeviceCodeSession {
pub async fn start(opts: ServerOptions) -> std::io::Result<Self> {
let client = default_client::create_client(&opts.originator);
let base_url = opts.issuer.trim_end_matches('/').to_string();
let api_base_url = format!("{}/api/accounts", base_url);
let api_base_url = format!("{base_url}/api/accounts");
let uc = request_user_code(&client, &api_base_url, &base_url, &opts.client_id).await?;
Ok(Self {
@@ -331,9 +329,9 @@ async fn request_user_code_via_browser(
let status = value
.get("status")
.and_then(|v| v.as_i64())
.and_then(serde_json::Value::as_i64)
.unwrap_or_default();
let ok = value.get("ok").and_then(|v| v.as_bool()).unwrap_or(false);
let ok = value.get("ok").and_then(serde_json::Value::as_bool).unwrap_or(false);
let body = value.get("body").and_then(|v| v.as_str()).unwrap_or("");
if ok {
@@ -346,8 +344,7 @@ async fn request_user_code_via_browser(
}
return Err(std::io::Error::other(format!(
"device code request failed with status {} while using browser fallback",
status
"device code request failed with status {status} while using browser fallback"
)));
}
+2 -3
View File
@@ -475,11 +475,10 @@ pub(crate) async fn persist_tokens_async(
let code_home = code_home.to_path_buf();
tokio::task::spawn_blocking(move || {
let auth_file = get_auth_file(&code_home);
if let Some(parent) = auth_file.parent() {
if !parent.exists() {
if let Some(parent) = auth_file.parent()
&& !parent.exists() {
std::fs::create_dir_all(parent).map_err(io::Error::other)?;
}
}
let mut tokens = TokenData {
id_token: parse_id_token(&id_token).map_err(io::Error::other)?,
@@ -24,7 +24,6 @@ use crate::outgoing_message::OutgoingMessageSender;
use crate::outgoing_message::OutgoingNotification;
use crate::session_store::SessionEntry;
use crate::session_store::SessionMap;
use serde_json;
pub async fn new_session(
request_id: RequestId,
+1 -1
View File
@@ -229,7 +229,7 @@ async fn run_code_tool_session_inner(
}
EventMsg::TaskComplete(TaskCompleteEvent { last_agent_message }) => {
let text = match last_agent_message {
Some(msg) => msg.clone(),
Some(msg) => msg,
None => "".to_string(),
};
let result = CallToolResult {
+14 -19
View File
@@ -96,7 +96,7 @@ impl MessageProcessor {
conversation_manager.clone(),
outgoing.clone(),
code_linux_sandbox_exe.clone(),
config_for_processor.clone(),
config_for_processor,
);
let session_map: SessionMap = Arc::new(Mutex::new(HashMap::new()));
Self {
@@ -112,8 +112,8 @@ impl MessageProcessor {
}
pub(crate) async fn process_request(&mut self, request: JSONRPCRequest) {
if let Ok(request_json) = serde_json::to_value(request.clone()) {
if let Ok(code_request) = serde_json::from_value::<ClientRequest>(request_json) {
if let Ok(request_json) = serde_json::to_value(request.clone())
&& let Ok(code_request) = serde_json::from_value::<ClientRequest>(request_json) {
// If the request is a Codex request, handle it with the Codex
// message processor.
self.code_message_processor
@@ -121,7 +121,6 @@ impl MessageProcessor {
.await;
return;
}
}
tracing::trace!("processing JSON-RPC request: {}", request.method);
// Hold on to the ID so we can respond.
@@ -214,8 +213,8 @@ impl MessageProcessor {
let mut request = request;
if request.method == mcp_types::InitializeRequest::METHOD {
if let Some(params) = request.params.as_mut() {
if request.method == mcp_types::InitializeRequest::METHOD
&& let Some(params) = request.params.as_mut() {
if let Some(protocol_version) = params.get_mut("protocolVersion") {
if let Some(num) = protocol_version.as_i64() {
*protocol_version = serde_json::Value::String(num.to_string());
@@ -254,7 +253,6 @@ impl MessageProcessor {
});
}
}
}
let client_request = match McpClientRequest::try_from(request) {
Ok(client_request) => client_request,
@@ -1010,14 +1008,14 @@ impl MessageProcessor {
let Some(session_entry) = session_entry else {
let error = JSONRPCErrorError {
code: INVALID_REQUEST_ERROR_CODE,
message: format!("unknown session id: {}", acp_session_id),
message: format!("unknown session id: {acp_session_id}"),
data: None,
};
self.outgoing.send_error(request_id, error).await;
return;
};
let session = session_entry.conversation.clone();
let session = session_entry.conversation;
let outgoing = self.outgoing.clone();
let requests_code_map = self.running_requests_id_to_code_uuid.clone();
@@ -1108,7 +1106,7 @@ impl MessageProcessor {
let Some(entry) = entry else {
return Err(JSONRPCErrorError {
code: INVALID_REQUEST_ERROR_CODE,
message: format!("unknown session id: {}", session_id),
message: format!("unknown session id: {session_id}"),
data: None,
});
};
@@ -1117,7 +1115,7 @@ impl MessageProcessor {
let selection = match resolve_model_selection(&model_id, &config_guard) {
Some(selection) => selection,
None => {
let message = format!("unknown model id: {}", model_id.to_string());
let message = format!("unknown model id: {}", model_id);
return Err(JSONRPCErrorError {
code: INVALID_REQUEST_ERROR_CODE,
message,
@@ -1143,15 +1141,14 @@ impl MessageProcessor {
drop(config_guard);
if let Some(op) = configure_op {
if let Err(err) = entry.conversation.submit(op).await {
if let Some(op) = configure_op
&& let Err(err) = entry.conversation.submit(op).await {
return Err(JSONRPCErrorError {
code: INTERNAL_ERROR_CODE,
message: err.to_string(),
data: None,
});
}
}
if let Some(models_meta) = models_meta_value {
let notification = acp::SessionNotification {
@@ -1467,14 +1464,12 @@ fn convert_mcp_servers(
}
acp::McpServer::Http { name, .. } => {
return Err(anyhow!(
"unsupported MCP transport for server '{}': HTTP servers are not yet supported",
name
"unsupported MCP transport for server '{name}': HTTP servers are not yet supported"
));
}
acp::McpServer::Sse { name, .. } => {
return Err(anyhow!(
"unsupported MCP transport for server '{}': SSE servers are not yet supported",
name
"unsupported MCP transport for server '{name}': SSE servers are not yet supported"
));
}
}
@@ -1625,7 +1620,7 @@ fn configure_session_op_from_config(config: &Config) -> Op {
model_text_verbosity: config.model_text_verbosity,
user_instructions: config.user_instructions.clone(),
base_instructions: config.base_instructions.clone(),
approval_policy: config.approval_policy.clone(),
approval_policy: config.approval_policy,
sandbox_policy: config.sandbox_policy.clone(),
disable_response_storage: config.disable_response_storage,
notify: config.notify.clone(),
+5 -7
View File
@@ -166,9 +166,8 @@ impl OllamaClient {
yield PullEvent::Error(err_msg.to_string());
return;
}
if let Some(status) = value.get("status").and_then(|s| s.as_str()) {
if status == "success" { yield PullEvent::Success; return; }
}
if let Some(status) = value.get("status").and_then(|s| s.as_str())
&& status == "success" { yield PullEvent::Success; return; }
}
}
}
@@ -241,7 +240,7 @@ impl OllamaClient {
// Try a few known locations/keys that different Ollama versions expose.
// Prefer numeric values; fall back to parsing strings if needed.
fn get_u64(v: &JsonValue, key: &str) -> Option<u64> {
v.get(key).and_then(|x| x.as_u64()).or_else(|| {
v.get(key).and_then(serde_json::Value::as_u64).or_else(|| {
v.get(key)
.and_then(|x| x.as_str())
.and_then(|s| s.parse::<u64>().ok())
@@ -249,11 +248,10 @@ impl OllamaClient {
}
// 1) Top-level `details.context_length` (observed in newer builds)
if let Some(details) = val.get("details").and_then(|d| d.as_object()) {
if let Some(n) = get_u64(&JsonValue::Object(details.clone()), "context_length") {
if let Some(details) = val.get("details").and_then(|d| d.as_object())
&& let Some(n) = get_u64(&JsonValue::Object(details.clone()), "context_length") {
return Ok(Some(n));
}
}
// 2) `model_info.context_length` (some builds)
if let Some(model_info) = val.get("model_info").and_then(|d| d.as_object()) {
+1 -1
View File
@@ -35,7 +35,7 @@ pub enum TurnLatencyPhase {
}
impl TurnLatencyPhase {
fn as_str(&self) -> &'static str {
fn as_str(self) -> &'static str {
match self {
TurnLatencyPhase::RequestScheduled => "request_scheduled",
TurnLatencyPhase::RequestCompleted => "request_completed",
+2 -3
View File
@@ -59,8 +59,8 @@ pub fn generate_ts(out_dir: &Path, prettier: Option<&Path>) -> Result<()> {
}
// Format with Prettier by passing individual files (no shell globbing)
if let Some(prettier_bin) = prettier {
if !ts_files.is_empty() {
if let Some(prettier_bin) = prettier
&& !ts_files.is_empty() {
let status = Command::new(prettier_bin)
.arg("--write")
.args(ts_files.iter().map(|p| p.as_os_str()))
@@ -72,7 +72,6 @@ pub fn generate_ts(out_dir: &Path, prettier: Option<&Path>) -> Result<()> {
return Err(anyhow!("Prettier failed with status {status}"));
}
}
}
Ok(())
}
+1 -1
View File
@@ -19,7 +19,7 @@ fn make_en_us_formatter() -> DecimalFormatter {
fn with_formatter<T>(f: impl FnOnce(&DecimalFormatter) -> T) -> T {
thread_local! {
static FORMATTER: OnceCell<DecimalFormatter> = OnceCell::new();
static FORMATTER: OnceCell<DecimalFormatter> = const { OnceCell::new() };
}
FORMATTER.with(|cell| {

Some files were not shown because too many files have changed in this diff Show More