Skip to content

refactor(frontend/ws): unify three WebSocket managers (useWebSocket + useGlobalWebSocket + useLiveEvents) into single useEventBus #6488

Description

@mrveiss

Discovery

The frontend has three independent WebSocket subsystems with identical state-enum values defined twice and 14 callers split across patterns. Each wires to a different backend bus, so collapsing them is partially blocked on #6486 (backend event-bus unification).

File LOC Connection model State enum Callers Backend endpoint
composables/useWebSocket.ts 381 Per-call new socket none (raw refs) 8 caller-supplied
composables/useGlobalWebSocket.ts + services/GlobalWebSocketService.ts 112 + 786 Singleton pub-sub ConnectionState 3 /api/ws (EventManager)
composables/useLiveEvents.ts + LiveEventService 123 + ~250 Singleton, channel-scoped LiveEventConnectionState (identical values to ConnectionState) 3 /api/ws/live (LiveEventManager)

State enum collision (pure duplication)

Both define exactly: 'disconnected' | 'connecting' | 'connected' | 'error'.

Backend coupling

Today the frontend's split mirrors the backend's split: /api/ws → EventManager, /api/ws/live → LiveEventManager. Once #6486 unifies the backend buses, the frontend can collapse to one transport.

Proposed Solution

Single composable useEventBus() built on GlobalWebSocketService with channel-scoped subscriptions folded in.

export function useEventBus() {
  return {
    subscribe(channel: string, handler: EventCallback): Unsubscribe,
    unsubscribe(channel: string, handler: EventCallback): void,
    isConnected: ComputedRef<boolean>,
    connectionState: ComputedRef<ConnectionState>,
  }
}

Channel format unchanged from useLiveEvents: 'global', 'agent:{id}', 'task:{id}', 'workflow:{id}'.

Caller migration (14 total)

Caller Current Target
Terminal.vue, SSHTerminal.vue, usePrometheusMetrics.ts, useOversoverAgent.ts, useWorkflowBuilder.ts, KnowledgeResearchPanel.vue, CodeQualityDashboard.vue (8 callers of useWebSocket) useWebSocket(url) useEventBus().subscribe(channel, handler)
useCaptchaStatus.ts, ChatInterface.ts (3 callers of useGlobalWebSocket) .on(event, cb) useEventBus().subscribe('global', cb)
HeartbeatPanel.vue, WorkflowLiveDashboard.vue, useToolApproval.ts (3 callers of useLiveEvents) .subscribe('agent:X', cb) useEventBus().subscribe('agent:X', cb)

Steps

  1. Coordinate with refactor(events): unify three parallel event-bus systems (EventManager + LiveEventManager + RedisEventStreamManager) #6486 — wait for backend bus facade or design unified /api/ws endpoint together
  2. Unify state enum — export single ConnectionState from GlobalWebSocketService, delete LiveEventConnectionState
  3. Add channel-scoped subscriptions to GlobalWebSocketService — fold LiveEventService channel logic in
  4. Create useEventBus() composable — thin wrapper, supports both 'global' and 'agent:X' syntax
  5. Migrate 14 callers in 3 batches (per-call → global → live-events)
  6. Mark deprecated: useWebSocket, useLiveEvents keep working for one release with deprecation warnings
  7. Delete after grep = 0

Acceptance Criteria

  • Single ConnectionState type exported; LiveEventConnectionState removed
  • useEventBus() composable created and supports channels: 'global', 'agent:{id}', 'task:{id}', 'workflow:{id}'
  • All 14 callers migrated; git grep "useWebSocket\\|useLiveEvents" returns 0 in non-shim code
  • Reconnect behavior preserved — exponential backoff + backend-wait mode kept from GlobalWebSocketService
  • Channel isolation verified — events from agent:123 do NOT leak to agent:456 subscribers (integration test)
  • Backend restart recovery — frontend reconnects automatically within 10s
  • Event payload shape unchanged — { type, payload } structure preserved for backward compat
  • Deprecation warnings emit during test suite run for any remaining old-API imports

Risks

  • Components may switch on specific enum values — grep for hardcoded 'connecting'/'error' literals before merge
  • Reconnect storms during migration — feature-flag the new bus and migrate callers behind it
  • Hidden state-machine logic in components (e.g., Terminal.vue) — audit each caller for assumptions

Files Affected

  • Modified: services/GlobalWebSocketService.ts (add channel routing)
  • New: composables/useEventBus.ts
  • Deleted (after sunset): composables/useWebSocket.ts, composables/useLiveEvents.ts, services/LiveEventService.ts
  • Modified: 14 caller files

Related / Dependencies

Activity

  1. mrveiss commented on May 1, 2026

    @mrveiss
    OwnerAuthor

    Cross-link from session 2026-05-01 audit:

    Today's investigation found that the auth-token plumbing is duplicated and inconsistent across the three WS services this issue proposes to unify:

    Filed #6700 as the precursor: extract a shared buildAuthenticatedWsUrl() helper. It's a 30-line mechanical change that fixes the immediate bug and lays a clean foundation for this consolidation.

    Recommend this issue depend on #6700 — once auth is centralized, the unification can focus on lifecycle/channel/event-bus semantics without re-litigating authentication.

  2. github-actions commented on May 22, 2026

    @github-actions
    Contributor

    PR #8294 (merged to Dev_new_gui) references this issue with a close keyword.

    refactor(events): WebSocket event/ws refactors (#6488, #6486, #6482)

    If this issue is fully resolved, close it manually. If work remains, no action is needed.

  3. mrveiss commented on May 22, 2026

    @mrveiss
    OwnerAuthor

    Implemented and merged via PR #8294 into Dev_new_gui. Closing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions