Skip to main content

leviath_cli/
bundled.rs

1//! The agent blueprints shipped inside the `lev` binary, and the planner that
2//! decides what to do with them.
3//!
4//! Embedding is what makes the ten blueprints under the workspace's `agents/`
5//! directory reachable outside a git checkout: `lev add` takes a local path,
6//! and an `agents/` directory next to the executable is a layout no real
7//! install has, so without the bundle a user who downloads a release binary
8//! gets a working runtime and zero agents to run on it.
9//!
10//! `build.rs` embeds every file of every blueprint via `include_str!` (23
11//! files, ~170 KB of text) and generates the [`BUNDLED_AGENTS`] table included
12//! below. `lev setup` offers to install them; `lev list` reports them.
13
14include!(concat!(env!("OUT_DIR"), "/bundled_agents.rs"));
15
16use std::path::Path;
17
18/// What `lev setup` should do with one bundled blueprint, given what is
19/// currently installed.
20#[derive(Debug, Clone, PartialEq, Eq)]
21pub enum AgentAction {
22    /// Not installed.
23    Install,
24    /// Installed at a different version.
25    Update { from: String },
26    /// Installed at the bundled version.
27    UpToDate,
28}
29
30impl AgentAction {
31    /// Whether applying this action would change anything on disk. Drives which
32    /// rows the wizard pre-checks.
33    pub fn is_change(&self) -> bool {
34        !matches!(self, Self::UpToDate)
35    }
36
37    /// Short label for the wizard's blueprint list.
38    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
47/// The installed version of `name` under `agents_dir`, if a readable manifest
48/// is there.
49///
50/// Deliberately lenient: a blueprint directory whose manifest is missing or
51/// unparseable reads as *not installed*, so the wizard offers a clean reinstall
52/// instead of refusing to plan. An unreadable manifest is exactly the state a
53/// half-finished copy leaves behind.
54pub 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
61/// Decide what to do with every bundled blueprint.
62///
63/// Version comparison is plain string inequality, not semver ordering: this
64/// crate has no semver dependency, and both versions are shown to the user
65/// anyway, so a hand-edited blueprint surfaces as an offered update they can
66/// decline rather than being silently overwritten or silently skipped.
67///
68/// Known limitation: a blueprint edited *without* bumping its version reads as
69/// up to date, because nothing hashes the contents.
70pub 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
84/// Write one bundled blueprint into `<agents_dir>/<name>/`, replacing whatever
85/// is there.
86///
87/// The existing tree is removed first rather than merged over: a stale file
88/// from an older version of the blueprint (a tool script that was dropped, say)
89/// would otherwise survive forever and keep being loaded. This mirrors what
90/// `lev add`'s directory install already does.
91pub 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        // Derive the parent from the *relative* path rather than calling
98        // `path.parent()`. `dest.join(rel)` always has a parent, so the `None`
99        // arm of `parent()` would be unreachable code pretending to be a
100        // handled case; splitting `rel` gives two arms that both actually
101        // happen - nested (`tools/web_fetch.rhai`) and flat (`agent.leviath`).
102        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    /// Every assertion here is an invariant over *all* discovered blueprints.
117    /// Naming individual agents would turn adding or renaming one into a test
118    /// edit, and would stop testing the property the moment the list drifted.
119    #[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    /// A tool script shipped under the same filename by more than one agent
153    /// must be byte-identical everywhere.
154    ///
155    /// Each agent directory is self-contained - that is what lets `lev add
156    /// <dir>` and `lev pack` work - so `web_fetch.rhai` and `web_search.rhai`
157    /// exist as five copies each rather than one shared file. That is fine
158    /// until one copy is fixed and the others are not: these scripts are the
159    /// agents' network surface, so a hardening change applied to one of five is
160    /// four agents still carrying the unfixed behaviour, with nothing to say so.
161    ///
162    /// This turns that silent drift into a test failure. Deliberately keyed on
163    /// filename over *all* discovered agents rather than naming the five, so it
164    /// keeps holding as agents are added or renamed.
165    #[test]
166    fn a_tool_script_shared_by_several_agents_is_identical_in_all_of_them() {
167        use std::collections::HashMap;
168
169        // filename -> (first agent that shipped it, its contents)
170        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        // Guard against a vacuous pass: if the scan found no tool scripts at
190        // all, the loop above asserts nothing.
191        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        // The recorded version drives install/update planning, so a build.rs
200        // scan that disagreed with the manifest would make the wizard lie.
201        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            // `.expect`, not `.unwrap_or_else(|e| panic!(...))`: the closure in
209            // the latter is a function that never runs on a passing test, which
210            // reads to llvm-cov as an uncovered region. For the same reason the
211            // message is a literal - a *call* in an `assert!`'s format args is
212            // also a region that only the failing path reaches.
213            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    /// The lint env for a bundled agent: the built-ins, the sub-agent tools,
226    /// and the agent's own `tools/<name>.rhai`, each of which defines `<name>`.
227    ///
228    /// Built by hand rather than through `LintEnv::offline`, which discovers
229    /// script tools by reading a directory: a bundled agent's files are
230    /// compiled into the binary and there is no directory to read.
231    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    /// No bundled agent ships a blueprint the linter calls broken.
256    ///
257    /// The errors this catches are the ones that are invisible on inspection: a
258    /// tool name matching nothing is silently dropped from what the stage
259    /// advertises, so the model is told the tool does not exist and the stage
260    /// cannot do its job. A permission for a tool the stage never granted is the
261    /// same drift from the other side, reading as a grant and not being one.
262    ///
263    /// Asserted by running the shipped linter rather than by a parallel copy of
264    /// its rules, and over all discovered agents rather than a list of names -
265    /// either would stop testing the property the moment it drifted.
266    #[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            // Every finding is rendered up front, and the errors are then
283            // *counted* rather than collected. Any per-error work - a `.map`
284            // that formats, a `.collect` into a list of messages - sits in a
285            // closure that only runs when the test is about to fail, which
286            // llvm-cov reads as an uncovered region for as long as the
287            // invariant holds. Counting has no such body.
288            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    /// The invariant above can actually fail - a check over shipped data that
303    /// happens to pass says nothing about whether it would catch drift.
304    #[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        // Reuse the same env shape a real bundled agent gets, minus any scripts.
324        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    // ─── installed_version ──────────────────────────────────────────────────
347
348    #[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        // A half-written install must read as "not installed" so the wizard
369        // offers a clean reinstall rather than refusing to plan.
370        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    // ─── plan_agent_actions ─────────────────────────────────────────────────
382
383    #[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        // Rewrite the installed manifest at a different version.
422        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    // ─── install_bundled ────────────────────────────────────────────────────
451
452    #[test]
453    fn install_writes_every_file_including_nested_ones() {
454        let dir = tempfile::tempdir().unwrap();
455        // Pick a blueprint that actually has a nested `tools/` file, so the
456        // create_dir_all arm is exercised by a real shipped layout rather than
457        // a fixture. If none ships nested files any more, the flat arm below
458        // still covers the rest.
459        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        // `agents_dir` is itself a file, so creating the blueprint directory
498        // under it fails.
499        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        // Isolating the `write` error from the `create_dir_all` error needs a
511        // layout where the directory step succeeds and only the write fails.
512        // A synthetic blueprint whose second entry names a path the first entry
513        // already created as a *directory* does exactly that: `create_dir_all`
514        // sees an existing dir and returns Ok, then the write hits EISDIR.
515        // No shipped blueprint has that shape, hence the hand-built one.
516        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        // The destination exists but is a *file*, so `remove_dir_all` fails
531        // rather than the write.
532        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}