walrus_daemon/daemon/
builder.rs1use crate::{
9 DaemonConfig, config,
10 daemon::event::{DaemonEvent, DaemonEventSender},
11 hook::{self, DaemonHook},
12};
13use anyhow::Result;
14use model::ProviderManager;
15use std::{
16 path::{Path, PathBuf},
17 sync::Arc,
18};
19use tokio::sync::RwLock;
20use wcore::{Runtime, ToolRequest};
21
22use super::Daemon;
23
24const SYSTEM_AGENT: &str = include_str!("../../prompts/system.md");
25
26impl Daemon {
27 pub(crate) async fn build(
30 config: &DaemonConfig,
31 config_dir: &Path,
32 event_tx: DaemonEventSender,
33 ) -> Result<Self> {
34 let runtime = Self::build_runtime(config, config_dir, &event_tx).await?;
35 Ok(Self {
36 runtime: Arc::new(RwLock::new(Arc::new(runtime))),
37 config_dir: config_dir.to_path_buf(),
38 event_tx,
39 })
40 }
41
42 pub async fn reload(&self) -> Result<()> {
47 let config = DaemonConfig::load(&self.config_dir.join("walrus.toml"))?;
48 let new_runtime = Self::build_runtime(&config, &self.config_dir, &self.event_tx).await?;
49 *self.runtime.write().await = Arc::new(new_runtime);
50 tracing::info!("daemon reloaded");
51 Ok(())
52 }
53
54 async fn build_runtime(
56 config: &DaemonConfig,
57 config_dir: &Path,
58 event_tx: &DaemonEventSender,
59 ) -> Result<Runtime<ProviderManager, DaemonHook>> {
60 let manager = Self::build_providers(config).await?;
61 let hook = Self::build_hook(config, config_dir).await;
62 let tool_tx = Self::build_tool_sender(event_tx);
63 let mut runtime = Runtime::new(manager, hook, Some(tool_tx)).await;
64 Self::load_agents(&mut runtime, config_dir)?;
65 Ok(runtime)
66 }
67
68 async fn build_providers(config: &DaemonConfig) -> Result<ProviderManager> {
70 let models = config.model.providers.values().cloned().collect::<Vec<_>>();
71 let manager = ProviderManager::from_configs(&models).await?;
72 tracing::info!(
73 "provider manager initialized — active model: {}",
74 manager.active_model()
75 );
76 Ok(manager)
77 }
78
79 async fn build_hook(config: &DaemonConfig, config_dir: &Path) -> DaemonHook {
81 let memory = memory::InMemory::new();
82 tracing::info!("using in-memory backend");
83
84 let skills_dir = config_dir.join(config::SKILLS_DIR);
85 let skills = hook::skill::SkillHandler::load(skills_dir).unwrap_or_else(|e| {
86 tracing::warn!("failed to load skills: {e}");
87 hook::skill::SkillHandler::load(PathBuf::new()).expect("empty skill handler")
88 });
89
90 let mcp_servers = config.mcp_servers.values().cloned().collect::<Vec<_>>();
91 let mcp_handler = hook::mcp::McpHandler::load(&mcp_servers).await;
92
93 DaemonHook::new(memory, skills, mcp_handler)
94 }
95
96 fn build_tool_sender(event_tx: &DaemonEventSender) -> wcore::ToolSender {
102 let (tool_tx, mut tool_rx) = tokio::sync::mpsc::unbounded_channel::<ToolRequest>();
103 let event_tx = event_tx.clone();
104 tokio::spawn(async move {
105 while let Some(req) = tool_rx.recv().await {
106 if event_tx.send(DaemonEvent::ToolCall(req)).is_err() {
107 break;
108 }
109 }
110 });
111 tool_tx
112 }
113
114 fn load_agents(
116 runtime: &mut Runtime<ProviderManager, DaemonHook>,
117 config_dir: &Path,
118 ) -> Result<()> {
119 let agents = crate::config::load_agents_dir(&config_dir.join(config::AGENTS_DIR))?;
120 runtime.add_agent(wcore::parse_agent_md(SYSTEM_AGENT)?);
121 for agent in agents {
122 tracing::info!("registered agent '{}'", agent.name);
123 runtime.add_agent(agent);
124 }
125 Ok(())
126 }
127}