From 1dc09c77e0dd801c54ee9bf599d3948d1dee3446 Mon Sep 17 00:00:00 2001 From: Ian Gulliver Date: Wed, 14 Oct 2026 22:35:53 -0700 Subject: [PATCH] Show agent liveness, stuck/zombie detection and parse errors in admin --- api/openapi.yaml | 64 ++++- docs/claude-protocol.md | 2 + docs/operations.md | 11 +- docs/plan.md | 2 +- internal/agents/health.go | 307 +++++++++++++++++++++++ internal/agents/health_internal_test.go | 106 ++++++++ internal/agents/manager.go | 11 + internal/api/api.gen.go | 294 +++++++++++++++------- internal/claude/badline_internal_test.go | 55 ++++ internal/claude/session.go | 61 ++++- internal/server/server.go | 12 + web/dist/admin.js | 71 +++++- web/dist/admin.js.map | 2 +- web/dist/api.js.map | 2 +- web/dist/state.js | 1 + web/dist/state.js.map | 2 +- web/src/admin.ts | 98 +++++++- web/src/api.gen.ts | 52 ++++ web/src/api.ts | 3 + web/src/state.ts | 1 + web/style.css | 29 ++- 21 files changed, 1072 insertions(+), 114 deletions(-) create mode 100644 internal/agents/health.go create mode 100644 internal/agents/health_internal_test.go create mode 100644 internal/claude/badline_internal_test.go diff --git a/api/openapi.yaml b/api/openapi.yaml index aa0a208..c171cc5 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -802,9 +802,19 @@ components: Status: type: object - required: [started_at, db_bytes, last_event_seq, projects, active_tasks, sse_clients, live_agents, running_turns, queued_turns, caps, rate_limits] + required: [started_at, db_bytes, last_event_seq, projects, active_tasks, sse_clients, live_agents, running_turns, queued_turns, caps, rate_limits, agent_processes, zombies, stuck_after_seconds, protocol_errors] properties: live_agents: { type: integer, description: Agent processes currently running. } + agent_processes: + type: array + description: Every agent process byre is tracking, idle sessions included. + items: { $ref: "#/components/schemas/AgentProcess" } + zombies: + type: array + description: Sessions marked running with no live process, and agent processes byre has no session for. + items: { $ref: "#/components/schemas/Zombie" } + stuck_after_seconds: { type: integer, description: How long a running turn may go without a stream event before it counts as stuck. } + protocol_errors: { $ref: "#/components/schemas/ProtocolErrors" } running_turns: type: array description: Turns running now, oldest session first. @@ -842,3 +852,55 @@ components: session_id: { type: string } task_id: { type: string } vendor: { type: string } + + AgentProcess: + type: object + required: [session_id, task_id, vendor, pid, state, last_event_at, health] + properties: + session_id: { type: string } + task_id: { type: string } + vendor: { type: string } + pid: { type: integer } + state: { type: string, description: The session's state, as on AgentSession. } + last_event_at: { type: string, format: date-time, description: When the process last wrote a line to its stream, or when it started. } + health: + type: string + enum: [ok, stuck, exited] + x-enum-varnames: [HealthOk, HealthStuck, HealthExited] + description: stuck means a turn is running and no stream event has arrived for stuck_after_seconds; exited means the process is gone. + + Zombie: + type: object + required: [kind, detail] + properties: + kind: + type: string + enum: [running_without_process, process_without_session] + detail: { type: string } + session_id: { type: string } + task_id: { type: string } + pid: { type: integer } + + ProtocolErrors: + type: object + required: [counts, recent] + properties: + counts: + type: object + description: Stream lines the adapter couldn't parse or didn't recognise since byre started, keyed by vendor. + additionalProperties: { type: integer } + recent: + type: array + description: The most recent ones, newest first. + items: { $ref: "#/components/schemas/ProtocolError" } + + ProtocolError: + type: object + required: [vendor, session_id, at, reason, raw, size] + properties: + vendor: { type: string } + session_id: { type: string } + at: { type: string, format: date-time } + reason: { type: string } + raw: { type: string, description: The line as received, cut to its first 4 KiB. } + size: { type: integer, description: The line's full length in bytes. } diff --git a/docs/claude-protocol.md b/docs/claude-protocol.md index 627de76..ad8703e 100644 --- a/docs/claude-protocol.md +++ b/docs/claude-protocol.md @@ -29,6 +29,8 @@ NDJSON both ways: one JSON object per line on stdin and stdout. Non-JSON stdout - Stdout events: `system` (subtype `init` carries `session_id`, `model`, tools, MCP server status), `assistant` and `user` (Anthropic message content blocks: `text`, `tool_use`, `tool_result`), `result` (ends a turn; `subtype` is `success` or `error_during_execution` after an interrupt, with cost, duration, turn count). - `--include-partial-messages` adds `stream_event` lines wrapping the raw API stream (`message_start`, `content_block_start`, `content_block_delta` with `text_delta`, `content_block_stop`, `message_delta`, `message_stop`), each with `parent_tool_use_id` (set inside subagents). The complete `assistant` event for a block still arrives, before that block's `content_block_stop`. Transcripts leave `stream_event` lines out. - The `init` event arrives with the first turn, not at launch, so the session ID is unknown until the first message is sent. +- While a tool runs, `tool_progress` lines with `"heartbeat": true`, the tool's `elapsed_time_seconds`, and a `parent_tool_use_id` naming the tool call arrive every 30 seconds. A Bash call also brings `system` `task_started` and `task_notification` lines around it. +- Lines that aren't a JSON object, don't parse, or have a `type` not in `knownTypes` (`internal/claude/session.go`) go to `Options.OnBadLine`; lines that parse are still delivered as events whatever their type. ## Control channel diff --git a/docs/operations.md b/docs/operations.md index 6382c2e..6102cb8 100644 --- a/docs/operations.md +++ b/docs/operations.md @@ -81,9 +81,16 @@ When Claude Code reports a rate-limit status of `rejected` and the turn then fai ## Admin view -`GET /api/admin/status` reports `running_turns` (sessions now `running`) and `queued_turns` (rows of `turn_queue`, in the order they will start), each as session, task and vendor; `caps`, the global and per-vendor limits from `DefaultLimits`; `rate_limits`, the latest `rate_limit_info` each vendor sent since byre started, unchanged; and server counts (start time, agent processes, projects, active tasks, SSE clients, database size including WAL, last event seq). A turn's queue row is written before its session's `queued` state event, so a refetch triggered by that event sees it. +`GET /api/admin/status` reports `running_turns` (sessions now `running`) and `queued_turns` (rows of `turn_queue`, in the order they will start), each as session, task and vendor; `caps`, the global and per-vendor limits from `DefaultLimits`; `rate_limits`, the latest `rate_limit_info` each vendor sent since byre started, unchanged; `agent_processes`, `zombies`, `stuck_after_seconds` and `protocol_errors` (below); and server counts (start time, agent processes, projects, active tasks, SSE clients, database size including WAL, last event seq). A turn's queue row is written before its session's `queued` state event, so a refetch triggered by that event sees it. -The Admin view (a pane tab, also opened in the right pane by the navbar's **Admin** button) shows all of it: running turns against each cap, queued turns in order, both linking to their tasks, each vendor's rate-limit status with its reset time and per-window utilization, and the server counts. It refetches the status when a `session.created`, `session.state`, `task.*` or `project.created` event arrives, when an `agent.vendor` event carries a `rate_limit_event`, and when the SSE stream reconnects; other events only advance the last-event seq it shows. There is no timer, so the agent-process count can lag until one of those events, and the "in 2h 10m" beside a reset time is as of the last render. +Agent health is `internal/agents/health.go`: + +- **Processes.** `agent_processes` lists every process byre is tracking (idle sessions keep theirs), with session, task, vendor, pid, the session's state, `last_event_at` (when the process last wrote any line to stdout, or when it started; `claude.Session.LastLine`) and `health`. `health` is `exited` when the stream has closed or `kill(pid, 0)` says the pid is gone, `stuck` when the session is `running` and nothing has arrived for `StuckAfter`, otherwise `ok`. The stream does mark long tool calls: while a tool runs, Claude Code sends a `tool_progress` line with `"heartbeat": true` every 30 seconds, so a long `go test` keeps `last_event_at` fresh and a silent stream means nothing is happening. +- **Zombies.** `running_without_process`: a session marked `running` whose process byre isn't tracking or has `exited`. `process_without_session`: a `claude` child of the byre process (from `ps -A -o pid=,ppid=,comm=`) whose pid belongs to no tracked process, or a tracked process still alive for a `completed` session. A live process for an `idle` session is normal and not a zombie. +- **Watching.** `Manager.Watch` recomputes health every `healthInterval`. When the set of problems (non-`ok` processes and zombies) changes, it logs each new one as `agent health problem` and broadcasts an unstored `admin.health` event (`seq` 0). Nothing is killed or repaired automatically. +- **Protocol errors.** `internal/claude` reports a stdout line that isn't a JSON object, doesn't parse, or has a `type` outside `knownTypes` (the line is still passed on), and the manager adds lines whose content it fails to decode. `protocol_errors.counts` counts them per vendor since byre started; `recent` holds the last `keptBadLines`, newest first, each with session, time, reason, the raw line cut to `badLineBytes` and its full `size`. They live in memory only, and each one also broadcasts `admin.health`. + +The Admin view (a pane tab, also opened in the right pane by the navbar's **Admin** button) shows, from the top: a Problems section only when there are zombies or protocol errors (one line per zombie, and per vendor the error count with the latest line folded under it); running turns against each cap, each running turn with the time since its process's last event and `stuck` or `exited` when it is; queued turns in order, both linking to their tasks; each vendor's rate-limit status with its reset time and per-window utilization, and the server counts. It refetches the status when a `session.created`, `session.state`, `task.*`, `project.created` or `admin.health` event arrives, when an `agent.vendor` event carries a `rate_limit_event`, and when the SSE stream reconnects. Other events advance the last-event seq it shows, and any event carrying a process's `agent_session_id` (stored or not, such as `agent.delta`) moves that process's last-event time forward, re-rendering at most once a second per process. A re-render keeps the scroll position and which folded lines are open. There is no timer, so "time since last event" and the "in 2h 10m" beside a reset time are as of the last render; a process going quiet long enough to be stuck shows up when the next health check broadcasts `admin.health`. Agent activity becomes events with `agent_session_id` set: `user.message`, `agent.message`, `agent.thinking`, `agent.tool_call`, `agent.tool_result`, `turn.ended`, `session.state`, `session.model`, and `session.context`. Anything else Claude Code emits (rate-limit status, task progress, state changes) is recorded unchanged as `agent.vendor`. The one exception is partial output: each top-level `text_delta` is broadcast to SSE clients as an `agent.delta` event (`payload.text`, `seq` 0) that is never stored, and the transcript grows a live message from them until the finished `agent.message` replaces it. A `turn.ended` event carries `inbox_item_id` when its final message was filed as a `report` item. `session.context` records the context size whenever it changes: `used` is the last assistant message's input, cache-read, cache-creation and output tokens, and `window` is the model's context window from the turn's `result` (`modelUsage[model].contextWindow`), so it is 0 until the session's first turn after a server start ends. The task header shows the latest one as a percentage of the window, coloured green, amber from 70%, red from 90%. diff --git a/docs/plan.md b/docs/plan.md index f92c6e9..f65e767 100644 --- a/docs/plan.md +++ b/docs/plan.md @@ -475,7 +475,7 @@ Record transcripts from the spike as the seed of the contract test suite. ### Milestones - **M0 — Skeleton.** launchd service, tailscale serve auth, SQLite schema, OpenAPI spec, projects, tasks, worktree create/delete, event log, SSE stream with resume, embedded SPA shell, basic server observability. - - **Status: done**, and running as a service on the Mac Studio. Observability covers queue state and vendor rate-limit status, through `GET /api/admin/status` and the SPA's Admin view; process liveness and stuck-process detection, adapter parse errors, and per-task snapshot counts and disk usage are not done. + - **Status: done**, and running as a service on the Mac Studio. Observability covers queue state, vendor rate-limit status, agent process liveness, stuck and zombie detection, and adapter and protocol-parse errors, through `GET /api/admin/status` and the SPA's Admin view; per-task snapshot counts and disk usage are not done. - **M1 — One agent end to end (Claude).** Sessions, streaming, messaging, the interface coverage for Claude, the `ask_human` MCP tool, turn summaries, inbox with all views and response types, web push notifications, restart-and-resume, concurrency caps. - **Done:** sessions, streaming, messaging, interrupt; `ask_human` with validation; each turn's final message filed to the inbox as its summary; inbox with global, project, and task views and every response type, answered as one batch and delivered to the agent as one message; restart-and-resume; global and per-vendor caps with queueing; rate limits as their own inbox state; the web UI (two panes, task switcher, transcript, inbox) with Markdown rendering; the session instructions from "Session configuration", given as an appended system prompt and a per-turn reminder; per-turn commits by byre; bouncing questions asked in plain prose; web push notifications through a service worker, with per-user preferences (blocking questions by default, other kinds opt-in) set in a Settings tab, bursts collapsed into one notification, and a click opening the task. Compaction is done, as a handoff into a fresh session; model switching is done, per message; image input is done, pasted or dropped into the composer and stored as blobs. - **Later:** subagent views and Mermaid rendering. diff --git a/internal/agents/health.go b/internal/agents/health.go new file mode 100644 index 0000000..0ac05a8 --- /dev/null +++ b/internal/agents/health.go @@ -0,0 +1,307 @@ +package agents + +import ( + "bufio" + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "log/slog" + "os" + "os/exec" + "path/filepath" + "slices" + "strconv" + "strings" + "sync" + "syscall" + "time" + + "g.fc.run/misc/byre/internal/api" +) + +const ( + StuckAfter = 5 * time.Minute + healthInterval = 15 * time.Second + keptBadLines = 50 + badLineBytes = 4096 +) + +type badLines struct { + mu sync.Mutex + counts map[string]int + recent []api.ProtocolError +} + +func newBadLines() *badLines { + return &badLines{counts: map[string]int{}} +} + +func (b *badLines) add(vendor, sessionID, reason string, line []byte, at time.Time) { + raw := line + if len(raw) > badLineBytes { + raw = raw[:badLineBytes] + } + e := api.ProtocolError{ + Vendor: vendor, + SessionId: sessionID, + At: at.UTC(), + Reason: reason, + Raw: string(raw), + Size: len(line), + } + + b.mu.Lock() + defer b.mu.Unlock() + b.counts[vendor]++ + if len(b.recent) < keptBadLines { + b.recent = append(b.recent, e) + return + } + copy(b.recent, b.recent[1:]) + b.recent[len(b.recent)-1] = e +} + +func (b *badLines) snapshot() api.ProtocolErrors { + b.mu.Lock() + defer b.mu.Unlock() + out := api.ProtocolErrors{Counts: map[string]int{}, Recent: []api.ProtocolError{}} + for v, n := range b.counts { + out.Counts[v] = n + } + for i := len(b.recent) - 1; i >= 0; i-- { + out.Recent = append(out.Recent, b.recent[i]) + } + return out +} + +func (m *Manager) recordBadLine(l *live, line []byte, reason string) { + m.bad.add(string(l.vendor), l.id, reason, line, time.Now()) + m.store.Broadcast(api.Event{Kind: "admin.health", Actor: "byre", Payload: map[string]any{}}) +} + +func isDecodeError(err error) bool { + var syntax *json.SyntaxError + var typ *json.UnmarshalTypeError + return errors.As(err, &syntax) || errors.As(err, &typ) +} + +func (m *Manager) ProtocolErrors() api.ProtocolErrors { + return m.bad.snapshot() +} + +type Health struct { + Processes []api.AgentProcess + Zombies []api.Zombie +} + +type procInfo struct { + sessionID string + taskID string + vendor api.AgentSessionVendor + pid int + state api.AgentSessionState + lastLine time.Time + exited bool +} + +func (m *Manager) Health(ctx context.Context) (Health, error) { + m.mu.Lock() + all := []*live{} + for _, l := range m.live { + all = append(all, l) + } + m.mu.Unlock() + + procs := []procInfo{} + for _, l := range all { + sess, err := m.store.GetSession(ctx, l.id) + if err != nil { + return Health{}, err + } + pid := l.sess.Pid() + procs = append(procs, procInfo{ + sessionID: l.id, + taskID: l.taskID, + vendor: l.vendor, + pid: pid, + state: sess.State, + lastLine: l.sess.LastLine(), + exited: l.sess.Exited() || !pidAlive(pid), + }) + } + + running, err := m.store.SessionsInState(ctx, api.AgentSessionStateRunning) + if err != nil { + return Health{}, err + } + + children, err := claudeChildren(ctx, os.Getpid()) + if err != nil { + return Health{}, err + } + + return assess(time.Now(), procs, running, children), nil +} + +func assess(now time.Time, procs []procInfo, running []api.AgentSession, children []int) Health { + slices.SortFunc(procs, func(a, b procInfo) int { return strings.Compare(a.sessionID, b.sessionID) }) + + h := Health{Processes: []api.AgentProcess{}, Zombies: []api.Zombie{}} + bySession := map[string]procInfo{} + tracked := map[int]bool{} + + for _, p := range procs { + bySession[p.sessionID] = p + health := api.HealthOk + switch { + case p.exited: + health = api.HealthExited + case p.state == api.AgentSessionStateRunning && now.Sub(p.lastLine) > StuckAfter: + health = api.HealthStuck + } + h.Processes = append(h.Processes, api.AgentProcess{ + SessionId: p.sessionID, + TaskId: p.taskID, + Vendor: string(p.vendor), + Pid: p.pid, + State: string(p.state), + LastEventAt: p.lastLine.UTC(), + Health: health, + }) + if p.exited { + continue + } + tracked[p.pid] = true + if p.state == api.AgentSessionStateCompleted { + h.Zombies = append(h.Zombies, api.Zombie{ + Kind: api.ProcessWithoutSession, + Detail: fmt.Sprintf("session is completed but its process %d is still alive", p.pid), + SessionId: &p.sessionID, + TaskId: &p.taskID, + Pid: &p.pid, + }) + } + } + + for _, sess := range running { + p, ok := bySession[sess.Id] + if ok && !p.exited { + continue + } + detail := "no agent process" + if ok { + detail = fmt.Sprintf("process %d has exited", p.pid) + } + h.Zombies = append(h.Zombies, api.Zombie{ + Kind: api.RunningWithoutProcess, + Detail: detail, + SessionId: &sess.Id, + TaskId: &sess.TaskId, + }) + } + + for _, pid := range children { + if tracked[pid] { + continue + } + h.Zombies = append(h.Zombies, api.Zombie{ + Kind: api.ProcessWithoutSession, + Detail: fmt.Sprintf("claude process %d is a child of byre but belongs to no session", pid), + Pid: &pid, + }) + } + + return h +} + +func pidAlive(pid int) bool { + err := syscall.Kill(pid, 0) + return err == nil || errors.Is(err, syscall.EPERM) +} + +func claudeChildren(ctx context.Context, parent int) ([]int, error) { + cctx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + + out, err := exec.CommandContext(cctx, "ps", "-A", "-o", "pid=,ppid=,comm=").Output() + if err != nil { + return nil, fmt.Errorf("list processes: %w", err) + } + return parseChildren(out, parent), nil +} + +func parseChildren(out []byte, parent int) []int { + pids := []int{} + sc := bufio.NewScanner(bytes.NewReader(out)) + for sc.Scan() { + fields := strings.Fields(sc.Text()) + if len(fields) < 3 { + continue + } + pid, err := strconv.Atoi(fields[0]) + if err != nil { + continue + } + ppid, err := strconv.Atoi(fields[1]) + if err != nil || ppid != parent { + continue + } + if filepath.Base(strings.Join(fields[2:], " ")) != "claude" { + continue + } + pids = append(pids, pid) + } + return pids +} + +func problems(h Health) []string { + out := []string{} + for _, p := range h.Processes { + if p.Health != api.HealthOk { + out = append(out, fmt.Sprintf("session %s process %d is %s", p.SessionId, p.Pid, p.Health)) + } + } + for _, z := range h.Zombies { + id := "" + if z.SessionId != nil { + id = "session " + *z.SessionId + ": " + } + out = append(out, fmt.Sprintf("%s: %s%s", z.Kind, id, z.Detail)) + } + slices.Sort(out) + return out +} + +func (m *Manager) Watch() { + t := time.NewTicker(healthInterval) + defer t.Stop() + + seen := []string{} + for { + select { + case <-m.watchStop: + return + case <-t.C: + } + + h, err := m.Health(context.Background()) + if err != nil { + slog.Error("check agent health", "err", err) + continue + } + + now := problems(h) + if slices.Equal(now, seen) { + continue + } + for _, p := range now { + if !slices.Contains(seen, p) { + slog.Warn("agent health problem", "problem", p) + } + } + seen = now + m.store.Broadcast(api.Event{Kind: "admin.health", Actor: "byre", Payload: map[string]any{}}) + } +} diff --git a/internal/agents/health_internal_test.go b/internal/agents/health_internal_test.go new file mode 100644 index 0000000..4221558 --- /dev/null +++ b/internal/agents/health_internal_test.go @@ -0,0 +1,106 @@ +package agents + +import ( + "fmt" + "strings" + "testing" + "time" + + "g.fc.run/misc/byre/internal/api" +) + +func TestAssessStuck(t *testing.T) { + now := time.Now() + procs := []procInfo{ + {sessionID: "a", pid: 10, state: api.AgentSessionStateRunning, lastLine: now.Add(-StuckAfter - time.Second)}, + {sessionID: "b", pid: 11, state: api.AgentSessionStateRunning, lastLine: now.Add(-StuckAfter + time.Second)}, + {sessionID: "c", pid: 12, state: api.AgentSessionStateIdle, lastLine: now.Add(-time.Hour)}, + } + running := []api.AgentSession{{Id: "a"}, {Id: "b"}} + + h := assess(now, procs, running, []int{10, 11, 12}) + + want := map[string]api.AgentProcessHealth{"a": api.HealthStuck, "b": api.HealthOk, "c": api.HealthOk} + for _, p := range h.Processes { + if p.Health != want[p.SessionId] { + t.Errorf("session %s health = %s, want %s", p.SessionId, p.Health, want[p.SessionId]) + } + } + if len(h.Zombies) != 0 { + t.Errorf("zombies = %+v, want none", h.Zombies) + } +} + +func TestAssessZombies(t *testing.T) { + now := time.Now() + procs := []procInfo{ + {sessionID: "exited", pid: 20, state: api.AgentSessionStateRunning, lastLine: now, exited: true}, + {sessionID: "done", pid: 21, state: api.AgentSessionStateCompleted, lastLine: now}, + {sessionID: "idle", pid: 22, state: api.AgentSessionStateIdle, lastLine: now}, + } + running := []api.AgentSession{{Id: "exited", TaskId: "t1"}, {Id: "gone", TaskId: "t2"}} + + h := assess(now, procs, running, []int{21, 22, 99}) + + got := []string{} + for _, z := range h.Zombies { + key := string(z.Kind) + if z.SessionId != nil { + key += " " + *z.SessionId + } + if z.Pid != nil { + key += fmt.Sprintf(" %d", *z.Pid) + } + got = append(got, key) + } + want := []string{ + "process_without_session done 21", + "running_without_process exited", + "running_without_process gone", + "process_without_session 99", + } + if strings.Join(got, "\n") != strings.Join(want, "\n") { + t.Errorf("zombies:\n%s\nwant:\n%s", strings.Join(got, "\n"), strings.Join(want, "\n")) + } + + for _, p := range h.Processes { + if p.SessionId == "exited" && p.Health != api.HealthExited { + t.Errorf("exited process health = %s", p.Health) + } + } +} + +func TestBadLinesRing(t *testing.T) { + b := newBadLines() + at := time.Now() + for i := range keptBadLines + 5 { + b.add("claude", "s", fmt.Sprintf("reason %d", i), []byte(fmt.Sprintf("line %d", i)), at) + } + b.add("other", "s", "big", []byte(strings.Repeat("x", badLineBytes*2)), at) + + snap := b.snapshot() + if snap.Counts["claude"] != keptBadLines+5 || snap.Counts["other"] != 1 { + t.Errorf("counts = %v", snap.Counts) + } + if len(snap.Recent) != keptBadLines { + t.Fatalf("kept %d, want %d", len(snap.Recent), keptBadLines) + } + if snap.Recent[0].Reason != "big" || len(snap.Recent[0].Raw) != badLineBytes || snap.Recent[0].Size != badLineBytes*2 { + t.Errorf("newest = %q, %d raw bytes, size %d", snap.Recent[0].Reason, len(snap.Recent[0].Raw), snap.Recent[0].Size) + } + if snap.Recent[1].Raw != fmt.Sprintf("line %d", keptBadLines+4) { + t.Errorf("second newest = %q", snap.Recent[1].Raw) + } + oldest := snap.Recent[len(snap.Recent)-1].Raw + if oldest != "line 6" { + t.Errorf("oldest = %q, want line 6", oldest) + } +} + +func TestParseChildren(t *testing.T) { + out := []byte(" 100 1 launchd\n 200 100 claude\n 201 100 /Users/x/.local/bin/claude\n 202 100 git\n 203 999 claude\n") + got := parseChildren(out, 100) + if fmt.Sprint(got) != "[200 201]" { + t.Errorf("children = %v", got) + } +} diff --git a/internal/agents/manager.go b/internal/agents/manager.go index e98eaa8..9d12287 100644 --- a/internal/agents/manager.go +++ b/internal/agents/manager.go @@ -50,6 +50,9 @@ type Manager struct { deliverMu sync.Mutex capMu sync.Mutex + + bad *badLines + watchStop chan struct{} } type live struct { @@ -82,6 +85,8 @@ func New(st *store.Store, limits Limits, worktreeRoot string) *Manager { live: map[string]*live{}, rateLimits: map[api.AgentSessionVendor]map[string]any{}, merges: map[string]string{}, + bad: newBadLines(), + watchStop: make(chan struct{}), } } @@ -262,6 +267,8 @@ func (m *Manager) Recover(ctx context.Context) error { } func (m *Manager) Close() { + close(m.watchStop) + m.mu.Lock() m.closing = true all := []*live{} @@ -559,6 +566,7 @@ func (m *Manager) ensureLive(ctx context.Context, sess api.AgentSession, task ap "UserPromptSubmit": {{Func: remindGuidance}}, "Stop": {{Func: m.bounceProseQuestion(l)}}, }, + OnBadLine: func(line []byte, reason string) { m.recordBadLine(l, line, reason) }, } started, err := m.store.SessionStarted(ctx, sess.Id) if err != nil { @@ -595,6 +603,9 @@ func (m *Manager) pump(l *live) { if err != nil { slog.Error("record agent event", "session", l.id, "type", ev.Type, "err", err) } + if isDecodeError(err) { + m.recordBadLine(l, ev.Raw, fmt.Sprintf("decode %s: %s", ev.Type, err)) + } if took := time.Since(start); took > 200*time.Millisecond { slog.Info("agent event slow", "session", l.id, "type", ev.Type, "took", took) } diff --git a/internal/api/api.gen.go b/internal/api/api.gen.go index 0bd4ce2..3ae46f7 100644 --- a/internal/api/api.gen.go +++ b/internal/api/api.gen.go @@ -21,6 +21,27 @@ import ( "github.com/oapi-codegen/runtime" ) +// Defines values for AgentProcessHealth. +const ( + HealthExited AgentProcessHealth = "exited" + HealthOk AgentProcessHealth = "ok" + HealthStuck AgentProcessHealth = "stuck" +) + +// Valid indicates whether the value is a known member of the AgentProcessHealth enum. +func (e AgentProcessHealth) Valid() bool { + switch e { + case HealthExited: + return true + case HealthOk: + return true + case HealthStuck: + return true + default: + return false + } +} + // Defines values for AgentSessionRole. const ( Implementer AgentSessionRole = "implementer" @@ -252,6 +273,43 @@ func (e TaskStatus) Valid() bool { } } +// Defines values for ZombieKind. +const ( + ProcessWithoutSession ZombieKind = "process_without_session" + RunningWithoutProcess ZombieKind = "running_without_process" +) + +// Valid indicates whether the value is a known member of the ZombieKind enum. +func (e ZombieKind) Valid() bool { + switch e { + case ProcessWithoutSession: + return true + case RunningWithoutProcess: + return true + default: + return false + } +} + +// AgentProcess defines model for AgentProcess. +type AgentProcess struct { + // Health stuck means a turn is running and no stream event has arrived for stuck_after_seconds; exited means the process is gone. + Health AgentProcessHealth `json:"health"` + + // LastEventAt When the process last wrote a line to its stream + LastEventAt time.Time `json:"last_event_at"` + Pid int `json:"pid"` + SessionId string `json:"session_id"` + + // State The session's state + State string `json:"state"` + TaskId string `json:"task_id"` + Vendor string `json:"vendor"` +} + +// AgentProcessHealth stuck means a turn is running and no stream event has arrived for stuck_after_seconds; exited means the process is gone. +type AgentProcessHealth string + // AgentSession defines model for AgentSession. type AgentSession struct { CreatedAt time.Time `json:"created_at"` @@ -474,6 +532,29 @@ type ProjectCreate struct { RepoPath string `json:"repo_path"` } +// ProtocolError defines model for ProtocolError. +type ProtocolError struct { + At time.Time `json:"at"` + + // Raw The line as received + Raw string `json:"raw"` + Reason string `json:"reason"` + SessionId string `json:"session_id"` + + // Size The line's full length in bytes. + Size int `json:"size"` + Vendor string `json:"vendor"` +} + +// ProtocolErrors defines model for ProtocolErrors. +type ProtocolErrors struct { + // Counts Stream lines the adapter couldn't parse or didn't recognise since byre started, keyed by vendor. + Counts map[string]int `json:"counts"` + + // Recent The most recent ones, newest first. + Recent []ProtocolError `json:"recent"` +} + // PushKey defines model for PushKey. type PushKey struct { // PublicKey Base64url @@ -500,14 +581,18 @@ type SessionCreate struct { // Status defines model for Status. type Status struct { - ActiveTasks int `json:"active_tasks"` - Caps Caps `json:"caps"` - DbBytes int64 `json:"db_bytes"` - LastEventSeq int64 `json:"last_event_seq"` + ActiveTasks int `json:"active_tasks"` + + // AgentProcesses Every agent process byre is tracking, idle sessions included. + AgentProcesses []AgentProcess `json:"agent_processes"` + Caps Caps `json:"caps"` + DbBytes int64 `json:"db_bytes"` + LastEventSeq int64 `json:"last_event_seq"` // LiveAgents Agent processes currently running. - LiveAgents int `json:"live_agents"` - Projects int `json:"projects"` + LiveAgents int `json:"live_agents"` + Projects int `json:"projects"` + ProtocolErrors ProtocolErrors `json:"protocol_errors"` // QueuedTurns Turns waiting for room under the caps, in the order they will start. QueuedTurns []TurnRef `json:"queued_turns"` @@ -519,6 +604,12 @@ type Status struct { RunningTurns []TurnRef `json:"running_turns"` SseClients int `json:"sse_clients"` StartedAt time.Time `json:"started_at"` + + // StuckAfterSeconds How long a running turn may go without a stream event before it counts as stuck. + StuckAfterSeconds int `json:"stuck_after_seconds"` + + // Zombies Sessions marked running with no live process, and agent processes byre has no session for. + Zombies []Zombie `json:"zombies"` } // Task defines model for Task. @@ -599,6 +690,18 @@ type UserUpdate struct { GitName string `json:"git_name"` } +// Zombie defines model for Zombie. +type Zombie struct { + Detail string `json:"detail"` + Kind ZombieKind `json:"kind"` + Pid *int `json:"pid,omitempty"` + SessionId *string `json:"session_id,omitempty"` + TaskId *string `json:"task_id,omitempty"` +} + +// ZombieKind defines model for Zombie.Kind. +type ZombieKind string + // StreamEventsParams defines parameters for StreamEvents. type StreamEventsParams struct { After *int64 `form:"after,omitempty" json:"after,omitempty"` @@ -1825,91 +1928,100 @@ func HandlerWithOptions(si ServerInterface, options StdHTTPServerOptions) http.H // const string: with thousands of chunks the chained `+` fold is several // times slower for the Go compiler than parsing a slice literal. var swaggerSpec = []string{ - "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", - "/w==", + "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", + "3N7+TwAAAP//", } // decodeSpec returns the embedded OpenAPI spec as raw JSON bytes, diff --git a/internal/claude/badline_internal_test.go b/internal/claude/badline_internal_test.go new file mode 100644 index 0000000..8922f10 --- /dev/null +++ b/internal/claude/badline_internal_test.go @@ -0,0 +1,55 @@ +package claude + +import ( + "strings" + "testing" + "time" +) + +func TestBadLines(t *testing.T) { + bad := []string{} + s := &Session{ + events: make(chan Event, 16), + done: make(chan struct{}), + onBadLine: func(line []byte, reason string) { + bad = append(bad, string(line)+" | "+reason) + }, + } + + input := strings.Join([]string{ + `{"type":"assistant","message":{"content":[]}}`, + ``, + `warning: something on stdout`, + `{"type":"assistant",`, + `{"type":"mystery","x":1}`, + `{"type":"keep_alive"}`, + `{"type":"tool_progress","heartbeat":true}`, + }, "\n") + + before := time.Now() + s.readLoop(strings.NewReader(input)) + + want := []string{ + `warning: something on stdout | not a JSON object`, + `{"type":"assistant", | unexpected end of JSON input`, + `{"type":"mystery","x":1} | unrecognised type "mystery"`, + } + if strings.Join(bad, "\n") != strings.Join(want, "\n") { + t.Errorf("bad lines:\n%s\nwant:\n%s", strings.Join(bad, "\n"), strings.Join(want, "\n")) + } + + types := []string{} + for ev := range s.events { + types = append(types, ev.Type) + } + if strings.Join(types, ",") != "assistant,mystery,tool_progress" { + t.Errorf("events = %v", types) + } + + if s.LastLine().Before(before) { + t.Errorf("last line time %v not updated", s.LastLine()) + } + if !s.Exited() { + t.Errorf("session not exited after stdout closed") + } +} diff --git a/internal/claude/session.go b/internal/claude/session.go index 00a1a73..a75c629 100644 --- a/internal/claude/session.go +++ b/internal/claude/session.go @@ -15,6 +15,7 @@ import ( "os" "os/exec" "sync" + "sync/atomic" "time" "g.fc.run/misc/byre/internal/mcp" @@ -39,6 +40,21 @@ type Options struct { Hooks map[string][]HookMatcher MCPServers []*mcp.Server Transcript io.Writer + OnBadLine func(line []byte, reason string) +} + +var knownTypes = map[string]bool{ + "assistant": true, + "user": true, + "result": true, + "system": true, + "stream_event": true, + "tool_progress": true, + "rate_limit_event": true, + "control_request": true, + "control_response": true, + "control_cancel_request": true, + "keep_alive": true, } type Event struct { @@ -175,6 +191,9 @@ type Session struct { idMu sync.Mutex id string + + onBadLine func(line []byte, reason string) + lastLine atomic.Int64 } type controlResult struct { @@ -200,7 +219,9 @@ func Start(ctx context.Context, opts Options) (*Session, error) { hooks: map[string]HookFunc{}, servers: map[string]*mcp.Server{}, id: initialID(opts), + onBadLine: opts.OnBadLine, } + s.lastLine.Store(time.Now().UnixNano()) args, err := s.buildArgs(opts) if err != nil { @@ -355,6 +376,23 @@ func (s *Session) Done() <-chan struct{} { return s.done } +func (s *Session) Pid() int { + return s.cmd.Process.Pid +} + +func (s *Session) LastLine() time.Time { + return time.Unix(0, s.lastLine.Load()) +} + +func (s *Session) Exited() bool { + select { + case <-s.done: + return true + default: + return false + } +} + func (s *Session) ID() string { s.idMu.Lock() defer s.idMu.Unlock() @@ -493,8 +531,14 @@ func (s *Session) readLoop(stdout io.Reader) { r := bufio.NewReader(stdout) for { line, err := r.ReadBytes('\n') - if len(line) > 1 && line[0] == '{' { - s.handleLine(line[:len(line)-1]) + line = bytes.TrimRight(line, "\r\n") + if len(line) > 0 { + s.lastLine.Store(time.Now().UnixNano()) + } + if len(line) > 0 && line[0] == '{' { + s.handleLine(line) + } else if len(line) > 0 { + s.badLine(line, "not a JSON object") } if err != nil { if err != io.EOF { @@ -510,10 +554,14 @@ func (s *Session) handleLine(line []byte) { env := envelope{} err := json.Unmarshal(line, &env) if err != nil { - slog.Error("parse claude message", "err", err, "line", string(line)) + s.badLine(line, err.Error()) return } + if !knownTypes[env.Type] { + s.badLine(line, fmt.Sprintf("unrecognised type %q", env.Type)) + } + s.writeMu.Lock() if env.Type == "control_response" { s.learnScrub(env.Response) @@ -545,6 +593,13 @@ func (s *Session) handleLine(line []byte) { s.events <- Event{Type: env.Type, Subtype: env.Subtype, Raw: append(json.RawMessage{}, line...), Read: read} } +func (s *Session) badLine(line []byte, reason string) { + slog.Warn("bad claude line", "reason", reason, "line", string(line)) + if s.onBadLine != nil { + s.onBadLine(line, reason) + } +} + func (s *Session) learnScrub(raw json.RawMessage) { resp := struct { Response struct { diff --git a/internal/server/server.go b/internal/server/server.go index ed369a7..d85bdc7 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -67,6 +67,7 @@ func New(dataDir string, spa fs.FS) (*Server, error) { s.store.Close() return nil, fmt.Errorf("recover sessions: %w", err) } + go s.agents.Watch() return s, nil } @@ -393,6 +394,12 @@ func (s *Server) GetStatus(w http.ResponseWriter, r *http.Request) { return } + health, err := s.agents.Health(r.Context()) + if err != nil { + writeError(w, http.StatusInternalServerError, err) + return + } + dbBytes := int64(0) for _, suffix := range []string{"", "-wal", "-shm"} { fi, err := os.Stat(s.store.Path() + suffix) @@ -413,6 +420,11 @@ func (s *Server) GetStatus(w http.ResponseWriter, r *http.Request) { QueuedTurns: usage.Queued, Caps: usage.Caps, RateLimits: usage.RateLimits, + + AgentProcesses: health.Processes, + Zombies: health.Zombies, + StuckAfterSeconds: int(agents.StuckAfter / time.Second), + ProtocolErrors: s.agents.ProtocolErrors(), }) } diff --git a/web/dist/admin.js b/web/dist/admin.js index ab2ae02..67fc071 100644 --- a/web/dist/admin.js +++ b/web/dist/admin.js @@ -1,7 +1,7 @@ import { get } from "./api.js"; import { ago, errorText, h, replace } from "./dom.js"; import { findTask, on, onEach, state } from "./state.js"; -const refreshKinds = new Set(["session.created", "session.state", "task.created", "task.merged", "task.discarded", "project.created"]); +const refreshKinds = new Set(["session.created", "session.state", "task.created", "task.merged", "task.discarded", "project.created", "admin.health"]); export function mountAdmin(root) { let status; let error = ""; @@ -13,7 +13,10 @@ export function mountAdmin(root) { replace(root, h("div", { class: error ? "empty error-text" : "empty" }, error || "Loading…")); return; } - replace(root, h("div", { class: "admin-view" }, error && h("p", { class: "error-text" }, error), turnsSection(status), rateLimitsSection(status.rate_limits), serverSection(status))); + const scrollTop = root.querySelector(".admin-view")?.scrollTop ?? 0; + const open = new Set([...root.querySelectorAll(".problems details[open]")].map((d) => d.dataset["vendor"] ?? "")); + replace(root, h("div", { class: "admin-view" }, error && h("p", { class: "error-text" }, error), problemsSection(status, open), turnsSection(status), rateLimitsSection(status.rate_limits), serverSection(status))); + root.querySelector(".admin-view").scrollTop = scrollTop; }; const refresh = async () => { if (inFlight) { @@ -45,8 +48,20 @@ export function mountAdmin(root) { void refresh(); return; } - if (status && ev.seq > status.last_event_seq) { + if (!status) { + return; + } + let changed = false; + const proc = status.agent_processes.find((p) => p.session_id === ev.agent_session_id); + if (proc && Date.parse(ev.created_at) - Date.parse(proc.last_event_at) >= 1000) { + proc.last_event_at = ev.created_at; + changed = true; + } + if (ev.seq > status.last_event_seq) { status.last_event_seq = ev.seq; + changed = true; + } + if (changed) { render(); } }; @@ -71,19 +86,63 @@ export function mountAdmin(root) { function turnsSection(s) { const vendors = [...new Set([...Object.keys(s.caps.per_vendor), ...s.running_turns.map((t) => t.vendor), ...s.queued_turns.map((t) => t.vendor)])].sort(); const runningFor = (vendor) => s.running_turns.filter((t) => t.vendor === vendor).length; - return h("section", { class: "admin-section" }, h("h2", {}, "Turns"), h("div", { class: "caps" }, capTile("All vendors", s.running_turns.length, s.caps.global), ...vendors.map((v) => capTile(v, runningFor(v), s.caps.per_vendor[v])), h("div", { class: "cap-tile" }, h("div", { class: "cap-label" }, "Queued"), h("div", { class: s.queued_turns.length > 0 ? "cap-value warn" : "cap-value" }, s.queued_turns.length))), h("h3", {}, "Running"), turnList(s.running_turns, "running", "No turns running."), h("h3", {}, "Queued"), turnList(s.queued_turns, "queued", "Nothing queued.")); + return h("section", { class: "admin-section" }, h("h2", {}, "Turns"), h("div", { class: "caps" }, capTile("All vendors", s.running_turns.length, s.caps.global), ...vendors.map((v) => capTile(v, runningFor(v), s.caps.per_vendor[v])), h("div", { class: "cap-tile" }, h("div", { class: "cap-label" }, "Queued"), h("div", { class: s.queued_turns.length > 0 ? "cap-value warn" : "cap-value" }, s.queued_turns.length))), h("h3", {}, "Running"), turnList(s.running_turns, "running", "No turns running.", s.agent_processes), h("h3", {}, "Queued"), turnList(s.queued_turns, "queued", "Nothing queued.", [])); } function capTile(label, running, cap) { const full = cap !== undefined && running >= cap; return h("div", { class: "cap-tile" }, h("div", { class: "cap-label" }, label), h("div", { class: full ? "cap-value warn" : "cap-value" }, running, h("span", { class: "cap-max" }, cap === undefined ? " / no cap" : ` / ${cap}`))); } -function turnList(turns, kind, empty) { +function turnList(turns, kind, empty, procs) { return h("ul", { class: "task-list" }, ...turns.map((t, i) => { const task = findTask(t.task_id); const project = task ? state.projects.find((p) => p.id === task.project_id) : undefined; - return h("li", {}, h("a", { href: `#/task/${t.task_id}` }, kind === "queued" ? h("span", { class: "queue-pos" }, `${i + 1}.`) : h("span", { class: "dot running", title: "running" }), h("span", { class: "task-title" }, project && h("span", { class: "project-name" }, `${project.name} ▸ `), task?.title ?? t.task_id), h("span", { class: "task-state" }, t.vendor))); + return h("li", {}, h("a", { href: `#/task/${t.task_id}` }, kind === "queued" ? h("span", { class: "queue-pos" }, `${i + 1}.`) : h("span", { class: "dot running", title: "running" }), h("span", { class: "task-title" }, project && h("span", { class: "project-name" }, `${project.name} ▸ `), task?.title ?? t.task_id), liveness(procs.find((p) => p.session_id === t.session_id)), h("span", { class: "task-state" }, t.vendor))); }), turns.length === 0 && h("li", { class: "empty-row" }, empty)); } +function taskLink(taskId) { + if (!taskId) { + return "—"; + } + const task = findTask(taskId); + const project = task ? state.projects.find((p) => p.id === task.project_id) : undefined; + return h("a", { href: `#/task/${taskId}` }, project ? `${project.name} ▸ ` : "", task?.title ?? taskId); +} +function since(iso) { + const seconds = Math.max(0, Math.round((Date.now() - Date.parse(iso)) / 1000)); + if (seconds < 60) { + return `${seconds}s ago`; + } + if (seconds < 3600) { + return `${Math.floor(seconds / 60)}m ${seconds % 60}s ago`; + } + return ago(iso); +} +function liveness(p) { + if (!p) { + return false; + } + if (p.health === "ok") { + return h("span", { class: "task-state" }, since(p.last_event_at)); + } + return h("span", { class: "task-state danger" }, `${p.health} · ${since(p.last_event_at)}`); +} +const zombieLabel = { + running_without_process: "Running with no process", + process_without_session: "Process with no session", +}; +function problemsSection(s, openVendors) { + const pe = s.protocol_errors; + const vendors = Object.keys(pe.counts).sort(); + if (s.zombies.length === 0 && vendors.length === 0) { + return false; + } + return h("section", { class: "admin-section" }, h("h2", {}, "Problems"), h("ul", { class: "problems" }, ...s.zombies.map((z) => h("li", {}, h("span", { class: "danger" }, zombieLabel[z.kind]), " ", z.task_id ? taskLink(z.task_id) : "", " ", h("span", { class: "hint" }, z.detail))), ...vendors.map((v) => { + const latest = pe.recent.find((e) => e.vendor === v); + const n = pe.counts[v] ?? 0; + return h("li", {}, h("span", { class: "danger" }, `${v}: ${n} bad stream line${n === 1 ? "" : "s"}`), latest && + h("details", { data: { vendor: v }, open: openVendors.has(v) }, h("summary", {}, `latest ${since(latest.at)}: ${latest.reason}`), h("pre", { class: "raw-line" }, latest.raw, new TextEncoder().encode(latest.raw).length < latest.size ? `… (${bytes(latest.size)})` : ""))); + }))); +} function rateLimitsSection(limits) { const vendors = Object.keys(limits).sort(); return h("section", { class: "admin-section" }, h("h2", {}, "Rate limits"), ...vendors.map((v) => rateLimitCard(v, limits[v] ?? {})), vendors.length === 0 && h("p", { class: "hint" }, "No vendor has reported a rate-limit status since byre started.")); diff --git a/web/dist/admin.js.map b/web/dist/admin.js.map index df6af72..fd88e65 100644 --- a/web/dist/admin.js.map +++ b/web/dist/admin.js.map @@ -1 +1 @@ -{"version":3,"file":"admin.js","sourceRoot":"","sources":["../src/admin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAA6C,MAAM,UAAU,CAAC;AAC1E,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AACtD,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEzD,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,CAAC,iBAAiB,EAAE,eAAe,EAAE,cAAc,EAAE,aAAa,EAAE,gBAAgB,EAAE,iBAAiB,CAAC,CAAC,CAAC;AAEvI,MAAM,UAAU,UAAU,CAAC,IAAiB;IAC1C,IAAI,MAA0B,CAAC;IAC/B,IAAI,KAAK,GAAG,EAAE,CAAC;IACf,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,IAAI,KAAK,GAAG,KAAK,CAAC;IAClB,IAAI,QAAQ,GAAG,KAAK,CAAC;IAErB,MAAM,MAAM,GAAG,GAAG,EAAE;QAClB,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,OAAO,EAAE,EAAE,KAAK,IAAI,UAAU,CAAC,CAAC,CAAC;YAC9F,OAAO;QACT,CAAC;QACD,OAAO,CACL,IAAI,EACJ,CAAC,CACC,KAAK,EACL,EAAE,KAAK,EAAE,YAAY,EAAE,EACvB,KAAK,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,YAAY,EAAE,EAAE,KAAK,CAAC,EAC/C,YAAY,CAAC,MAAM,CAAC,EACpB,iBAAiB,CAAC,MAAM,CAAC,WAAW,CAAC,EACrC,aAAa,CAAC,MAAM,CAAC,CACtB,CACF,CAAC;IACJ,CAAC,CAAC;IAEF,MAAM,OAAO,GAAG,KAAK,IAAI,EAAE;QACzB,IAAI,QAAQ,EAAE,CAAC;YACb,KAAK,GAAG,IAAI,CAAC;YACb,OAAO;QACT,CAAC;QACD,QAAQ,GAAG,IAAI,CAAC;QAChB,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,mBAAmB,CAAC,CAAC;YAC5C,IAAI,QAAQ,EAAE,CAAC;gBACb,OAAO;YACT,CAAC;YACD,MAAM,GAAG,IAAI,CAAC;YACd,KAAK,GAAG,EAAE,CAAC;QACb,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,KAAK,GAAG,yBAAyB,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC;QACpD,CAAC;QACD,QAAQ,GAAG,KAAK,CAAC;QACjB,MAAM,EAAE,CAAC;QACT,IAAI,KAAK,IAAI,CAAC,QAAQ,EAAE,CAAC;YACvB,KAAK,GAAG,KAAK,CAAC;YACd,KAAK,OAAO,EAAE,CAAC;QACjB,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,OAAO,GAAG,CAAC,MAAgB,EAAE,EAAE;QACnC,MAAM,EAAE,GAAG,MAAmB,CAAC;QAC/B,IAAI,YAAY,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,KAAK,cAAc,IAAI,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,kBAAkB,CAAC,EAAE,CAAC;YAC3G,KAAK,OAAO,EAAE,CAAC;YACf,OAAO;QACT,CAAC;QACD,IAAI,MAAM,IAAI,EAAE,CAAC,GAAG,GAAG,MAAM,CAAC,cAAc,EAAE,CAAC;YAC7C,MAAM,CAAC,cAAc,GAAG,EAAE,CAAC,GAAG,CAAC;YAC/B,MAAM,EAAE,CAAC;QACX,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,IAAI,GAAG;QACX,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC;QACpB,EAAE,CAAC,YAAY,EAAE,GAAG,EAAE;YACpB,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC;gBACpB,KAAK,OAAO,EAAE,CAAC;YACjB,CAAC;QACH,CAAC,CAAC;QACF,MAAM,CAAC,CAAC,UAAU,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC;KACtC,CAAC;IAEF,MAAM,EAAE,CAAC;IACT,KAAK,OAAO,EAAE,CAAC;IAEf,OAAO,GAAG,EAAE;QACV,QAAQ,GAAG,IAAI,CAAC;QAChB,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,GAAG,EAAE,CAAC;QACR,CAAC;IACH,CAAC,CAAC;AACJ,CAAC;AAED,SAAS,YAAY,CAAC,CAAS;IAC7B,MAAM,OAAO,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,GAAG,CAAC,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,GAAG,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAC1J,MAAM,UAAU,GAAG,CAAC,MAAc,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,MAAM,CAAC;IAEjG,OAAO,CAAC,CACN,SAAS,EACT,EAAE,KAAK,EAAE,eAAe,EAAE,EAC1B,CAAC,CAAC,IAAI,EAAE,EAAE,EAAE,OAAO,CAAC,EACpB,CAAC,CACC,KAAK,EACL,EAAE,KAAK,EAAE,MAAM,EAAE,EACjB,OAAO,CAAC,aAAa,EAAE,CAAC,CAAC,aAAa,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,EAC7D,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,EACtE,CAAC,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CACpL,EACD,CAAC,CAAC,IAAI,EAAE,EAAE,EAAE,SAAS,CAAC,EACtB,QAAQ,CAAC,CAAC,CAAC,aAAa,EAAE,SAAS,EAAE,mBAAmB,CAAC,EACzD,CAAC,CAAC,IAAI,EAAE,EAAE,EAAE,QAAQ,CAAC,EACrB,QAAQ,CAAC,CAAC,CAAC,YAAY,EAAE,QAAQ,EAAE,iBAAiB,CAAC,CACtD,CAAC;AACJ,CAAC;AAED,SAAS,OAAO,CAAC,KAAa,EAAE,OAAe,EAAE,GAAuB;IACtE,MAAM,IAAI,GAAG,GAAG,KAAK,SAAS,IAAI,OAAO,IAAI,GAAG,CAAC;IACjD,OAAO,CAAC,CACN,KAAK,EACL,EAAE,KAAK,EAAE,UAAU,EAAE,EACrB,CAAC,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE,KAAK,CAAC,EACvC,CAAC,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,WAAW,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC,CACpJ,CAAC;AACJ,CAAC;AAED,SAAS,QAAQ,CAAC,KAAgB,EAAE,IAA0B,EAAE,KAAa;IAC3E,OAAO,CAAC,CACN,IAAI,EACJ,EAAE,KAAK,EAAE,WAAW,EAAE,EACtB,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QACpB,MAAM,IAAI,GAAG,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;QACjC,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACxF,OAAO,CAAC,CACN,IAAI,EACJ,EAAE,EACF,CAAC,CACC,GAAG,EACH,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC,OAAO,EAAE,EAAE,EAC/B,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,EAC1H,CAAC,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,YAAY,EAAE,EAAE,OAAO,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,cAAc,EAAE,EAAE,GAAG,OAAO,CAAC,IAAI,KAAK,CAAC,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC,CAAC,OAAO,CAAC,EACnI,CAAC,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,YAAY,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,CAC7C,CACF,CAAC;IACJ,CAAC,CAAC,EACF,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE,KAAK,CAAC,CAC7D,CAAC;AACJ,CAAC;AAED,SAAS,iBAAiB,CAAC,MAA6B;IACtD,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;IAC3C,OAAO,CAAC,CACN,SAAS,EACT,EAAE,KAAK,EAAE,eAAe,EAAE,EAC1B,CAAC,CAAC,IAAI,EAAE,EAAE,EAAE,aAAa,CAAC,EAC1B,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,EACxD,OAAO,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,gEAAgE,CAAC,CACpH,CAAC;AACJ,CAAC;AAED,SAAS,aAAa,CAAC,MAAc,EAAE,IAA6B;IAClE,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,QAAQ,EAAE,cAAc,EAAE,GAAG,IAAI,EAAE,GAAG,IAAI,CAAC;IAC1E,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,IAAI,SAAS,CAAC,CAAC;IAC/C,MAAM,IAAI,GAAG,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,UAAU,KAAK,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC;IAC7F,MAAM,OAAO,GAAG,CAAC,cAAc,IAAI,EAAE,CAAgE,CAAC;IAEtG,OAAO,CAAC,CACN,KAAK,EACL,EAAE,KAAK,EAAE,iBAAiB,EAAE,EAC5B,CAAC,CACC,QAAQ,EACR,EAAE,EACF,CAAC,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,MAAM,CAAC,EACtC,CAAC,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,aAAa,IAAI,EAAE,EAAE,EAAE,UAAU,CAAC,EACrD,aAAa,KAAK,SAAS,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,EAAE,MAAM,CAAC,aAAa,CAAC,CAAC,EACnE,OAAO,QAAQ,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,SAAS,EAAE,SAAS,CAAC,QAAQ,CAAC,CAAC,CAC/F,EACD,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM,GAAG,CAAC;QAC7B,CAAC,CACC,OAAO,EACP,EAAE,KAAK,EAAE,YAAY,EAAE,EACvB,CAAC,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,EAAE,QAAQ,CAAC,CAAC,EAC9E,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,CAC3C,CAAC,CACC,IAAI,EACJ,EAAE,EACF,CAAC,CAAC,IAAI,EAAE,EAAE,EAAE,IAAI,CAAC,EACjB,CAAC,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,gBAAgB,CAAC,CAAC,CAAC,WAAW,CAAC,EAAE,EAAE,CAAC,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,WAAW,GAAG,GAAG,CAAC,GAAG,CAAC,EAC9H,CAAC,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CACpE,CACF,CACF,EACH,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC;QAC1B,CAAC,CACC,IAAI,EACJ,EAAE,KAAK,EAAE,IAAI,EAAE,EACf,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,EAAE,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAClI,CACJ,CAAC;AACJ,CAAC;AAED,SAAS,gBAAgB,CAAC,CAAqB;IAC7C,IAAI,CAAC,KAAK,SAAS,EAAE,CAAC;QACpB,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,IAAI,CAAC,IAAI,GAAG,EAAE,CAAC;QACb,OAAO,QAAQ,CAAC;IAClB,CAAC;IACD,IAAI,CAAC,IAAI,GAAG,EAAE,CAAC;QACb,OAAO,MAAM,CAAC;IAChB,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,SAAS,SAAS,CAAC,WAAmB;IACpC,MAAM,EAAE,GAAG,IAAI,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IACxC,MAAM,IAAI,GAAG,EAAE,CAAC,cAAc,CAAC,SAAS,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;IACpG,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,GAAG,KAAK,CAAC,CAAC;IAChE,IAAI,OAAO,IAAI,CAAC,EAAE,CAAC;QACjB,OAAO,GAAG,IAAI,WAAW,CAAC;IAC5B,CAAC;IACD,MAAM,GAAG,GAAG,OAAO,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,OAAO,GAAG,CAAC,CAAC,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,EAAE,CAAC,KAAK,OAAO,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC;IACrL,OAAO,GAAG,IAAI,QAAQ,GAAG,GAAG,CAAC;AAC/B,CAAC;AAED,SAAS,aAAa,CAAC,CAAS;IAC9B,MAAM,IAAI,GAAuB;QAC/B,CAAC,SAAS,EAAE,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,cAAc,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC;QAChF,CAAC,iBAAiB,EAAE,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;QAC1C,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;QAChC,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC;QACxC,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;QACvC,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;QAC/B,CAAC,YAAY,EAAE,IAAI,CAAC,CAAC,cAAc,EAAE,CAAC;KACvC,CAAC;IACF,OAAO,CAAC,CAAC,SAAS,EAAE,EAAE,KAAK,EAAE,eAAe,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAClK,CAAC;AAED,SAAS,KAAK,CAAC,CAAS;IACtB,MAAM,KAAK,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IAC5C,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,OAAO,CAAC,IAAI,IAAI,IAAI,CAAC,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACzC,CAAC,IAAI,IAAI,CAAC;QACV,CAAC,EAAE,CAAC;IACN,CAAC;IACD,OAAO,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;AACrD,CAAC"} \ No newline at end of file +{"version":3,"file":"admin.js","sourceRoot":"","sources":["../src/admin.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/web/dist/api.js.map b/web/dist/api.js.map index 144941a..c1933c6 100644 --- a/web/dist/api.js.map +++ b/web/dist/api.js.map @@ -1 +1 @@ -{"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/web/dist/state.js b/web/dist/state.js index 714c858..76ba31b 100644 --- a/web/dist/state.js +++ b/web/dist/state.js @@ -110,6 +110,7 @@ export function handleEvent(ev) { if (ev.task_id && ev.task_id === state.selectedTaskId && !liveDuringLoad) { notify("stream", ev); } + notify("event", ev); return; } if (ev.task_id && ev.task_id === state.selectedTaskId) { diff --git a/web/dist/state.js.map b/web/dist/state.js.map index 54ad24d..5cff20b 100644 --- a/web/dist/state.js.map +++ b/web/dist/state.js.map @@ -1 +1 @@ -{"version":3,"file":"state.js","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"state.js","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/web/src/admin.ts b/web/src/admin.ts index 778e844..711ed9a 100644 --- a/web/src/admin.ts +++ b/web/src/admin.ts @@ -1,8 +1,8 @@ -import { get, type ByreEvent, type Status, type TurnRef } from "./api.js"; +import { get, type AgentProcess, type ByreEvent, type Status, type TurnRef, type Zombie } from "./api.js"; import { ago, errorText, h, replace } from "./dom.js"; import { findTask, on, onEach, state } from "./state.js"; -const refreshKinds = new Set(["session.created", "session.state", "task.created", "task.merged", "task.discarded", "project.created"]); +const refreshKinds = new Set(["session.created", "session.state", "task.created", "task.merged", "task.discarded", "project.created", "admin.health"]); export function mountAdmin(root: HTMLElement): () => void { let status: Status | undefined; @@ -16,17 +16,21 @@ export function mountAdmin(root: HTMLElement): () => void { replace(root, h("div", { class: error ? "empty error-text" : "empty" }, error || "Loading…")); return; } + const scrollTop = root.querySelector(".admin-view")?.scrollTop ?? 0; + const open = new Set([...root.querySelectorAll(".problems details[open]")].map((d) => d.dataset["vendor"] ?? "")); replace( root, h( "div", { class: "admin-view" }, error && h("p", { class: "error-text" }, error), + problemsSection(status, open), turnsSection(status), rateLimitsSection(status.rate_limits), serverSection(status), ), ); + root.querySelector(".admin-view")!.scrollTop = scrollTop; }; const refresh = async () => { @@ -59,8 +63,20 @@ export function mountAdmin(root: HTMLElement): () => void { void refresh(); return; } - if (status && ev.seq > status.last_event_seq) { + if (!status) { + return; + } + let changed = false; + const proc = status.agent_processes.find((p) => p.session_id === ev.agent_session_id); + if (proc && Date.parse(ev.created_at) - Date.parse(proc.last_event_at) >= 1000) { + proc.last_event_at = ev.created_at; + changed = true; + } + if (ev.seq > status.last_event_seq) { status.last_event_seq = ev.seq; + changed = true; + } + if (changed) { render(); } }; @@ -102,9 +118,9 @@ function turnsSection(s: Status): HTMLElement { h("div", { class: "cap-tile" }, h("div", { class: "cap-label" }, "Queued"), h("div", { class: s.queued_turns.length > 0 ? "cap-value warn" : "cap-value" }, s.queued_turns.length)), ), h("h3", {}, "Running"), - turnList(s.running_turns, "running", "No turns running."), + turnList(s.running_turns, "running", "No turns running.", s.agent_processes), h("h3", {}, "Queued"), - turnList(s.queued_turns, "queued", "Nothing queued."), + turnList(s.queued_turns, "queued", "Nothing queued.", []), ); } @@ -118,7 +134,7 @@ function capTile(label: string, running: number, cap: number | undefined): HTMLE ); } -function turnList(turns: TurnRef[], kind: "running" | "queued", empty: string): HTMLElement { +function turnList(turns: TurnRef[], kind: "running" | "queued", empty: string, procs: AgentProcess[]): HTMLElement { return h( "ul", { class: "task-list" }, @@ -133,6 +149,7 @@ function turnList(turns: TurnRef[], kind: "running" | "queued", empty: string): { href: `#/task/${t.task_id}` }, kind === "queued" ? h("span", { class: "queue-pos" }, `${i + 1}.`) : h("span", { class: "dot running", title: "running" }), h("span", { class: "task-title" }, project && h("span", { class: "project-name" }, `${project.name} ▸ `), task?.title ?? t.task_id), + liveness(procs.find((p) => p.session_id === t.session_id)), h("span", { class: "task-state" }, t.vendor), ), ); @@ -141,6 +158,75 @@ function turnList(turns: TurnRef[], kind: "running" | "queued", empty: string): ); } +function taskLink(taskId: string | undefined): HTMLElement | string { + if (!taskId) { + return "—"; + } + const task = findTask(taskId); + const project = task ? state.projects.find((p) => p.id === task.project_id) : undefined; + return h("a", { href: `#/task/${taskId}` }, project ? `${project.name} ▸ ` : "", task?.title ?? taskId); +} + +function since(iso: string): string { + const seconds = Math.max(0, Math.round((Date.now() - Date.parse(iso)) / 1000)); + if (seconds < 60) { + return `${seconds}s ago`; + } + if (seconds < 3600) { + return `${Math.floor(seconds / 60)}m ${seconds % 60}s ago`; + } + return ago(iso); +} + +function liveness(p: AgentProcess | undefined): HTMLElement | false { + if (!p) { + return false; + } + if (p.health === "ok") { + return h("span", { class: "task-state" }, since(p.last_event_at)); + } + return h("span", { class: "task-state danger" }, `${p.health} · ${since(p.last_event_at)}`); +} + +const zombieLabel: Record = { + running_without_process: "Running with no process", + process_without_session: "Process with no session", +}; + +function problemsSection(s: Status, openVendors: Set): HTMLElement | false { + const pe = s.protocol_errors; + const vendors = Object.keys(pe.counts).sort(); + if (s.zombies.length === 0 && vendors.length === 0) { + return false; + } + return h( + "section", + { class: "admin-section" }, + h("h2", {}, "Problems"), + h( + "ul", + { class: "problems" }, + ...s.zombies.map((z) => h("li", {}, h("span", { class: "danger" }, zombieLabel[z.kind]), " ", z.task_id ? taskLink(z.task_id) : "", " ", h("span", { class: "hint" }, z.detail))), + ...vendors.map((v) => { + const latest = pe.recent.find((e) => e.vendor === v); + const n = pe.counts[v] ?? 0; + return h( + "li", + {}, + h("span", { class: "danger" }, `${v}: ${n} bad stream line${n === 1 ? "" : "s"}`), + latest && + h( + "details", + { data: { vendor: v }, open: openVendors.has(v) }, + h("summary", {}, `latest ${since(latest.at)}: ${latest.reason}`), + h("pre", { class: "raw-line" }, latest.raw, new TextEncoder().encode(latest.raw).length < latest.size ? `… (${bytes(latest.size)})` : ""), + ), + ); + }), + ), + ); +} + function rateLimitsSection(limits: Status["rate_limits"]): HTMLElement { const vendors = Object.keys(limits).sort(); return h( diff --git a/web/src/api.gen.ts b/web/src/api.gen.ts index a185066..c6a06fe 100644 --- a/web/src/api.gen.ts +++ b/web/src/api.gen.ts @@ -732,6 +732,13 @@ export interface components { Status: { /** @description Agent processes currently running. */ live_agents: number; + /** @description Every agent process byre is tracking, idle sessions included. */ + agent_processes: components["schemas"]["AgentProcess"][]; + /** @description Sessions marked running with no live process, and agent processes byre has no session for. */ + zombies: components["schemas"]["Zombie"][]; + /** @description How long a running turn may go without a stream event before it counts as stuck. */ + stuck_after_seconds: number; + protocol_errors: components["schemas"]["ProtocolErrors"]; /** @description Turns running now, oldest session first. */ running_turns: components["schemas"]["TurnRef"][]; /** @description Turns waiting for room under the caps, in the order they will start. */ @@ -764,6 +771,51 @@ export interface components { task_id: string; vendor: string; }; + AgentProcess: { + session_id: string; + task_id: string; + vendor: string; + pid: number; + /** @description The session's state */ + state: string; + /** + * Format: date-time + * @description When the process last wrote a line to its stream + */ + last_event_at: string; + /** + * @description stuck means a turn is running and no stream event has arrived for stuck_after_seconds; exited means the process is gone. + * @enum {string} + */ + health: "ok" | "stuck" | "exited"; + }; + Zombie: { + /** @enum {string} */ + kind: "running_without_process" | "process_without_session"; + detail: string; + session_id?: string; + task_id?: string; + pid?: number; + }; + ProtocolErrors: { + /** @description Stream lines the adapter couldn't parse or didn't recognise since byre started, keyed by vendor. */ + counts: { + [key: string]: number; + }; + /** @description The most recent ones, newest first. */ + recent: components["schemas"]["ProtocolError"][]; + }; + ProtocolError: { + vendor: string; + session_id: string; + /** Format: date-time */ + at: string; + reason: string; + /** @description The line as received */ + raw: string; + /** @description The line's full length in bytes. */ + size: number; + }; }; responses: { /** @description Error. */ diff --git a/web/src/api.ts b/web/src/api.ts index b4532db..bd4d02b 100644 --- a/web/src/api.ts +++ b/web/src/api.ts @@ -14,6 +14,9 @@ export type ByreEvent = Schemas["Event"]; export type Status = Schemas["Status"]; export type NotificationPrefs = Schemas["NotificationPrefs"]; export type TurnRef = Schemas["TurnRef"]; +export type AgentProcess = Schemas["AgentProcess"]; +export type Zombie = Schemas["Zombie"]; +export type ProtocolErrors = Schemas["ProtocolErrors"]; type Method = "get" | "post" | "put" | "delete"; diff --git a/web/src/state.ts b/web/src/state.ts index 691bd3a..a758a2e 100644 --- a/web/src/state.ts +++ b/web/src/state.ts @@ -131,6 +131,7 @@ export function handleEvent(ev: ByreEvent): void { if (ev.task_id && ev.task_id === state.selectedTaskId && !liveDuringLoad) { notify("stream", ev); } + notify("event", ev); return; } diff --git a/web/style.css b/web/style.css index 7d21418..e4633e4 100644 --- a/web/style.css +++ b/web/style.css @@ -1385,7 +1385,9 @@ pre.diff .meta { } .rl-status.danger, -.rl-windows .danger { +.rl-windows .danger, +.problems .danger, +.task-state.danger { color: var(--danger); } @@ -1402,6 +1404,31 @@ pre.diff .meta { border-bottom: 1px solid var(--border); } +.problems { + list-style: none; + margin: 0; + padding: 0; +} + +.problems li { + padding: 2px 0; + word-break: break-word; +} + +.problems summary { + color: var(--muted); + cursor: pointer; +} + +.raw-line { + margin: 4px 0 0; + font-size: 11px; + white-space: pre-wrap; + word-break: break-all; + max-height: 12em; + overflow-y: auto; +} + .rl-windows th { font-weight: 400; font-size: 11px;