Skip to main content

robit_agent/
bootstrap.rs

1//! Bootstrap module — common setup for loading skills and creating tools.
2//!
3//! This module provides reusable functions for frontends (robit-tui, robit-gui, etc.)
4//! to avoid duplicating skill loading and tool creation code.
5
6use std::path::PathBuf;
7use std::sync::Arc;
8
9use robit_ai::config::RobitConfig;
10
11use crate::skill::{load_skills, Skill, SkillRegistry};
12use crate::tool::bash::BashTool;
13use crate::tool::edit::EditTool;
14use crate::tool::find::FindTool;
15use crate::tool::grep::GrepTool;
16use crate::tool::load_skill::LoadSkillTool;
17use crate::tool::ls::LsTool;
18use crate::tool::memory::{ForgetTool, ListMemoriesTool, MemorizeTool, RecallTool};
19use crate::tool::read::ReadTool;
20use crate::tool::search_history::SearchHistoryTool;
21use crate::tool::write::WriteTool;
22use crate::tool::ToolRegistry;
23use crate::SkillLoadError;
24
25// ============================================================================
26// BootstrapResult
27// ============================================================================
28
29/// Result of bootstrapping skills and tools.
30pub struct BootstrapResult {
31    /// The skill registry, ready for use.
32    pub skill_registry: Arc<SkillRegistry>,
33    /// The tool registry, ready for use.
34    pub tool_registry: Arc<ToolRegistry>,
35    /// Total skills loaded (before filtering by enabled_skills).
36    pub total_skills_loaded: usize,
37    /// Any errors that occurred during skill loading (non-fatal).
38    pub skill_load_errors: Vec<SkillLoadError>,
39}
40
41// ============================================================================
42// Bootstrap functions
43// ============================================================================
44
45/// Bootstrap both skills and tools in one call.
46///
47/// This is the main entry point for frontends. It:
48/// 1. Loads skills from global and project directories
49/// 2. Filters skills by config.enabled_skills
50/// 3. Creates SkillRegistry
51/// 4. Creates ToolRegistry with all standard tools
52///
53/// Returns a BootstrapResult with both registries and metadata.
54pub fn bootstrap(
55    config: &RobitConfig,
56    working_dir: &PathBuf,
57    base_tool_names: &[&str],
58) -> BootstrapResult {
59    let (skills, skill_load_errors) = load_all_skills(working_dir);
60    let total_skills_loaded = skills.len();
61
62    let filtered_skills = filter_skills_by_config(skills, config);
63
64    let skill_registry = Arc::new(SkillRegistry::new(filtered_skills, base_tool_names));
65    let tool_registry = Arc::new(create_tools_from_config(config, Arc::clone(&skill_registry)));
66
67    BootstrapResult {
68        skill_registry,
69        tool_registry,
70        total_skills_loaded,
71        skill_load_errors,
72    }
73}
74
75/// Load skills from standard locations (global ~/.robit/skills and project .robit/skills).
76///
77/// Returns (loaded_skills, load_errors).
78pub fn load_all_skills(working_dir: &PathBuf) -> (Vec<Skill>, Vec<SkillLoadError>) {
79    let global_skills_dir = dirs::home_dir().map(|h| h.join(".robit/skills"));
80    let project_skills_dir = Some(working_dir.join(".robit/skills"));
81
82    load_skills(global_skills_dir, project_skills_dir)
83}
84
85/// Filter skills by the enabled_skills list in config, if present.
86pub fn filter_skills_by_config(skills: Vec<Skill>, config: &RobitConfig) -> Vec<Skill> {
87    let enabled_skills = config.app.as_ref().and_then(|a| a.enabled_skills.as_ref());
88
89    match enabled_skills {
90        Some(list) => skills
91            .into_iter()
92            .filter(|s| list.contains(&s.frontmatter.name))
93            .collect(),
94        None => skills,
95    }
96}
97
98/// Create a ToolRegistry with tools filtered by config.enabled_tools.
99///
100/// - If enabled_tools is not specified: all tools are registered
101/// - If enabled_tools is specified: only register tools in the list
102/// - `read`, `load_skill`, and memory tools are always registered (required for basic functionality)
103pub fn create_tools_from_config(
104    config: &RobitConfig,
105    skill_registry: Arc<SkillRegistry>,
106) -> ToolRegistry {
107    let mut tools = ToolRegistry::new();
108    let context_config = config.app.as_ref().and_then(|a| a.context.as_ref());
109    let max_lines = context_config.and_then(|c| c.max_output_lines).unwrap_or(500);
110    let max_bytes = context_config
111        .and_then(|c| c.max_output_bytes)
112        .unwrap_or(51200);
113
114    // Always register read, load_skill, memory, and history tools (required for basic functionality)
115    tools.register(ReadTool::new(max_lines, max_bytes));
116    tools.register(LoadSkillTool::new(skill_registry));
117    tools.register(MemorizeTool::new());
118    tools.register(RecallTool::new());
119    tools.register(ForgetTool::new());
120    tools.register(ListMemoriesTool::new());
121    tools.register(SearchHistoryTool::new());
122
123    // Get enabled tools from config
124    let enabled_tools = config.app.as_ref().and_then(|a| a.enabled_tools.as_ref());
125
126    match enabled_tools {
127        Some(list) => {
128            // Configured: only register specified tools (always available tools already registered)
129            for tool_name in list {
130                match tool_name.as_str() {
131                    "read" => {} // already registered
132                    "load_skill" => {} // already registered
133                    "memorize" => {} // already registered
134                    "recall" => {} // already registered
135                    "forget" => {} // already registered
136                    "list_memories" => {} // already registered
137                    "search_history" => {} // already registered
138                    "bash" => tools.register(BashTool::new(max_bytes)),
139                    "write" => tools.register(WriteTool::new()),
140                    "edit" => tools.register(EditTool::new()),
141                    "ls" => tools.register(LsTool::new()),
142                    "find" => tools.register(FindTool::new(max_bytes)),
143                    "grep" => tools.register(GrepTool::new(max_lines, max_bytes)),
144                    _ => tracing::warn!("Unknown tool in enabled_tools config: {}", tool_name),
145                }
146            }
147        }
148        None => {
149            // Not configured: register all remaining tools
150            tools.register(BashTool::new(max_bytes));
151            tools.register(WriteTool::new());
152            tools.register(EditTool::new());
153            tools.register(LsTool::new());
154            tools.register(FindTool::new(max_bytes));
155            tools.register(GrepTool::new(max_lines, max_bytes));
156        }
157    }
158
159    tools
160}
161
162/// Log any skill load errors as warnings.
163///
164/// Convenience function for frontends to log errors without duplicating code.
165pub fn log_skill_errors(errors: &[SkillLoadError]) {
166    for err in errors {
167        tracing::warn!("Skill load error: {:?}", err);
168    }
169}