//! Approval management use std::sync::Arc; use serde_json::Value; use zclaw_types::{Result, HandRun, HandRunId, HandRunStatus, TriggerSource}; use zclaw_hands::HandContext; use super::Kernel; impl Kernel { // ============================================================ // Approval Management // ============================================================ /// List pending approvals pub async fn list_approvals(&self) -> Vec { let approvals = self.pending_approvals.lock().await; approvals.iter().filter(|a| a.status == "pending").cloned().collect() } /// Get a single approval by ID (any status, not just pending) /// /// Returns None if no approval with the given ID exists. pub async fn get_approval(&self, id: &str) -> Option { let approvals = self.pending_approvals.lock().await; approvals.iter().find(|a| a.id == id).cloned() } /// Create a pending approval (called when a needs_approval hand is triggered) pub async fn create_approval(&self, hand_id: String, input: serde_json::Value) -> super::ApprovalEntry { let entry = super::ApprovalEntry { id: uuid::Uuid::new_v4().to_string(), hand_id, status: "pending".to_string(), created_at: chrono::Utc::now(), input, reject_reason: None, }; let mut approvals = self.pending_approvals.lock().await; approvals.push(entry.clone()); entry } /// Respond to an approval pub async fn respond_to_approval( &self, id: &str, approved: bool, reason: Option, ) -> Result<()> { let mut approvals = self.pending_approvals.lock().await; let entry = approvals.iter_mut().find(|a| a.id == id && a.status == "pending") .ok_or_else(|| zclaw_types::ZclawError::NotFound(format!("Approval not found: {}", id)))?; entry.status = if approved { "approved".to_string() } else { "rejected".to_string() }; if let Some(r) = reason { entry.reject_reason = Some(r); } if approved { let hand_id = entry.hand_id.clone(); let input = entry.input.clone(); drop(approvals); // Release lock before async hand execution // Execute the hand in background with HandRun tracking let hands = self.hands.clone(); let approvals = self.pending_approvals.clone(); let memory = self.memory.clone(); let running_hand_runs = self.running_hand_runs.clone(); let id_owned = id.to_string(); tokio::spawn(async move { // Create HandRun record for tracking let run_id = HandRunId::new(); let now = chrono::Utc::now().to_rfc3339(); let mut run = HandRun { id: run_id, hand_name: hand_id.clone(), trigger_source: TriggerSource::Manual, params: input.clone(), status: HandRunStatus::Pending, result: None, error: None, duration_ms: None, created_at: now.clone(), started_at: None, completed_at: None, }; let _ = memory.save_hand_run(&run).await.map_err(|e| { tracing::error!("[Approval] Failed to save hand run: {}", e); }); run.status = HandRunStatus::Running; run.started_at = Some(chrono::Utc::now().to_rfc3339()); let _ = memory.update_hand_run(&run).await.map_err(|e| { tracing::error!("[Approval] Failed to update hand run (running): {}", e); }); // Register cancellation flag let cancel_flag = Arc::new(std::sync::atomic::AtomicBool::new(false)); running_hand_runs.insert(run.id, cancel_flag.clone()); let context = HandContext::default(); let start = std::time::Instant::now(); let result = hands.execute(&hand_id, &context, input).await; let duration = start.elapsed(); // Remove from running map running_hand_runs.remove(&run.id); // Update HandRun with result let completed_at = chrono::Utc::now().to_rfc3339(); match &result { Ok(res) => { run.status = HandRunStatus::Completed; run.result = Some(res.output.clone()); run.error = res.error.clone(); } Err(e) => { run.status = HandRunStatus::Failed; run.error = Some(e.to_string()); } } run.duration_ms = Some(duration.as_millis() as u64); run.completed_at = Some(completed_at); let _ = memory.update_hand_run(&run).await.map_err(|e| { tracing::error!("[Approval] Failed to update hand run (completed): {}", e); }); // Update approval status based on execution result let mut approvals = approvals.lock().await; if let Some(entry) = approvals.iter_mut().find(|a| a.id == id_owned) { match result { Ok(_) => entry.status = "completed".to_string(), Err(e) => { entry.status = "failed".to_string(); if let Some(obj) = entry.input.as_object_mut() { obj.insert("error".to_string(), Value::String(format!("{}", e))); } } } } }); } Ok(()) } /// Cancel a pending approval pub async fn cancel_approval(&self, id: &str) -> Result<()> { let mut approvals = self.pending_approvals.lock().await; let entry = approvals.iter_mut().find(|a| a.id == id && a.status == "pending") .ok_or_else(|| zclaw_types::ZclawError::NotFound(format!("Approval not found: {}", id)))?; entry.status = "cancelled".to_string(); Ok(()) } }