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
JavaScript
/**
* 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