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    /// Every name in a stage's `available_tools` has to resolve to a tool that
226    /// exists, and every `[stages.X.tool_permissions]` key has to be a tool that
227    /// stage actually grants.
228    ///
229    /// A typo here is invisible on its own: `filter_tools_by_available` silently
230    /// omits a name matching nothing, so the stage just quietly advertises one
231    /// tool fewer. And because dispatch refuses anything a stage did not offer,
232    /// the same typo means the model is told the tool does not exist and the
233    /// stage cannot do its job - a silent omission that is really a silent
234    /// failure, which is worth a test. A permission entry for an ungranted tool is the same drift seen
235    /// from the other side: it reads as a grant and is not one.
236    ///
237    /// An invariant over all discovered agents rather than a list of names -
238    /// naming them would stop testing the property the moment the list drifted.
239    #[test]
240    fn every_stage_tool_name_resolves_and_every_permission_names_a_granted_tool() {
241        // Sub-agent tools are provided by the host, not `BuiltinTools`.
242        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            // This agent's own Rhai tools: `tools/<name>.rhai` defines `<name>`.
256            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                    // `server__tool` is an MCP tool, resolvable only once that
279                    // server is installed - not something a manifest can be
280                    // checked against here.
281                    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    /// The invariant above can actually fail - a check over shipped data that
305    /// happens to pass says nothing about whether it would catch drift.
306    #[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    // ─── installed_version ──────────────────────────────────────────────────
349
350    #[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        // A half-written install must read as "not installed" so the wizard
371        // offers a clean reinstall rather than refusing to plan.
372        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    // ─── plan_agent_actions ─────────────────────────────────────────────────
384
385    #[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        // Rewrite the installed manifest at a different version.
424        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    // ─── install_bundled ────────────────────────────────────────────────────
453
454    #[test]
455    fn install_writes_every_file_including_nested_ones() {
456        let dir = tempfile::tempdir().unwrap();
457        // Pick a blueprint that actually has a nested `tools/` file, so the
458        // create_dir_all arm is exercised by a real shipped layout rather than
459        // a fixture. If none ships nested files any more, the flat arm below
460        // still covers the rest.
461        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        // `agents_dir` is itself a file, so creating the blueprint directory
500        // under it fails.
501        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        // Isolating the `write` error from the `create_dir_all` error needs a
513        // layout where the directory step succeeds and only the write fails.
514        // A synthetic blueprint whose second entry names a path the first entry
515        // already created as a *directory* does exactly that: `create_dir_all`
516        // sees an existing dir and returns Ok, then the write hits EISDIR.
517        // No shipped blueprint has that shape, hence the hand-built one.
518        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        // The destination exists but is a *file*, so `remove_dir_all` fails
533        // rather than the write.
534        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}