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