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