1use 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#[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#[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#[derive(Debug, Clone, Serialize, Deserialize)]
123pub struct PluginCapability {
124 pub name:String,
125 pub description:String,
126 pub RequiredPermissions:Vec<String>,
127}
128
129#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
131pub enum PluginPermission {
132 Filesystem { read:bool, write:bool, paths:Vec<String> },
134 Network { outbound:bool, inbound:bool, hosts:Vec<String> },
136 System { cpu:bool, memory:bool },
138 InterPlugin { plugins:Vec<String>, actions:Vec<String> },
140 Custom(String),
142}
143
144#[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#[derive(Debug, Clone, Serialize, Deserialize)]
172pub enum PluginValidationResult {
173 Valid,
174 Invalid(String),
175 Warning(String),
176}
177
178#[async_trait]
180pub trait PluginHooks: Send + Sync {
181 async fn on_load(&self) -> Result<()> { Ok(()) }
183
184 async fn on_start(&self) -> Result<()> { Ok(()) }
186
187 async fn on_stop(&self) -> Result<()> { Ok(()) }
189
190 async fn on_unload(&self) -> Result<()> { Ok(()) }
192
193 async fn on_config_changed(&self, _old:&serde_json::Value, _new:&serde_json::Value) -> Result<()> { Ok(()) }
195}
196
197#[async_trait]
199pub trait Plugin: PluginHooks + Send + Sync {
200 fn metadata(&self) -> &PluginMetadata;
202
203 fn sandbox_config(&self) -> PluginSandboxConfig { PluginSandboxConfig::default() }
205
206 fn permissions(&self) -> Vec<PluginPermission> { vec![] }
208
209 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 async fn get_state(&self) -> Result<serde_json::Value> { Ok(serde_json::json!({})) }
216
217 fn has_capability(&self, _capability:&str) -> bool { false }
219
220 fn has_permission(&self, _permission:&PluginPermission) -> bool { false }
222}
223
224#[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 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 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#[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
283pub 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
293pub 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 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 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 pub fn set_sandbox_enabled(&mut self, enabled:bool) { self.EnableSandbox = enabled; }
336
337 pub async fn discover_plugins(&self, directory:&str) -> Result<Vec<String>> {
339 let Discovered = vec![];
340
341 dev_log!("extensions", "[PluginManager] Discovering plugins in directory: {}", directory);
344 Ok(Discovered)
345 }
346
347 pub async fn load_from_manifest(&self, path:&str) -> Result<String> {
349 dev_log!("extensions", "[PluginManager] Loading plugin from manifest: {}", path);
352 Ok("loaded_plugin".to_string())
353 }
354
355 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 self.ValidatePluginMetadata(metadata)?;
367
368 self.CheckAirVersionCompatibility(metadata)?;
370
371 self.CheckApiVersionCompatibility(metadata)?;
373
374 self.check_dependencies(metadata).await?;
376
377 self.validate_capabilities_and_permissions(plugin.as_ref().as_ref())?;
379
380 let sandbox = if self.EnableSandbox {
382 plugin.sandbox_config()
383 } else {
384 PluginSandboxConfig { enabled:false, ..Default::default() }
385 };
386
387 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 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 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 pub fn validate_capabilities_and_permissions(&self, plugin:&dyn Plugin) -> Result<()> {
447 let permissions = plugin.permissions();
448
449 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 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 pub fn CheckApiVersionCompatibility(&self, _Metadata:&PluginMetadata) -> Result<()> {
496 Ok(())
499 }
500
501 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 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 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 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 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 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 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 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 pub async fn unload(&self, plugin_id:&str) -> Result<()> {
716 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 dev_log!("extensions", "error: [PluginManager] Plugin unload error: {}: {}", plugin_id, e);
737 Err(e)
738 },
739 Err(_) => {
740 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 pub async fn send_message(&self, message:PluginMessage) -> Result<PluginMessage> {
749 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 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 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 fn check_inter_plugin_permission(
789 &self,
790 _sender:&PluginRegistry,
791 _target:&PluginRegistry,
792 _message:&PluginMessage,
793 ) -> bool {
794 true
797 }
798
799 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 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 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 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 pub fn validate_plugin(&self, plugin:&dyn Plugin) -> PluginValidationResult {
853 let metadata = plugin.metadata();
854
855 if let Err(e) = self.ValidatePluginMetadata(metadata) {
857 return PluginValidationResult::Invalid(e.to_string());
858 }
859
860 if let Err(e) = self.CheckAirVersionCompatibility(metadata) {
862 return PluginValidationResult::Invalid(format!("Version compatibility error: {}", e));
863 }
864
865 PluginValidationResult::Valid
866 }
867
868 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 pub async fn resolve_load_order(&self) -> Result<Vec<String>> {
884 let plugins = self.plugins.read().await;
885
886 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 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 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#[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#[derive(Debug, Clone, Serialize, Deserialize)]
959pub enum PluginEvent {
960 Loaded { plugin_id:String },
962 Started { plugin_id:String },
964 Stopped { plugin_id:String },
966 Unloaded { plugin_id:String },
968 Error { plugin_id:String, error:String },
970 Message { from:String, to:String, action:String },
972 ConfigChanged { old:serde_json::Value, new:serde_json::Value },
974}
975
976#[async_trait]
978pub trait PluginEventHandler: Send + Sync {
979 async fn handle_event(&self, event:&PluginEvent) -> Result<()>;
981}
982
983pub struct PluginEventBus {
985 handlers:Arc<RwLock<Vec<Box<dyn PluginEventHandler>>>>,
986}
987
988impl PluginEventBus {
989 pub fn new() -> Self { Self { handlers:Arc::new(RwLock::new(vec![])) } }
991
992 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 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#[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#[derive(Debug, Clone, Serialize, Deserialize)]
1028pub struct PluginManifest {
1029 pub plugin:PluginMetadata,
1030 pub main:String,
1031 pub sandbox:Option<PluginSandboxConfig>,
1032}
1033
1034pub struct PluginLoader {
1036 PluginPaths:Vec<String>,
1037}
1038
1039impl PluginLoader {
1040 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 pub fn add_path(&mut self, path:String) { self.PluginPaths.push(path); }
1052
1053 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 pub async fn discover_in_path(&self, path:&str) -> Result<Vec<PluginDiscoveryResult>> {
1078 let Results = vec![];
1079
1080 dev_log!("extensions", "[PluginLoader] Discovering plugins in: {}", path);
1083 Ok(Results)
1084 }
1085
1086 pub async fn load_from_discovery(&self, discovery:&PluginDiscoveryResult) -> Result<Arc<Box<dyn Plugin>>> {
1088 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#[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 pub fn current() -> Self { Self { major:1, minor:0, patch:0, PreRelease:None } }
1117
1118 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 pub fn IsCompatible(&self, other:&ApiVersion) -> bool {
1141 if self.major != other.major {
1143 return false;
1144 }
1145
1146 if other.minor < self.minor {
1148 return false;
1149 }
1150
1151 true
1152 }
1153}
1154
1155pub struct ApiVersionManager {
1157 CurrentVersion:ApiVersion,
1158 CompatibleVersions:Vec<ApiVersion>,
1159}
1160
1161impl ApiVersionManager {
1162 pub fn new() -> Self {
1164 let current = ApiVersion::current();
1165 Self { CurrentVersion:current.clone(), CompatibleVersions:vec![current] }
1166 }
1167
1168 pub fn current(&self) -> &ApiVersion { &self.CurrentVersion }
1170
1171 pub fn IsCompatible(&self, version:&ApiVersion) -> bool { self.CurrentVersion.IsCompatible(version) }
1173
1174 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
1186pub struct PluginSandboxManager {
1192 sandboxes:Arc<RwLock<HashMap<String, PluginSandboxConfig>>>,
1193}
1194
1195impl PluginSandboxManager {
1196 pub fn new() -> Self { Self { sandboxes:Arc::new(RwLock::new(HashMap::new())) } }
1198
1199 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 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 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 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 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 let result = manager.start("test").await;
1287 assert!(result.is_ok());
1288
1289 let plugins = manager.list_plugins().await.unwrap();
1291 assert_eq!(plugins[0].state, PluginState::Running);
1292
1293 let result = manager.stop("test").await;
1295 assert!(result.is_ok());
1296
1297 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 let result = manager.validate_plugin(&TestPlugin);
1349 assert!(matches!(result, PluginValidationResult::Valid));
1350
1351 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}