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>
This commit is contained in:
admin
2026-01-28 00:27:56 +04:00
Unverified
parent 3b128ba3bd
commit b52318eeae
1724 changed files with 351216 additions and 0 deletions

View File

@@ -0,0 +1,512 @@
/**
* EventBus Integration Tests
*
* Tests the full flow of events through the EventBus to stores:
* Event → EventBus → Handlers → Store Actions → State Updates
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { ClientEventBus } from './EventBus.js';
import { setupEventHandlers } from './handlers.js';
import { useChatStore } from '../stores/chatStore.js';
import { useAgentStore } from '../stores/agentStore.js';
import { ApprovalType, ApprovalStatus } from '@dexto/core';
describe('EventBus Integration', () => {
let bus: ClientEventBus;
let cleanup: () => void;
beforeEach(() => {
bus = new ClientEventBus();
cleanup = setupEventHandlers(bus);
// Reset stores to clean state
useChatStore.setState({ sessions: new Map() });
useAgentStore.setState({
status: 'idle',
connectionStatus: 'disconnected',
lastHeartbeat: null,
activeSessionId: null,
currentToolName: null,
connectionError: null,
reconnectAttempts: 0,
});
});
afterEach(() => {
cleanup();
});
// =========================================================================
// LLM Events
// =========================================================================
describe('LLM Events', () => {
it('should process llm:thinking and update stores', () => {
bus.dispatch({
name: 'llm:thinking',
sessionId: 'test-session',
});
// Check agent status
expect(useAgentStore.getState().status).toBe('thinking');
expect(useAgentStore.getState().activeSessionId).toBe('test-session');
// Check chat processing state
const sessionState = useChatStore.getState().getSessionState('test-session');
expect(sessionState.processing).toBe(true);
});
it('should process llm:chunk and create streaming message', () => {
bus.dispatch({
name: 'llm:chunk',
sessionId: 'test-session',
content: 'Hello',
chunkType: 'text',
});
const sessionState = useChatStore.getState().getSessionState('test-session');
expect(sessionState.streamingMessage).not.toBeNull();
expect(sessionState.streamingMessage?.content).toBe('Hello');
expect(sessionState.streamingMessage?.role).toBe('assistant');
});
it('should append chunks to streaming message', () => {
// First chunk
bus.dispatch({
name: 'llm:chunk',
sessionId: 'test-session',
content: 'Hello',
chunkType: 'text',
});
// Second chunk
bus.dispatch({
name: 'llm:chunk',
sessionId: 'test-session',
content: ' world',
chunkType: 'text',
});
const sessionState = useChatStore.getState().getSessionState('test-session');
expect(sessionState.streamingMessage?.content).toBe('Hello world');
});
it('should handle reasoning chunks separately', () => {
// Text chunk
bus.dispatch({
name: 'llm:chunk',
sessionId: 'test-session',
content: 'Answer',
chunkType: 'text',
});
// Reasoning chunk
bus.dispatch({
name: 'llm:chunk',
sessionId: 'test-session',
content: 'Thinking...',
chunkType: 'reasoning',
});
const sessionState = useChatStore.getState().getSessionState('test-session');
expect(sessionState.streamingMessage?.content).toBe('Answer');
expect(sessionState.streamingMessage?.reasoning).toBe('Thinking...');
});
it('should finalize streaming message on llm:response', () => {
// Create streaming message
bus.dispatch({
name: 'llm:chunk',
sessionId: 'test-session',
content: 'Complete response',
chunkType: 'text',
});
// Finalize
bus.dispatch({
name: 'llm:response',
sessionId: 'test-session',
content: 'Complete response',
model: 'gpt-4',
provider: 'openai',
tokenUsage: {
inputTokens: 10,
outputTokens: 20,
totalTokens: 30,
},
});
const sessionState = useChatStore.getState().getSessionState('test-session');
expect(sessionState.streamingMessage).toBeNull();
expect(sessionState.messages).toHaveLength(1);
expect(sessionState.messages[0].content).toBe('Complete response');
expect(sessionState.messages[0].model).toBe('gpt-4');
expect(sessionState.messages[0].tokenUsage?.totalTokens).toBe(30);
});
it('should handle llm:error and update stores', () => {
bus.dispatch({
name: 'llm:error',
sessionId: 'test-session',
error: {
name: 'TestError',
message: 'Something went wrong',
},
context: 'During generation',
recoverable: true,
});
// Check agent status
expect(useAgentStore.getState().status).toBe('idle');
// Check error state
const sessionState = useChatStore.getState().getSessionState('test-session');
expect(sessionState.error).not.toBeNull();
expect(sessionState.error?.message).toBe('Something went wrong');
expect(sessionState.error?.recoverable).toBe(true);
expect(sessionState.processing).toBe(false);
});
});
// =========================================================================
// Tool Events
// =========================================================================
describe('Tool Events', () => {
it('should process llm:tool-call and create tool message', () => {
bus.dispatch({
name: 'llm:tool-call',
sessionId: 'test-session',
toolName: 'read_file',
args: { path: '/test.txt' },
callId: 'call-123',
});
// Check agent status
expect(useAgentStore.getState().status).toBe('executing_tool');
expect(useAgentStore.getState().currentToolName).toBe('read_file');
// Check tool message
const sessionState = useChatStore.getState().getSessionState('test-session');
expect(sessionState.messages).toHaveLength(1);
expect(sessionState.messages[0].toolName).toBe('read_file');
expect(sessionState.messages[0].toolCallId).toBe('call-123');
});
it('should update tool message with result', () => {
// Create tool call
bus.dispatch({
name: 'llm:tool-call',
sessionId: 'test-session',
toolName: 'read_file',
args: { path: '/test.txt' },
callId: 'call-123',
});
// Add result
const sanitizedResult = {
content: [{ type: 'text' as const, text: 'File contents' }],
meta: { toolName: 'read_file', toolCallId: 'call-123', success: true },
};
bus.dispatch({
name: 'llm:tool-result',
sessionId: 'test-session',
toolName: 'read_file',
callId: 'call-123',
success: true,
sanitized: sanitizedResult,
});
const sessionState = useChatStore.getState().getSessionState('test-session');
const toolMessage = sessionState.messages[0];
expect(toolMessage.toolResult).toEqual(sanitizedResult);
expect(toolMessage.toolResultSuccess).toBe(true);
});
it('should handle tool result with approval requirements', () => {
// Create tool call
bus.dispatch({
name: 'llm:tool-call',
sessionId: 'test-session',
toolName: 'write_file',
args: { path: '/test.txt', content: 'data' },
callId: 'call-456',
});
// Add result with approval
const sanitizedResult = {
content: [{ type: 'text' as const, text: 'File written' }],
meta: { toolName: 'write_file', toolCallId: 'call-456', success: true },
};
bus.dispatch({
name: 'llm:tool-result',
sessionId: 'test-session',
toolName: 'write_file',
callId: 'call-456',
success: true,
sanitized: sanitizedResult,
requireApproval: true,
approvalStatus: 'approved',
});
const sessionState = useChatStore.getState().getSessionState('test-session');
const toolMessage = sessionState.messages[0];
expect(toolMessage.requireApproval).toBe(true);
expect(toolMessage.approvalStatus).toBe('approved');
});
});
// =========================================================================
// Approval Events
// =========================================================================
describe('Approval Events', () => {
it('should process approval:request', () => {
bus.dispatch({
name: 'approval:request',
sessionId: 'test-session',
type: ApprovalType.TOOL_CONFIRMATION,
approvalId: 'approval-123',
timeout: 30000,
timestamp: new Date(),
metadata: {
toolName: 'write_file',
toolCallId: 'call-write-123',
args: { path: '/test.txt' },
},
});
expect(useAgentStore.getState().status).toBe('awaiting_approval');
expect(useAgentStore.getState().activeSessionId).toBe('test-session');
});
it('should process approval:response with approved status', () => {
// Set awaiting approval
bus.dispatch({
name: 'approval:request',
sessionId: 'test-session',
type: ApprovalType.TOOL_CONFIRMATION,
approvalId: 'approval-123',
timeout: 30000,
timestamp: new Date(),
metadata: {
toolName: 'write_file',
toolCallId: 'call-write-123',
args: { path: '/test.txt' },
},
});
// Approve
bus.dispatch({
name: 'approval:response',
sessionId: 'test-session',
approvalId: 'approval-123',
status: ApprovalStatus.APPROVED,
});
// Status transitions to 'thinking' - agent is resuming execution after approval
expect(useAgentStore.getState().status).toBe('thinking');
});
it('should process approval:response with rejected status', () => {
// Set awaiting approval
bus.dispatch({
name: 'approval:request',
sessionId: 'test-session',
type: ApprovalType.TOOL_CONFIRMATION,
approvalId: 'approval-456',
timeout: 30000,
timestamp: new Date(),
metadata: {
toolName: 'write_file',
toolCallId: 'call-write-456',
args: { path: '/test.txt' },
},
});
// Reject
bus.dispatch({
name: 'approval:response',
sessionId: 'test-session',
approvalId: 'approval-456',
status: ApprovalStatus.DENIED,
});
expect(useAgentStore.getState().status).toBe('idle');
});
});
// =========================================================================
// Run Events
// =========================================================================
describe('Run Events', () => {
it('should process run:complete', () => {
// Set processing state
useChatStore.getState().setProcessing('test-session', true);
useAgentStore.getState().setThinking('test-session');
// Complete run
bus.dispatch({
name: 'run:complete',
sessionId: 'test-session',
finishReason: 'stop',
stepCount: 3,
durationMs: 1500,
});
// Check states reset
const sessionState = useChatStore.getState().getSessionState('test-session');
expect(sessionState.processing).toBe(false);
expect(useAgentStore.getState().status).toBe('idle');
});
});
// =========================================================================
// Message Events
// =========================================================================
describe('Message Events', () => {
it('should process message:dequeued with text content', () => {
bus.dispatch({
name: 'message:dequeued',
sessionId: 'test-session',
count: 1,
ids: ['queued-1'],
coalesced: false,
content: [{ type: 'text', text: 'Hello from queue' }],
});
const sessionState = useChatStore.getState().getSessionState('test-session');
expect(sessionState.messages).toHaveLength(1);
expect(sessionState.messages[0].role).toBe('user');
expect(sessionState.messages[0].content).toBe('Hello from queue');
});
it('should process message:dequeued with image attachment', () => {
bus.dispatch({
name: 'message:dequeued',
sessionId: 'test-session',
count: 1,
ids: ['queued-2'],
coalesced: false,
content: [
{ type: 'text', text: 'Check this image' },
{ type: 'image', image: 'base64data', mimeType: 'image/png' },
],
});
const sessionState = useChatStore.getState().getSessionState('test-session');
expect(sessionState.messages).toHaveLength(1);
expect(sessionState.messages[0].content).toBe('Check this image');
expect(sessionState.messages[0].imageData).toEqual({
image: 'base64data',
mimeType: 'image/png',
});
});
it('should process message:dequeued with file attachment', () => {
bus.dispatch({
name: 'message:dequeued',
sessionId: 'test-session',
count: 1,
ids: ['queued-3'],
coalesced: false,
content: [
{ type: 'text', text: 'Here is the file' },
{
type: 'file',
data: 'filedata',
mimeType: 'text/plain',
filename: 'test.txt',
},
],
});
const sessionState = useChatStore.getState().getSessionState('test-session');
expect(sessionState.messages).toHaveLength(1);
expect(sessionState.messages[0].fileData).toEqual({
data: 'filedata',
mimeType: 'text/plain',
filename: 'test.txt',
});
});
});
// =========================================================================
// Multi-Session Support
// =========================================================================
describe('Multi-Session Support', () => {
it('should handle events for multiple sessions independently', () => {
// Session 1
bus.dispatch({
name: 'llm:thinking',
sessionId: 'session-1',
});
bus.dispatch({
name: 'llm:chunk',
sessionId: 'session-1',
content: 'Response 1',
chunkType: 'text',
});
// Session 2
bus.dispatch({
name: 'llm:thinking',
sessionId: 'session-2',
});
bus.dispatch({
name: 'llm:chunk',
sessionId: 'session-2',
content: 'Response 2',
chunkType: 'text',
});
// Verify isolation
const session1 = useChatStore.getState().getSessionState('session-1');
const session2 = useChatStore.getState().getSessionState('session-2');
expect(session1.streamingMessage?.content).toBe('Response 1');
expect(session2.streamingMessage?.content).toBe('Response 2');
expect(session1.processing).toBe(true);
expect(session2.processing).toBe(true);
});
});
// =========================================================================
// Error Handling
// =========================================================================
describe('Error Handling', () => {
it('should handle unknown events gracefully', () => {
// Dispatch unknown event (should not throw)
expect(() => {
bus.dispatch({
// @ts-expect-error Testing unknown event
name: 'unknown:event',
sessionId: 'test-session',
});
}).not.toThrow();
});
it('should handle events with missing sessionId', () => {
// Some events might not have sessionId
expect(() => {
bus.dispatch({
name: 'context:compacted',
sessionId: 'test-session',
originalTokens: 1000,
compactedTokens: 500,
originalMessages: 10,
compactedMessages: 5,
strategy: 'auto',
reason: 'overflow',
});
}).not.toThrow();
});
});
});