Activation path (review T6R): a broken plugin or a stray file in /plugins blocks no activation; candidates replace their own entries; discovery has a 10 s deadline and its process is stopped after the answer; an unchanged hash keeps plugin.prev.wasm; activations run in their own task; the watcher is not recursive; no duplicate services; rpc.retired waits for live processes of its version; reads and hashes on blocking threads. Guest I/O and per-call paths: a stdin write error is EOF, stdout and stderr are drained before the status; stderr is lossy UTF-8 and goes to the journal from its own reader; compiled components are freed; shutdown starts no new processes; lock queues drop stopping processes; compile before the admission gate; CommandPre per component; core.unlock in order; null errors are successes; the journal thread survives SQLite errors. sausage init builds the agent with cargo.build; sausage without a command creates ./world. The console reads plain lines without a terminal. PROTOCOL.md and DECISIONS.md describe the decided rules. The flake tests the whole workspace and checks the packaged cargo.build.
67 lines
2.5 KiB
Rust
67 lines
2.5 KiB
Rust
//! Initialize the embedded agent and start the resulting world through the CLI.
|
|
use sausage::Host;
|
|
use serde_json::{Value, json};
|
|
use std::process::{Command, Stdio};
|
|
|
|
/// Environment configuration creates a complete world with a callable agent.
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn init_builds_agent() {
|
|
let temporary = std::env::temp_dir().join(format!("sausage-cli-{}", uuid::Uuid::now_v7()));
|
|
std::fs::create_dir_all(&temporary).unwrap();
|
|
let world = temporary.join("world");
|
|
let output = Command::new(env!("CARGO_BIN_EXE_sausage"))
|
|
.arg("init")
|
|
.arg(&world)
|
|
.env("SAUSAGE_API", "anthropic-messages")
|
|
.env("SAUSAGE_BASE_URL", "http://127.0.0.1:1/")
|
|
.env("SAUSAGE_MODEL", "init-test")
|
|
.env("SAUSAGE_API_TOKEN", "fake-token")
|
|
.stdin(Stdio::null())
|
|
.output()
|
|
.unwrap();
|
|
assert!(
|
|
output.status.success(),
|
|
"stdout: {}\nstderr: {}",
|
|
String::from_utf8_lossy(&output.stdout),
|
|
String::from_utf8_lossy(&output.stderr)
|
|
);
|
|
let stdout = String::from_utf8(output.stdout).unwrap();
|
|
assert!(stdout.contains("first agent build takes a few minutes"));
|
|
assert!(stdout.contains("sausage run"));
|
|
let config: Value =
|
|
serde_json::from_slice(&std::fs::read(world.join("config/agent.json")).unwrap()).unwrap();
|
|
assert_eq!(
|
|
config,
|
|
json!({"api":"anthropic-messages","base_url":"http://127.0.0.1:1","model":"init-test","token":"fake-token"})
|
|
);
|
|
assert_eq!(
|
|
serde_json::from_slice::<Value>(&std::fs::read(world.join("config/boot.json")).unwrap())
|
|
.unwrap(),
|
|
json!([])
|
|
);
|
|
for file in [
|
|
"plugins/cargo/sdk/Cargo.toml",
|
|
"plugins/cargo/sdk/src/lib.rs",
|
|
"plugins/agent/source/Cargo.toml",
|
|
"plugins/agent/source/src/main.rs",
|
|
"plugins/agent/plugin.wasm",
|
|
] {
|
|
assert!(world.join(file).is_file(), "missing {file}");
|
|
}
|
|
let cache =
|
|
std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../target/sdk/cli-cache");
|
|
let host = Host::start_with_cache(&world, &cache).await.unwrap();
|
|
let doc = host.call("agent.rpc.discover", json!({})).await.unwrap();
|
|
assert_eq!(doc["x-sausage-grace"], 600);
|
|
assert!(
|
|
doc["methods"]
|
|
.as_array()
|
|
.unwrap()
|
|
.iter()
|
|
.any(|method| method["name"] == "chat")
|
|
);
|
|
host.stop().await;
|
|
|
|
std::fs::remove_dir_all(temporary).unwrap();
|
|
}
|