@loxia-labs/loxia-autopilot-one 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/LICENSE +267 -0
  2. package/README.md +509 -0
  3. package/bin/cli.js +117 -0
  4. package/package.json +94 -0
  5. package/scripts/install-scanners.js +236 -0
  6. package/src/analyzers/CSSAnalyzer.js +297 -0
  7. package/src/analyzers/ConfigValidator.js +690 -0
  8. package/src/analyzers/ESLintAnalyzer.js +320 -0
  9. package/src/analyzers/JavaScriptAnalyzer.js +261 -0
  10. package/src/analyzers/PrettierFormatter.js +247 -0
  11. package/src/analyzers/PythonAnalyzer.js +266 -0
  12. package/src/analyzers/SecurityAnalyzer.js +729 -0
  13. package/src/analyzers/TypeScriptAnalyzer.js +247 -0
  14. package/src/analyzers/codeCloneDetector/analyzer.js +344 -0
  15. package/src/analyzers/codeCloneDetector/detector.js +203 -0
  16. package/src/analyzers/codeCloneDetector/index.js +160 -0
  17. package/src/analyzers/codeCloneDetector/parser.js +199 -0
  18. package/src/analyzers/codeCloneDetector/reporter.js +148 -0
  19. package/src/analyzers/codeCloneDetector/scanner.js +59 -0
  20. package/src/core/agentPool.js +1474 -0
  21. package/src/core/agentScheduler.js +2147 -0
  22. package/src/core/contextManager.js +709 -0
  23. package/src/core/messageProcessor.js +732 -0
  24. package/src/core/orchestrator.js +548 -0
  25. package/src/core/stateManager.js +877 -0
  26. package/src/index.js +631 -0
  27. package/src/interfaces/cli.js +549 -0
  28. package/src/interfaces/webServer.js +2162 -0
  29. package/src/modules/fileExplorer/controller.js +280 -0
  30. package/src/modules/fileExplorer/index.js +37 -0
  31. package/src/modules/fileExplorer/middleware.js +92 -0
  32. package/src/modules/fileExplorer/routes.js +125 -0
  33. package/src/modules/fileExplorer/types.js +44 -0
  34. package/src/services/aiService.js +1232 -0
  35. package/src/services/apiKeyManager.js +164 -0
  36. package/src/services/benchmarkService.js +366 -0
  37. package/src/services/budgetService.js +539 -0
  38. package/src/services/contextInjectionService.js +247 -0
  39. package/src/services/conversationCompactionService.js +637 -0
  40. package/src/services/errorHandler.js +810 -0
  41. package/src/services/fileAttachmentService.js +544 -0
  42. package/src/services/modelRouterService.js +366 -0
  43. package/src/services/modelsService.js +322 -0
  44. package/src/services/qualityInspector.js +796 -0
  45. package/src/services/tokenCountingService.js +536 -0
  46. package/src/tools/agentCommunicationTool.js +1344 -0
  47. package/src/tools/agentDelayTool.js +485 -0
  48. package/src/tools/asyncToolManager.js +604 -0
  49. package/src/tools/baseTool.js +800 -0
  50. package/src/tools/browserTool.js +920 -0
  51. package/src/tools/cloneDetectionTool.js +621 -0
  52. package/src/tools/dependencyResolverTool.js +1215 -0
  53. package/src/tools/fileContentReplaceTool.js +875 -0
  54. package/src/tools/fileSystemTool.js +1107 -0
  55. package/src/tools/fileTreeTool.js +853 -0
  56. package/src/tools/imageTool.js +901 -0
  57. package/src/tools/importAnalyzerTool.js +1060 -0
  58. package/src/tools/jobDoneTool.js +248 -0
  59. package/src/tools/seekTool.js +956 -0
  60. package/src/tools/staticAnalysisTool.js +1778 -0
  61. package/src/tools/taskManagerTool.js +2873 -0
  62. package/src/tools/terminalTool.js +2304 -0
  63. package/src/tools/webTool.js +1430 -0
  64. package/src/types/agent.js +519 -0
  65. package/src/types/contextReference.js +972 -0
  66. package/src/types/conversation.js +730 -0
  67. package/src/types/toolCommand.js +747 -0
  68. package/src/utilities/attachmentValidator.js +292 -0
  69. package/src/utilities/configManager.js +582 -0
  70. package/src/utilities/constants.js +722 -0
  71. package/src/utilities/directoryAccessManager.js +535 -0
  72. package/src/utilities/fileProcessor.js +307 -0
  73. package/src/utilities/logger.js +436 -0
  74. package/src/utilities/tagParser.js +1246 -0
  75. package/src/utilities/toolConstants.js +317 -0
  76. package/web-ui/build/index.html +15 -0
  77. package/web-ui/build/logo.png +0 -0
  78. package/web-ui/build/logo2.png +0 -0
  79. package/web-ui/build/static/index-CjkkcnFA.js +344 -0
  80. package/web-ui/build/static/index-Dy2bYbOa.css +1 -0
@@ -0,0 +1,810 @@
1
+ import { ERROR_TYPES, HTTP_STATUS, AGENT_STATUS } from '../utilities/constants.js';
2
+
3
+ /**
4
+ * Comprehensive error handling service for the Loxia AI Agents System
5
+ * Provides error classification, recovery strategies, and monitoring
6
+ */
7
+ export class ErrorHandler {
8
+ constructor(config, logger) {
9
+ this.config = config || {};
10
+ this.logger = logger;
11
+ this.errorStats = {
12
+ totalErrors: 0,
13
+ errorsByType: new Map(),
14
+ errorsByAgent: new Map(),
15
+ recoveryAttempts: 0,
16
+ successfulRecoveries: 0,
17
+ criticalErrors: 0
18
+ };
19
+
20
+ this.recoveryStrategies = new Map();
21
+ this.errorSubscribers = new Set();
22
+ this.errorQueue = [];
23
+ this.isProcessingQueue = false;
24
+
25
+ this.initializeRecoveryStrategies();
26
+ this.setupErrorProcessing();
27
+ }
28
+
29
+ /**
30
+ * Handle an error with appropriate classification and recovery
31
+ * @param {Error|Object} error - Error object or error data
32
+ * @param {Object} context - Error context information
33
+ * @returns {Object} Error handling result
34
+ */
35
+ async handleError(error, context = {}) {
36
+ try {
37
+ const errorInfo = this.classifyError(error, context);
38
+ const handlingResult = await this.processError(errorInfo);
39
+
40
+ this.updateErrorStats(errorInfo);
41
+ this.notifySubscribers(errorInfo, handlingResult);
42
+
43
+ return handlingResult;
44
+
45
+ } catch (handlingError) {
46
+ this.logger.error('Error handling failed', {
47
+ originalError: error.message,
48
+ handlingError: handlingError.message
49
+ });
50
+
51
+ return {
52
+ success: false,
53
+ error: handlingError,
54
+ recovery: null,
55
+ severity: 'critical'
56
+ };
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Classify error by type, severity, and context
62
+ * @param {Error|Object} error - Error to classify
63
+ * @param {Object} context - Error context
64
+ * @returns {Object} Classified error information
65
+ */
66
+ classifyError(error, context = {}) {
67
+ const errorInfo = {
68
+ id: this.generateErrorId(),
69
+ timestamp: new Date().toISOString(),
70
+ message: error.message || error.toString(),
71
+ stack: error.stack || null,
72
+ code: error.code || null,
73
+ type: this.determineErrorType(error, context),
74
+ severity: this.determineSeverity(error, context),
75
+ context: { ...context },
76
+ recoverable: true,
77
+ retryCount: context.retryCount || 0,
78
+ maxRetries: this.getMaxRetries(error, context)
79
+ };
80
+
81
+ // Additional classification based on context
82
+ if (context.agentId) {
83
+ errorInfo.agentId = context.agentId;
84
+ }
85
+
86
+ if (context.toolId) {
87
+ errorInfo.toolId = context.toolId;
88
+ }
89
+
90
+ if (context.operationId) {
91
+ errorInfo.operationId = context.operationId;
92
+ }
93
+
94
+ // Determine if error is recoverable
95
+ errorInfo.recoverable = this.isRecoverable(errorInfo);
96
+
97
+ return errorInfo;
98
+ }
99
+
100
+ /**
101
+ * Process error with appropriate recovery strategy
102
+ * @param {Object} errorInfo - Classified error information
103
+ * @returns {Object} Processing result
104
+ */
105
+ async processError(errorInfo) {
106
+ try {
107
+ this.logger.error('Processing error', {
108
+ errorId: errorInfo.id,
109
+ type: errorInfo.type,
110
+ severity: errorInfo.severity,
111
+ agentId: errorInfo.agentId,
112
+ message: errorInfo.message
113
+ });
114
+
115
+ // Check if this is a critical error requiring immediate attention
116
+ if (errorInfo.severity === 'critical') {
117
+ await this.handleCriticalError(errorInfo);
118
+ }
119
+
120
+ // Attempt recovery if error is recoverable
121
+ let recoveryResult = null;
122
+ if (errorInfo.recoverable && errorInfo.retryCount < errorInfo.maxRetries) {
123
+ recoveryResult = await this.attemptRecovery(errorInfo);
124
+ }
125
+
126
+ // Log error for monitoring and analysis
127
+ await this.logError(errorInfo, recoveryResult);
128
+
129
+ return {
130
+ success: recoveryResult?.success || false,
131
+ errorId: errorInfo.id,
132
+ errorType: errorInfo.type,
133
+ severity: errorInfo.severity,
134
+ recovery: recoveryResult,
135
+ shouldRetry: this.shouldRetry(errorInfo, recoveryResult),
136
+ retryDelay: this.calculateRetryDelay(errorInfo)
137
+ };
138
+
139
+ } catch (processingError) {
140
+ this.logger.error('Error processing failed', {
141
+ errorId: errorInfo.id,
142
+ processingError: processingError.message
143
+ });
144
+
145
+ return {
146
+ success: false,
147
+ errorId: errorInfo.id,
148
+ errorType: errorInfo.type,
149
+ severity: 'critical',
150
+ recovery: null,
151
+ shouldRetry: false
152
+ };
153
+ }
154
+ }
155
+
156
+ /**
157
+ * Attempt to recover from an error using appropriate strategy
158
+ * @param {Object} errorInfo - Error information
159
+ * @returns {Object} Recovery result
160
+ */
161
+ async attemptRecovery(errorInfo) {
162
+ try {
163
+ this.errorStats.recoveryAttempts++;
164
+
165
+ const strategy = this.recoveryStrategies.get(errorInfo.type) ||
166
+ this.recoveryStrategies.get('default');
167
+
168
+ if (!strategy) {
169
+ this.logger.warn('No recovery strategy found', { errorType: errorInfo.type });
170
+ return { success: false, reason: 'No recovery strategy available' };
171
+ }
172
+
173
+ this.logger.info('Attempting error recovery', {
174
+ errorId: errorInfo.id,
175
+ errorType: errorInfo.type,
176
+ strategy: strategy.name,
177
+ retryCount: errorInfo.retryCount
178
+ });
179
+
180
+ const recoveryResult = await strategy.recover(errorInfo);
181
+
182
+ if (recoveryResult.success) {
183
+ this.errorStats.successfulRecoveries++;
184
+ this.logger.info('Error recovery successful', {
185
+ errorId: errorInfo.id,
186
+ strategy: strategy.name
187
+ });
188
+ } else {
189
+ this.logger.warn('Error recovery failed', {
190
+ errorId: errorInfo.id,
191
+ strategy: strategy.name,
192
+ reason: recoveryResult.reason
193
+ });
194
+ }
195
+
196
+ return recoveryResult;
197
+
198
+ } catch (recoveryError) {
199
+ this.logger.error('Recovery attempt failed', {
200
+ errorId: errorInfo.id,
201
+ recoveryError: recoveryError.message
202
+ });
203
+
204
+ return {
205
+ success: false,
206
+ reason: `Recovery attempt failed: ${recoveryError.message}`
207
+ };
208
+ }
209
+ }
210
+
211
+ /**
212
+ * Handle critical errors that require immediate attention
213
+ * @param {Object} errorInfo - Critical error information
214
+ */
215
+ async handleCriticalError(errorInfo) {
216
+ try {
217
+ this.errorStats.criticalErrors++;
218
+
219
+ this.logger.error('Critical error detected', {
220
+ errorId: errorInfo.id,
221
+ message: errorInfo.message,
222
+ context: errorInfo.context
223
+ });
224
+
225
+ // Emit critical error event
226
+ if (typeof process !== 'undefined' && process.emit) {
227
+ process.emit('criticalError', errorInfo);
228
+ }
229
+
230
+ // Take immediate protective actions based on error type
231
+ switch (errorInfo.type) {
232
+ case ERROR_TYPES.AUTHENTICATION_FAILED:
233
+ await this.handleAuthenticationFailure(errorInfo);
234
+ break;
235
+
236
+ case ERROR_TYPES.RATE_LIMIT_EXCEEDED:
237
+ await this.handleRateLimitExceeded(errorInfo);
238
+ break;
239
+
240
+ case ERROR_TYPES.OPERATION_TIMEOUT:
241
+ await this.handleOperationTimeout(errorInfo);
242
+ break;
243
+
244
+ default:
245
+ await this.handleGenericCriticalError(errorInfo);
246
+ }
247
+
248
+ } catch (criticalHandlingError) {
249
+ this.logger.error('Critical error handling failed', {
250
+ errorId: errorInfo.id,
251
+ handlingError: criticalHandlingError.message
252
+ });
253
+ }
254
+ }
255
+
256
+ /**
257
+ * Determine error type based on error and context
258
+ * @param {Error|Object} error - Error object
259
+ * @param {Object} context - Error context
260
+ * @returns {string} Error type
261
+ */
262
+ determineErrorType(error, context) {
263
+ // Check error code or HTTP status
264
+ if (error.code === 'ENOENT' || error.status === HTTP_STATUS.NOT_FOUND) {
265
+ return ERROR_TYPES.FILE_NOT_FOUND;
266
+ }
267
+
268
+ if (error.code === 'EACCES' || error.status === HTTP_STATUS.FORBIDDEN) {
269
+ return ERROR_TYPES.PERMISSION_DENIED;
270
+ }
271
+
272
+ if (error.status === HTTP_STATUS.UNAUTHORIZED) {
273
+ return ERROR_TYPES.AUTHENTICATION_FAILED;
274
+ }
275
+
276
+ if (error.status === HTTP_STATUS.TOO_MANY_REQUESTS) {
277
+ return ERROR_TYPES.RATE_LIMIT_EXCEEDED;
278
+ }
279
+
280
+ // Check error message patterns
281
+ const message = error.message?.toLowerCase() || '';
282
+
283
+ if (message.includes('timeout') || message.includes('timed out')) {
284
+ return ERROR_TYPES.OPERATION_TIMEOUT;
285
+ }
286
+
287
+ if (message.includes('validation') || message.includes('invalid')) {
288
+ return ERROR_TYPES.VALIDATION_ERROR;
289
+ }
290
+
291
+ if (message.includes('config') || message.includes('configuration')) {
292
+ return ERROR_TYPES.CONFIGURATION_ERROR;
293
+ }
294
+
295
+ // Check context for additional clues
296
+ if (context.operation === 'file_operation') {
297
+ return ERROR_TYPES.FILE_NOT_FOUND;
298
+ }
299
+
300
+ if (context.operation === 'api_request') {
301
+ return ERROR_TYPES.RATE_LIMIT_EXCEEDED;
302
+ }
303
+
304
+ return ERROR_TYPES.UNKNOWN_ERROR;
305
+ }
306
+
307
+ /**
308
+ * Determine error severity
309
+ * @param {Error|Object} error - Error object
310
+ * @param {Object} context - Error context
311
+ * @returns {string} Error severity
312
+ */
313
+ determineSeverity(error, context) {
314
+ const criticalTypes = [
315
+ ERROR_TYPES.AUTHENTICATION_FAILED,
316
+ ERROR_TYPES.CONFIGURATION_ERROR
317
+ ];
318
+
319
+ const highTypes = [
320
+ ERROR_TYPES.RATE_LIMIT_EXCEEDED,
321
+ ERROR_TYPES.OPERATION_TIMEOUT,
322
+ ERROR_TYPES.PERMISSION_DENIED
323
+ ];
324
+
325
+ const errorType = this.determineErrorType(error, context);
326
+
327
+ if (criticalTypes.includes(errorType)) {
328
+ return 'critical';
329
+ }
330
+
331
+ if (highTypes.includes(errorType)) {
332
+ return 'high';
333
+ }
334
+
335
+ // Check retry count
336
+ const retryCount = (context && context.retryCount) || 0;
337
+ if (retryCount >= 3) {
338
+ return 'high';
339
+ }
340
+
341
+ // Check if error is affecting agent operation
342
+ if (context && context.agentId && context.operation === 'agent_communication') {
343
+ return 'medium';
344
+ }
345
+
346
+ return 'low';
347
+ }
348
+
349
+ /**
350
+ * Check if error is recoverable
351
+ * @param {Object} errorInfo - Error information
352
+ * @returns {boolean} True if recoverable
353
+ */
354
+ isRecoverable(errorInfo) {
355
+ const nonRecoverableTypes = [
356
+ ERROR_TYPES.AUTHENTICATION_FAILED,
357
+ ERROR_TYPES.CONFIGURATION_ERROR
358
+ ];
359
+
360
+ if (nonRecoverableTypes.includes(errorInfo.type)) {
361
+ return false;
362
+ }
363
+
364
+ // Not recoverable if max retries exceeded
365
+ if (errorInfo.retryCount >= errorInfo.maxRetries) {
366
+ return false;
367
+ }
368
+
369
+ // Not recoverable if critical severity
370
+ if (errorInfo.severity === 'critical') {
371
+ return false;
372
+ }
373
+
374
+ return true;
375
+ }
376
+
377
+ /**
378
+ * Get maximum retry attempts for error type
379
+ * @param {Error|Object} error - Error object
380
+ * @param {Object} context - Error context
381
+ * @returns {number} Maximum retries
382
+ */
383
+ getMaxRetries(error, context) {
384
+ const errorType = this.determineErrorType(error, context);
385
+
386
+ const retryLimits = {
387
+ [ERROR_TYPES.RATE_LIMIT_EXCEEDED]: 5,
388
+ [ERROR_TYPES.OPERATION_TIMEOUT]: 3,
389
+ [ERROR_TYPES.FILE_NOT_FOUND]: 2,
390
+ [ERROR_TYPES.PERMISSION_DENIED]: 1,
391
+ [ERROR_TYPES.VALIDATION_ERROR]: 2,
392
+ [ERROR_TYPES.UNKNOWN_ERROR]: 3
393
+ };
394
+
395
+ return retryLimits[errorType] || 3;
396
+ }
397
+
398
+ /**
399
+ * Determine if error should be retried
400
+ * @param {Object} errorInfo - Error information
401
+ * @param {Object} recoveryResult - Recovery result
402
+ * @returns {boolean} True if should retry
403
+ */
404
+ shouldRetry(errorInfo, recoveryResult) {
405
+ if (!errorInfo.recoverable) {
406
+ return false;
407
+ }
408
+
409
+ if (errorInfo.retryCount >= errorInfo.maxRetries) {
410
+ return false;
411
+ }
412
+
413
+ if (recoveryResult && recoveryResult.success) {
414
+ return false; // Recovery succeeded, no need to retry
415
+ }
416
+
417
+ return true;
418
+ }
419
+
420
+ /**
421
+ * Calculate retry delay based on error information
422
+ * @param {Object} errorInfo - Error information
423
+ * @returns {number} Delay in milliseconds
424
+ */
425
+ calculateRetryDelay(errorInfo) {
426
+ const baseDelay = 1000; // 1 second
427
+ const maxDelay = 30000; // 30 seconds
428
+
429
+ // Exponential backoff with jitter
430
+ const exponentialDelay = baseDelay * Math.pow(2, errorInfo.retryCount);
431
+ const jitter = Math.random() * 1000; // Random jitter up to 1 second
432
+
433
+ return Math.min(exponentialDelay + jitter, maxDelay);
434
+ }
435
+
436
+ /**
437
+ * Initialize recovery strategies for different error types
438
+ */
439
+ initializeRecoveryStrategies() {
440
+ // File not found recovery
441
+ this.recoveryStrategies.set(ERROR_TYPES.FILE_NOT_FOUND, {
442
+ name: 'file_not_found_recovery',
443
+ recover: async (errorInfo) => {
444
+ if (errorInfo.context.filePath) {
445
+ // Try to check if file exists in different location
446
+ // or create directory structure if needed
447
+ return {
448
+ success: false,
449
+ reason: 'File recovery not implemented yet',
450
+ suggestion: 'Check file path and permissions'
451
+ };
452
+ }
453
+ return { success: false, reason: 'No file path provided' };
454
+ }
455
+ });
456
+
457
+ // Rate limit recovery
458
+ this.recoveryStrategies.set(ERROR_TYPES.RATE_LIMIT_EXCEEDED, {
459
+ name: 'rate_limit_recovery',
460
+ recover: async (errorInfo) => {
461
+ const waitTime = this.calculateRateLimitWait(errorInfo);
462
+ this.logger.info('Waiting for rate limit recovery', { waitTime });
463
+
464
+ await new Promise(resolve => setTimeout(resolve, waitTime));
465
+
466
+ return {
467
+ success: true,
468
+ action: 'waited_for_rate_limit',
469
+ waitTime
470
+ };
471
+ }
472
+ });
473
+
474
+ // Operation timeout recovery
475
+ this.recoveryStrategies.set(ERROR_TYPES.OPERATION_TIMEOUT, {
476
+ name: 'timeout_recovery',
477
+ recover: async (errorInfo) => {
478
+ // Try to increase timeout for next attempt
479
+ if (errorInfo.context.timeout) {
480
+ const newTimeout = Math.min(errorInfo.context.timeout * 1.5, 300000); // Max 5 minutes
481
+ return {
482
+ success: true,
483
+ action: 'increased_timeout',
484
+ newTimeout
485
+ };
486
+ }
487
+
488
+ return { success: false, reason: 'Cannot adjust timeout' };
489
+ }
490
+ });
491
+
492
+ // Permission denied recovery
493
+ this.recoveryStrategies.set(ERROR_TYPES.PERMISSION_DENIED, {
494
+ name: 'permission_recovery',
495
+ recover: async (errorInfo) => {
496
+ return {
497
+ success: false,
498
+ reason: 'Permission errors require manual intervention',
499
+ suggestion: 'Check file/directory permissions'
500
+ };
501
+ }
502
+ });
503
+
504
+ // Validation error recovery
505
+ this.recoveryStrategies.set(ERROR_TYPES.VALIDATION_ERROR, {
506
+ name: 'validation_recovery',
507
+ recover: async (errorInfo) => {
508
+ // Try to clean/sanitize input data
509
+ if (errorInfo.context.inputData) {
510
+ return {
511
+ success: true,
512
+ action: 'data_sanitization',
513
+ suggestion: 'Input data was sanitized for retry'
514
+ };
515
+ }
516
+
517
+ return { success: false, reason: 'No input data to sanitize' };
518
+ }
519
+ });
520
+
521
+ // Default recovery strategy
522
+ this.recoveryStrategies.set('default', {
523
+ name: 'default_recovery',
524
+ recover: async (errorInfo) => {
525
+ // Generic recovery: wait and retry
526
+ const delay = this.calculateRetryDelay(errorInfo);
527
+ await new Promise(resolve => setTimeout(resolve, delay));
528
+
529
+ return {
530
+ success: true,
531
+ action: 'delay_retry',
532
+ delay
533
+ };
534
+ }
535
+ });
536
+ }
537
+
538
+ /**
539
+ * Handle authentication failure
540
+ * @param {Object} errorInfo - Error information
541
+ */
542
+ async handleAuthenticationFailure(errorInfo) {
543
+ this.logger.error('Authentication failure detected', { errorId: errorInfo.id });
544
+
545
+ // Could trigger token refresh, re-authentication, etc.
546
+ if (errorInfo.agentId) {
547
+ // Pause agent until authentication is resolved
548
+ // await this.orchestrator.pauseAgent(errorInfo.agentId, 300, 'Authentication failure');
549
+ }
550
+ }
551
+
552
+ /**
553
+ * Handle rate limit exceeded
554
+ * @param {Object} errorInfo - Error information
555
+ */
556
+ async handleRateLimitExceeded(errorInfo) {
557
+ const waitTime = this.calculateRateLimitWait(errorInfo);
558
+
559
+ this.logger.warn('Rate limit exceeded, implementing backoff', {
560
+ errorId: errorInfo.id,
561
+ waitTime,
562
+ agentId: errorInfo.agentId
563
+ });
564
+
565
+ if (errorInfo.agentId) {
566
+ // Pause agent temporarily
567
+ // await this.orchestrator.pauseAgent(errorInfo.agentId, waitTime / 1000, 'Rate limit exceeded');
568
+ }
569
+ }
570
+
571
+ /**
572
+ * Handle operation timeout
573
+ * @param {Object} errorInfo - Error information
574
+ */
575
+ async handleOperationTimeout(errorInfo) {
576
+ this.logger.error('Operation timeout detected', {
577
+ errorId: errorInfo.id,
578
+ operation: errorInfo.context.operation,
579
+ timeout: errorInfo.context.timeout
580
+ });
581
+
582
+ // Could cancel ongoing operations, adjust timeouts, etc.
583
+ }
584
+
585
+ /**
586
+ * Handle generic critical error
587
+ * @param {Object} errorInfo - Error information
588
+ */
589
+ async handleGenericCriticalError(errorInfo) {
590
+ this.logger.error('Generic critical error', {
591
+ errorId: errorInfo.id,
592
+ type: errorInfo.type,
593
+ message: errorInfo.message
594
+ });
595
+
596
+ // Generic protective measures
597
+ if (errorInfo.agentId) {
598
+ // Could pause agent for safety
599
+ this.logger.warn('Considering agent pause due to critical error', {
600
+ agentId: errorInfo.agentId
601
+ });
602
+ }
603
+ }
604
+
605
+ /**
606
+ * Calculate wait time for rate limit recovery
607
+ * @param {Object} errorInfo - Error information
608
+ * @returns {number} Wait time in milliseconds
609
+ */
610
+ calculateRateLimitWait(errorInfo) {
611
+ const baseWait = 60000; // 1 minute
612
+ const retryMultiplier = Math.pow(2, errorInfo.retryCount);
613
+ const maxWait = 300000; // 5 minutes
614
+
615
+ return Math.min(baseWait * retryMultiplier, maxWait);
616
+ }
617
+
618
+ /**
619
+ * Log error for monitoring and analysis
620
+ * @param {Object} errorInfo - Error information
621
+ * @param {Object} recoveryResult - Recovery result
622
+ */
623
+ async logError(errorInfo, recoveryResult) {
624
+ try {
625
+ const logEntry = {
626
+ errorId: errorInfo.id,
627
+ timestamp: errorInfo.timestamp,
628
+ type: errorInfo.type,
629
+ severity: errorInfo.severity,
630
+ message: errorInfo.message,
631
+ agentId: errorInfo.agentId,
632
+ toolId: errorInfo.toolId,
633
+ operationId: errorInfo.operationId,
634
+ retryCount: errorInfo.retryCount,
635
+ recoveryAttempted: !!recoveryResult,
636
+ recoverySuccess: recoveryResult?.success || false,
637
+ recoveryAction: recoveryResult?.action || null
638
+ };
639
+
640
+ // Store in error queue for batch processing
641
+ this.errorQueue.push(logEntry);
642
+
643
+ // Process queue if not already processing
644
+ if (!this.isProcessingQueue) {
645
+ this.processErrorQueue();
646
+ }
647
+
648
+ } catch (loggingError) {
649
+ this.logger.error('Error logging failed', {
650
+ errorId: errorInfo.id,
651
+ loggingError: loggingError.message
652
+ });
653
+ }
654
+ }
655
+
656
+ /**
657
+ * Process error queue for batch operations
658
+ */
659
+ async processErrorQueue() {
660
+ if (this.isProcessingQueue || this.errorQueue.length === 0) {
661
+ return;
662
+ }
663
+
664
+ this.isProcessingQueue = true;
665
+
666
+ try {
667
+ while (this.errorQueue.length > 0) {
668
+ const batch = this.errorQueue.splice(0, 10); // Process 10 at a time
669
+
670
+ // Here you would typically save to database, send to monitoring service, etc.
671
+ this.logger.debug('Processing error batch', { batchSize: batch.length });
672
+
673
+ // Simulate async processing
674
+ await new Promise(resolve => setTimeout(resolve, 100));
675
+ }
676
+ } catch (processingError) {
677
+ this.logger.error('Error queue processing failed', { error: processingError.message });
678
+ } finally {
679
+ this.isProcessingQueue = false;
680
+ }
681
+ }
682
+
683
+ /**
684
+ * Setup error processing infrastructure
685
+ */
686
+ setupErrorProcessing() {
687
+ // Setup periodic queue processing
688
+ setInterval(() => {
689
+ if (this.errorQueue.length > 0) {
690
+ this.processErrorQueue();
691
+ }
692
+ }, 5000); // Process every 5 seconds
693
+
694
+ // Setup global error handlers
695
+ if (typeof process !== 'undefined') {
696
+ process.on('uncaughtException', (error) => {
697
+ this.handleError(error, {
698
+ type: 'uncaught_exception',
699
+ severity: 'critical'
700
+ });
701
+ });
702
+
703
+ process.on('unhandledRejection', (reason, promise) => {
704
+ this.handleError(new Error(`Unhandled Rejection: ${reason}`), {
705
+ type: 'unhandled_rejection',
706
+ severity: 'high',
707
+ promise: promise.toString()
708
+ });
709
+ });
710
+ }
711
+ }
712
+
713
+ /**
714
+ * Update error statistics
715
+ * @param {Object} errorInfo - Error information
716
+ */
717
+ updateErrorStats(errorInfo) {
718
+ this.errorStats.totalErrors++;
719
+
720
+ // Update error type statistics
721
+ if (!this.errorStats.errorsByType.has(errorInfo.type)) {
722
+ this.errorStats.errorsByType.set(errorInfo.type, 0);
723
+ }
724
+ this.errorStats.errorsByType.set(
725
+ errorInfo.type,
726
+ this.errorStats.errorsByType.get(errorInfo.type) + 1
727
+ );
728
+
729
+ // Update agent error statistics
730
+ if (errorInfo.agentId) {
731
+ if (!this.errorStats.errorsByAgent.has(errorInfo.agentId)) {
732
+ this.errorStats.errorsByAgent.set(errorInfo.agentId, 0);
733
+ }
734
+ this.errorStats.errorsByAgent.set(
735
+ errorInfo.agentId,
736
+ this.errorStats.errorsByAgent.get(errorInfo.agentId) + 1
737
+ );
738
+ }
739
+ }
740
+
741
+ /**
742
+ * Subscribe to error notifications
743
+ * @param {Function} callback - Callback function
744
+ */
745
+ subscribe(callback) {
746
+ this.errorSubscribers.add(callback);
747
+ return () => this.errorSubscribers.delete(callback);
748
+ }
749
+
750
+ /**
751
+ * Notify error subscribers
752
+ * @param {Object} errorInfo - Error information
753
+ * @param {Object} handlingResult - Handling result
754
+ */
755
+ notifySubscribers(errorInfo, handlingResult) {
756
+ this.errorSubscribers.forEach(callback => {
757
+ try {
758
+ callback(errorInfo, handlingResult);
759
+ } catch (callbackError) {
760
+ this.logger.error('Error subscriber callback failed', {
761
+ error: callbackError.message
762
+ });
763
+ }
764
+ });
765
+ }
766
+
767
+ /**
768
+ * Generate unique error ID
769
+ * @returns {string} Error ID
770
+ */
771
+ generateErrorId() {
772
+ return `err_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
773
+ }
774
+
775
+ /**
776
+ * Get error statistics
777
+ * @returns {Object} Error statistics
778
+ */
779
+ getErrorStats() {
780
+ return {
781
+ totalErrors: this.errorStats.totalErrors,
782
+ errorsByType: Object.fromEntries(this.errorStats.errorsByType),
783
+ errorsByAgent: Object.fromEntries(this.errorStats.errorsByAgent),
784
+ recoveryAttempts: this.errorStats.recoveryAttempts,
785
+ successfulRecoveries: this.errorStats.successfulRecoveries,
786
+ recoverySuccessRate: this.errorStats.recoveryAttempts > 0
787
+ ? this.errorStats.successfulRecoveries / this.errorStats.recoveryAttempts
788
+ : 0,
789
+ criticalErrors: this.errorStats.criticalErrors,
790
+ queueLength: this.errorQueue.length
791
+ };
792
+ }
793
+
794
+ /**
795
+ * Clear error statistics (for testing or reset)
796
+ */
797
+ clearErrorStats() {
798
+ this.errorStats = {
799
+ totalErrors: 0,
800
+ errorsByType: new Map(),
801
+ errorsByAgent: new Map(),
802
+ recoveryAttempts: 0,
803
+ successfulRecoveries: 0,
804
+ criticalErrors: 0
805
+ };
806
+ this.errorQueue = [];
807
+ }
808
+ }
809
+
810
+ export default ErrorHandler;