Skip to main content

vtcode_core/subagents/
discovery.rs

1use anyhow::{Context, Result};
2use std::path::{Path, PathBuf};
3use vtcode_config::{DiscoveredSubagents, SubagentDiscoveryInput, discover_subagents};
4
5use crate::plugins::components::AgentsHandler;
6use crate::plugins::manifest::PluginManifest;
7use vtcode_commons::VtCodePaths;
8
9// ─── Subagent Discovery ────────────────────────────────────────────────────
10
11/// Discover workspace + plugin primary/subagent specs used by the controller
12/// and by interactive session bootstrap primary-agent selection.
13pub async fn discover_controller_subagents(workspace_root: &Path) -> Result<DiscoveredSubagents> {
14    let plugin_agent_files = discover_plugin_agent_files(workspace_root).await?;
15    let mut input = SubagentDiscoveryInput::new(workspace_root.to_path_buf());
16    input.plugin_agent_files = plugin_agent_files;
17    discover_subagents(&input)
18}
19
20async fn discover_plugin_agent_files(workspace_root: &Path) -> Result<Vec<(String, PathBuf)>> {
21    let mut files = Vec::new();
22    for plugin_root in trusted_plugin_roots(workspace_root) {
23        if !plugin_root.exists() || !plugin_root.is_dir() {
24            continue;
25        }
26
27        let mut plugin_entries = tokio::fs::read_dir(&plugin_root)
28            .await
29            .with_context(|| format!("Failed to read plugin directory {}", plugin_root.display()))?;
30        while let Some(entry) = plugin_entries.next_entry().await? {
31            let path = entry.path();
32            if !path.is_dir() {
33                continue;
34            }
35            let manifest_path = path.join(".vtcode-plugin/plugin.json");
36            if !manifest_path.exists() {
37                continue;
38            }
39
40            let manifest: PluginManifest = serde_json::from_str(
41                &tokio::fs::read_to_string(&manifest_path)
42                    .await
43                    .with_context(|| format!("Failed to read plugin manifest {}", manifest_path.display()))?,
44            )
45            .with_context(|| format!("Failed to parse plugin manifest {}", manifest_path.display()))?;
46            for agent_path in AgentsHandler::process_agents(&path, manifest.agents.clone()).await? {
47                files.push((manifest.name.clone(), agent_path));
48            }
49        }
50    }
51    Ok(files)
52}
53
54fn trusted_plugin_roots(workspace_root: &Path) -> Vec<PathBuf> {
55    let mut roots = Vec::with_capacity(4);
56    if let Some(codex_home) = std::env::var_os("CODEX_HOME").map(PathBuf::from) {
57        roots.push(codex_home.join("plugins"));
58    } else if let Ok(paths) = VtCodePaths::resolve() {
59        roots.push(paths.plugins_dir());
60        roots.push(paths.legacy_dir().join("plugins"));
61    }
62    roots.push(workspace_root.join(".vtcode/plugins"));
63    roots.push(workspace_root.join(".agents/plugins"));
64    roots
65}