harness: flip into needs_login on 401 mid-turn (closes #419)

This commit is contained in:
damocles 2026-05-25 21:32:01 +02:00 committed by Mara
commit 799804e3d1
6 changed files with 164 additions and 22 deletions

View file

@ -684,19 +684,33 @@ impl Bus {
/// `Arc<Mutex<LoginState>>` should also call this so the web UI
/// drops its periodic /api/state poll while a turn loop is running.
///
/// `"rate_limited"` sets the rate-limited flag and writes a sentinel
/// file at `{state_dir}/hyperhive-rate-limited` so the host-side
/// dashboard can show the status without a live socket call.
/// Any other status clears the flag and removes the sentinel.
/// Sentinel files survive harness restart so the host-side dashboard
/// can render the status without a live socket call:
/// - `"rate_limited"` writes `{state_dir}/hyperhive-rate-limited`
/// (cleared by any other status).
/// - `"needs_login_idle"` writes `{state_dir}/hyperhive-needs-login`
/// so a 401-triggered re-auth flag persists across harness restart
/// (#419). The web UI's `/login` POST handler clears it via
/// `clear_needs_login_sentinel` once the operator re-auths.
/// - `"online"` clears both sentinels — the agent is healthy again.
pub fn emit_status(&self, status: impl Into<String>) {
let status = status.into();
let sentinel = crate::paths::state_dir().join("hyperhive-rate-limited");
let rate_limited_path = crate::paths::state_dir().join("hyperhive-rate-limited");
let needs_login_path = crate::paths::state_dir().join("hyperhive-needs-login");
if status == "rate_limited" {
self.rate_limited.store(true, Ordering::Relaxed);
let _ = std::fs::write(&sentinel, b"");
let _ = std::fs::write(&rate_limited_path, b"");
} else {
self.rate_limited.store(false, Ordering::Relaxed);
let _ = std::fs::remove_file(&sentinel);
let _ = std::fs::remove_file(&rate_limited_path);
}
if status == "needs_login_idle" {
let _ = std::fs::write(&needs_login_path, b"");
} else if status == "online" {
// Re-auth completed (or manual flip back to online) — drop
// the sentinel. `needs_login_in_progress` is a transient
// mid-flow status and shouldn't clear yet.
let _ = std::fs::remove_file(&needs_login_path);
}
self.emit(LiveEvent::StatusChanged { status });
}