- 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>
5.4 KiB
Event System
Centralized event dispatch system for the Dexto WebUI.
Architecture
SSE Stream → EventBus → Middleware → Handlers → Zustand Stores → React Components
Components
-
EventBus (
EventBus.ts) - Central event dispatcher- Manages event subscriptions
- Executes middleware pipeline
- Maintains event history for debugging
-
Handlers (
handlers.ts) - Event-to-store mapping- Registry of handlers by event name
- Each handler updates appropriate Zustand stores
- Simple, focused functions with minimal logic
-
Middleware (
middleware/) - Cross-cutting concerns- Logging middleware for debugging
- Extensible for analytics, notifications, etc.
-
Types (
types.ts) - TypeScript definitions- Re-exports StreamingEvent from @dexto/core
- Client-only events (connection status, etc.)
Usage
Setting Up Event Handlers
In your app initialization or EventBusProvider:
import { useEventBus } from '@/components/providers/EventBusProvider';
import { setupEventHandlers } from '@/lib/events';
function MyApp() {
const bus = useEventBus();
useEffect(() => {
const cleanup = setupEventHandlers(bus);
return cleanup;
}, [bus]);
return <YourComponents />;
}
Dispatching Events
Events are automatically dispatched from the SSE stream. For testing or manual dispatch:
import { eventBus } from '@/lib/events';
eventBus.dispatch({
name: 'llm:chunk',
sessionId: 'session-123',
content: 'Hello',
chunkType: 'text',
});
Subscribing to Events
For custom logic beyond the default handlers:
import { useEventBus } from '@/components/providers/EventBusProvider';
function MyComponent() {
const bus = useEventBus();
useEffect(() => {
const subscription = bus.on('llm:response', (event) => {
console.log('Response received:', event.content);
});
return () => subscription.unsubscribe();
}, [bus]);
}
Event Handlers
Each handler corresponds to a StreamingEvent type from @dexto/core:
| Event | Handler | Store Updates |
|---|---|---|
llm:thinking |
handleLLMThinking |
chatStore (processing=true), agentStore (status='thinking') |
llm:chunk |
handleLLMChunk |
chatStore (append to streaming message) |
llm:response |
handleLLMResponse |
chatStore (finalize message with metadata) |
llm:tool-call |
handleToolCall |
chatStore (add tool message), agentStore (status='executing_tool') |
llm:tool-result |
handleToolResult |
chatStore (update tool message with result) |
llm:error |
handleLLMError |
chatStore (set error, processing=false), agentStore (status='idle') |
approval:request |
handleApprovalRequest |
agentStore (status='awaiting_approval') |
approval:response |
handleApprovalResponse |
agentStore (status='idle') |
run:complete |
handleRunComplete |
chatStore (processing=false), agentStore (status='idle') |
session:title-updated |
handleSessionTitleUpdated |
(handled by TanStack Query) |
message:dequeued |
handleMessageDequeued |
chatStore (add user message from queue) |
context:compressed |
handleContextCompressed |
(log for debugging) |
Adding New Handlers
- Define the handler function in
handlers.ts:
function handleMyNewEvent(event: EventByName<'my:event'>): void {
const { sessionId, data } = event;
// Update stores as needed
useSomeStore.getState().updateSomething(sessionId, data);
}
- Register in
registerHandlers():
export function registerHandlers(): void {
// ... existing handlers
handlers.set('my:event', handleMyNewEvent);
}
- Add tests in
handlers.test.ts:
describe('handleMyNewEvent', () => {
it('should update the store correctly', () => {
const event = {
name: 'my:event' as const,
sessionId: TEST_SESSION_ID,
data: 'test',
};
handleMyNewEvent(event);
const state = useSomeStore.getState();
expect(state.data).toBe('test');
});
});
Testing
Run tests:
pnpm test:unit packages/webui/lib/events/handlers.test.ts
Each handler is tested in isolation to verify correct store updates.
Design Principles
-
Handler simplicity - Handlers extract data from events and call store actions. No complex logic.
-
Store-driven - All state changes go through Zustand stores. Handlers don't mutate state directly.
-
Type safety - Events are strongly typed via StreamingEvent union from @dexto/core.
-
Testability - Each handler can be tested independently with mocked stores.
-
Single responsibility - One handler per event type, focused on one concern.
Migration from useChat
The handler registry replaces the 200+ LOC switch statement in useChat.ts:
Before:
// In useChat.ts
switch (event.name) {
case 'llm:thinking':
setProcessing(true);
// ... more logic
break;
case 'llm:chunk':
// ... 30+ lines
break;
// ... 10+ more cases
}
After:
// In handlers.ts
function handleLLMThinking(event) {
useChatStore.getState().setProcessing(event.sessionId, true);
useAgentStore.getState().setThinking(event.sessionId);
}
function handleLLMChunk(event) {
// Simple, focused logic
}
// Register all handlers
registerHandlers();
This provides:
- Better testability (test each handler independently)
- Clearer separation of concerns
- Easier to add/modify handlers
- Type safety with EventByName helper