//! Trigger commands: CRUD + execute //! //! Triggers are registered in the Kernel's TriggerManager. use serde::{Deserialize, Serialize}; use serde_json; use tauri::State; use super::{validate_id, KernelState}; // ============================================================ // Trigger Commands // ============================================================ /// Trigger configuration for creation/update #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct TriggerConfigRequest { pub id: String, pub name: String, pub hand_id: String, pub trigger_type: TriggerTypeRequest, #[serde(default = "default_trigger_enabled")] pub enabled: bool, #[serde(default)] pub description: Option, #[serde(default)] pub tags: Vec, } fn default_trigger_enabled() -> bool { true } /// Trigger type for API #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "type", rename_all = "snake_case")] pub enum TriggerTypeRequest { Schedule { cron: String }, Event { pattern: String }, Webhook { path: String, secret: Option }, MessagePattern { pattern: String }, FileSystem { path: String, events: Vec }, Manual, } /// Trigger response #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct TriggerResponse { pub id: String, pub name: String, pub hand_id: String, pub trigger_type: TriggerTypeRequest, pub enabled: bool, pub created_at: String, pub modified_at: String, pub description: Option, pub tags: Vec, } impl From for TriggerResponse { fn from(entry: zclaw_kernel::trigger_manager::TriggerEntry) -> Self { let trigger_type = match entry.config.trigger_type { zclaw_hands::TriggerType::Schedule { cron } => { TriggerTypeRequest::Schedule { cron } } zclaw_hands::TriggerType::Event { pattern } => { TriggerTypeRequest::Event { pattern } } zclaw_hands::TriggerType::Webhook { path, secret } => { TriggerTypeRequest::Webhook { path, secret } } zclaw_hands::TriggerType::MessagePattern { pattern } => { TriggerTypeRequest::MessagePattern { pattern } } zclaw_hands::TriggerType::FileSystem { path, events } => { TriggerTypeRequest::FileSystem { path, events: events.iter().map(|e| format!("{:?}", e).to_lowercase()).collect(), } } zclaw_hands::TriggerType::Manual => TriggerTypeRequest::Manual, }; Self { id: entry.config.id, name: entry.config.name, hand_id: entry.config.hand_id, trigger_type, enabled: entry.config.enabled, created_at: entry.created_at.to_rfc3339(), modified_at: entry.modified_at.to_rfc3339(), description: entry.description, tags: entry.tags, } } } /// List all triggers // @reserved: trigger management // @connected #[tauri::command] pub async fn trigger_list( state: State<'_, KernelState>, ) -> Result, String> { let kernel_lock = state.lock().await; let kernel = kernel_lock.as_ref() .ok_or_else(|| "Kernel not initialized".to_string())?; let triggers = kernel.list_triggers().await; Ok(triggers.into_iter().map(TriggerResponse::from).collect()) } /// Get a specific trigger // @reserved: trigger management // @connected #[tauri::command] pub async fn trigger_get( state: State<'_, KernelState>, id: String, ) -> Result, String> { // Validate trigger ID let id = validate_id(&id, "trigger_id")?; let kernel_lock = state.lock().await; let kernel = kernel_lock.as_ref() .ok_or_else(|| "Kernel not initialized".to_string())?; Ok(kernel.get_trigger(&id).await.map(TriggerResponse::from)) } /// Create a new trigger // @reserved: trigger management // @connected #[tauri::command] pub async fn trigger_create( state: State<'_, KernelState>, request: TriggerConfigRequest, ) -> Result { let kernel_lock = state.lock().await; let kernel = kernel_lock.as_ref() .ok_or_else(|| "Kernel not initialized".to_string())?; // Convert request to config let trigger_type = match request.trigger_type { TriggerTypeRequest::Schedule { cron } => { zclaw_hands::TriggerType::Schedule { cron } } TriggerTypeRequest::Event { pattern } => { zclaw_hands::TriggerType::Event { pattern } } TriggerTypeRequest::Webhook { path, secret } => { zclaw_hands::TriggerType::Webhook { path, secret } } TriggerTypeRequest::MessagePattern { pattern } => { zclaw_hands::TriggerType::MessagePattern { pattern } } TriggerTypeRequest::FileSystem { path, events } => { zclaw_hands::TriggerType::FileSystem { path, events: events.iter().filter_map(|e| match e.as_str() { "created" => Some(zclaw_hands::FileEvent::Created), "modified" => Some(zclaw_hands::FileEvent::Modified), "deleted" => Some(zclaw_hands::FileEvent::Deleted), "any" => Some(zclaw_hands::FileEvent::Any), _ => None, }).collect(), } } TriggerTypeRequest::Manual => zclaw_hands::TriggerType::Manual, }; let config = zclaw_hands::TriggerConfig { id: request.id, name: request.name, hand_id: request.hand_id, trigger_type, enabled: request.enabled, max_executions_per_hour: 10, }; let entry = kernel.create_trigger(config).await .map_err(|e| format!("Failed to create trigger: {}", e))?; Ok(TriggerResponse::from(entry)) } /// Update a trigger // @reserved: trigger management // @connected #[tauri::command] pub async fn trigger_update( state: State<'_, KernelState>, id: String, name: Option, enabled: Option, hand_id: Option, ) -> Result { let kernel_lock = state.lock().await; let kernel = kernel_lock.as_ref() .ok_or_else(|| "Kernel not initialized".to_string())?; let update = zclaw_kernel::trigger_manager::TriggerUpdateRequest { name, enabled, hand_id, trigger_type: None, }; let entry = kernel.update_trigger(&id, update).await .map_err(|e| format!("Failed to update trigger: {}", e))?; Ok(TriggerResponse::from(entry)) } /// Delete a trigger // @connected #[tauri::command] pub async fn trigger_delete( state: State<'_, KernelState>, id: String, ) -> Result<(), String> { // Validate trigger ID let id = validate_id(&id, "trigger_id")?; let kernel_lock = state.lock().await; let kernel = kernel_lock.as_ref() .ok_or_else(|| "Kernel not initialized".to_string())?; kernel.delete_trigger(&id).await .map_err(|e| format!("Failed to delete trigger: {}", e)) } /// Execute a trigger manually // @reserved: trigger management // @connected #[tauri::command] pub async fn trigger_execute( state: State<'_, KernelState>, id: String, input: serde_json::Value, ) -> Result { // Validate trigger ID let id = validate_id(&id, "trigger_id")?; let kernel_lock = state.lock().await; let kernel = kernel_lock.as_ref() .ok_or_else(|| "Kernel not initialized".to_string())?; let result = kernel.execute_trigger(&id, input).await .map_err(|e| format!("Failed to execute trigger: {}", e))?; Ok(serde_json::to_value(result).unwrap_or(serde_json::json!({}))) }