orchflow_core/manager/
plugins.rs

1use super::{Event, Manager};
2use async_trait::async_trait;
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5use std::sync::Arc;
6
7#[async_trait]
8pub trait Plugin: Send + Sync {
9    /// Get the plugin's unique ID
10    fn id(&self) -> &str;
11
12    /// Get the plugin's metadata
13    fn metadata(&self) -> PluginMetadata;
14
15    /// Initialize the plugin
16    async fn init(&mut self, context: PluginContext) -> Result<(), String>;
17
18    /// Handle an event
19    async fn handle_event(&mut self, event: &Event) -> Result<(), String>;
20
21    /// Handle a custom JSON-RPC request
22    async fn handle_request(&mut self, method: &str, _params: Value) -> Result<Value, String> {
23        Err(format!("Unknown method: {method}"))
24    }
25
26    /// Shutdown the plugin
27    async fn shutdown(&mut self) -> Result<(), String>;
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct PluginMetadata {
32    pub name: String,
33    pub version: String,
34    pub author: String,
35    pub description: String,
36    pub capabilities: Vec<String>,
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct PluginInfo {
41    pub id: String,
42    pub name: String,
43    pub version: String,
44    pub author: String,
45    pub description: String,
46    pub capabilities: Vec<String>,
47    pub loaded: bool,
48}
49
50#[derive(Clone)]
51pub struct PluginContext {
52    pub manager: Arc<Manager>,
53    pub plugin_id: String,
54}
55
56impl PluginContext {
57    /// Execute an action
58    pub async fn execute(&self, action: super::Action) -> Result<Value, String> {
59        self.manager
60            .execute_action(action)
61            .await
62            .map_err(|e| e.to_string())
63    }
64
65    /// Subscribe to specific event types
66    pub async fn subscribe(&self, events: Vec<String>) -> Result<(), String> {
67        self.manager
68            .subscribe_plugin(&self.plugin_id, events)
69            .await
70            .map_err(|e| e.to_string())
71    }
72}
73
74/// Dispatch an event to all plugins
75pub(super) async fn dispatch_event_to_plugins(manager: &Manager, event: &Event) {
76    let plugins = manager.plugins.read().await;
77    let subscriptions = manager.plugin_subscriptions.read().await;
78
79    // Get event type as string for subscription matching
80    let event_type = match event {
81        Event::OrchestratorStarted => "orchestrator_started",
82        Event::OrchestratorStopping => "orchestrator_stopping",
83        Event::SessionCreated { .. } => "session_created",
84        Event::SessionUpdated { .. } => "session_updated",
85        Event::SessionDeleted { .. } => "session_deleted",
86        Event::PaneCreated { .. } => "pane_created",
87        Event::PaneOutput { .. } => "pane_output",
88        Event::PaneClosed { .. } => "pane_closed",
89        Event::PaneDestroyed { .. } => "pane_destroyed",
90        Event::PaneFocused { .. } => "pane_focused",
91        Event::PaneResized { .. } => "pane_resized",
92        Event::FileOpened { .. } => "file_opened",
93        Event::FileSaved { .. } => "file_saved",
94        Event::FileChanged { .. } => "file_changed",
95        Event::FileWatchStarted { .. } => "file_watch_started",
96        Event::FileWatchStopped { .. } => "file_watch_stopped",
97        Event::FileWatchEvent { .. } => "file_watch_event",
98        Event::CommandExecuted { .. } => "command_executed",
99        Event::CommandCompleted { .. } => "command_completed",
100        Event::PluginLoaded { .. } => "plugin_loaded",
101        Event::PluginUnloaded { .. } => "plugin_unloaded",
102        Event::PluginError { .. } => "plugin_error",
103        Event::Custom { event_type, .. } => event_type.as_str(),
104        Event::FileRead { .. } => "file_read",
105    };
106
107    // Dispatch to subscribed plugins
108    for (plugin_id, plugin) in plugins.iter() {
109        // Check if plugin is subscribed to this event type
110        if let Some(events) = subscriptions.get(plugin_id) {
111            if events.contains(&event_type.to_string()) || events.contains(&"*".to_string()) {
112                // Clone the plugin reference to avoid holding the lock
113                let plugin_id_clone = plugin_id.clone();
114                let plugin_clone = Arc::clone(plugin);
115                let event_clone = event.clone();
116                let event_type_clone = event_type.to_string();
117
118                // Dispatch event in a separate task to avoid blocking
119                tokio::spawn(async move {
120                    let mut plugin_guard = plugin_clone.lock().await;
121                    if let Err(e) = plugin_guard.handle_event(&event_clone).await {
122                        tracing::error!(
123                            "Plugin {} error handling event {}: {}",
124                            plugin_id_clone,
125                            event_type_clone,
126                            e
127                        );
128                    }
129                });
130            }
131        }
132    }
133}