agent_engine/extensions/
loader.rs1use std::sync::Arc;
8
9use tokio::sync::{mpsc, RwLock};
10
11use super::hooks::events::{HookEvent, HookResult};
12use super::manager::{ExtensionLoadFailure, ExtensionManager};
13
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub enum ExtensionLoaderEvent {
16 Started,
17 Loaded {
18 plugin: String,
19 loaded: usize,
20 failed: usize,
21 },
22 Failed {
23 failure: ExtensionLoadFailure,
24 loaded: usize,
25 failed: usize,
26 },
27 Finished {
28 loaded: Vec<String>,
29 failed: Vec<ExtensionLoadFailure>,
30 },
31}
32
33impl ExtensionLoaderEvent {
34 pub fn progress_counts(&self) -> Option<(usize, usize)> {
35 match self {
36 ExtensionLoaderEvent::Started => Some((0, 0)),
37 ExtensionLoaderEvent::Loaded { loaded, failed, .. } => Some((*loaded, *failed)),
38 ExtensionLoaderEvent::Failed { loaded, failed, .. } => Some((*loaded, *failed)),
39 ExtensionLoaderEvent::Finished { loaded, failed } => Some((loaded.len(), failed.len())),
40 }
41 }
42}
43
44pub fn spawn_discover_and_load(
57 manager: Arc<RwLock<ExtensionManager>>,
58 tx: mpsc::UnboundedSender<ExtensionLoaderEvent>,
59 session_id: Option<String>,
60) -> tokio::task::JoinHandle<()> {
61 tokio::spawn(async move {
62 let _ = tx.send(ExtensionLoaderEvent::Started);
63 let (loaded, failed) = manager
64 .write()
65 .await
66 .discover_and_load_with_progress(|event| {
67 let _ = tx.send(event);
68 })
69 .await;
70
71 if let Some(session_id) = session_id {
73 let hook_bus = manager.read().await.hook_bus().clone();
74 let event = HookEvent::on_session_start(&session_id);
75 if let HookResult::Inject { content } = hook_bus.emit(&event).await {
76 tracing::debug!(
77 len = content.len(),
78 "on_session_start injected session-scoped context"
79 );
80 hook_bus.set_session_injection(content).await;
81 }
82 }
83
84 let _ = tx.send(ExtensionLoaderEvent::Finished { loaded, failed });
85 })
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91
92 #[test]
93 fn progress_counts_report_finished_totals() {
94 let event = ExtensionLoaderEvent::Finished {
95 loaded: vec!["a".into(), "b".into()],
96 failed: vec![ExtensionLoadFailure {
97 plugin: "bad".into(),
98 manifest_path: None,
99 reason: "oops".into(),
100 hint: "fix it".into(),
101 }],
102 };
103 assert_eq!(event.progress_counts(), Some((2, 1)));
104 }
105}