claude-flow
Version:
Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration
355 lines (309 loc) • 14.5 kB
text/typescript
/**
* Intent Router Plugin
*
* Smart query → agent/tool mapping using @ruvector/wasm semantic matching.
* Routes user intents to optimal handlers with confidence scoring.
*
* Features:
* - Semantic intent classification (<1ms)
* - Multi-label routing (can route to multiple handlers)
* - Confidence-based fallback
* - Intent history for optimization
* - Dynamic route learning
*
* @example
* ```typescript
* import { intentRouterPlugin } from '@claude-flow/plugins/examples/ruvector-plugins';
* await getDefaultRegistry().register(intentRouterPlugin);
* ```
*/
import {
PluginBuilder,
MCPToolBuilder,
HookBuilder,
HookEvent,
HookPriority,
Security,
} from '../../src/index.js';
// Import shared vector utilities (consolidated from all plugins)
import {
IVectorDB,
createVectorDB,
generateHashEmbedding,
} from './shared/vector-utils.js';
// ============================================================================
// Types
// ============================================================================
export interface Intent {
id: string;
pattern: string;
category: string;
handlers: IntentHandler[];
priority: number;
examples: string[];
embedding?: Float32Array;
metadata: {
createdAt: Date;
updatedAt: Date;
usageCount: number;
successRate: number;
};
}
export interface IntentHandler {
type: 'agent' | 'tool' | 'workflow' | 'skill';
name: string;
config?: Record<string, unknown>;
weight: number;
}
export interface RouteResult {
intent: Intent;
confidence: number;
handlers: Array<IntentHandler & { score: number }>;
alternatives: Array<{ intent: Intent; confidence: number }>;
}
export interface RouterConfig {
minConfidence: number;
maxAlternatives: number;
enableLearning: boolean;
defaultHandler?: IntentHandler;
}
// ============================================================================
// Intent Router Core
// ============================================================================
export class IntentRouter {
private vectorDb: IVectorDB | null = null;
private intents = new Map<string, Intent>();
private routeHistory: Array<{ query: string; intentId: string; success: boolean; timestamp: Date }> = [];
private config: RouterConfig;
private dimensions = 768;
private nextId = 1;
private initPromise: Promise<void> | null = null;
constructor(config?: Partial<RouterConfig>) {
this.config = {
minConfidence: 0.4,
maxAlternatives: 3,
enableLearning: true,
...config,
};
}
async initialize(): Promise<void> {
if (this.vectorDb) return;
if (this.initPromise) return this.initPromise;
this.initPromise = (async () => {
this.vectorDb = await createVectorDB(this.dimensions);
await this.initializeDefaultIntents();
})();
return this.initPromise;
}
private async ensureInitialized(): Promise<IVectorDB> {
await this.initialize();
return this.vectorDb!;
}
/**
* Register a new intent pattern.
*/
async registerIntent(
pattern: string,
category: string,
handlers: IntentHandler[],
options?: { priority?: number; examples?: string[] }
): Promise<Intent> {
const db = await this.ensureInitialized();
const safePattern = Security.validateString(pattern, { maxLength: 500 });
const safeCategory = Security.validateString(category, { maxLength: 100 });
const id = `intent-${this.nextId++}`;
const examples = options?.examples ?? [];
const allText = [safePattern, ...examples].join(' ');
const embedding = this.generateEmbedding(allText);
const intent: Intent = {
id,
pattern: safePattern,
category: safeCategory,
handlers,
priority: options?.priority ?? 0,
examples,
embedding,
metadata: { createdAt: new Date(), updatedAt: new Date(), usageCount: 0, successRate: 1.0 },
};
db.insert(embedding, id, { pattern: safePattern, category: safeCategory, priority: intent.priority });
this.intents.set(id, intent);
return intent;
}
/**
* Route a query to the best matching intent handlers (<1ms).
*/
async route(query: string): Promise<RouteResult> {
const db = await this.ensureInitialized();
const safeQuery = Security.validateString(query, { maxLength: 1000 });
const queryEmbedding = this.generateEmbedding(safeQuery);
const searchResults = db.search(queryEmbedding, this.config.maxAlternatives + 1);
if (searchResults.length === 0 || searchResults[0].score < this.config.minConfidence) {
if (this.config.defaultHandler) {
const fallbackIntent: Intent = {
id: 'fallback', pattern: 'default', category: 'general',
handlers: [this.config.defaultHandler], priority: -1, examples: [],
metadata: { createdAt: new Date(), updatedAt: new Date(), usageCount: 0, successRate: 0 },
};
return { intent: fallbackIntent, confidence: 0, handlers: [{ ...this.config.defaultHandler, score: 0 }], alternatives: [] };
}
throw new Error('No matching intent found');
}
const bestMatch = searchResults[0];
const intent = this.intents.get(bestMatch.id)!;
intent.metadata.usageCount++;
intent.metadata.updatedAt = new Date();
if (this.config.enableLearning) {
this.routeHistory.push({ query: safeQuery, intentId: intent.id, success: true, timestamp: new Date() });
if (this.routeHistory.length > 1000) this.routeHistory = this.routeHistory.slice(-500);
}
const scoredHandlers = intent.handlers.map(h => ({ ...h, score: h.weight * bestMatch.score })).sort((a, b) => b.score - a.score);
const alternatives = searchResults.slice(1, this.config.maxAlternatives + 1)
.filter(r => r.score >= this.config.minConfidence * 0.7)
.map(r => ({ intent: this.intents.get(r.id)!, confidence: r.score }))
.filter(a => a.intent);
return { intent, confidence: bestMatch.score, handlers: scoredHandlers, alternatives };
}
/**
* Provide feedback on a routing decision.
*/
async feedback(intentId: string, success: boolean): Promise<void> {
const intent = this.intents.get(intentId);
if (!intent) return;
const alpha = 0.1;
intent.metadata.successRate = alpha * (success ? 1 : 0) + (1 - alpha) * intent.metadata.successRate;
const lastRoute = this.routeHistory.find(r => r.intentId === intentId);
if (lastRoute) lastRoute.success = success;
}
/**
* Get router statistics.
*/
getStats(): {
totalIntents: number;
byCategory: Record<string, number>;
topIntents: Array<{ id: string; pattern: string; usageCount: number; successRate: number }>;
recentRoutes: number;
} {
const byCategory: Record<string, number> = {};
for (const intent of this.intents.values()) {
byCategory[intent.category] = (byCategory[intent.category] ?? 0) + 1;
}
const topIntents = Array.from(this.intents.values())
.sort((a, b) => b.metadata.usageCount - a.metadata.usageCount)
.slice(0, 5)
.map(i => ({ id: i.id, pattern: i.pattern, usageCount: i.metadata.usageCount, successRate: i.metadata.successRate }));
return { totalIntents: this.intents.size, byCategory, topIntents, recentRoutes: this.routeHistory.length };
}
listIntents(): Intent[] {
return Array.from(this.intents.values()).sort((a, b) => b.priority - a.priority);
}
// =========================================================================
// Private Helpers
// =========================================================================
private async initializeDefaultIntents(): Promise<void> {
const defaults: Array<{ pattern: string; category: string; handlers: IntentHandler[]; priority: number; examples: string[] }> = [
{ pattern: 'write code implement function', category: 'coding', handlers: [{ type: 'agent', name: 'coder', weight: 1.0 }], priority: 10, examples: ['create a function', 'implement feature'] },
{ pattern: 'review code analyze quality', category: 'coding', handlers: [{ type: 'agent', name: 'reviewer', weight: 1.0 }], priority: 10, examples: ['review my code', 'check for bugs'] },
{ pattern: 'test write unit test', category: 'testing', handlers: [{ type: 'agent', name: 'tester', weight: 1.0 }], priority: 10, examples: ['write tests', 'add unit tests'] },
{ pattern: 'search find research', category: 'research', handlers: [{ type: 'agent', name: 'researcher', weight: 1.0 }, { type: 'tool', name: 'WebSearch', weight: 0.8 }], priority: 8, examples: ['look up', 'research topic'] },
{ pattern: 'plan design architecture', category: 'planning', handlers: [{ type: 'agent', name: 'planner', weight: 1.0 }], priority: 8, examples: ['plan implementation', 'design system'] },
{ pattern: 'read file open', category: 'files', handlers: [{ type: 'tool', name: 'Read', weight: 1.0 }], priority: 5, examples: ['show me file', 'read content'] },
{ pattern: 'edit modify change', category: 'files', handlers: [{ type: 'tool', name: 'Edit', weight: 1.0 }], priority: 5, examples: ['change this', 'update file'] },
{ pattern: 'search grep find', category: 'files', handlers: [{ type: 'tool', name: 'Grep', weight: 1.0 }, { type: 'tool', name: 'Glob', weight: 0.8 }], priority: 5, examples: ['find in files', 'search codebase'] },
{ pattern: 'commit git save', category: 'git', handlers: [{ type: 'skill', name: 'commit', weight: 1.0 }], priority: 7, examples: ['commit changes', 'save to git'] },
{ pattern: 'swarm coordinate agents', category: 'swarm', handlers: [{ type: 'agent', name: 'hierarchical-coordinator', weight: 1.0 }], priority: 9, examples: ['run swarm', 'coordinate agents'] },
];
for (const def of defaults) {
await this.registerIntent(def.pattern, def.category, def.handlers, { priority: def.priority, examples: def.examples });
}
}
private generateEmbedding(text: string): Float32Array {
const embedding = new Float32Array(this.dimensions);
const normalized = text.toLowerCase();
let hash = 0;
for (let i = 0; i < normalized.length; i++) { hash = ((hash << 5) - hash) + normalized.charCodeAt(i); hash = hash & hash; }
for (let i = 0; i < this.dimensions; i++) { embedding[i] = Math.sin(hash * (i + 1) * 0.001) * 0.5 + 0.5; }
let norm = 0;
for (let i = 0; i < this.dimensions; i++) norm += embedding[i] * embedding[i];
norm = Math.sqrt(norm);
for (let i = 0; i < this.dimensions; i++) embedding[i] /= norm;
return embedding;
}
}
// ============================================================================
// Plugin Definition
// ============================================================================
let routerInstance: IntentRouter | null = null;
async function getRouter(): Promise<IntentRouter> {
if (!routerInstance) {
routerInstance = new IntentRouter({ defaultHandler: { type: 'agent', name: 'general-purpose', weight: 0.5 } });
await routerInstance.initialize();
}
return routerInstance;
}
export const intentRouterPlugin = new PluginBuilder('intent-router', '1.0.0')
.withDescription('Smart intent → handler routing using @ruvector/wasm HNSW (<1ms)')
.withAuthor('Claude Flow Team')
.withTags(['routing', 'intent', 'semantic', 'ruvector', 'hnsw'])
.withMCPTools([
new MCPToolBuilder('intent-route')
.withDescription('Route a query to best handlers (<1ms)')
.addStringParam('query', 'User query or intent', { required: true })
.withHandler(async (params) => {
try {
const router = await getRouter();
const result = await router.route(params.query as string);
const handlersOutput = result.handlers.map((h, i) => ` ${i + 1}. [${h.type}] ${h.name} (${(h.score * 100).toFixed(0)}%)`).join('\n');
return { content: [{ type: 'text', text: `🎯 **${result.intent.pattern}** (${(result.confidence * 100).toFixed(0)}%)\n\n**Handlers:**\n${handlersOutput}` }] };
} catch (error) {
return { content: [{ type: 'text', text: `❌ Error: ${error instanceof Error ? error.message : String(error)}` }], isError: true };
}
})
.build(),
new MCPToolBuilder('intent-register')
.withDescription('Register a new intent')
.addStringParam('pattern', 'Intent pattern', { required: true })
.addStringParam('category', 'Category', { required: true })
.addStringParam('handlers', 'JSON handlers [{type, name, weight}]', { required: true })
.withHandler(async (params) => {
try {
const router = await getRouter();
const handlers = JSON.parse(params.handlers as string) as IntentHandler[];
const intent = await router.registerIntent(params.pattern as string, params.category as string, handlers);
return { content: [{ type: 'text', text: `✅ Registered: ${intent.id}` }] };
} catch (error) {
return { content: [{ type: 'text', text: `❌ Error: ${error instanceof Error ? error.message : String(error)}` }], isError: true };
}
})
.build(),
new MCPToolBuilder('intent-stats')
.withDescription('Get router statistics')
.withHandler(async () => {
const router = await getRouter();
const stats = router.getStats();
return { content: [{ type: 'text', text: `📊 **Intent Router:**\n\n**Intents:** ${stats.totalIntents}\n**Routes:** ${stats.recentRoutes}\n**Backend:** @ruvector/wasm HNSW` }] };
})
.build(),
])
.withHooks([
new HookBuilder(HookEvent.PreTaskExecute)
.withName('intent-auto-route')
.withDescription('Auto-route tasks')
.withPriority(HookPriority.High)
.when((ctx) => (ctx.data as { autoRoute?: boolean; query?: string } | undefined)?.autoRoute === true)
.handle(async (ctx) => {
const data = ctx.data as { query: string };
try {
const router = await getRouter();
const result = await router.route(data.query);
return { success: true, data: { ...data, routedIntent: result.intent.id, routedHandlers: result.handlers }, modified: true };
} catch { return { success: true }; }
})
.build(),
])
.onInitialize(async (ctx) => {
ctx.logger.info('Intent Router initializing with @ruvector/wasm...');
const router = await getRouter();
ctx.logger.info(`Intent Router ready - ${router.listIntents().length} intents, HNSW enabled`);
})
.build();
export default intentRouterPlugin;