diff --git a/hive-c0re/src/actions.rs b/hive-c0re/src/actions.rs index 3ad26267..9e9726f3 100644 --- a/hive-c0re/src/actions.rs +++ b/hive-c0re/src/actions.rs @@ -559,14 +559,17 @@ async fn run_apply_commit( ); } - coord.set_queue_step(queue_entry_id, "nixos-container update"); // Container-level rebuild (or first-time create) against meta#. + // Step labels are emitted inside rebuild_no_meta via the callback so + // the dashboard reflects actual phase progress rather than a static + // "nixos-container update" label for the whole multi-minute window. let build_result = lifecycle::rebuild_no_meta( &approval.agent, agent_dir, applied_dir, claude_dir, notes_dir, + &|step| coord.set_queue_step(queue_entry_id, step), ) .await; diff --git a/hive-c0re/src/auto_update.rs b/hive-c0re/src/auto_update.rs index dbf75e86..456fd958 100644 --- a/hive-c0re/src/auto_update.rs +++ b/hive-c0re/src/auto_update.rs @@ -86,7 +86,6 @@ pub async fn rebuild_agent( // lifecycle_action; this catches the auto-update scan + any // other direct caller. let guard = coord.transient_guard(name, crate::coordinator::TransientKind::Rebuilding); - coord.set_queue_step(queue_entry_id, "nixos-container update"); let result = lifecycle::rebuild( name, &coord.hyperhive_flake, @@ -97,6 +96,7 @@ pub async fn rebuild_agent( coord.dashboard_port, &coord.operator_pronouns, &coord.context_window_tokens, + &|step| coord.set_queue_step(queue_entry_id, step), ) .await; drop(guard); diff --git a/hive-c0re/src/lifecycle.rs b/hive-c0re/src/lifecycle.rs index 17ab511b..9dc9d080 100644 --- a/hive-c0re/src/lifecycle.rs +++ b/hive-c0re/src/lifecycle.rs @@ -354,6 +354,7 @@ pub async fn rebuild( dashboard_port: u16, operator_pronouns: &str, context_window_tokens: &std::collections::HashMap, + on_step: &(dyn Fn(&str) + Send + Sync), ) -> Result<()> { // Sync the meta flake (idempotent — no-op when the rendered // flake matches disk) so a manual rebuild from the dashboard @@ -373,7 +374,7 @@ pub async fn rebuild( // `applied//main` currently points at (deployed/). // Commits the lock if it changed. crate::meta::lock_update_for_rebuild(name).await?; - rebuild_no_meta(name, agent_dir, applied_dir, claude_dir, notes_dir).await + rebuild_no_meta(name, agent_dir, applied_dir, claude_dir, notes_dir, on_step).await } /// Container-level rebuild without touching the meta repo. Callers @@ -381,12 +382,18 @@ pub async fn rebuild( /// drives meta through the two-phase prepare/finalize/abort flow) /// use this directly. Public `rebuild` wraps it with idempotent meta /// sync + lock-bump-and-commit. +/// +/// `on_step` is called at each phase boundary with a short human-readable +/// label so callers can surface progress (e.g. update the rebuild-queue +/// step shown in the dashboard). Pass `&|_| ()` when progress reporting +/// is not needed. pub async fn rebuild_no_meta( name: &str, agent_dir: &Path, applied_dir: &Path, claude_dir: &Path, notes_dir: &Path, + on_step: &(dyn Fn(&str) + Send + Sync), ) -> Result<()> { validate(name)?; if let Some(other) = port_collision(name).await { @@ -433,9 +440,12 @@ pub async fn rebuild_no_meta( // When the container is already stopped there's no // downtime to shave — let `update` do the build inline // rather than evaluating the flake twice for nothing. + on_step("nix build"); prebuild_toplevel(name, &flake_ref).await?; + on_step("nixos-container stop"); run(&["stop", &container]).await?; } + on_step("nixos-container update"); run(&["update", &container, "--flake", &flake_ref]).await?; if was_running { // Normal path: start into the new generation. The activation @@ -450,6 +460,7 @@ pub async fn rebuild_no_meta( // `stop` requests a graceful SIGTERM drain; `kill` then SIGKILLs // any lingering processes so the next `start` enters a clean state // without a generation transition, letting the activation succeed. + on_step("nixos-container start"); if let Err(start_err) = run(&["start", &container]).await { tracing::warn!( container = %container, @@ -488,10 +499,12 @@ pub async fn rebuild_no_meta( // the build fails, no container record is left around to // clean up — so a pre-build adds nothing but a duplicate // eval. + on_step("nixos-container create"); run(&["create", &container, "--flake", &flake_ref]).await?; set_nspawn_flags(&container, agent_dir, claude_dir, notes_dir)?; set_resource_limits(&container)?; systemd_daemon_reload().await?; + on_step("nixos-container start"); run(&["start", &container]).await } } diff --git a/hive-c0re/src/server.rs b/hive-c0re/src/server.rs index 52b436c7..c4d7d2cc 100644 --- a/hive-c0re/src/server.rs +++ b/hive-c0re/src/server.rs @@ -141,6 +141,7 @@ async fn dispatch(req: &HostRequest, coord: Arc) -> HostResponse { coord.dashboard_port, &coord.operator_pronouns, &coord.context_window_tokens, + &|_| (), ) .await; // Mirror auto_update::rebuild_agent — the manager wants