herdr phase 1c: wizard runtime picker + on_launch auto-dispatch

Closes the operator loop for the second-runtime path. Missions
created with runtime='local_herdr' now spawn a Herdr pane on their
target_node automatically on draft→running.

Frontend (MissionWizard):
  - Step 4 grows a "Runtime" section above Schedule
  - Radio: "Hosted (ZeroClaw)" default | "On a fleet node (Herdr)"
  - Local-Herdr shows a dropdown of ONLINE nodes only (from
    /api/nodes filtered by status='online')
  - canNext blocks Next when local_herdr picked without a node
  - Review step shows "Runtime: Herdr on <node-name>" or "Hosted"
  - Empty-online-nodes state hints "Connect one from INFRA first"

Backend:
  - mission_orchestrator::on_launch grows a NodeHub param; when
    mission.runtime_kind='local_herdr' + target_node_id set +
    hub present → calls fleet_herdr::dispatch(). Non-fatal:
    logs and continues so a research_only mission with a Herdr
    runtime chosen accidentally still boots the team.
  - routes::missions::set_status passes state.node_hub through.
  - Test call sites updated to pass None for the new param
    (integration tests don't drive real fleet nodes).

CLI stub: on_launch currently hard-codes cli="claude" for the
Herdr pane. Phase 4 will read that from the team template so a
research team → kimi, gpu team → claude, etc.

Verified: cargo check --workspace + cargo test
-p cm-api --test mission_orchestrator + tsc --noEmit all green.
This commit is contained in:
Omar Sobh
2026-07-20 10:02:40 -07:00
parent 47f986257f
commit 2b3ec27757
4 changed files with 137 additions and 7 deletions
+7 -1
View File
@@ -432,7 +432,13 @@ pub async fn set_status(
if prior.status == "draft" && body.status == "running" {
if let Err(e) =
crate::mission_orchestrator::on_launch(&state.pool, user.workspace_id, user.user_id, id)
crate::mission_orchestrator::on_launch(
&state.pool,
user.workspace_id,
user.user_id,
id,
Some(state.node_hub.clone()),
)
.await
{
eprintln!("mission {id}: on_launch failed: {e}");