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