feat: add template setup executable support
Templates can now include a `setup` file that runs in the workdir before agent execution starts. Used for workspace initialization like pulling tool binaries or installing dependencies.
This commit is contained in:
16
src/agent.rs
16
src/agent.rs
@@ -327,6 +327,22 @@ async fn agent_loop(
|
||||
ensure_workspace(&exec, &workdir).await;
|
||||
let _ = tokio::fs::write(format!("{}/requirement.md", workdir), &requirement).await;
|
||||
|
||||
// Run template setup if present
|
||||
if let Some(ref tid) = template_id {
|
||||
let template_dir = if template::is_repo_template(tid) {
|
||||
template::extract_repo_template(tid, mgr.template_repo.as_ref())
|
||||
.await
|
||||
.ok()
|
||||
} else {
|
||||
Some(std::path::Path::new(template::templates_dir()).join(tid))
|
||||
};
|
||||
if let Some(ref tdir) = template_dir {
|
||||
if let Err(e) = template::run_setup(tdir, &workdir).await {
|
||||
tracing::error!("Template setup failed for {}: {}", tid, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let instructions = if let Some(ref t) = loaded_template {
|
||||
t.instructions.clone()
|
||||
} else {
|
||||
|
||||
@@ -402,6 +402,20 @@ pub async fn extract_repo_template(template_id: &str, repo_cfg: Option<&Template
|
||||
}
|
||||
}
|
||||
|
||||
// Make setup executable if present
|
||||
let setup_file = dest.join("setup");
|
||||
if setup_file.is_file() {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
if let Ok(meta) = tokio::fs::metadata(&setup_file).await {
|
||||
let mut perms = meta.permissions();
|
||||
perms.set_mode(perms.mode() | 0o111);
|
||||
let _ = tokio::fs::set_permissions(&setup_file, perms).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tracing::info!("Extracted repo template '{}' to {}", template_id, dest.display());
|
||||
Ok(dest)
|
||||
}
|
||||
@@ -483,6 +497,44 @@ pub async fn select_template(llm: &LlmClient, requirement: &str, repo_cfg: Optio
|
||||
|
||||
// --- Template loading ---
|
||||
|
||||
/// Run the template's `setup` executable with cwd set to workdir.
|
||||
/// The template directory is read-only; setup runs in the workdir to initialize
|
||||
/// the workspace environment (e.g. pulling binaries, installing deps).
|
||||
pub async fn run_setup(template_dir: &Path, workdir: &str) -> anyhow::Result<()> {
|
||||
let setup = template_dir.join("setup");
|
||||
if !setup.exists() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
tracing::info!("Running template setup in workdir: {}", workdir);
|
||||
let output = tokio::process::Command::new(&setup)
|
||||
.current_dir(workdir)
|
||||
.output()
|
||||
.await
|
||||
.map_err(|e| anyhow::anyhow!("Failed to run setup: {}", e))?;
|
||||
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
|
||||
if !stdout.is_empty() {
|
||||
tracing::info!("setup stdout: {}", stdout.trim_end());
|
||||
}
|
||||
if !stderr.is_empty() {
|
||||
tracing::warn!("setup stderr: {}", stderr.trim_end());
|
||||
}
|
||||
|
||||
if !output.status.success() {
|
||||
anyhow::bail!(
|
||||
"Template setup failed (exit {}): {}",
|
||||
output.status.code().unwrap_or(-1),
|
||||
stderr.trim_end()
|
||||
);
|
||||
}
|
||||
|
||||
tracing::info!("Template setup completed successfully");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Copy template contents to workdir (excluding template.json, tools/, kb/, INSTRUCTIONS.md).
|
||||
pub async fn apply_template(template_dir: &Path, workdir: &str) -> anyhow::Result<()> {
|
||||
if !template_dir.is_dir() {
|
||||
@@ -507,6 +559,7 @@ async fn copy_dir_recursive(src: &Path, dst: &Path) -> anyhow::Result<()> {
|
||||
|| name_str == "tools"
|
||||
|| name_str == "kb"
|
||||
|| name_str == "examples"
|
||||
|| name_str == "setup"
|
||||
|| name_str == "INSTRUCTIONS.md")
|
||||
{
|
||||
continue;
|
||||
|
||||
Reference in New Issue
Block a user