Compare commits
8 changed files with 97 additions and 198 deletions
|
|
@ -132,8 +132,8 @@ without it has nothing of its own to withdraw.
|
||||||
|
|
||||||
`InitConfig` approvals seed a brand-new agent's proposed config repo.
|
`InitConfig` approvals seed a brand-new agent's proposed config repo.
|
||||||
On approve, hive-c0re seeds it with a default `agent.nix` template and
|
On approve, hive-c0re seeds it with a default `agent.nix` template and
|
||||||
pushes a todo (`push_todo_submitter`) into the submitting agent's
|
sends `HelperEvent::ConfigReady { agent }` to the submitting agent's
|
||||||
in-container store. The submitting agent reviews, edits, and
|
inbox via `notify_submitter`. The submitting agent reviews, edits, and
|
||||||
commits the template; the operator then **spawns** the agent (the
|
commits the template; the operator then **spawns** the agent (the
|
||||||
`Spawn` approval / `◆ R3QU3ST SP4WN` button), which creates the
|
`Spawn` approval / `◆ R3QU3ST SP4WN` button), which creates the
|
||||||
container from that config. From then on, config changes go through a
|
container from that config. From then on, config changes go through a
|
||||||
|
|
@ -626,6 +626,12 @@ root agent. Variants (`hive_sh4re::HelperEvent`):
|
||||||
- `ApprovalResolved { id, agent, commit_ref, status, note }` —
|
- `ApprovalResolved { id, agent, commit_ref, status, note }` —
|
||||||
fired by `actions::approve` + `actions::deny` whenever an
|
fired by `actions::approve` + `actions::deny` whenever an
|
||||||
approval transitions to its terminal state.
|
approval transitions to its terminal state.
|
||||||
|
- `Rebuilt { agent, ok, note }` — `auto_update::rebuild_agent`
|
||||||
|
(covers startup scan + manual `/rebuild` from dashboard) +
|
||||||
|
the `MergeConfigPr` deploy.
|
||||||
|
- `Killed { agent }` — admin `HostRequest::Kill` + dashboard
|
||||||
|
`/kill` + the `Kill` MCP tool.
|
||||||
|
- `Destroyed { agent }` — `actions::destroy`.
|
||||||
- `ContainerCrash { agent, note }` — `crash_watch`: a previously-
|
- `ContainerCrash { agent, note }` — `crash_watch`: a previously-
|
||||||
running container went away with no operator-initiated transient
|
running container went away with no operator-initiated transient
|
||||||
state (Stopping / Restarting / Destroying / Rebuilding) AND no
|
state (Stopping / Restarting / Destroying / Rebuilding) AND no
|
||||||
|
|
@ -634,6 +640,16 @@ root agent. Variants (`hive_sh4re::HelperEvent`):
|
||||||
lifecycle op finishes between two crash-watch polls and the
|
lifecycle op finishes between two crash-watch polls and the
|
||||||
container shows briefly as "stopped without transient" before
|
container shows briefly as "stopped without transient" before
|
||||||
the next start). The root agent can `start` it again or escalate.
|
the next start). The root agent can `start` it again or escalate.
|
||||||
|
- `NeedsLogin { agent }` — sub-agent has no claude session yet.
|
||||||
|
The root agent can't act directly (interactive OAuth); typically flags
|
||||||
|
the operator.
|
||||||
|
- `LoggedIn { agent }` — sub-agent just completed login. The root agent
|
||||||
|
often greets the agent on this event.
|
||||||
|
- `ConfigReady { agent }` — a new agent's proposed config repo was
|
||||||
|
just seeded (post-`InitConfig` approval). The root agent can now
|
||||||
|
edit `/agents/<agent>/config/agent.nix`, commit the changes, and
|
||||||
|
hand off to the operator to **spawn** the agent (the `Spawn`
|
||||||
|
approval creates the container from that config).
|
||||||
- `NeedsUpdate { agent }` — sub-agent's recorded flake rev is
|
- `NeedsUpdate { agent }` — sub-agent's recorded flake rev is
|
||||||
stale. The root agent calls `update(name)` to rebuild — idempotent,
|
stale. The root agent calls `update(name)` to rebuild — idempotent,
|
||||||
no approval required.
|
no approval required.
|
||||||
|
|
@ -646,33 +662,20 @@ root agent. Variants (`hive_sh4re::HelperEvent`):
|
||||||
The recipient responds via `Answer { id, answer }` and the
|
The recipient responds via `Answer { id, answer }` and the
|
||||||
asker sees the matching `QuestionAnswered`.
|
asker sees the matching `QuestionAnswered`.
|
||||||
|
|
||||||
The rest of the original lifecycle notices — `Rebuilt`, `Killed`,
|
Optional `sha` field on `ApprovalResolved` and `Rebuilt` carries the
|
||||||
`Destroyed`, `NeedsLogin`, `LoggedIn`, `ConfigReady` — were pure "FYI,
|
canonical hive-c0re-vouched commit sha. Optional `tag` on the same two
|
||||||
check when convenient" events with no reason to drive an immediate
|
carries the deploy bookkeeping tag — `deployed/<id>` on a successful
|
||||||
turn, so they've been migrated off `HelperEvent` onto
|
build or `failed/<id>` on a failed one, planted by the `MergeConfigPr`
|
||||||
`push_todo`/`push_todo_submitter` (see above): `agent_todo_socket`
|
deploy. Both fields are `Option`: `None` on the paths that don't deploy
|
||||||
push instead of a broker message, `subsystem = "core"`,
|
a new commit (spawn / init_config / meta-update / deny, and
|
||||||
`key = "<event>:<agent>"` for dedup, one free-text `summary` in place
|
|
||||||
of the old structured fields (`rebuilt_todo_summary` renders
|
|
||||||
`Rebuilt`'s former `ok`/`note`/`sha`/`tag` into that string).
|
|
||||||
|
|
||||||
Optional `sha` field on `ApprovalResolved` carries the canonical
|
|
||||||
hive-c0re-vouched commit sha. Optional `tag` carries the deploy
|
|
||||||
bookkeeping tag — `deployed/<id>` on a successful build or
|
|
||||||
`failed/<id>` on a failed one, planted by the `MergeConfigPr` deploy.
|
|
||||||
Both fields are `Option`: `None` on the paths that don't deploy a new
|
|
||||||
commit (spawn / init_config / meta-update / deny, and
|
|
||||||
`auto_update::rebuild_agent` reapplying the existing main, or the
|
`auto_update::rebuild_agent` reapplying the existing main, or the
|
||||||
dashboard `↻ R3BU1LD` button when the lock didn't move). When set,
|
dashboard `↻ R3BU1LD` button when the lock didn't move). When set,
|
||||||
`git show <sha>` against `/agents/<n>/applied.git` inside the
|
`git show <sha>` against `/agents/<n>/applied.git` inside the
|
||||||
bootstrap container yields the exact tree that was referenced.
|
bootstrap container yields the exact tree that was referenced.
|
||||||
|
|
||||||
To add a new lifecycle notice: if it needs to drive an immediate turn
|
To add a new event: new `HelperEvent` variant + call sites + update
|
||||||
(a live conversation like `Ask`/`Answer`, or something genuinely
|
`prompts/system.md` (`<!-- role:manager -->` block, the lifecycle-
|
||||||
urgent), add a `HelperEvent` variant + call sites + update
|
event list) so the root agent knows the new shape.
|
||||||
`prompts/system.md`'s message-event list. If it's "FYI, check when
|
|
||||||
convenient," call `push_todo`/`push_todo_submitter` directly instead —
|
|
||||||
no new wire type needed.
|
|
||||||
|
|
||||||
## Auto-update on startup
|
## Auto-update on startup
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,13 +15,14 @@ Your config repo is mounted **read-only** at `/agents/{label}/config/` — `agen
|
||||||
|
|
||||||
Approval boundary: lifecycle ops on _existing_ direct children (`kill`, `start`, `restart`) are at your discretion — no operator approval needed (requires `lifecycle` tool group). _Creating_ a new agent (two-step: `request_init_config` + `request_apply_commit`) and _changing_ any agent's config (`request_apply_commit`) both go through the approval queue (requires `approvals` tool group). The operator only signs off on changes; you run the day-to-day.
|
Approval boundary: lifecycle ops on _existing_ direct children (`kill`, `start`, `restart`) are at your discretion — no operator approval needed (requires `lifecycle` tool group). _Creating_ a new agent (two-step: `request_init_config` + `request_apply_commit`) and _changing_ any agent's config (`request_apply_commit`) both go through the approval queue (requires `approvals` tool group). The operator only signs off on changes; you run the day-to-day.
|
||||||
|
|
||||||
Messages from sender `system` are hyperhive helper events (JSON body, `event` field discriminates): `approval_resolved`, `container_crash`, `needs_update`, `question_asked`, `question_answered`. Use these to react to lifecycle changes:
|
Messages from sender `system` are hyperhive helper events (JSON body, `event` field discriminates): `approval_resolved`, `config_ready`, `spawned`, `rebuilt`, `killed`, `destroyed`, `container_crash`, `needs_login`, `logged_in`, `needs_update`, `question_asked`, `question_answered`. Use these to react to lifecycle changes:
|
||||||
|
|
||||||
|
- `config_ready` — the proposed config repo for a new agent was just seeded (post-`InitConfig` approval). Review and edit `/agents/<agent>/config/agent.nix`, commit your changes, then call `request_apply_commit` with the commit sha.
|
||||||
|
- `needs_login` — agent has no claude session yet. Flag the operator if it's been long.
|
||||||
|
- `logged_in` — agent just completed login; first useful turn is imminent.
|
||||||
- `needs_update` — agent's flake rev is stale. Call `update(name)` to rebuild — it's idempotent and doesn't need approval.
|
- `needs_update` — agent's flake rev is stale. Call `update(name)` to rebuild — it's idempotent and doesn't need approval.
|
||||||
- `container_crash` — restart with `start(name)`. If it crashes again, ask the operator.
|
- `container_crash` — restart with `start(name)`. If it crashes again, ask the operator.
|
||||||
- otherwise pick up answers to questions you asked.
|
- otherwise greet freshly-spawned agents, retry failed rebuilds, pick up answers to questions you asked.
|
||||||
|
|
||||||
Lifecycle notices that don't need an immediate turn — a new agent spawned, its config repo seeded, a container rebuilt/killed/destroyed, or its login state changing — surface as todos instead of messages now. Call `get_loose_ends` to see them.
|
|
||||||
|
|
||||||
Durable knowledge:
|
Durable knowledge:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -739,18 +739,12 @@ async fn finish_approval(
|
||||||
match approval.kind {
|
match approval.kind {
|
||||||
ApprovalKind::InitConfig => {
|
ApprovalKind::InitConfig => {
|
||||||
if ok {
|
if ok {
|
||||||
coord
|
coord.notify_submitter(
|
||||||
.push_todo_submitter(
|
|
||||||
approval.id,
|
approval.id,
|
||||||
"core",
|
&HelperEvent::ConfigReady {
|
||||||
Some(format!("config_ready:{}", approval.agent)),
|
agent: approval.agent.to_string(),
|
||||||
format!(
|
},
|
||||||
"agent '{}' config repo ready — edit + apply",
|
);
|
||||||
approval.agent
|
|
||||||
),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ApprovalKind::Spawn => {
|
ApprovalKind::Spawn => {
|
||||||
|
|
@ -777,22 +771,16 @@ async fn finish_approval(
|
||||||
// lifecycle event. (It is never a first spawn — the agent already
|
// lifecycle event. (It is never a first spawn — the agent already
|
||||||
// exists — so it never needs the Spawned arm above.)
|
// exists — so it never needs the Spawned arm above.)
|
||||||
ApprovalKind::MergeConfigPr => {
|
ApprovalKind::MergeConfigPr => {
|
||||||
let summary = crate::coordinator::rebuilt_todo_summary(
|
coord.notify_submitter(
|
||||||
approval.agent.as_str(),
|
|
||||||
ok,
|
|
||||||
note.as_deref(),
|
|
||||||
approval.fetched_sha.as_deref(),
|
|
||||||
terminal_tag.as_deref(),
|
|
||||||
);
|
|
||||||
coord
|
|
||||||
.push_todo_submitter(
|
|
||||||
approval.id,
|
approval.id,
|
||||||
"core",
|
&HelperEvent::Rebuilt {
|
||||||
Some(format!("rebuilt:{}", approval.agent)),
|
agent: approval.agent.to_string(),
|
||||||
summary,
|
ok,
|
||||||
None,
|
note,
|
||||||
)
|
sha: approval.fetched_sha.clone(),
|
||||||
.await;
|
tag: terminal_tag,
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
// UpdateMetaInputs / SchedulePrompt: ApprovalResolved already
|
// UpdateMetaInputs / SchedulePrompt: ApprovalResolved already
|
||||||
// carries the result. No separate lifecycle event needed.
|
// carries the result. No separate lifecycle event needed.
|
||||||
|
|
@ -963,15 +951,9 @@ pub async fn destroy(coord: &Arc<Coordinator>, name: &str, purge: bool) -> Resul
|
||||||
tracing::warn!(%name, error = ?e, "agent_power: remove on destroy failed");
|
tracing::warn!(%name, error = ?e, "agent_power: remove on destroy failed");
|
||||||
}
|
}
|
||||||
drop(guard);
|
drop(guard);
|
||||||
coord
|
coord.notify_manager(&HelperEvent::Destroyed {
|
||||||
.push_todo(
|
agent: name.to_owned(),
|
||||||
hive_sh4re::MANAGER_AGENT,
|
});
|
||||||
"core",
|
|
||||||
Some(format!("destroyed:{name}")),
|
|
||||||
format!("agent '{name}' destroyed"),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
// Container row disappeared — rescan so the dashboard fires
|
// Container row disappeared — rescan so the dashboard fires
|
||||||
// `ContainerRemoved` for the gone row, then emit the
|
// `ContainerRemoved` for the gone row, then emit the
|
||||||
// tombstones snapshot (gained one on destroy, lost one on
|
// tombstones snapshot (gained one on destroy, lost one on
|
||||||
|
|
|
||||||
|
|
@ -1613,82 +1613,3 @@ impl Coordinator {
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `push_todo`/`push_todo_submitter` summary text for a rebuild outcome —
|
|
||||||
/// shared by the two `Rebuilt` call sites (`actions::finish_approval`'s
|
|
||||||
/// `MergeConfigPr` arm, `job_queue::exec::run_emit_rebuilt`) so the wording
|
|
||||||
/// stays identical regardless of which path fired. Pure + independently
|
|
||||||
/// testable, unlike the old `HelperEvent::Rebuilt`'s separate `sha`/`tag`
|
|
||||||
/// fields — those become part of the summary text itself now, since a todo
|
|
||||||
/// carries one string, not a structured payload.
|
|
||||||
#[must_use]
|
|
||||||
pub fn rebuilt_todo_summary(
|
|
||||||
agent: &str,
|
|
||||||
ok: bool,
|
|
||||||
note: Option<&str>,
|
|
||||||
sha: Option<&str>,
|
|
||||||
tag: Option<&str>,
|
|
||||||
) -> String {
|
|
||||||
use std::fmt::Write as _;
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
return format!(
|
|
||||||
"agent '{agent}' rebuild FAILED: {}",
|
|
||||||
note.unwrap_or("unknown error")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut summary = format!("agent '{agent}' rebuilt");
|
|
||||||
if let Some(sha) = sha {
|
|
||||||
let _ = write!(summary, " @ {sha}");
|
|
||||||
}
|
|
||||||
if let Some(tag) = tag {
|
|
||||||
let _ = write!(summary, " ({tag})");
|
|
||||||
}
|
|
||||||
summary
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod rebuilt_todo_summary_tests {
|
|
||||||
use super::rebuilt_todo_summary;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn failure_reports_the_note() {
|
|
||||||
assert_eq!(
|
|
||||||
rebuilt_todo_summary("iris", false, Some("build failed"), None, None),
|
|
||||||
"agent 'iris' rebuild FAILED: build failed"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn failure_without_a_note_says_unknown() {
|
|
||||||
assert_eq!(
|
|
||||||
rebuilt_todo_summary("iris", false, None, None, None),
|
|
||||||
"agent 'iris' rebuild FAILED: unknown error"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn success_with_no_sha_or_tag_is_bare() {
|
|
||||||
assert_eq!(
|
|
||||||
rebuilt_todo_summary("iris", true, None, None, None),
|
|
||||||
"agent 'iris' rebuilt"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn success_with_sha_and_tag_carries_both() {
|
|
||||||
assert_eq!(
|
|
||||||
rebuilt_todo_summary("iris", true, None, Some("abc123"), Some("deployed/42")),
|
|
||||||
"agent 'iris' rebuilt @ abc123 (deployed/42)"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn success_with_sha_only() {
|
|
||||||
assert_eq!(
|
|
||||||
rebuilt_todo_summary("iris", true, None, Some("abc123"), None),
|
|
||||||
"agent 'iris' rebuilt @ abc123"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,7 @@ pub(super) async fn run_node(
|
||||||
outcome,
|
outcome,
|
||||||
} => run_resolve_approval(coord, coord.job_queue.dag_of(id), *approval_id, *outcome).await,
|
} => run_resolve_approval(coord, coord.job_queue.dag_of(id), *approval_id, *outcome).await,
|
||||||
NodeKind::EmitRebuilt { ok, .. } => {
|
NodeKind::EmitRebuilt { ok, .. } => {
|
||||||
run_emit_rebuilt(coord, agent, coord.job_queue.dag_of(id), *ok).await;
|
run_emit_rebuilt(coord, agent, coord.job_queue.dag_of(id), *ok);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
NodeKind::SetWanted { up, .. } => run_set_wanted(coord, agent, *up),
|
NodeKind::SetWanted { up, .. } => run_set_wanted(coord, agent, *up),
|
||||||
|
|
@ -144,23 +144,19 @@ async fn run_resolve_approval(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Emit this agent's rebuild-complete todo. `ok` is not computed — it is which
|
/// Emit this agent's `Rebuilt` manager event. `ok` is not computed — it is which
|
||||||
/// of the tail pair the graph let run. The failure note comes from the DAG's
|
/// of the tail pair the graph let run. The failure note comes from the DAG's
|
||||||
/// first failing node, since the branch knows *that* it failed but not *why*.
|
/// first failing node, since the branch knows *that* it failed but not *why*.
|
||||||
async fn run_emit_rebuilt(coord: &Arc<Coordinator>, agent: &str, dag_id: Option<u64>, ok: bool) {
|
fn run_emit_rebuilt(coord: &Arc<Coordinator>, agent: &str, dag_id: Option<u64>, ok: bool) {
|
||||||
let note = (!ok)
|
coord.notify_manager(&hive_sh4re::HelperEvent::Rebuilt {
|
||||||
|
agent: agent.to_owned(),
|
||||||
|
ok,
|
||||||
|
note: (!ok)
|
||||||
.then(|| dag_id.and_then(|dag| coord.job_queue.first_error(dag)))
|
.then(|| dag_id.and_then(|dag| coord.job_queue.first_error(dag)))
|
||||||
.flatten();
|
.flatten(),
|
||||||
let summary = crate::coordinator::rebuilt_todo_summary(agent, ok, note.as_deref(), None, None);
|
sha: None,
|
||||||
coord
|
tag: None,
|
||||||
.push_todo(
|
});
|
||||||
hive_sh4re::MANAGER_AGENT,
|
|
||||||
"core",
|
|
||||||
Some(format!("rebuilt:{agent}")),
|
|
||||||
summary,
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write the agent's durable power intent — the DAG-node form of the old
|
/// Write the agent's durable power intent — the DAG-node form of the old
|
||||||
|
|
@ -429,15 +425,9 @@ async fn run_stop(coord: &Arc<Coordinator>, name: &str) -> Result<()> {
|
||||||
// own kind now.
|
// own kind now.
|
||||||
crate::lifecycle::kill(name).await?;
|
crate::lifecycle::kill(name).await?;
|
||||||
coord.unregister_agent(name);
|
coord.unregister_agent(name);
|
||||||
coord
|
coord.notify_manager(&hive_sh4re::HelperEvent::Killed {
|
||||||
.push_todo(
|
agent: name.to_owned(),
|
||||||
hive_sh4re::MANAGER_AGENT,
|
});
|
||||||
"core",
|
|
||||||
Some(format!("killed:{name}")),
|
|
||||||
format!("agent '{name}' killed"),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
coord.rescan_containers_and_emit().await;
|
coord.rescan_containers_and_emit().await;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -129,15 +129,9 @@ pub(super) async fn handle_kill(coord: &Arc<Coordinator>, agent: &str, name: &st
|
||||||
.await;
|
.await;
|
||||||
match result {
|
match result {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
coord
|
coord.notify_manager(&hive_sh4re::HelperEvent::Killed {
|
||||||
.push_todo(
|
agent: name.to_owned(),
|
||||||
hive_sh4re::MANAGER_AGENT,
|
});
|
||||||
"core",
|
|
||||||
Some(format!("killed:{name}")),
|
|
||||||
format!("agent '{name}' killed"),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
Response::Ok
|
Response::Ok
|
||||||
}
|
}
|
||||||
Err(e) => Response::Err {
|
Err(e) => Response::Err {
|
||||||
|
|
|
||||||
|
|
@ -56,8 +56,7 @@ pub fn spawn(coord: Arc<Coordinator>) {
|
||||||
¤t_logged_in,
|
¤t_logged_in,
|
||||||
&sub_agents,
|
&sub_agents,
|
||||||
&prev_sub_agents,
|
&prev_sub_agents,
|
||||||
)
|
);
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
// Periodic container rescan — catches state flips that
|
// Periodic container rescan — catches state flips that
|
||||||
// happen outside our mutation surface (operator runs
|
// happen outside our mutation surface (operator runs
|
||||||
|
|
@ -123,7 +122,7 @@ fn is_deliberate_stop(active: Option<bool>, recently_cleared: Option<bool>) -> b
|
||||||
active.unwrap_or(false) || recently_cleared.unwrap_or(false)
|
active.unwrap_or(false) || recently_cleared.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn emit_login_transitions(
|
fn emit_login_transitions(
|
||||||
coord: &Coordinator,
|
coord: &Coordinator,
|
||||||
prev: &HashSet<String>,
|
prev: &HashSet<String>,
|
||||||
current: &HashSet<String>,
|
current: &HashSet<String>,
|
||||||
|
|
@ -132,15 +131,9 @@ async fn emit_login_transitions(
|
||||||
) {
|
) {
|
||||||
for agent in current.difference(prev) {
|
for agent in current.difference(prev) {
|
||||||
tracing::info!(%agent, "agent logged in");
|
tracing::info!(%agent, "agent logged in");
|
||||||
coord
|
coord.notify_manager(&hive_sh4re::HelperEvent::LoggedIn {
|
||||||
.push_todo(
|
agent: agent.clone(),
|
||||||
hive_sh4re::MANAGER_AGENT,
|
});
|
||||||
"core",
|
|
||||||
Some(format!("logged_in:{agent}")),
|
|
||||||
format!("agent '{agent}' logged in"),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
// Detect transitions into "needs login": an agent that was previously
|
// Detect transitions into "needs login": an agent that was previously
|
||||||
// logged-in goes unsigned (credentials deleted), OR a brand-new agent
|
// logged-in goes unsigned (credentials deleted), OR a brand-new agent
|
||||||
|
|
@ -163,15 +156,9 @@ async fn emit_login_transitions(
|
||||||
.collect();
|
.collect();
|
||||||
for agent in current_needs.difference(&prev_needs) {
|
for agent in current_needs.difference(&prev_needs) {
|
||||||
tracing::info!(%agent, "agent needs login");
|
tracing::info!(%agent, "agent needs login");
|
||||||
coord
|
coord.notify_manager(&hive_sh4re::HelperEvent::NeedsLogin {
|
||||||
.push_todo(
|
agent: (*agent).to_owned(),
|
||||||
hive_sh4re::MANAGER_AGENT,
|
});
|
||||||
"core",
|
|
||||||
Some(format!("needs_login:{agent}")),
|
|
||||||
format!("agent '{agent}' needs login"),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -507,6 +507,27 @@ pub enum HelperEvent {
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
tag: Option<String>,
|
tag: Option<String>,
|
||||||
},
|
},
|
||||||
|
/// A container was rebuilt (auto-update or manual).
|
||||||
|
Rebuilt {
|
||||||
|
agent: String,
|
||||||
|
ok: bool,
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
note: Option<String>,
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
sha: Option<String>,
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
tag: Option<String>,
|
||||||
|
},
|
||||||
|
/// A new agent's proposed config repo was seeded post-`InitConfig`.
|
||||||
|
ConfigReady { agent: String },
|
||||||
|
/// A sub-agent's container was stopped (systemd unit down; state kept).
|
||||||
|
Killed { agent: String },
|
||||||
|
/// A sub-agent's container was torn down (state dirs preserved by default).
|
||||||
|
Destroyed { agent: String },
|
||||||
|
/// A sub-agent's container has no claude session yet.
|
||||||
|
NeedsLogin { agent: String },
|
||||||
|
/// A sub-agent just completed claude login.
|
||||||
|
LoggedIn { agent: String },
|
||||||
/// A sub-agent's recorded flake rev is stale relative to hyperhive.
|
/// A sub-agent's recorded flake rev is stale relative to hyperhive.
|
||||||
NeedsUpdate { agent: String },
|
NeedsUpdate { agent: String },
|
||||||
/// Container exited without an operator-initiated stop (crash).
|
/// Container exited without an operator-initiated stop (crash).
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue