use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use byob_exec::{Exec, Mode};
use byob_plugins::mcp::{McpConnector, McpSandbox};
use byob_plugins::{AgentDef, PluginSources, ToolNames, UseSkillTool};
use byob_tools::Registry;
pub struct Loaded {
pub prompt: String,
pub agents: Vec<AgentDef>,
}
pub fn load(tools: &mut Registry, linked: Vec<PathBuf>, workspace: &Path) -> Loaded {
byob_tools::register_builtins(tools);
let paths = byob_store::Paths::resolve();
let mode = Mode::from_env();
match &mode {
Ok(mode) => byob_exec::register(tools, Exec { mode: mode.clone(), data_dir: paths.as_ref().ok().map(|p| p.data_dir.clone()) }),
Err(e) => log::warn!("no run_command/run_python: {e}"),
}
let sandbox = match mode {
Ok(Mode::Sandboxed(sb)) => Some(sb),
_ => None,
};
let mut sources = PluginSources { linked, ..Default::default() };
match &paths {
Ok(paths) => {
sources.roots.push(paths.config_dir.join("plugins"));
sources.roots.extend(byob_store::Settings::load(&paths.settings()).value.plugin_dirs);
}
Err(e) => log::warn!("no config folder ({e}); only --plugin folders are loaded"),
}
let found = byob_plugins::discover(&sources);
for e in &found.errors {
log::warn!("plugin {}: {}", e.path.display(), e.message);
}
let mut names = ToolNames::new(tools.specs().into_iter().map(|s| s.name));
let no_settings = BTreeMap::new();
let python = byob_plugins::default_python();
let mcp_sandbox = sandbox.clone().map(|sandbox| McpSandbox { sandbox, workspace: workspace.to_path_buf() });
for plugin in found.active() {
for p in &plugin.problems {
log::warn!("plugin {}: {p}", plugin.id());
}
for tool in byob_plugins::python_tools_in(plugin, &no_settings, &python, &mut names, sandbox.as_ref()) {
tools.register(tool);
}
let plugin_ref = byob_plugins::PluginRef::new(plugin, &no_settings);
for (server, config) in &plugin.manifest.mcp_servers {
let config = config.with_settings(&plugin_ref.env);
let connected = McpConnector::connect_in(&config, Some(&plugin.dir), mcp_sandbox.as_ref())
.and_then(|c| byob_plugins::connector_tools(&plugin_ref, config.is_local(), Arc::new(c), &mut names));
match connected {
Ok(list) => {
log::info!("plugin {}: MCP server {server}: {} tools", plugin.id(), list.len());
for tool in list {
tools.register(tool);
}
}
Err(e) => log::warn!("plugin {}: MCP server {server}: {e}", plugin.id()),
}
}
log::info!("plugin {} loaded", plugin.id());
}
let skills = found.skills();
let mut prompt = format!("{}\n\n{}", byob_tools::artifact::PROMPT, found.prompt());
if !skills.is_empty() {
prompt.push_str("\n\n");
prompt.push_str(&byob_plugins::skills_prompt(&skills));
tools.register(Arc::new(UseSkillTool::new(skills)));
}
let mine = match &paths {
Ok(paths) => {
let (agents, errors) = byob_plugins::load_agents_dir(&paths.config_dir.join("agents"));
for e in errors {
log::warn!("agent {e}");
}
agents
}
Err(_) => Vec::new(),
};
let agents = byob_plugins::merge_agents([mine, found.agents()]);
if !agents.is_empty() {
log::info!("{} subagent(s): {}", agents.len(), agents.iter().map(|a| a.name.as_str()).collect::<Vec<_>>().join(", "));
}
Loaded { prompt: prompt.trim().to_owned(), agents }
}