Skip to main content

engram/
app_state.rs

1//! Application state for the Engram MCP server.
2//!
3//! Wraps storage, embedder, search engine, realtime broadcaster, and
4//! (when the `hooks` feature is enabled) the lifecycle `HookManager`.
5
6use crate::embedding::{Embedder, EmbeddingCache};
7#[cfg(feature = "hooks")]
8use crate::hooks::{HookContext, HookManager, HookResult, LifecycleHook};
9use crate::realtime::RealtimeManager;
10use crate::search::{FuzzyEngine, SearchConfig};
11use crate::storage::Storage;
12#[cfg(feature = "meilisearch")]
13use crate::storage::{MeilisearchBackend, MeilisearchIndexer};
14use std::sync::Arc;
15
16/// Application state shared across MCP handlers.
17pub struct AppState {
18    pub storage: Arc<Storage>,
19    pub embedder: Arc<dyn Embedder + Send + Sync>,
20    pub fuzzy_engine: Arc<parking_lot::Mutex<FuzzyEngine>>,
21    pub search_config: SearchConfig,
22    pub realtime: Option<Arc<RealtimeManager>>,
23    pub embedding_cache: Arc<EmbeddingCache>,
24    #[cfg(feature = "meilisearch")]
25    pub meili: Option<Arc<MeilisearchBackend>>,
26    #[cfg(feature = "meilisearch")]
27    pub meili_indexer: Option<Arc<MeilisearchIndexer>>,
28    #[cfg(feature = "meilisearch")]
29    pub meili_sync_interval: Option<u64>,
30    /// Hook manager for lifecycle hooks (Phase L - ENG-78). See issue #11
31    /// for the in-progress wiring of `trigger_hook` into MCP handlers.
32    #[cfg(feature = "hooks")]
33    pub hook_manager: Option<Arc<HookManager>>,
34}
35
36impl AppState {
37    pub fn new(
38        storage: Arc<Storage>,
39        embedder: Arc<dyn Embedder + Send + Sync>,
40        fuzzy_engine: Arc<parking_lot::Mutex<FuzzyEngine>>,
41        search_config: SearchConfig,
42        realtime: Option<Arc<RealtimeManager>>,
43        embedding_cache: Arc<EmbeddingCache>,
44        #[cfg(feature = "meilisearch")] meili: Option<Arc<MeilisearchBackend>>,
45        #[cfg(feature = "meilisearch")] meili_indexer: Option<Arc<MeilisearchIndexer>>,
46        #[cfg(feature = "meilisearch")] meili_sync_interval: Option<u64>,
47    ) -> Self {
48        Self {
49            storage,
50            embedder,
51            fuzzy_engine,
52            search_config,
53            realtime,
54            embedding_cache,
55            #[cfg(feature = "meilisearch")]
56            meili,
57            #[cfg(feature = "meilisearch")]
58            meili_indexer,
59            #[cfg(feature = "meilisearch")]
60            meili_sync_interval,
61            #[cfg(feature = "hooks")]
62            hook_manager: None,
63        }
64    }
65
66    /// Enable lifecycle hooks (call during startup if hooks feature is enabled).
67    #[cfg(feature = "hooks")]
68    pub fn enable_hooks(&mut self) {
69        let mut hook_manager = HookManager::new();
70
71        hook_manager.register(LifecycleHook::SessionStart, |_hook, context| {
72            eprintln!(
73                "[Hook] SessionStart: session_id={:?}, workspace={:?}",
74                context.session_id, context.workspace
75            );
76            Ok(HookResult::Continue)
77        });
78
79        self.hook_manager = Some(Arc::new(hook_manager));
80        tracing::info!("Lifecycle hooks enabled");
81    }
82
83    /// Trigger a lifecycle hook. Returns an empty vec if hooks are not enabled.
84    #[cfg(feature = "hooks")]
85    pub fn trigger_hook(
86        &self,
87        hook: LifecycleHook,
88        context: HookContext,
89    ) -> crate::Result<Vec<HookResult>> {
90        if let Some(ref manager) = self.hook_manager {
91            manager.trigger(hook, &context)
92        } else {
93            Ok(vec![])
94        }
95    }
96}