snow-flow 1.3.23 โ†’ 1.3.25

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.
@@ -26,6 +26,8 @@ class BaseMCPServer {
26
26
  this.tools = new Map();
27
27
  // Performance tracking
28
28
  this.toolMetrics = new Map();
29
+ // ๐Ÿ”ด SNOW-003 FIX: Circuit breaker implementation
30
+ this.circuitBreakers = new Map();
29
31
  /**
30
32
  * Tool handlers map
31
33
  */
@@ -159,48 +161,214 @@ class BaseMCPServer {
159
161
  }
160
162
  }
161
163
  /**
162
- * Execute tool with retry logic
164
+ * ๐Ÿ”ด SNOW-003 FIX: Enhanced retry logic with intelligent backoff and circuit breaker
165
+ * Addresses the 19% failure rate with better retry strategies and failure prevention
163
166
  */
164
167
  async executeWithRetry(toolName, args, attempt = 1) {
165
- const maxRetries = 3;
166
- const backoffMs = 1000 * Math.pow(2, attempt - 1); // Exponential backoff
168
+ // ๐Ÿ”ด CRITICAL: Increased retries from 3 to 6 for better resilience
169
+ const maxRetries = 6;
170
+ // ๐Ÿ”ด CRITICAL: Intelligent backoff based on error type
171
+ const backoffMs = this.calculateBackoff(attempt, toolName);
172
+ // ๐Ÿ”ด CRITICAL: Dynamic timeout based on tool complexity
173
+ const timeout = this.calculateTimeout(toolName);
167
174
  try {
168
175
  // Get tool handler
169
176
  const handler = this.getToolHandler(toolName);
170
177
  if (!handler) {
171
178
  throw new types_js_1.McpError(types_js_1.ErrorCode.MethodNotFound, `Unknown tool: ${toolName}`);
172
179
  }
173
- // Execute with timeout
174
- const timeout = 30000; // 30 seconds
180
+ // ๐Ÿ”ด CRITICAL: Memory usage check before execution
181
+ if (attempt === 1) {
182
+ await this.checkMemoryUsage();
183
+ }
184
+ // Execute with dynamic timeout
175
185
  const result = await Promise.race([
176
186
  handler(args),
177
- new Promise((_, reject) => setTimeout(() => reject(new Error('Tool execution timeout')), timeout)),
187
+ new Promise((_, reject) => setTimeout(() => reject(new Error(`Tool execution timeout after ${timeout}ms`)), timeout)),
178
188
  ]);
189
+ // ๐Ÿ”ด SUCCESS: Reset circuit breaker on success
190
+ this.resetCircuitBreaker(toolName);
179
191
  return result;
180
192
  }
181
193
  catch (error) {
182
- this.logger.error(`Tool ${toolName} execution failed (attempt ${attempt}):`, error);
183
- // Check if retryable
184
- if (attempt < maxRetries && this.isRetryableError(error)) {
185
- this.logger.info(`Retrying ${toolName} after ${backoffMs}ms...`);
194
+ this.logger.error(`๐Ÿ”ด Tool ${toolName} execution failed (attempt ${attempt}/${maxRetries}):`, error);
195
+ // ๐Ÿ”ด CRITICAL: Update circuit breaker
196
+ this.updateCircuitBreaker(toolName, error);
197
+ // Check if retryable and within limits
198
+ if (attempt < maxRetries && this.isRetryableError(error) && !this.isCircuitBreakerOpen(toolName)) {
199
+ this.logger.info(`๐Ÿ”„ Retrying ${toolName} after ${backoffMs}ms (attempt ${attempt + 1}/${maxRetries})...`);
186
200
  await new Promise(resolve => setTimeout(resolve, backoffMs));
187
201
  return this.executeWithRetry(toolName, args, attempt + 1);
188
202
  }
189
- // Final failure
190
- throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Tool execution failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
203
+ // ๐Ÿ”ด FINAL FAILURE: Enhanced error reporting
204
+ const errorMessage = this.createEnhancedErrorMessage(toolName, error, attempt, maxRetries);
205
+ throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, errorMessage);
191
206
  }
192
207
  }
193
208
  /**
194
- * Check if error is retryable
209
+ * ๐Ÿ”ด SNOW-003 FIX: Calculate intelligent backoff based on error type and attempt
210
+ */
211
+ calculateBackoff(attempt, toolName) {
212
+ // Base exponential backoff: 1s, 2s, 4s, 8s, 16s, 32s
213
+ const baseBackoff = 1000 * Math.pow(2, attempt - 1);
214
+ // Add jitter to prevent thundering herd (ยฑ25%)
215
+ const jitter = baseBackoff * 0.25 * (Math.random() - 0.5);
216
+ // Cap maximum backoff at 30 seconds
217
+ const maxBackoff = 30000;
218
+ return Math.min(baseBackoff + jitter, maxBackoff);
219
+ }
220
+ /**
221
+ * ๐Ÿ”ด SNOW-003 FIX: Calculate dynamic timeout based on tool complexity
222
+ */
223
+ calculateTimeout(toolName) {
224
+ // Tool-specific timeouts based on complexity
225
+ const timeoutMap = {
226
+ 'snow_create_flow': 90000, // Flow creation: 90s
227
+ 'snow_deploy': 120000, // Deployment: 2 minutes
228
+ 'snow_comprehensive_search': 45000, // Search: 45s
229
+ 'snow_find_artifact': 30000, // Find: 30s
230
+ 'snow_validate_live_connection': 15000, // Validation: 15s
231
+ };
232
+ // Default timeout for unknown tools
233
+ return timeoutMap[toolName] || 60000; // 60s default (increased from 30s)
234
+ }
235
+ updateCircuitBreaker(toolName, error) {
236
+ const breaker = this.circuitBreakers.get(toolName) || { failures: 0, lastFailure: 0, isOpen: false };
237
+ breaker.failures++;
238
+ breaker.lastFailure = Date.now();
239
+ // Open circuit breaker after 5 failures within 5 minutes
240
+ if (breaker.failures >= 5 && (Date.now() - breaker.lastFailure) < 300000) {
241
+ breaker.isOpen = true;
242
+ this.logger.warn(`๐Ÿšจ Circuit breaker opened for ${toolName} due to repeated failures`);
243
+ }
244
+ this.circuitBreakers.set(toolName, breaker);
245
+ }
246
+ isCircuitBreakerOpen(toolName) {
247
+ const breaker = this.circuitBreakers.get(toolName);
248
+ if (!breaker || !breaker.isOpen)
249
+ return false;
250
+ // Auto-reset circuit breaker after 10 minutes
251
+ if (Date.now() - breaker.lastFailure > 600000) {
252
+ breaker.isOpen = false;
253
+ breaker.failures = 0;
254
+ this.circuitBreakers.set(toolName, breaker);
255
+ this.logger.info(`โœ… Circuit breaker reset for ${toolName}`);
256
+ return false;
257
+ }
258
+ return true;
259
+ }
260
+ resetCircuitBreaker(toolName) {
261
+ const breaker = this.circuitBreakers.get(toolName);
262
+ if (breaker) {
263
+ breaker.failures = 0;
264
+ breaker.isOpen = false;
265
+ this.circuitBreakers.set(toolName, breaker);
266
+ }
267
+ }
268
+ /**
269
+ * ๐Ÿ”ด SNOW-003 FIX: Memory usage monitoring to prevent memory exhaustion failures
270
+ */
271
+ async checkMemoryUsage() {
272
+ try {
273
+ const memUsage = process.memoryUsage();
274
+ const heapUsedMB = Math.round(memUsage.heapUsed / 1024 / 1024);
275
+ const heapTotalMB = Math.round(memUsage.heapTotal / 1024 / 1024);
276
+ // Log memory usage if high (>200MB)
277
+ if (heapUsedMB > 200) {
278
+ this.logger.warn(`โš ๏ธ High memory usage: ${heapUsedMB}MB heap used, ${heapTotalMB}MB total`);
279
+ }
280
+ // Trigger garbage collection if memory usage is very high (>500MB)
281
+ if (heapUsedMB > 500 && global.gc) {
282
+ this.logger.info('๐Ÿงน Triggering garbage collection due to high memory usage');
283
+ global.gc();
284
+ }
285
+ // Fail fast if memory usage is critical (>800MB)
286
+ if (heapUsedMB > 800) {
287
+ throw new Error(`Critical memory usage: ${heapUsedMB}MB. Operation aborted to prevent system instability.`);
288
+ }
289
+ }
290
+ catch (error) {
291
+ this.logger.warn('Could not check memory usage:', error);
292
+ }
293
+ }
294
+ /**
295
+ * ๐Ÿ”ด SNOW-003 FIX: Enhanced error message with troubleshooting guidance
296
+ */
297
+ createEnhancedErrorMessage(toolName, error, attempts, maxRetries) {
298
+ const baseMessage = `Tool '${toolName}' failed after ${attempts}/${maxRetries} attempts`;
299
+ const errorDetail = error instanceof Error ? error.message : String(error);
300
+ let troubleshooting = '';
301
+ // Add specific troubleshooting based on error type
302
+ if (error.response?.status === 401) {
303
+ troubleshooting = '\n๐Ÿ’ก Authentication issue: Run "snow-flow auth login" to re-authenticate';
304
+ }
305
+ else if (error.response?.status === 403) {
306
+ troubleshooting = '\n๐Ÿ’ก Permission issue: Check ServiceNow user permissions and OAuth scopes';
307
+ }
308
+ else if (error.response?.status >= 500) {
309
+ troubleshooting = '\n๐Ÿ’ก ServiceNow server issue: Try again later or contact ServiceNow administrator';
310
+ }
311
+ else if (errorDetail.includes('timeout')) {
312
+ troubleshooting = '\n๐Ÿ’ก Timeout issue: ServiceNow instance may be slow - try again later';
313
+ }
314
+ else if (errorDetail.includes('network') || errorDetail.includes('connection')) {
315
+ troubleshooting = '\n๐Ÿ’ก Network issue: Check internet connection and ServiceNow instance availability';
316
+ }
317
+ return `${baseMessage}: ${errorDetail}${troubleshooting}`;
318
+ }
319
+ /**
320
+ * ๐Ÿ”ด SNOW-003 FIX: Enhanced error classification for ServiceNow specific errors
321
+ * Addresses the 19% failure rate by properly categorizing retryable errors
195
322
  */
196
323
  isRetryableError(error) {
197
324
  if (error instanceof Error) {
198
325
  const message = error.message.toLowerCase();
199
- return (message.includes('timeout') ||
326
+ // ๐Ÿ”ด CRITICAL: ServiceNow specific retryable errors
327
+ const serviceNowRetryable = (message.includes('timeout') ||
200
328
  message.includes('econnreset') ||
201
329
  message.includes('socket hang up') ||
202
330
  message.includes('enotfound') ||
203
- message.includes('rate limit'));
331
+ message.includes('rate limit') ||
332
+ message.includes('service unavailable') ||
333
+ message.includes('bad gateway') ||
334
+ message.includes('gateway timeout') ||
335
+ message.includes('connection refused') ||
336
+ message.includes('network error') ||
337
+ message.includes('dns lookup failed') ||
338
+ message.includes('connect etimedout') ||
339
+ message.includes('index not available') ||
340
+ message.includes('search index updating') ||
341
+ message.includes('temporary failure') ||
342
+ message.includes('server is busy') ||
343
+ message.includes('database lock') ||
344
+ message.includes('deadlock detected'));
345
+ // ๐Ÿ”ด CRITICAL: HTTP status code based retry logic
346
+ if (error.response?.status) {
347
+ const status = error.response.status;
348
+ const httpRetryable = (status === 429 || // Rate limit
349
+ status === 502 || // Bad Gateway
350
+ status === 503 || // Service Unavailable
351
+ status === 504 || // Gateway Timeout
352
+ status === 507 || // Insufficient Storage
353
+ status === 520 || // CloudFlare unknown error
354
+ status === 521 || // Web server is down
355
+ status === 522 || // Connection timed out
356
+ status === 523 || // Origin is unreachable
357
+ status === 524 // A timeout occurred
358
+ );
359
+ // 401 is retryable only once (for token refresh)
360
+ const authRetryable = status === 401 && !error.config?._retry;
361
+ return httpRetryable || authRetryable;
362
+ }
363
+ return serviceNowRetryable;
364
+ }
365
+ // Handle specific error types
366
+ if (error.code) {
367
+ const retryableCodes = [
368
+ 'ECONNRESET', 'ENOTFOUND', 'ECONNREFUSED', 'ETIMEDOUT',
369
+ 'ESOCKETTIMEDOUT', 'EHOSTUNREACH', 'EPIPE', 'EAI_AGAIN'
370
+ ];
371
+ return retryableCodes.includes(error.code);
204
372
  }
205
373
  return false;
206
374
  }