Files
2026-10-03 19:39:35 -07:00

90 lines
3.1 KiB
Rust

//! Heartbeat and cron yield to a held subagent. Do not edit.
mod support;
use bxcore::{SessionId, Timestamp};
use loopd::channel::Context;
use loopd::llama::Client;
use loopd::memory::NoMemory;
use loopd::scheduler::{Scheduler, TickWork};
use loopd::tools::Registry;
use std::sync::Arc;
use support::{FakeServer, Home, Reply, ScriptedPort};
const CHAT: &str = "/v1/chat/completions";
fn sse() -> Reply {
let data1 = r#"data: {"choices":[{"finish_reason":null,"index":0,"delta":{"content":"noted"}}],"id":"h","timings":{"cache_n":0,"prompt_n":10,"predicted_n":1}}"#;
let data2 = r#"data: {"choices":[{"finish_reason":"stop","index":0,"delta":{}}],"id":"h","timings":{"cache_n":0,"prompt_n":10,"predicted_n":1}}"#;
let body = format!("{data1}\n\n{data2}\n\ndata: [DONE]\n\n");
Reply::raw(format!(
"HTTP/1.1 200 OK\r\nContent-Type: text/event-stream\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{body}",
body.len()
))
}
fn ts(s: &str) -> Timestamp {
Timestamp::parse(s).unwrap()
}
#[test]
fn a_held_subagent_skips_a_due_heartbeat() {
let home = Home::new();
home.write("heartbeat.md", "Look around.\n");
home.write("compact.md", "Summarise.\n");
let server = FakeServer::start();
let mut cfg = home.config(&server.socket);
cfg.heartbeat.every_s = 60;
cfg.heartbeat.prompt = home.dir.join("heartbeat.md");
server.route(CHAT, vec![sse()]);
let ctx = Arc::new(Context::new(
cfg.clone(),
Client::new(cfg),
Box::new(ScriptedPort::new(vec![])),
Registry::m2b(),
Box::new(NoMemory),
));
let held = ctx
.hold(SessionId::new("sub-mm-thread-1").unwrap())
.expect("hold");
let mut sched = Scheduler::new(ts("2026-10-03T07:00:00.000Z"));
let work = sched.tick(&ctx, ts("2026-10-03T07:01:00.000Z"));
assert!(work.is_empty(), "{work:?}");
assert!(server.requests_to(CHAT).is_empty());
drop(held);
let work = sched.tick(&ctx, ts("2026-10-03T07:02:00.000Z"));
assert_eq!(work, vec![TickWork::Heartbeat]);
assert_eq!(server.requests_to(CHAT).len(), 1);
}
#[test]
fn a_held_subagent_skips_a_due_cron_job() {
let home = Home::new();
home.write("compact.md", "Summarise.\n");
home.write(
"jobs/mail.toml",
"schedule = \"0 7 * * *\"\nprompt = \"Check mail.\"\n",
);
let server = FakeServer::start();
let cfg = home.config(&server.socket);
server.route(CHAT, vec![sse()]);
let ctx = Arc::new(Context::new(
cfg.clone(),
Client::new(cfg),
Box::new(ScriptedPort::new(vec![])),
Registry::m2b(),
Box::new(NoMemory),
));
let held = ctx
.hold(SessionId::new("sub-mm-thread-1").unwrap())
.expect("hold");
let mut sched = Scheduler::new(ts("2026-10-03T06:00:00.000Z"));
let work = sched.tick(&ctx, ts("2026-10-03T07:00:00.000Z"));
assert!(work.is_empty(), "{work:?}");
assert!(server.requests_to(CHAT).is_empty());
drop(held);
let work = sched.tick(&ctx, ts("2026-10-03T07:00:00.000Z"));
assert_eq!(work.len(), 1);
assert!(matches!(work[0], TickWork::Job { .. }), "{work:?}");
}