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