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
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#[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#[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#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
144pub enum PluginPermission {
145 Filesystem { read:bool, write:bool, paths:Vec<String> },
147
148 Network { outbound:bool, inbound:bool, hosts:Vec<String> },
150
151 System { cpu:bool, memory:bool },
153
154 InterPlugin { plugins:Vec<String>, actions:Vec<String> },
156
157 Custom(String),
159}
160
161#[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#[derive(Debug, Clone, Serialize, Deserialize)]
201pub enum PluginValidationResult {
202 Valid,
203
204 Invalid(String),
205
206 Warning(String),
207}
208
209#[async_trait]
211pub trait PluginHooks: Send + Sync {
212 async fn on_load(&self) -> Result<()> { Ok(()) }
214
215 async fn on_start(&self) -> Result<()> { Ok(()) }
217
218 async fn on_stop(&self) -> Result<()> { Ok(()) }
220
221 async fn on_unload(&self) -> Result<()> { Ok(()) }
223
224 async fn on_config_changed(&self, _old:&serde_json::Value, _new:&serde_json::Value) -> Result<()> { Ok(()) }
226}
227
228#[async_trait]
230pub trait Plugin: PluginHooks + Send + Sync {
231 fn metadata(&self) -> &PluginMetadata;
233
234 fn sandbox_config(&self) -> PluginSandboxConfig { PluginSandboxConfig::default() }
236
237 fn permissions(&self) -> Vec<PluginPermission> { vec![] }
239
240 async fn Message(&self, from:&str, _message:&PluginMessage) -> Result<PluginMessage> {
242 Err(AirError::Plugin(format!("Plugin {} does not handle messages", from)))
243 }
244
245 async fn get_state(&self) -> Result<serde_json::Value> { Ok(serde_json::json!({})) }
247
248 fn has_capability(&self, _capability:&str) -> bool { false }
250
251 fn has_permission(&self, _permission:&PluginPermission) -> bool { false }
253}
254
255#[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 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 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#[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
329pub 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
344pub 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 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 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 pub fn set_sandbox_enabled(&mut self, enabled:bool) { self.EnableSandbox = enabled; }
405
406 pub async fn discover_plugins(&self, directory:&str) -> Result<Vec<String>> {
408 let Discovered = vec![];
409
410 dev_log!("extensions", "[PluginManager] Discovering plugins in directory: {}", directory);
413
414 Ok(Discovered)
415 }
416
417 pub async fn load_from_manifest(&self, path:&str) -> Result<String> {
419 dev_log!("extensions", "[PluginManager] Loading plugin from manifest: {}", path);
422
423 Ok("loaded_plugin".to_string())
424 }
425
426 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 self.ValidatePluginMetadata(metadata)?;
439
440 self.CheckAirVersionCompatibility(metadata)?;
442
443 self.CheckApiVersionCompatibility(metadata)?;
445
446 self.check_dependencies(metadata).await?;
448
449 self.validate_capabilities_and_permissions(plugin.as_ref().as_ref())?;
451
452 let sandbox = if self.EnableSandbox {
454 plugin.sandbox_config()
455 } else {
456 PluginSandboxConfig { enabled:false, ..Default::default() }
457 };
458
459 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 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 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 pub fn validate_capabilities_and_permissions(&self, plugin:&dyn Plugin) -> Result<()> {
527 let permissions = plugin.permissions();
528
529 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 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 pub fn CheckApiVersionCompatibility(&self, _Metadata:&PluginMetadata) -> Result<()> {
578 Ok(())
581 }
582
583 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 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 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 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 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 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 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 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 pub async fn unload(&self, plugin_id:&str) -> Result<()> {
855 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 dev_log!("extensions", "error: [PluginManager] Plugin unload error: {}: {}", plugin_id, e);
880
881 Err(e)
882 },
883
884 Err(_) => {
885 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 pub async fn send_message(&self, message:PluginMessage) -> Result<PluginMessage> {
895 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 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 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 fn check_inter_plugin_permission(
935 &self,
936
937 _sender:&PluginRegistry,
938
939 _target:&PluginRegistry,
940
941 _message:&PluginMessage,
942 ) -> bool {
943 true
946 }
947
948 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 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 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 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 pub fn validate_plugin(&self, plugin:&dyn Plugin) -> PluginValidationResult {
1008 let metadata = plugin.metadata();
1009
1010 if let Err(e) = self.ValidatePluginMetadata(metadata) {
1012 return PluginValidationResult::Invalid(e.to_string());
1013 }
1014
1015 if let Err(e) = self.CheckAirVersionCompatibility(metadata) {
1017 return PluginValidationResult::Invalid(format!("Version compatibility error: {}", e));
1018 }
1019
1020 PluginValidationResult::Valid
1021 }
1022
1023 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 pub async fn resolve_load_order(&self) -> Result<Vec<String>> {
1042 let plugins = self.plugins.read().await;
1043
1044 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 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 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#[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#[derive(Debug, Clone, Serialize, Deserialize)]
1129pub enum PluginEvent {
1130 Loaded { plugin_id:String },
1132
1133 Started { plugin_id:String },
1135
1136 Stopped { plugin_id:String },
1138
1139 Unloaded { plugin_id:String },
1141
1142 Error { plugin_id:String, error:String },
1144
1145 Message { from:String, to:String, action:String },
1147
1148 ConfigChanged { old:serde_json::Value, new:serde_json::Value },
1150}
1151
1152#[async_trait]
1154pub trait PluginEventHandler: Send + Sync {
1155 async fn Event(&self, event:&PluginEvent) -> Result<()>;
1157}
1158
1159pub struct PluginEventBus {
1161 handlers:Arc<RwLock<Vec<Box<dyn PluginEventHandler>>>>,
1162}
1163
1164impl PluginEventBus {
1165 pub fn new() -> Self { Self { handlers:Arc::new(RwLock::new(vec![])) } }
1167
1168 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 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#[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#[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
1217pub struct PluginLoader {
1219 PluginPaths:Vec<String>,
1220}
1221
1222impl PluginLoader {
1223 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 pub fn add_path(&mut self, path:String) { self.PluginPaths.push(path); }
1235
1236 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 pub async fn discover_in_path(&self, path:&str) -> Result<Vec<PluginDiscoveryResult>> {
1262 let Results = vec![];
1263
1264 dev_log!("extensions", "[PluginLoader] Discovering plugins in: {}", path);
1267
1268 Ok(Results)
1269 }
1270
1271 pub async fn load_from_discovery(&self, discovery:&PluginDiscoveryResult) -> Result<Arc<Box<dyn Plugin>>> {
1273 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#[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 pub fn current() -> Self { Self { major:1, minor:0, patch:0, PreRelease:None } }
1305
1306 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 pub fn IsCompatible(&self, other:&ApiVersion) -> bool {
1330 if self.major != other.major {
1332 return false;
1333 }
1334
1335 if other.minor < self.minor {
1337 return false;
1338 }
1339
1340 true
1341 }
1342}
1343
1344pub struct ApiVersionManager {
1346 CurrentVersion:ApiVersion,
1347
1348 CompatibleVersions:Vec<ApiVersion>,
1349}
1350
1351impl ApiVersionManager {
1352 pub fn new() -> Self {
1354 let current = ApiVersion::current();
1355
1356 Self { CurrentVersion:current.clone(), CompatibleVersions:vec![current] }
1357 }
1358
1359 pub fn current(&self) -> &ApiVersion { &self.CurrentVersion }
1361
1362 pub fn IsCompatible(&self, version:&ApiVersion) -> bool { self.CurrentVersion.IsCompatible(version) }
1364
1365 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
1377pub struct PluginSandboxManager {
1383 sandboxes:Arc<RwLock<HashMap<String, PluginSandboxConfig>>>,
1384}
1385
1386impl PluginSandboxManager {
1387 pub fn new() -> Self { Self { sandboxes:Arc::new(RwLock::new(HashMap::new())) } }
1389
1390 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 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 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 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 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 let result = manager.start("test").await;
1491
1492 assert!(result.is_ok());
1493
1494 let plugins = manager.list_plugins().await.unwrap();
1496
1497 assert_eq!(plugins[0].state, PluginState::Running);
1498
1499 let result = manager.stop("test").await;
1501
1502 assert!(result.is_ok());
1503
1504 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 let result = manager.validate_plugin(&TestPlugin);
1567
1568 assert!(matches!(result, PluginValidationResult::Valid));
1569
1570 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}