diff --git a/assets/sigrid-photo.png b/assets/sigrid-photo.png new file mode 100644 index 0000000..a70aa84 Binary files /dev/null and b/assets/sigrid-photo.png differ diff --git a/rust/.claw/sessions/session-1775386832313-0.jsonl b/rust/.claw/sessions/session-1775386832313-0.jsonl new file mode 100644 index 0000000..81f7a9d --- /dev/null +++ b/rust/.claw/sessions/session-1775386832313-0.jsonl @@ -0,0 +1,2 @@ +{"created_at_ms":1775386832313,"session_id":"session-1775386832313-0","type":"session_meta","updated_at_ms":1775386832313,"version":1} +{"message":{"blocks":[{"text":"status --help","type":"text"}],"role":"user"},"type":"message"} diff --git a/rust/.claw/sessions/session-1775386842352-0.jsonl b/rust/.claw/sessions/session-1775386842352-0.jsonl new file mode 100644 index 0000000..4a678ac --- /dev/null +++ b/rust/.claw/sessions/session-1775386842352-0.jsonl @@ -0,0 +1,2 @@ +{"created_at_ms":1775386842352,"session_id":"session-1775386842352-0","type":"session_meta","updated_at_ms":1775386842352,"version":1} +{"message":{"blocks":[{"text":"doctor --help","type":"text"}],"role":"user"},"type":"message"} diff --git a/rust/.claw/sessions/session-1775386852257-0.jsonl b/rust/.claw/sessions/session-1775386852257-0.jsonl new file mode 100644 index 0000000..fa8cb03 --- /dev/null +++ b/rust/.claw/sessions/session-1775386852257-0.jsonl @@ -0,0 +1,2 @@ +{"created_at_ms":1775386852257,"session_id":"session-1775386852257-0","type":"session_meta","updated_at_ms":1775386852257,"version":1} +{"message":{"blocks":[{"text":"doctor --help","type":"text"}],"role":"user"},"type":"message"} diff --git a/rust/.claw/sessions/session-1775386853666-0.jsonl b/rust/.claw/sessions/session-1775386853666-0.jsonl new file mode 100644 index 0000000..d2bd303 --- /dev/null +++ b/rust/.claw/sessions/session-1775386853666-0.jsonl @@ -0,0 +1,2 @@ +{"created_at_ms":1775386853666,"session_id":"session-1775386853666-0","type":"session_meta","updated_at_ms":1775386853666,"version":1} +{"message":{"blocks":[{"text":"status --help","type":"text"}],"role":"user"},"type":"message"} diff --git a/rust/crates/runtime/src/lane_events.rs b/rust/crates/runtime/src/lane_events.rs index c96f829..9ee88a4 100644 --- a/rust/crates/runtime/src/lane_events.rs +++ b/rust/crates/runtime/src/lane_events.rs @@ -114,8 +114,12 @@ impl LaneEvent { #[must_use] pub fn finished(emitted_at: impl Into, detail: Option) -> Self { - Self::new(LaneEventName::Finished, LaneEventStatus::Completed, emitted_at) - .with_optional_detail(detail) + Self::new( + LaneEventName::Finished, + LaneEventStatus::Completed, + emitted_at, + ) + .with_optional_detail(detail) } #[must_use] @@ -161,19 +165,14 @@ impl LaneEvent { mod tests { use serde_json::json; - use super::{ - LaneEvent, LaneEventBlocker, LaneEventName, LaneEventStatus, LaneFailureClass, - }; + use super::{LaneEvent, LaneEventBlocker, LaneEventName, LaneEventStatus, LaneFailureClass}; #[test] fn canonical_lane_event_names_serialize_to_expected_wire_values() { let cases = [ (LaneEventName::Started, "lane.started"), (LaneEventName::Ready, "lane.ready"), - ( - LaneEventName::PromptMisdelivery, - "lane.prompt_misdelivery", - ), + (LaneEventName::PromptMisdelivery, "lane.prompt_misdelivery"), (LaneEventName::Blocked, "lane.blocked"), (LaneEventName::Red, "lane.red"), (LaneEventName::Green, "lane.green"), @@ -193,7 +192,10 @@ mod tests { ]; for (event, expected) in cases { - assert_eq!(serde_json::to_value(event).expect("serialize event"), json!(expected)); + assert_eq!( + serde_json::to_value(event).expect("serialize event"), + json!(expected) + ); } } diff --git a/rust/crates/runtime/src/mcp_lifecycle_hardened.rs b/rust/crates/runtime/src/mcp_lifecycle_hardened.rs index 969eff4..01000f8 100644 --- a/rust/crates/runtime/src/mcp_lifecycle_hardened.rs +++ b/rust/crates/runtime/src/mcp_lifecycle_hardened.rs @@ -599,7 +599,10 @@ mod tests { )); match result { - McpPhaseResult::Failure { phase: failed_phase, error } => { + McpPhaseResult::Failure { + phase: failed_phase, + error, + } => { assert_eq!(failed_phase, phase); assert_eq!(error.phase, phase); assert_eq!( diff --git a/rust/crates/runtime/src/mcp_stdio.rs b/rust/crates/runtime/src/mcp_stdio.rs index 4ebdf62..be732e1 100644 --- a/rust/crates/runtime/src/mcp_stdio.rs +++ b/rust/crates/runtime/src/mcp_stdio.rs @@ -360,8 +360,10 @@ impl McpServerManagerError { } fn recoverable(&self) -> bool { - !matches!(self.lifecycle_phase(), McpLifecyclePhase::InitializeHandshake) - && matches!(self, Self::Transport { .. } | Self::Timeout { .. }) + !matches!( + self.lifecycle_phase(), + McpLifecyclePhase::InitializeHandshake + ) && matches!(self, Self::Transport { .. } | Self::Timeout { .. }) } fn discovery_failure(&self, server_name: &str) -> McpDiscoveryFailure { @@ -417,10 +419,9 @@ impl McpServerManagerError { ("method".to_string(), (*method).to_string()), ("timeout_ms".to_string(), timeout_ms.to_string()), ]), - Self::UnknownTool { qualified_name } => BTreeMap::from([( - "qualified_tool".to_string(), - qualified_name.clone(), - )]), + Self::UnknownTool { qualified_name } => { + BTreeMap::from([("qualified_tool".to_string(), qualified_name.clone())]) + } Self::UnknownServer { server_name } => { BTreeMap::from([("server".to_string(), server_name.clone())]) } @@ -1425,11 +1426,10 @@ mod tests { use crate::mcp_client::McpClientBootstrap; use super::{ - spawn_mcp_stdio_process, JsonRpcId, JsonRpcRequest, JsonRpcResponse, - McpInitializeClientInfo, McpInitializeParams, McpInitializeResult, McpInitializeServerInfo, - McpListToolsResult, McpReadResourceParams, McpReadResourceResult, McpServerManager, - McpServerManagerError, McpStdioProcess, McpTool, McpToolCallParams, - unsupported_server_failed_server, + spawn_mcp_stdio_process, unsupported_server_failed_server, JsonRpcId, JsonRpcRequest, + JsonRpcResponse, McpInitializeClientInfo, McpInitializeParams, McpInitializeResult, + McpInitializeServerInfo, McpListToolsResult, McpReadResourceParams, McpReadResourceResult, + McpServerManager, McpServerManagerError, McpStdioProcess, McpTool, McpToolCallParams, }; use crate::McpLifecyclePhase; @@ -2698,7 +2698,10 @@ mod tests { ); assert!(!report.failed_servers[0].recoverable); assert_eq!( - report.failed_servers[0].context.get("method").map(String::as_str), + report.failed_servers[0] + .context + .get("method") + .map(String::as_str), Some("initialize") ); assert!(report.failed_servers[0].error.contains("initialize")); diff --git a/rust/crates/runtime/src/session_control.rs b/rust/crates/runtime/src/session_control.rs index 1d0c419..a39bc21 100644 --- a/rust/crates/runtime/src/session_control.rs +++ b/rust/crates/runtime/src/session_control.rs @@ -4,7 +4,6 @@ use std::fs; use std::path::{Path, PathBuf}; use std::time::UNIX_EPOCH; - use crate::session::{Session, SessionError}; pub const PRIMARY_SESSION_EXTENSION: &str = "jsonl"; diff --git a/rust/crates/runtime/src/task_packet.rs b/rust/crates/runtime/src/task_packet.rs index 2170f22..86d1c6c 100644 --- a/rust/crates/runtime/src/task_packet.rs +++ b/rust/crates/runtime/src/task_packet.rs @@ -66,11 +66,7 @@ pub fn validate_packet(packet: TaskPacket) -> Result) -> Task { - self.create_task( - prompt.to_owned(), - description.map(str::to_owned), - None, - ) + self.create_task(prompt.to_owned(), description.map(str::to_owned), None) } pub fn create_from_packet( diff --git a/rust/crates/runtime/src/worker_boot.rs b/rust/crates/runtime/src/worker_boot.rs index 4854e2a..038e021 100644 --- a/rust/crates/runtime/src/worker_boot.rs +++ b/rust/crates/runtime/src/worker_boot.rs @@ -257,7 +257,9 @@ impl WorkerRegistry { let prompt_preview = prompt_preview(worker.last_prompt.as_deref().unwrap_or_default()); let message = match observation.target { WorkerPromptTarget::Shell => { - format!("worker prompt landed in shell instead of coding agent: {prompt_preview}") + format!( + "worker prompt landed in shell instead of coding agent: {prompt_preview}" + ) } WorkerPromptTarget::WrongTarget => format!( "worker prompt landed in the wrong target instead of {}: {}", @@ -312,7 +314,9 @@ impl WorkerRegistry { worker.last_error = None; } - if detect_ready_for_prompt(screen_text, &lowered) && worker.status != WorkerStatus::ReadyForPrompt { + if detect_ready_for_prompt(screen_text, &lowered) + && worker.status != WorkerStatus::ReadyForPrompt + { worker.status = WorkerStatus::ReadyForPrompt; worker.prompt_in_flight = false; if matches!( @@ -412,7 +416,10 @@ impl WorkerRegistry { worker_id: worker.worker_id.clone(), status: worker.status, ready: worker.status == WorkerStatus::ReadyForPrompt, - blocked: matches!(worker.status, WorkerStatus::TrustRequired | WorkerStatus::Failed), + blocked: matches!( + worker.status, + WorkerStatus::TrustRequired | WorkerStatus::Failed + ), replay_prompt_ready: worker.replay_prompt.is_some(), last_error: worker.last_error.clone(), }) diff --git a/rust/crates/tools/src/lane_completion.rs b/rust/crates/tools/src/lane_completion.rs index 6fe1d8f..c44c508 100644 --- a/rust/crates/tools/src/lane_completion.rs +++ b/rust/crates/tools/src/lane_completion.rs @@ -16,7 +16,7 @@ use runtime::{ use crate::AgentOutput; /// Detects if a lane should be automatically marked as completed. -/// +/// /// Returns `Some(LaneContext)` with `completed = true` if all conditions met, /// `None` if lane should remain active. #[allow(dead_code)] @@ -29,29 +29,29 @@ pub(crate) fn detect_lane_completion( if output.error.is_some() { return None; } - + // Must have finished status if !output.status.eq_ignore_ascii_case("completed") && !output.status.eq_ignore_ascii_case("finished") { return None; } - + // Must have no current blocker if output.current_blocker.is_some() { return None; } - + // Must have green tests if !test_green { return None; } - + // Must have pushed code if !has_pushed { return None; } - + // All conditions met — create completed context Some(LaneContext { lane_id: output.agent_id.clone(), @@ -67,9 +67,7 @@ pub(crate) fn detect_lane_completion( /// Evaluates policy actions for a completed lane. #[allow(dead_code)] -pub(crate) fn evaluate_completed_lane( - context: &LaneContext, -) -> Vec { +pub(crate) fn evaluate_completed_lane(context: &LaneContext) -> Vec { let engine = PolicyEngine::new(vec![ PolicyRule::new( "closeout-completed-lane", @@ -87,7 +85,7 @@ pub(crate) fn evaluate_completed_lane( 5, ), ]); - + evaluate(&engine, context) } @@ -114,53 +112,53 @@ mod tests { error: None, } } - + #[test] fn detects_completion_when_all_conditions_met() { let output = test_output(); let result = detect_lane_completion(&output, true, true); - + assert!(result.is_some()); let context = result.unwrap(); assert!(context.completed); assert_eq!(context.green_level, 3); assert_eq!(context.blocker, LaneBlocker::None); } - + #[test] fn no_completion_when_error_present() { let mut output = test_output(); output.error = Some("Build failed".to_string()); - + let result = detect_lane_completion(&output, true, true); assert!(result.is_none()); } - + #[test] fn no_completion_when_not_finished() { let mut output = test_output(); output.status = "Running".to_string(); - + let result = detect_lane_completion(&output, true, true); assert!(result.is_none()); } - + #[test] fn no_completion_when_tests_not_green() { let output = test_output(); - + let result = detect_lane_completion(&output, false, true); assert!(result.is_none()); } - + #[test] fn no_completion_when_not_pushed() { let output = test_output(); - + let result = detect_lane_completion(&output, true, false); assert!(result.is_none()); } - + #[test] fn evaluate_triggers_closeout_for_completed_lane() { let context = LaneContext { @@ -173,9 +171,9 @@ mod tests { completed: true, reconciled: false, }; - + let actions = evaluate_completed_lane(&context); - + assert!(actions.contains(&PolicyAction::CloseoutLane)); assert!(actions.contains(&PolicyAction::CleanupSession)); } diff --git a/rust/crates/tools/src/lib.rs b/rust/crates/tools/src/lib.rs index e686899..b19938a 100644 --- a/rust/crates/tools/src/lib.rs +++ b/rust/crates/tools/src/lib.rs @@ -17,7 +17,6 @@ use runtime::{ permission_enforcer::{EnforcementResult, PermissionEnforcer}, read_file, summary_compression::compress_summary_text, - TaskPacket, task_registry::TaskRegistry, team_cron_registry::{CronRegistry, TeamRegistry}, worker_boot::{WorkerReadySnapshot, WorkerRegistry}, @@ -25,7 +24,7 @@ use runtime::{ BranchFreshness, ContentBlock, ConversationMessage, ConversationRuntime, GrepSearchInput, LaneEvent, LaneEventBlocker, LaneEventName, LaneEventStatus, LaneFailureClass, McpDegradedReport, MessageRole, PermissionMode, PermissionPolicy, PromptCacheEvent, - RuntimeError, Session, ToolError, ToolExecutor, + RuntimeError, Session, TaskPacket, ToolError, ToolExecutor, }; use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; @@ -1878,27 +1877,25 @@ fn branch_divergence_output( dangerously_disable_sandbox: None, return_code_interpretation: Some("preflight_blocked:branch_divergence".to_string()), no_output_expected: Some(false), - structured_content: Some(vec![ - serde_json::to_value( - LaneEvent::new( - LaneEventName::BranchStaleAgainstMain, - LaneEventStatus::Blocked, - iso8601_now(), - ) - .with_failure_class(LaneFailureClass::BranchDivergence) - .with_detail(stderr.clone()) - .with_data(json!({ - "branch": branch, - "mainRef": main_ref, - "commitsBehind": commits_behind, - "commitsAhead": commits_ahead, - "missingCommits": missing_fixes, - "blockedCommand": command, - "recommendedAction": format!("merge or rebase {main_ref} before workspace tests") - })), + structured_content: Some(vec![serde_json::to_value( + LaneEvent::new( + LaneEventName::BranchStaleAgainstMain, + LaneEventStatus::Blocked, + iso8601_now(), ) - .expect("lane event should serialize"), - ]), + .with_failure_class(LaneFailureClass::BranchDivergence) + .with_detail(stderr.clone()) + .with_data(json!({ + "branch": branch, + "mainRef": main_ref, + "commitsBehind": commits_behind, + "commitsAhead": commits_ahead, + "missingCommits": missing_fixes, + "blockedCommand": command, + "recommendedAction": format!("merge or rebase {main_ref} before workspace tests") + })), + ) + .expect("lane event should serialize")]), persisted_output_path: None, persisted_output_size: None, sandbox_status: None, @@ -3297,12 +3294,12 @@ fn persist_agent_terminal_state( next_manifest.current_blocker = blocker.clone(); next_manifest.error = error; if let Some(blocker) = blocker { - next_manifest.lane_events.push( - LaneEvent::blocked(iso8601_now(), &blocker), - ); - next_manifest.lane_events.push( - LaneEvent::failed(iso8601_now(), &blocker), - ); + next_manifest + .lane_events + .push(LaneEvent::blocked(iso8601_now(), &blocker)); + next_manifest + .lane_events + .push(LaneEvent::failed(iso8601_now(), &blocker)); } else { next_manifest.current_blocker = None; let compressed_detail = result @@ -4952,8 +4949,8 @@ mod tests { agent_permission_policy, allowed_tools_for_subagent, classify_lane_failure, execute_agent_with_spawn, execute_tool, final_assistant_text, mvp_tool_specs, permission_mode_from_plugin, persist_agent_terminal_state, push_output_block, - run_task_packet, AgentInput, AgentJob, GlobalToolRegistry, LaneEventName, - LaneFailureClass, SubagentToolExecutor, + run_task_packet, AgentInput, AgentJob, GlobalToolRegistry, LaneEventName, LaneFailureClass, + SubagentToolExecutor, }; use api::OutputContentBlock; use runtime::{ @@ -5977,7 +5974,10 @@ mod tests { "gateway routing rejected the request", LaneFailureClass::GatewayRouting, ), - ("tool failed: denied tool execution from hook", LaneFailureClass::ToolRuntime), + ( + "tool failed: denied tool execution from hook", + LaneFailureClass::ToolRuntime, + ), ("thread creation failed", LaneFailureClass::Infra), ]; @@ -6000,11 +6000,17 @@ mod tests { (LaneEventName::MergeReady, "lane.merge.ready"), (LaneEventName::Finished, "lane.finished"), (LaneEventName::Failed, "lane.failed"), - (LaneEventName::BranchStaleAgainstMain, "branch.stale_against_main"), + ( + LaneEventName::BranchStaleAgainstMain, + "branch.stale_against_main", + ), ]; for (event, expected) in cases { - assert_eq!(serde_json::to_value(event).expect("serialize lane event"), json!(expected)); + assert_eq!( + serde_json::to_value(event).expect("serialize lane event"), + json!(expected) + ); } }