merge branch 'claude/vibrant-austin' into main

# Conflicts:
#	autoresearch.jsonl
#	autoresearch.md
#	experiments/bench_tauri_runtime_surface.py
#	experiments/worklog.md
#	tauri-app/src-tauri/src/runtime/health.rs
#	tauri-app/src-tauri/src/runtime/mod.rs
#	tauri-app/src-tauri/src/runtime/types.rs
#	tauri-app/src/features/runtime/components/RuntimeSurface.test.tsx
This commit is contained in:
Evandro Fonseca Junior committed 2026-03-27 03:20:13 -03:00
commit c91cbe51f2
10 files changed
+254 -6

No files matched your search

+25
View File
@@ -62,6 +62,31 @@ pub fn wait_for_health(addr: &str, timeout: Duration) -> bool {
false
}
pub fn check_health(addr: &str, timeout: Duration) -> bool {
let Some(socket_addr) = resolve_socket_addr(addr) else {
return false;
};
match TcpStream::connect_timeout(&socket_addr, timeout) {
Ok(mut stream) => {
let _ = stream.set_read_timeout(Some(timeout));
let _ = stream.set_write_timeout(Some(timeout));
let request =
format!("GET /health HTTP/1.1\r\nHost: {addr}\r\nConnection: close\r\n\r\n");
if stream.write_all(request.as_bytes()).is_ok() {
let mut response = String::new();
return stream.read_to_string(&mut response).is_ok()
&& health_response_is_ready(&response);
}
false
}
Err(_) => false,
}
}
#[cfg(test)]
mod tests {
use super::wait_for_health;
+136 -1
View File
@@ -10,7 +10,7 @@ use std::time::Duration;
use tauri::{AppHandle, State};
use self::config::{load_runtime_config, save_runtime_config};
use self::health::wait_for_health;
use self::health::{check_health, wait_for_health};
use self::process::{find_localllm_binary, spawn_localllm};
use self::types::{
ConnectionMode, RuntimeConfig, RuntimeErrorInfo, RuntimeSnapshot, RuntimeStatus, StartPolicy,
@@ -82,6 +82,28 @@ fn refresh_process_state(inner: &mut RuntimeInner) {
if inner.status == RuntimeStatus::Stopped {
inner.status = RuntimeStatus::Ready;
}
if inner.config.connection_mode == ConnectionMode::Local
&& matches!(inner.status, RuntimeStatus::Ready | RuntimeStatus::Degraded)
{
if check_health(&inner.config.addr, Duration::from_millis(400)) {
inner.status = RuntimeStatus::Ready;
if inner.last_error.as_ref().map(|error| error.code.as_str())
== Some("health_check_failed")
{
inner.last_error = None;
}
} else {
inner.status = RuntimeStatus::Degraded;
inner.last_error = Some(runtime_error(
"health_check_failed",
format!(
"Local API process is still running, but /health is no longer ready on {}.",
inner.config.addr
),
));
}
}
}
Err(error) => {
inner.child = None;
@@ -252,3 +274,116 @@ pub fn stop_runtime(state: State<'_, RuntimeState>) -> Result<RuntimeSnapshot, S
inner.last_error = None;
Ok(build_snapshot(&inner))
}
#[cfg(test)]
mod tests {
use super::*;
fn spawn_healthy_server() -> String {
let listener = std::net::TcpListener::bind("127.0.0.1:0").expect("bind healthy port");
let addr = listener
.local_addr()
.expect("read healthy addr")
.to_string();
std::thread::spawn(move || {
if let Ok((mut stream, _)) = listener.accept() {
use std::io::{Read, Write};
let mut buffer = [0_u8; 1024];
let _ = stream.read(&mut buffer);
let _ = stream.write_all(
b"HTTP/1.1 200 OK\r\ncontent-type: application/json\r\ncontent-length: 58\r\n\r\n{\"status\":\"ok\",\"model_loaded\":true,\"model_id\":\"demo\"}",
);
let _ = stream.flush();
}
});
addr
}
fn unused_local_addr() -> String {
let listener = std::net::TcpListener::bind("127.0.0.1:0").expect("bind unused port");
let addr = listener.local_addr().expect("read local addr").to_string();
drop(listener);
addr
}
fn spawn_sleeping_child() -> Child {
std::process::Command::new("python")
.args(["-c", "import time; time.sleep(60)"])
.spawn()
.expect("spawn sleeping child")
}
#[test]
fn runtime_revalidation_marks_unhealthy_local_process_as_degraded() {
let mut child = spawn_sleeping_child();
let addr = unused_local_addr();
let (status, error_code) = {
let mut inner = RuntimeInner {
child: Some(child),
status: RuntimeStatus::Ready,
last_error: None,
config: RuntimeConfig {
model_path: Some("D:/models/demo.gguf".to_string()),
addr,
..RuntimeConfig::default()
},
};
refresh_process_state(&mut inner);
child = inner.child.take().expect("process remains managed");
(
inner.status,
inner.last_error.as_ref().map(|error| error.code.clone()),
)
};
let _ = child.kill();
let _ = child.wait();
assert_eq!(status, RuntimeStatus::Degraded);
assert_eq!(error_code.as_deref(), Some("health_check_failed"));
}
#[test]
fn runtime_revalidation_restores_local_process_to_ready_after_health_recovers() {
let mut child = spawn_sleeping_child();
let addr = spawn_healthy_server();
let (status, error_code) = {
let mut inner = RuntimeInner {
child: Some(child),
status: RuntimeStatus::Degraded,
last_error: Some(runtime_error(
"health_check_failed",
"Local API process is still running, but /health is no longer ready.",
)),
config: RuntimeConfig {
model_path: Some("D:/models/demo.gguf".to_string()),
addr,
..RuntimeConfig::default()
},
};
refresh_process_state(&mut inner);
child = inner.child.take().expect("process remains managed");
(
inner.status,
inner.last_error.as_ref().map(|error| error.code.clone()),
)
};
let _ = child.kill();
let _ = child.wait();
assert_eq!(status, RuntimeStatus::Ready);
assert_eq!(error_code, None);
}
}
+1 -1
View File
@@ -16,7 +16,7 @@ pub enum StartPolicy {
OnAppLaunch,
}
#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Default)]
#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Default, Debug)]
#[serde(rename_all = "snake_case")]
pub enum RuntimeStatus {
#[default]