1use std::collections::HashMap;
8use std::path::{Path, PathBuf};
9use std::sync::Arc;
10use std::time::Duration;
11
12use chrono::Utc;
13use tokio::sync::{broadcast, RwLock};
14
15use bamboo_agent_core::storage::Storage;
16use bamboo_agent_core::AgentEvent;
17use bamboo_engine::Agent;
18use bamboo_llm::Config;
19use bamboo_llm::LLMProvider;
20use bamboo_mcp::manager::McpServerManager;
21use bamboo_skills::{SkillManager, SkillStoreConfig};
22use bamboo_storage::LockedSessionStore;
23use bamboo_storage::SessionStoreV2;
24
25use crate::error::AppError;
26use crate::schedule_app::manager::{build_schedule_context, ScheduleContext};
27use crate::schedule_app::ScheduleManager;
28use crate::schedule_app::ScheduleStore;
29use bamboo_engine::execution::spawn::{SpawnContext, SpawnScheduler};
30use bamboo_metrics::metrics_service::MetricsService;
31
32use super::{AgentRunner, AgentStatus};
33
34pub(super) type PermissionChecker = dyn bamboo_tools::permission::PermissionChecker;
36
37type ProviderHandles = (Arc<RwLock<Arc<dyn LLMProvider>>>, Arc<dyn LLMProvider>);
39
40pub async fn init_storage(
44 data_dir: &Path,
45) -> Result<(Arc<SessionStoreV2>, Arc<dyn Storage>), AppError> {
46 tracing::info!("Initializing session store V2 at: {:?}", data_dir);
47 let session_store = Arc::new(SessionStoreV2::new(data_dir.to_path_buf()).await.map_err(
48 |error| {
49 tracing::error!(
50 "Failed to initialize SessionStoreV2 at {:?}: {}",
51 data_dir,
52 error
53 );
54 AppError::StorageError(error)
55 },
56 )?);
57
58 match session_store.migrate_runtime_sidecars().await {
64 Ok(0) => {}
65 Ok(count) => tracing::info!("Runtime sidecar migration created {count} sidecar(s)"),
66 Err(error) => {
67 tracing::warn!("Runtime sidecar migration failed (continuing): {error}");
69 }
70 }
71
72 let session_store_for_rebuild = session_store.clone();
73 tokio::spawn(async move {
74 let purged_rows = match session_store_for_rebuild
75 .search_index()
76 .prune_stale_sessions()
77 .await
78 {
79 Ok(count) => count,
80 Err(error) => {
81 tracing::warn!("Background session search index prune failed: {}", error);
82 0
83 }
84 };
85
86 if let Err(error) = session_store_for_rebuild.rebuild_search_index().await {
87 tracing::warn!("Background session search index rebuild failed: {}", error);
88 } else {
89 tracing::info!("Background session search index rebuild completed");
90 }
91
92 match session_store_for_rebuild
93 .search_index()
94 .maybe_vacuum_if_needed(purged_rows)
95 .await
96 {
97 Ok(true) => tracing::info!("Background session search index vacuum completed"),
98 Ok(false) => {}
99 Err(error) => {
100 tracing::warn!("Background session search index vacuum failed: {}", error)
101 }
102 }
103 });
104
105 let storage: Arc<dyn Storage> = session_store.clone();
106 tracing::info!(
107 "Session store V2 initialized (index: {:?}, sessions: {:?})",
108 session_store.index_path(),
109 session_store.sessions_root_dir()
110 );
111 Ok((session_store, storage))
112}
113
114pub async fn init_skill_manager(data_dir: &Path) -> Arc<SkillManager> {
116 let project_dir = std::env::var_os("BAMBOO_WORKSPACE_DIR")
117 .map(PathBuf::from)
118 .or_else(|| std::env::current_dir().ok());
119 let active_mode = std::env::var("BAMBOO_SKILL_MODE")
120 .ok()
121 .map(|value| value.trim().to_string())
122 .filter(|value| !value.is_empty());
123
124 let skill_manager = Arc::new(SkillManager::with_config(SkillStoreConfig {
125 skills_dir: data_dir.join("skills"),
126 project_dir,
127 active_mode,
128 }));
129 if let Err(error) = skill_manager.initialize().await {
130 tracing::warn!("Failed to initialize skill manager: {}", error);
131 }
132 skill_manager
133}
134
135pub fn build_provider_handles(provider: Arc<dyn LLMProvider>) -> ProviderHandles {
141 let provider_lock: Arc<RwLock<Arc<dyn LLMProvider>>> = Arc::new(RwLock::new(provider));
142 let provider_handle: Arc<dyn LLMProvider> = Arc::new(
143 crate::reloadable_provider::ReloadableProvider::new(provider_lock.clone()),
144 );
145 (provider_lock, provider_handle)
146}
147
148pub async fn load_permission_checker(bamboo_home_dir: &Path) -> Arc<PermissionChecker> {
152 let storage = bamboo_tools::permission::storage::PermissionStorage::new(bamboo_home_dir);
153 let permission_config = match storage.load().await {
154 Ok(Some(config)) => config,
155 Ok(None) => {
156 let cfg = bamboo_tools::permission::PermissionConfig::new();
157 cfg.set_enabled(false);
158 cfg
159 }
160 Err(error) => {
161 tracing::warn!("Failed to load permission config; defaulting to disabled: {error}");
162 let cfg = bamboo_tools::permission::PermissionConfig::new();
163 cfg.set_enabled(false);
164 cfg
165 }
166 };
167 permission_config.cleanup_expired_grants();
168 Arc::new(bamboo_tools::permission::ConfigPermissionChecker::new(
169 Arc::new(permission_config),
170 ))
171}
172
173pub fn init_mcp_manager(config: Arc<RwLock<Config>>) -> Arc<McpServerManager> {
175 let mcp_manager = Arc::new(McpServerManager::new_with_config(config.clone()));
176
177 {
179 let mcp_manager = mcp_manager.clone();
180 let config = config.clone();
181 tokio::spawn(async move {
182 let mcp_config = config.read().await.mcp.clone();
183 mcp_manager.initialize_from_config(&mcp_config).await;
184 });
185 }
186
187 mcp_manager
188}
189
190pub async fn init_metrics_service(data_dir: &Path) -> Result<Arc<MetricsService>, AppError> {
192 let service = MetricsService::new(data_dir.join("metrics.db"))
193 .await
194 .map_err(|error| {
195 tracing::error!("Failed to initialize metrics storage: {}", error);
196 AppError::InternalError(anyhow::anyhow!(
197 "Failed to initialize metrics storage: {error}"
198 ))
199 })?;
200 Ok(Arc::new(service))
201}
202
203pub fn spawn_runner_cleanup_task(
205 runners: Arc<RwLock<HashMap<String, AgentRunner>>>,
206 log_prefix: Option<&'static str>,
207) {
208 tokio::spawn(async move {
209 loop {
210 tokio::time::sleep(Duration::from_secs(60)).await;
211
212 let mut runners_guard = runners.write().await;
213 let now = Utc::now();
214
215 runners_guard.retain(|session_id, runner| {
216 let should_keep = match &runner.status {
217 AgentStatus::Running => true,
218 _ => {
219 let age = now.signed_duration_since(
220 runner.completed_at.unwrap_or(runner.started_at),
221 );
222 age.num_seconds() < 300 }
224 };
225
226 if !should_keep {
227 if let Some(prefix) = log_prefix {
228 tracing::debug!("[{}:{}] Cleaning up completed runner", prefix, session_id);
229 } else {
230 tracing::debug!("[{}] Cleaning up completed runner", session_id);
231 }
232 }
233
234 should_keep
235 });
236 }
237 });
238}
239
240pub async fn init_schedule_store(data_dir: &PathBuf) -> Result<Arc<ScheduleStore>, AppError> {
242 let store = ScheduleStore::new(data_dir.clone())
243 .await
244 .map_err(|error| {
245 tracing::error!(
246 "Failed to initialize ScheduleStore at {:?}: {}",
247 data_dir,
248 error
249 );
250 AppError::StorageError(error)
251 })?;
252 Ok(Arc::new(store))
253}
254
255#[allow(clippy::too_many_arguments)]
257pub fn build_spawn_scheduler(
258 agent: Arc<Agent>,
259 child_tools: Arc<dyn bamboo_agent_core::tools::ToolExecutor>,
260 sessions: bamboo_engine::SessionCache,
261 agent_runners: Arc<RwLock<HashMap<String, AgentRunner>>>,
262 session_event_senders: Arc<RwLock<HashMap<String, broadcast::Sender<AgentEvent>>>>,
263 external_child_runner: Option<Arc<dyn bamboo_engine::runtime::execution::ExternalChildRunner>>,
264 provider_router: Option<Arc<bamboo_llm::ProviderModelRouter>>,
265 completion_handler: Option<Arc<dyn bamboo_engine::execution::ChildCompletionHandler>>,
266 app_data_dir: Option<std::path::PathBuf>,
267 account_feed_inbox: Option<bamboo_engine::execution::AccountFeedInbox>,
268) -> Arc<SpawnScheduler> {
269 Arc::new(SpawnScheduler::new(SpawnContext {
270 agent,
271 tools: child_tools,
272 sessions_cache: sessions,
273 agent_runners,
274 session_event_senders,
275 external_child_runner,
276 provider_router,
277 app_data_dir,
278 completion_handler,
279 account_feed_inbox,
280 }))
281}
282
283#[allow(clippy::too_many_arguments)]
285pub fn build_schedule_manager(
286 schedule_store: Arc<ScheduleStore>,
287 agent: Arc<Agent>,
288 tools_for_schedules: Arc<dyn bamboo_agent_core::tools::ToolExecutor>,
289 sessions: bamboo_engine::SessionCache,
290 agent_runners: Arc<RwLock<HashMap<String, AgentRunner>>>,
291 session_event_senders: Arc<RwLock<HashMap<String, broadcast::Sender<AgentEvent>>>>,
292 persistence: Arc<LockedSessionStore>,
293 config: Arc<RwLock<Config>>,
294 provider_registry: Arc<bamboo_llm::ProviderRegistry>,
295 app_data_dir: Option<std::path::PathBuf>,
296 account_feed_inbox: Option<bamboo_engine::execution::AccountFeedInbox>,
297) -> Arc<ScheduleManager> {
298 let base_ctx = ScheduleContext {
299 schedule_store,
300 agent,
301 tools: tools_for_schedules,
302 sessions_cache: sessions,
303 agent_runners,
304 session_event_senders,
305 account_feed_inbox,
306 persistence,
307 app_data_dir,
308 trigger_engine: crate::schedule_app::default_trigger_engine(),
309 resolve_run_config: Arc::new(|_| unimplemented!("replaced by build_schedule_context")),
310 };
311 Arc::new(ScheduleManager::new(build_schedule_context(
312 base_ctx,
313 config,
314 provider_registry,
315 )))
316}