Skip to content

D15: Studio event bus WS /events - #852

Merged
LinuxDevil merged 1 commit into
mainfrom
fb-d15-event-bus
Nov 2, 2026
Merged

LinuxDevil merged 1 commit into
mainfrom
fb-d15-event-bus

Conversation

@LinuxDevil

Copy link
Copy Markdown
Owner

What changed

One Studio-wide WebSocket, WS /events, pushes StudioEvents to every open tab; the agents list refreshes when any agent changes, replacing polling.

New code

  • shared/wireTypes.ts: StudioEvent union (agent.changed, version.summary, draft.changed, suggestion.new, inbox.count).
  • server/events/eventBus.ts: StudioEventBus — synchronous fan-out over EventEmitter (setMaxListeners(0)), publish/subscribe.
  • server/ws/upgradeRouter.ts: the ONE upgrade listener on the http.Server. Runs the shared Host/Origin/token gate (moved from wsServer.ts), then dispatches by pathname to the first matching registered route (exact string or RegExp, registration order); unmatched paths are destroyed; duplicate registrations throw. Later WS endpoints (e.g. the Author stream) register on it.
  • server/events/eventsWs.ts: attachEventsWebSocket(router, events) registers /events; each socket gets every published event as JSON; nothing on connect.
  • src/runtime/runtimeClient.ts: subscribeEvents(onEvent, connection?) — WS /events?token=.
  • src/state/useStudioEvents.ts: subscribes once, reconnects 2 s after loss (same pattern as useAgentStream).

Changed code

  • server/wsServer.ts: no longer listens to upgrade itself — registers /^\/agents\/([^/]+)\/stream$/ on the router; stream behavior (status + chat snapshot on connect, id validation) unchanged.
  • server/workspace/context.ts: events: StudioEventBus on WorkspaceContext, built in createWorkspaceContext — the bus is per workspace.
  • server/app.ts: eventBus?: StudioEventBus in CreateAppOptions (test seam for the default context); /events added to API_PREFIXES and the SPA fallback regex.
  • Publishers (workspaceOf(req).events.publish(...) after the write succeeds): PUT/DELETE /agents/:id → agent.changed 'save'; POST /agents (blank/template/copy) → 'save'; PATCH /agents/:id/meta → 'meta'; POST /agents/:id/versions → 'version' (not on a no-op save); restore → 'restore'; draft create/withdraw/approve/reject → draft.changed (+ agent.changed 'draft' on approve).
  • server/index.ts: createUpgradeRouter(server, access), attachWebSocketServer(router, runs), attachEventsWebSocket(router, bus); upgradeRouter exposed on StudioServerHandle.
  • src/state/useAgentDocument.ts: agent.changed → refreshAgents(), debounced 300 ms.
  • vite.config.ts: /events proxied with ws: true.

Test summary

  • server/__tests__/eventBus.test.ts (4): publish/subscribe/unsubscribe, many listeners.
  • server/ws/__tests__/upgradeRouter.test.ts (8): exact-path and RegExp match, registration order, duplicate throws, unmatched destroyed, gate rejects 401/403 before any handler runs.
  • server/__tests__/eventsWs.test.ts (11): real startStudioServer — agent.changed frame after PUT /agents/:id, 401 without token, unknown path destroyed, stream still answers status; plus per-route publish assertions against an injected bus.
  • src/state/__tests__/useStudioEvents.test.tsx (4): subscribe once, reconnect after loss, unsubscribe on unmount, debounced agents-list refresh.

Gates: Studio typecheck, typecheck:server, test:server (842), client vitest --run (946) all green; root tsc --noEmit, lint, test:coverage (7163), fallow, docs:ratchet, api:check, docs:llms:check, size-check all green. No uncaused failures observed (coverage needed --minWorkers=1 --maxWorkers=4 locally).

Acceptance criteria

  • A ws client on /events?token=<t> receives agent.changed within one tick of a PUT /agents/x (eventsWs.test)
  • /events without the token is rejected with 401, same as the stream (eventsWs.test)
  • /agents/x/stream still works; /other is destroyed (eventsWs.test, upgradeRouter.test)
  • Two tabs: saving in one refreshes the agents list in the other — covered by the socket e2e + the debounced-refresh hook test; manual two-tab verification left for smoke
  • An unsubscribed listener receives nothing (eventBus.test)

Generated with Devin

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@LinuxDevil
LinuxDevil merged commit b182ed4 into main Nov 2, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant