UNPKG

ai-debug-local-mcp

Version:

🎯 ENHANCED AI GUIDANCE v4.1.2: Dramatically improved tool descriptions help AI users choose the right tools instead of 'close enough' options. Ultra-fast keyboard automation (10x speed), universal recording, multi-ecosystem debugging support, and compreh

677 lines 30.7 kB
/** * AI Feedback Cloud Handler - Remote Collection API * * Provides cloud-based feedback collection API endpoints for remote AI-Debug users * to submit feedback from distributed systems with privacy-preserving aggregation. */ import { BaseHandler } from './base-handler.js'; import { UserFriendlyLogger } from '../utils/user-friendly-logger.js'; import { AIFeedbackCollector } from '../utils/ai-feedback-collector.js'; import * as crypto from 'crypto'; /** * Cloud Feedback Collection Handler for Remote AI-Debug Users */ export class AIFeedbackCloudHandler extends BaseHandler { feedbackCollector; logger; batchQueue = []; processedBatches = new Set(); constructor() { super(); this.feedbackCollector = new AIFeedbackCollector({ persistenceMode: 'file', enableAutoCollection: true, analysisEnabled: true, privacyMode: 'anonymous' }); this.logger = new UserFriendlyLogger('CloudFeedback'); // Load persisted feedback this.feedbackCollector.loadPersistedFeedback().catch(error => { this.logger.warn(`Failed to load persisted feedback: ${error instanceof Error ? error.message : 'Unknown error'}`); }); } tools = [ { name: 'upload_feedback_batch', description: '☁️ UPLOAD FEEDBACK BATCH: Submit a batch of AI feedback from remote users to the cloud collection system.', inputSchema: { type: 'object', properties: { sourceSystem: { type: 'string', description: 'Identifier for the source system submitting feedback' }, feedbackEntries: { type: 'array', description: 'Array of feedback entries to upload', items: { type: 'object', properties: { sessionId: { type: 'string' }, agentType: { type: 'string' }, taskDescription: { type: 'string' }, outcome: { type: 'string', enum: ['success', 'partial_success', 'failure'] }, userExperience: { type: 'object', properties: { satisfaction: { type: 'number', minimum: 1, maximum: 10 }, efficiency: { type: 'number', minimum: 1, maximum: 10 }, clarity: { type: 'number', minimum: 1, maximum: 10 }, usefulness: { type: 'number', minimum: 1, maximum: 10 } } }, feedback: { type: 'object', properties: { strengths: { type: 'array', items: { type: 'string' } }, weaknesses: { type: 'array', items: { type: 'string' } }, suggestions: { type: 'array', items: { type: 'string' } }, wouldUseAgain: { type: 'boolean' }, recommendToOthers: { type: 'boolean' } } }, contextualData: { type: 'object', properties: { framework: { type: 'string' }, projectComplexity: { type: 'string', enum: ['simple', 'moderate', 'complex'] }, sessionDuration: { type: 'number' } } } }, required: ['sessionId', 'agentType', 'taskDescription', 'outcome'] } }, metadata: { type: 'object', description: 'Metadata about the submission', properties: { version: { type: 'string', description: 'AI-Debug version' }, systemType: { type: 'string', enum: ['local', 'cloud', 'enterprise'], description: 'Type of system submitting feedback' }, anonymized: { type: 'boolean', default: true, description: 'Whether data has been anonymized' }, region: { type: 'string', description: 'Geographic region (optional)' } }, required: ['version', 'systemType'] }, userIdHash: { type: 'string', description: 'Optional privacy-preserving user identifier hash' } }, required: ['sourceSystem', 'feedbackEntries', 'metadata'] } }, { name: 'validate_feedback_batch', description: '🔍 VALIDATE FEEDBACK BATCH: Validate and sanitize feedback batch before submission to ensure data quality.', inputSchema: { type: 'object', properties: { feedbackEntries: { type: 'array', description: 'Array of feedback entries to validate', items: { type: 'object' } }, strictValidation: { type: 'boolean', default: false, description: 'Enable strict validation (rejects entries with any issues)' } }, required: ['feedbackEntries'] } }, { name: 'get_cloud_collection_status', description: '📊 GET CLOUD COLLECTION STATUS: Get status of cloud feedback collection system including batch processing stats.', inputSchema: { type: 'object', properties: { includeQueueStatus: { type: 'boolean', default: true, description: 'Include processing queue status' } } } }, { name: 'process_feedback_queue', description: '⚡ PROCESS FEEDBACK QUEUE: Process pending feedback batches in the collection queue.', inputSchema: { type: 'object', properties: { maxBatches: { type: 'number', default: 10, description: 'Maximum number of batches to process' }, forceProcess: { type: 'boolean', default: false, description: 'Force processing even if batch validation fails' } } } }, { name: 'export_cloud_feedback', description: '📤 EXPORT CLOUD FEEDBACK: Export aggregated cloud feedback data for analysis or backup.', inputSchema: { type: 'object', properties: { format: { type: 'string', enum: ['json', 'csv', 'analytics'], default: 'json', description: 'Export format' }, timeRange: { type: 'object', description: 'Time range for export', properties: { start: { type: 'number', description: 'Start timestamp (Unix milliseconds)' }, end: { type: 'number', description: 'End timestamp (Unix milliseconds)' } } }, includeMetrics: { type: 'boolean', default: true, description: 'Include calculated metrics in export' } } } }, { name: 'configure_cloud_collection', description: '⚙️ CONFIGURE CLOUD COLLECTION: Configure cloud feedback collection settings and privacy options.', inputSchema: { type: 'object', properties: { batchProcessingEnabled: { type: 'boolean', description: 'Enable automatic batch processing' }, maxBatchSize: { type: 'number', description: 'Maximum entries per batch' }, privacyLevel: { type: 'string', enum: ['minimal', 'standard', 'strict'], description: 'Privacy level for data collection' }, retentionDays: { type: 'number', description: 'Data retention period in days' }, enableRealTimeProcessing: { type: 'boolean', description: 'Process batches immediately upon receipt' } } } } ]; async handle(toolName, args) { try { switch (toolName) { case 'upload_feedback_batch': return await this.uploadFeedbackBatch(args); case 'validate_feedback_batch': return await this.validateFeedbackBatch(args); case 'get_cloud_collection_status': return await this.getCloudCollectionStatus(args); case 'process_feedback_queue': return await this.processFeedbackQueue(args); case 'export_cloud_feedback': return await this.exportCloudFeedback(args); case 'configure_cloud_collection': return await this.configureCloudCollection(args); default: throw new Error(`Unknown tool: ${toolName}`); } } catch (error) { this.logger.error(`Cloud feedback tool ${toolName} failed: ${error instanceof Error ? error.message : 'Unknown error'}`); return { success: false, error: error instanceof Error ? error.message : 'Unknown error' }; } } /** * Upload a batch of feedback from remote users */ async uploadFeedbackBatch(args) { const { sourceSystem, feedbackEntries, metadata, userIdHash } = args; this.logger.info(`☁️ Uploading feedback batch from ${sourceSystem} with ${feedbackEntries.length} entries`); try { // Generate batch ID const batchId = this.generateBatchId(sourceSystem); // Validate the batch const validation = await this.validateFeedbackBatch({ feedbackEntries, strictValidation: false }); if (!validation.isValid && validation.errors.length > 0) { this.logger.warn(`Batch validation failed: ${validation.errors.join(', ')}`); return { success: false, batchId, message: 'Batch validation failed', errors: validation.errors, warnings: validation.warnings }; } // Create cloud feedback batch const cloudBatch = { batchId, timestamp: Date.now(), sourceSystem, userIdHash: userIdHash ? this.hashUserId(userIdHash) : undefined, feedbackEntries: validation.sanitizedEntries, metadata: { version: metadata.version || 'unknown', systemType: metadata.systemType || 'unknown', anonymized: metadata.anonymized !== false, // Default to true region: metadata.region } }; // Add to processing queue this.batchQueue.push(cloudBatch); // Process immediately if enabled (simplified for now) await this.processBatch(cloudBatch); this.logger.success(`✅ Uploaded and processed batch ${batchId} with ${validation.sanitizedEntries.length} valid entries`); return { success: true, batchId, message: 'Feedback batch uploaded successfully', entriesProcessed: validation.sanitizedEntries.length, warnings: validation.warnings }; } catch (error) { this.logger.error(`Failed to upload feedback batch: ${error instanceof Error ? error.message : 'Unknown error'}`); return { success: false, message: 'Failed to upload batch', error: error instanceof Error ? error.message : 'Unknown error' }; } } /** * Validate feedback batch for quality and privacy */ async validateFeedbackBatch(args) { const { feedbackEntries, strictValidation = false } = args; this.logger.info(`🔍 Validating feedback batch with ${feedbackEntries.length} entries`); const validation = { isValid: true, errors: [], warnings: [], sanitizedEntries: [] }; for (let i = 0; i < feedbackEntries.length; i++) { const entry = feedbackEntries[i]; const sanitizedEntry = this.sanitizeFeedbackEntry(entry, i, validation); if (sanitizedEntry) { validation.sanitizedEntries.push(sanitizedEntry); } else if (strictValidation) { validation.isValid = false; validation.errors.push(`Entry ${i}: Failed strict validation`); } } // Overall validation if (validation.sanitizedEntries.length === 0) { validation.isValid = false; validation.errors.push('No valid entries found in batch'); } this.logger.info(`✅ Validation complete: ${validation.sanitizedEntries.length} valid entries, ${validation.errors.length} errors`); return validation; } /** * Get cloud collection system status */ async getCloudCollectionStatus(args) { this.logger.info('📊 Getting cloud collection status'); try { const storageInfo = this.feedbackCollector.getStorageInfo(); const allFeedback = this.feedbackCollector.getAllFeedback(); // Calculate cloud-specific metrics const cloudEntries = allFeedback.filter(entry => entry.source === 'cloud' || entry.batchId); const status = { success: true, system: { isOnline: true, lastProcessed: Date.now(), version: '1.0.0' }, storage: { mode: storageInfo.persistenceMode, totalEntries: storageInfo.memoryEntries, cloudEntries: cloudEntries.length, localEntries: allFeedback.length - cloudEntries.length, estimatedSize: storageInfo.estimatedSize, location: storageInfo.storageLocation }, processing: { queueLength: this.batchQueue.length, processedBatches: this.processedBatches.size, lastBatchProcessed: this.processedBatches.size > 0 ? Date.now() : null }, configuration: { privacyMode: this.feedbackCollector.getConfiguration().privacyMode, batchProcessingEnabled: true, realTimeProcessing: true } }; if (args.includeQueueStatus) { const queueStatus = { ...status.processing, queueSystems: this.batchQueue.map(batch => batch.sourceSystem), queueAge: this.batchQueue.length > 0 ? Date.now() - Math.min(...this.batchQueue.map(b => b.timestamp)) : 0 }; status.processing = queueStatus; } return status; } catch (error) { this.logger.error(`Failed to get status: ${error instanceof Error ? error.message : 'Unknown error'}`); return { success: false, message: 'Failed to get status', error: error instanceof Error ? error.message : 'Unknown error' }; } } /** * Process feedback batches in queue */ async processFeedbackQueue(args) { const { maxBatches = 10, forceProcess = false } = args; this.logger.info(`⚡ Processing feedback queue: ${this.batchQueue.length} batches pending`); try { const processedBatches = []; const errors = []; const batchesToProcess = this.batchQueue.splice(0, maxBatches); for (const batch of batchesToProcess) { try { if (!this.processedBatches.has(batch.batchId)) { await this.processBatch(batch); processedBatches.push(batch.batchId); this.processedBatches.add(batch.batchId); } } catch (error) { errors.push({ batchId: batch.batchId, error: error instanceof Error ? error.message : 'Unknown error' }); if (!forceProcess) { // Re-add to queue if not forcing this.batchQueue.unshift(batch); } } } this.logger.success(`✅ Processed ${processedBatches.length} batches, ${errors.length} errors`); return { success: true, message: `Processed ${processedBatches.length} batches`, processedBatches, errors, remainingInQueue: this.batchQueue.length }; } catch (error) { this.logger.error(`Failed to process queue: ${error instanceof Error ? error.message : 'Unknown error'}`); return { success: false, message: 'Failed to process queue', error: error instanceof Error ? error.message : 'Unknown error' }; } } /** * Export aggregated cloud feedback data */ async exportCloudFeedback(args) { const { format = 'json', timeRange, includeMetrics = true } = args; this.logger.info(`📤 Exporting cloud feedback data in ${format} format`); try { let allFeedback = this.feedbackCollector.getAllFeedback(); // Apply time range filter if specified if (timeRange) { allFeedback = allFeedback.filter(entry => entry.timestamp >= timeRange.start && entry.timestamp <= timeRange.end); } // Filter for cloud entries (those with batch metadata) const cloudFeedback = allFeedback.filter(entry => entry.batchId || entry.source === 'cloud'); let exportData; if (format === 'analytics') { // Include analytics in export const analytics = await this.feedbackCollector.getFeedbackAnalytics(); exportData = { summary: { totalEntries: cloudFeedback.length, timeRange: timeRange || { start: 'all', end: 'all' }, exportTimestamp: Date.now() }, analytics: includeMetrics ? analytics : null, feedbackData: cloudFeedback.slice(0, 100) // Limit for size }; } else { exportData = this.feedbackCollector.exportFeedbackData(format); } const exportSize = JSON.stringify(exportData).length; this.logger.success(`✅ Exported ${cloudFeedback.length} cloud feedback entries`); return { success: true, format, entriesExported: cloudFeedback.length, exportSize: `${Math.round(exportSize / 1024)}KB`, includesAnalytics: format === 'analytics' && includeMetrics }; } catch (error) { this.logger.error(`Failed to export data: ${error instanceof Error ? error.message : 'Unknown error'}`); return { success: false, message: 'Failed to export data', error: error instanceof Error ? error.message : 'Unknown error' }; } } /** * Configure cloud collection settings */ async configureCloudCollection(args) { this.logger.info('⚙️ Configuring cloud collection settings'); try { // Update collector configuration const collectorUpdates = {}; if (args.privacyLevel) { const privacyMap = { 'minimal': 'full', 'standard': 'anonymous', 'strict': 'opt_in' }; collectorUpdates.privacyMode = privacyMap[args.privacyLevel] || 'anonymous'; } this.feedbackCollector.updateConfiguration(collectorUpdates); // Note: In a real implementation, batch processing settings would be stored separately const configuration = { batchProcessingEnabled: args.batchProcessingEnabled !== false, maxBatchSize: args.maxBatchSize || 100, privacyLevel: args.privacyLevel || 'standard', retentionDays: args.retentionDays || 90, enableRealTimeProcessing: args.enableRealTimeProcessing !== false }; this.logger.success('✅ Cloud collection configuration updated'); return { success: true, message: 'Configuration updated successfully', configuration, effectiveDate: new Date().toISOString() }; } catch (error) { this.logger.error(`Failed to configure cloud collection: ${error instanceof Error ? error.message : 'Unknown error'}`); return { success: false, message: 'Failed to update configuration', error: error instanceof Error ? error.message : 'Unknown error' }; } } // Helper methods generateBatchId(sourceSystem) { const timestamp = Date.now(); const randomSuffix = crypto.randomBytes(4).toString('hex'); return `batch_${sourceSystem}_${timestamp}_${randomSuffix}`; } hashUserId(userId) { return crypto.createHash('sha256').update(userId + 'ai-debug-salt').digest('hex').substring(0, 16); } sanitizeFeedbackEntry(entry, index, validation) { try { // Required fields validation if (!entry.sessionId || !entry.agentType || !entry.taskDescription || !entry.outcome) { validation.errors.push(`Entry ${index}: Missing required fields`); return null; } // Sanitize and validate ratings const userExperience = entry.userExperience || {}; const sanitizedRatings = { satisfaction: this.sanitizeRating(userExperience.satisfaction), efficiency: this.sanitizeRating(userExperience.efficiency), clarity: this.sanitizeRating(userExperience.clarity), usefulness: this.sanitizeRating(userExperience.usefulness) }; // Sanitize text fields const feedback = entry.feedback || {}; const sanitizedFeedback = { strengths: this.sanitizeTextArray(feedback.strengths), weaknesses: this.sanitizeTextArray(feedback.weaknesses), suggestions: this.sanitizeTextArray(feedback.suggestions), wouldUseAgain: Boolean(feedback.wouldUseAgain), recommendToOthers: Boolean(feedback.recommendToOthers) }; // Privacy-preserving sanitization const sanitizedEntry = { sessionId: this.sanitizeSessionId(entry.sessionId), agentType: this.sanitizeAgentType(entry.agentType), taskDescription: this.sanitizeText(entry.taskDescription, 500), outcome: entry.outcome, userExperience: sanitizedRatings, feedback: sanitizedFeedback, technicalMetrics: { responseTimeMs: Math.max(0, Number(entry.technicalMetrics?.responseTimeMs) || 0), tokensSaved: Math.max(0, Number(entry.technicalMetrics?.tokensSaved) || 0), errorsEncountered: this.sanitizeTextArray(entry.technicalMetrics?.errorsEncountered), recoveryActions: this.sanitizeTextArray(entry.technicalMetrics?.recoveryActions) }, contextualData: { framework: this.sanitizeText(entry.contextualData?.framework, 50) || 'unknown', projectComplexity: this.sanitizeComplexity(entry.contextualData?.projectComplexity), userType: 'ai_assistant', // Always set to ai_assistant for cloud submissions sessionDuration: Math.max(0, Number(entry.contextualData?.sessionDuration) || 0) } }; return sanitizedEntry; } catch (error) { validation.errors.push(`Entry ${index}: Sanitization failed - ${error instanceof Error ? error.message : 'Unknown error'}`); return null; } } sanitizeRating(rating) { const num = Number(rating); if (isNaN(num)) return 8; // Default rating return Math.max(1, Math.min(10, Math.round(num))); } sanitizeTextArray(arr) { if (!Array.isArray(arr)) return []; return arr .map(item => this.sanitizeText(String(item), 200)) .filter(item => item.length > 0) .slice(0, 10); // Limit array size } sanitizeText(text, maxLength) { if (typeof text !== 'string') return ''; // Remove potentially sensitive information let sanitized = text .replace(/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g, '[email]') // emails .replace(/\b(?:https?:\/\/)?(?:www\.)?[a-zA-Z0-9-]+\.[a-zA-Z]{2,}(?:\/[^\s]*)?\b/g, '[url]') // URLs .replace(/\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g, '[ip]') // IP addresses .replace(/[^\w\s\-.,!?()]/g, '') // Remove special characters .trim(); return sanitized.substring(0, maxLength); } sanitizeSessionId(sessionId) { // Hash session ID for privacy const sanitized = String(sessionId).replace(/[^a-zA-Z0-9_-]/g, ''); return crypto.createHash('md5').update(sanitized).digest('hex').substring(0, 12); } sanitizeAgentType(agentType) { const validAgents = [ 'debug-discovery-agent', 'performance-analysis-agent', 'accessibility-audit-agent', 'error-investigation-agent', 'validation-testing-agent', 'framework-specialist-agent', 'data-extraction-agent', 'testing-infrastructure-agent' ]; const sanitized = String(agentType).toLowerCase().trim(); return validAgents.includes(sanitized) ? sanitized : 'unknown-agent'; } sanitizeComplexity(complexity) { const sanitized = String(complexity).toLowerCase().trim(); if (['simple', 'moderate', 'complex'].includes(sanitized)) { return sanitized; } return 'moderate'; } async processBatch(batch) { this.logger.info(`Processing batch ${batch.batchId} with ${batch.feedbackEntries.length} entries`); // Process each feedback entry for (const entry of batch.feedbackEntries) { try { // Add batch metadata to entry const enhancedEntry = { ...entry, batchId: batch.batchId, source: 'cloud', sourceSystem: batch.sourceSystem, userIdHash: batch.userIdHash, cloudMetadata: batch.metadata }; // Collect feedback using existing collector await this.feedbackCollector.collectFeedback(enhancedEntry.sessionId || 'unknown', enhancedEntry.agentType || 'unknown-agent', enhancedEntry); } catch (error) { this.logger.warn(`Failed to process entry in batch ${batch.batchId}: ${error instanceof Error ? error.message : 'Unknown error'}`); } } this.logger.success(`✅ Successfully processed batch ${batch.batchId}`); } } //# sourceMappingURL=ai-feedback-cloud-handler.js.map