Skip to main content

AirLibrary/Plugins/
mod.rs

1//! # Plugin Architecture
2//!
3//! ## Responsibilities
4//!
5//! This module provides a comprehensive plugin system for the Air daemon,
6//! enabling extensibility through dynamically loaded plugins that can enhance
7//! daemon functionality. The plugin system is responsible for:
8//!
9//! - **Plugin Discovery**: Automatically discovering available plugins from
10//!   configured directories
11//! - **Plugin Loading**: Dynamically loading plugins into the daemon runtime
12//! - **Plugin Validation**: Validating plugin metadata, dependencies, and
13//!   compatibility
14//! - **Sandboxing**: Isolating plugins to prevent crashes and security issues
15//! - **Lifecycle Management**: Managing plugin states (load, start, stop,
16//!   unload) with proper hooks
17//! - **API Registration**: Extending the daemon API through plugin-provided
18//!   commands and handlers
19//! - **Inter-Plugin Communication Enabling**: plugins to communicate with each
20//!   other via message passing
21//! - **Permission Management**: Enforcing fine-grained permissions and
22//!   capabilities for plugins
23//! - **Version Compatibility**: Ensuring plugins are compatible with the daemon
24//!   version
25//! - **Dependency Resolution**: Resolving and validating plugin dependencies
26//!
27//! ## VSCode Extension Architecture Patterns
28//!
29//! This implementation draws inspiration from VSCode's extension architecture:
30//! - Reference: vs/platform/extensions/common/ extensionHostStarter.ts
31//! - Reference: vs/server/node/ extensionHostConnection.ts
32//! - Reference: vs/platform/remote/common/ remoteAgentConnection.ts
33//!
34//! Patterns adopted from VSCode extensions:
35//! - Separate extension host process for isolation and crash protection
36//! - Activation events to trigger extension loading on-demand
37//! - Contribution points for extending functionality
38//! - Message-based communication between host and extensions
39//! - State management and lifecycle hooks
40//! - API versioning for backward compatibility
41//! - Permission and capability descriptors
42//!
43//! ## Integration with Cocoon Extension Host
44//!
45//! The plugin system is designed to integrate with the Cocoon Extension Host
46//! (similar to VSCode's extension host architecture). This provides:
47//! - Isolated execution environments for plugins
48//! - Crash recovery and resilience
49//! - Resource management and limits
50//! - Communication via IPC channels
51//! - Hot reload capability without daemon restart
52//!
53//! ## FUTURE Enhancements
54//!
55//! - **Plugin Marketplace**: Implement a central plugin marketplace for
56//! discovery and installation (similar to VSCode's extension marketplace)
57//! - **Hot Reload Support**: Implement live reloading of plugins without daemon
58//! restart
59//! - **Advanced Sandboxing**: Add more sophisticated sandboxing with resource
60//! quotas, network isolation, and filesystem access controls
61//! - **Plugin Distribution**: Implement plugin packaging, signing, and
62//! distribution mechanisms
63//! - **Automatic Updates**: Add automatic plugin update checking and
64//! installation
65//! - **Telemetry Integration**: Add plugin usage telemetry and reporting
66//! - **Plugin Profiles**: Support multiple plugin configurations for different
67//! environments
68//! - **Security Audit**: Implement comprehensive security audit and
69//! vulnerability scanning for plugins
70//! - **Performance Monitoring**: Add detailed performance monitoring and
71//!   profiling for plugins
72//! - **Plugin Debugging**: Provide debugging tools and interfaces for plugin
73//!   developers
74//!
75//! ## Security and Isolation
76//!
77//! - Plugins run in isolated processes to prevent daemon crashes
78//! - Fine-grained permission system controls plugin capabilities
79//! - API version compatibility checks prevent breaking changes
80//! - Resource limits prevent plugin exhaustion attacks
81//! - Plugin authentication and signing to prevent malicious plugins
82//! - Filesystem and network access restrictions
83
84pub mod ApiVersion;
85
86pub mod EventBus;
87
88use std::{collections::HashMap, sync::Arc, time::Duration};
89
90use async_trait::async_trait;
91use serde::{Deserialize, Serialize};
92use tokio::sync::RwLock;
93use chrono::{DateTime, Utc};
94use uuid::Uuid;
95
96use crate::{AirError, Result, dev_log};
97
98// =============================================================================
99// Plugin Types and Traits
100// =============================================================================
101
102/// Plugin metadata
103#[derive(Debug, Clone, Serialize, Deserialize)]
104pub struct PluginMetadata {
105	pub id:String,
106
107	pub name:String,
108
109	pub version:String,
110
111	pub description:String,
112
113	pub author:String,
114
115	pub MinAirVersion:String,
116
117	pub MaxAirVersion:Option<String>,
118
119	pub dependencies:Vec<PluginDependency>,
120
121	pub capabilities:Vec<String>,
122}
123
124/// Plugin dependency specification
125#[derive(Debug, Clone, Serialize, Deserialize)]
126pub struct PluginDependency {
127	pub PluginId:String,
128
129	pub MinVersion:String,
130
131	pub MaxVersion:Option<String>,
132
133	pub optional:bool,
134}
135
136/// Plugin capability and permission descriptor
137#[derive(Debug, Clone, Serialize, Deserialize)]
138pub struct PluginCapability {
139	pub name:String,
140
141	pub description:String,
142
143	pub RequiredPermissions:Vec<String>,
144}
145
146/// Plugin permission
147#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
148pub enum PluginPermission {
149	/// Access filesystem
150	Filesystem { read:bool, write:bool, paths:Vec<String> },
151
152	/// Access network
153	Network { outbound:bool, inbound:bool, hosts:Vec<String> },
154
155	/// Access system resources
156	System { cpu:bool, memory:bool },
157
158	/// Access other plugins
159	InterPlugin { plugins:Vec<String>, actions:Vec<String> },
160
161	/// Custom permission
162	Custom(String),
163}
164
165/// Plugin sandbox configuration
166#[derive(Debug, Clone, Serialize, Deserialize)]
167pub struct PluginSandboxConfig {
168	pub enabled:bool,
169
170	pub MaxMemoryMb:Option<u64>,
171
172	pub MaxCPUPercent:Option<f64>,
173
174	pub NetworkAllowed:bool,
175
176	pub FilesystemAllowed:bool,
177
178	pub AllowedPaths:Vec<String>,
179
180	pub TimeoutSecs:Option<u64>,
181}
182
183impl Default for PluginSandboxConfig {
184	fn default() -> Self {
185		Self {
186			enabled:true,
187
188			MaxMemoryMb:Some(128),
189
190			MaxCPUPercent:Some(10.0),
191
192			NetworkAllowed:false,
193
194			FilesystemAllowed:false,
195
196			AllowedPaths:vec![],
197
198			TimeoutSecs:Some(30),
199		}
200	}
201}
202
203/// Plugin validation result
204#[derive(Debug, Clone, Serialize, Deserialize)]
205pub enum PluginValidationResult {
206	Valid,
207
208	Invalid(String),
209
210	Warning(String),
211}
212
213/// Plugin lifecycle hooks
214#[async_trait]
215pub trait PluginHooks: Send + Sync {
216	/// Called when plugin is being loaded
217	async fn on_load(&self) -> Result<()> { Ok(()) }
218
219	/// Called when plugin is starting
220	async fn on_start(&self) -> Result<()> { Ok(()) }
221
222	/// Called when plugin is stopping
223	async fn on_stop(&self) -> Result<()> { Ok(()) }
224
225	/// Called when plugin is being unloaded
226	async fn on_unload(&self) -> Result<()> { Ok(()) }
227
228	/// Called when configuration changes
229	async fn on_config_changed(&self, _old:&serde_json::Value, _new:&serde_json::Value) -> Result<()> { Ok(()) }
230}
231
232/// Plugin interface trait
233#[async_trait]
234pub trait Plugin: PluginHooks + Send + Sync {
235	/// Get plugin metadata
236	fn metadata(&self) -> &PluginMetadata;
237
238	/// Get plugin sandbox configuration
239	fn sandbox_config(&self) -> PluginSandboxConfig { PluginSandboxConfig::default() }
240
241	/// Get plugin permissions
242	fn permissions(&self) -> Vec<PluginPermission> { vec![] }
243
244	/// Handle inter-plugin message
245	async fn Message(&self, from:&str, _message:&PluginMessage) -> Result<PluginMessage> {
246		Err(AirError::Plugin(format!("Plugin {} does not handle messages", from)))
247	}
248
249	/// Get plugin state for diagnostics
250	async fn get_state(&self) -> Result<serde_json::Value> { Ok(serde_json::json!({})) }
251
252	/// Check if plugin has specific capability
253	fn has_capability(&self, _capability:&str) -> bool { false }
254
255	/// Check if plugin has specific permission
256	fn has_permission(&self, _permission:&PluginPermission) -> bool { false }
257}
258
259/// Inter-plugin message
260#[derive(Debug, Clone, Serialize, Deserialize)]
261pub struct PluginMessage {
262	pub id:String,
263
264	pub from:String,
265
266	pub to:String,
267
268	pub action:String,
269
270	pub data:serde_json::Value,
271
272	pub timestamp:DateTime<Utc>,
273}
274
275impl PluginMessage {
276	/// Create a new plugin message
277	pub fn new(from:String, to:String, action:String, data:serde_json::Value) -> Self {
278		Self { id:Uuid::new_v4().to_string(), from, to, action, data, timestamp:Utc::now() }
279	}
280
281	/// Validate message format and content
282	pub fn validate(&self) -> Result<()> {
283		if self.id.is_empty() {
284			return Err(crate::AirError::Plugin("Message ID cannot be empty".to_string()));
285		}
286
287		if self.from.is_empty() {
288			return Err(crate::AirError::Plugin("Message sender cannot be empty".to_string()));
289		}
290
291		if self.to.is_empty() {
292			return Err(crate::AirError::Plugin("Message recipient cannot be empty".to_string()));
293		}
294
295		if self.action.is_empty() {
296			return Err(crate::AirError::Plugin("Message action cannot be empty".to_string()));
297		}
298
299		if self.action.len() > 100 {
300			return Err(crate::AirError::Plugin("Message action too long".to_string()));
301		}
302
303		Ok(())
304	}
305}
306
307// =============================================================================
308// Plugin Manager
309// =============================================================================
310
311/// Plugin state tracking
312#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
313pub enum PluginState {
314	#[serde(rename = "unloaded")]
315	Unloaded,
316
317	#[serde(rename = "loaded")]
318	Loaded,
319
320	#[serde(rename = "starting")]
321	Starting,
322
323	#[serde(rename = "running")]
324	Running,
325
326	#[serde(rename = "stopping")]
327	Stopping,
328
329	#[serde(rename = "error")]
330	Error,
331}
332
333/// Plugin registry entry
334pub struct PluginRegistry {
335	pub plugin:Arc<Box<dyn Plugin>>,
336
337	pub state:PluginState,
338
339	pub StartedAt:Option<DateTime<Utc>>,
340
341	pub LoadedAt:Option<DateTime<Utc>>,
342
343	pub error:Option<String>,
344
345	pub sandbox:PluginSandboxConfig,
346}
347
348/// Main plugin manager
349pub struct PluginManager {
350	plugins:Arc<RwLock<HashMap<String, PluginRegistry>>>,
351
352	#[allow(dead_code)]
353	MessageQueue:Arc<RwLock<Vec<PluginMessage>>>,
354
355	AirVersion:String,
356
357	EnableSandbox:bool,
358
359	StartupTimeout:Duration,
360
361	OperationTimeout:Duration,
362}
363
364impl PluginManager {
365	/// Create a new plugin manager
366	pub fn new(AirVersion:String) -> Self {
367		Self {
368			plugins:Arc::new(RwLock::new(HashMap::new())),
369
370			MessageQueue:Arc::new(RwLock::new(Vec::new())),
371
372			AirVersion,
373
374			EnableSandbox:true,
375
376			StartupTimeout:Duration::from_secs(30),
377
378			OperationTimeout:Duration::from_secs(60),
379		}
380	}
381
382	/// Create a new plugin manager with custom configuration
383	pub fn with_config(
384		AirVersion:String,
385
386		EnableSandbox:bool,
387
388		StartupTimeoutSecs:u64,
389
390		OperationTimeoutSecs:u64,
391	) -> Self {
392		Self {
393			plugins:Arc::new(RwLock::new(HashMap::new())),
394
395			MessageQueue:Arc::new(RwLock::new(Vec::new())),
396
397			AirVersion,
398
399			EnableSandbox,
400
401			StartupTimeout:Duration::from_secs(StartupTimeoutSecs),
402
403			OperationTimeout:Duration::from_secs(OperationTimeoutSecs),
404		}
405	}
406
407	/// Enable or disable sandbox mode
408	pub fn set_sandbox_enabled(&mut self, enabled:bool) { self.EnableSandbox = enabled; }
409
410	/// Discover plugins from a directory
411	pub async fn discover_plugins(&self, directory:&str) -> Result<Vec<String>> {
412		let Discovered = vec![];
413
414		// In production, this would scan the directory for plugin manifests
415		// For now, we return an empty list
416		dev_log!("extensions", "[PluginManager] Discovering plugins in directory: {}", directory);
417
418		Ok(Discovered)
419	}
420
421	/// Load a plugin from a manifest file
422	pub async fn load_from_manifest(&self, path:&str) -> Result<String> {
423		// In production, this would load and parse a plugin manifest
424		// For now, we return a mock plugin ID
425		dev_log!("extensions", "[PluginManager] Loading plugin from manifest: {}", path);
426
427		Ok("loaded_plugin".to_string())
428	}
429
430	/// Register a plugin
431	pub async fn register(&self, plugin:Arc<Box<dyn Plugin>>) -> Result<()> {
432		let metadata = plugin.metadata();
433
434		dev_log!(
435			"extensions",
436			"[PluginManager] Registering plugin: {} v{}",
437			metadata.name,
438			metadata.version
439		);
440
441		// Validate plugin metadata
442		self.ValidatePluginMetadata(metadata)?;
443
444		// Check Air version compatibility
445		self.CheckAirVersionCompatibility(metadata)?;
446
447		// Check API version compatibility
448		self.CheckApiVersionCompatibility(metadata)?;
449
450		// Check dependencies
451		self.check_dependencies(metadata).await?;
452
453		// Validate plugin capabilities and permissions
454		self.validate_capabilities_and_permissions(plugin.as_ref().as_ref())?;
455
456		// Setup sandbox configuration
457		let sandbox = if self.EnableSandbox {
458			plugin.sandbox_config()
459		} else {
460			PluginSandboxConfig { enabled:false, ..Default::default() }
461		};
462
463		// Load plugin with timeout
464		let LoadResult = tokio::time::timeout(self.StartupTimeout, plugin.on_load()).await;
465
466		let _load_result = LoadResult
467			.map_err(|_| {
468				AirError::Plugin(format!("Plugin {} load timeout after {:?}", metadata.name, self.StartupTimeout))
469			})?
470			.map_err(|e| {
471				dev_log!(
472					"extensions",
473					"error: [PluginManager] Failed to load plugin {}: {}",
474					metadata.name,
475					e
476				);
477				e
478			})?;
479
480		// Register in map
481		let mut plugins = self.plugins.write().await;
482
483		plugins.insert(
484			metadata.id.clone(),
485			PluginRegistry {
486				plugin:plugin.clone(),
487				state:PluginState::Loaded,
488				StartedAt:None,
489				LoadedAt:Some(Utc::now()),
490				error:None,
491				sandbox,
492			},
493		);
494
495		dev_log!("extensions", "[PluginManager] Plugin registered: {}", metadata.name);
496
497		Ok(())
498	}
499
500	/// Validate plugin metadata
501	pub fn ValidatePluginMetadata(&self, metadata:&PluginMetadata) -> Result<()> {
502		if metadata.id.is_empty() {
503			return Err(crate::AirError::Plugin("Plugin ID cannot be empty".to_string()));
504		}
505
506		if metadata.id.len() > 100 {
507			return Err(crate::AirError::Plugin("Plugin ID too long (max 100 characters)".to_string()));
508		}
509
510		if !metadata.id.chars().all(|c| c.is_alphanumeric() || c == '-' || c == '_') {
511			return Err(crate::AirError::Plugin("Plugin ID contains invalid characters".to_string()));
512		}
513
514		if metadata.name.is_empty() {
515			return Err(crate::AirError::Plugin("Plugin name cannot be empty".to_string()));
516		}
517
518		if metadata.version.is_empty() {
519			return Err(crate::AirError::Plugin("Plugin version cannot be empty".to_string()));
520		}
521
522		if metadata.author.is_empty() {
523			return Err(crate::AirError::Plugin("Plugin author cannot be empty".to_string()));
524		}
525
526		Ok(())
527	}
528
529	/// Validate plugin capabilities and permissions
530	pub fn validate_capabilities_and_permissions(&self, plugin:&dyn Plugin) -> Result<()> {
531		let permissions = plugin.permissions();
532
533		// Check for dangerous permissions
534		for permission in &permissions {
535			match permission {
536				PluginPermission::Filesystem { write, .. } if *write => {
537					dev_log!(
538						"extensions",
539						"warn: [PluginManager] Plugin {} requests filesystem write access",
540						plugin.metadata().id
541					);
542				},
543
544				PluginPermission::Network { .. } => {
545					dev_log!(
546						"extensions",
547						"warn: [PluginManager] Plugin {} requests network access",
548						plugin.metadata().id
549					);
550				},
551
552				_ => {},
553			}
554		}
555
556		Ok(())
557	}
558
559	/// Check Air version compatibility
560	pub fn CheckAirVersionCompatibility(&self, metadata:&PluginMetadata) -> Result<()> {
561		if !self.version_satisfies(&self.AirVersion, &metadata.MinAirVersion) {
562			return Err(AirError::Plugin(format!(
563				"Plugin requires Air version {} or higher, current: {}",
564				metadata.MinAirVersion, self.AirVersion
565			)));
566		}
567
568		if let Some(max_version) = &metadata.MaxAirVersion {
569			if !self.version_satisfies(max_version, &self.AirVersion) {
570				return Err(AirError::Plugin(format!(
571					"Plugin is incompatible with Air version {}, max supported: {}",
572					self.AirVersion, max_version
573				)));
574			}
575		}
576
577		Ok(())
578	}
579
580	/// Check API version compatibility
581	pub fn CheckApiVersionCompatibility(&self, _Metadata:&PluginMetadata) -> Result<()> {
582		// Check if plugin declares compatibility with current API version
583		// In production, this would check against the daemon's API version
584		Ok(())
585	}
586
587	/// Check plugin dependencies
588	pub async fn check_dependencies(&self, metadata:&PluginMetadata) -> Result<()> {
589		let plugins = self.plugins.read().await;
590
591		for dep in &metadata.dependencies {
592			if !dep.optional {
593				let DepPlugin = plugins
594					.get(&dep.PluginId)
595					.ok_or_else(|| AirError::Plugin(format!("Required dependency not found: {}", dep.PluginId)))?;
596
597				let DepVersion = &DepPlugin.plugin.metadata().version;
598
599				if !self.version_satisfies(DepVersion, &dep.MinVersion) {
600					return Err(AirError::Plugin(format!(
601						"Dependency {} version {} does not satisfy requirement {}",
602						dep.PluginId, DepVersion, dep.MinVersion
603					)));
604				}
605
606				if DepPlugin.state != PluginState::Running && DepPlugin.state != PluginState::Loaded {
607					return Err(AirError::Plugin(format!(
608						"Dependency {} is not ready (state: {:?})",
609						dep.PluginId, DepPlugin.state
610					)));
611				}
612			}
613		}
614
615		Ok(())
616	}
617
618	/// Start a plugin
619	pub async fn start(&self, PluginId:&str) -> Result<()> {
620		let mut plugins = self.plugins.write().await;
621
622		let registry = plugins
623			.get_mut(PluginId)
624			.ok_or_else(|| AirError::Plugin(format!("Plugin not found: {}", PluginId)))?;
625
626		if registry.state == PluginState::Running {
627			dev_log!("extensions", "[PluginManager] Plugin {} already running", PluginId);
628
629			return Ok(());
630		}
631
632		registry.state = PluginState::Starting;
633
634		// Check sandbox configuration
635		if self.EnableSandbox && registry.sandbox.enabled {
636			dev_log!("extensions", "[PluginManager] Starting plugin {} in sandbox mode", PluginId);
637		}
638
639		let plugin = registry.plugin.clone();
640
641		drop(plugins);
642
643		let StartResult = tokio::time::timeout(self.StartupTimeout, plugin.on_start()).await;
644
645		match StartResult {
646			Ok(Ok(())) => {
647				let mut plugins = self.plugins.write().await;
648
649				if let Some(registry) = plugins.get_mut(PluginId) {
650					registry.state = PluginState::Running;
651
652					registry.StartedAt = Some(Utc::now());
653
654					registry.error = None;
655				}
656
657				dev_log!("extensions", "[PluginManager] Plugin started: {}", PluginId);
658
659				Ok(())
660			},
661
662			Ok(Err(e)) => {
663				let mut plugins = self.plugins.write().await;
664
665				if let Some(registry) = plugins.get_mut(PluginId) {
666					registry.state = PluginState::Error;
667
668					registry.error = Some(e.to_string());
669				}
670
671				dev_log!("extensions", "error: [PluginManager] Plugin start failed: {}: {}", PluginId, e);
672
673				Err(e)
674			},
675
676			Err(_) => {
677				let mut plugins = self.plugins.write().await;
678
679				if let Some(registry) = plugins.get_mut(PluginId) {
680					registry.state = PluginState::Error;
681
682					registry.error = Some(format!("Startup timeout after {:?}", self.StartupTimeout));
683				}
684
685				dev_log!("extensions", "error: [PluginManager] Plugin start timeout: {}", PluginId);
686
687				Err(AirError::Plugin(format!("Plugin {} startup timeout", PluginId)))
688			},
689		}
690	}
691
692	/// Stop a plugin
693	pub async fn stop(&self, PluginId:&str) -> Result<()> {
694		let mut plugins = self.plugins.write().await;
695
696		let registry = plugins
697			.get_mut(PluginId)
698			.ok_or_else(|| AirError::Plugin(format!("Plugin not found: {}", PluginId)))?;
699
700		if registry.state != PluginState::Running {
701			dev_log!("extensions", "[PluginManager] Plugin {} not running", PluginId);
702
703			return Ok(());
704		}
705
706		registry.state = PluginState::Stopping;
707
708		let plugin = registry.plugin.clone();
709
710		drop(plugins);
711
712		let StopResult = tokio::time::timeout(self.OperationTimeout, plugin.on_stop()).await;
713
714		match StopResult {
715			Ok(Ok(())) => {
716				let mut plugins = self.plugins.write().await;
717
718				if let Some(registry) = plugins.get_mut(PluginId) {
719					registry.state = PluginState::Loaded;
720
721					registry.StartedAt = None;
722				}
723
724				dev_log!("extensions", "[PluginManager] Plugin stopped: {}", PluginId);
725
726				Ok(())
727			},
728
729			Ok(Err(e)) => {
730				let mut plugins = self.plugins.write().await;
731
732				if let Some(registry) = plugins.get_mut(PluginId) {
733					registry.state = PluginState::Error;
734
735					registry.error = Some(e.to_string());
736				}
737
738				dev_log!("extensions", "error: [PluginManager] Plugin stop failed: {}: {}", PluginId, e);
739
740				Err(e)
741			},
742
743			Err(_) => {
744				let mut plugins = self.plugins.write().await;
745
746				if let Some(registry) = plugins.get_mut(PluginId) {
747					registry.state = PluginState::Error;
748
749					registry.error = Some(format!("Stop timeout after {:?}", self.OperationTimeout));
750				}
751
752				dev_log!("extensions", "error: [PluginManager] Plugin stop timeout: {}", PluginId);
753
754				Err(AirError::Plugin(format!("Plugin {} stop timeout", PluginId)))
755			},
756		}
757	}
758
759	/// Start all registered plugins
760	pub async fn start_all(&self) -> Result<()> {
761		let PluginIds:Vec<String> = self.plugins.read().await.keys().cloned().collect();
762
763		dev_log!("extensions", "[PluginManager] Starting {} plugins", PluginIds.len());
764
765		for PluginId in PluginIds {
766			if let Err(e) = self.start(&PluginId).await {
767				dev_log!("extensions", "warn: [PluginManager] Failed to start plugin {}: {}", PluginId, e);
768			}
769		}
770
771		Ok(())
772	}
773
774	/// Stop all running plugins
775	pub async fn stop_all(&self) -> Result<()> {
776		let PluginIds:Vec<String> = self.plugins.read().await.keys().cloned().collect();
777
778		dev_log!("extensions", "[PluginManager] Stopping {} plugins", PluginIds.len());
779
780		// Stop in reverse order to respect dependencies
781		for plugin_id in PluginIds.into_iter().rev() {
782			if let Err(e) = self.stop(&plugin_id).await {
783				dev_log!("extensions", "warn: [PluginManager] Failed to stop plugin {}: {}", plugin_id, e);
784			}
785		}
786
787		Ok(())
788	}
789
790	/// Load a plugin
791	pub async fn load(&self, plugin_id:&str) -> Result<()> {
792		let mut plugins = self.plugins.write().await;
793
794		let registry = plugins
795			.get_mut(plugin_id)
796			.ok_or_else(|| AirError::Plugin(format!("Plugin not found: {}", plugin_id)))?;
797
798		if registry.state != PluginState::Unloaded {
799			dev_log!("extensions", "[PluginManager] Plugin {} already loaded", plugin_id);
800
801			return Ok(());
802		}
803
804		let plugin = registry.plugin.clone();
805
806		drop(plugins);
807
808		let LoadResult = tokio::time::timeout(self.StartupTimeout, plugin.on_load()).await;
809
810		match LoadResult {
811			Ok(Ok(())) => {
812				let mut plugins = self.plugins.write().await;
813
814				if let Some(registry) = plugins.get_mut(plugin_id) {
815					registry.state = PluginState::Loaded;
816
817					registry.LoadedAt = Some(Utc::now());
818
819					registry.error = None;
820				}
821
822				dev_log!("extensions", "[PluginManager] Plugin loaded: {}", plugin_id);
823
824				Ok(())
825			},
826
827			Ok(Err(e)) => {
828				let mut plugins = self.plugins.write().await;
829
830				if let Some(registry) = plugins.get_mut(plugin_id) {
831					registry.state = PluginState::Error;
832
833					registry.error = Some(e.to_string());
834				}
835
836				dev_log!("extensions", "error: [PluginManager] Plugin load failed: {}: {}", plugin_id, e);
837
838				Err(e)
839			},
840
841			Err(_) => {
842				let mut plugins = self.plugins.write().await;
843
844				if let Some(registry) = plugins.get_mut(plugin_id) {
845					registry.state = PluginState::Error;
846
847					registry.error = Some(format!("Load timeout after {:?}", self.StartupTimeout));
848				}
849
850				dev_log!("extensions", "error: [PluginManager] Plugin load timeout: {}", plugin_id);
851
852				Err(AirError::Plugin(format!("Plugin {} load timeout", plugin_id)))
853			},
854		}
855	}
856
857	/// Unload a plugin
858	pub async fn unload(&self, plugin_id:&str) -> Result<()> {
859		// First stop the plugin
860		self.stop(plugin_id).await?;
861
862		let mut plugins = self.plugins.write().await;
863
864		let registry = plugins
865			.get(plugin_id)
866			.ok_or_else(|| AirError::Plugin(format!("Plugin not found: {}", plugin_id)))?;
867
868		let plugin = registry.plugin.clone();
869
870		plugins.remove(plugin_id);
871
872		let UnloadResult = tokio::time::timeout(self.OperationTimeout, plugin.on_unload()).await;
873
874		match UnloadResult {
875			Ok(Ok(())) => {
876				dev_log!("extensions", "[PluginManager] Plugin unloaded: {}", plugin_id);
877
878				Ok(())
879			},
880
881			Ok(Err(e)) => {
882				// Plugin is removed from registry even if unload fails
883				dev_log!("extensions", "error: [PluginManager] Plugin unload error: {}: {}", plugin_id, e);
884
885				Err(e)
886			},
887
888			Err(_) => {
889				// Plugin is removed from registry even if timeout occurs
890				dev_log!("extensions", "warn: [PluginManager] Plugin unload timeout: {}", plugin_id);
891
892				Err(AirError::Plugin(format!("Plugin {} unload timeout", plugin_id)))
893			},
894		}
895	}
896
897	/// Send message from one plugin to another
898	pub async fn send_message(&self, message:PluginMessage) -> Result<PluginMessage> {
899		// Validate message
900		message.validate()?;
901
902		let plugins = self.plugins.read().await;
903
904		let target = plugins
905			.get(&message.to)
906			.ok_or_else(|| AirError::Plugin(format!("Target plugin not found: {}", message.to)))?;
907
908		if target.state != PluginState::Running {
909			return Err(AirError::Plugin(format!(
910				"Target plugin not running: {} (state: {:?})",
911				message.to, target.state
912			)));
913		}
914
915		// Check if sender has permission to send to receiver
916		let SenderMetadata = plugins
917			.get(&message.from)
918			.ok_or_else(|| AirError::Plugin(format!("Sender plugin not found: {}", message.from)))?;
919
920		if !self.check_inter_plugin_permission(SenderMetadata, target, &message) {
921			return Err(AirError::Plugin(format!(
922				"Permission denied: {} cannot send to {}",
923				message.from, message.to
924			)));
925		}
926
927		let plugin = target.plugin.clone();
928
929		drop(plugins);
930
931		// Send message with timeout
932		let SendResult = tokio::time::timeout(self.OperationTimeout, plugin.Message(&message.from, &message)).await;
933
934		SendResult.map_err(|_| AirError::Plugin(format!("Message send timeout: {} -> {}", message.from, message.to)))?
935	}
936
937	/// Check inter-plugin communication permission
938	fn check_inter_plugin_permission(
939		&self,
940
941		_sender:&PluginRegistry,
942
943		_target:&PluginRegistry,
944
945		_message:&PluginMessage,
946	) -> bool {
947		// In production, this would check if sender has permission to communicate with
948		// target For now, we allow all communication
949		true
950	}
951
952	/// Get plugin list with details
953	pub async fn list_plugins(&self) -> Result<Vec<PluginInfo>> {
954		let plugins = self.plugins.read().await;
955
956		let mut result = Vec::new();
957
958		for (id, registry) in plugins.iter() {
959			let metadata = registry.plugin.metadata().clone();
960
961			result.push(PluginInfo {
962				id:id.clone(),
963				metadata,
964				state:registry.state,
965				UptimeSecs:registry.StartedAt.map(|t| (Utc::now() - t).num_seconds() as u64).unwrap_or(0),
966				error:registry.error.clone(),
967			});
968		}
969
970		Ok(result)
971	}
972
973	/// Get plugin state
974	pub async fn get_plugin_state(&self, plugin_id:&str) -> Result<serde_json::Value> {
975		let plugins = self.plugins.read().await;
976
977		let registry = plugins
978			.get(plugin_id)
979			.ok_or_else(|| AirError::Plugin(format!("Plugin not found: {}", plugin_id)))?;
980
981		registry.plugin.get_state().await
982	}
983
984	/// Get plugin permissions
985	pub async fn get_plugin_permissions(&self, plugin_id:&str) -> Result<Vec<PluginPermission>> {
986		let plugins = self.plugins.read().await;
987
988		let registry = plugins
989			.get(plugin_id)
990			.ok_or_else(|| AirError::Plugin(format!("Plugin not found: {}", plugin_id)))?;
991
992		Ok(registry.plugin.permissions())
993	}
994
995	/// Validate all plugins
996	pub async fn validate_all_plugins(&self) -> Vec<(String, PluginValidationResult)> {
997		let plugins = self.plugins.read().await;
998
999		let mut results = vec![];
1000
1001		for (id, registry) in plugins.iter() {
1002			let result = self.validate_plugin(registry.plugin.as_ref().as_ref());
1003
1004			results.push((id.clone(), result));
1005		}
1006
1007		results
1008	}
1009
1010	/// Validate a single plugin
1011	pub fn validate_plugin(&self, plugin:&dyn Plugin) -> PluginValidationResult {
1012		let metadata = plugin.metadata();
1013
1014		// Validate metadata
1015		if let Err(e) = self.ValidatePluginMetadata(metadata) {
1016			return PluginValidationResult::Invalid(e.to_string());
1017		}
1018
1019		// Check version compatibility
1020		if let Err(e) = self.CheckAirVersionCompatibility(metadata) {
1021			return PluginValidationResult::Invalid(format!("Version compatibility error: {}", e));
1022		}
1023
1024		PluginValidationResult::Valid
1025	}
1026
1027	/// Get dependency graph
1028	pub async fn get_dependency_graph(&self) -> Result<serde_json::Value> {
1029		let plugins = self.plugins.read().await;
1030
1031		let mut graph = serde_json::Map::new();
1032
1033		for (id, registry) in plugins.iter() {
1034			let metadata = registry.plugin.metadata();
1035
1036			let dependencies:Vec<String> = metadata.dependencies.iter().map(|d| d.PluginId.clone()).collect();
1037
1038			graph.insert(id.clone(), serde_json::json!(dependencies));
1039		}
1040
1041		Ok(serde_json::Value::Object(graph))
1042	}
1043
1044	/// Resolve plugin load order based on dependencies
1045	pub async fn resolve_load_order(&self) -> Result<Vec<String>> {
1046		let plugins = self.plugins.read().await;
1047
1048		// Topological sort based on dependencies
1049		let mut visited = std::collections::HashSet::new();
1050
1051		let mut order = vec![];
1052
1053		for plugin_id in plugins.keys() {
1054			self.VisitPluginForLoadOrder(plugin_id, &mut visited, &mut order, &plugins)?;
1055		}
1056
1057		Ok(order)
1058	}
1059
1060	/// Visit plugin for load order (helper function)
1061	fn VisitPluginForLoadOrder(
1062		&self,
1063
1064		plugin_id:&str,
1065
1066		visited:&mut std::collections::HashSet<String>,
1067
1068		order:&mut Vec<String>,
1069
1070		plugins:&HashMap<String, PluginRegistry>,
1071	) -> Result<()> {
1072		if visited.contains(plugin_id) {
1073			return Ok(());
1074		}
1075
1076		visited.insert(plugin_id.to_string());
1077
1078		if let Some(registry) = plugins.get(plugin_id) {
1079			let metadata = registry.plugin.metadata();
1080
1081			for dep in &metadata.dependencies {
1082				if !dep.optional {
1083					self.VisitPluginForLoadOrder(&dep.PluginId, visited, order, plugins)?;
1084				}
1085			}
1086		}
1087
1088		order.push(plugin_id.to_string());
1089
1090		Ok(())
1091	}
1092
1093	/// Simple version satisfaction check (X.Y.Z format)
1094	fn version_satisfies(&self, actual:&str, required:&str) -> bool {
1095		let ActualParts:Vec<&str> = actual.split('.').collect();
1096
1097		let RequiredParts:Vec<&str> = required.split('.').collect();
1098
1099		for (i, required_part) in RequiredParts.iter().enumerate() {
1100			if let (Ok(a), Ok(r)) = (ActualParts.get(i).unwrap_or(&"0").parse::<u32>(), required_part.parse::<u32>()) {
1101				if a > r {
1102					return true;
1103				} else if a < r {
1104					return false;
1105				}
1106			}
1107		}
1108
1109		true
1110	}
1111}
1112
1113/// Plugin information for listing
1114#[derive(Debug, Clone, Serialize, Deserialize)]
1115pub struct PluginInfo {
1116	pub id:String,
1117
1118	pub metadata:PluginMetadata,
1119
1120	pub state:PluginState,
1121
1122	pub UptimeSecs:u64,
1123
1124	pub error:Option<String>,
1125}
1126
1127// =============================================================================
1128// Plugin Event System
1129// =============================================================================
1130
1131/// Plugin event types
1132#[derive(Debug, Clone, Serialize, Deserialize)]
1133pub enum PluginEvent {
1134	/// Plugin was loaded
1135	Loaded { plugin_id:String },
1136
1137	/// Plugin was started
1138	Started { plugin_id:String },
1139
1140	/// Plugin was stopped
1141	Stopped { plugin_id:String },
1142
1143	/// Plugin was unloaded
1144	Unloaded { plugin_id:String },
1145
1146	/// Plugin encountered an error
1147	Error { plugin_id:String, error:String },
1148
1149	/// Plugin sent a message
1150	Message { from:String, to:String, action:String },
1151
1152	/// Configuration changed
1153	ConfigChanged { old:serde_json::Value, new:serde_json::Value },
1154}
1155
1156/// Plugin event handler
1157#[async_trait]
1158pub trait PluginEventHandler: Send + Sync {
1159	/// Handle a plugin event
1160	async fn Event(&self, event:&PluginEvent) -> Result<()>;
1161}
1162
1163/// Event bus for plugin events
1164pub struct PluginEventBus {
1165	handlers:Arc<RwLock<Vec<Box<dyn PluginEventHandler>>>>,
1166}
1167
1168impl PluginEventBus {
1169	/// Create a new event bus
1170	pub fn new() -> Self { Self { handlers:Arc::new(RwLock::new(vec![])) } }
1171
1172	/// Register an event handler
1173	pub async fn register_handler(&self, handler:Box<dyn PluginEventHandler>) {
1174		let mut handlers = self.handlers.write().await;
1175
1176		handlers.push(handler);
1177	}
1178
1179	/// Emit an event to all handlers
1180	pub async fn emit(&self, event:PluginEvent) {
1181		let handlers = self.handlers.read().await;
1182
1183		for handler in handlers.iter() {
1184			if let Err(e) = handler.Event(&event).await {
1185				dev_log!("extensions", "error: [PluginEventBus] Event handler error: {}", e);
1186			}
1187		}
1188	}
1189}
1190
1191impl Default for PluginEventBus {
1192	fn default() -> Self { Self::new() }
1193}
1194
1195// =============================================================================
1196// Plugin Discovery and Loading
1197// =============================================================================
1198
1199/// Plugin discovery result
1200#[derive(Debug, Clone, Serialize, Deserialize)]
1201pub struct PluginDiscoveryResult {
1202	pub plugin_id:String,
1203
1204	pub ManifestPath:String,
1205
1206	pub metadata:PluginMetadata,
1207
1208	pub enabled:bool,
1209}
1210
1211/// Plugin manifest
1212#[derive(Debug, Clone, Serialize, Deserialize)]
1213pub struct PluginManifest {
1214	pub plugin:PluginMetadata,
1215
1216	pub main:String,
1217
1218	pub sandbox:Option<PluginSandboxConfig>,
1219}
1220
1221/// Plugin loader for discovering and loading plugins
1222pub struct PluginLoader {
1223	PluginPaths:Vec<String>,
1224}
1225
1226impl PluginLoader {
1227	/// Create a new plugin loader
1228	pub fn new() -> Self {
1229		Self {
1230			PluginPaths:vec![
1231				"/usr/local/lib/Air/plugins".to_string(),
1232				"~/.local/share/Air/plugins".to_string(),
1233			],
1234		}
1235	}
1236
1237	/// Add a plugin discovery path
1238	pub fn add_path(&mut self, path:String) { self.PluginPaths.push(path); }
1239
1240	/// Discover plugins from all configured paths
1241	pub async fn discover_all(&self) -> Result<Vec<PluginDiscoveryResult>> {
1242		let mut results = vec![];
1243
1244		for path in &self.PluginPaths {
1245			match self.discover_in_path(path).await {
1246				Ok(mut discovered) => {
1247					results.append(&mut discovered);
1248				},
1249
1250				Err(e) => {
1251					dev_log!(
1252						"extensions",
1253						"warn: [PluginLoader] Failed to discover plugins in {}: {}",
1254						path,
1255						e
1256					);
1257				},
1258			}
1259		}
1260
1261		Ok(results)
1262	}
1263
1264	/// Discover plugins in a specific path
1265	pub async fn discover_in_path(&self, path:&str) -> Result<Vec<PluginDiscoveryResult>> {
1266		let Results = vec![];
1267
1268		// In production, this would scan the directory for plugin manifests
1269		// For now, we return an empty list
1270		dev_log!("extensions", "[PluginLoader] Discovering plugins in: {}", path);
1271
1272		Ok(Results)
1273	}
1274
1275	/// Load a plugin from a discovery result
1276	pub async fn load_from_discovery(&self, discovery:&PluginDiscoveryResult) -> Result<Arc<Box<dyn Plugin>>> {
1277		// In production, this would load the plugin from the manifest
1278		// For now, we return an error
1279		Err(AirError::Plugin(format!(
1280			"Plugin loading not yet implemented: {}",
1281			discovery.plugin_id
1282		)))
1283	}
1284}
1285
1286impl Default for PluginLoader {
1287	fn default() -> Self { Self::new() }
1288}
1289
1290// =============================================================================
1291// Plugin Isolation and Sandboxing
1292// =============================================================================
1293
1294/// Plugin sandbox manager
1295pub struct PluginSandboxManager {
1296	sandboxes:Arc<RwLock<HashMap<String, PluginSandboxConfig>>>,
1297}
1298
1299impl PluginSandboxManager {
1300	/// Create a new sandbox manager
1301	pub fn new() -> Self { Self { sandboxes:Arc::new(RwLock::new(HashMap::new())) } }
1302
1303	/// Create a sandbox for a plugin
1304	pub async fn create_sandbox(&self, plugin_id:String, config:PluginSandboxConfig) -> Result<()> {
1305		let mut sandboxes = self.sandboxes.write().await;
1306
1307		sandboxes.insert(plugin_id, config);
1308
1309		Ok(())
1310	}
1311
1312	/// Get sandbox configuration
1313	pub async fn get_sandbox(&self, plugin_id:&str) -> Option<PluginSandboxConfig> {
1314		let sandboxes = self.sandboxes.read().await;
1315
1316		sandboxes.get(plugin_id).cloned()
1317	}
1318
1319	/// Remove a sandbox
1320	pub async fn remove_sandbox(&self, plugin_id:&str) {
1321		let mut sandboxes = self.sandboxes.write().await;
1322
1323		sandboxes.remove(plugin_id);
1324	}
1325
1326	/// Check if a plugin is running in a sandbox
1327	pub async fn is_sandboxed(&self, plugin_id:&str) -> bool {
1328		let sandboxes = self.sandboxes.read().await;
1329
1330		sandboxes.get(plugin_id).map_or(false, |s| s.enabled)
1331	}
1332}
1333
1334impl Default for PluginSandboxManager {
1335	fn default() -> Self { Self::new() }
1336}
1337
1338#[cfg(test)]
1339mod tests {
1340
1341	use super::*;
1342
1343	struct TestPlugin;
1344
1345	// Use Box::leak to create static metadata
1346	fn test_metadata() -> &'static PluginMetadata {
1347		Box::leak(Box::new(PluginMetadata {
1348			id:"test".to_string(),
1349			name:"Test Plugin".to_string(),
1350			version:"1.0.0".to_string(),
1351			description:"A test plugin".to_string(),
1352			author:"Test".to_string(),
1353			MinAirVersion:"0.1.0".to_string(),
1354			MaxAirVersion:None,
1355			dependencies:vec![],
1356			capabilities:vec![],
1357		}))
1358	}
1359
1360	#[async_trait]
1361	impl PluginHooks for TestPlugin {}
1362
1363	#[async_trait]
1364	impl Plugin for TestPlugin {
1365		fn metadata(&self) -> &PluginMetadata { test_metadata() }
1366	}
1367
1368	#[tokio::test]
1369	async fn test_plugin_manager_creation() {
1370		let manager = PluginManager::new("0.1.0".to_string());
1371
1372		let plugins = manager.list_plugins().await.unwrap();
1373
1374		assert!(plugins.is_empty());
1375	}
1376
1377	#[tokio::test]
1378	async fn test_plugin_registration() {
1379		let manager = PluginManager::new("0.1.0".to_string());
1380
1381		let plugin = Arc::new(Box::new(TestPlugin) as Box<dyn Plugin>);
1382
1383		let result = manager.register(plugin.clone()).await;
1384
1385		assert!(result.is_ok());
1386
1387		let plugins = manager.list_plugins().await.unwrap();
1388
1389		assert_eq!(plugins.len(), 1);
1390
1391		assert_eq!(plugins[0].id, "test");
1392	}
1393
1394	#[tokio::test]
1395	async fn test_plugin_lifecycle() {
1396		let manager = PluginManager::new("0.1.0".to_string());
1397
1398		let plugin = Arc::new(Box::new(TestPlugin) as Box<dyn Plugin>);
1399
1400		manager.register(plugin.clone()).await.unwrap();
1401
1402		// Start the plugin
1403		let result = manager.start("test").await;
1404
1405		assert!(result.is_ok());
1406
1407		// Check state
1408		let plugins = manager.list_plugins().await.unwrap();
1409
1410		assert_eq!(plugins[0].state, PluginState::Running);
1411
1412		// Stop the plugin
1413		let result = manager.stop("test").await;
1414
1415		assert!(result.is_ok());
1416
1417		// Check state
1418		let plugins = manager.list_plugins().await.unwrap();
1419
1420		assert_eq!(plugins[0].state, PluginState::Loaded);
1421	}
1422
1423	#[tokio::test]
1424	async fn test_version_satisfaction() {
1425		let manager = PluginManager::new("1.0.0".to_string());
1426
1427		assert!(manager.version_satisfies("1.0.0", "0.1.0"));
1428
1429		assert!(manager.version_satisfies("1.2.0", "1.0.0"));
1430
1431		assert!(manager.version_satisfies("1.0.5", "1.0.0"));
1432
1433		assert!(!manager.version_satisfies("0.9.0", "1.0.0"));
1434	}
1435
1436	#[tokio::test]
1437	async fn test_plugin_message_validation() {
1438		let message = PluginMessage::new(
1439			"sender".to_string(),
1440			"receiver".to_string(),
1441			"action".to_string(),
1442			serde_json::json!({}),
1443		);
1444
1445		assert!(message.validate().is_ok());
1446	}
1447
1448	#[tokio::test]
1449	async fn test_api_version_compatibility() {
1450		let v1 = ApiVersion::ApiVersion { major:1, minor:0, patch:0, PreRelease:None };
1451
1452		let v2 = ApiVersion::ApiVersion { major:1, minor:1, patch:0, PreRelease:None };
1453
1454		let v3 = ApiVersion::ApiVersion { major:2, minor:0, patch:0, PreRelease:None };
1455
1456		assert!(v1.IsCompatible(&v2));
1457
1458		assert!(!v1.IsCompatible(&v3));
1459	}
1460
1461	#[tokio::test]
1462	async fn test_sandbox_config_default() {
1463		let config = PluginSandboxConfig::default();
1464
1465		assert!(config.enabled);
1466
1467		assert_eq!(config.MaxMemoryMb, Some(128));
1468
1469		assert!(!config.NetworkAllowed);
1470
1471		assert!(!config.FilesystemAllowed);
1472	}
1473
1474	#[tokio::test]
1475	async fn test_plugin_metadata_validation() {
1476		let manager = PluginManager::new("1.0.0".to_string());
1477
1478		// Directly reference TestPlugin to avoid trait bound issues
1479		let result = manager.validate_plugin(&TestPlugin);
1480
1481		assert!(matches!(result, PluginValidationResult::Valid));
1482
1483		// Verify the TestPlugin metadata can be accessed
1484		let metadata = test_metadata();
1485
1486		assert_eq!(metadata.id, "test");
1487
1488		assert_eq!(metadata.name, "Test Plugin");
1489
1490		assert_eq!(metadata.version, "1.0.0");
1491
1492		assert_eq!(metadata.author, "Test");
1493
1494		assert_eq!(metadata.description, "A test plugin");
1495	}
1496}