- 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.
Rebuild the bundled web UI as a Next.js (React + TypeScript) static export
implementing the Claude Activity Dashboard design: a left-nav "Control Center"
hub over Runs, Repositories, Agents, Approvals, Git Servers, Activity, and
Shared, styled with the Leeworks design-system tokens (flat, dark, border-led).
The dashboard is a pure client of the existing API (same contract as curl):
the browser prompts for the token once, stores it in localStorage, attaches it
to every call, and renders all API values as React text so agent-authored
strings stay inert. Control actions enqueue a command and poll it to a terminal
state, matching the worker model.
The build output is committed to src/handler/api/static/ so the wheel ships it
and FastAPI serves it same-origin. app.py now mounts the export at "/" after the
API routers (a non-shadowing fallback: unmatched paths 404, no SPA rewrite).
UI-serving tests updated for the export; frontend source lives in frontend/.
Claude-Session: https://claude.ai/code/session_01ATgVWRjFzG8nHEnwgZpJWD
Co-authored-by: Claude <noreply@anthropic.com>