1include!(concat!(env!("OUT_DIR"), "/bundled_agents.rs"));
15
16use std::path::Path;
17
18#[derive(Debug, Clone, PartialEq, Eq)]
21pub enum AgentAction {
22 Install,
24 Update { from: String },
26 UpToDate,
28}
29
30impl AgentAction {
31 pub fn is_change(&self) -> bool {
34 !matches!(self, Self::UpToDate)
35 }
36
37 pub fn label(&self, to: &str) -> String {
39 match self {
40 Self::Install => format!("install {to}"),
41 Self::Update { from } => format!("update {from} → {to}"),
42 Self::UpToDate => "up to date".to_string(),
43 }
44 }
45}
46
47pub fn installed_version(agents_dir: &Path, name: &str) -> Option<String> {
55 let manifest = std::fs::read_to_string(agents_dir.join(name).join("agent.leviath")).ok()?;
56 leviath_core::manifest::parse_manifest(&manifest)
57 .ok()
58 .map(|bp| bp.version)
59}
60
61pub fn plan_agent_actions(agents_dir: &Path) -> Vec<(&'static BundledAgent, AgentAction)> {
71 BUNDLED_AGENTS
72 .iter()
73 .map(|agent| {
74 let action = match installed_version(agents_dir, agent.name) {
75 None => AgentAction::Install,
76 Some(v) if v == agent.version => AgentAction::UpToDate,
77 Some(from) => AgentAction::Update { from },
78 };
79 (agent, action)
80 })
81 .collect()
82}
83
84pub fn install_bundled(agent: &BundledAgent, agents_dir: &Path) -> anyhow::Result<()> {
92 let dest = agents_dir.join(agent.name);
93 if dest.exists() {
94 std::fs::remove_dir_all(&dest)?;
95 }
96 for (rel, contents) in agent.files {
97 let parent = match rel.rsplit_once('/') {
103 Some((dir, _)) => dest.join(dir),
104 None => dest.clone(),
105 };
106 std::fs::create_dir_all(&parent)?;
107 std::fs::write(dest.join(rel), contents)?;
108 }
109 Ok(())
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115
116 #[test]
120 fn every_bundled_agent_has_a_name_version_and_manifest() {
121 assert!(
122 !BUNDLED_AGENTS.is_empty(),
123 "the binary shipped with no blueprints -- build.rs found no agents/ directory"
124 );
125 for agent in BUNDLED_AGENTS {
126 assert!(!agent.name.is_empty(), "a bundled agent has an empty name");
127 assert!(
128 !agent.version.is_empty(),
129 "bundled agent {} has an empty version",
130 agent.name
131 );
132 assert!(
133 agent.files.iter().any(|(rel, _)| *rel == "agent.leviath"),
134 "bundled agent {} has no agent.leviath",
135 agent.name
136 );
137 for (rel, contents) in agent.files {
138 assert!(
139 !rel.is_empty(),
140 "bundled agent {} has an empty path",
141 agent.name
142 );
143 assert!(
144 !contents.is_empty(),
145 "bundled agent {} has an empty file {rel}",
146 agent.name
147 );
148 }
149 }
150 }
151
152 #[test]
166 fn a_tool_script_shared_by_several_agents_is_identical_in_all_of_them() {
167 use std::collections::HashMap;
168
169 let mut first_seen: HashMap<&str, (&str, &str)> = HashMap::new();
171 for agent in BUNDLED_AGENTS {
172 for (rel, contents) in agent.files {
173 let Some(filename) = rel.strip_prefix("tools/") else {
174 continue;
175 };
176 match first_seen.get(filename) {
177 Some((other, expected)) => assert!(
178 expected == contents,
179 "tools/{filename} differs between bundled agents {other} and {} - \
180 a change to one copy was not applied to the others",
181 agent.name
182 ),
183 None => {
184 first_seen.insert(filename, (agent.name, contents));
185 }
186 }
187 }
188 }
189 assert!(
192 !first_seen.is_empty(),
193 "no bundled agent ships a tools/ script - this invariant is not being tested"
194 );
195 }
196
197 #[test]
198 fn every_bundled_manifest_parses_and_agrees_with_its_recorded_version() {
199 for agent in BUNDLED_AGENTS {
202 let manifest = agent
203 .files
204 .iter()
205 .find(|(rel, _)| *rel == "agent.leviath")
206 .map(|(_, c)| *c)
207 .expect("checked above");
208 let parsed = leviath_core::manifest::parse_manifest(manifest);
214 assert!(
215 parsed.is_ok(),
216 "bundled agent {} does not parse",
217 agent.name
218 );
219 let blueprint = parsed.expect("asserted Ok just above");
220 assert_eq!(blueprint.version, agent.version);
221 assert_eq!(blueprint.name, agent.name);
222 }
223 }
224
225 #[test]
240 fn every_stage_tool_name_resolves_and_every_permission_names_a_granted_tool() {
241 const SUBAGENT: &[&str] = &[
243 "spawn_agent",
244 "check_agent",
245 "wait_for_agent",
246 "send_to_agent",
247 "kill_agent",
248 ];
249 let builtin = leviath_tools::BuiltinTools::new(leviath_tools::ToolContext::new(
250 std::path::PathBuf::from("."),
251 ))
252 .names();
253
254 for agent in BUNDLED_AGENTS {
255 let scripts: Vec<&str> = agent
257 .files
258 .iter()
259 .filter_map(|(rel, _)| rel.strip_prefix("tools/"))
260 .filter_map(|f| f.strip_suffix(".rhai"))
261 .collect();
262 let manifest = agent
263 .files
264 .iter()
265 .find(|(rel, _)| *rel == "agent.leviath")
266 .map(|(_, c)| *c)
267 .expect("every bundled agent has a manifest");
268 let parsed = leviath_core::manifest::parse_manifest(manifest);
269 assert!(
270 parsed.is_ok(),
271 "bundled agent {} does not parse",
272 agent.name
273 );
274 let blueprint = parsed.expect("asserted Ok just above");
275
276 for stage in &blueprint.stages {
277 for tool in &stage.available_tools {
278 let known = tool.contains("__")
282 || builtin.iter().any(|b| b == tool)
283 || SUBAGENT.contains(&tool.as_str())
284 || scripts.contains(&tool.as_str());
285 assert!(
286 known,
287 "{}: stage '{}' grants '{}', which is not a built-in, a sub-agent tool, or one of this agent's own tools/*.rhai",
288 agent.name, stage.name, tool
289 );
290 }
291 for granted in stage.tool_permissions.keys() {
292 assert!(
293 stage.available_tools.contains(granted),
294 "{}: stage '{}' sets a permission for '{}', which it does not grant in available_tools",
295 agent.name,
296 stage.name,
297 granted
298 );
299 }
300 }
301 }
302 }
303
304 #[test]
307 fn the_stage_tool_invariant_rejects_a_typo_and_an_orphan_permission() {
308 let builtin = leviath_tools::BuiltinTools::new(leviath_tools::ToolContext::new(
309 std::path::PathBuf::from("."),
310 ))
311 .names();
312 assert!(
313 !builtin.iter().any(|b| b == "raed_file"),
314 "a misspelled tool must not resolve"
315 );
316
317 let manifest = r#"
318[agent]
319name = "x"
320version = "0.1.0"
321description = "x"
322
323[stages.only]
324model = { provider = "anthropic", model = "m" }
325available_tools = ["read_file"]
326
327[stages.only.tool_permissions]
328write_file = "allow"
329"#;
330 let bp = leviath_core::manifest::parse_manifest(manifest)
331 .expect("the fixture parses; it is the invariant that should object");
332 let stage = &bp.stages[0];
333 assert!(
334 !stage.available_tools.contains(&"write_file".to_string()),
335 "the orphan-permission arm has something to catch"
336 );
337 }
338
339 #[test]
340 fn bundled_agent_names_are_unique() {
341 let mut names: Vec<&str> = BUNDLED_AGENTS.iter().map(|a| a.name).collect();
342 names.sort_unstable();
343 let count = names.len();
344 names.dedup();
345 assert_eq!(count, names.len(), "duplicate bundled agent names");
346 }
347
348 #[test]
351 fn installed_version_reads_a_manifest() {
352 let dir = tempfile::tempdir().unwrap();
353 let agent = &BUNDLED_AGENTS[0];
354 install_bundled(agent, dir.path()).unwrap();
355
356 assert_eq!(
357 installed_version(dir.path(), agent.name).as_deref(),
358 Some(agent.version)
359 );
360 }
361
362 #[test]
363 fn installed_version_is_none_when_nothing_is_installed() {
364 let dir = tempfile::tempdir().unwrap();
365 assert!(installed_version(dir.path(), "not-installed").is_none());
366 }
367
368 #[test]
369 fn installed_version_is_none_for_an_unparseable_manifest() {
370 let dir = tempfile::tempdir().unwrap();
373 std::fs::create_dir_all(dir.path().join("broken")).unwrap();
374 std::fs::write(
375 dir.path().join("broken/agent.leviath"),
376 "not valid toml {{{",
377 )
378 .unwrap();
379
380 assert!(installed_version(dir.path(), "broken").is_none());
381 }
382
383 #[test]
386 fn plan_offers_to_install_everything_into_an_empty_dir() {
387 let dir = tempfile::tempdir().unwrap();
388
389 let plan = plan_agent_actions(dir.path());
390
391 assert_eq!(plan.len(), BUNDLED_AGENTS.len());
392 for (agent, action) in &plan {
393 assert_eq!(*action, AgentAction::Install);
394 assert!(action.is_change());
395 assert_eq!(
396 action.label(agent.version),
397 format!("install {}", agent.version)
398 );
399 }
400 }
401
402 #[test]
403 fn plan_reports_up_to_date_after_installing() {
404 let dir = tempfile::tempdir().unwrap();
405 for agent in BUNDLED_AGENTS {
406 install_bundled(agent, dir.path()).unwrap();
407 }
408
409 let plan = plan_agent_actions(dir.path());
410
411 for (agent, action) in &plan {
412 assert_eq!(*action, AgentAction::UpToDate, "{}", agent.name);
413 assert!(!action.is_change());
414 assert_eq!(action.label(agent.version), "up to date");
415 }
416 }
417
418 #[test]
419 fn plan_reports_an_update_when_the_installed_version_differs() {
420 let dir = tempfile::tempdir().unwrap();
421 let agent = &BUNDLED_AGENTS[0];
422 install_bundled(agent, dir.path()).unwrap();
423 let manifest_path = dir.path().join(agent.name).join("agent.leviath");
425 let manifest = std::fs::read_to_string(&manifest_path).unwrap();
426 let bumped = manifest.replacen(
427 &format!("version = \"{}\"", agent.version),
428 "version = \"9.9.9\"",
429 1,
430 );
431 std::fs::write(&manifest_path, bumped).unwrap();
432
433 let plan = plan_agent_actions(dir.path());
434 let (_, action) = plan
435 .iter()
436 .find(|(a, _)| a.name == agent.name)
437 .expect("the bundled agent is in the plan");
438
439 assert_eq!(
440 *action,
441 AgentAction::Update {
442 from: "9.9.9".to_string()
443 }
444 );
445 assert!(action.is_change());
446 assert_eq!(
447 action.label(agent.version),
448 format!("update 9.9.9 → {}", agent.version)
449 );
450 }
451
452 #[test]
455 fn install_writes_every_file_including_nested_ones() {
456 let dir = tempfile::tempdir().unwrap();
457 for agent in BUNDLED_AGENTS {
462 install_bundled(agent, dir.path()).unwrap();
463 for (rel, contents) in agent.files {
464 let written = std::fs::read_to_string(dir.path().join(agent.name).join(rel));
465 assert!(written.is_ok(), "{}/{rel} was not written", agent.name);
466 assert_eq!(written.expect("asserted Ok just above"), *contents);
467 }
468 }
469 assert!(
470 BUNDLED_AGENTS
471 .iter()
472 .any(|a| a.files.iter().any(|(rel, _)| rel.contains('/'))),
473 "no bundled blueprint has a nested file, so install's mkdir path is untested"
474 );
475 }
476
477 #[test]
478 fn install_replaces_an_existing_tree_and_drops_stale_files() {
479 let dir = tempfile::tempdir().unwrap();
480 let agent = &BUNDLED_AGENTS[0];
481 install_bundled(agent, dir.path()).unwrap();
482 let stale = dir
483 .path()
484 .join(agent.name)
485 .join("stale-from-an-older-version");
486 std::fs::write(&stale, "leftover").unwrap();
487
488 install_bundled(agent, dir.path()).unwrap();
489
490 assert!(
491 !stale.exists(),
492 "a reinstall must not leave files from the previous version behind"
493 );
494 assert!(dir.path().join(agent.name).join("agent.leviath").exists());
495 }
496
497 #[test]
498 fn install_surfaces_a_directory_creation_failure() {
499 let dir = tempfile::tempdir().unwrap();
502 let blocked = dir.path().join("not-a-dir");
503 std::fs::write(&blocked, "").unwrap();
504
505 let result = install_bundled(&BUNDLED_AGENTS[0], &blocked);
506
507 assert!(result.is_err());
508 }
509
510 #[test]
511 fn install_surfaces_a_file_write_failure() {
512 let agent = BundledAgent {
519 name: "collides-with-its-own-directory",
520 version: "0.0.1",
521 files: &[("tools/a.rhai", "nested first"), ("tools", "then the dir")],
522 };
523 let dir = tempfile::tempdir().unwrap();
524
525 let result = install_bundled(&agent, dir.path());
526
527 assert!(result.is_err());
528 }
529
530 #[test]
531 fn install_surfaces_a_remove_failure() {
532 let dir = tempfile::tempdir().unwrap();
535 let agent = &BUNDLED_AGENTS[0];
536 std::fs::write(dir.path().join(agent.name), "").unwrap();
537
538 let result = install_bundled(agent, dir.path());
539
540 assert!(result.is_err());
541 }
542}