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