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 fn lint_env_for(agent: &BundledAgent) -> crate::lint::LintEnv {
232 let mut known_tools: std::collections::HashSet<String> = leviath_tools::BuiltinTools::new(
233 leviath_tools::ToolContext::new(std::path::PathBuf::from(".")),
234 )
235 .names()
236 .into_iter()
237 .collect();
238 known_tools.extend(leviath_tools::BuiltinTools::subagent_tool_names());
239 known_tools.extend(
240 agent
241 .files
242 .iter()
243 .filter_map(|(rel, _)| rel.strip_prefix("tools/"))
244 .filter_map(|f| f.strip_suffix(".rhai"))
245 .map(str::to_string),
246 );
247 crate::lint::LintEnv {
248 known_tools,
249 known_models: crate::commands::models::closed_catalog_models(),
250 available_providers: None,
251 read_paths: None,
252 }
253 }
254
255 #[test]
267 fn no_bundled_agent_has_a_lint_error() {
268 for agent in BUNDLED_AGENTS {
269 let manifest = agent
270 .files
271 .iter()
272 .find(|(rel, _)| *rel == "agent.leviath")
273 .map(|(_, c)| *c)
274 .expect("every bundled agent has a manifest");
275 let parsed = leviath_core::manifest::parse_manifest(manifest);
276 assert!(
277 parsed.is_ok(),
278 "bundled agent {} does not parse",
279 agent.name
280 );
281 let blueprint = parsed.expect("asserted Ok just above");
282 let rendered: Vec<(bool, String)> =
289 crate::lint::lint_manifest(manifest, &blueprint, &lint_env_for(agent))
290 .iter()
291 .map(|f| (f.is_error(), format!("{} [{}]", f.one_line(), f.code)))
292 .collect();
293 let error_count = rendered.iter().filter(|(is_error, _)| *is_error).count();
294 assert_eq!(
295 error_count, 0,
296 "bundled agent {} has lint errors, among {rendered:?}",
297 agent.name
298 );
299 }
300 }
301
302 #[test]
305 fn the_lint_invariant_catches_a_typo_and_an_orphan_permission() {
306 let manifest = r#"
307[agent]
308name = "x"
309version = "0.1.0"
310description = "x"
311
312[stages.only]
313mode = "autonomous"
314model = { provider = "anthropic", model = "claude-sonnet-5" }
315max_iterations = 5
316available_tools = ["read_file", "raed_file"]
317
318[stages.only.tool_permissions]
319write_file = "allow"
320"#;
321 let bp = leviath_core::manifest::parse_manifest(manifest)
322 .expect("the fixture parses; it is the lint that should object");
323 let env = lint_env_for(&BundledAgent {
325 name: "x",
326 version: "0.1.0",
327 files: &[],
328 });
329 let codes: Vec<&str> = crate::lint::lint_manifest(manifest, &bp, &env)
330 .iter()
331 .filter(|f| f.is_error())
332 .map(|f| f.code)
333 .collect();
334 assert_eq!(codes, ["unknown-tool", "orphan-stage-permission"]);
335 }
336
337 #[test]
338 fn bundled_agent_names_are_unique() {
339 let mut names: Vec<&str> = BUNDLED_AGENTS.iter().map(|a| a.name).collect();
340 names.sort_unstable();
341 let count = names.len();
342 names.dedup();
343 assert_eq!(count, names.len(), "duplicate bundled agent names");
344 }
345
346 #[test]
349 fn installed_version_reads_a_manifest() {
350 let dir = tempfile::tempdir().unwrap();
351 let agent = &BUNDLED_AGENTS[0];
352 install_bundled(agent, dir.path()).unwrap();
353
354 assert_eq!(
355 installed_version(dir.path(), agent.name).as_deref(),
356 Some(agent.version)
357 );
358 }
359
360 #[test]
361 fn installed_version_is_none_when_nothing_is_installed() {
362 let dir = tempfile::tempdir().unwrap();
363 assert!(installed_version(dir.path(), "not-installed").is_none());
364 }
365
366 #[test]
367 fn installed_version_is_none_for_an_unparseable_manifest() {
368 let dir = tempfile::tempdir().unwrap();
371 std::fs::create_dir_all(dir.path().join("broken")).unwrap();
372 std::fs::write(
373 dir.path().join("broken/agent.leviath"),
374 "not valid toml {{{",
375 )
376 .unwrap();
377
378 assert!(installed_version(dir.path(), "broken").is_none());
379 }
380
381 #[test]
384 fn plan_offers_to_install_everything_into_an_empty_dir() {
385 let dir = tempfile::tempdir().unwrap();
386
387 let plan = plan_agent_actions(dir.path());
388
389 assert_eq!(plan.len(), BUNDLED_AGENTS.len());
390 for (agent, action) in &plan {
391 assert_eq!(*action, AgentAction::Install);
392 assert!(action.is_change());
393 assert_eq!(
394 action.label(agent.version),
395 format!("install {}", agent.version)
396 );
397 }
398 }
399
400 #[test]
401 fn plan_reports_up_to_date_after_installing() {
402 let dir = tempfile::tempdir().unwrap();
403 for agent in BUNDLED_AGENTS {
404 install_bundled(agent, dir.path()).unwrap();
405 }
406
407 let plan = plan_agent_actions(dir.path());
408
409 for (agent, action) in &plan {
410 assert_eq!(*action, AgentAction::UpToDate, "{}", agent.name);
411 assert!(!action.is_change());
412 assert_eq!(action.label(agent.version), "up to date");
413 }
414 }
415
416 #[test]
417 fn plan_reports_an_update_when_the_installed_version_differs() {
418 let dir = tempfile::tempdir().unwrap();
419 let agent = &BUNDLED_AGENTS[0];
420 install_bundled(agent, dir.path()).unwrap();
421 let manifest_path = dir.path().join(agent.name).join("agent.leviath");
423 let manifest = std::fs::read_to_string(&manifest_path).unwrap();
424 let bumped = manifest.replacen(
425 &format!("version = \"{}\"", agent.version),
426 "version = \"9.9.9\"",
427 1,
428 );
429 std::fs::write(&manifest_path, bumped).unwrap();
430
431 let plan = plan_agent_actions(dir.path());
432 let (_, action) = plan
433 .iter()
434 .find(|(a, _)| a.name == agent.name)
435 .expect("the bundled agent is in the plan");
436
437 assert_eq!(
438 *action,
439 AgentAction::Update {
440 from: "9.9.9".to_string()
441 }
442 );
443 assert!(action.is_change());
444 assert_eq!(
445 action.label(agent.version),
446 format!("update 9.9.9 → {}", agent.version)
447 );
448 }
449
450 #[test]
453 fn install_writes_every_file_including_nested_ones() {
454 let dir = tempfile::tempdir().unwrap();
455 for agent in BUNDLED_AGENTS {
460 install_bundled(agent, dir.path()).unwrap();
461 for (rel, contents) in agent.files {
462 let written = std::fs::read_to_string(dir.path().join(agent.name).join(rel));
463 assert!(written.is_ok(), "{}/{rel} was not written", agent.name);
464 assert_eq!(written.expect("asserted Ok just above"), *contents);
465 }
466 }
467 assert!(
468 BUNDLED_AGENTS
469 .iter()
470 .any(|a| a.files.iter().any(|(rel, _)| rel.contains('/'))),
471 "no bundled blueprint has a nested file, so install's mkdir path is untested"
472 );
473 }
474
475 #[test]
476 fn install_replaces_an_existing_tree_and_drops_stale_files() {
477 let dir = tempfile::tempdir().unwrap();
478 let agent = &BUNDLED_AGENTS[0];
479 install_bundled(agent, dir.path()).unwrap();
480 let stale = dir
481 .path()
482 .join(agent.name)
483 .join("stale-from-an-older-version");
484 std::fs::write(&stale, "leftover").unwrap();
485
486 install_bundled(agent, dir.path()).unwrap();
487
488 assert!(
489 !stale.exists(),
490 "a reinstall must not leave files from the previous version behind"
491 );
492 assert!(dir.path().join(agent.name).join("agent.leviath").exists());
493 }
494
495 #[test]
496 fn install_surfaces_a_directory_creation_failure() {
497 let dir = tempfile::tempdir().unwrap();
500 let blocked = dir.path().join("not-a-dir");
501 std::fs::write(&blocked, "").unwrap();
502
503 let result = install_bundled(&BUNDLED_AGENTS[0], &blocked);
504
505 assert!(result.is_err());
506 }
507
508 #[test]
509 fn install_surfaces_a_file_write_failure() {
510 let agent = BundledAgent {
517 name: "collides-with-its-own-directory",
518 version: "0.0.1",
519 files: &[("tools/a.rhai", "nested first"), ("tools", "then the dir")],
520 };
521 let dir = tempfile::tempdir().unwrap();
522
523 let result = install_bundled(&agent, dir.path());
524
525 assert!(result.is_err());
526 }
527
528 #[test]
529 fn install_surfaces_a_remove_failure() {
530 let dir = tempfile::tempdir().unwrap();
533 let agent = &BUNDLED_AGENTS[0];
534 std::fs::write(dir.path().join(agent.name), "").unwrap();
535
536 let result = install_bundled(agent, dir.path());
537
538 assert!(result.is_err());
539 }
540}