mirror of
https://github.com/0xWheatyz/handler.git
synced 2026-08-30 07:36:24 +00:00
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:
@@ -17,6 +17,7 @@ import {
|
||||
AuthError,
|
||||
createClient,
|
||||
type Agent,
|
||||
type AgentEvent,
|
||||
type ApiError,
|
||||
type Approval,
|
||||
type Checkmark,
|
||||
@@ -83,6 +84,9 @@ interface StoreValue {
|
||||
log: LogEntry[];
|
||||
logOffset: number;
|
||||
pageLog: (dir: 1 | -1) => void;
|
||||
/* Headless run event stream for the selected run, oldest-first, appended by cursor
|
||||
* polls (empty for legacy tmux agents). */
|
||||
events: AgentEvent[];
|
||||
|
||||
approvals: Approval[];
|
||||
hosts: Host[];
|
||||
@@ -204,6 +208,9 @@ export function DashboardProvider({
|
||||
const [checkmarkMissing, setCheckmarkMissing] = useState(false);
|
||||
const [log, setLog] = useState<LogEntry[]>([]);
|
||||
const [logOffset, setLogOffset] = useState(0);
|
||||
const [events, setEvents] = useState<AgentEvent[]>([]);
|
||||
const eventsRef = useRef<AgentEvent[]>([]);
|
||||
eventsRef.current = events;
|
||||
const [approvals, setApprovals] = useState<Approval[]>([]);
|
||||
const [hosts, setHosts] = useState<Host[]>([]);
|
||||
const [commands, setCommands] = useState<Command[]>([]);
|
||||
@@ -282,6 +289,17 @@ export function DashboardProvider({
|
||||
} catch (e) {
|
||||
swallow(e);
|
||||
}
|
||||
try {
|
||||
// Cursor poll: only events newer than what we already hold come back.
|
||||
const cur = eventsRef.current;
|
||||
const after = cur.length ? cur[cur.length - 1].id : 0;
|
||||
const fresh = await clientRef.current.api<AgentEvent[]>(
|
||||
`${path}/events?after_id=${after}&limit=500`,
|
||||
);
|
||||
if (fresh.length) setEvents((prev) => [...prev, ...fresh]);
|
||||
} catch (e) {
|
||||
swallow(e);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const loadApprovals = useCallback(async (projectId: string) => {
|
||||
@@ -407,6 +425,8 @@ export function DashboardProvider({
|
||||
setCheckmark(null);
|
||||
setCheckmarkMissing(false);
|
||||
setLog([]);
|
||||
setEvents([]);
|
||||
eventsRef.current = [];
|
||||
void loadRun(projectId, name);
|
||||
},
|
||||
[loadRun],
|
||||
@@ -925,6 +945,7 @@ export function DashboardProvider({
|
||||
log,
|
||||
logOffset,
|
||||
pageLog,
|
||||
events,
|
||||
approvals,
|
||||
hosts,
|
||||
commands,
|
||||
|
||||
Reference in New Issue
Block a user