hive-agent: a logged-in agent polls the swarm login, and logs out when it is deleted
wait_for_login read the store leaf only while the agent waited for a login. A logged-in agent now reads it every 5 minutes, and a paused one once before it resumes, so a login made or deleted in the swarm UI reaches an agent that never failed. A newer version is installed; a store that answers nothing stored after a version was installed means the stored login was deleted, and the agent is logged out as /api/logout does it, with the recorded version forgotten so the next login installs. A read that fails changes nothing.
This commit is contained in:
parent
71a4d1d255
commit
a2c9e9a8d4
1 changed files with 188 additions and 23 deletions
|
|
@ -6,9 +6,10 @@ use axum::{
|
|||
response::{IntoResponse, Response},
|
||||
};
|
||||
|
||||
use crate::events::Bus;
|
||||
use crate::login::LoginState;
|
||||
|
||||
use super::AppState;
|
||||
use super::{AppState, LoginStateCell};
|
||||
|
||||
/// Operator-driven `/logout`: SIGINT claude, delete the credential
|
||||
/// files (via [`crate::login::clear_session`]), flip `LoginState::NeedsLogin`.
|
||||
|
|
@ -20,6 +21,13 @@ use super::AppState;
|
|||
/// (the `/api/logout` bullet) for the three-step rationale +
|
||||
/// preservation invariants.
|
||||
pub(super) async fn post_logout(State(state): State<AppState>) -> Response {
|
||||
let said = force_logout(&state.login, &state.bus, "operator: /logout").await;
|
||||
(axum::http::StatusCode::OK, format!("ok: {said}")).into_response()
|
||||
}
|
||||
|
||||
/// The three steps of a logout, shared by `/api/logout` and the swarm login
|
||||
/// watch: `by` heads the Note. Returns what was wiped, for the response.
|
||||
pub(crate) async fn force_logout(login: &LoginStateCell, bus: &Bus, by: &str) -> String {
|
||||
// Step 1: SIGINT claude (best-effort, matches `post_cancel_turn`).
|
||||
let _ = super::sigint_claude();
|
||||
// Step 2: delete OAuth credential files only — login::clear_session owns
|
||||
|
|
@ -34,19 +42,13 @@ pub(super) async fn post_logout(State(state): State<AppState>) -> Response {
|
|||
};
|
||||
// Step 3: flip LoginState + emit Note. Turn loop sees the flip on
|
||||
// its next iteration and parks into wait_for_login.
|
||||
*state.login.lock().unwrap() = LoginState::NeedsLogin;
|
||||
state.bus.emit(crate::events::LiveEvent::Note {
|
||||
text: format!(
|
||||
"operator: /logout — {wipe_summary} in {}{warn_suffix}",
|
||||
dir.display()
|
||||
),
|
||||
*login.lock().unwrap() = LoginState::NeedsLogin;
|
||||
let said = format!("{wipe_summary} in {}{warn_suffix}", dir.display());
|
||||
bus.emit(crate::events::LiveEvent::Note {
|
||||
text: format!("{by} — {said}"),
|
||||
});
|
||||
state.bus.emit_status("needs_login_idle");
|
||||
(
|
||||
axum::http::StatusCode::OK,
|
||||
format!("ok: {wipe_summary} in {}{warn_suffix}", dir.display()),
|
||||
)
|
||||
.into_response()
|
||||
bus.emit_status("needs_login_idle");
|
||||
said
|
||||
}
|
||||
|
||||
/// Human-readable summary of a [`crate::login::ClearedSession`] outcome for
|
||||
|
|
|
|||
Loading…
Reference in a new issue