feat(control,api,ui): worker liveness + run event stream (phase 3)

- worker: heartbeat every loop pass (workers registry); reaper pass
  every ~15s marks a silent worker's running runs crashed and flips
  agents stuck in 'working' to crashed (paused/blocked keep their
  still-accurate status). Idempotent via finish_run's running-guard;
  any surviving worker can reap; no auto-requeue (half-done runs may
  have pushed). Dead workers' registry rows are dropped once settled.
- api: GET /projects/{p}/agents/{name}/events - the persisted
  stream-json event log, oldest-first, cursor-paged by row id;
  AgentOut exposes session_id/worker_id
- frontend: Run events panel in the run detail (assistant text, tool
  chips, result footer with cost/turns, runner notices, raw lines),
  cursor-appended on the existing 5s poll; 'Crashed' filter + danger
  badge; crashed agents show their frozen last frame ('last output
  before crash'); static export regenerated

Suite 290 -> 296 green; next build clean.
This commit is contained in:
2026-07-21 23:10:40 -04:00
parent 650f376934
commit 6c2e73d4ec
20 changed files with 428 additions and 16 deletions
+1
View File
@@ -21,6 +21,7 @@ export function statusTone(status: string | null | undefined): Tone {
case "blocked":
case "rejected":
case "error":
case "crashed":
return "danger";
case "pending":
case "queued":