core: review fixes, init builds the agent, console without a terminal
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.
This commit is contained in:
+71
-1
@@ -177,7 +177,34 @@ pub async fn execute_line(host: &Host, line: &str) -> Result<String> {
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
call(host, "core.log", params, false).await
|
||||
match host.call("core.log", params).await {
|
||||
Ok(entries) => entries
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|entry| {
|
||||
let time =
|
||||
chrono::DateTime::parse_from_rfc3339(entry["time"].as_str().unwrap())?
|
||||
.with_timezone(&chrono::Local);
|
||||
let pid = entry["pid"].as_str().unwrap();
|
||||
let prefix = format!("{}-", entry["plugin"].as_str().unwrap());
|
||||
let short = pid
|
||||
.strip_prefix(&prefix)
|
||||
.and_then(|uuid| uuid.get(..8))
|
||||
.map(|uuid| format!("{prefix}{uuid}"))
|
||||
.unwrap_or_else(|| pid.to_owned());
|
||||
Ok(format!(
|
||||
"{} {} {} {}",
|
||||
time.format("%H:%M:%S%.3f"),
|
||||
short,
|
||||
entry["stream"].as_str().unwrap(),
|
||||
entry["line"].as_str().unwrap()
|
||||
))
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()
|
||||
.map(|lines| lines.join("\n")),
|
||||
Err(error) => Ok(format!("error: {}", pretty(&error)?)),
|
||||
}
|
||||
}
|
||||
"activate" if words.len() == 2 => {
|
||||
call(
|
||||
@@ -245,7 +272,11 @@ fn indent(text: &str) -> String {
|
||||
}
|
||||
|
||||
/// Run the line editor on a dedicated thread, with notifications through its external printer.
|
||||
/// Without a terminal (for example, input from a pipe), read plain lines instead.
|
||||
pub async fn run_input(host: Host) -> Result<()> {
|
||||
if !std::io::IsTerminal::is_terminal(&std::io::stdin()) {
|
||||
return run_plain(host).await;
|
||||
}
|
||||
let history = host.0.world.state_dir("core").join("history");
|
||||
let (ready, started) = tokio::sync::oneshot::channel();
|
||||
let (sender, mut lines) = tokio::sync::mpsc::unbounded_channel::<
|
||||
@@ -323,3 +354,42 @@ pub async fn run_input(host: Host) -> Result<()> {
|
||||
};
|
||||
tokio::select! {result=commands=>result,result=print_notifications=>result}
|
||||
}
|
||||
|
||||
/// Read plain lines from stdin, without line editing or history. A detached
|
||||
/// thread reads them, so that a blocked read does not hold the runtime open.
|
||||
async fn run_plain(host: Host) -> Result<()> {
|
||||
let (sender, mut lines) = tokio::sync::mpsc::unbounded_channel::<String>();
|
||||
std::thread::spawn(move || {
|
||||
for line in std::io::stdin().lines() {
|
||||
let Ok(line) = line else { break };
|
||||
if sender.send(line).is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
let mut notifications = host.subscribe();
|
||||
let print_notifications = async move {
|
||||
loop {
|
||||
match notifications.recv().await {
|
||||
Ok(value) => println!("{}", indent(¬ification_text(&value))),
|
||||
Err(tokio::sync::broadcast::error::RecvError::Lagged(_)) => continue,
|
||||
Err(tokio::sync::broadcast::error::RecvError::Closed) => return Ok(()),
|
||||
}
|
||||
}
|
||||
};
|
||||
let commands = async {
|
||||
while let Some(line) = lines.recv().await {
|
||||
let text = execute_line(&host, &line)
|
||||
.await
|
||||
.unwrap_or_else(|error| format!("error: {error}"));
|
||||
if !text.is_empty() {
|
||||
println!("{}", indent(&text));
|
||||
}
|
||||
if line.trim() == "quit" {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
};
|
||||
tokio::select! {result=commands=>result,result=print_notifications=>result}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user