Files
SuperCharged-Claude-Code-Up…/dexto/packages/webui/lib/events/USAGE.md
admin b52318eeae feat: Add intelligent auto-router and enhanced integrations
- Add intelligent-router.sh hook for automatic agent routing
- Add AUTO-TRIGGER-SUMMARY.md documentation
- Add FINAL-INTEGRATION-SUMMARY.md documentation
- Complete Prometheus integration (6 commands + 4 tools)
- Complete Dexto integration (12 commands + 5 tools)
- Enhanced Ralph with access to all agents
- Fix /clawd command (removed disable-model-invocation)
- Update hooks.json to v5 with intelligent routing
- 291 total skills now available
- All 21 commands with automatic routing

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-28 00:27:56 +04:00

9.4 KiB

EventBus Usage Guide

Complete integration guide for the EventBus system with SSE event dispatching.

Architecture

SSE Stream → useEventDispatch → EventBus → Middleware → Handlers → Stores → UI Updates

Setup

1. Wrap App with EventBusProvider

// app/root.tsx or similar
import { EventBusProvider } from '@/components/providers/EventBusProvider';

export default function App() {
    return (
        <EventBusProvider
            enableLogging={true}           // Console logging (dev only by default)
            enableActivityLogging={true}   // Event log store
            enableNotifications={true}     // Toast notifications
        >
            <YourApp />
        </EventBusProvider>
    );
}

The provider automatically:

  • Registers middleware (logging, activity, notifications)
  • Sets up event handlers to dispatch to stores
  • Cleans up on unmount

2. Dispatch SSE Events

In your message streaming component:

import { createMessageStream } from '@dexto/client-sdk';
import { useEventDispatch } from '@/lib/events';
import { client } from '@/lib/client';

export function useMessageStream(sessionId: string) {
    const { dispatchEvent } = useEventDispatch();

    const sendMessage = async (message: string) => {
        const responsePromise = client.api['message-stream'].$post({
            json: { message, sessionId }
        });

        const stream = createMessageStream(responsePromise);

        for await (const event of stream) {
            // Dispatch to EventBus - handlers will update stores
            dispatchEvent(event);
        }
    };

    return { sendMessage };
}

3. Components React to Store Updates

Components subscribe to stores as usual:

import { useChatStore } from '@/lib/stores/chatStore';
import { useAgentStore } from '@/lib/stores/agentStore';

export function ChatInterface({ sessionId }: { sessionId: string }) {
    // Get session state
    const sessionState = useChatStore(state =>
        state.getSessionState(sessionId)
    );

    // Get agent status
    const agentStatus = useAgentStore(state => state.status);

    return (
        <div>
            {sessionState.streamingMessage && (
                <StreamingMessage message={sessionState.streamingMessage} />
            )}

            {sessionState.messages.map(msg => (
                <Message key={msg.id} message={msg} />
            ))}

            {agentStatus === 'thinking' && <ThinkingIndicator />}
        </div>
    );
}

Event Flow Examples

Example 1: LLM Response

1. SSE: llm:thinking
   → Handler: handleLLMThinking
   → Store: agentStore.setThinking(sessionId)
   → Store: chatStore.setProcessing(sessionId, true)
   → UI: Show thinking indicator

2. SSE: llm:chunk (content: "Hello")
   → Handler: handleLLMChunk
   → Store: chatStore.setStreamingMessage(sessionId, newMessage)
   → UI: Show streaming message

3. SSE: llm:chunk (content: " world")
   → Handler: handleLLMChunk
   → Store: chatStore.appendToStreamingMessage(sessionId, " world")
   → UI: Update streaming message

4. SSE: llm:response (tokenUsage, model)
   → Handler: handleLLMResponse
   → Store: chatStore.finalizeStreamingMessage(sessionId, metadata)
   → UI: Move to messages array, show token count

5. SSE: run:complete
   → Handler: handleRunComplete
   → Store: agentStore.setIdle()
   → Store: chatStore.setProcessing(sessionId, false)
   → UI: Hide thinking indicator

Example 2: Tool Call

1. SSE: llm:tool-call (toolName: "read_file", callId: "123")
   → Handler: handleToolCall
   → Store: chatStore.addMessage(sessionId, toolMessage)
   → Store: agentStore.setExecutingTool(sessionId, "read_file")
   → UI: Show tool message, show executing indicator

2. SSE: llm:tool-result (callId: "123", success: true, sanitized: "File contents")
   → Handler: handleToolResult
   → Store: chatStore.updateMessage(sessionId, messageId, { toolResult, success })
   → UI: Update tool message with result

Example 3: Approval Request

1. SSE: approval:request (type: TOOL_CONFIRMATION, toolName: "write_file")
   → Handler: handleApprovalRequest
   → Store: agentStore.setAwaitingApproval(sessionId)
   → Middleware (notification): Show toast "Tool write_file needs approval"
   → UI: Show approval dialog

2. User clicks "Approve"
   → API: POST /api/approval/respond { status: "approved" }

3. SSE: approval:response (status: "approved")
   → Handler: handleApprovalResponse
   → Store: agentStore.setIdle()
   → UI: Hide approval dialog, resume processing

Middleware

Logging Middleware

Logs all events to console (dev mode only by default):

[EventBus] llm:thinking → sessionId: abc-123
[EventBus] llm:chunk → sessionId: abc-123, chunkType: text, content: Hello

Activity Middleware

Logs events to EventLogStore for debugging panel:

import { useEventLogStore } from '@/lib/stores/eventLogStore';

export function DebugPanel() {
    const events = useEventLogStore(state => state.events);

    return (
        <div>
            {events.map(event => (
                <div key={event.id}>
                    {event.timestamp} - {event.category} - {event.description}
                </div>
            ))}
        </div>
    );
}

Notification Middleware

Shows toast notifications for important events:

  • approval:request → "Tool X needs your approval"
  • llm:error → Error message with recovery info
  • Session-aware: Only notifies for current session

Testing

Unit Test - Individual Handler

import { describe, it, expect, beforeEach } from 'vitest';
import { ClientEventBus } from './EventBus';
import { handleLLMChunk } from './handlers';
import { useChatStore } from '../stores/chatStore';

describe('handleLLMChunk', () => {
    beforeEach(() => {
        useChatStore.setState({ sessions: new Map() });
    });

    it('should create streaming message on first chunk', () => {
        handleLLMChunk({
            name: 'llm:chunk',
            sessionId: 'test',
            content: 'Hello',
            chunkType: 'text',
        });

        const state = useChatStore.getState().getSessionState('test');
        expect(state.streamingMessage?.content).toBe('Hello');
    });
});

Integration Test - Full Flow

import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { ClientEventBus } from './EventBus';
import { setupEventHandlers } from './handlers';
import { useChatStore } from '../stores/chatStore';
import { useAgentStore } from '../stores/agentStore';

describe('EventBus Integration', () => {
    let bus: ClientEventBus;
    let cleanup: () => void;

    beforeEach(() => {
        bus = new ClientEventBus();
        cleanup = setupEventHandlers(bus);

        // Reset stores
        useChatStore.setState({ sessions: new Map() });
        useAgentStore.setState({ status: 'idle', /* ... */ });
    });

    afterEach(() => {
        cleanup();
    });

    it('should handle full LLM response flow', () => {
        // Thinking
        bus.dispatch({ name: 'llm:thinking', sessionId: 'test' });
        expect(useAgentStore.getState().status).toBe('thinking');

        // Chunk
        bus.dispatch({
            name: 'llm:chunk',
            sessionId: 'test',
            content: 'Response',
            chunkType: 'text',
        });
        expect(useChatStore.getState().getSessionState('test').streamingMessage)
            .not.toBeNull();

        // Complete
        bus.dispatch({
            name: 'llm:response',
            sessionId: 'test',
            tokenUsage: { totalTokens: 100 },
        });
        expect(useChatStore.getState().getSessionState('test').messages)
            .toHaveLength(1);
    });
});

Custom Middleware

Create custom middleware to extend functionality:

import type { EventMiddleware } from '@/lib/events';

export const analyticsMiddleware: EventMiddleware = (event, next) => {
    // Track analytics
    if (event.name === 'llm:response') {
        analytics.track('llm_response', {
            sessionId: event.sessionId,
            model: event.model,
            tokens: event.tokenUsage?.totalTokens,
        });
    }

    return next(event);
};

// Use it
<EventBusProvider middleware={[analyticsMiddleware]}>
    <App />
</EventBusProvider>

Advanced: Direct Bus Access

For cases where you need direct access to the bus:

import { useEventBus } from '@/components/providers/EventBusProvider';

export function CustomComponent() {
    const bus = useEventBus();

    useEffect(() => {
        // Subscribe to specific event
        const sub = bus.on('llm:error', (event) => {
            console.error('LLM Error:', event.error);
        });

        return () => sub.unsubscribe();
    }, [bus]);

    // Dispatch custom event
    const handleAction = () => {
        bus.dispatch({
            name: 'custom:event',
            sessionId: 'test',
            // ... custom data
        });
    };
}

Summary

  1. EventBusProvider - Wrap your app, registers middleware and handlers
  2. useEventDispatch - Use in components to dispatch SSE events
  3. Middleware - Intercepts events (logging, activity, notifications)
  4. Handlers - Process events and update stores
  5. Stores - Hold state, trigger React re-renders
  6. Components - Subscribe to stores, render UI

All wired together automatically. Just dispatch events and let the system handle the rest.