- 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>
354 lines
9.4 KiB
Markdown
354 lines
9.4 KiB
Markdown
# 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
|
|
|
|
```tsx
|
|
// 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:
|
|
|
|
```tsx
|
|
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:
|
|
|
|
```tsx
|
|
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:
|
|
|
|
```tsx
|
|
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
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
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:
|
|
|
|
```typescript
|
|
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:
|
|
|
|
```tsx
|
|
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.
|