genesis-ai-cli 14.9.0 → 14.11.0

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/dist/src/index.js CHANGED
@@ -1438,6 +1438,65 @@ async function cmdAutonomous(subcommand, options) {
1438
1438
  console.log(c(`Unknown autonomous subcommand: ${subcommand}`, 'red'));
1439
1439
  console.log('Use: status, init, balance, approvals, approve, stop, agent, run');
1440
1440
  }
1441
+ /**
1442
+ * v14.10: Observability stats command
1443
+ */
1444
+ async function cmdStats(subcommand, options) {
1445
+ const { getMCPMetrics } = await import('./mcp/index.js');
1446
+ const { getMetricsRegistry } = await import('./observability/metrics.js');
1447
+ console.log(c('\n=== GENESIS OBSERVABILITY STATS (v14.11) ===\n', 'bold'));
1448
+ if (!subcommand || subcommand === 'mcp') {
1449
+ const { mcpCallsTotal, mcpLatency, mcpConnectionsActive, mcpErrorsTotal, registry } = getMCPMetrics();
1450
+ console.log(c('MCP Metrics:', 'cyan'));
1451
+ console.log(` Active connections: ${mcpConnectionsActive.get()}`);
1452
+ console.log();
1453
+ // Display Prometheus format
1454
+ console.log(c('Prometheus Export:', 'dim'));
1455
+ console.log(registry.toPrometheus());
1456
+ return;
1457
+ }
1458
+ if (subcommand === 'prometheus') {
1459
+ const registry = getMetricsRegistry('genesis');
1460
+ console.log(registry.toPrometheus());
1461
+ return;
1462
+ }
1463
+ if (subcommand === 'json') {
1464
+ const { mcpCallsTotal, mcpLatency, mcpConnectionsActive, mcpErrorsTotal } = getMCPMetrics();
1465
+ console.log(JSON.stringify({
1466
+ mcp: {
1467
+ activeConnections: mcpConnectionsActive.get(),
1468
+ },
1469
+ timestamp: new Date().toISOString(),
1470
+ }, null, 2));
1471
+ return;
1472
+ }
1473
+ if (subcommand === 'alert') {
1474
+ // Test Slack alerts
1475
+ const { getAlerter } = await import('./observability/alerting.js');
1476
+ const alerter = getAlerter();
1477
+ const slackUrl = process.env.SLACK_WEBHOOK_URL;
1478
+ if (!slackUrl) {
1479
+ console.log(c('Slack not configured.', 'yellow'));
1480
+ console.log('Set SLACK_WEBHOOK_URL in your .env file to enable alerts.');
1481
+ return;
1482
+ }
1483
+ console.log(c('Sending test alert to Slack...', 'cyan'));
1484
+ const sent = await alerter.info('Genesis Test Alert', 'This is a test alert from Genesis observability system.', { labels: { test: 'true', version: '14.10' } });
1485
+ if (sent) {
1486
+ console.log(c('Test alert sent successfully!', 'green'));
1487
+ }
1488
+ else {
1489
+ console.log(c('Failed to send test alert.', 'red'));
1490
+ }
1491
+ return;
1492
+ }
1493
+ console.log(c('Usage:', 'yellow'));
1494
+ console.log(' genesis stats - Show MCP stats');
1495
+ console.log(' genesis stats mcp - Show MCP stats');
1496
+ console.log(' genesis stats prometheus - Prometheus format');
1497
+ console.log(' genesis stats json - JSON format');
1498
+ console.log(' genesis stats alert - Test Slack alert');
1499
+ }
1441
1500
  async function cmdHardware() {
1442
1501
  console.log(c('\n=== HARDWARE PROFILE ===\n', 'bold'));
1443
1502
  const hw = (0, index_js_1.detectHardware)();
@@ -1580,6 +1639,10 @@ ${c('Commands:', 'bold')}
1580
1639
  ${c('install', 'green')} Install/update Genesis globally (npm)
1581
1640
  ${c('status', 'green')} Show MCP servers status
1582
1641
  ${c('hardware', 'green')} Show hardware profile & router config
1642
+ ${c('stats', 'green')} [subcommand] v14.10: Observability metrics
1643
+ mcp MCP call stats (default)
1644
+ prometheus Prometheus format
1645
+ json JSON format
1583
1646
  ${c('help', 'green')} Show this help
1584
1647
 
1585
1648
  ${c('MCP Servers (13):', 'bold')}
@@ -2046,6 +2109,10 @@ async function cmdInstall(options) {
2046
2109
  // Main
2047
2110
  // ============================================================================
2048
2111
  async function main() {
2112
+ // v14.11: Install graceful shutdown handlers
2113
+ const { installShutdownHandlers, registerDefaultHandlers } = await import('./lifecycle/shutdown.js');
2114
+ installShutdownHandlers();
2115
+ registerDefaultHandlers();
2049
2116
  const args = process.argv.slice(2);
2050
2117
  const command = args[0];
2051
2118
  if (!command || command === 'help' || command === '--help' || command === '-h') {
@@ -2203,6 +2270,10 @@ async function main() {
2203
2270
  // v14.7: Autonomous bounty hunting
2204
2271
  await cmdBounty(positional, options);
2205
2272
  break;
2273
+ case 'stats':
2274
+ // v14.10: Observability stats
2275
+ await cmdStats(positional, options);
2276
+ break;
2206
2277
  default:
2207
2278
  console.error(c(`Unknown command: ${command}`, 'red'));
2208
2279
  console.log('Use "genesis help" for usage information');
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Graceful Shutdown Handler (v14.11)
3
+ *
4
+ * Ensures clean shutdown on SIGTERM/SIGINT:
5
+ * - Completes in-flight requests
6
+ * - Closes MCP connections
7
+ * - Flushes logs and metrics
8
+ * - Stops rate limiters
9
+ */
10
+ type ShutdownHandler = () => Promise<void> | void;
11
+ /**
12
+ * Register a shutdown handler
13
+ */
14
+ export declare function onShutdown(handler: ShutdownHandler): void;
15
+ /**
16
+ * Set shutdown timeout
17
+ */
18
+ export declare function setShutdownTimeout(ms: number): void;
19
+ /**
20
+ * Check if shutdown is in progress
21
+ */
22
+ export declare function isShutdownInProgress(): boolean;
23
+ /**
24
+ * Install signal handlers (call once at startup)
25
+ */
26
+ export declare function installShutdownHandlers(): void;
27
+ /**
28
+ * Register default Genesis shutdown handlers
29
+ */
30
+ export declare function registerDefaultHandlers(): void;
31
+ export {};
@@ -0,0 +1,129 @@
1
+ "use strict";
2
+ /**
3
+ * Graceful Shutdown Handler (v14.11)
4
+ *
5
+ * Ensures clean shutdown on SIGTERM/SIGINT:
6
+ * - Completes in-flight requests
7
+ * - Closes MCP connections
8
+ * - Flushes logs and metrics
9
+ * - Stops rate limiters
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.onShutdown = onShutdown;
13
+ exports.setShutdownTimeout = setShutdownTimeout;
14
+ exports.isShutdownInProgress = isShutdownInProgress;
15
+ exports.installShutdownHandlers = installShutdownHandlers;
16
+ exports.registerDefaultHandlers = registerDefaultHandlers;
17
+ const shutdownHandlers = [];
18
+ let isShuttingDown = false;
19
+ let shutdownTimeout = 30000; // 30s max shutdown time
20
+ /**
21
+ * Register a shutdown handler
22
+ */
23
+ function onShutdown(handler) {
24
+ shutdownHandlers.push(handler);
25
+ }
26
+ /**
27
+ * Set shutdown timeout
28
+ */
29
+ function setShutdownTimeout(ms) {
30
+ shutdownTimeout = ms;
31
+ }
32
+ /**
33
+ * Check if shutdown is in progress
34
+ */
35
+ function isShutdownInProgress() {
36
+ return isShuttingDown;
37
+ }
38
+ /**
39
+ * Execute graceful shutdown
40
+ */
41
+ async function executeShutdown(signal) {
42
+ if (isShuttingDown) {
43
+ console.log(`[Shutdown] Already shutting down, ignoring ${signal}`);
44
+ return;
45
+ }
46
+ isShuttingDown = true;
47
+ console.log(`\n[Shutdown] Received ${signal}, starting graceful shutdown...`);
48
+ // Set a hard timeout
49
+ const forceExitTimer = setTimeout(() => {
50
+ console.error('[Shutdown] Timeout exceeded, forcing exit');
51
+ process.exit(1);
52
+ }, shutdownTimeout);
53
+ try {
54
+ // Run all handlers in reverse order (LIFO)
55
+ for (let i = shutdownHandlers.length - 1; i >= 0; i--) {
56
+ try {
57
+ await Promise.resolve(shutdownHandlers[i]());
58
+ }
59
+ catch (error) {
60
+ console.error(`[Shutdown] Handler ${i} failed:`, error);
61
+ }
62
+ }
63
+ console.log('[Shutdown] Graceful shutdown complete');
64
+ clearTimeout(forceExitTimer);
65
+ process.exit(0);
66
+ }
67
+ catch (error) {
68
+ console.error('[Shutdown] Error during shutdown:', error);
69
+ clearTimeout(forceExitTimer);
70
+ process.exit(1);
71
+ }
72
+ }
73
+ /**
74
+ * Install signal handlers (call once at startup)
75
+ */
76
+ function installShutdownHandlers() {
77
+ process.on('SIGTERM', () => executeShutdown('SIGTERM'));
78
+ process.on('SIGINT', () => executeShutdown('SIGINT'));
79
+ // Handle uncaught errors gracefully
80
+ process.on('uncaughtException', (error) => {
81
+ console.error('[Fatal] Uncaught exception:', error);
82
+ executeShutdown('uncaughtException').catch(() => process.exit(1));
83
+ });
84
+ process.on('unhandledRejection', (reason) => {
85
+ console.error('[Fatal] Unhandled rejection:', reason);
86
+ // Don't exit on unhandled rejection, just log it
87
+ });
88
+ }
89
+ /**
90
+ * Register default Genesis shutdown handlers
91
+ */
92
+ function registerDefaultHandlers() {
93
+ // Close MCP connections
94
+ onShutdown(async () => {
95
+ try {
96
+ const { getMCPClient } = await import('../mcp/index.js');
97
+ const client = getMCPClient();
98
+ await client.close();
99
+ console.log('[Shutdown] MCP connections closed');
100
+ }
101
+ catch {
102
+ // MCP module may not be loaded
103
+ }
104
+ });
105
+ // Stop rate limiters
106
+ onShutdown(async () => {
107
+ try {
108
+ const { resetRateLimiter } = await import('./rate-limiter.js');
109
+ resetRateLimiter();
110
+ console.log('[Shutdown] Rate limiters stopped');
111
+ }
112
+ catch {
113
+ // Rate limiter may not be loaded
114
+ }
115
+ });
116
+ // Flush alerter
117
+ onShutdown(async () => {
118
+ try {
119
+ const { getAlerter } = await import('../observability/alerting.js');
120
+ const alerter = getAlerter();
121
+ await alerter.flush();
122
+ alerter.stop();
123
+ console.log('[Shutdown] Alerts flushed');
124
+ }
125
+ catch {
126
+ // Alerter may not be loaded
127
+ }
128
+ });
129
+ }
@@ -30,6 +30,14 @@ export * from './parallel-executor.js';
30
30
  export * from './transformers.js';
31
31
  export * from './client-manager.js';
32
32
  import { MCPServerName } from '../types.js';
33
+ import { Counter, Histogram } from '../observability/metrics.js';
34
+ export declare function getMCPMetrics(): {
35
+ mcpCallsTotal: Counter;
36
+ mcpLatency: Histogram;
37
+ mcpConnectionsActive: import("../observability/metrics.js").Gauge;
38
+ mcpErrorsTotal: Counter;
39
+ registry: import("../observability/metrics.js").MetricsRegistry;
40
+ };
33
41
  export type MCPMode = 'real' | 'simulated' | 'hybrid';
34
42
  export interface MCPCallOptions {
35
43
  timeout?: number;
@@ -38,6 +38,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
38
38
  };
39
39
  Object.defineProperty(exports, "__esModule", { value: true });
40
40
  exports.MCP_SERVER_REGISTRY = exports.mcpClient = void 0;
41
+ exports.getMCPMetrics = getMCPMetrics;
41
42
  exports.getMCPClient = getMCPClient;
42
43
  exports.resetMCPClient = resetMCPClient;
43
44
  exports.isSimulatedMode = isSimulatedMode;
@@ -57,6 +58,113 @@ __exportStar(require("./client-manager.js"), exports);
57
58
  const index_js_1 = require("@modelcontextprotocol/sdk/client/index.js");
58
59
  const stdio_js_1 = require("@modelcontextprotocol/sdk/client/stdio.js");
59
60
  const crypto_1 = require("crypto");
61
+ const metrics_js_1 = require("../observability/metrics.js");
62
+ const alerting_js_1 = require("../observability/alerting.js");
63
+ const rate_limiter_js_1 = require("../lifecycle/rate-limiter.js");
64
+ // ============================================================================
65
+ // MCP Rate Limiters (v14.11 - Hardening)
66
+ // Per-provider rate limits to respect API quotas
67
+ // ============================================================================
68
+ const PROVIDER_RATE_LIMITS = {
69
+ // OpenAI: 10,000 RPM (tier 2) = 166.67/sec
70
+ 'openai': { maxTokens: 500, refillRate: 166 },
71
+ // Anthropic: 4,000 RPM = 66.67/sec
72
+ 'anthropic': { maxTokens: 200, refillRate: 66 },
73
+ // GitHub: 5,000/hour = 1.39/sec
74
+ 'github': { maxTokens: 50, refillRate: 1.4 },
75
+ // arXiv: 3/second
76
+ 'arxiv': { maxTokens: 10, refillRate: 3 },
77
+ // Semantic Scholar: 100/second (generous)
78
+ 'semantic-scholar': { maxTokens: 100, refillRate: 100 },
79
+ // Brave Search: 15/second
80
+ 'brave-search': { maxTokens: 30, refillRate: 15 },
81
+ // Default for others: 10/second
82
+ 'default': { maxTokens: 50, refillRate: 10 },
83
+ };
84
+ const providerRateLimiters = new Map();
85
+ function getProviderRateLimiter(server) {
86
+ if (!providerRateLimiters.has(server)) {
87
+ const config = PROVIDER_RATE_LIMITS[server] || PROVIDER_RATE_LIMITS['default'];
88
+ providerRateLimiters.set(server, new rate_limiter_js_1.RateLimiter(config));
89
+ }
90
+ return providerRateLimiters.get(server);
91
+ }
92
+ // ============================================================================
93
+ // MCP Metrics (v14.10 - Observability)
94
+ // ============================================================================
95
+ const metricsRegistry = (0, metrics_js_1.getMetricsRegistry)('genesis');
96
+ const mcpCallsTotal = metricsRegistry.counter({
97
+ name: 'mcp_calls_total',
98
+ help: 'Total MCP tool calls',
99
+ labels: ['server', 'tool', 'status'],
100
+ });
101
+ const mcpLatency = metricsRegistry.histogram({
102
+ name: 'mcp_latency_seconds',
103
+ help: 'MCP call latency in seconds',
104
+ labels: ['server', 'tool'],
105
+ buckets: [0.1, 0.5, 1, 2, 5, 10, 30, 60],
106
+ });
107
+ const mcpConnectionsActive = metricsRegistry.gauge({
108
+ name: 'mcp_connections_active',
109
+ help: 'Active MCP server connections',
110
+ });
111
+ const mcpErrorsTotal = metricsRegistry.counter({
112
+ name: 'mcp_errors_total',
113
+ help: 'Total MCP errors by type',
114
+ labels: ['server', 'error_type'],
115
+ });
116
+ // Export for CLI stats
117
+ function getMCPMetrics() {
118
+ return { mcpCallsTotal, mcpLatency, mcpConnectionsActive, mcpErrorsTotal, registry: metricsRegistry };
119
+ }
120
+ // ============================================================================
121
+ // Security: Secret Sanitization (v14.11)
122
+ // ============================================================================
123
+ const SECRET_PATTERNS = [
124
+ /sk-[a-zA-Z0-9-_]{20,}/g, // OpenAI keys
125
+ /sk-ant-[a-zA-Z0-9-_]{20,}/g, // Anthropic keys
126
+ /ghp_[a-zA-Z0-9]{36}/g, // GitHub PAT
127
+ /gho_[a-zA-Z0-9]{36}/g, // GitHub OAuth
128
+ /[a-zA-Z0-9]{32,}/g, // Generic API keys (if looks like key)
129
+ /Bearer\s+[a-zA-Z0-9-_.]+/gi, // Bearer tokens
130
+ /password[=:]\s*["']?[^"'\s]+/gi, // Passwords in strings
131
+ /api[_-]?key[=:]\s*["']?[^"'\s]+/gi, // API keys in strings
132
+ ];
133
+ function sanitizeSecrets(input) {
134
+ let sanitized = input;
135
+ for (const pattern of SECRET_PATTERNS) {
136
+ sanitized = sanitized.replace(pattern, (match) => {
137
+ // Keep first 4 chars for identification, redact rest
138
+ if (match.length > 8) {
139
+ return match.slice(0, 4) + '***REDACTED***';
140
+ }
141
+ return '***REDACTED***';
142
+ });
143
+ }
144
+ return sanitized;
145
+ }
146
+ function sanitizeObject(obj) {
147
+ const sanitized = {};
148
+ for (const [key, value] of Object.entries(obj)) {
149
+ const lowerKey = key.toLowerCase();
150
+ // Redact known sensitive keys
151
+ if (lowerKey.includes('password') || lowerKey.includes('secret') ||
152
+ lowerKey.includes('token') || lowerKey.includes('key') ||
153
+ lowerKey.includes('auth') || lowerKey.includes('credential')) {
154
+ sanitized[key] = '***REDACTED***';
155
+ }
156
+ else if (typeof value === 'string') {
157
+ sanitized[key] = sanitizeSecrets(value);
158
+ }
159
+ else if (typeof value === 'object' && value !== null) {
160
+ sanitized[key] = sanitizeObject(value);
161
+ }
162
+ else {
163
+ sanitized[key] = value;
164
+ }
165
+ }
166
+ return sanitized;
167
+ }
60
168
  /**
61
169
  * Registry of MCP servers and how to spawn them.
62
170
  * These are the 18 MCP servers Genesis uses.
@@ -470,6 +578,8 @@ class MCPConnectionManager {
470
578
  if (this.logCalls) {
471
579
  console.log(`[MCP] Connected to ${server}`);
472
580
  }
581
+ // v14.10: Track active connections
582
+ mcpConnectionsActive.inc();
473
583
  return {
474
584
  client,
475
585
  transport,
@@ -480,37 +590,153 @@ class MCPConnectionManager {
480
590
  /**
481
591
  * Call a tool on an MCP server
482
592
  * v7.18: Added timeout wrapper for faster failure
593
+ * v14.10: Added structured logging with latency tracking
483
594
  */
484
595
  async callTool(server, tool, args) {
596
+ const startTime = performance.now();
597
+ const callId = `${server}.${tool}.${Date.now().toString(36)}`;
598
+ // v14.11: Rate limiting per provider
599
+ const rateLimiter = getProviderRateLimiter(server);
600
+ const rateCheck = rateLimiter.check(server);
601
+ if (!rateCheck.allowed) {
602
+ const waitMs = rateCheck.retryAfterMs || 1000;
603
+ if (this.logCalls) {
604
+ console.log(JSON.stringify({
605
+ level: 'warn',
606
+ time: Date.now(),
607
+ msg: 'Rate limited, waiting',
608
+ server,
609
+ tool,
610
+ waitMs,
611
+ }));
612
+ }
613
+ // Wait and retry
614
+ await new Promise(resolve => setTimeout(resolve, waitMs));
615
+ }
485
616
  const connection = await this.getConnection(server);
486
617
  if (this.logCalls) {
487
- console.log(`[MCP] ${server}.${tool}(${JSON.stringify(args).slice(0, 100)}...)`);
618
+ // v14.11: Sanitize args to prevent secret leakage
619
+ const safeArgs = sanitizeObject(args);
620
+ console.log(JSON.stringify({
621
+ level: 'debug',
622
+ time: Date.now(),
623
+ msg: 'MCP call started',
624
+ callId,
625
+ server,
626
+ tool,
627
+ argsPreview: sanitizeSecrets(JSON.stringify(safeArgs).slice(0, 100)),
628
+ }));
488
629
  }
489
630
  // v7.18: Wrap call in timeout for faster failure (15s default, 30s/120s for heavy ops)
490
631
  const isHeavyOp = ['firecrawl_crawl', 'parse_paper_content', 'web_search'].includes(tool);
491
632
  const isImageGen = server === 'huggingface' || server === 'stability-ai' || tool.includes('generate') || tool.includes('infer');
492
633
  const callTimeout = isImageGen ? 120000 : isHeavyOp ? 30000 : 15000; // 120s for image gen (HF cold start)
493
- const result = await Promise.race([
494
- connection.client.callTool({
495
- name: tool,
496
- arguments: args,
497
- }),
498
- new Promise((_, reject) => setTimeout(() => reject(new Error(`MCP call to ${server}.${tool} timed out after ${callTimeout}ms`)), callTimeout)),
499
- ]);
500
- // Parse result content
501
- const content = result.content;
502
- if (content && content.length > 0) {
503
- const first = content[0];
504
- if (first.type === 'text' && typeof first.text === 'string') {
505
- try {
506
- return JSON.parse(first.text);
634
+ // v14.11: Retry with exponential backoff (3 attempts)
635
+ const maxRetries = 3;
636
+ let lastError = null;
637
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
638
+ try {
639
+ const result = await Promise.race([
640
+ connection.client.callTool({
641
+ name: tool,
642
+ arguments: args,
643
+ }),
644
+ new Promise((_, reject) => setTimeout(() => reject(new Error(`MCP call to ${server}.${tool} timed out after ${callTimeout}ms`)), callTimeout)),
645
+ ]);
646
+ const latencyMs = Math.round(performance.now() - startTime);
647
+ // v14.10: Track metrics
648
+ mcpCallsTotal.inc({ server, tool, status: 'success' });
649
+ mcpLatency.observe(latencyMs / 1000, { server, tool });
650
+ if (this.logCalls) {
651
+ console.log(JSON.stringify({
652
+ level: 'info',
653
+ time: Date.now(),
654
+ msg: 'MCP call completed',
655
+ callId,
656
+ server,
657
+ tool,
658
+ latencyMs,
659
+ success: true,
660
+ attempt: attempt + 1,
661
+ }));
507
662
  }
508
- catch {
509
- return first.text;
663
+ // Parse result content
664
+ const content = result.content;
665
+ if (content && content.length > 0) {
666
+ const first = content[0];
667
+ if (first.type === 'text' && typeof first.text === 'string') {
668
+ try {
669
+ return JSON.parse(first.text);
670
+ }
671
+ catch {
672
+ return first.text;
673
+ }
674
+ }
510
675
  }
676
+ return result;
677
+ }
678
+ catch (error) {
679
+ lastError = error;
680
+ const errorMsg = lastError.message || '';
681
+ // v14.11: Only retry on transient errors (timeout, rate limit, server error)
682
+ const isRetryable = errorMsg.includes('timed out') ||
683
+ errorMsg.includes('rate limit') ||
684
+ errorMsg.includes('429') ||
685
+ errorMsg.includes('503') ||
686
+ errorMsg.includes('ECONNRESET');
687
+ if (!isRetryable || attempt === maxRetries - 1) {
688
+ break; // Don't retry on permanent errors or last attempt
689
+ }
690
+ // Exponential backoff with jitter: 1s, 2s, 4s
691
+ const baseDelay = Math.pow(2, attempt) * 1000;
692
+ const jitter = Math.random() * baseDelay * 0.3;
693
+ const delay = baseDelay + jitter;
694
+ if (this.logCalls) {
695
+ console.log(JSON.stringify({
696
+ level: 'warn',
697
+ time: Date.now(),
698
+ msg: 'MCP call failed, retrying',
699
+ callId,
700
+ server,
701
+ tool,
702
+ attempt: attempt + 1,
703
+ nextRetryMs: Math.round(delay),
704
+ error: errorMsg,
705
+ }));
706
+ }
707
+ await new Promise(resolve => setTimeout(resolve, delay));
511
708
  }
512
709
  }
513
- return result;
710
+ // All retries failed
711
+ const latencyMs = Math.round(performance.now() - startTime);
712
+ const errorType = lastError?.message?.includes('timed out') ? 'timeout' : 'error';
713
+ // v14.10: Track error metrics
714
+ mcpCallsTotal.inc({ server, tool, status: 'error' });
715
+ mcpLatency.observe(latencyMs / 1000, { server, tool });
716
+ mcpErrorsTotal.inc({ server, error_type: errorType });
717
+ // v14.10: Send alert on MCP failures (if Slack configured)
718
+ try {
719
+ const alerter = (0, alerting_js_1.getAlerter)();
720
+ await alerter.warning(`MCP call failed: ${server}.${tool}`, lastError?.message || 'Unknown error', { labels: { server, tool, errorType, latencyMs: String(latencyMs), retries: String(maxRetries) } });
721
+ }
722
+ catch {
723
+ // Don't fail main operation if alerting fails
724
+ }
725
+ if (this.logCalls) {
726
+ console.log(JSON.stringify({
727
+ level: 'error',
728
+ time: Date.now(),
729
+ msg: 'MCP call failed after retries',
730
+ callId,
731
+ server,
732
+ tool,
733
+ latencyMs,
734
+ success: false,
735
+ retries: maxRetries,
736
+ error: lastError?.message,
737
+ }));
738
+ }
739
+ throw lastError || new Error(`MCP call to ${server}.${tool} failed`);
514
740
  }
515
741
  /**
516
742
  * List available tools on an MCP server (names only)
@@ -558,6 +784,8 @@ class MCPConnectionManager {
558
784
  }
559
785
  connection.connected = false;
560
786
  this.connections.delete(server);
787
+ // v14.10: Track active connections
788
+ mcpConnectionsActive.dec();
561
789
  }
562
790
  }
563
791
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genesis-ai-cli",
3
- "version": "14.9.0",
3
+ "version": "14.11.0",
4
4
  "description": "Fully Autonomous AI System with RSI (Recursive Self-Improvement) - Self-funding, Self-deploying, Production Memory, A2A Protocol & Governance",
5
5
  "main": "dist/src/index.js",
6
6
  "types": "dist/src/index.d.ts",