From c7b3296ef655f524b76f3c2e92b93780ad0cc558 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Wed, 8 Apr 2026 11:21:13 +0900 Subject: [PATCH] =?UTF-8?q?style:=20cargo=20fmt=20=E2=80=94=20fix=20CI=20f?= =?UTF-8?q?ormatting=20failures?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pre-existing formatting issues in anthropic.rs surfaced by CI cargo fmt check. No functional changes. --- ROADMAP.md | 2 +- rust/crates/api/src/providers/anthropic.rs | 38 ++-- .../crates/api/src/providers/openai_compat.rs | 35 ++-- rust/crates/runtime/src/config.rs | 6 +- rust/crates/runtime/src/lib.rs | 10 +- rust/crates/runtime/src/mcp_server.rs | 4 +- rust/crates/runtime/src/prompt.rs | 13 +- rust/crates/runtime/src/session.rs | 13 +- rust/crates/runtime/src/worker_boot.rs | 34 +++- rust/crates/rusty-claude-cli/src/main.rs | 133 +++++++------- rust/crates/tools/src/lib.rs | 165 +++++++++--------- 11 files changed, 251 insertions(+), 202 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 0eec812..d27e3fa 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -308,7 +308,7 @@ Priority order: P0 = blocks CI/green state, P1 = blocks integration wiring, P2 = 19. **Subcommand help falls through into runtime/API path** — **done**: `claw doctor --help`, `claw status --help`, `claw sandbox --help`, and nested `mcp`/`skills` help are now intercepted locally without runtime/provider startup, with regression tests covering the direct CLI paths. 20. **Session state classification gap (working vs blocked vs finished vs truly stale)** — **done**: agent manifests now derive machine states such as `working`, `blocked_background_job`, `blocked_merge_conflict`, `degraded_mcp`, `interrupted_transport`, `finished_pending_report`, and `finished_cleanable`, and terminal-state persistence records commit provenance plus derived state so downstream monitoring can distinguish quiet progress from truly idle sessions. 21. **Resumed `/status` JSON parity gap** — dogfooding shows fresh `claw status --output-format json` now emits structured JSON, but resumed slash-command status still leaks through a text-shaped path in at least one dispatch path. Local CI-equivalent repro fails `rust/crates/rusty-claude-cli/tests/resume_slash_commands.rs::resumed_status_command_emits_structured_json_when_requested` with `expected value at line 1 column 1`, so resumed automation can receive text where JSON was explicitly requested. **Action:** unify fresh vs resumed `/status` rendering through one output-format contract and add regression coverage so resumed JSON output is guaranteed valid. -22. **Opaque failure surface for session/runtime crashes** — repeated dogfood-facing failures can currently collapse to generic wrappers like `Something went wrong while processing your request. Please try again, or use /new to start a fresh session.` without exposing whether the fault was provider auth, session corruption, slash-command dispatch, render failure, or transport/runtime panic. This blocks fast self-recovery and turns actionable clawability bugs into blind retries. **Action:** preserve a short user-safe failure class (`provider_auth`, `session_load`, `command_dispatch`, `render`, `runtime_panic`, etc.), attach a local trace/session id, and ensure operators can jump from the chat-visible error to the exact failure log quickly. +22. **Opaque failure surface for session/runtime crashes** — **done**: `safe_failure_class()` in `error.rs` classifies all API errors into 8 user-safe classes (`provider_auth`, `provider_internal`, `provider_retry_exhausted`, `provider_rate_limit`, `provider_transport`, `provider_error`, `context_window`, `runtime_io`). `format_user_visible_api_error` in `main.rs` attaches session ID + request trace ID to every user-visible error. Coverage in `opaque_provider_wrapper_surfaces_failure_class_session_and_trace` and 3 related tests. 23. **`doctor --output-format json` check-level structure gap** — **done**: `claw doctor --output-format json` now keeps the human-readable `message`/`report` while also emitting structured per-check diagnostics (`name`, `status`, `summary`, `details`, plus typed fields like workspace paths and sandbox fallback data), with regression coverage in `output_format_contract.rs`. 24. **Plugin lifecycle init/shutdown test flakes under workspace-parallel execution** — dogfooding surfaced that `build_runtime_runs_plugin_lifecycle_init_and_shutdown` can fail under `cargo test --workspace` while passing in isolation because sibling tests race on tempdir-backed shell init script paths. This is test brittleness rather than a code-path regression, but it still destabilizes CI confidence and wastes diagnosis cycles. **Action:** isolate temp resources per test robustly (unique dirs + no shared cwd assumptions), audit cleanup timing, and add a regression guard so the plugin lifecycle test remains stable under parallel workspace execution. 26. **Resumed local-command JSON parity gap** — **done**: direct `claw --output-format json` already had structured renderers for `sandbox`, `mcp`, `skills`, `version`, and `init`, but resumed `claw --output-format json --resume /…` paths still fell back to prose because resumed slash dispatch only emitted JSON for `/status`. Resumed `/sandbox`, `/mcp`, `/skills`, `/version`, and `/init` now reuse the same JSON envelopes as their direct CLI counterparts, with regression coverage in `rust/crates/rusty-claude-cli/tests/resume_slash_commands.rs` and `rust/crates/rusty-claude-cli/tests/output_format_contract.rs`. diff --git a/rust/crates/api/src/providers/anthropic.rs b/rust/crates/api/src/providers/anthropic.rs index 830ec4a..eefcd20 100644 --- a/rust/crates/api/src/providers/anthropic.rs +++ b/rust/crates/api/src/providers/anthropic.rs @@ -515,7 +515,10 @@ impl AnthropicClient { input_tokens: u32, } - let request_url = format!("{}/v1/messages/count_tokens", self.base_url.trim_end_matches('/')); + let request_url = format!( + "{}/v1/messages/count_tokens", + self.base_url.trim_end_matches('/') + ); let mut request_body = self.request_profile.render_json_body(request)?; strip_unsupported_beta_body_fields(&mut request_body); let response = self @@ -528,12 +531,7 @@ impl AnthropicClient { let response = expect_success(response).await?; let body = response.text().await.map_err(ApiError::from)?; let parsed = serde_json::from_str::(&body).map_err(|error| { - ApiError::json_deserialize( - "Anthropic count_tokens", - &request.model, - &body, - error, - ) + ApiError::json_deserialize("Anthropic count_tokens", &request.model, &body, error) })?; Ok(parsed.input_tokens) } @@ -597,7 +595,9 @@ fn jitter_for_base(base: Duration) -> Duration { let tick = JITTER_COUNTER.fetch_add(1, Ordering::Relaxed); // splitmix64 finalizer — mixes the low bits so large bases still see // jitter across their full range instead of being clamped to subsec nanos. - let mut mixed = raw_nanos.wrapping_add(tick).wrapping_add(0x9E37_79B9_7F4A_7C15); + let mut mixed = raw_nanos + .wrapping_add(tick) + .wrapping_add(0x9E37_79B9_7F4A_7C15); mixed = (mixed ^ (mixed >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9); mixed = (mixed ^ (mixed >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB); mixed ^= mixed >> 31; @@ -1268,7 +1268,7 @@ mod tests { tools: None, tool_choice: None, stream: false, - ..Default::default() + ..Default::default() }; assert!(request.with_streaming().stream); @@ -1464,10 +1464,14 @@ mod tests { // temperature is kept (Anthropic supports it) assert_eq!(body["temperature"], serde_json::json!(0.7)); // frequency_penalty and presence_penalty are removed - assert!(body.get("frequency_penalty").is_none(), - "frequency_penalty must be stripped for Anthropic"); - assert!(body.get("presence_penalty").is_none(), - "presence_penalty must be stripped for Anthropic"); + assert!( + body.get("frequency_penalty").is_none(), + "frequency_penalty must be stripped for Anthropic" + ); + assert!( + body.get("presence_penalty").is_none(), + "presence_penalty must be stripped for Anthropic" + ); // stop is renamed to stop_sequences assert!(body.get("stop").is_none(), "stop must be renamed"); assert_eq!(body["stop_sequences"], serde_json::json!(["\n"])); @@ -1484,8 +1488,10 @@ mod tests { super::strip_unsupported_beta_body_fields(&mut body); assert!(body.get("stop").is_none()); - assert!(body.get("stop_sequences").is_none(), - "empty stop should not produce stop_sequences"); + assert!( + body.get("stop_sequences").is_none(), + "empty stop should not produce stop_sequences" + ); } #[test] @@ -1499,7 +1505,7 @@ mod tests { tools: None, tool_choice: None, stream: false, - ..Default::default() + ..Default::default() }; let mut rendered = client diff --git a/rust/crates/api/src/providers/openai_compat.rs b/rust/crates/api/src/providers/openai_compat.rs index aa56498..59919b0 100644 --- a/rust/crates/api/src/providers/openai_compat.rs +++ b/rust/crates/api/src/providers/openai_compat.rs @@ -135,12 +135,7 @@ impl OpenAiCompatClient { let request_id = request_id_from_headers(response.headers()); let body = response.text().await.map_err(ApiError::from)?; let payload = serde_json::from_str::(&body).map_err(|error| { - ApiError::json_deserialize( - self.config.provider_name, - &request.model, - &body, - error, - ) + ApiError::json_deserialize(self.config.provider_name, &request.model, &body, error) })?; let mut normalized = normalize_response(&request.model, payload)?; if normalized.request_id.is_none() { @@ -160,10 +155,7 @@ impl OpenAiCompatClient { Ok(MessageStream { request_id: request_id_from_headers(response.headers()), response, - parser: OpenAiSseParser::with_context( - self.config.provider_name, - request.model.clone(), - ), + parser: OpenAiSseParser::with_context(self.config.provider_name, request.model.clone()), pending: VecDeque::new(), done: false, state: StreamState::new(request.model.clone()), @@ -253,7 +245,9 @@ fn jitter_for_base(base: Duration) -> Duration { .map(|elapsed| u64::try_from(elapsed.as_nanos()).unwrap_or(u64::MAX)) .unwrap_or(0); let tick = JITTER_COUNTER.fetch_add(1, Ordering::Relaxed); - let mut mixed = raw_nanos.wrapping_add(tick).wrapping_add(0x9E37_79B9_7F4A_7C15); + let mut mixed = raw_nanos + .wrapping_add(tick) + .wrapping_add(0x9E37_79B9_7F4A_7C15); mixed = (mixed ^ (mixed >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9); mixed = (mixed ^ (mixed >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB); mixed ^= mixed >> 31; @@ -1110,7 +1104,7 @@ mod tests { tools: None, tool_choice: None, stream: true, - ..Default::default() + ..Default::default() }, OpenAiCompatConfig::openai(), ); @@ -1129,7 +1123,7 @@ mod tests { tools: None, tool_choice: None, stream: true, - ..Default::default() + ..Default::default() }, OpenAiCompatConfig::xai(), ); @@ -1240,8 +1234,14 @@ mod tests { ..Default::default() }; let payload = build_chat_completion_request(&request, OpenAiCompatConfig::openai()); - assert!(payload.get("temperature").is_none(), "reasoning model should strip temperature"); - assert!(payload.get("top_p").is_none(), "reasoning model should strip top_p"); + assert!( + payload.get("temperature").is_none(), + "reasoning model should strip temperature" + ); + assert!( + payload.get("top_p").is_none(), + "reasoning model should strip top_p" + ); assert!(payload.get("frequency_penalty").is_none()); assert!(payload.get("presence_penalty").is_none()); // stop is safe for all providers @@ -1269,7 +1269,10 @@ mod tests { ..Default::default() }; let payload = build_chat_completion_request(&request, OpenAiCompatConfig::openai()); - assert!(payload.get("temperature").is_none(), "temperature should be absent"); + assert!( + payload.get("temperature").is_none(), + "temperature should be absent" + ); assert!(payload.get("top_p").is_none(), "top_p should be absent"); assert!(payload.get("frequency_penalty").is_none()); assert!(payload.get("presence_penalty").is_none()); diff --git a/rust/crates/runtime/src/config.rs b/rust/crates/runtime/src/config.rs index 1aad012..c1fe496 100644 --- a/rust/crates/runtime/src/config.rs +++ b/rust/crates/runtime/src/config.rs @@ -908,8 +908,10 @@ fn parse_optional_trusted_roots(root: &JsonValue) -> Result, ConfigE let Some(object) = root.as_object() else { return Ok(Vec::new()); }; - Ok(optional_string_array(object, "trustedRoots", "merged settings.trustedRoots")? - .unwrap_or_default()) + Ok( + optional_string_array(object, "trustedRoots", "merged settings.trustedRoots")? + .unwrap_or_default(), + ) } fn parse_filesystem_mode_label(value: &str) -> Result { diff --git a/rust/crates/runtime/src/lib.rs b/rust/crates/runtime/src/lib.rs index e87dfd7..e691df2 100644 --- a/rust/crates/runtime/src/lib.rs +++ b/rust/crates/runtime/src/lib.rs @@ -56,10 +56,6 @@ pub use compact::{ compact_session, estimate_session_tokens, format_compact_summary, get_compact_continuation_message, should_compact, CompactionConfig, CompactionResult, }; -pub use config_validate::{ - check_unsupported_format, format_diagnostics, validate_config_file, ConfigDiagnostic, - DiagnosticKind, ValidationResult, -}; pub use config::{ ConfigEntry, ConfigError, ConfigLoader, ConfigSource, McpConfigCollection, McpManagedProxyServerConfig, McpOAuthConfig, McpRemoteServerConfig, McpSdkServerConfig, @@ -68,17 +64,21 @@ pub use config::{ RuntimeHookConfig, RuntimePermissionRuleConfig, RuntimePluginConfig, ScopedMcpServerConfig, CLAW_SETTINGS_SCHEMA_NAME, }; +pub use config_validate::{ + check_unsupported_format, format_diagnostics, validate_config_file, ConfigDiagnostic, + DiagnosticKind, ValidationResult, +}; pub use conversation::{ auto_compaction_threshold_from_env, ApiClient, ApiRequest, AssistantEvent, AutoCompactionEvent, ConversationRuntime, PromptCacheEvent, RuntimeError, StaticToolExecutor, ToolError, ToolExecutor, TurnSummary, }; -pub use git_context::{GitCommitEntry, GitContext}; pub use file_ops::{ edit_file, glob_search, grep_search, read_file, write_file, EditFileOutput, GlobSearchOutput, GrepSearchInput, GrepSearchOutput, ReadFileOutput, StructuredPatchHunk, TextFilePayload, WriteFileOutput, }; +pub use git_context::{GitCommitEntry, GitContext}; pub use hooks::{ HookAbortSignal, HookEvent, HookProgressEvent, HookProgressReporter, HookRunResult, HookRunner, }; diff --git a/rust/crates/runtime/src/mcp_server.rs b/rust/crates/runtime/src/mcp_server.rs index 23b9bc3..4610ed4 100644 --- a/rust/crates/runtime/src/mcp_server.rs +++ b/rust/crates/runtime/src/mcp_server.rs @@ -366,9 +366,7 @@ mod tests { server_name: "test".to_string(), server_version: "0.0.0".to_string(), tools: Vec::new(), - tool_handler: Box::new(|name, args| { - Ok(format!("called {name} with {args}")) - }), + tool_handler: Box::new(|name, args| Ok(format!("called {name} with {args}"))), }, stdin: BufReader::new(stdin()), stdout: stdout(), diff --git a/rust/crates/runtime/src/prompt.rs b/rust/crates/runtime/src/prompt.rs index ad85d0b..e46b7eb 100644 --- a/rust/crates/runtime/src/prompt.rs +++ b/rust/crates/runtime/src/prompt.rs @@ -253,7 +253,6 @@ fn read_git_status(cwd: &Path) -> Option { } } - fn read_git_diff(cwd: &Path) -> Option { let mut sections = Vec::new(); @@ -715,8 +714,16 @@ mod tests { .render(); // then: branch, recent commits and staged files are present in context - let gc = context.git_context.as_ref().expect("git context should be present"); - let commits: String = gc.recent_commits.iter().map(|c| c.subject.clone()).collect::>().join("\n"); + let gc = context + .git_context + .as_ref() + .expect("git context should be present"); + let commits: String = gc + .recent_commits + .iter() + .map(|c| c.subject.clone()) + .collect::>() + .join("\n"); assert!(commits.contains("first commit")); assert!(commits.contains("second commit")); assert!(commits.contains("third commit")); diff --git a/rust/crates/runtime/src/session.rs b/rust/crates/runtime/src/session.rs index b7b7a5c..689b65a 100644 --- a/rust/crates/runtime/src/session.rs +++ b/rust/crates/runtime/src/session.rs @@ -1441,8 +1441,12 @@ mod tests { /// Called by external consumers (e.g. clawhip) to enumerate sessions for a CWD. #[allow(dead_code)] pub fn workspace_sessions_dir(cwd: &std::path::Path) -> Result { - let store = crate::session_control::SessionStore::from_cwd(cwd) - .map_err(|e| SessionError::Io(std::io::Error::new(std::io::ErrorKind::Other, e.to_string())))?; + let store = crate::session_control::SessionStore::from_cwd(cwd).map_err(|e| { + SessionError::Io(std::io::Error::new( + std::io::ErrorKind::Other, + e.to_string(), + )) + })?; Ok(store.sessions_dir().to_path_buf()) } @@ -1481,7 +1485,10 @@ mod workspace_sessions_dir_tests { let dir_a = workspace_sessions_dir(&tmp_a).expect("dir a"); let dir_b = workspace_sessions_dir(&tmp_b).expect("dir b"); - assert_ne!(dir_a, dir_b, "different CWDs must produce different session dirs"); + assert_ne!( + dir_a, dir_b, + "different CWDs must produce different session dirs" + ); fs::remove_dir_all(&tmp_a).ok(); fs::remove_dir_all(&tmp_b).ok(); diff --git a/rust/crates/runtime/src/worker_boot.rs b/rust/crates/runtime/src/worker_boot.rs index 93e4464..6909768 100644 --- a/rust/crates/runtime/src/worker_boot.rs +++ b/rust/crates/runtime/src/worker_boot.rs @@ -1105,7 +1105,13 @@ mod tests { #[test] fn emit_state_file_writes_worker_status_on_transition() { - let cwd_path = std::env::temp_dir().join(format!("claw-state-test-{}", std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap_or_default().as_nanos())); + let cwd_path = std::env::temp_dir().join(format!( + "claw-state-test-{}", + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap_or_default() + .as_nanos() + )); std::fs::create_dir_all(&cwd_path).expect("test dir should create"); let cwd = cwd_path.to_str().expect("test path should be utf8"); let registry = WorkerRegistry::new(); @@ -1113,11 +1119,19 @@ mod tests { // After create the worker is Spawning — state file should exist let state_path = cwd_path.join(".claw").join("worker-state.json"); - assert!(state_path.exists(), "state file should exist after worker creation"); + assert!( + state_path.exists(), + "state file should exist after worker creation" + ); let raw = std::fs::read_to_string(&state_path).expect("state file should be readable"); - let value: serde_json::Value = serde_json::from_str(&raw).expect("state file should be valid JSON"); - assert_eq!(value["status"].as_str(), Some("spawning"), "initial status should be spawning"); + let value: serde_json::Value = + serde_json::from_str(&raw).expect("state file should be valid JSON"); + assert_eq!( + value["status"].as_str(), + Some("spawning"), + "initial status should be spawning" + ); assert_eq!(value["is_ready"].as_bool(), Some(false)); // Transition to ReadyForPrompt by observing trust-cleared text @@ -1125,14 +1139,20 @@ mod tests { .observe(&worker.worker_id, "Ready for input\n>") .expect("observe ready should succeed"); - let raw = std::fs::read_to_string(&state_path).expect("state file should be readable after observe"); - let value: serde_json::Value = serde_json::from_str(&raw).expect("state file should be valid JSON after observe"); + let raw = std::fs::read_to_string(&state_path) + .expect("state file should be readable after observe"); + let value: serde_json::Value = + serde_json::from_str(&raw).expect("state file should be valid JSON after observe"); assert_eq!( value["status"].as_str(), Some("ready_for_prompt"), "status should be ready_for_prompt after observe" ); - assert_eq!(value["is_ready"].as_bool(), Some(true), "is_ready should be true when ReadyForPrompt"); + assert_eq!( + value["is_ready"].as_bool(), + Some(true), + "is_ready should be true when ReadyForPrompt" + ); } #[test] diff --git a/rust/crates/rusty-claude-cli/src/main.rs b/rust/crates/rusty-claude-cli/src/main.rs index 3c09cc5..e5d55e4 100644 --- a/rust/crates/rusty-claude-cli/src/main.rs +++ b/rust/crates/rusty-claude-cli/src/main.rs @@ -54,7 +54,9 @@ use runtime::{ }; use serde::Deserialize; use serde_json::{json, Map, Value}; -use tools::{execute_tool, mvp_tool_specs, GlobalToolRegistry, RuntimeToolDefinition, ToolSearchOutput}; +use tools::{ + execute_tool, mvp_tool_specs, GlobalToolRegistry, RuntimeToolDefinition, ToolSearchOutput, +}; const DEFAULT_MODEL: &str = "claude-opus-4-6"; fn max_tokens_for_model(model: &str) -> u32 { @@ -205,8 +207,11 @@ fn run() -> Result<(), Box> { run_stale_base_preflight(base_commit.as_deref()); let stdin_context = read_piped_stdin(); let effective_prompt = merge_prompt_with_stdin(&prompt, stdin_context.as_deref()); - LiveCli::new(model, true, allowed_tools, permission_mode)? - .run_turn_with_output(&effective_prompt, output_format, false)?; + LiveCli::new(model, true, allowed_tools, permission_mode)?.run_turn_with_output( + &effective_prompt, + output_format, + false, + )?; } CliAction::Login { output_format } => run_login(output_format)?, CliAction::Logout { output_format } => run_logout(output_format)?, @@ -942,11 +947,7 @@ fn config_permission_mode_for_current_dir() -> Option { fn config_model_for_current_dir() -> Option { let cwd = env::current_dir().ok()?; let loader = ConfigLoader::default_for(&cwd); - loader - .load() - .ok()? - .model() - .map(ToOwned::to_owned) + loader.load().ok()?.model().map(ToOwned::to_owned) } fn resolve_repl_model(cli_model: String) -> String { @@ -1021,10 +1022,7 @@ fn parse_system_prompt_args( }) } -fn parse_export_args( - args: &[String], - output_format: CliOutputFormat, -) -> Result { +fn parse_export_args(args: &[String], output_format: CliOutputFormat) -> Result { let mut session_reference = LATEST_SESSION_REFERENCE.to_string(); let mut output_path: Option = None; let mut index = 0; @@ -1336,8 +1334,13 @@ fn run_worker_state(output_format: CliOutputFormat) -> Result<(), Box println!("No worker state file found at {}", state_path.display()), - CliOutputFormat::Json => println!("{}", serde_json::json!({"error": "no_state_file", "path": state_path.display().to_string()})), + CliOutputFormat::Text => { + println!("No worker state file found at {}", state_path.display()) + } + CliOutputFormat::Json => println!( + "{}", + serde_json::json!({"error": "no_state_file", "path": state_path.display().to_string()}) + ), } return Ok(()); } @@ -2660,7 +2663,8 @@ fn run_resume_command( json: None, }), SlashCommand::History { count } => { - let limit = parse_history_count(count.as_deref()).map_err(|error| -> Box { error.into() })?; + let limit = parse_history_count(count.as_deref()) + .map_err(|error| -> Box { error.into() })?; let entries = collect_session_prompt_history(session); Ok(ResumeCommandOutcome { session: session.clone(), @@ -5331,16 +5335,18 @@ fn format_history_timestamp(timestamp_ms: u64) -> String { let seconds = seconds_of_day % 60; let (year, month, day) = civil_from_days(i64::try_from(days_since_epoch).unwrap_or(0)); - format!( - "{year:04}-{month:02}-{day:02}T{hours:02}:{minutes:02}:{seconds:02}.{subsec_ms:03}Z" - ) + format!("{year:04}-{month:02}-{day:02}T{hours:02}:{minutes:02}:{seconds:02}.{subsec_ms:03}Z") } // Computes civil (Gregorian) year/month/day from days since the Unix epoch // (1970-01-01) using Howard Hinnant's `civil_from_days` algorithm. fn civil_from_days(days: i64) -> (i32, u32, u32) { let z = days + 719_468; - let era = if z >= 0 { z / 146_097 } else { (z - 146_096) / 146_097 }; + let era = if z >= 0 { + z / 146_097 + } else { + (z - 146_096) / 146_097 + }; let doe = (z - era * 146_097) as u64; // [0, 146_096] let yoe = (doe - doe / 1_460 + doe / 36_524 - doe / 146_096) / 365; // [0, 399] let y = yoe as i64 + era * 400; @@ -6391,7 +6397,10 @@ impl ApiClient for AnthropicRuntimeClient { .await; match result { Ok(events) => return Ok(events), - Err(error) if error.to_string().contains("post-tool stall") && attempt < max_attempts => { + Err(error) + if error.to_string().contains("post-tool stall") + && attempt < max_attempts => + { // Stalled after tool completion — nudge the model by // re-sending the same request. continue; @@ -6400,9 +6409,7 @@ impl ApiClient for AnthropicRuntimeClient { } } - Err(RuntimeError::new( - "post-tool continuation nudge exhausted", - )) + Err(RuntimeError::new("post-tool continuation nudge exhausted")) }) } } @@ -6416,13 +6423,13 @@ impl AnthropicRuntimeClient { message_request: &MessageRequest, apply_stall_timeout: bool, ) -> Result, RuntimeError> { - let mut stream = - self.client - .stream_message(message_request) - .await - .map_err(|error| { - RuntimeError::new(format_user_visible_api_error(&self.session_id, &error)) - })?; + let mut stream = self + .client + .stream_message(message_request) + .await + .map_err(|error| { + RuntimeError::new(format_user_visible_api_error(&self.session_id, &error)) + })?; let mut stdout = io::stdout(); let mut sink = io::sink(); let out: &mut dyn Write = if self.emit_output { @@ -6442,10 +6449,7 @@ impl AnthropicRuntimeClient { let next = if apply_stall_timeout && !received_any_event { match tokio::time::timeout(POST_TOOL_STALL_TIMEOUT, stream.next_event()).await { Ok(inner) => inner.map_err(|error| { - RuntimeError::new(format_user_visible_api_error( - &self.session_id, - &error, - )) + RuntimeError::new(format_user_visible_api_error(&self.session_id, &error)) })?, Err(_elapsed) => { return Err(RuntimeError::new( @@ -6629,9 +6633,15 @@ fn format_context_window_blocked_error(session_id: &str, error: &api::ApiError) context_window_tokens, } => { lines.push(format!(" Model {model}")); - lines.push(format!(" Input estimate ~{estimated_input_tokens} tokens (heuristic)")); - lines.push(format!(" Requested output {requested_output_tokens} tokens")); - lines.push(format!(" Total estimate ~{estimated_total_tokens} tokens (heuristic)")); + lines.push(format!( + " Input estimate ~{estimated_input_tokens} tokens (heuristic)" + )); + lines.push(format!( + " Requested output {requested_output_tokens} tokens" + )); + lines.push(format!( + " Total estimate ~{estimated_total_tokens} tokens (heuristic)" + )); lines.push(format!(" Context window {context_window_tokens} tokens")); } api::ApiError::Api { message, body, .. } => { @@ -7604,7 +7614,10 @@ fn print_help_to(out: &mut impl Write) -> io::Result<()> { writeln!(out, " claw login")?; writeln!(out, " claw logout")?; writeln!(out, " claw init")?; - writeln!(out, " claw export [PATH] [--session SESSION] [--output PATH]")?; + writeln!( + out, + " claw export [PATH] [--session SESSION] [--output PATH]" + )?; writeln!( out, " Dump the latest (or named) session as markdown; writes to PATH or stdout" @@ -7669,10 +7682,7 @@ fn print_help_to(out: &mut impl Write) -> io::Result<()> { out, " claw --output-format json prompt \"explain src/main.rs\"" )?; - writeln!( - out, - " claw --compact \"summarize Cargo.toml\" | wc -l" - )?; + writeln!(out, " claw --compact \"summarize Cargo.toml\" | wc -l")?; writeln!( out, " claw --allowedTools read,glob \"summarize Cargo.toml\"" @@ -7723,18 +7733,19 @@ mod tests { format_resume_report, format_status_report, format_tool_call_start, format_tool_result, format_ultraplan_report, format_unknown_slash_command, format_unknown_slash_command_message, format_user_visible_api_error, - merge_prompt_with_stdin, normalize_permission_mode, parse_args, parse_git_status_branch, - parse_git_status_metadata_for, parse_git_workspace_summary, permission_policy, - print_help_to, push_output_block, render_config_report, render_diff_report, - render_diff_report_for, render_memory_report, render_repl_help, render_resume_usage, - resolve_model_alias, resolve_model_alias_with_config, resolve_repl_model, - resolve_session_reference, response_to_events, resume_supported_slash_commands, - run_resume_command, slash_command_completion_candidates_with_sessions, status_context, - validate_no_args, write_mcp_server_fixture, CliAction, CliOutputFormat, CliToolExecutor, - GitWorkspaceSummary, InternalPromptProgressEvent, InternalPromptProgressState, LiveCli, - LocalHelpTopic, SlashCommand, StatusUsage, DEFAULT_MODEL, LATEST_SESSION_REFERENCE, - PromptHistoryEntry, render_prompt_history_report, parse_history_count, - parse_export_args, render_session_markdown, summarize_tool_payload_for_markdown, short_tool_id, + merge_prompt_with_stdin, normalize_permission_mode, parse_args, parse_export_args, + parse_git_status_branch, parse_git_status_metadata_for, parse_git_workspace_summary, + parse_history_count, permission_policy, print_help_to, push_output_block, + render_config_report, render_diff_report, render_diff_report_for, render_memory_report, + render_prompt_history_report, render_repl_help, render_resume_usage, + render_session_markdown, resolve_model_alias, resolve_model_alias_with_config, + resolve_repl_model, resolve_session_reference, response_to_events, + resume_supported_slash_commands, run_resume_command, short_tool_id, + slash_command_completion_candidates_with_sessions, status_context, + summarize_tool_payload_for_markdown, validate_no_args, write_mcp_server_fixture, CliAction, + CliOutputFormat, CliToolExecutor, GitWorkspaceSummary, InternalPromptProgressEvent, + InternalPromptProgressState, LiveCli, LocalHelpTopic, PromptHistoryEntry, SlashCommand, + StatusUsage, DEFAULT_MODEL, LATEST_SESSION_REFERENCE, }; use api::{ApiError, MessageResponse, OutputContentBlock, Usage}; use plugins::{ @@ -8586,8 +8597,12 @@ mod tests { } ); assert_eq!( - parse_args(&["state".to_string(), "--output-format".to_string(), "json".to_string()]) - .expect("state --output-format json should parse"), + parse_args(&[ + "state".to_string(), + "--output-format".to_string(), + "json".to_string() + ]) + .expect("state --output-format json should parse"), CliAction::State { output_format: CliOutputFormat::Json, } @@ -9417,8 +9432,7 @@ mod tests { std::env::remove_var("ANTHROPIC_MODEL"); std::env::set_var("ANTHROPIC_MODEL", "sonnet"); - let resolved = - with_current_dir(&root, || resolve_repl_model(DEFAULT_MODEL.to_string())); + let resolved = with_current_dir(&root, || resolve_repl_model(DEFAULT_MODEL.to_string())); assert_eq!(resolved, "claude-sonnet-4-6"); @@ -9437,8 +9451,7 @@ mod tests { std::env::set_var("CLAW_CONFIG_HOME", &config_home); std::env::remove_var("ANTHROPIC_MODEL"); - let resolved = - with_current_dir(&root, || resolve_repl_model(DEFAULT_MODEL.to_string())); + let resolved = with_current_dir(&root, || resolve_repl_model(DEFAULT_MODEL.to_string())); assert_eq!(resolved, DEFAULT_MODEL); diff --git a/rust/crates/tools/src/lib.rs b/rust/crates/tools/src/lib.rs index 0ccd700..dcc7f0b 100644 --- a/rust/crates/tools/src/lib.rs +++ b/rust/crates/tools/src/lib.rs @@ -1244,10 +1244,8 @@ fn execute_tool_with_enforcer( } "WorkerRestart" => from_value::(input).and_then(run_worker_restart), "WorkerTerminate" => from_value::(input).and_then(run_worker_terminate), - "WorkerObserveCompletion" => { - from_value::(input) - .and_then(run_worker_observe_completion) - } + "WorkerObserveCompletion" => from_value::(input) + .and_then(run_worker_observe_completion), "TeamCreate" => from_value::(input).and_then(run_team_create), "TeamDelete" => from_value::(input).and_then(run_team_delete), "CronCreate" => from_value::(input).and_then(run_cron_create), @@ -1510,9 +1508,7 @@ fn run_worker_terminate(input: WorkerIdInput) -> Result { } #[allow(clippy::needless_pass_by_value)] -fn run_worker_observe_completion( - input: WorkerObserveCompletionInput, -) -> Result { +fn run_worker_observe_completion(input: WorkerObserveCompletionInput) -> Result { let worker = global_worker_registry().observe_completion( &input.worker_id, &input.finish_reason, @@ -3826,7 +3822,8 @@ impl ApiClient for ProviderRuntimeClient { }) .collect::>(); let messages = convert_messages(&request.messages); - let system = (!request.system_prompt.is_empty()).then(|| request.system_prompt.join("\n\n")); + let system = + (!request.system_prompt.is_empty()).then(|| request.system_prompt.join("\n\n")); let tool_choice = (!self.allowed_tools.is_empty()).then_some(ToolChoice::Auto); let runtime = &self.runtime; @@ -5379,8 +5376,8 @@ mod tests { GlobalToolRegistry, LaneEventName, LaneFailureClass, ProviderRuntimeClient, SubagentToolExecutor, }; - use runtime::ProviderFallbackConfig; use api::OutputContentBlock; + use runtime::ProviderFallbackConfig; use runtime::{ permission_enforcer::PermissionEnforcer, ApiRequest, AssistantEvent, ConversationRuntime, PermissionMode, PermissionPolicy, RuntimeError, Session, TaskPacket, ToolExecutor, @@ -5566,11 +5563,7 @@ mod tests { // Use the actual OS temp dir so the worktree path matches the allowlist let tmp_root = std::env::temp_dir().to_str().expect("utf-8").to_string(); let settings = format!("{{\"trustedRoots\": [\"{tmp_root}\"]}}"); - fs::write( - claw_dir.join("settings.json"), - settings, - ) - .expect("write settings"); + fs::write(claw_dir.join("settings.json"), settings).expect("write settings"); // WorkerCreate with no per-call trusted_roots — config should supply them let cwd = worktree.to_str().expect("valid utf-8").to_string(); @@ -5605,13 +5598,13 @@ mod tests { let worker_id = output["worker_id"].as_str().expect("worker_id").to_string(); // Terminate - let terminated = execute_tool( - "WorkerTerminate", - &json!({"worker_id": worker_id}), - ) - .expect("WorkerTerminate should succeed"); + let terminated = execute_tool("WorkerTerminate", &json!({"worker_id": worker_id})) + .expect("WorkerTerminate should succeed"); let term_output: serde_json::Value = serde_json::from_str(&terminated).expect("json"); - assert_eq!(term_output["status"], "finished", "terminated worker should be finished"); + assert_eq!( + term_output["status"], "finished", + "terminated worker should be finished" + ); assert_eq!( term_output["prompt_in_flight"], false, "prompt_in_flight should be cleared on termination" @@ -5637,11 +5630,8 @@ mod tests { .expect("WorkerObserve should succeed"); // Restart - let restarted = execute_tool( - "WorkerRestart", - &json!({"worker_id": worker_id}), - ) - .expect("WorkerRestart should succeed"); + let restarted = execute_tool("WorkerRestart", &json!({"worker_id": worker_id})) + .expect("WorkerRestart should succeed"); let restart_output: serde_json::Value = serde_json::from_str(&restarted).expect("json"); assert_eq!( restart_output["status"], "spawning", @@ -5667,11 +5657,8 @@ mod tests { let created_output: serde_json::Value = serde_json::from_str(&created).expect("json"); let worker_id = created_output["worker_id"].as_str().expect("worker_id"); - let fetched = execute_tool( - "WorkerGet", - &json!({"worker_id": worker_id}), - ) - .expect("WorkerGet should succeed"); + let fetched = execute_tool("WorkerGet", &json!({"worker_id": worker_id})) + .expect("WorkerGet should succeed"); let fetched_output: serde_json::Value = serde_json::from_str(&fetched).expect("json"); assert_eq!(fetched_output["worker_id"], worker_id); assert_eq!(fetched_output["status"], "spawning"); @@ -5705,11 +5692,8 @@ mod tests { let worker_id = created_output["worker_id"].as_str().expect("worker_id"); // Worker is still in spawning — await_ready should return not-ready snapshot - let snapshot = execute_tool( - "WorkerAwaitReady", - &json!({"worker_id": worker_id}), - ) - .expect("WorkerAwaitReady should succeed even when not ready"); + let snapshot = execute_tool("WorkerAwaitReady", &json!({"worker_id": worker_id})) + .expect("WorkerAwaitReady should succeed even when not ready"); let snap_output: serde_json::Value = serde_json::from_str(&snapshot).expect("json"); assert_eq!( snap_output["ready"], false, @@ -5751,21 +5735,27 @@ mod tests { let state_path = worktree.join(".claw").join("worker-state.json"); // 1. Create worker WITHOUT trusted_roots - let created = execute_tool( - "WorkerCreate", - &json!({"cwd": cwd}), - ) - .expect("WorkerCreate should succeed"); + let created = execute_tool("WorkerCreate", &json!({"cwd": cwd})) + .expect("WorkerCreate should succeed"); let created_output: serde_json::Value = serde_json::from_str(&created).expect("json"); - let worker_id = created_output["worker_id"].as_str().expect("worker_id").to_string(); + let worker_id = created_output["worker_id"] + .as_str() + .expect("worker_id") + .to_string(); // State file should exist after create - assert!(state_path.exists(), "state file should be written after WorkerCreate"); - let state: serde_json::Value = serde_json::from_str( - &fs::read_to_string(&state_path).expect("read state") - ).expect("parse state"); + assert!( + state_path.exists(), + "state file should be written after WorkerCreate" + ); + let state: serde_json::Value = + serde_json::from_str(&fs::read_to_string(&state_path).expect("read state")) + .expect("parse state"); assert_eq!(state["status"], "spawning"); assert_eq!(state["is_ready"], false); - assert!(state["seconds_since_update"].is_number(), "seconds_since_update must be present"); + assert!( + state["seconds_since_update"].is_number(), + "seconds_since_update must be present" + ); // 2. Force trust_required via observe execute_tool( @@ -5773,26 +5763,27 @@ mod tests { &json!({"worker_id": worker_id, "screen_text": "Do you trust the files in this folder?"}), ) .expect("WorkerObserve should succeed"); - let state: serde_json::Value = serde_json::from_str( - &fs::read_to_string(&state_path).expect("read state") - ).expect("parse state"); - assert_eq!(state["status"], "trust_required", - "state file must reflect trust_required stall"); + let state: serde_json::Value = + serde_json::from_str(&fs::read_to_string(&state_path).expect("read state")) + .expect("parse state"); + assert_eq!( + state["status"], "trust_required", + "state file must reflect trust_required stall" + ); assert_eq!(state["is_ready"], false); assert_eq!(state["trust_gate_cleared"], false); assert!(state["seconds_since_update"].is_number()); // 3. WorkerResolveTrust -> state file reflects recovery - execute_tool( - "WorkerResolveTrust", - &json!({"worker_id": worker_id}), - ) - .expect("WorkerResolveTrust should succeed"); - let state: serde_json::Value = serde_json::from_str( - &fs::read_to_string(&state_path).expect("read state") - ).expect("parse state"); - assert_eq!(state["status"], "spawning", - "state file must show spawning after trust resolved"); + execute_tool("WorkerResolveTrust", &json!({"worker_id": worker_id})) + .expect("WorkerResolveTrust should succeed"); + let state: serde_json::Value = + serde_json::from_str(&fs::read_to_string(&state_path).expect("read state")) + .expect("parse state"); + assert_eq!( + state["status"], "spawning", + "state file must show spawning after trust resolved" + ); assert_eq!(state["trust_gate_cleared"], true); // 4. Observe ready screen -> state file shows ready_for_prompt @@ -5801,13 +5792,17 @@ mod tests { &json!({"worker_id": worker_id, "screen_text": "Ready for input\n>"}), ) .expect("WorkerObserve ready should succeed"); - let state: serde_json::Value = serde_json::from_str( - &fs::read_to_string(&state_path).expect("read state") - ).expect("parse state"); - assert_eq!(state["status"], "ready_for_prompt", - "state file must show ready_for_prompt after ready screen"); - assert_eq!(state["is_ready"], true, - "is_ready must be true in state file at ready_for_prompt"); + let state: serde_json::Value = + serde_json::from_str(&fs::read_to_string(&state_path).expect("read state")) + .expect("parse state"); + assert_eq!( + state["status"], "ready_for_prompt", + "state file must show ready_for_prompt after ready screen" + ); + assert_eq!( + state["is_ready"], true, + "is_ready must be true in state file at ready_for_prompt" + ); fs::remove_dir_all(&worktree).ok(); } @@ -5815,13 +5810,13 @@ mod tests { #[test] fn stall_detect_and_resolve_trust_end_to_end() { // 1. Create worker WITHOUT trusted_roots so trust won't auto-resolve - let created = execute_tool( - "WorkerCreate", - &json!({"cwd": "/no/trusted/root/here"}), - ) - .expect("WorkerCreate should succeed"); + let created = execute_tool("WorkerCreate", &json!({"cwd": "/no/trusted/root/here"})) + .expect("WorkerCreate should succeed"); let created_output: serde_json::Value = serde_json::from_str(&created).expect("json"); - let worker_id = created_output["worker_id"].as_str().expect("worker_id").to_string(); + let worker_id = created_output["worker_id"] + .as_str() + .expect("worker_id") + .to_string(); assert_eq!(created_output["trust_auto_resolve"], false); // 2. Observe trust prompt screen text -> worker stalls at trust_required @@ -5840,11 +5835,8 @@ mod tests { ); assert_eq!(stalled_output["trust_gate_cleared"], false); // 3. Clawhip calls WorkerResolveTrust to unblock - let resolved = execute_tool( - "WorkerResolveTrust", - &json!({"worker_id": worker_id}), - ) - .expect("WorkerResolveTrust should succeed"); + let resolved = execute_tool("WorkerResolveTrust", &json!({"worker_id": worker_id})) + .expect("WorkerResolveTrust should succeed"); let resolved_output: serde_json::Value = serde_json::from_str(&resolved).expect("json"); assert_eq!( resolved_output["status"], "spawning", @@ -5877,7 +5869,10 @@ mod tests { ) .expect("WorkerCreate should succeed"); let created_output: serde_json::Value = serde_json::from_str(&created).expect("json"); - let worker_id = created_output["worker_id"].as_str().expect("worker_id").to_string(); + let worker_id = created_output["worker_id"] + .as_str() + .expect("worker_id") + .to_string(); // Force trust_required let stalled = execute_tool( @@ -5892,17 +5887,15 @@ mod tests { assert_eq!(stalled_output["status"], "trust_required"); // WorkerRestart resets the worker - let restarted = execute_tool( - "WorkerRestart", - &json!({"worker_id": worker_id}), - ) - .expect("WorkerRestart should succeed"); + let restarted = execute_tool("WorkerRestart", &json!({"worker_id": worker_id})) + .expect("WorkerRestart should succeed"); let restarted_output: serde_json::Value = serde_json::from_str(&restarted).expect("json"); assert_eq!( restarted_output["status"], "spawning", "restarted worker should be back at spawning" ); - assert_eq!(restarted_output["trust_gate_cleared"], false, + assert_eq!( + restarted_output["trust_gate_cleared"], false, "restart clears trust — next observe loop must re-acquire trust" ); }