feat: failure-injection theater — cloud outage → on-board Qwen, live

Prototype #1 of the APESS innovation set: make the Uno Q's unique story —
a real LLM running on the board — visible and demonstrable. A team can
inject a cloud outage and watch their agent keep reasoning on the on-board
Qwen, which is exactly ADD Layer 4 (failure modes) / Layer 5 (edge vs cloud)
made concrete instead of merely documented.

- New `fallback` NodeActivityKind (api + client, kept in sync). `mapNodeEvent`
  now recognizes ZeroClaw's failover log lines ("ModelProvider call failed",
  "Exhausted retries, trying next model") and surfaces them as a first-class
  resilience signal — NOT swallowed by the generic error branch. Rendered in
  a distinct rose in the board-activity feeds.
- ResiliencePanel (Module 2): a "Simulate cloud outage" button that routes a
  prompt through the board's new `chaos` agent; streams the live failover and
  shows a "survived" banner when a fallback is followed by a response. Sim mode
  plays a deterministic failover so it demos with zero hardware.
- Board config: a `chaos` agent backed by a deliberately-dead cloud endpoint
  (:9099) with `fallback = ["llamacpp.local"]`, so the outage is deterministic
  and workshop-safe (no tunnel-hacking, no real cloud to kill).

Tests: api 34, front-end 191 (+ResiliencePanel), typecheck clean, build passes.
Live-on-board validation pending (board USB link down at commit time).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
Omar Sobh
2026-07-04 18:54:52 -07:00
co-authored by Claude Opus 4.8
parent 1ad6da64f7
commit 42c4225425
10 changed files with 276 additions and 4 deletions
+2
View File
@@ -8,6 +8,7 @@ const KIND_DOT: Record<NodeActivityKind, string> = {
flash: 'bg-primary',
error: 'bg-destructive',
response: 'bg-teal',
fallback: 'bg-rose',
}
export interface BoardActivityProps {
@@ -36,6 +37,7 @@ export function BoardActivity({ activity, nameFor }: BoardActivityProps) {
'truncate',
e.kind === 'error' && 'text-destructive',
e.kind === 'flash' && 'text-foreground font-medium',
e.kind === 'fallback' && 'text-rose font-medium',
)}
>
{e.label}