Skip to main content
Glama
mutation-tracker.js7.81 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.mutationTracker = exports.MutationTracker = void 0; const intent_classifier_js_1 = require("./intent-classifier.js"); const mutation_validator_js_1 = require("./mutation-validator.js"); const intent_sanitizer_js_1 = require("./intent-sanitizer.js"); const workflow_sanitizer_js_1 = require("./workflow-sanitizer.js"); const logger_js_1 = require("../utils/logger.js"); class MutationTracker { constructor() { this.recentMutations = []; this.RECENT_MUTATIONS_LIMIT = 100; } async processMutation(data, userId) { try { if (!this.validateMutationData(data)) { logger_js_1.logger.debug('Mutation data validation failed'); return null; } const workflowBefore = workflow_sanitizer_js_1.WorkflowSanitizer.sanitizeWorkflowRaw(data.workflowBefore); const workflowAfter = workflow_sanitizer_js_1.WorkflowSanitizer.sanitizeWorkflowRaw(data.workflowAfter); const sanitizedIntent = intent_sanitizer_js_1.intentSanitizer.sanitize(data.userIntent); if (mutation_validator_js_1.mutationValidator.shouldExclude(data)) { logger_js_1.logger.debug('Mutation excluded from tracking based on quality criteria'); return null; } if (mutation_validator_js_1.mutationValidator.isDuplicate(workflowBefore, workflowAfter, data.operations, this.recentMutations)) { logger_js_1.logger.debug('Duplicate mutation detected, skipping tracking'); return null; } const hashBefore = mutation_validator_js_1.mutationValidator.hashWorkflow(workflowBefore); const hashAfter = mutation_validator_js_1.mutationValidator.hashWorkflow(workflowAfter); const structureHashBefore = workflow_sanitizer_js_1.WorkflowSanitizer.generateWorkflowHash(workflowBefore); const structureHashAfter = workflow_sanitizer_js_1.WorkflowSanitizer.generateWorkflowHash(workflowAfter); const intentClassification = intent_classifier_js_1.intentClassifier.classify(data.operations, sanitizedIntent); const changeMetrics = this.calculateChangeMetrics(data.operations); const validationMetrics = this.calculateValidationMetrics(data.validationBefore, data.validationAfter); const record = { userId, sessionId: data.sessionId, workflowBefore, workflowAfter, workflowHashBefore: hashBefore, workflowHashAfter: hashAfter, workflowStructureHashBefore: structureHashBefore, workflowStructureHashAfter: structureHashAfter, userIntent: sanitizedIntent, intentClassification, toolName: data.toolName, operations: data.operations, operationCount: data.operations.length, operationTypes: this.extractOperationTypes(data.operations), validationBefore: data.validationBefore, validationAfter: data.validationAfter, ...validationMetrics, ...changeMetrics, mutationSuccess: data.mutationSuccess, mutationError: data.mutationError, durationMs: data.durationMs, }; this.addToRecentMutations(hashBefore, hashAfter, data.operations); return record; } catch (error) { logger_js_1.logger.error('Error processing mutation:', error); return null; } } validateMutationData(data) { const validationResult = mutation_validator_js_1.mutationValidator.validate(data); if (!validationResult.valid) { logger_js_1.logger.warn('Mutation data validation failed:', validationResult.errors); return false; } if (validationResult.warnings.length > 0) { logger_js_1.logger.debug('Mutation data validation warnings:', validationResult.warnings); } return true; } calculateChangeMetrics(operations) { const metrics = { nodesAdded: 0, nodesRemoved: 0, nodesModified: 0, connectionsAdded: 0, connectionsRemoved: 0, propertiesChanged: 0, }; for (const op of operations) { switch (op.type) { case 'addNode': metrics.nodesAdded++; break; case 'removeNode': metrics.nodesRemoved++; break; case 'updateNode': metrics.nodesModified++; if ('updates' in op && op.updates) { metrics.propertiesChanged += Object.keys(op.updates).length; } break; case 'addConnection': metrics.connectionsAdded++; break; case 'removeConnection': metrics.connectionsRemoved++; break; case 'rewireConnection': metrics.connectionsRemoved++; metrics.connectionsAdded++; break; case 'replaceConnections': if ('connections' in op && op.connections) { metrics.connectionsRemoved++; metrics.connectionsAdded++; } break; case 'updateSettings': if ('settings' in op && op.settings) { metrics.propertiesChanged += Object.keys(op.settings).length; } break; case 'moveNode': case 'enableNode': case 'disableNode': case 'updateName': case 'addTag': case 'removeTag': case 'activateWorkflow': case 'deactivateWorkflow': case 'cleanStaleConnections': metrics.propertiesChanged++; break; } } return metrics; } calculateValidationMetrics(validationBefore, validationAfter) { if (!validationBefore || !validationAfter) { return { validationImproved: null, errorsResolved: 0, errorsIntroduced: 0, }; } const errorsBefore = validationBefore.errors?.length || 0; const errorsAfter = validationAfter.errors?.length || 0; const errorsResolved = Math.max(0, errorsBefore - errorsAfter); const errorsIntroduced = Math.max(0, errorsAfter - errorsBefore); const validationImproved = errorsBefore > errorsAfter; return { validationImproved, errorsResolved, errorsIntroduced, }; } extractOperationTypes(operations) { const types = new Set(operations.map((op) => op.type)); return Array.from(types); } addToRecentMutations(hashBefore, hashAfter, operations) { this.recentMutations.push({ hashBefore, hashAfter, operations }); if (this.recentMutations.length > this.RECENT_MUTATIONS_LIMIT) { this.recentMutations.shift(); } } clearRecentMutations() { this.recentMutations = []; } getRecentMutationsCount() { return this.recentMutations.length; } } exports.MutationTracker = MutationTracker; exports.mutationTracker = new MutationTracker(); //# sourceMappingURL=mutation-tracker.js.map

Latest Blog Posts

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/czlonkowski/n8n-mcp'

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