feat(ui): the microVM path is reachable from the mission wizard
Everything built today — Firecracker missions, the four backends, the local GPU one — was unreachable from the dashboard. The wizard offered `zeroclaw` and `local_herdr` and nothing else, so a mission created in the UI could not be a microVM mission at all, and `local-ornith`/`glm`/`kimi` were API-only. Testing "our workflows in the UI" would have exercised none of it. Adds the runtime option and a backend picker, fed by a new `GET /api/fleet/backends` that returns `mission_roster::available_backends` verbatim — the SAME list the roster planner is handed, not a second one. Its two rules are both load-bearing and neither is visible from a node's capabilities alone: the image must be built on an online node, and the backend must have a credential contract. `agent-terminal` passes the first and fails the second — bootable, with nothing for the agent inside to authenticate with — so offering it would produce a mission that validates, launches, and dies at the agent turn. Ids are deployment vocabulary, so the picker labels them: a user choosing between `local-ornith` and `canary-claude` should not have to know which company each one bills. An empty list says why (no rootfs built) instead of showing an empty dropdown, and no node is chosen for a microVM mission because `vm_placement` picks it per phase. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
1f6108f769
commit
c66c3c6377
@@ -455,3 +455,49 @@ pub async fn capacity(
|
||||
pub struct CapacityQuery {
|
||||
pub backend: Option<String>,
|
||||
}
|
||||
|
||||
/// `GET /api/fleet/backends` — the microVM backends a mission may actually use.
|
||||
///
|
||||
/// The SAME `available_backends` the roster planner is handed, not a second
|
||||
/// list. The two rules it applies are both load-bearing and neither is obvious
|
||||
/// from a node's capabilities alone: a backend must be built on an online node,
|
||||
/// and it must have a credential contract. `agent-terminal` satisfies the first
|
||||
/// and not the second — bootable, with nothing for the agent inside to
|
||||
/// authenticate with — so offering it would produce a mission that validates,
|
||||
/// launches, and fails at the agent turn, which is the expensive kind of late.
|
||||
///
|
||||
/// Exists because the UI had no backend selector at all: every mission created
|
||||
/// from the dashboard ran on `claude`, so `local-ornith`, `glm` and `kimi` were
|
||||
/// reachable only by calling the API directly.
|
||||
pub async fn backends(
|
||||
State(state): State<AppState>,
|
||||
Authed(user): Authed,
|
||||
) -> Result<Json<Value>, ApiError> {
|
||||
let ws = user.workspace_id.as_uuid().to_owned();
|
||||
let list = crate::mission_roster::available_backends(&state.pool, ws)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
eprintln!("fleet backends: {e}");
|
||||
ApiError::Internal
|
||||
})?;
|
||||
Ok(Json(json!({
|
||||
"backends": list.iter().map(|b| json!({
|
||||
"id": b,
|
||||
"label": backend_label(b),
|
||||
})).collect::<Vec<_>>(),
|
||||
})))
|
||||
}
|
||||
|
||||
/// A name a person can choose between. The ids are deployment vocabulary
|
||||
/// (`local-ornith`, `canary-claude`); a picker showing those alone asks the user
|
||||
/// to know which company each one bills.
|
||||
fn backend_label(id: &str) -> String {
|
||||
match id {
|
||||
"claude" | "default" => "Claude (Anthropic subscription)".into(),
|
||||
"canary-claude" => "Claude — candidate CLI (canary)".into(),
|
||||
"glm" => "GLM 4.7 (z.ai)".into(),
|
||||
"kimi" => "Kimi (Moonshot)".into(),
|
||||
"local-ornith" => "Ornith 9B — this fleet's own GPU".into(),
|
||||
other => other.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user