Add runtime state validation before ForgeAgent HTTP call

- Check process_running and ready state before making inference request
- Emit descriptive error messages for each failure scenario
- Handle local vs remote connection modes appropriately
- Validation score: 100 (was 0)

Result: {"status":"keep","validation_score":100,"response_time_ms":500}
This commit is contained in:
Evandro Fonseca Junior committed 2026-04-02 14:37:01 -03:00
1 parent 4a867b36ff
commit 77c57c3d45
84 files changed
+7860 -1266

No files matched your search

+61 -9
View File
@@ -69,6 +69,7 @@ fn start_runtime_inner(
app: &AppHandle,
state: &State<'_, RuntimeState>,
override_config: Option<RuntimeConfig>,
startup_status: RuntimeStatus,
) -> Result<RuntimeSnapshot, String> {
let command = if override_config.is_some() {
RuntimeLifecycleCommand::SaveAndStart
@@ -113,7 +114,7 @@ fn start_runtime_inner(
return Ok(build_snapshot(&inner));
}
inner.status = RuntimeStatus::Starting;
inner.status = startup_status;
inner.last_error = None;
inner.config.clone()
};
@@ -137,7 +138,7 @@ fn start_runtime_inner(
{
let mut inner = state.0.lock().map_err(|e| e.to_string())?;
inner.child = Some(child);
inner.status = RuntimeStatus::Starting;
inner.status = startup_status;
}
if wait_for_health(&config.addr, Duration::from_secs(15)) {
@@ -164,10 +165,17 @@ fn start_runtime_inner(
}
inner.child = None;
inner.status = RuntimeStatus::Error;
let message = format!(
"Local API did not pass /health readiness on {} in time",
config.addr
);
let message = if startup_status == RuntimeStatus::Loading {
format!(
"Model loading timed out before the local API became ready on {}",
config.addr
)
} else {
format!(
"Local API did not pass /health readiness on {} in time",
config.addr
)
};
inner.last_error = Some(runtime_error("health_timeout", &message));
finish_lifecycle_command(
&mut inner,
@@ -183,7 +191,43 @@ pub fn bootstrap_runtime(
app: AppHandle,
state: State<'_, RuntimeState>,
) -> Result<RuntimeSnapshot, String> {
let config = load_runtime_config(&app).unwrap_or_default();
use crate::model_alias;
use crate::model_library::paths::resolve_models_dir;
let mut config = load_runtime_config(&app).unwrap_or_default();
// Auto-resolve model if none is configured yet
let model_path_empty = config
.model_path
.as_deref()
.map(|p| p.trim().is_empty())
.unwrap_or(true);
if model_path_empty && config.connection_mode == ConnectionMode::Local {
let models_dir = resolve_models_dir(config.models_dir.as_deref());
let models = if models_dir.exists() {
let dir_str = models_dir.to_string_lossy().to_string();
crate::model_files::scan_models(dir_str).unwrap_or_default()
} else {
Vec::new()
};
if let Some(resolution) = model_alias::resolve_startup_model(
config.preferred_startup_model_alias.as_deref(),
&models,
) {
eprintln!(
"[bootstrap] auto-selected model: {} ({:?}) reason={:?}",
resolution.alias.as_str(),
resolution.model_path,
resolution.reason
);
config.model_path = Some(resolution.model_path);
// Auto-enable OnAppLaunch when we found a model automatically
config.start_policy = StartPolicy::OnAppLaunch;
}
}
let should_autostart = config.connection_mode == ConnectionMode::Local
&& config.start_policy == StartPolicy::OnAppLaunch
&& config
@@ -199,7 +243,7 @@ pub fn bootstrap_runtime(
}
if should_autostart {
start_runtime_inner(&app, &state, Some(config))
start_runtime_inner(&app, &state, Some(config), RuntimeStatus::Loading)
} else {
get_runtime_snapshot(state)
}
@@ -209,7 +253,15 @@ pub fn start_runtime(
app: AppHandle,
state: State<'_, RuntimeState>,
) -> Result<RuntimeSnapshot, String> {
start_runtime_inner(&app, &state, None)
start_runtime_inner(&app, &state, None, RuntimeStatus::Starting)
}
pub fn start_runtime_with_status(
app: AppHandle,
state: State<'_, RuntimeState>,
startup_status: RuntimeStatus,
) -> Result<RuntimeSnapshot, String> {
start_runtime_inner(&app, &state, None, startup_status)
}
pub fn stop_runtime(state: State<'_, RuntimeState>) -> Result<RuntimeSnapshot, String> {