loopd: [heartbeat] table and summarise name check
Co-Authored-By: ornith-1.5-35b-a3b <agent@wntrmute.dev>
This commit is contained in:
@@ -177,6 +177,8 @@ pub struct Config {
|
||||
pub memory: Memory,
|
||||
#[serde(default)]
|
||||
pub compact: Compact,
|
||||
#[serde(default)]
|
||||
pub heartbeat: Heartbeat,
|
||||
}
|
||||
|
||||
/// `[memory]`: `memoryd`'s socket for `loopd` (M5a spec, section 9). An empty path means the
|
||||
@@ -207,6 +209,24 @@ impl Default for Compact {
|
||||
}
|
||||
}
|
||||
|
||||
/// `[heartbeat]`: how often the standing `heartbeat` session runs (M5d spec, section 4.1).
|
||||
/// `every_s` 0 disables it.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
|
||||
#[serde(deny_unknown_fields, default)]
|
||||
pub struct Heartbeat {
|
||||
pub every_s: u64,
|
||||
pub prompt: std::path::PathBuf,
|
||||
}
|
||||
|
||||
impl Default for Heartbeat {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
every_s: 0,
|
||||
prompt: std::path::PathBuf::from("heartbeat.md"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ConfigError {
|
||||
Read(std::path::PathBuf, std::io::Error),
|
||||
@@ -235,6 +255,7 @@ impl Config {
|
||||
toml::from_str(&text).map_err(|e| ConfigError::Parse(path.to_path_buf(), e))?;
|
||||
if let Some(parent) = path.parent() {
|
||||
config.baseline.system = parent.join(&config.baseline.system);
|
||||
config.heartbeat.prompt = parent.join(&config.heartbeat.prompt);
|
||||
}
|
||||
Ok(config)
|
||||
}
|
||||
@@ -259,4 +280,19 @@ impl Config {
|
||||
pub fn compact_prompt(&self) -> std::path::PathBuf {
|
||||
self.baseline.system.with_file_name("compact.md")
|
||||
}
|
||||
|
||||
/// The heartbeat prompt, already joined to the config file's directory by `load`.
|
||||
pub fn heartbeat_prompt(&self) -> std::path::PathBuf {
|
||||
self.heartbeat.prompt.clone()
|
||||
}
|
||||
|
||||
/// Unknown `[compact] summarise` names are a config error (M5d spec, section 4.3).
|
||||
pub fn check_summarise(&self, registry: &crate::tools::Registry) -> Result<(), String> {
|
||||
for name in &self.compact.summarise {
|
||||
if registry.get(name).is_none() {
|
||||
return Err(format!("[compact] summarise: unknown tool {name}"));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +58,11 @@ fn run_selftest(path: &Path) -> ExitCode {
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(e) = cfg.check_summarise(&loopd::tools::Registry::m5c()) {
|
||||
eprintln!("loopd: {e}");
|
||||
return ExitCode::from(1);
|
||||
}
|
||||
|
||||
let client = Client::new(cfg);
|
||||
match run_selftest_check(&client) {
|
||||
Ok(()) => ExitCode::SUCCESS,
|
||||
@@ -74,6 +79,11 @@ fn run_serve(path: &Path) -> ExitCode {
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(e) = cfg.check_summarise(&loopd::tools::Registry::m5c()) {
|
||||
eprintln!("loopd: {e}");
|
||||
return ExitCode::from(1);
|
||||
}
|
||||
|
||||
// One loopd per home (M5b spec, section 9a). The lock comes before the socket is touched and
|
||||
// before the self-test, so a second copy neither takes the socket of the one that runs nor
|
||||
// sends its slot a prompt. Held until the process ends.
|
||||
|
||||
Reference in New Issue
Block a user