@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.
- package/LICENSE +267 -0
- package/README.md +509 -0
- package/bin/cli.js +117 -0
- package/package.json +94 -0
- package/scripts/install-scanners.js +236 -0
- package/src/analyzers/CSSAnalyzer.js +297 -0
- package/src/analyzers/ConfigValidator.js +690 -0
- package/src/analyzers/ESLintAnalyzer.js +320 -0
- package/src/analyzers/JavaScriptAnalyzer.js +261 -0
- package/src/analyzers/PrettierFormatter.js +247 -0
- package/src/analyzers/PythonAnalyzer.js +266 -0
- package/src/analyzers/SecurityAnalyzer.js +729 -0
- package/src/analyzers/TypeScriptAnalyzer.js +247 -0
- package/src/analyzers/codeCloneDetector/analyzer.js +344 -0
- package/src/analyzers/codeCloneDetector/detector.js +203 -0
- package/src/analyzers/codeCloneDetector/index.js +160 -0
- package/src/analyzers/codeCloneDetector/parser.js +199 -0
- package/src/analyzers/codeCloneDetector/reporter.js +148 -0
- package/src/analyzers/codeCloneDetector/scanner.js +59 -0
- package/src/core/agentPool.js +1474 -0
- package/src/core/agentScheduler.js +2147 -0
- package/src/core/contextManager.js +709 -0
- package/src/core/messageProcessor.js +732 -0
- package/src/core/orchestrator.js +548 -0
- package/src/core/stateManager.js +877 -0
- package/src/index.js +631 -0
- package/src/interfaces/cli.js +549 -0
- package/src/interfaces/webServer.js +2162 -0
- package/src/modules/fileExplorer/controller.js +280 -0
- package/src/modules/fileExplorer/index.js +37 -0
- package/src/modules/fileExplorer/middleware.js +92 -0
- package/src/modules/fileExplorer/routes.js +125 -0
- package/src/modules/fileExplorer/types.js +44 -0
- package/src/services/aiService.js +1232 -0
- package/src/services/apiKeyManager.js +164 -0
- package/src/services/benchmarkService.js +366 -0
- package/src/services/budgetService.js +539 -0
- package/src/services/contextInjectionService.js +247 -0
- package/src/services/conversationCompactionService.js +637 -0
- package/src/services/errorHandler.js +810 -0
- package/src/services/fileAttachmentService.js +544 -0
- package/src/services/modelRouterService.js +366 -0
- package/src/services/modelsService.js +322 -0
- package/src/services/qualityInspector.js +796 -0
- package/src/services/tokenCountingService.js +536 -0
- package/src/tools/agentCommunicationTool.js +1344 -0
- package/src/tools/agentDelayTool.js +485 -0
- package/src/tools/asyncToolManager.js +604 -0
- package/src/tools/baseTool.js +800 -0
- package/src/tools/browserTool.js +920 -0
- package/src/tools/cloneDetectionTool.js +621 -0
- package/src/tools/dependencyResolverTool.js +1215 -0
- package/src/tools/fileContentReplaceTool.js +875 -0
- package/src/tools/fileSystemTool.js +1107 -0
- package/src/tools/fileTreeTool.js +853 -0
- package/src/tools/imageTool.js +901 -0
- package/src/tools/importAnalyzerTool.js +1060 -0
- package/src/tools/jobDoneTool.js +248 -0
- package/src/tools/seekTool.js +956 -0
- package/src/tools/staticAnalysisTool.js +1778 -0
- package/src/tools/taskManagerTool.js +2873 -0
- package/src/tools/terminalTool.js +2304 -0
- package/src/tools/webTool.js +1430 -0
- package/src/types/agent.js +519 -0
- package/src/types/contextReference.js +972 -0
- package/src/types/conversation.js +730 -0
- package/src/types/toolCommand.js +747 -0
- package/src/utilities/attachmentValidator.js +292 -0
- package/src/utilities/configManager.js +582 -0
- package/src/utilities/constants.js +722 -0
- package/src/utilities/directoryAccessManager.js +535 -0
- package/src/utilities/fileProcessor.js +307 -0
- package/src/utilities/logger.js +436 -0
- package/src/utilities/tagParser.js +1246 -0
- package/src/utilities/toolConstants.js +317 -0
- package/web-ui/build/index.html +15 -0
- package/web-ui/build/logo.png +0 -0
- package/web-ui/build/logo2.png +0 -0
- package/web-ui/build/static/index-CjkkcnFA.js +344 -0
- 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;
|