#![cfg(unix)]
mod common;
use std::process::{Command, Stdio};
use std::time::{Duration, Instant};
use serde_json::{Value, json};
struct MockLlm {
child: std::process::Child,
addr_file: String,
uri: String,
}
impl Drop for MockLlm {
fn drop(&mut self) {
let _ = self.child.kill();
let _ = self.child.wait();
let _ = std::fs::remove_file(&self.addr_file);
}
}
fn spawn_mock_llm(playbook: &Value) -> MockLlm {
let pb = common::unique_path("prompt-playbook", "json");
std::fs::write(&pb, playbook.to_string()).unwrap();
let addr_file = common::unique_path("prompt-mock-llm", "addr");
let _ = std::fs::remove_file(&addr_file);
let child = Command::new(env!("CARGO_BIN_EXE_agentd"))
.args(["--internal-mock-llm", &addr_file, &format!("file:{pb}")])
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.expect("spawn mock llm");
let addr = common::read_addr_file(&addr_file);
MockLlm {
child,
addr_file,
uri: format!("http://{addr}"),
}
}
#[test]
fn a_prompt_runs_once_and_prints_the_answer() {
let llm = spawn_mock_llm(&json!({"turns": [{"content": "42 open issues, 3 stale."}]}));
let out = Command::new(env!("CARGO_BIN_EXE_agentd"))
.args([
"--prompt",
"How many open issues?",
"--intelligence",
&llm.uri,
])
.output()
.expect("run agentd");
assert!(out.status.success(), "exit: {:?}", out.status);
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(
stdout.contains("42 open issues"),
"the answer rides stdout: {stdout}"
);
}
#[test]
fn a_prompt_that_sets_up_recurring_work_leaves_a_live_daemon() {
let playbook = json!({"turns": [
{"tool_calls": [{"name": "workflow.create", "arguments": {"definition": {
"name": "watcher",
"version": 3,
"steps": {
"tick": {"kind": "loop", "interval": "30s"},
"note": {"kind": "finish", "depends_on": ["tick"], "status": "completed", "output": "checked"}
}
}}}]},
{"content": "Set up 'watcher' to run every 30s."}
]});
let llm = spawn_mock_llm(&playbook);
let mut child = Command::new(env!("CARGO_BIN_EXE_agentd"))
.args([
"--prompt",
"Check the queue every 30 seconds from now on.",
"--intelligence",
&llm.uri,
"--store-kind",
"memory",
])
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.expect("spawn agentd");
let deadline = Instant::now() + Duration::from_secs(12);
let mut exited_early = None;
while Instant::now() < deadline {
if let Ok(Some(st)) = child.try_wait() {
exited_early = Some(st);
break;
}
std::thread::sleep(Duration::from_millis(150));
}
let alive = exited_early.is_none();
let _ = child.kill();
let _ = child.wait();
assert!(
alive,
"the instance exited ({exited_early:?}) despite arming a loop workflow — \
`auto` must re-read the live workflow set, not just the configured one"
);
}