1use super::init::{
2 build_provider_handles, build_schedule_manager, build_spawn_scheduler, init_mcp_manager,
3 init_metrics_service, init_schedule_store, init_skill_manager, init_storage,
4 load_permission_checker, spawn_runner_cleanup_task,
5};
6use super::tools::{build_base_tools, build_root_tools};
7use super::*;
8use crate::tools::OptionalSubagentModelResolver;
9use bamboo_agent_core::storage::Storage;
10
11impl AppState {
12 pub async fn new(bamboo_home_dir: PathBuf) -> Result<Self, AppError> {
47 bamboo_config::paths::init_bamboo_dir(bamboo_home_dir.clone());
50
51 let config = Config::from_data_dir(Some(bamboo_home_dir.clone()));
53
54 let provider_registry =
55 match bamboo_llm::ProviderRegistry::from_config(&config, bamboo_home_dir.clone()).await
56 {
57 Ok(registry) => Arc::new(registry),
58 Err(e) => {
59 tracing::error!("Failed to create provider registry: {}", e);
60 Arc::new(
61 bamboo_llm::ProviderRegistry::from_config(
62 &Config::default(),
63 bamboo_home_dir.clone(),
64 )
65 .await
66 .expect("Cannot create even an empty provider registry"),
67 )
68 }
69 };
70
71 let provider = provider_registry.get_default().unwrap_or_else(|| {
72 let default_provider_name = provider_registry.default_provider_name();
73 let message = if config.has_provider_instances() {
74 format!(
75 "Default provider instance '{}' is not available or failed to initialize",
76 default_provider_name
77 )
78 } else {
79 format!(
80 "Provider '{}' is not available or failed to initialize",
81 config.provider
82 )
83 };
84 Arc::new(UnconfiguredProvider { message }) as Arc<dyn LLMProvider>
85 });
86
87 Self::new_with_provider(bamboo_home_dir, config, provider).await
88 }
89
90 pub async fn new_with_provider(
105 bamboo_home_dir: PathBuf,
106 config: Config,
107 provider: Arc<dyn LLMProvider>,
108 ) -> Result<Self, AppError> {
109 let data_dir = bamboo_home_dir.clone();
111 let (session_store, storage) = init_storage(&data_dir).await?;
112 let persistence = Arc::new(LockedSessionStore::new(storage.clone()));
113
114 let sessions: bamboo_engine::SessionCache = Arc::new(dashmap::DashMap::new());
116
117 let config = Arc::new(RwLock::new(config));
118
119 {
128 let config_for_workspace = config.clone();
129 let last_known: Arc<std::sync::Mutex<Option<PathBuf>>> =
130 Arc::new(std::sync::Mutex::new(None));
131 bamboo_agent_core::workspace_state::set_default_workspace_provider(Box::new(
132 move || match config_for_workspace.try_read() {
133 Ok(cfg) => {
134 let path = cfg.get_default_work_area_path();
135 if let Ok(mut cache) = last_known.lock() {
136 *cache = path.clone();
137 }
138 path
139 }
140 Err(_) => last_known.lock().ok().and_then(|c| c.clone()),
141 },
142 ));
143 }
144
145 let permission_checker = load_permission_checker(&bamboo_home_dir).await;
146 let notification_service = Arc::new(bamboo_notification::NotificationService::new(
147 bamboo_home_dir.join("notification_preferences.json"),
148 ));
149 let mcp_manager = init_mcp_manager(config.clone());
150 let skill_manager = init_skill_manager(&data_dir).await;
151 let metrics_service = init_metrics_service(&data_dir).await?;
152
153 let startup_sessions = {
154 let entries = session_store.list_index_entries().await;
155 let mut sessions = Vec::new();
156 for entry in entries {
157 if let Some(session) = session_store
158 .load_session(&entry.id)
159 .await
160 .map_err(AppError::StorageError)?
161 {
162 sessions.push(session);
163 }
164 }
165 sessions
166 };
167 metrics_service
168 .reconcile_startup_sessions(startup_sessions, &[])
169 .await
170 .map_err(|error| {
171 AppError::InternalError(anyhow::anyhow!(
172 "Failed to reconcile stale metrics state on startup: {error}"
173 ))
174 })?;
175
176 let agent_runners: Arc<RwLock<HashMap<String, AgentRunner>>> =
177 Arc::new(RwLock::new(HashMap::new()));
178 spawn_runner_cleanup_task(agent_runners.clone(), None);
179
180 let process_registry = Arc::new(ProcessRegistry::new());
181 let (provider_lock, provider_handle) = build_provider_handles(provider);
182
183 let config_snapshot = config.read().await;
185 let provider_registry = match bamboo_llm::ProviderRegistry::from_config(
186 &config_snapshot,
187 bamboo_home_dir.clone(),
188 )
189 .await
190 {
191 Ok(registry) => Arc::new(registry),
192 Err(e) => {
193 tracing::error!("Failed to create provider registry: {}", e);
194 Arc::new(
195 bamboo_llm::ProviderRegistry::from_config(
196 &Config::default(),
197 bamboo_home_dir.clone(),
198 )
199 .await
200 .expect("Cannot create even an empty provider registry"),
201 )
202 }
203 };
204 drop(config_snapshot);
205
206 let provider_router = Arc::new(bamboo_llm::ProviderModelRouter::new(
207 provider_registry.clone(),
208 ));
209 let model_catalog = Arc::new(bamboo_llm::ModelCatalogService::new(
210 provider_registry.clone(),
211 ));
212
213 let base_tools = build_base_tools(
214 config.clone(),
215 permission_checker.clone(),
216 mcp_manager.clone(),
217 skill_manager.clone(),
218 storage.clone(),
219 persistence.clone(),
220 sessions.clone(),
221 bamboo_home_dir.clone(),
222 );
223
224 let session_event_senders: Arc<RwLock<HashMap<String, broadcast::Sender<AgentEvent>>>> =
226 Arc::new(RwLock::new(HashMap::new()));
227
228 let account_sink = bamboo_engine::events::AccountEventSink::new(data_dir.join("events"))
231 .map_err(|e| {
232 AppError::InternalError(anyhow::anyhow!(
233 "failed to initialize account change-feed journal: {e}"
234 ))
235 })?;
236
237 let child_tools: Arc<dyn bamboo_agent_core::tools::ToolExecutor> = base_tools.clone();
240
241 let agent = Arc::new(
246 bamboo_engine::Agent::builder()
247 .storage(storage.clone())
248 .persistence(persistence.clone())
249 .attachment_reader(session_store.clone())
250 .skill_manager(skill_manager.clone())
251 .metrics_collector(metrics_service.collector())
252 .config(config.clone())
253 .provider(provider_handle.clone())
254 .default_tools(base_tools.clone())
255 .build()
256 .expect("agent runtime should be fully configured"),
257 );
258
259 let child_completion_coordinator =
260 Arc::new(bamboo_engine::ChildCompletionCoordinator::new(
261 storage.clone(),
262 persistence.clone(),
263 sessions.clone(),
264 agent_runners.clone(),
265 session_event_senders.clone(),
266 agent.clone(),
267 config.clone(),
268 provider_registry.clone(),
269 provider_router.clone(),
270 data_dir.clone(),
271 Some(account_sink.inbox()),
272 ));
273
274 let config_snapshot = config.read().await.clone();
276
277 let mcp_proxy_shutdown = tokio_util::sync::CancellationToken::new();
284 if let Some(broker) = config_snapshot.subagents.broker.clone() {
285 if !broker.endpoint.trim().is_empty() {
286 let backend: std::sync::Arc<dyn bamboo_agent_core::tools::ToolExecutor> =
287 std::sync::Arc::new(bamboo_mcp::executor::McpToolExecutor::new(
288 mcp_manager.clone(),
289 mcp_manager.tool_index(),
290 ));
291 let shutdown = mcp_proxy_shutdown.clone();
292 tokio::spawn(async move {
293 let me = bamboo_broker::AgentRef {
294 session_id: bamboo_broker::ORCHESTRATOR_ID.to_string(),
295 role: Some("orchestrator".into()),
296 };
297 let allowlist =
303 std::sync::Arc::new(bamboo_broker::RoleToolAllowlist::unrestricted());
304 bamboo_broker::serve_mcp_proxy_supervised(
305 &broker.endpoint,
306 me,
307 &broker.token,
308 backend,
309 allowlist,
310 shutdown,
311 )
312 .await;
313 });
314 }
315 }
316 let external_runner =
317 bamboo_engine::external_agents::runtime::build_external_child_runner(&config_snapshot);
318 let spawn_scheduler = build_spawn_scheduler(
319 agent.clone(),
320 child_tools,
321 sessions.clone(),
322 agent_runners.clone(),
323 session_event_senders.clone(),
324 external_runner,
325 Some(provider_router.clone()),
326 Some(child_completion_coordinator.clone()),
327 Some(data_dir.clone()),
328 Some(account_sink.inbox()),
329 );
330
331 let tools_with_task = base_tools.clone();
332
333 let schedule_store = init_schedule_store(&data_dir).await?;
334 let schedule_manager = build_schedule_manager(
335 schedule_store.clone(),
336 agent.clone(),
337 tools_with_task.clone(),
338 sessions.clone(),
339 agent_runners.clone(),
340 session_event_senders.clone(),
341 persistence.clone(),
342 config.clone(),
343 provider_registry.clone(),
344 Some(data_dir.clone()),
345 Some(account_sink.inbox()),
346 );
347
348 bamboo_engine::auto_dream::spawn_auto_dream_task(
349 bamboo_engine::auto_dream::AutoDreamContext {
350 session_store: session_store.clone(),
351 storage: storage.clone(),
352 provider: provider_handle.clone(),
353 config: config.clone(),
354 provider_registry: provider_registry.clone(),
355 },
356 );
357
358 bamboo_engine::gardener::spawn_gardener_task(bamboo_engine::auto_dream::AutoDreamContext {
362 session_store: session_store.clone(),
363 storage: storage.clone(),
364 provider: provider_handle.clone(),
365 config: config.clone(),
366 provider_registry: provider_registry.clone(),
367 });
368
369 let config_for_resolver = config.clone();
370 let subagent_model_resolver: OptionalSubagentModelResolver = {
371 let registry = provider_registry.clone();
372 Some(Arc::new(
373 move |subagent_type: String| -> futures::future::BoxFuture<
374 'static,
375 Option<bamboo_domain::ProviderModelRef>,
376 > {
377 let config_for_resolver = config_for_resolver.clone();
378 let registry = registry.clone();
379 Box::pin(async move {
380 let config_snap = config_for_resolver.read().await.clone();
381 bamboo_engine::model_config_helper::resolve_subagent_model_ref(
382 &config_snap,
383 &config_snap.provider,
384 ®istry,
385 &subagent_type,
386 )
387 })
388 },
389 ))
390 };
391
392 let tools = build_root_tools(
393 tools_with_task.clone(),
394 schedule_store.clone(),
395 schedule_manager.clone(),
396 session_store.clone(),
397 storage.clone(),
398 persistence.clone(),
399 spawn_scheduler.clone(),
400 sessions.clone(),
401 agent_runners.clone(),
402 session_event_senders.clone(),
403 subagent_model_resolver,
404 config.clone(),
405 provider_registry.clone(),
406 config_snapshot.subagents.broker.clone(),
407 );
408
409 child_completion_coordinator
410 .set_root_tools(tools.clone())
411 .await;
412
413 let tool_factory =
414 crate::tools::ToolSurfaceFactory::new(base_tools, tools_with_task, tools);
415
416 let session_repo = bamboo_engine::SessionRepository::new(
417 sessions.clone(),
418 storage.clone(),
419 persistence.clone(),
420 );
421
422 let child_adapter = Arc::new(crate::tools::ChildSessionAdapter {
429 session_store: session_store.clone(),
430 storage: storage.clone(),
431 persistence: persistence.clone(),
432 scheduler: spawn_scheduler.clone(),
433 sessions_cache: sessions.clone(),
434 agent_runners: agent_runners.clone(),
435 session_event_senders: session_event_senders.clone(),
436 subagent_model_resolver: None,
437 config: config.clone(),
438 parent_wait_slots: Arc::new(dashmap::DashMap::new()),
439 });
440 let guardian_spawner: Arc<dyn bamboo_engine::GuardianSpawner> = child_adapter.clone();
441 child_completion_coordinator
444 .set_guardian_spawner(guardian_spawner.clone())
445 .await;
446
447 let bash_resume_hook: Arc<dyn bamboo_engine::BashResumeHook> =
451 child_completion_coordinator.clone();
452
453 Ok(Self {
454 app_data_dir: bamboo_home_dir,
455 config,
456 config_io_lock: Arc::new(tokio::sync::Mutex::new(())),
457 provider: provider_lock,
458 provider_handle,
459 sessions,
460 storage,
461 session_store,
462 session_repo,
463 persistence,
464 spawn_scheduler,
465 child_completion_coordinator,
466 guardian_spawner,
467 bash_resume_hook,
468 schedule_store,
469 schedule_manager,
470 tool_factory,
471 permission_checker,
472 notification_service,
473 cancel_tokens: Arc::new(RwLock::new(HashMap::new())),
474 mcp_proxy_shutdown,
475 skill_manager,
476 mcp_manager,
477 metrics_service,
478 agent_runners,
479 session_event_senders,
480 account_sink,
481 process_registry,
482 metrics_bus: None, agent,
484 provider_registry,
485 provider_router,
486 model_catalog,
487 title_gen_in_flight: Arc::new(dashmap::DashSet::new()),
488 })
489 }
490}