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 bamboo_agent_core::workspace_state::set_default_workspace_provider(|| {
113 bamboo_llm::Config::from_data_dir(Some(bamboo_config::paths::bamboo_dir()))
114 .get_default_work_area_path()
115 });
116
117 let data_dir = bamboo_home_dir.clone();
118 let (session_store, storage) = init_storage(&data_dir).await?;
119 let persistence = Arc::new(LockedSessionStore::new(storage.clone()));
120
121 let sessions: bamboo_engine::SessionCache = Arc::new(dashmap::DashMap::new());
123
124 let config = Arc::new(RwLock::new(config));
125
126 let permission_checker = load_permission_checker(&bamboo_home_dir).await;
127 let notification_service = Arc::new(bamboo_notification::NotificationService::new(
128 bamboo_home_dir.join("notification_preferences.json"),
129 ));
130 let mcp_manager = init_mcp_manager(config.clone());
131 let skill_manager = init_skill_manager(&data_dir).await;
132 let metrics_service = init_metrics_service(&data_dir).await?;
133
134 let startup_sessions = {
135 let entries = session_store.list_index_entries().await;
136 let mut sessions = Vec::new();
137 for entry in entries {
138 if let Some(session) = session_store
139 .load_session(&entry.id)
140 .await
141 .map_err(AppError::StorageError)?
142 {
143 sessions.push(session);
144 }
145 }
146 sessions
147 };
148 metrics_service
149 .reconcile_startup_sessions(startup_sessions, &[])
150 .await
151 .map_err(|error| {
152 AppError::InternalError(anyhow::anyhow!(
153 "Failed to reconcile stale metrics state on startup: {error}"
154 ))
155 })?;
156
157 let agent_runners: Arc<RwLock<HashMap<String, AgentRunner>>> =
158 Arc::new(RwLock::new(HashMap::new()));
159 spawn_runner_cleanup_task(agent_runners.clone(), None);
160
161 let process_registry = Arc::new(ProcessRegistry::new());
162 let (provider_lock, provider_handle) = build_provider_handles(provider);
163
164 let config_snapshot = config.read().await;
166 let provider_registry = match bamboo_llm::ProviderRegistry::from_config(
167 &config_snapshot,
168 bamboo_home_dir.clone(),
169 )
170 .await
171 {
172 Ok(registry) => Arc::new(registry),
173 Err(e) => {
174 tracing::error!("Failed to create provider registry: {}", e);
175 Arc::new(
176 bamboo_llm::ProviderRegistry::from_config(
177 &Config::default(),
178 bamboo_home_dir.clone(),
179 )
180 .await
181 .expect("Cannot create even an empty provider registry"),
182 )
183 }
184 };
185 drop(config_snapshot);
186
187 let provider_router = Arc::new(bamboo_llm::ProviderModelRouter::new(
188 provider_registry.clone(),
189 ));
190 let model_catalog = Arc::new(bamboo_llm::ModelCatalogService::new(
191 provider_registry.clone(),
192 ));
193
194 let base_tools = build_base_tools(
195 config.clone(),
196 permission_checker.clone(),
197 mcp_manager.clone(),
198 skill_manager.clone(),
199 storage.clone(),
200 persistence.clone(),
201 sessions.clone(),
202 bamboo_home_dir.clone(),
203 );
204
205 let session_event_senders: Arc<RwLock<HashMap<String, broadcast::Sender<AgentEvent>>>> =
207 Arc::new(RwLock::new(HashMap::new()));
208
209 let account_sink = bamboo_engine::events::AccountEventSink::new(data_dir.join("events"))
212 .map_err(|e| {
213 AppError::InternalError(anyhow::anyhow!(
214 "failed to initialize account change-feed journal: {e}"
215 ))
216 })?;
217
218 let child_tools: Arc<dyn bamboo_agent_core::tools::ToolExecutor> = base_tools.clone();
221
222 let agent = Arc::new(
227 bamboo_engine::Agent::builder()
228 .storage(storage.clone())
229 .persistence(persistence.clone())
230 .attachment_reader(session_store.clone())
231 .skill_manager(skill_manager.clone())
232 .metrics_collector(metrics_service.collector())
233 .config(config.clone())
234 .provider(provider_handle.clone())
235 .default_tools(base_tools.clone())
236 .build()
237 .expect("agent runtime should be fully configured"),
238 );
239
240 let child_completion_coordinator =
241 Arc::new(bamboo_engine::ChildCompletionCoordinator::new(
242 storage.clone(),
243 persistence.clone(),
244 sessions.clone(),
245 agent_runners.clone(),
246 session_event_senders.clone(),
247 agent.clone(),
248 config.clone(),
249 provider_registry.clone(),
250 provider_router.clone(),
251 data_dir.clone(),
252 Some(account_sink.inbox()),
253 ));
254
255 let config_snapshot = config.read().await.clone();
257
258 if let Some(broker) = config_snapshot.subagents.broker.clone() {
262 if !broker.endpoint.trim().is_empty() {
263 let backend: std::sync::Arc<dyn bamboo_agent_core::tools::ToolExecutor> =
264 std::sync::Arc::new(bamboo_mcp::executor::McpToolExecutor::new(
265 mcp_manager.clone(),
266 mcp_manager.tool_index(),
267 ));
268 tokio::spawn(async move {
269 let me = bamboo_broker::AgentRef {
270 session_id: bamboo_broker::ORCHESTRATOR_ID.to_string(),
271 role: Some("orchestrator".into()),
272 };
273 if let Err(e) =
274 bamboo_broker::serve_mcp_proxy(&broker.endpoint, me, &broker.token, backend)
275 .await
276 {
277 tracing::warn!("MCP proxy service ended: {e}");
278 }
279 });
280 }
281 }
282 let external_runner =
283 bamboo_engine::external_agents::runtime::build_external_child_runner(&config_snapshot);
284 let spawn_scheduler = build_spawn_scheduler(
285 agent.clone(),
286 child_tools,
287 sessions.clone(),
288 agent_runners.clone(),
289 session_event_senders.clone(),
290 external_runner,
291 Some(provider_router.clone()),
292 Some(child_completion_coordinator.clone()),
293 Some(data_dir.clone()),
294 Some(account_sink.inbox()),
295 );
296
297 let tools_with_task = base_tools.clone();
298
299 let schedule_store = init_schedule_store(&data_dir).await?;
300 let schedule_manager = build_schedule_manager(
301 schedule_store.clone(),
302 agent.clone(),
303 tools_with_task.clone(),
304 sessions.clone(),
305 agent_runners.clone(),
306 session_event_senders.clone(),
307 persistence.clone(),
308 config.clone(),
309 provider_registry.clone(),
310 Some(data_dir.clone()),
311 Some(account_sink.inbox()),
312 );
313
314 bamboo_engine::auto_dream::spawn_auto_dream_task(
315 bamboo_engine::auto_dream::AutoDreamContext {
316 session_store: session_store.clone(),
317 storage: storage.clone(),
318 provider: provider_handle.clone(),
319 config: config.clone(),
320 provider_registry: provider_registry.clone(),
321 },
322 );
323
324 bamboo_engine::gardener::spawn_gardener_task(bamboo_engine::auto_dream::AutoDreamContext {
328 session_store: session_store.clone(),
329 storage: storage.clone(),
330 provider: provider_handle.clone(),
331 config: config.clone(),
332 provider_registry: provider_registry.clone(),
333 });
334
335 let config_for_resolver = config.clone();
336 let subagent_model_resolver: OptionalSubagentModelResolver = {
337 let registry = provider_registry.clone();
338 Some(Arc::new(
339 move |subagent_type: String| -> futures::future::BoxFuture<
340 'static,
341 Option<bamboo_domain::ProviderModelRef>,
342 > {
343 let config_for_resolver = config_for_resolver.clone();
344 let registry = registry.clone();
345 Box::pin(async move {
346 let config_snap = config_for_resolver.read().await.clone();
347 bamboo_engine::model_config_helper::resolve_subagent_model_ref(
348 &config_snap,
349 &config_snap.provider,
350 ®istry,
351 &subagent_type,
352 )
353 })
354 },
355 ))
356 };
357
358 let tools = build_root_tools(
359 tools_with_task.clone(),
360 schedule_store.clone(),
361 schedule_manager.clone(),
362 session_store.clone(),
363 storage.clone(),
364 persistence.clone(),
365 spawn_scheduler.clone(),
366 sessions.clone(),
367 agent_runners.clone(),
368 session_event_senders.clone(),
369 subagent_model_resolver,
370 config.clone(),
371 provider_registry.clone(),
372 config_snapshot.subagents.broker.clone(),
373 );
374
375 child_completion_coordinator
376 .set_root_tools(tools.clone())
377 .await;
378
379 let tool_factory =
380 crate::tools::ToolSurfaceFactory::new(base_tools, tools_with_task, tools);
381
382 let session_repo = bamboo_engine::SessionRepository::new(
383 sessions.clone(),
384 storage.clone(),
385 persistence.clone(),
386 );
387
388 let child_adapter = Arc::new(crate::tools::ChildSessionAdapter {
395 session_store: session_store.clone(),
396 storage: storage.clone(),
397 persistence: persistence.clone(),
398 scheduler: spawn_scheduler.clone(),
399 sessions_cache: sessions.clone(),
400 agent_runners: agent_runners.clone(),
401 session_event_senders: session_event_senders.clone(),
402 subagent_model_resolver: None,
403 config: config.clone(),
404 parent_wait_slots: Arc::new(dashmap::DashMap::new()),
405 });
406 let guardian_spawner: Arc<dyn bamboo_engine::GuardianSpawner> = child_adapter.clone();
407 child_completion_coordinator
410 .set_guardian_spawner(guardian_spawner.clone())
411 .await;
412
413 let bash_resume_hook: Arc<dyn bamboo_engine::BashResumeHook> =
417 child_completion_coordinator.clone();
418
419 Ok(Self {
420 app_data_dir: bamboo_home_dir,
421 config,
422 provider: provider_lock,
423 provider_handle,
424 sessions,
425 storage,
426 session_store,
427 session_repo,
428 persistence,
429 spawn_scheduler,
430 child_completion_coordinator,
431 guardian_spawner,
432 bash_resume_hook,
433 schedule_store,
434 schedule_manager,
435 tool_factory,
436 permission_checker,
437 notification_service,
438 cancel_tokens: Arc::new(RwLock::new(HashMap::new())),
439 skill_manager,
440 mcp_manager,
441 metrics_service,
442 agent_runners,
443 session_event_senders,
444 account_sink,
445 process_registry,
446 metrics_bus: None, agent,
448 provider_registry,
449 provider_router,
450 model_catalog,
451 title_gen_in_flight: Arc::new(dashmap::DashSet::new()),
452 })
453 }
454}