use std::path::Path;
use vtcode_core::config::loader::ConfigManager;
use vtcode_core::config::types::SystemPromptMode;
use vtcode_core::prompts::PromptContext;
use vtcode_core::prompts::system::{
compose_system_instruction_text, default_lightweight_prompt, default_system_prompt,
minimal_system_prompt, specialized_system_prompt,
};
fn fallback_base_system_prompt(vt_cfg: Option<&vtcode_core::config::VTCodeConfig>) -> &'static str {
match vt_cfg.map(|cfg| cfg.agent.system_prompt_mode) {
Some(SystemPromptMode::Minimal) => minimal_system_prompt(),
Some(SystemPromptMode::Lightweight) => default_lightweight_prompt(),
Some(SystemPromptMode::Specialized) => specialized_system_prompt(),
_ => default_system_prompt(),
}
}
pub(crate) async fn read_system_prompt(
workspace: &Path,
session_addendum: Option<&str>,
available_tools: &[String],
available_subagents: &[(String, String, bool)],
) -> String {
let mut prompt_context =
PromptContext::from_workspace_tools(workspace, available_tools.iter().cloned());
prompt_context.load_available_skills();
let vt_cfg = ConfigManager::load_from_workspace(workspace)
.ok()
.map(|manager| manager.config().clone());
let mut prompt =
compose_system_instruction_text(workspace, vt_cfg.as_ref(), Some(&prompt_context)).await;
if prompt.is_empty() {
prompt = fallback_base_system_prompt(vt_cfg.as_ref()).to_string();
}
if let Some(addendum) = session_addendum {
let trimmed = addendum.trim();
if !trimmed.is_empty() {
prompt.push_str("\n\n");
prompt.push_str(trimmed);
}
}
if !available_subagents.is_empty() {
let mut section = String::from("## Subagents\n");
section.push_str(
"Delegated child agents available in this session. Treat the main thread as the controller: keep the next blocking step local, and delegate only bounded independent work. Read-only agents may be used proactively when their description matches; write-capable agents require explicit delegation.\n",
);
section.push_str(
"Users can explicitly target one with natural language or an `@agent-<name>` mention.\n",
);
section.push_str(
"If the user explicitly selects a subagent for the task, delegate with `spawn_agent` to that subagent instead of handling the task on the main thread. Join child results back into the parent flow before you depend on them.\n",
);
for (name, description, read_only) in available_subagents {
let suffix = if *read_only {
" Read-only."
} else {
" Explicit delegation only."
};
section.push_str(&format!("- {name}: {description}{suffix}\n"));
}
prompt.push_str("\n\n");
prompt.push_str(section.trim_end());
}
prompt
}
#[cfg(test)]
mod tests {
use super::*;
use vtcode_core::config::VTCodeConfig;
use vtcode_core::config::types::SystemPromptMode;
#[test]
fn test_fallback_base_system_prompt_defaults_to_default() {
assert_eq!(fallback_base_system_prompt(None), default_system_prompt());
}
#[test]
fn test_fallback_base_system_prompt_uses_minimal_mode() {
let mut config = VTCodeConfig::default();
config.agent.system_prompt_mode = SystemPromptMode::Minimal;
assert_eq!(
fallback_base_system_prompt(Some(&config)),
minimal_system_prompt()
);
}
#[test]
fn test_fallback_base_system_prompt_uses_lightweight_mode() {
let mut config = VTCodeConfig::default();
config.agent.system_prompt_mode = SystemPromptMode::Lightweight;
assert_eq!(
fallback_base_system_prompt(Some(&config)),
default_lightweight_prompt()
);
}
#[test]
fn test_fallback_base_system_prompt_uses_specialized_mode() {
let mut config = VTCodeConfig::default();
config.agent.system_prompt_mode = SystemPromptMode::Specialized;
assert_eq!(
fallback_base_system_prompt(Some(&config)),
specialized_system_prompt()
);
}
#[tokio::test]
async fn test_read_system_prompt_includes_explicit_subagent_execution_model() {
let workspace = tempfile::TempDir::new().expect("workspace");
let prompt = read_system_prompt(
workspace.path(),
None,
&[],
&[(
"explorer".to_string(),
"Read-only repo explorer".to_string(),
true,
)],
)
.await;
assert!(prompt.contains("main thread as the controller"));
assert!(prompt.contains("bounded independent work"));
assert!(prompt.contains("Join child results back into the parent flow"));
}
}