Skip to main content
Glama
command-gateway.ts52.8 kB
/** * Natural Language Command Gateway * Processes natural language commands and routes them to appropriate handlers */ import { RecognizedIntent, CommandProcessingResult, Entity } from '../types/nl.js'; import { IntentRecognitionEngine } from './intent-recognizer.js'; import logger from '../../../logger.js'; /** * Command context for processing */ export interface CommandContext { sessionId: string; userId?: string; currentProject?: string; currentTask?: string; conversationHistory: RecognizedIntent[]; userPreferences: Record<string, unknown>; } /** * Command processing configuration */ export interface CommandGatewayConfig { /** Maximum processing time for commands (ms) */ maxProcessingTime: number; /** Whether to track command history */ trackHistory: boolean; /** Maximum history entries to keep */ maxHistoryEntries: number; /** Whether to enable context-aware processing */ enableContextAware: boolean; /** Confidence threshold for auto-execution */ autoExecuteThreshold: number; } /** * Command validation result */ export interface ValidationResult { isValid: boolean; errors: string[]; warnings: string[]; suggestions: string[]; normalizedParams: Record<string, unknown>; } /** * Natural Language Command Gateway * Main entry point for processing natural language commands */ export class CommandGateway { private static instance: CommandGateway; private intentRecognizer: IntentRecognitionEngine; private config: CommandGatewayConfig; private commandHistory = new Map<string, RecognizedIntent[]>(); private contextCache = new Map<string, CommandContext>(); private sessionMetrics = new Map<string, { commands: Array<{ processingTime: number }> }>(); private intentSuccessMetrics = new Map<string, { total: number; successful: number; failed: number; lastUpdated: Date; recentFailures: Array<{ input: string; error: string; timestamp: Date }>; }>(); private constructor() { this.intentRecognizer = IntentRecognitionEngine.getInstance(); this.config = { maxProcessingTime: 10000, trackHistory: true, maxHistoryEntries: 50, enableContextAware: true, autoExecuteThreshold: 0.8 }; } /** * Get singleton instance */ static getInstance(): CommandGateway { if (!CommandGateway.instance) { CommandGateway.instance = new CommandGateway(); } return CommandGateway.instance; } /** * Process natural language command */ async processCommand( input: string, context: Partial<CommandContext> = {} ): Promise<CommandProcessingResult> { const startTime = Date.now(); const sessionId = context.sessionId || 'default'; try { logger.info({ sessionId, input: input.substring(0, 100) }, 'Processing natural language command'); // Get or create command context const commandContext = this.getOrCreateContext(sessionId, context); // Recognize intent from natural language input const recognitionResult = await this.intentRecognizer.recognizeIntent( input, this.buildRecognitionContext(commandContext) ); if (!recognitionResult) { return this.createFailureResult( input, 'Unable to understand the command. Please try rephrasing or use a more specific request.', ['Try: "Create a project called MyApp"', 'Try: "List all tasks"', 'Try: "Run task 123"'], startTime ); } // Convert RecognitionResult to RecognizedIntent const recognizedIntent: RecognizedIntent = { intent: recognitionResult.intent, confidence: recognitionResult.confidence, confidenceLevel: recognitionResult.confidenceLevel, entities: this.convertEntitiesToArray(recognitionResult.entities as Record<string, unknown>), originalInput: input, processedInput: input.toLowerCase().trim(), alternatives: recognitionResult.alternatives.map(alt => ({ intent: alt.intent, confidence: alt.confidence })), metadata: { processingTime: recognitionResult.processingTime, method: recognitionResult.strategy === 'pattern' ? 'pattern' : recognitionResult.strategy === 'llm' ? 'llm' : 'hybrid', timestamp: recognitionResult.metadata.timestamp } }; // Debug logging for entity extraction logger.info({ intent: recognizedIntent.intent, confidence: recognizedIntent.confidence, strategy: recognitionResult.strategy, rawEntities: recognitionResult.entities, convertedEntities: recognizedIntent.entities, originalInput: input }, 'Intent recognition and entity extraction debug'); // Update command history if (this.config.trackHistory) { this.updateCommandHistory(sessionId, recognizedIntent); } // Validate and normalize parameters const validation = await this.validateCommand(recognizedIntent, commandContext); if (!validation.isValid) { return this.createValidationErrorResult( recognizedIntent, validation, startTime ); } // Check if command requires confirmation const requiresConfirmation = this.shouldRequireConfirmation(recognizedIntent, validation); // Map intent to tool parameters const toolParams = await this.mapIntentToToolParams(recognizedIntent, validation.normalizedParams); // Debug logging for parameter extraction logger.info({ intent: recognizedIntent.intent, entities: recognizedIntent.entities, normalizedParams: validation.normalizedParams, toolParams, originalInput: input }, 'CommandGateway parameter extraction debug'); const processingTime = Date.now() - startTime; // Track successful intent recognition and command mapping this.trackIntentSuccess(recognizedIntent.intent, true, input); return { success: true, intent: recognizedIntent, toolParams, validationErrors: [], suggestions: validation.suggestions, metadata: { processingTime, confidence: recognizedIntent.confidence, requiresConfirmation, ambiguousInput: recognizedIntent.confidence < 0.7 } }; } catch (error) { logger.error({ err: error, sessionId, input }, 'Command processing failed'); // Track failed intent recognition or command mapping this.trackIntentSuccess('unknown', false, input, error instanceof Error ? error.message : 'Unknown error'); return this.createFailureResult( input, `Command processing failed: ${error instanceof Error ? error.message : 'Unknown error'}`, ['Please try again with a simpler command', 'Check your input for typos'], startTime ); } } /** * Get or create command context for session */ private getOrCreateContext(sessionId: string, partialContext: Partial<CommandContext>): CommandContext { let context = this.contextCache.get(sessionId); if (!context) { context = { sessionId, userId: partialContext.userId, currentProject: partialContext.currentProject, currentTask: partialContext.currentTask, conversationHistory: [], userPreferences: {} }; this.contextCache.set(sessionId, context); } // Update context with new information if (partialContext.currentProject) { context.currentProject = partialContext.currentProject; } if (partialContext.currentTask) { context.currentTask = partialContext.currentTask; } if (partialContext.userPreferences) { Object.assign(context.userPreferences, partialContext.userPreferences); } return context; } /** * Build recognition context from command context */ private buildRecognitionContext(context: CommandContext): Record<string, unknown> { return { currentProject: context.currentProject, currentTask: context.currentTask, recentIntents: context.conversationHistory.slice(-5).map(h => h.intent), userPreferences: context.userPreferences, sessionId: context.sessionId }; } /** * Update command history for session */ private updateCommandHistory(sessionId: string, intent: RecognizedIntent): void { let history = this.commandHistory.get(sessionId) || []; history.push(intent); // Limit history size if (history.length > this.config.maxHistoryEntries) { history = history.slice(-this.config.maxHistoryEntries); } this.commandHistory.set(sessionId, history); // Update context conversation history const context = this.contextCache.get(sessionId); if (context) { context.conversationHistory = history; } } /** * Validate command and normalize parameters */ private async validateCommand( intent: RecognizedIntent, context: CommandContext ): Promise<ValidationResult> { const errors: string[] = []; const warnings: string[] = []; const suggestions: string[] = []; // Convert entities array to Record format for validation const normalizedParams: Record<string, unknown> = {}; for (const entity of intent.entities) { // Map entity types to parameter names const paramName = this.mapEntityTypeToParamName(entity.type); normalizedParams[paramName] = entity.value; } // Intent-specific validation switch (intent.intent) { case 'create_project': return this.validateCreateProject(intent, normalizedParams, errors, warnings, suggestions); case 'create_task': return this.validateCreateTask(intent, context, normalizedParams, errors, warnings, suggestions); case 'list_projects': case 'list_tasks': return this.validateListCommand(intent, normalizedParams, errors, warnings, suggestions); case 'run_task': return this.validateRunTask(intent, context, normalizedParams, errors, warnings, suggestions); case 'check_status': return this.validateStatusCheck(intent, context, normalizedParams, errors, warnings, suggestions); case 'decompose_task': return this.validateDecomposeTask(intent, context, normalizedParams, errors, warnings, suggestions); case 'decompose_epic': return this.validateDecomposeEpic(intent, context, normalizedParams, errors, warnings, suggestions); case 'decompose_project': return this.validateDecomposeProject(intent, context, normalizedParams, errors, warnings, suggestions); case 'search_files': return this.validateSearchFiles(intent, context, normalizedParams, errors, warnings, suggestions); case 'search_content': return this.validateSearchContent(intent, context, normalizedParams, errors, warnings, suggestions); case 'parse_prd': case 'parse_tasks': case 'import_artifact': return this.validateArtifactOperation(intent, context, normalizedParams, errors, warnings, suggestions); case 'update_project': return this.validateUpdateProject(intent, context, normalizedParams, errors, warnings, suggestions); case 'assign_task': return this.validateAssignTask(intent, context, normalizedParams, errors, warnings, suggestions); default: errors.push(`Unsupported intent: ${intent.intent}`); suggestions.push('Try using a supported command like create, list, run, status, or decompose'); } return { isValid: errors.length === 0, errors, warnings, suggestions, normalizedParams }; } /** * Validate create project command */ private validateCreateProject( intent: RecognizedIntent, params: Record<string, unknown>, errors: string[], warnings: string[], suggestions: string[] ): ValidationResult { // Project name is required if (!params.projectName) { errors.push('Project name is required'); suggestions.push('Try: "Create a project called MyApp"'); } else { // Normalize project name params.projectName = String(params.projectName).trim(); // Validate project name format if (!/^[a-zA-Z0-9\-_\s]+$/.test(String(params.projectName))) { warnings.push('Project name contains special characters that may cause issues'); suggestions.push('Consider using only letters, numbers, hyphens, and underscores'); } } // Set default description if not provided if (!params.description) { params.description = `Project: ${params.projectName}`; warnings.push('No description provided, using default'); } return { isValid: errors.length === 0, errors, warnings, suggestions, normalizedParams: params }; } /** * Validate create task command */ private validateCreateTask( intent: RecognizedIntent, context: CommandContext, params: Record<string, unknown>, errors: string[], warnings: string[], suggestions: string[] ): ValidationResult { // Check for task title in various forms const hasTaskTitle = params.taskTitle || params.taskName || (params.words && Array.isArray(params.words) && params.words.length > 0); if (!hasTaskTitle) { errors.push('Task title is required'); suggestions.push('Try: "Create a task for implementing authentication"'); } else { // Extract task title from words if present if (params.words && Array.isArray(params.words) && params.words.length > 0) { params.taskTitle = params.words.join(' '); warnings.push('Task title extracted from input'); } } // Use current project if no project specified if (!params.projectName && context.currentProject) { params.projectName = context.currentProject; warnings.push(`Using current project: ${context.currentProject}`); } else if (!params.projectName) { errors.push('Project name is required when no current project is set'); suggestions.push('Specify a project or set a current project first'); } // Validate priority if (params.priority) { const validPriorities = ['low', 'medium', 'high', 'critical']; if (!validPriorities.includes(String(params.priority).toLowerCase())) { warnings.push('Invalid priority, using medium as default'); params.priority = 'medium'; } else { params.priority = String(params.priority).toLowerCase(); } } else { params.priority = 'medium'; } // Validate task type if (params.type) { const validTypes = ['development', 'testing', 'documentation', 'research', 'bug', 'feature']; if (!validTypes.includes(String(params.type).toLowerCase())) { warnings.push('Invalid task type, using development as default'); params.type = 'development'; } else { params.type = String(params.type).toLowerCase(); } } else { params.type = 'development'; } return { isValid: errors.length === 0, errors, warnings, suggestions, normalizedParams: params }; } /** * Validate list command */ private validateListCommand( intent: RecognizedIntent, params: Record<string, unknown>, errors: string[], warnings: string[], suggestions: string[] ): ValidationResult { // Validate status filter if (params.status) { const validStatuses = ['pending', 'in_progress', 'completed', 'blocked', 'cancelled']; if (!validStatuses.includes(String(params.status).toLowerCase())) { warnings.push('Invalid status filter, showing all items'); delete params.status; } else { params.status = String(params.status).toLowerCase(); } } // Validate timeframe filter if (params.timeframe) { const validTimeframes = ['today', 'tomorrow', 'this week', 'next week', 'this month']; const timeframeStr = String(params.timeframe).toLowerCase(); if (!validTimeframes.includes(timeframeStr) && !/^\d{4}-\d{2}-\d{2}$/.test(timeframeStr)) { warnings.push('Invalid timeframe filter, showing all items'); delete params.timeframe; } } return { isValid: errors.length === 0, errors, warnings, suggestions, normalizedParams: params }; } /** * Validate run task command */ private validateRunTask( intent: RecognizedIntent, context: CommandContext, params: Record<string, unknown>, errors: string[], warnings: string[], suggestions: string[] ): ValidationResult { // Check for task ID in various forms const hasTaskId = params.taskId || params.taskTitle || (params.numbers && Array.isArray(params.numbers) && params.numbers.length > 0); if (!hasTaskId) { errors.push('Task ID or task title is required'); suggestions.push('Try: "Run task 123" or "Run the authentication task"'); } else { // Normalize task ID from numbers array if present if (params.numbers && Array.isArray(params.numbers) && params.numbers.length > 0) { params.taskId = `task-${params.numbers[0]}`; warnings.push('Task ID extracted from number'); } // If task title is provided, we'll need to resolve it to an ID if (params.taskTitle && !params.taskId) { warnings.push('Task title provided, will attempt to resolve to task ID'); } } return { isValid: errors.length === 0, errors, warnings, suggestions, normalizedParams: params }; } /** * Validate status check command */ private validateStatusCheck( intent: RecognizedIntent, context: CommandContext, params: Record<string, unknown>, errors: string[], warnings: string[], suggestions: string[] ): ValidationResult { // Use current project if no specific target if (!params.projectName && !params.taskId && context.currentProject) { params.projectName = context.currentProject; warnings.push(`Checking status of current project: ${context.currentProject}`); } // If no target specified, show general status if (!params.projectName && !params.taskId) { warnings.push('No specific target, showing general status'); } return { isValid: errors.length === 0, errors, warnings, suggestions, normalizedParams: params }; } /** * Validate decompose task command */ private validateDecomposeTask( intent: RecognizedIntent, context: CommandContext, params: Record<string, unknown>, errors: string[], warnings: string[], suggestions: string[] ): ValidationResult { // Check for task ID in various forms const hasTaskId = params.taskId || params.taskTitle; if (!hasTaskId) { errors.push('Task ID or task title is required for decomposition'); suggestions.push('Try: "Decompose task T001" or "Break down the authentication task"'); } else { // Normalize task identifier if (params.taskTitle && !params.taskId) { warnings.push('Task title provided, will attempt to resolve to task ID'); } } // Validate decomposition scope if provided if (params.decompositionScope) { const validScopes = ['development tasks', 'implementation steps', 'technical tasks', 'all aspects']; const scope = String(params.decompositionScope).toLowerCase(); if (!validScopes.some(validScope => scope.includes(validScope))) { warnings.push('Decomposition scope may be too broad or unclear'); suggestions.push('Consider specifying: development tasks, implementation steps, or technical tasks'); } } return { isValid: errors.length === 0, errors, warnings, suggestions, normalizedParams: params }; } /** * Validate decompose epic command */ private validateDecomposeEpic( intent: RecognizedIntent, context: CommandContext, params: Record<string, unknown>, errors: string[], warnings: string[], suggestions: string[] ): ValidationResult { // Check for epic ID in various forms const hasEpicId = params.epicId || params.epicTitle; if (!hasEpicId) { errors.push('Epic ID or epic title is required for decomposition'); suggestions.push('Try: "Decompose epic E001" or "Break down the authentication epic"'); } else { // Normalize epic identifier if (params.epicTitle && !params.epicId) { warnings.push('Epic title provided, will attempt to resolve to epic ID'); } } // Validate decomposition scope if provided if (params.decompositionScope) { const validScopes = ['development tasks', 'implementation steps', 'technical tasks', 'all aspects']; const scope = String(params.decompositionScope).toLowerCase(); if (!validScopes.some(validScope => scope.includes(validScope))) { warnings.push('Decomposition scope may be too broad or unclear'); suggestions.push('Consider specifying: development tasks, implementation steps, or technical tasks'); } } return { isValid: errors.length === 0, errors, warnings, suggestions, normalizedParams: params }; } /** * Validate decompose project command */ private validateDecomposeProject( intent: RecognizedIntent, context: CommandContext, params: Record<string, unknown>, errors: string[], warnings: string[], suggestions: string[] ): ValidationResult { // Project name is required if (!params.projectName) { errors.push('Project name or ID is required for decomposition'); suggestions.push('Try: "Decompose project MyApp" or "Break down project PID-001"'); } else { // Normalize project name params.projectName = String(params.projectName).trim(); } // Validate decomposition scope if provided if (params.decompositionScope) { const validScopes = ['development tasks', 'implementation phases', 'technical components', 'all aspects']; const scope = String(params.decompositionScope).toLowerCase(); if (!validScopes.some(validScope => scope.includes(validScope))) { warnings.push('Decomposition scope may be too broad or unclear'); suggestions.push('Consider specifying: development tasks, implementation phases, or technical components'); } } // Validate decomposition details if provided if (params.decompositionDetails) { const details = String(params.decompositionDetails); if (details.length > 1000) { warnings.push('Decomposition details are very long, may affect processing performance'); } } return { isValid: errors.length === 0, errors, warnings, suggestions, normalizedParams: params }; } /** * Validate search files command */ private validateSearchFiles( intent: RecognizedIntent, context: CommandContext, params: Record<string, unknown>, errors: string[], warnings: string[], suggestions: string[] ): ValidationResult { // Search pattern is required if (!params.searchPattern && !params.fileName) { errors.push('Search pattern or file name is required'); suggestions.push('Try: "Search for *.js files" or "Find config files"'); } else { // Normalize search pattern if (params.searchPattern) { params.searchPattern = String(params.searchPattern).trim(); // Validate search pattern format if (String(params.searchPattern).length < 2) { warnings.push('Search pattern is very short, may return too many results'); } } if (params.fileName) { params.fileName = String(params.fileName).trim(); } } // Validate file extensions if provided if (params.fileExtensions) { const extensions = Array.isArray(params.fileExtensions) ? params.fileExtensions : [params.fileExtensions]; params.fileExtensions = extensions.map(ext => String(ext).startsWith('.') ? String(ext) : `.${ext}` ); } // Set default search directory if not provided if (!params.searchDirectory) { params.searchDirectory = context.currentProject || '.'; } return { isValid: errors.length === 0, errors, warnings, suggestions, normalizedParams: params }; } /** * Validate search content command */ private validateSearchContent( intent: RecognizedIntent, context: CommandContext, params: Record<string, unknown>, errors: string[], warnings: string[], suggestions: string[] ): ValidationResult { // Search query is required if (!params.searchQuery) { errors.push('Search query is required'); suggestions.push('Try: "Search content for authentication" or "Find code containing API"'); } else { // Normalize search query params.searchQuery = String(params.searchQuery).trim(); // Validate search query length if (String(params.searchQuery).length < 3) { warnings.push('Search query is very short, may return too many results'); suggestions.push('Consider using a more specific search term'); } } // Validate regex pattern if provided if (params.useRegex && params.regexPattern) { try { new RegExp(String(params.regexPattern)); params.regexPattern = String(params.regexPattern); } catch { errors.push('Invalid regular expression pattern'); suggestions.push('Check your regex syntax or use simple text search'); } } // Validate case sensitivity option if (params.caseSensitive !== undefined) { params.caseSensitive = Boolean(params.caseSensitive); } else { params.caseSensitive = false; // Default to case insensitive } // Set default search directory if not provided if (!params.searchDirectory) { params.searchDirectory = context.currentProject || '.'; } // Validate file extensions if provided if (params.fileExtensions) { const extensions = Array.isArray(params.fileExtensions) ? params.fileExtensions : [params.fileExtensions]; params.fileExtensions = extensions.map(ext => String(ext).startsWith('.') ? String(ext) : `.${ext}` ); } return { isValid: errors.length === 0, errors, warnings, suggestions, normalizedParams: params }; } /** * Validate artifact processing commands (parse_prd, parse_tasks, import_artifact) */ private validateArtifactOperation( intent: RecognizedIntent, context: CommandContext, params: Record<string, unknown>, errors: string[], warnings: string[], suggestions: string[] ): ValidationResult { // Project name is required for all artifact operations if (!params.projectName) { errors.push('Project name is required for artifact operations'); suggestions.push('Try: "Parse PRD for project MyApp" or "Import tasks for PID-001"'); } else { params.projectName = String(params.projectName).trim(); } // File path or artifact content is required if (!params.filePath && !params.artifactContent && !params.artifactData) { if (intent.intent === 'parse_prd') { errors.push('PRD file path or content is required'); suggestions.push('Try: "Parse PRD from requirements.md" or provide PRD content'); } else if (intent.intent === 'parse_tasks') { errors.push('Task list file path or content is required'); suggestions.push('Try: "Parse tasks from todo.md" or provide task list content'); } else if (intent.intent === 'import_artifact') { errors.push('Artifact file path or content is required'); suggestions.push('Try: "Import artifact from data.json" or provide artifact content'); } } else { // Validate file path if provided if (params.filePath) { params.filePath = String(params.filePath).trim(); // Basic file path validation if (!String(params.filePath).includes('.')) { warnings.push('File path may be missing extension'); } // Validate file extension for specific artifact types const filePath = String(params.filePath).toLowerCase(); if (intent.intent === 'parse_prd') { if (!filePath.endsWith('.md') && !filePath.endsWith('.txt') && !filePath.endsWith('.doc')) { warnings.push('PRD files are typically .md, .txt, or .doc files'); } } else if (intent.intent === 'parse_tasks') { if (!filePath.endsWith('.md') && !filePath.endsWith('.txt') && !filePath.endsWith('.json')) { warnings.push('Task list files are typically .md, .txt, or .json files'); } } } // Normalize artifact content if provided if (params.artifactContent) { params.artifactContent = String(params.artifactContent).trim(); if (String(params.artifactContent).length < 10) { warnings.push('Artifact content seems very short'); } } } // Validate artifact type if specified if (params.artifactType) { const validTypes = ['prd', 'requirements', 'tasks', 'todo', 'specifications', 'documentation']; const artifactType = String(params.artifactType).toLowerCase(); if (!validTypes.includes(artifactType)) { warnings.push(`Artifact type '${artifactType}' may not be recognized`); suggestions.push(`Consider using: ${validTypes.join(', ')}`); } params.artifactType = artifactType; } else { // Set default artifact type based on intent if (intent.intent === 'parse_prd') { params.artifactType = 'prd'; } else if (intent.intent === 'parse_tasks') { params.artifactType = 'tasks'; } } // Validate parsing options if (params.parseOptions) { const options = params.parseOptions as Record<string, unknown>; // Validate extract tasks option if (options.extractTasks !== undefined) { options.extractTasks = Boolean(options.extractTasks); } // Validate extract requirements option if (options.extractRequirements !== undefined) { options.extractRequirements = Boolean(options.extractRequirements); } // Validate preserve structure option if (options.preserveStructure !== undefined) { options.preserveStructure = Boolean(options.preserveStructure); } params.parseOptions = options; } return { isValid: errors.length === 0, errors, warnings, suggestions, normalizedParams: params }; } /** * Validate update project command */ private validateUpdateProject( intent: RecognizedIntent, context: CommandContext, params: Record<string, unknown>, errors: string[], warnings: string[], suggestions: string[] ): ValidationResult { // Project name is required if (!params.projectName) { errors.push('Project name is required for update operation'); suggestions.push('Try: "Update project MyApp status to completed"'); } else { params.projectName = String(params.projectName).trim(); } // At least one update parameter should be provided const updateableFields = ['status', 'priority', 'description', 'assignee', 'deadline']; const hasUpdates = updateableFields.some(field => params[field] !== undefined); if (!hasUpdates) { warnings.push('No specific updates provided, will prompt for changes'); suggestions.push('Try specifying what to update: status, priority, description, assignee, or deadline'); } // Validate status if provided if (params.status) { const validStatuses = ['pending', 'in_progress', 'completed', 'blocked', 'cancelled']; const status = String(params.status).toLowerCase().replace(/\s+/g, '_'); if (!validStatuses.includes(status)) { errors.push(`Invalid status: ${params.status}`); suggestions.push(`Valid statuses: ${validStatuses.join(', ')}`); } else { params.status = status; } } // Validate priority if provided if (params.priority) { const validPriorities = ['low', 'medium', 'high', 'critical']; const priority = String(params.priority).toLowerCase(); if (!validPriorities.includes(priority)) { warnings.push(`Priority '${params.priority}' may not be recognized`); suggestions.push(`Valid priorities: ${validPriorities.join(', ')}`); } else { params.priority = priority; } } // Validate assignee if provided if (params.assignee) { params.assignee = String(params.assignee).trim(); if (String(params.assignee).length < 2) { warnings.push('Assignee name seems very short'); } } // Normalize description if provided if (params.description) { params.description = String(params.description).trim(); } return { isValid: errors.length === 0, errors, warnings, suggestions, normalizedParams: params }; } /** * Validate assign task command */ private validateAssignTask( intent: RecognizedIntent, context: CommandContext, params: Record<string, unknown>, errors: string[], warnings: string[], suggestions: string[] ): ValidationResult { // Task identifier is required if (!params.taskId && !params.taskTitle) { errors.push('Task ID or title is required for assignment'); suggestions.push('Try: "Assign task T-001 to John" or "Assign authentication task to Sarah"'); } else { if (params.taskId) { params.taskId = String(params.taskId).trim(); } if (params.taskTitle) { params.taskTitle = String(params.taskTitle).trim(); warnings.push('Task title provided, will attempt to resolve to task ID'); } } // Assignee is required if (!params.assignee) { errors.push('Assignee is required for task assignment'); suggestions.push('Try: "Assign task to John" or "Assign to team-frontend"'); } else { params.assignee = String(params.assignee).trim(); // Validate assignee format if (String(params.assignee).length < 2) { errors.push('Assignee name is too short'); } // Check for common assignee patterns const assignee = String(params.assignee).toLowerCase(); if (assignee.includes('team-') || assignee.includes('group-')) { warnings.push('Assigning to a team/group - ensure the team exists'); } } // Validate assignment options if (params.force !== undefined) { params.force = Boolean(params.force); } else { params.force = false; } // Validate reassignment scenario if (context.currentTask && params.taskId === context.currentTask) { warnings.push('Task may already be assigned, use force option if needed'); } return { isValid: errors.length === 0, errors, warnings, suggestions, normalizedParams: params }; } /** * Check if command should require confirmation */ private shouldRequireConfirmation(intent: RecognizedIntent, validation: ValidationResult): boolean { // Require confirmation for low confidence commands if (intent.confidence < this.config.autoExecuteThreshold) { return true; } // Require confirmation for commands with validation warnings if (validation.warnings.length > 0) { return true; } // Require confirmation for destructive operations (future) const destructiveIntents = ['delete_project', 'delete_task', 'archive_project']; if (destructiveIntents.includes(intent.intent)) { return true; } return false; } /** * Map recognized intent to tool parameters */ private async mapIntentToToolParams( intent: RecognizedIntent, normalizedParams: Record<string, unknown> ): Promise<Record<string, unknown>> { const toolParams: Record<string, unknown> = {}; switch (intent.intent) { case 'create_project': toolParams.command = 'create'; toolParams.projectName = normalizedParams.projectName; toolParams.description = normalizedParams.description; toolParams.options = { priority: normalizedParams.priority || 'medium', type: normalizedParams.type || 'development' }; break; case 'create_task': toolParams.command = 'create'; toolParams.projectName = normalizedParams.projectName; toolParams.description = normalizedParams.taskTitle; toolParams.options = { priority: normalizedParams.priority || 'medium', type: normalizedParams.type || 'development', assignee: normalizedParams.assignee }; break; case 'list_projects': toolParams.command = 'list'; toolParams.options = { type: 'projects', status: normalizedParams.status, timeframe: normalizedParams.timeframe }; break; case 'list_tasks': toolParams.command = 'list'; toolParams.options = { type: 'tasks', status: normalizedParams.status, timeframe: normalizedParams.timeframe, assignee: normalizedParams.assignee, project: normalizedParams.projectName }; break; case 'run_task': toolParams.command = 'run'; toolParams.taskId = normalizedParams.taskId || normalizedParams.taskTitle; toolParams.options = { force: normalizedParams.force || false }; break; case 'check_status': toolParams.command = 'status'; toolParams.projectName = normalizedParams.projectName; toolParams.taskId = normalizedParams.taskId; toolParams.options = { detailed: true }; break; case 'decompose_task': toolParams.command = 'decompose'; toolParams.taskId = normalizedParams.taskId || normalizedParams.taskTitle; toolParams.description = normalizedParams.description; toolParams.options = { scope: normalizedParams.decompositionScope, details: normalizedParams.decompositionDetails, force: normalizedParams.force || false }; break; case 'decompose_epic': toolParams.command = 'decompose'; toolParams.epicId = normalizedParams.epicId || normalizedParams.epicTitle; toolParams.description = normalizedParams.description; toolParams.options = { scope: normalizedParams.decompositionScope, details: normalizedParams.decompositionDetails, force: normalizedParams.force || false }; break; case 'decompose_project': toolParams.command = 'decompose'; toolParams.projectName = normalizedParams.projectName; toolParams.description = normalizedParams.description; toolParams.options = { scope: normalizedParams.decompositionScope, details: normalizedParams.decompositionDetails, force: normalizedParams.force || false }; break; case 'open_project': toolParams.command = 'open'; toolParams.projectName = normalizedParams.projectName; toolParams.options = {}; break; case 'update_project': toolParams.command = 'update'; toolParams.projectName = normalizedParams.projectName; toolParams.updates = normalizedParams.updates || {}; toolParams.options = {}; break; case 'search_files': toolParams.command = 'search'; toolParams.searchType = 'files'; toolParams.searchPattern = normalizedParams.searchPattern || normalizedParams.fileName; toolParams.options = { directory: normalizedParams.searchDirectory, extensions: normalizedParams.fileExtensions, recursive: true }; break; case 'search_content': toolParams.command = 'search'; toolParams.searchType = 'content'; toolParams.searchQuery = normalizedParams.searchQuery; toolParams.options = { directory: normalizedParams.searchDirectory, extensions: normalizedParams.fileExtensions, useRegex: normalizedParams.useRegex || false, caseSensitive: normalizedParams.caseSensitive || false, regexPattern: normalizedParams.regexPattern }; break; case 'parse_prd': toolParams.command = 'parse'; toolParams.artifactType = 'prd'; toolParams.projectName = normalizedParams.projectName; toolParams.filePath = normalizedParams.filePath; toolParams.artifactContent = normalizedParams.artifactContent; toolParams.options = normalizedParams.parseOptions || {}; break; case 'parse_tasks': toolParams.command = 'parse'; toolParams.artifactType = 'tasks'; toolParams.projectName = normalizedParams.projectName; toolParams.filePath = normalizedParams.filePath; toolParams.artifactContent = normalizedParams.artifactContent; toolParams.options = normalizedParams.parseOptions || {}; break; case 'import_artifact': toolParams.command = 'import'; toolParams.artifactType = normalizedParams.artifactType || 'generic'; toolParams.projectName = normalizedParams.projectName; toolParams.filePath = normalizedParams.filePath; toolParams.artifactContent = normalizedParams.artifactContent; toolParams.options = normalizedParams.parseOptions || {}; break; case 'assign_task': toolParams.command = 'assign'; toolParams.taskId = normalizedParams.taskId || normalizedParams.taskTitle; toolParams.assignee = normalizedParams.assignee; toolParams.options = { force: normalizedParams.force || false }; break; case 'refine_task': toolParams.command = 'refine'; toolParams.taskId = normalizedParams.taskId || normalizedParams.taskTitle; toolParams.refinements = normalizedParams.description || normalizedParams.refinements; toolParams.options = { scope: normalizedParams.refinementScope }; break; case 'get_help': toolParams.command = 'help'; toolParams.topic = normalizedParams.helpTopic || normalizedParams.topic; toolParams.options = { detailed: true }; break; case 'unrecognized_intent': case 'clarification_needed': case 'unknown': toolParams.command = 'fallback'; toolParams.originalInput = normalizedParams.originalInput; toolParams.suggestions = normalizedParams.suggestions || []; toolParams.options = { intent: intent.intent, confidence: intent.confidence }; break; default: throw new Error(`Unsupported intent for tool mapping: ${intent.intent}`); } return toolParams; } /** * Create failure result */ private createFailureResult( input: string, message: string, suggestions: string[], startTime: number ): CommandProcessingResult { return { success: false, intent: { intent: 'unknown', confidence: 0, confidenceLevel: 'very_low', entities: [], originalInput: input, processedInput: input.toLowerCase().trim(), alternatives: [], metadata: { processingTime: Date.now() - startTime, method: 'pattern', timestamp: new Date() } }, toolParams: {}, validationErrors: [message], suggestions, metadata: { processingTime: Date.now() - startTime, confidence: 0, requiresConfirmation: false, ambiguousInput: true } }; } /** * Create validation error result */ private createValidationErrorResult( intent: RecognizedIntent, validation: ValidationResult, startTime: number ): CommandProcessingResult { return { success: false, intent, toolParams: {}, validationErrors: validation.errors, suggestions: validation.suggestions, metadata: { processingTime: Date.now() - startTime, confidence: intent.confidence, requiresConfirmation: false, ambiguousInput: intent.confidence < 0.7 } }; } /** * Map entity type to parameter name */ private mapEntityTypeToParamName(entityType: string): string { const mapping: Record<string, string> = { 'project_name': 'projectName', 'task_name': 'taskName', 'task_title': 'taskTitle', 'task_id': 'taskId', 'description': 'description', 'priority': 'priority', 'type': 'type', 'status': 'status', 'assignee': 'assignee', 'timeframe': 'timeframe', 'features': 'features', 'decomposition_scope': 'decompositionScope', 'decomposition_details': 'decompositionDetails' }; return mapping[entityType] || entityType; } /** * Convert entities from Record format to Entity array format */ private convertEntitiesToArray(entities: Record<string, unknown> | Entity[]): Entity[] { // If already an array of entities, return as-is if (Array.isArray(entities)) { return entities.map(entity => ({ type: entity.type || 'unknown', value: String(entity.value || ''), confidence: entity.confidence || 1.0 })); } // Convert from Record format const entityArray: Entity[] = []; for (const [type, value] of Object.entries(entities)) { if (value !== undefined && value !== null) { entityArray.push({ type, value: String(value), confidence: 1.0 // Default confidence for extracted entities }); } } return entityArray; } /** * Update configuration */ updateConfig(newConfig: Partial<CommandGatewayConfig>): void { this.config = { ...this.config, ...newConfig }; logger.info({ config: this.config }, 'Command Gateway configuration updated'); } /** * Get current configuration */ getConfig(): CommandGatewayConfig { return { ...this.config }; } /** * Clear command history for session */ clearHistory(sessionId: string): void { this.commandHistory.delete(sessionId); this.contextCache.delete(sessionId); logger.info({ sessionId }, 'Command history cleared'); } /** * Get command history for session */ getHistory(sessionId: string): RecognizedIntent[] { return this.commandHistory.get(sessionId) || []; } /** * Get processing statistics */ getStatistics(): { totalSessions: number; totalCommands: number; averageProcessingTime: number; successRate: number; } { const totalSessions = this.commandHistory.size; let totalCommands = 0; let successfulCommands = 0; for (const history of this.commandHistory.values()) { totalCommands += history.length; successfulCommands += history.filter(h => h.confidence >= 0.7).length; } return { totalSessions, totalCommands, averageProcessingTime: this.calculateAverageProcessingTime(), // Real tracking implementation successRate: totalCommands > 0 ? successfulCommands / totalCommands : 0 }; } /** * Calculate average processing time from session metrics */ private calculateAverageProcessingTime(): number { const allSessions = Array.from(this.sessionMetrics.values()); if (allSessions.length === 0) { return 0; } const totalProcessingTime = allSessions.reduce((sum: number, session: { commands: Array<{ processingTime: number }> }) => { return sum + session.commands.reduce((cmdSum: number, cmd: { processingTime: number }) => cmdSum + cmd.processingTime, 0); }, 0); const totalCommands = allSessions.reduce((sum: number, session: { commands: Array<{ processingTime: number }> }) => sum + session.commands.length, 0); return totalCommands > 0 ? totalProcessingTime / totalCommands : 0; } /** * Track intent recognition success/failure for monitoring */ private trackIntentSuccess(intent: string, success: boolean, input: string, error?: string): void { const metrics = this.intentSuccessMetrics.get(intent) || { total: 0, successful: 0, failed: 0, lastUpdated: new Date(), recentFailures: [] }; metrics.total++; metrics.lastUpdated = new Date(); if (success) { metrics.successful++; } else { metrics.failed++; // Track recent failures for debugging metrics.recentFailures.push({ input: input.substring(0, 100), // Limit input length for storage error: error || 'Unknown error', timestamp: new Date() }); // Keep only last 10 failures per intent if (metrics.recentFailures.length > 10) { metrics.recentFailures = metrics.recentFailures.slice(-10); } } this.intentSuccessMetrics.set(intent, metrics); // Log significant failure rates for monitoring const failureRate = metrics.failed / metrics.total; if (metrics.total >= 5 && failureRate > 0.5) { logger.warn({ intent, total: metrics.total, successful: metrics.successful, failed: metrics.failed, failureRate: Math.round(failureRate * 100), recentFailures: metrics.recentFailures.slice(-3) }, 'High intent recognition failure rate detected'); } } /** * Get intent recognition success rate statistics */ getIntentSuccessRates(): Record<string, { intent: string; total: number; successRate: number; failureRate: number; lastUpdated: Date; recentFailures?: Array<{ input: string; error: string; timestamp: Date }>; }> { const stats: Record<string, { intent: string; total: number; successRate: number; failureRate: number; lastUpdated: Date; recentFailures?: Array<{ input: string; error: string; timestamp: Date }>; }> = {}; for (const [intent, metrics] of this.intentSuccessMetrics.entries()) { const successRate = metrics.total > 0 ? metrics.successful / metrics.total : 0; const failureRate = metrics.total > 0 ? metrics.failed / metrics.total : 0; stats[intent] = { intent, total: metrics.total, successRate: Math.round(successRate * 100) / 100, failureRate: Math.round(failureRate * 100) / 100, lastUpdated: metrics.lastUpdated }; // Include recent failures for intents with high failure rates if (failureRate > 0.3 && metrics.recentFailures.length > 0) { stats[intent].recentFailures = metrics.recentFailures.slice(-5); } } return stats; } /** * Get overall system health metrics */ getSystemHealthMetrics(): { totalCommands: number; overallSuccessRate: number; intentCoverage: number; problematicIntents: string[]; lastUpdated: Date; } { let totalCommands = 0; let totalSuccessful = 0; const problematicIntents: string[] = []; let lastUpdated = new Date(0); for (const [intent, metrics] of this.intentSuccessMetrics.entries()) { totalCommands += metrics.total; totalSuccessful += metrics.successful; if (metrics.lastUpdated > lastUpdated) { lastUpdated = metrics.lastUpdated; } // Mark intents with >30% failure rate as problematic const failureRate = metrics.total > 0 ? metrics.failed / metrics.total : 0; if (metrics.total >= 3 && failureRate > 0.3) { problematicIntents.push(intent); } } const overallSuccessRate = totalCommands > 0 ? totalSuccessful / totalCommands : 0; const intentCoverage = this.intentSuccessMetrics.size; return { totalCommands, overallSuccessRate: Math.round(overallSuccessRate * 100) / 100, intentCoverage, problematicIntents, lastUpdated }; } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/freshtechbro/vibe-coder-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server