PicoBot/build.rs
xiaoxixi 5501c539fc feat: remove agent run groups, add WebUI agent definition management
- drop agent_run_groups table and group_id/scope_kind/scope_id columns (schema v8)
- remove group_id from AgentExecutionContext and recovery group counters
- flatten TasksPage background tab into a per-run list
- add WebUI Agents page with definition CRUD and inline provider/model
- bump version to 1.11.0
2026-08-13 14:03:01 +08:00

169 lines
5.3 KiB
Rust

use std::env;
use std::fs;
use std::io::Write;
use std::path::Path;
use std::process::Command;
fn main() {
let out_dir = env::var("OUT_DIR").unwrap();
build_webui(Path::new(&out_dir));
println!("cargo:rerun-if-changed=resources/skills");
let skills_dir = Path::new("resources/skills");
let skills_out_dir = Path::new(&out_dir).join("skills");
fs::create_dir_all(&skills_out_dir).unwrap();
println!("cargo:rerun-if-changed=resources/agents");
let agents_dir = Path::new("resources/agents");
let agents_out_dir = Path::new(&out_dir).join("agents");
fs::create_dir_all(&agents_out_dir).unwrap();
let mut agents = Vec::new();
if let Ok(entries) = fs::read_dir(agents_dir) {
for entry in entries.flatten() {
let path = entry.path();
if path.extension().and_then(|e| e.to_str()) != Some("md") {
continue;
}
let agent_name = path.file_stem().unwrap().to_str().unwrap().to_string();
fs::copy(&path, agents_out_dir.join(format!("{agent_name}.md"))).unwrap();
agents.push(agent_name);
}
}
agents.sort();
let mut skills = Vec::new();
if let Ok(entries) = fs::read_dir(skills_dir) {
for entry in entries.flatten() {
let path = entry.path();
if !path.is_dir() {
continue;
}
let skill_name = path.file_name().unwrap().to_str().unwrap().to_string();
let compressed_path = skills_out_dir.join(format!("{}.tar.zst", skill_name));
let compressed = compress_skill_dir(&path);
fs::write(&compressed_path, &compressed).unwrap();
skills.push(skill_name);
}
}
let mut code = String::from(
r#"pub struct EmbeddedSkill {
pub name: &'static str,
pub data: &'static [u8],
}
pub static EMBEDDED_SKILLS: &[EmbeddedSkill] = &[
"#,
);
for name in &skills {
let file_path = skills_out_dir
.join(format!("{}.tar.zst", name))
.to_string_lossy()
.to_string();
code.push_str(&format!(
" EmbeddedSkill {{ name: \"{}\", data: include_bytes!(\"{}\") }},\n",
name, file_path
));
}
code.push_str("];\n");
let generated_path = Path::new(&out_dir).join("embedded_skills.rs");
let mut f = fs::File::create(&generated_path).unwrap();
f.write_all(code.as_bytes()).unwrap();
let mut agent_code = String::from(
r#"pub struct EmbeddedAgent {
pub name: &'static str,
pub content: &'static str,
}
pub static EMBEDDED_AGENTS: &[EmbeddedAgent] = &[
"#,
);
for name in &agents {
let file_path = agents_out_dir
.join(format!("{name}.md"))
.to_string_lossy()
.to_string();
agent_code.push_str(&format!(
" EmbeddedAgent {{ name: \"{name}\", content: include_str!(\"{file_path}\") }},\n",
));
}
agent_code.push_str("];\n");
let agent_path = Path::new(&out_dir).join("embedded_agents.rs");
let mut f = fs::File::create(&agent_path).unwrap();
f.write_all(agent_code.as_bytes()).unwrap();
}
fn build_webui(out_dir: &Path) {
for path in [
"webui/src",
"webui/index.html",
"webui/jsconfig.json",
"webui/package.json",
"webui/package-lock.json",
"webui/svelte.config.js",
"webui/vite.config.js",
"webui/public",
] {
println!("cargo:rerun-if-changed={path}");
}
let webui_dir = Path::new("webui");
let lockfile = webui_dir.join("package-lock.json");
let dependency_stamp = webui_dir
.join("node_modules")
.join(".picobot-package-lock.json");
let lockfile_contents = fs::read(&lockfile).expect("failed to read webui/package-lock.json");
let dependencies_current = fs::read(&dependency_stamp)
.is_ok_and(|stamp| stamp == lockfile_contents)
&& webui_dir.join("node_modules/.bin/vite").is_file();
if !dependencies_current {
run_npm(webui_dir, &["ci", "--no-audit", "--no-fund"], None);
fs::write(&dependency_stamp, &lockfile_contents)
.expect("failed to write WebUI dependency stamp");
}
let webui_out_dir = out_dir.join("webui");
run_npm(webui_dir, &["run", "build"], Some(&webui_out_dir));
}
fn run_npm(webui_dir: &Path, args: &[&str], output_dir: Option<&Path>) {
let mut command = Command::new("npm");
command.args(args).current_dir(webui_dir);
if let Some(output_dir) = output_dir {
command.env("PICOBOT_WEBUI_OUT_DIR", output_dir);
}
let output = command.output().unwrap_or_else(|error| {
panic!("failed to start npm for the WebUI build ({error}); install Node.js 20+ and npm")
});
if !output.status.success() {
panic!(
"WebUI command `npm {}` failed\nstdout:\n{}\nstderr:\n{}",
args.join(" "),
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
}
}
fn compress_skill_dir(dir: &Path) -> Vec<u8> {
let mut buf = Vec::new();
let mut builder = tar::Builder::new(&mut buf);
builder.follow_symlinks(false);
builder
.append_dir_all(dir.file_name().unwrap().to_str().unwrap(), dir)
.unwrap();
drop(builder);
zstd::encode_all(buf.as_slice(), 3).unwrap()
}