swarm: add a declared "paused" agent wanted state
mara (#4170): swarm-ui's wanted-state dropdown could only ever declare up/offline/destroy, with no way to swarm-declare the existing hive-local turn-loop pause (`hivectl agent pause|resume`). `AgentState::Paused` is not a fifth peer of Up/Offline/Destroyed on the power axis this enum otherwise answers — it's Up plus an orthogonal turn-loop pause. `hive-c0re`'s `workers::wanted` reconcile loop now decides the two axes independently (`decide` for power, the new `decide_pause` for the marker), so a stopped agent declared Paused converges with both a Start and a Pause in the same pass. Known, deliberate limitation: a Paused declaration on an agent this hive has never deployed only reaches Deploy this pass — writing the pause marker into a harness dir that may not exist yet was judged not worth the risk, so it converges on the next pass once the agent is present instead. swarm-ui's WantedMenu gains a fourth "paused" option (warning-tone badge). No separate "resume" entry — selecting "up" from a paused row already clears the marker via the same decide_pause path. Pause/resume marker writes go through one shared Coordinator::set_paused_by_name helper, used by both the interactive dashboard pause/resume handlers and this reconcile loop, instead of each duplicating the parse-name/write-marker/track-rescan shape. swarm-ui's "offline" and "paused" confirm dialogs share one confirmTarget state and one ConfirmDialog instead of two near-identical copies. Closes #4170
This commit is contained in:
parent
560f727797
commit
513554fe9a
6 changed files with 483 additions and 55 deletions
|
|
@ -440,6 +440,26 @@ pub struct ApprovalAdded<'a> {
|
|||
pub pr_number: Option<u64>,
|
||||
}
|
||||
|
||||
/// The two ways [`Coordinator::set_paused_by_name`] can fail. See its own
|
||||
/// doc comment for why this is a distinct type rather than one
|
||||
/// `anyhow::Error` (`Write` already wraps one, `BadName` never needs to).
|
||||
#[derive(Debug)]
|
||||
pub enum SetPausedByNameError {
|
||||
/// `name` did not parse as a [`hive_types::Ident`] — the parse error's
|
||||
/// own message.
|
||||
BadName(&'static str),
|
||||
Write(anyhow::Error),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for SetPausedByNameError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
SetPausedByNameError::BadName(msg) => write!(f, "bad agent name: {msg}"),
|
||||
SetPausedByNameError::Write(e) => write!(f, "{e}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Coordinator {
|
||||
pub fn open(
|
||||
db_path: &Path,
|
||||
|
|
@ -1449,6 +1469,28 @@ impl Coordinator {
|
|||
crate::priv_client::set_agent_paused(name.as_str(), paused).await
|
||||
}
|
||||
|
||||
/// [`Self::set_paused`] from a plain agent-name string, parsing it
|
||||
/// first — the exact shape both `dashboard::lifecycle_ops`'s
|
||||
/// `/api/pause`/`/api/resume` handlers and `workers::wanted`'s
|
||||
/// reconcile loop need, previously duplicated between them (mara, PR
|
||||
/// review: "could this be a bit less duplicated code?"). A distinct
|
||||
/// error variant per failure mode, not a single
|
||||
/// `anyhow::Error`, because the two callers report the two cases
|
||||
/// differently: a bad name is the caller's mistake (400 there, a
|
||||
/// warned skip in the reconcile loop), a write failure is this
|
||||
/// host's (500 there, also a warned skip here).
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// `BadName` when `name` is not a legal [`hive_types::Ident`];
|
||||
/// `Write` for everything [`Self::set_paused`] itself can fail with.
|
||||
pub async fn set_paused_by_name(name: &str, paused: bool) -> Result<(), SetPausedByNameError> {
|
||||
let id = hive_types::Ident::parse(name).map_err(SetPausedByNameError::BadName)?;
|
||||
Self::set_paused(&id, paused)
|
||||
.await
|
||||
.map_err(SetPausedByNameError::Write)
|
||||
}
|
||||
|
||||
/// Enumerate names that have a persistent state dir under
|
||||
/// `/var/lib/hyperhive/agents/` (i.e. config / claude creds /
|
||||
/// notes survive). Includes both currently-existing containers and
|
||||
|
|
|
|||
Loading…
Reference in a new issue