diff --git a/crates/protocol/src/fleet.rs b/crates/protocol/src/fleet.rs index 11ef671e..5357149a 100644 --- a/crates/protocol/src/fleet.rs +++ b/crates/protocol/src/fleet.rs @@ -317,11 +317,12 @@ pub enum FleetHostSpec { /// The trust level determines what a worker is allowed to do and what /// secrets it may access. The default for new workers is [`FleetTrustLevel::Sandbox`]; /// operators must explicitly raise trust for SSH or container workers. -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Default)] #[serde(rename_all = "snake_case")] pub enum FleetTrustLevel { /// Fully isolated: no network, no secrets, no writes outside `.codewhale/fleet/`. /// Suitable for untrusted code review, community PR checks, or third-party tool runs. + #[default] Sandbox = 0, /// Local-only worker with access to the workspace and configured secrets. /// Default for local workers. May read repo files but writes are gated. @@ -337,12 +338,6 @@ pub enum FleetTrustLevel { Operator = 3, } -impl Default for FleetTrustLevel { - fn default() -> Self { - Self::Sandbox - } -} - impl FleetTrustLevel { /// Whether this trust level is allowed to access provider secrets. #[must_use] diff --git a/crates/tui/src/fleet/manager.rs b/crates/tui/src/fleet/manager.rs index 26b8c9a8..2c628b1d 100644 --- a/crates/tui/src/fleet/manager.rs +++ b/crates/tui/src/fleet/manager.rs @@ -652,39 +652,39 @@ impl FleetManager { // Register with the sub-agent manager for headless worker tracking. // The engine's agent_open path handles actual sub-agent spawning. - if let Some(ref mgr) = self.sub_agent_manager { - if let Ok(guard) = mgr.try_write() { - let run = self - .ledger - .rebuild_state() - .ok() - .and_then(|state| state.runs.get(&entry.run_id.0).cloned()); - let worker_spec = run - .as_ref() - .and_then(|r| r.worker_specs.iter().find(|w| w.id == worker_id).cloned()) - .unwrap_or_else(|| FleetWorkerSpec { - id: worker_id.to_string(), - name: worker_id.to_string(), - host: FleetHostSpec::Local, - trust_level: Some(FleetTrustLevel::Local), - labels: BTreeMap::new(), - capabilities: vec![], - max_concurrent_tasks: Some(1), - }); - let worker = worker_runtime::fleet_task_to_worker_spec( - worker_id, - &entry.run_id.0, - task_spec, - &worker_spec, - "auto", - &self.workspace, - ); - let worker = worker_runtime::apply_exec_hardening(worker, &self.exec_config); - // drop guard after registering so we don't hold the write lock - drop(guard); - if let Ok(mut guard) = mgr.try_write() { - guard.register_worker(worker); - } + if let Some(ref mgr) = self.sub_agent_manager + && let Ok(guard) = mgr.try_write() + { + let run = self + .ledger + .rebuild_state() + .ok() + .and_then(|state| state.runs.get(&entry.run_id.0).cloned()); + let worker_spec = run + .as_ref() + .and_then(|r| r.worker_specs.iter().find(|w| w.id == worker_id).cloned()) + .unwrap_or_else(|| FleetWorkerSpec { + id: worker_id.to_string(), + name: worker_id.to_string(), + host: FleetHostSpec::Local, + trust_level: Some(FleetTrustLevel::Local), + labels: BTreeMap::new(), + capabilities: vec![], + max_concurrent_tasks: Some(1), + }); + let worker = worker_runtime::fleet_task_to_worker_spec( + worker_id, + &entry.run_id.0, + task_spec, + &worker_spec, + "auto", + &self.workspace, + ); + let worker = worker_runtime::apply_exec_hardening(worker, &self.exec_config); + // drop guard after registering so we don't hold the write lock + drop(guard); + if let Ok(mut guard) = mgr.try_write() { + guard.register_worker(worker); } } diff --git a/crates/tui/src/fleet/scheduler.rs b/crates/tui/src/fleet/scheduler.rs index b62a33a8..764f1d5b 100644 --- a/crates/tui/src/fleet/scheduler.rs +++ b/crates/tui/src/fleet/scheduler.rs @@ -178,10 +178,7 @@ impl FleetScheduler { worker_id: &str, report: &mut FleetSchedulerReport, ) -> Result<()> { - let retry_policy = task_spec - .retry_policy - .clone() - .unwrap_or_else(FleetRetryPolicy::default); + let retry_policy = task_spec.retry_policy.clone().unwrap_or_default(); if task.entry.attempts < retry_policy.max_attempts { let lease_expires_at = self.lease_expires_at(); self.ledger.lease_task( @@ -342,15 +339,14 @@ impl FleetScheduler { } fn task_is_stale(&self, task: &FleetTaskState, state: &FleetLedgerState) -> bool { - if let Some(worker_id) = task.leased_to.as_deref() { - if let Some(heartbeat) = state.heartbeats.get(worker_id) - && let Ok(last) = DateTime::parse_from_rfc3339(&heartbeat.timestamp) - { - let age = self.now.signed_duration_since(last.with_timezone(&Utc)); - return age - .to_std() - .map_or(true, |age| age > self.policy.heartbeat_timeout); - } + if let Some(worker_id) = task.leased_to.as_deref() + && let Some(heartbeat) = state.heartbeats.get(worker_id) + && let Ok(last) = DateTime::parse_from_rfc3339(&heartbeat.timestamp) + { + let age = self.now.signed_duration_since(last.with_timezone(&Utc)); + return age + .to_std() + .map_or(true, |age| age > self.policy.heartbeat_timeout); } if let Some(deadline) = task.entry.lease_deadline.as_deref() && let Ok(deadline) = DateTime::parse_from_rfc3339(deadline) diff --git a/crates/tui/src/fleet/task_spec.rs b/crates/tui/src/fleet/task_spec.rs index f0425d7f..dbc56a65 100644 --- a/crates/tui/src/fleet/task_spec.rs +++ b/crates/tui/src/fleet/task_spec.rs @@ -37,7 +37,7 @@ pub struct FleetTaskSpecDocument { enum FleetTaskSpecFile { Document(FleetTaskSpecDocument), Tasks(Vec), - Single(FleetTaskSpec), + Single(Box), } impl FleetTaskSpecFile { @@ -61,7 +61,7 @@ impl FleetTaskSpecFile { labels: BTreeMap::new(), security_policy: None, workers: Vec::new(), - tasks: vec![task], + tasks: vec![*task], }, } } @@ -133,6 +133,7 @@ pub fn validate_task_spec_document(doc: &FleetTaskSpecDocument) -> Result<()> { Ok(()) } +#[allow(clippy::too_many_arguments)] pub fn write_fleet_artifact_ref( workspace: &Path, run_id: &FleetRunId,