use super::super::ShellState;
use crate::AppModel;
use crate::app::Roots;
use crate::cli_outbound::Cli;
use crate::git_tree::tests::fixture::Fixture;
use crate::projects::runner::BlStore;
use crate::ui_state::SystemClock;
use crate::xdg::Env;
use std::os::unix::fs::PermissionsExt;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use tempfile::{TempDir, tempdir};
pub(super) const MINT_SEED: u64 = 0xcbb8;
pub(super) const MINTED_FIRST: &str = "saddlebag";
pub(super) fn fake_lernie(dir: &Path) -> Cli {
let path = dir.join("lernie");
std::fs::write(
&path,
format!(
"#!/bin/sh\ncase \"$1\" in\n{}esac\nexit 0\n",
crate::test_support::authoring_new_arm()
),
)
.unwrap();
let mut perms = std::fs::metadata(&path).unwrap().permissions();
perms.set_mode(0o755);
std::fs::set_permissions(&path, perms).unwrap();
Cli::new(path)
}
pub(super) fn seed_world(world: &World) {
let lernie_home = crate::world::layout_under(&world.yog_data).lernie;
std::fs::create_dir_all(&lernie_home).unwrap();
std::fs::write(lernie_home.join("models.yaml"), b"models: {}\n").unwrap();
}
pub(super) struct World {
_root: TempDir,
fx: Fixture,
spheres: Vec<Fixture>,
pub(super) model: AppModel,
deriver: crate::app::Deriver,
pub(super) state: ShellState,
pub(super) ws: PathBuf,
pub(super) yog_data: PathBuf,
lernie_workspaces: PathBuf,
}
impl World {
pub(super) fn converge(&mut self) {
self.deriver.step();
self.model.refresh();
}
pub(super) fn add_workspace(&mut self, name: &str, agent: &str) -> PathBuf {
let fx = Fixture::new();
fx.build_agent(agent, name);
let ws = self.lernie_workspaces.join(name);
std::os::unix::fs::symlink(&fx.path, &ws).unwrap();
self.spheres.push(fx);
ws
}
pub(super) fn add_child(&self, parent_id: &str, child_id: &str) {
self.fx.build_child(parent_id, child_id);
}
pub(super) fn add_root(&self, conv_id: &str, name: &str) {
self.fx.build_agent(conv_id, name);
self.fx.name_agent(conv_id, name);
}
pub(super) fn quiet(&self, conv_id: &str) {
self.fx
.mark_ref(&format!("refs/lernie/abandoned/{conv_id}"));
}
}
#[derive(PartialEq, Eq)]
enum Roster {
One,
Empty,
}
pub(super) fn world_titled(title: &str) -> World {
build_world(title, &Roster::One)
}
pub(super) fn world() -> World {
build_world("hello", &Roster::One)
}
pub(super) fn world_empty() -> World {
build_world("hello", &Roster::Empty)
}
fn build_world(title: &str, roster: &Roster) -> World {
let root = tempdir().unwrap();
let yog_data = root.path().join("yog");
let roots = Roots {
yog_data: yog_data.clone(),
lernie_data: root.path().join("lernie"),
yog_state: root.path().join("state"),
balls_clones: root.path().join("balls").join("clones"),
home: root.path().join("home"),
world: crate::test_support::world_under(root.path()),
};
std::fs::create_dir_all(roots.lernie_data.join("workspaces")).unwrap();
std::fs::write(
roots.lernie_data.join("models.yaml"),
b"models:\n m:\n provider: anthropic\n model_id: m\n context_window: 200000\n",
)
.unwrap();
std::fs::create_dir_all(&roots.yog_state).unwrap();
let fx = Fixture::new();
fx.build_agent("c-1", title);
let messages = fx.path.join("agents/c-1/messages");
std::fs::create_dir_all(&messages).unwrap();
std::fs::write(messages.join("001-user.md"), "please ping").unwrap();
std::fs::write(
messages.join("002-opus.json"),
br#"{"content":[{"type":"text","text":"pong reply"}]}"#,
)
.unwrap();
let step = fx.path.join("steps/c-1/001");
std::fs::create_dir_all(step.join("tools/toolu_1")).unwrap();
std::fs::write(
step.join("response.json"),
b"{\"type\":\"usage\",\"input_tokens\":10,\"output_tokens\":5,\"cache_read_tokens\":50000}\n{\"type\":\"error\",\"kind\":\"auth\",\"message\":\"no credential for this provider\",\"provider_detail\":null}\n{\"type\":\"end\"}\n",
)
.unwrap();
std::fs::write(step.join("tools/toolu_1/input.json"), br#"{"name":"Read"}"#).unwrap();
std::fs::write(
step.join("tools/toolu_1/output.json"),
br#"{"exit_code":0}"#,
)
.unwrap();
fx.deposit_message(
"c-1",
"user-001.md",
"---\nfrom: user\ndeposited_at: t0\n---\nfollow-up message",
);
let lernie_workspaces = roots.lernie_data.join("workspaces");
let ws = lernie_workspaces.join("ws");
if *roster != Roster::Empty {
std::os::unix::fs::symlink(&fx.path, &ws).unwrap();
}
let env = Env::from_pairs([("HOME", root.path().display().to_string())]);
let balls = Box::new(BlStore::new(env.balls_layout(), Cli::new("bl")));
let (model, deriver) = AppModel::boot(
roots,
(*roster == Roster::One).then(|| ws.clone()),
Arc::new(SystemClock),
balls,
Some("me".into()),
);
let mut state = ShellState::new(&env, Arc::new(SystemClock)).unwrap();
state.start.mint_seed = MINT_SEED;
World {
_root: root,
fx,
spheres: Vec::new(),
model,
deriver,
state,
ws,
yog_data,
lernie_workspaces,
}
}