From 8d9ba1084916b14d7865dad1d5e6f4493a750860 Mon Sep 17 00:00:00 2001 From: Ian Gulliver Date: Sat, 17 Oct 2026 23:24:26 -0700 Subject: [PATCH] Show connected clients and their last human action in Status --- api/openapi.yaml | 1 + docs/operations.md | 6 +- internal/api/api.gen.go | 470 +++++++++++++++++----------------- internal/server/hub.go | 36 ++- internal/server/server.go | 24 ++ internal/server/sse.go | 2 + internal/server/terminal.go | 5 +- mac/Sources/Byre/Status.swift | 34 ++- 8 files changed, 332 insertions(+), 246 deletions(-) diff --git a/api/openapi.yaml b/api/openapi.yaml index a27a280..8cc2aad 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -1734,6 +1734,7 @@ components: host: { type: string, description: The Mac's name from its Byre-Host header; empty when the client sent none. } commit: { type: string, description: The git commit the app was built from, from its Byre-Commit header; empty when the client sent none. } connected_at: { type: string, format: date-time, description: When the client's oldest open stream connected. } + last_action_at: { type: string, format: date-time, description: When byre last received a request the human made from this client since byre started; absent when none. } TurnRef: type: object diff --git a/docs/operations.md b/docs/operations.md index b62b1f3..9dd0c31 100644 --- a/docs/operations.md +++ b/docs/operations.md @@ -192,7 +192,7 @@ Codex runs with its own home, `codex` under the data directory (`CODEX_HOME`, se ## Status -`GET /api/admin/status` reports `running_turns` (task sessions now `running`, as session, task and vendor; lead turns aren't listed); `rate_limits`, the latest status each vendor sent since byre started (Claude's `rate_limit_info`, with `windowMins` added to each `unifiedWindows` entry by `addClaudeWindowMins` in `internal/agents/manager.go` from the window's name, since Claude doesn't send a length; Codex's `account/rateLimits/updated` reshaped by `codexRateLimit` in `internal/agents/codex.go` into the same `status`, `rateLimitType` and `unifiedWindows` fields, `windowMins` from its `windowDurationMins`). Claude only reports during its turns; Codex is also asked once at startup (`readCodexRateLimit`, a short-lived `codex app-server` that calls `account/rateLimits/read` without opening a thread), so its entry is there before any Codex turn runs, and every Codex update broadcasts `admin.health`; `agent_processes`, `zombies`, `stuck_after_seconds` and `protocol_errors` (below); `clients`, one entry per distinct login, `host` and `commit` among the open `GET /api/events` streams, with the oldest stream's `connected_at` (the app opens two streams, so it shows once). `host` and `commit` come from the `Byre-Host` and `Byre-Commit` headers, which the macOS app sends on every request: the Mac's name (`Host.current().localizedName`, else `ProcessInfo.processInfo.hostName`, the same name `ClientRun.swift` reports) and the `ByreCommit` that `mac/bundle.sh` stamped into its `Info.plist`; they are empty for a client that sends neither. To check that a rebuilt app is running, compare each client's `commit` with the commit it was built from. And server counts (start time, agent processes, projects, active tasks, SSE clients, database size including WAL, last event seq). +`GET /api/admin/status` reports `running_turns` (task sessions now `running`, as session, task and vendor; lead turns aren't listed); `rate_limits`, the latest status each vendor sent since byre started (Claude's `rate_limit_info`, with `windowMins` added to each `unifiedWindows` entry by `addClaudeWindowMins` in `internal/agents/manager.go` from the window's name, since Claude doesn't send a length; Codex's `account/rateLimits/updated` reshaped by `codexRateLimit` in `internal/agents/codex.go` into the same `status`, `rateLimitType` and `unifiedWindows` fields, `windowMins` from its `windowDurationMins`). Claude only reports during its turns; Codex is also asked once at startup (`readCodexRateLimit`, a short-lived `codex app-server` that calls `account/rateLimits/read` without opening a thread), so its entry is there before any Codex turn runs, and every Codex update broadcasts `admin.health`; `agent_processes`, `zombies`, `stuck_after_seconds` and `protocol_errors` (below); `clients`, one entry per distinct login, `host` and `commit` among the open `GET /api/events` streams, with the oldest stream's `connected_at` (the app opens two streams, so it shows once). `host` and `commit` come from the `Byre-Host` and `Byre-Commit` headers, which the macOS app sends on every request: the Mac's name (`Host.current().localizedName`, else `ProcessInfo.processInfo.hostName`, the same name `ClientRun.swift` reports) and the `ByreCommit` that `mac/bundle.sh` stamped into its `Info.plist`; they are empty for a client that sends neither. To check that a rebuilt app is running, compare each client's `commit` with the commit it was built from. `last_action_at` is when byre last received a request the human made from that login, host and commit: any request other than a `GET`, except the three the app sends on its own (`PUT /api/me/pane-focus`, `PUT /api/tasks/{taskId}/summary-read`, `POST /api/inbox/{itemId}/runs`, listed in `automaticRequests` in `internal/server/server.go`), plus `GET /api/inbox/{itemId}/bundle`, which the app fetches only when the human clicks Run (`humanGets`), and every binary message on a terminal WebSocket, which includes the replies SwiftTerm sends by itself to queries in the output (cursor position and the like). The `identify` middleware and `terminalInput` record it in the hub's memory, so it is absent until that client's first action after byre starts. An action at least a second after the last one from that client that broadcast, and every `GET /api/events` stream opening or closing, broadcasts an unstored `admin.clients` event (`clientsChanged`). And server counts (start time, agent processes, projects, active tasks, SSE clients, database size including WAL, last event seq). Agent health is `internal/agents/health.go`: @@ -201,7 +201,7 @@ Agent health is `internal/agents/health.go`: - **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 left pane's **Status** tab 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); each running turn, linking to its task, with the time since its process's last event and `stuck` or `exited` when it is; each vendor's rate-limit status with its reset time and, per window, the share used, the share of the window's time elapsed (from `resetsAt` and `windowMins`), and a pace bar whose fill is the share used and whose vertical line is the share elapsed, the fill warn-coloured when usage is ahead of the line; 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`. +The left pane's **Status** tab 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); each running turn, linking to its task, with the time since its process's last event and `stuck` or `exited` when it is; each vendor's rate-limit status with its reset time and, per window, the share used, the share of the window's time elapsed (from `resetsAt` and `windowMins`), and a pace bar whose fill is the share used and whose vertical line is the share elapsed, the fill warn-coloured when usage is ahead of the line; a Clients table, one row per entry in `clients`, with the Mac, user, the build's commit (cut to 8 characters, the full one in its tooltip), the time since it connected and the time since its last action; and the server counts. It refetches the status when a `session.created`, `session.state`, `task.*`, `project.created`, `admin.health` or `admin.clients` 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.subagents`, `session.model`, and `session.context`. Anything else Claude Code emits (rate-limit status, task progress, state changes) is recorded unchanged as `agent.vendor`. Codex items map onto the same kinds (`handleCodex` in `internal/agents/codex.go`): an `agentMessage` becomes `agent.message`, a `reasoning` item's summary `agent.thinking`, and `commandExecution` (as `Bash`), `fileChange` (as `apply_patch`), `dynamicToolCall`, `mcpToolCall` and `webSearch` items an `agent.tool_call` when they start and an `agent.tool_result` when they complete. Other completed items and the `error` and `warning` notifications are recorded as `agent.vendor`; the remaining notifications (thread status, MCP startup and the like) aren't recorded. `item/agentMessage/delta` becomes `agent.delta`, and `thread/tokenUsage/updated` `session.context`. 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 `summary: true` when its `result` is the turn's summary (see above). Activity inside a subagent (`agent.message`, `agent.thinking`, `agent.tool_call`, `agent.tool_result`) carries `payload.parent_tool_use_id`, the ID of the tool call that started the subagent, and never updates `session.context`. `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's **Compact** button shows the latest one as a background fill across the button, proportional to the window, tinted amber from 70% and red from 90%; the exact count is in its tooltip. @@ -370,7 +370,7 @@ A workflow is a named graph of stages, each a stage name, the stages it comes af ## Events -`GET /api/events` is server-sent events: `id` is the event's `seq`, `data` the Event JSON. Reconnecting with `Last-Event-ID` or `?after=` replays everything after that seq before going live. `?task_id=` limits the stream, replay and live, to one task's events (404 for an unknown task); `?lead=true` to the events of the caller's lead agent sessions. An event with an `owner` (a lead agent's events, a user's `focus.updated`, `lead.note_saved` and `lead.note_deleted`) goes only to that user's streams; the others never see it. Unstored events (`seq` 0, sent with `Store.Broadcast`: `agent.delta` and `admin.health`) go out live only, without an `id` line, so they don't move `Last-Event-ID`. A client that falls further behind than its subscriber buffer (`subscribe` in `internal/server/hub.go`) is disconnected and catches up by reconnecting. Every 15s the server sends an `event: ping` so clients can tell a live stream from a dead one. Once the replay is written, before anything live, the server sends an `event: ready`. +`GET /api/events` is server-sent events: `id` is the event's `seq`, `data` the Event JSON. Reconnecting with `Last-Event-ID` or `?after=` replays everything after that seq before going live. `?task_id=` limits the stream, replay and live, to one task's events (404 for an unknown task); `?lead=true` to the events of the caller's lead agent sessions. An event with an `owner` (a lead agent's events, a user's `focus.updated`, `lead.note_saved` and `lead.note_deleted`) goes only to that user's streams; the others never see it. Unstored events (`seq` 0, sent with `Store.Broadcast`: `agent.delta`, `admin.health` and `admin.clients`) go out live only, without an `id` line, so they don't move `Last-Event-ID`. A client that falls further behind than its subscriber buffer (`subscribe` in `internal/server/hub.go`) is disconnected and catches up by reconnecting. Every 15s the server sends an `event: ping` so clients can tell a live stream from a dead one. Once the replay is written, before anything live, the server sends an `event: ready`. The macOS app keeps two streams (`EventStream` in `mac/Sources/Byre/EventStream.swift`). The global one (`startGlobalStream` in `mac/Sources/Byre/Store.swift`), opened after the `last_event_seq` of `GET /api/admin/status`, carries every event the user may see from the app's startup onward; `handleEvent` (`Store.swift`) drives the task, session, inbox and lead session lists, the right pane's focus and the Status tab from it, and hands each event with a `task_id` to that task's feed. The lead's (`startLeadStream`) first fetches the last 50 turns from `GET /api/me/lead/events`, retrying every 2s until it succeeds, then opens with `?lead=true` and `after` the last seq of that page, replays what arrived since and stays live. There is no per-task stream: selecting a task opens nothing. Only the global stream drives the connection dot. diff --git a/internal/api/api.gen.go b/internal/api/api.gen.go index b69ce89..c9f9757 100644 --- a/internal/api/api.gen.go +++ b/internal/api/api.gen.go @@ -646,8 +646,11 @@ type ConnectedClient struct { ConnectedAt time.Time `json:"connected_at"` // Host The Mac's name from its Byre-Host header; empty when the client sent none. - Host string `json:"host"` - Login string `json:"login"` + Host string `json:"host"` + + // LastActionAt When byre last received a request the human made from this client since byre started; absent when none. + LastActionAt *time.Time `json:"last_action_at,omitempty"` + Login string `json:"login"` } // DirEntry defines model for DirEntry. @@ -3627,237 +3630,238 @@ 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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", + "IcrTpVneoBlbC8wO7vN7FZD59On/DgA=", } // decodeSpec returns the embedded OpenAPI spec as raw JSON bytes, diff --git a/internal/server/hub.go b/internal/server/hub.go index cad3552..dec24c6 100644 --- a/internal/server/hub.go +++ b/internal/server/hub.go @@ -3,17 +3,36 @@ package server import ( "sort" "sync" + "time" "g.fc.run/misc/byre/internal/api" ) +type clientKey struct{ login, host, commit string } + type hub struct { - mu sync.Mutex - subs map[chan api.Event]api.ConnectedClient + mu sync.Mutex + subs map[chan api.Event]api.ConnectedClient + lastAction map[clientKey]time.Time + lastNotified map[clientKey]time.Time } func newHub() *hub { - return &hub{subs: map[chan api.Event]api.ConnectedClient{}} + return &hub{subs: map[chan api.Event]api.ConnectedClient{}, lastAction: map[clientKey]time.Time{}, lastNotified: map[clientKey]time.Time{}} +} + +func (h *hub) acted(login, host, commit string) bool { + h.mu.Lock() + defer h.mu.Unlock() + k := clientKey{login, host, commit} + now := time.Now() + h.lastAction[k] = now + prev, ok := h.lastNotified[k] + if ok && now.Sub(prev) < time.Second { + return false + } + h.lastNotified[k] = now + return true } func (h *hub) subscribe(client api.ConnectedClient) chan api.Event { @@ -57,10 +76,9 @@ func (h *hub) count() int { func (h *hub) clients() []api.ConnectedClient { h.mu.Lock() defer h.mu.Unlock() - type key struct{ login, host, commit string } - oldest := map[key]api.ConnectedClient{} + oldest := map[clientKey]api.ConnectedClient{} for _, c := range h.subs { - k := key{c.Login, c.Host, c.Commit} + k := clientKey{c.Login, c.Host, c.Commit} prev, ok := oldest[k] if ok && !c.ConnectedAt.Before(prev.ConnectedAt) { continue @@ -68,7 +86,11 @@ func (h *hub) clients() []api.ConnectedClient { oldest[k] = c } out := []api.ConnectedClient{} - for _, c := range oldest { + for k, c := range oldest { + at, ok := h.lastAction[k] + if ok { + c.LastActionAt = &at + } out = append(out, c) } sort.Slice(out, func(i, j int) bool { return out[i].ConnectedAt.Before(out[j].ConnectedAt) }) diff --git a/internal/server/server.go b/internal/server/server.go index 54c1e6b..6dcf3b1 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -134,10 +134,34 @@ func (s *Server) identify(next http.Handler) http.Handler { return } + if (r.Method != http.MethodGet && !automaticRequests[r.Pattern]) || humanGets[r.Pattern] { + s.acted(login, r) + } + next.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), ctxKey{}, login))) }) } +func (s *Server) acted(login string, r *http.Request) { + if s.hub.acted(login, r.Header.Get("Byre-Host"), r.Header.Get("Byre-Commit")) { + s.clientsChanged() + } +} + +func (s *Server) clientsChanged() { + s.store.Broadcast(api.Event{Kind: "admin.clients", Actor: "byre", Payload: map[string]any{}}) +} + +var automaticRequests = map[string]bool{ + "PUT /api/me/pane-focus": true, + "PUT /api/tasks/{taskId}/summary-read": true, + "POST /api/inbox/{itemId}/runs": true, +} + +var humanGets = map[string]bool{ + "GET /api/inbox/{itemId}/bundle": true, +} + func (s *Server) ensureUser(ctx context.Context, login, name string) error { s.usersMu.Lock() defer s.usersMu.Unlock() diff --git a/internal/server/sse.go b/internal/server/sse.go index 220332e..6eae96f 100644 --- a/internal/server/sse.go +++ b/internal/server/sse.go @@ -61,7 +61,9 @@ func (s *Server) StreamEvents(w http.ResponseWriter, r *http.Request, params api Commit: r.Header.Get("Byre-Commit"), ConnectedAt: time.Now(), }) + defer s.clientsChanged() defer s.hub.unsubscribe(live) + s.clientsChanged() for { events, err := backfill(last) diff --git a/internal/server/terminal.go b/internal/server/terminal.go index 5532ad6..d5a4997 100644 --- a/internal/server/terminal.go +++ b/internal/server/terminal.go @@ -232,7 +232,7 @@ func (s *Server) GetTaskTerminal(w http.ResponseWriter, r *http.Request, taskID defer cancel() go func() { defer cancel() - terminalInput(ctx, conn, term) + terminalInput(ctx, conn, term, func() { s.acted(actor(r), r) }) }() if len(backlog) > 0 { @@ -264,13 +264,14 @@ func (s *Server) GetTaskTerminal(w http.ResponseWriter, r *http.Request, taskID } } -func terminalInput(ctx context.Context, conn *websocket.Conn, term *terminal) { +func terminalInput(ctx context.Context, conn *websocket.Conn, term *terminal, acted func()) { for { typ, b, err := conn.Read(ctx) if err != nil { return } if typ == websocket.MessageBinary { + acted() _, err = term.ptmx.Write(b) if err != nil { slog.Debug("terminal input", "err", err) diff --git a/mac/Sources/Byre/Status.swift b/mac/Sources/Byre/Status.swift index 0914f23..0f292a0 100644 --- a/mac/Sources/Byre/Status.swift +++ b/mac/Sources/Byre/Status.swift @@ -4,7 +4,7 @@ import SwiftUI typealias AgentProcess = Components.Schemas.AgentProcess typealias Zombie = Components.Schemas.Zombie -private let refreshKinds: Set = ["session.created", "session.state", "task.created", "task.merged", "task.discarded", "project.created", "admin.health"] +private let refreshKinds: Set = ["session.created", "session.state", "task.created", "task.merged", "task.discarded", "project.created", "admin.health", "admin.clients"] private let zombieLabel: [Zombie.KindPayload: String] = [ .runningWithoutProcess: "Running with no process", @@ -88,6 +88,8 @@ struct StatusView: View { if vendors.isEmpty { Text("No vendor has reported a rate-limit status since byre started.").foregroundStyle(Palette.muted) } + section("Clients") + ClientTable(status: status) section("Server") ServerRows(status: status) } @@ -377,6 +379,36 @@ private struct PaceBar: View { } } +private struct ClientTable: View { + let status: ServerStatus + + var body: some View { + if status.clients.isEmpty { + Text("No clients connected.").foregroundStyle(Palette.muted) + } else { + Grid(alignment: .leading, horizontalSpacing: 12, verticalSpacing: 2) { + GridRow { + ForEach(["Mac", "User", "Build", "Connected", "Last action"], id: \.self) { h in + Text(h).font(Fonts.font(11)).foregroundStyle(Palette.muted) + } + } + Divider().overlay(Palette.border) + ForEach(Array(status.clients.enumerated()), id: \.offset) { _, c in + GridRow { + Text(c.host.isEmpty ? "—" : c.host) + Text(c.login) + Text(c.commit.isEmpty ? "—" : String(c.commit.prefix(8))).help(c.commit) + Text(since(c.connectedAt)) + Text(c.lastActionAt.map(since) ?? "—") + } + .textSelection(.enabled) + Divider().overlay(Palette.border) + } + } + } + } +} + private struct ServerRows: View { let status: ServerStatus