//! Hand definition and types use async_trait::async_trait; use serde::{Deserialize, Serialize}; use serde_json::Value; use zclaw_types::{Result, AgentId}; /// Hand configuration #[derive(Debug, Clone, Serialize, Deserialize)] pub struct HandConfig { /// Unique hand identifier pub id: String, /// Human-readable name pub name: String, /// Hand description pub description: String, /// Whether this hand needs approval before execution #[serde(default)] pub needs_approval: bool, /// Required dependencies #[serde(default)] pub dependencies: Vec, /// Input schema #[serde(default)] pub input_schema: Option, /// Tags for categorization #[serde(default)] pub tags: Vec, /// Whether the hand is enabled #[serde(default = "default_enabled")] pub enabled: bool, /// Maximum concurrent executions for this hand (0 = unlimited) #[serde(default)] pub max_concurrent: u32, /// Timeout in seconds for each execution (0 = use HandContext default) #[serde(default)] pub timeout_secs: u64, } impl Default for HandConfig { fn default() -> Self { Self { id: String::new(), name: String::new(), description: String::new(), needs_approval: false, dependencies: Vec::new(), input_schema: None, tags: Vec::new(), enabled: true, max_concurrent: 0, timeout_secs: 0, } } } fn default_enabled() -> bool { true } /// Hand execution context #[derive(Debug, Clone)] pub struct HandContext { /// Agent ID executing the hand pub agent_id: AgentId, /// Working directory pub working_dir: Option, /// Environment variables pub env: std::collections::HashMap, /// Timeout in seconds pub timeout_secs: u64, /// Callback URL for async results pub callback_url: Option, } impl Default for HandContext { fn default() -> Self { Self { agent_id: AgentId::new(), working_dir: None, env: std::collections::HashMap::new(), timeout_secs: 300, callback_url: None, } } } /// Hand execution result #[derive(Debug, Clone, Serialize, Deserialize)] pub struct HandResult { /// Whether execution succeeded pub success: bool, /// Output data pub output: Value, /// Error message if failed #[serde(default)] pub error: Option, /// Execution duration in milliseconds #[serde(default)] pub duration_ms: Option, /// Status message #[serde(default)] pub status: String, } impl HandResult { pub fn success(output: Value) -> Self { Self { success: true, output, error: None, duration_ms: None, status: "completed".to_string(), } } pub fn error(message: impl Into) -> Self { Self { success: false, output: Value::Null, error: Some(message.into()), duration_ms: None, status: "failed".to_string(), } } pub fn pending(status: impl Into) -> Self { Self { success: true, output: Value::Null, error: None, duration_ms: None, status: status.into(), } } } /// Hand execution status #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum HandStatus { Idle, Running, PendingApproval, Completed, Failed, } /// Hand trait - autonomous capability #[async_trait] pub trait Hand: Send + Sync { /// Get the hand configuration fn config(&self) -> &HandConfig; /// Execute the hand async fn execute(&self, context: &HandContext, input: Value) -> Result; /// Check if the hand needs approval fn needs_approval(&self) -> bool { self.config().needs_approval } /// Check dependencies fn check_dependencies(&self) -> Result> { let missing: Vec = self.config().dependencies.iter() .filter(|dep| !self.is_dependency_available(dep)) .cloned() .collect(); Ok(missing) } /// Check if a specific dependency is available fn is_dependency_available(&self, _dep: &str) -> bool { true // Default implementation } /// Get current status fn status(&self) -> HandStatus { HandStatus::Idle } /// P2-03: Get the number of tools this hand exposes (default: 0) fn tool_count(&self) -> u32 { 0 } /// P2-03: Get the number of metrics this hand tracks (default: 0) fn metric_count(&self) -> u32 { 0 } }