@vfarcic/dot-ai 0.90.0 → 0.91.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.
@@ -0,0 +1,1158 @@
1
+ "use strict";
2
+ /**
3
+ * Remediate Tool - AI-powered Kubernetes issue analysis and remediation
4
+ */
5
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ var desc = Object.getOwnPropertyDescriptor(m, k);
8
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
9
+ desc = { enumerable: true, get: function() { return m[k]; } };
10
+ }
11
+ Object.defineProperty(o, k2, desc);
12
+ }) : (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ o[k2] = m[k];
15
+ }));
16
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
17
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
18
+ }) : function(o, v) {
19
+ o["default"] = v;
20
+ });
21
+ var __importStar = (this && this.__importStar) || (function () {
22
+ var ownKeys = function(o) {
23
+ ownKeys = Object.getOwnPropertyNames || function (o) {
24
+ var ar = [];
25
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
26
+ return ar;
27
+ };
28
+ return ownKeys(o);
29
+ };
30
+ return function (mod) {
31
+ if (mod && mod.__esModule) return mod;
32
+ var result = {};
33
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
34
+ __setModuleDefault(result, mod);
35
+ return result;
36
+ };
37
+ })();
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.REMEDIATE_TOOL_INPUT_SCHEMA = exports.SAFE_OPERATIONS = exports.REMEDIATE_TOOL_DESCRIPTION = exports.REMEDIATE_TOOL_NAME = void 0;
40
+ exports.parseAIFinalAnalysis = parseAIFinalAnalysis;
41
+ exports.parseAIResponse = parseAIResponse;
42
+ exports.handleRemediateTool = handleRemediateTool;
43
+ const zod_1 = require("zod");
44
+ const error_handling_1 = require("../core/error-handling");
45
+ const claude_1 = require("../core/claude");
46
+ const session_utils_1 = require("../core/session-utils");
47
+ const kubernetes_utils_1 = require("../core/kubernetes-utils");
48
+ const fs = __importStar(require("fs"));
49
+ const path = __importStar(require("path"));
50
+ const crypto = __importStar(require("crypto"));
51
+ // Tool metadata for direct MCP registration
52
+ exports.REMEDIATE_TOOL_NAME = 'remediate';
53
+ exports.REMEDIATE_TOOL_DESCRIPTION = 'AI-powered Kubernetes issue analysis that provides root cause identification and actionable remediation steps. Unlike basic kubectl commands, this tool performs multi-step investigation, correlates cluster data, and generates intelligent solutions. Use when users want to understand WHY something is broken, not just see raw status. Ideal for: troubleshooting failures, diagnosing performance issues, analyzing pod problems, investigating networking/storage issues, or any "what\'s wrong" questions.';
54
+ // Safety: Whitelist of allowed read-only operations
55
+ exports.SAFE_OPERATIONS = ['get', 'describe', 'logs', 'events', 'top', 'explain'];
56
+ /**
57
+ * Check if command arguments contain dry-run flag (making any operation safe)
58
+ */
59
+ function hasDryRunFlag(args) {
60
+ if (!args)
61
+ return false;
62
+ return args.some(arg => arg === '--dry-run=client' ||
63
+ arg === '--dry-run=server' ||
64
+ arg === '--dry-run' ||
65
+ arg.startsWith('--dry-run='));
66
+ }
67
+ // Zod schema for MCP registration
68
+ exports.REMEDIATE_TOOL_INPUT_SCHEMA = {
69
+ issue: zod_1.z.string().min(1).max(2000).describe('Issue description that needs to be analyzed and remediated').optional(),
70
+ context: zod_1.z.object({
71
+ event: zod_1.z.any().optional().describe('Kubernetes event object'),
72
+ logs: zod_1.z.array(zod_1.z.string()).optional().describe('Relevant log entries'),
73
+ metrics: zod_1.z.any().optional().describe('Relevant metrics data'),
74
+ podSpec: zod_1.z.any().optional().describe('Pod specification if relevant'),
75
+ relatedEvents: zod_1.z.array(zod_1.z.any()).optional().describe('Related Kubernetes events')
76
+ }).optional().describe('Optional initial context to help with analysis'),
77
+ mode: zod_1.z.enum(['manual', 'automatic']).optional().default('manual').describe('Execution mode: manual requires user approval, automatic executes based on thresholds'),
78
+ confidenceThreshold: zod_1.z.number().min(0).max(1).optional().default(0.8).describe('For automatic mode: minimum confidence required for execution (default: 0.8)'),
79
+ maxRiskLevel: zod_1.z.enum(['low', 'medium', 'high']).optional().default('low').describe('For automatic mode: maximum risk level allowed for execution (default: low)'),
80
+ executeChoice: zod_1.z.number().min(1).max(2).optional().describe('Execute a previously generated choice (1=Execute via MCP, 2=Execute via agent)'),
81
+ sessionId: zod_1.z.string().optional().describe('Session ID from previous remediate call when executing a choice'),
82
+ executedCommands: zod_1.z.array(zod_1.z.string()).optional().describe('Commands that were executed to remediate the issue')
83
+ };
84
+ /**
85
+ * Generate unique session ID for investigation tracking
86
+ */
87
+ function generateSessionId() {
88
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '').slice(0, 15);
89
+ const random = crypto.randomBytes(8).toString('hex');
90
+ return `rem_${timestamp}_${random}`;
91
+ }
92
+ /**
93
+ * Write session file to session directory
94
+ */
95
+ function writeSessionFile(sessionDir, sessionId, sessionData) {
96
+ const sessionPath = path.join(sessionDir, `${sessionId}.json`);
97
+ const sessionJson = JSON.stringify(sessionData, null, 2);
98
+ fs.writeFileSync(sessionPath, sessionJson, 'utf8');
99
+ }
100
+ /**
101
+ * Read session file from session directory
102
+ */
103
+ function readSessionFile(sessionDir, sessionId) {
104
+ const sessionPath = path.join(sessionDir, `${sessionId}.json`);
105
+ if (!fs.existsSync(sessionPath)) {
106
+ throw new Error(`Session file not found: ${sessionId}`);
107
+ }
108
+ const sessionJson = fs.readFileSync(sessionPath, 'utf8');
109
+ return JSON.parse(sessionJson);
110
+ }
111
+ /**
112
+ * Update existing session file
113
+ */
114
+ function updateSessionFile(sessionDir, sessionId, updates) {
115
+ const session = readSessionFile(sessionDir, sessionId);
116
+ const updatedSession = {
117
+ ...session,
118
+ ...updates,
119
+ updated: new Date()
120
+ };
121
+ writeSessionFile(sessionDir, sessionId, updatedSession);
122
+ }
123
+ /**
124
+ * AI-driven investigation loop - iteratively gather data and analyze until complete
125
+ */
126
+ async function conductInvestigation(session, sessionDir, claudeIntegration, logger, requestId) {
127
+ const maxIterations = 20; // Allow more comprehensive investigations
128
+ let currentIteration = session.iterations.length;
129
+ logger.info('Starting AI investigation loop', {
130
+ requestId,
131
+ sessionId: session.sessionId,
132
+ currentIterations: currentIteration
133
+ });
134
+ while (currentIteration < maxIterations) {
135
+ logger.debug(`Starting investigation iteration ${currentIteration + 1}`, { requestId, sessionId: session.sessionId });
136
+ try {
137
+ // Get AI analysis with investigation prompts
138
+ const aiAnalysis = await analyzeCurrentState(session, claudeIntegration, logger, requestId);
139
+ // Parse AI response for data requests and completion status
140
+ const { dataRequests, isComplete, needsMoreSpecificInfo, parsedResponse } = parseAIResponse(aiAnalysis);
141
+ // Handle early termination when issue description is too vague
142
+ if (needsMoreSpecificInfo) {
143
+ logger.info('Investigation terminated: needs more specific information', {
144
+ requestId,
145
+ sessionId: session.sessionId,
146
+ iteration: currentIteration + 1
147
+ });
148
+ throw error_handling_1.ErrorHandler.createError(error_handling_1.ErrorCategory.VALIDATION, error_handling_1.ErrorSeverity.MEDIUM, 'Unable to find relevant resources for the reported issue. Please be more specific about which resource type or component is having problems (e.g., "my sqls.devopstoolkit.live resource named test-db" instead of "my database").', {
149
+ operation: 'investigation_early_termination',
150
+ component: 'RemediateTool',
151
+ input: { sessionId: session.sessionId, issue: session.issue }
152
+ });
153
+ }
154
+ // Gather safe data from Kubernetes using kubectl
155
+ const gatheredData = await gatherSafeData(dataRequests, logger, requestId);
156
+ // Create iteration record
157
+ const iteration = {
158
+ step: currentIteration + 1,
159
+ aiAnalysis,
160
+ dataRequests,
161
+ gatheredData,
162
+ complete: isComplete,
163
+ timestamp: new Date()
164
+ };
165
+ // Store parsed response data if available
166
+ if (parsedResponse) {
167
+ logger.debug('AI investigation analysis', {
168
+ requestId,
169
+ sessionId: session.sessionId,
170
+ confidence: parsedResponse.confidence,
171
+ reasoning: parsedResponse.reasoning,
172
+ dataRequestCount: parsedResponse.dataRequests.length
173
+ });
174
+ }
175
+ // Update session with new iteration
176
+ session.iterations.push(iteration);
177
+ updateSessionFile(sessionDir, session.sessionId, { iterations: session.iterations });
178
+ logger.debug('Investigation iteration completed', {
179
+ requestId,
180
+ sessionId: session.sessionId,
181
+ step: iteration.step,
182
+ dataRequestCount: dataRequests.length,
183
+ complete: iteration.complete
184
+ });
185
+ // Check if analysis is complete
186
+ if (iteration.complete) {
187
+ logger.info('Investigation completed by AI decision', {
188
+ requestId,
189
+ sessionId: session.sessionId,
190
+ totalIterations: iteration.step,
191
+ confidence: parsedResponse?.confidence,
192
+ reasoning: parsedResponse?.reasoning
193
+ });
194
+ break;
195
+ }
196
+ currentIteration++;
197
+ }
198
+ catch (error) {
199
+ logger.error('Investigation iteration failed', error, {
200
+ requestId,
201
+ sessionId: session.sessionId,
202
+ iteration: currentIteration + 1
203
+ });
204
+ // Mark session as failed
205
+ updateSessionFile(sessionDir, session.sessionId, { status: 'failed' });
206
+ throw error_handling_1.ErrorHandler.createError(error_handling_1.ErrorCategory.AI_SERVICE, error_handling_1.ErrorSeverity.HIGH, `Investigation failed at iteration ${currentIteration + 1}: ${error instanceof Error ? error.message : 'Unknown error'}`, {
207
+ operation: 'investigation_loop',
208
+ component: 'RemediateTool',
209
+ input: { sessionId: session.sessionId, iteration: currentIteration + 1 }
210
+ });
211
+ }
212
+ }
213
+ // Generate final analysis
214
+ const finalAnalysis = await generateFinalAnalysis(session, logger, requestId);
215
+ // Update session with final analysis
216
+ updateSessionFile(sessionDir, session.sessionId, {
217
+ finalAnalysis,
218
+ status: 'analysis_complete'
219
+ });
220
+ logger.info('Investigation and analysis completed', {
221
+ requestId,
222
+ sessionId: session.sessionId,
223
+ rootCause: finalAnalysis.analysis.rootCause,
224
+ recommendedActions: finalAnalysis.remediation.actions.length
225
+ });
226
+ return finalAnalysis;
227
+ }
228
+ /**
229
+ * Analyze current state using AI with investigation prompts
230
+ */
231
+ async function analyzeCurrentState(session, claudeIntegration, logger, requestId) {
232
+ logger.debug('Analyzing current state with AI', { requestId, sessionId: session.sessionId });
233
+ try {
234
+ // Load investigation prompt template
235
+ const promptPath = path.join(process.cwd(), 'prompts', 'remediate-investigation.md');
236
+ const promptTemplate = fs.readFileSync(promptPath, 'utf8');
237
+ // Discover cluster API resources for complete visibility - REQUIRED for quality remediation
238
+ let clusterApiResources = '';
239
+ try {
240
+ // Use kubectl api-resources directly - simple and reliable
241
+ clusterApiResources = await (0, kubernetes_utils_1.executeKubectl)(['api-resources']);
242
+ logger.debug('Discovered cluster API resources', {
243
+ requestId,
244
+ sessionId: session.sessionId,
245
+ outputLength: clusterApiResources.length
246
+ });
247
+ }
248
+ catch (error) {
249
+ const errorMessage = `Failed to discover cluster API resources: ${error instanceof Error ? error.message : String(error)}. Complete API visibility is required for quality remediation recommendations.`;
250
+ logger.error('API discovery failed - aborting remediation', error, {
251
+ requestId,
252
+ sessionId: session.sessionId
253
+ });
254
+ throw new Error(errorMessage);
255
+ }
256
+ // Prepare template variables
257
+ const currentIteration = session.iterations.length + 1;
258
+ const maxIterations = 20;
259
+ const initialContextJson = JSON.stringify(session.initialContext, null, 2);
260
+ const previousIterationsJson = JSON.stringify(session.iterations.map(iter => ({
261
+ step: iter.step,
262
+ analysis: iter.aiAnalysis,
263
+ dataRequests: iter.dataRequests,
264
+ gatheredData: iter.gatheredData
265
+ })), null, 2);
266
+ // Replace template variables
267
+ const investigationPrompt = promptTemplate
268
+ .replace('{issue}', session.issue)
269
+ .replace('{initialContext}', initialContextJson)
270
+ .replace('{currentIteration}', currentIteration.toString())
271
+ .replace('{maxIterations}', maxIterations.toString())
272
+ .replace('{previousIterations}', previousIterationsJson)
273
+ .replace('{clusterApiResources}', clusterApiResources);
274
+ logger.debug('Sending investigation prompt to Claude', {
275
+ requestId,
276
+ sessionId: session.sessionId,
277
+ promptLength: investigationPrompt.length,
278
+ iteration: currentIteration
279
+ });
280
+ // Send to Claude AI
281
+ const aiResponse = await claudeIntegration.sendMessage(investigationPrompt);
282
+ logger.debug('Received AI analysis response', {
283
+ requestId,
284
+ sessionId: session.sessionId,
285
+ responseLength: aiResponse.content.length
286
+ });
287
+ return aiResponse.content;
288
+ }
289
+ catch (error) {
290
+ logger.error('Failed to analyze current state with AI', error, { requestId, sessionId: session.sessionId });
291
+ throw error_handling_1.ErrorHandler.createError(error_handling_1.ErrorCategory.AI_SERVICE, error_handling_1.ErrorSeverity.HIGH, `AI analysis failed: ${error instanceof Error ? error.message : 'Unknown error'}`, {
292
+ operation: 'ai_analysis',
293
+ component: 'RemediateTool',
294
+ requestId,
295
+ sessionId: session.sessionId,
296
+ suggestedActions: [
297
+ 'Check ANTHROPIC_API_KEY is set correctly',
298
+ 'Verify prompts/remediate-investigation.md exists',
299
+ 'Check network connectivity to Anthropic API'
300
+ ]
301
+ });
302
+ }
303
+ }
304
+ /**
305
+ * Parse AI final analysis response
306
+ */
307
+ function parseAIFinalAnalysis(aiResponse) {
308
+ try {
309
+ // Try to extract JSON from the response
310
+ const jsonMatch = aiResponse.match(/\{[\s\S]*\}/);
311
+ if (!jsonMatch) {
312
+ throw new Error('No JSON found in AI final analysis response');
313
+ }
314
+ const parsed = JSON.parse(jsonMatch[0]);
315
+ // Validate required fields
316
+ if (!parsed.issueStatus || !parsed.rootCause || !parsed.confidence || !Array.isArray(parsed.factors) || !parsed.remediation) {
317
+ throw new Error('Invalid AI final analysis response structure');
318
+ }
319
+ // Validate issueStatus field
320
+ if (!['active', 'resolved', 'non_existent'].includes(parsed.issueStatus)) {
321
+ throw new Error(`Invalid issue status: ${parsed.issueStatus}. Must be 'active', 'resolved', or 'non_existent'`);
322
+ }
323
+ if (!parsed.remediation.summary || !Array.isArray(parsed.remediation.actions) || !parsed.remediation.risk) {
324
+ throw new Error('Invalid remediation structure in AI final analysis response');
325
+ }
326
+ // Validate each remediation action
327
+ for (const action of parsed.remediation.actions) {
328
+ if (!action.description || !action.risk || !action.rationale) {
329
+ throw new Error('Invalid remediation action structure');
330
+ }
331
+ if (!['low', 'medium', 'high'].includes(action.risk)) {
332
+ throw new Error(`Invalid risk level: ${action.risk}`);
333
+ }
334
+ }
335
+ // Validate overall risk level
336
+ if (!['low', 'medium', 'high'].includes(parsed.remediation.risk)) {
337
+ throw new Error(`Invalid overall risk level: ${parsed.remediation.risk}`);
338
+ }
339
+ // Validate confidence is between 0 and 1
340
+ if (parsed.confidence < 0 || parsed.confidence > 1) {
341
+ throw new Error(`Invalid confidence value: ${parsed.confidence}. Must be between 0 and 1`);
342
+ }
343
+ return parsed;
344
+ }
345
+ catch (error) {
346
+ throw new Error(`Failed to parse AI final analysis response: ${error instanceof Error ? error.message : 'Unknown error'}`);
347
+ }
348
+ }
349
+ /**
350
+ * Parse AI response for data requests and investigation status
351
+ */
352
+ function parseAIResponse(aiResponse) {
353
+ try {
354
+ // Try to extract JSON from the response
355
+ const jsonMatch = aiResponse.match(/\{[\s\S]*\}/);
356
+ if (!jsonMatch) {
357
+ throw new Error('No JSON found in AI response');
358
+ }
359
+ const parsed = JSON.parse(jsonMatch[0]);
360
+ // Validate required fields
361
+ if (typeof parsed.investigationComplete !== 'boolean') {
362
+ throw new Error('Missing or invalid investigationComplete field');
363
+ }
364
+ if (!Array.isArray(parsed.dataRequests)) {
365
+ throw new Error('Missing or invalid dataRequests field');
366
+ }
367
+ // Validate data requests format
368
+ for (const request of parsed.dataRequests) {
369
+ // Check if operation is safe (read-only) or has dry-run flag
370
+ const isDryRun = hasDryRunFlag(request.args);
371
+ const isSafeOperation = exports.SAFE_OPERATIONS.includes(request.type);
372
+ if (!isSafeOperation && !isDryRun) {
373
+ throw new Error(`Invalid data request type: ${request.type}. Allowed: ${exports.SAFE_OPERATIONS.join(', ')} or any operation with --dry-run flag`);
374
+ }
375
+ if (!request.resource || !request.rationale) {
376
+ throw new Error('Data request missing required fields: resource, rationale');
377
+ }
378
+ }
379
+ return {
380
+ dataRequests: parsed.dataRequests,
381
+ isComplete: parsed.investigationComplete,
382
+ needsMoreSpecificInfo: parsed.needsMoreSpecificInfo,
383
+ parsedResponse: parsed
384
+ };
385
+ }
386
+ catch (error) {
387
+ // Fallback: try to extract data requests from text patterns
388
+ console.warn('Failed to parse AI JSON response, using fallback parsing:', error instanceof Error ? error.message : 'Unknown error');
389
+ // Simple fallback - assume investigation needs to continue and no data requests
390
+ return {
391
+ dataRequests: [],
392
+ isComplete: false
393
+ };
394
+ }
395
+ }
396
+ /**
397
+ * Gather safe data from Kubernetes using kubectl
398
+ * Implements resilient error handling - failed requests don't kill the investigation
399
+ */
400
+ async function gatherSafeData(dataRequests, logger, requestId) {
401
+ logger.debug('Gathering safe data from Kubernetes', { requestId, requestCount: dataRequests.length });
402
+ const result = {
403
+ successful: {},
404
+ failed: {},
405
+ summary: {
406
+ total: dataRequests.length,
407
+ successful: 0,
408
+ failed: 0
409
+ }
410
+ };
411
+ // Process each data request independently
412
+ for (let i = 0; i < dataRequests.length; i++) {
413
+ const request = dataRequests[i];
414
+ const dataRequestId = `${requestId}-req-${i}`;
415
+ try {
416
+ // Safety validation - allow read-only operations OR operations with dry-run flag
417
+ const isDryRun = hasDryRunFlag(request.args);
418
+ const isReadOnlyOperation = exports.SAFE_OPERATIONS.includes(request.type);
419
+ if (!isReadOnlyOperation && !isDryRun) {
420
+ const error = `Unsafe operation '${request.type}' - only allowed: ${exports.SAFE_OPERATIONS.join(', ')} or any operation with --dry-run flag`;
421
+ result.failed[dataRequestId] = {
422
+ error,
423
+ command: `kubectl ${request.type} ${request.resource}${request.args ? ' ' + request.args.join(' ') : ''}`,
424
+ suggestion: 'Use read-only operations (get, describe, logs, events, top) or add --dry-run=client to validate commands safely'
425
+ };
426
+ result.summary.failed++;
427
+ logger.warn('Rejected unsafe kubectl operation', { requestId, dataRequestId, operation: request.type, isDryRun });
428
+ continue;
429
+ }
430
+ // Build kubectl command
431
+ const args = [request.type, request.resource];
432
+ if (request.namespace) {
433
+ args.push('-n', request.namespace);
434
+ }
435
+ // Add any additional arguments (like --dry-run=client)
436
+ if (request.args && request.args.length > 0) {
437
+ args.push(...request.args);
438
+ }
439
+ // Add output format for structured data (only for read-only commands that support it)
440
+ if ((request.type === 'get' || request.type === 'events' || request.type === 'top') && !isDryRun) {
441
+ args.push('-o', 'yaml');
442
+ }
443
+ logger.debug('Executing kubectl command', {
444
+ requestId,
445
+ dataRequestId,
446
+ command: `kubectl ${args.join(' ')}`,
447
+ rationale: request.rationale
448
+ });
449
+ // Execute kubectl command
450
+ const output = await (0, kubernetes_utils_1.executeKubectl)(args, { timeout: 30000 });
451
+ // Store successful result
452
+ result.successful[dataRequestId] = {
453
+ request,
454
+ output,
455
+ command: `kubectl ${args.join(' ')}`,
456
+ timestamp: new Date().toISOString()
457
+ };
458
+ result.summary.successful++;
459
+ logger.debug('kubectl command successful', {
460
+ requestId,
461
+ dataRequestId,
462
+ outputLength: output.length
463
+ });
464
+ }
465
+ catch (error) {
466
+ // Store failed result with error details
467
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
468
+ const command = `kubectl ${request.type} ${request.resource}${request.namespace ? ` -n ${request.namespace}` : ''}`;
469
+ result.failed[dataRequestId] = {
470
+ error: errorMessage,
471
+ command,
472
+ suggestion: generateErrorSuggestion(errorMessage)
473
+ };
474
+ result.summary.failed++;
475
+ logger.warn('kubectl command failed', {
476
+ requestId,
477
+ dataRequestId,
478
+ command,
479
+ error: errorMessage,
480
+ rationale: request.rationale
481
+ });
482
+ }
483
+ }
484
+ logger.info('Data gathering completed', {
485
+ requestId,
486
+ successful: result.summary.successful,
487
+ failed: result.summary.failed,
488
+ total: result.summary.total
489
+ });
490
+ return result;
491
+ }
492
+ /**
493
+ * Generate helpful suggestions based on kubectl error messages
494
+ */
495
+ function generateErrorSuggestion(errorMessage) {
496
+ const lowerError = errorMessage.toLowerCase();
497
+ if (lowerError.includes('not found')) {
498
+ return 'Resource may not exist or may be in a different namespace. Try listing available resources first.';
499
+ }
500
+ if (lowerError.includes('forbidden')) {
501
+ return 'Insufficient permissions. Check RBAC configuration for read access to this resource.';
502
+ }
503
+ if (lowerError.includes('namespace') && lowerError.includes('not found')) {
504
+ return 'Namespace does not exist. Try listing available namespaces first.';
505
+ }
506
+ if (lowerError.includes('connection refused') || lowerError.includes('timeout')) {
507
+ return 'Cannot connect to Kubernetes cluster. Verify cluster connectivity and kubectl configuration.';
508
+ }
509
+ return undefined;
510
+ }
511
+ /**
512
+ * Generate final analysis and remediation recommendations using AI
513
+ */
514
+ async function generateFinalAnalysis(session, logger, requestId) {
515
+ logger.debug('Generating final analysis with AI', { requestId, sessionId: session.sessionId });
516
+ try {
517
+ // Initialize Claude integration
518
+ const claudeApiKey = process.env.ANTHROPIC_API_KEY;
519
+ if (!claudeApiKey) {
520
+ throw error_handling_1.ErrorHandler.createError(error_handling_1.ErrorCategory.CONFIGURATION, error_handling_1.ErrorSeverity.HIGH, 'ANTHROPIC_API_KEY environment variable not set for final analysis', {
521
+ operation: 'generateFinalAnalysis',
522
+ component: 'RemediateTool',
523
+ requestId,
524
+ sessionId: session.sessionId
525
+ });
526
+ }
527
+ const claudeIntegration = new claude_1.ClaudeIntegration(claudeApiKey);
528
+ // Load final analysis prompt template
529
+ const promptPath = path.join(process.cwd(), 'prompts', 'remediate-final-analysis.md');
530
+ const promptTemplate = fs.readFileSync(promptPath, 'utf8');
531
+ // Prepare template variables - extract actual data source identifiers
532
+ const dataSources = session.iterations.flatMap(iter => {
533
+ if (iter.gatheredData && iter.gatheredData.successful) {
534
+ return Object.keys(iter.gatheredData.successful);
535
+ }
536
+ return [];
537
+ });
538
+ // Compile complete investigation data for AI analysis
539
+ const completeInvestigationData = session.iterations.map(iter => ({
540
+ iteration: iter.step,
541
+ analysis: iter.aiAnalysis,
542
+ dataGathered: Object.entries(iter.gatheredData).map(([key, value]) => ({
543
+ source: key,
544
+ data: typeof value === 'string' ? value.substring(0, 1000) : JSON.stringify(value).substring(0, 1000)
545
+ }))
546
+ }));
547
+ // Replace template variables
548
+ const finalAnalysisPrompt = promptTemplate
549
+ .replace('{issue}', session.issue)
550
+ .replace('{iterations}', session.iterations.length.toString())
551
+ .replace('{dataSources}', dataSources.join(', '))
552
+ .replace('{completeInvestigationData}', JSON.stringify(completeInvestigationData, null, 2));
553
+ logger.debug('Sending final analysis request to Claude AI', {
554
+ requestId,
555
+ sessionId: session.sessionId,
556
+ promptLength: finalAnalysisPrompt.length
557
+ });
558
+ // Send to Claude AI
559
+ const aiResponse = await claudeIntegration.sendMessage(finalAnalysisPrompt);
560
+ logger.debug('Received AI final analysis response', {
561
+ requestId,
562
+ sessionId: session.sessionId,
563
+ responseLength: aiResponse.content.length
564
+ });
565
+ // Parse AI response
566
+ const finalAnalysis = parseAIFinalAnalysis(aiResponse.content);
567
+ logger.info('Final analysis generated successfully', {
568
+ requestId,
569
+ sessionId: session.sessionId,
570
+ confidence: finalAnalysis.confidence,
571
+ actionCount: finalAnalysis.remediation.actions.length,
572
+ overallRisk: finalAnalysis.remediation.risk
573
+ });
574
+ // Convert data sources to human-readable format
575
+ const humanReadableDataSources = dataSources.length > 0
576
+ ? [`Analyzed ${dataSources.length} data sources from ${session.iterations.length} investigation iterations`]
577
+ : ['cluster-resources', 'pod-status', 'node-capacity'];
578
+ // Handle different issue statuses
579
+ if (finalAnalysis.issueStatus === 'resolved' || finalAnalysis.issueStatus === 'non_existent') {
580
+ // Issue is resolved or doesn't exist - return success status
581
+ const statusMessage = finalAnalysis.issueStatus === 'resolved'
582
+ ? 'Issue has been successfully resolved'
583
+ : 'No issues found - system is healthy';
584
+ return {
585
+ status: 'success',
586
+ analysis: {
587
+ rootCause: finalAnalysis.rootCause,
588
+ confidence: finalAnalysis.confidence,
589
+ factors: finalAnalysis.factors
590
+ },
591
+ remediation: {
592
+ summary: finalAnalysis.remediation.summary,
593
+ actions: finalAnalysis.remediation.actions,
594
+ risk: finalAnalysis.remediation.risk
595
+ },
596
+ validationIntent: finalAnalysis.validationIntent,
597
+ sessionId: session.sessionId,
598
+ investigation: {
599
+ iterations: session.iterations.length,
600
+ dataGathered: humanReadableDataSources
601
+ },
602
+ executed: false,
603
+ mode: session.mode,
604
+ // Success state guidance
605
+ guidance: `✅ ${statusMessage.toUpperCase()}: ${finalAnalysis.remediation.summary}`,
606
+ agentInstructions: `1. Show user that the ${finalAnalysis.issueStatus === 'resolved' ? 'issue has been resolved' : 'no issues were found'}\n2. Display the analysis and confidence level\n3. Explain the current healthy state\n4. No further action required`,
607
+ nextAction: undefined,
608
+ message: `${statusMessage} with ${Math.round(finalAnalysis.confidence * 100)}% confidence.`
609
+ };
610
+ }
611
+ // Issue is active - generate execution options
612
+ const commandsSummary = finalAnalysis.remediation.actions.length === 1
613
+ ? `The following kubectl command will be executed:\n${finalAnalysis.remediation.actions[0].command}`
614
+ : `The following ${finalAnalysis.remediation.actions.length} kubectl commands will be executed:\n${finalAnalysis.remediation.actions.map((action, i) => `${i + 1}. ${action.command}`).join('\n')}`;
615
+ // Generate risk summary
616
+ const highRiskActions = finalAnalysis.remediation.actions.filter(a => a.risk === 'high');
617
+ const mediumRiskActions = finalAnalysis.remediation.actions.filter(a => a.risk === 'medium');
618
+ const riskSummary = [
619
+ ...(highRiskActions.length > 0 ? [`${highRiskActions.length} HIGH RISK actions require careful review`] : []),
620
+ ...(mediumRiskActions.length > 0 ? [`${mediumRiskActions.length} MEDIUM RISK actions should be executed with monitoring`] : []),
621
+ "All actions are designed to be safe kubectl operations (no destructive commands)"
622
+ ].join('. ');
623
+ // Return active issue response with execution choices
624
+ return {
625
+ status: 'awaiting_user_approval',
626
+ analysis: {
627
+ rootCause: finalAnalysis.rootCause,
628
+ confidence: finalAnalysis.confidence,
629
+ factors: finalAnalysis.factors
630
+ },
631
+ remediation: {
632
+ summary: finalAnalysis.remediation.summary,
633
+ actions: finalAnalysis.remediation.actions,
634
+ risk: finalAnalysis.remediation.risk
635
+ },
636
+ validationIntent: finalAnalysis.validationIntent,
637
+ sessionId: session.sessionId,
638
+ investigation: {
639
+ iterations: session.iterations.length,
640
+ dataGathered: humanReadableDataSources
641
+ },
642
+ executed: false,
643
+ mode: session.mode,
644
+ // Active issue guidance
645
+ guidance: `🔴 CRITICAL: Present the kubectl commands to the user and ask them to choose execution method. DO NOT execute commands without user approval.\n\n${commandsSummary}\n\nRisk Assessment: ${riskSummary}`,
646
+ agentInstructions: `1. Show the user the root cause analysis and confidence level\n2. Display the kubectl commands that will be executed\n3. Explain the risk assessment\n4. Present the two execution choices and wait for user selection\n5. When user selects option 1 or 2, call the remediate tool again with: executeChoice: [1 or 2], sessionId: "${session.sessionId}", mode: "${session.mode}"\n6. Do NOT automatically execute any commands until user makes their choice`,
647
+ nextAction: 'remediate',
648
+ message: `AI analysis identified the root cause with ${Math.round(finalAnalysis.confidence * 100)}% confidence. ${finalAnalysis.remediation.actions.length} remediation actions are recommended.`
649
+ };
650
+ }
651
+ catch (error) {
652
+ logger.error('Failed to generate final analysis', error, {
653
+ requestId,
654
+ sessionId: session.sessionId
655
+ });
656
+ throw error_handling_1.ErrorHandler.createError(error_handling_1.ErrorCategory.AI_SERVICE, error_handling_1.ErrorSeverity.HIGH, `Final analysis generation failed: ${error instanceof Error ? error.message : 'Unknown error'}`, {
657
+ operation: 'generateFinalAnalysis',
658
+ component: 'RemediateTool',
659
+ requestId,
660
+ sessionId: session.sessionId,
661
+ suggestedActions: [
662
+ 'Check ANTHROPIC_API_KEY is set correctly',
663
+ 'Verify prompts/remediate-final-analysis.md exists',
664
+ 'Check network connectivity to Anthropic API',
665
+ 'Review AI response format for parsing issues'
666
+ ]
667
+ });
668
+ }
669
+ }
670
+ /**
671
+ * Execute user choice from previous session
672
+ */
673
+ async function executeUserChoice(sessionDir, sessionId, choice, logger, requestId) {
674
+ try {
675
+ // Load previous session
676
+ const session = readSessionFile(sessionDir, sessionId);
677
+ if (!session.finalAnalysis) {
678
+ throw error_handling_1.ErrorHandler.createError(error_handling_1.ErrorCategory.VALIDATION, error_handling_1.ErrorSeverity.HIGH, 'Session does not have final analysis - cannot execute choice', { operation: 'choice_execution', component: 'RemediateTool', sessionId });
679
+ }
680
+ logger.info('Loaded session for choice execution', {
681
+ requestId,
682
+ sessionId,
683
+ choice,
684
+ actionCount: session.finalAnalysis.remediation.actions.length
685
+ });
686
+ // Handle different choices
687
+ switch (choice) {
688
+ case 1: // Execute automatically via MCP
689
+ return await executeRemediationCommands(session, sessionDir, logger, requestId);
690
+ case 2: { // Execute via agent
691
+ // Use validation intent directly from final analysis
692
+ const validationIntent = session.finalAnalysis.validationIntent || 'Check the status of the affected resources to verify the issue has been resolved';
693
+ return {
694
+ content: [
695
+ {
696
+ type: 'text',
697
+ text: JSON.stringify({
698
+ status: 'success',
699
+ sessionId: sessionId,
700
+ message: 'Ready for agent execution',
701
+ remediation: session.finalAnalysis.remediation,
702
+ instructions: {
703
+ nextSteps: [
704
+ 'STEP 1: Execute the kubectl commands shown in the remediation section using your Bash tool',
705
+ 'STEP 2: After successful execution, call the remediation tool with validation using these parameters:',
706
+ `issue: "${validationIntent}"`,
707
+ `executedCommands: [list of commands you executed]`,
708
+ 'STEP 3: The tool will perform fresh validation to confirm the issue is resolved'
709
+ ]
710
+ }
711
+ }, null, 2)
712
+ }
713
+ ]
714
+ };
715
+ }
716
+ default:
717
+ throw error_handling_1.ErrorHandler.createError(error_handling_1.ErrorCategory.VALIDATION, error_handling_1.ErrorSeverity.HIGH, `Invalid choice: ${choice}. Must be 1 or 2`, { operation: 'choice_validation', component: 'RemediateTool' });
718
+ }
719
+ }
720
+ catch (error) {
721
+ logger.error('Choice execution failed', error, { requestId, sessionId, choice });
722
+ if (error instanceof Error && error.message.includes('Session file not found')) {
723
+ throw error_handling_1.ErrorHandler.createError(error_handling_1.ErrorCategory.OPERATION, error_handling_1.ErrorSeverity.HIGH, `Session not found: ${sessionId}. The session may have expired or been deleted.`, { operation: 'session_loading', component: 'RemediateTool' });
724
+ }
725
+ throw error;
726
+ }
727
+ }
728
+ /**
729
+ * Execute remediation commands via kubectl
730
+ */
731
+ async function executeRemediationCommands(session, sessionDir, logger, requestId) {
732
+ const results = [];
733
+ const finalAnalysis = session.finalAnalysis;
734
+ let overallSuccess = true;
735
+ logger.info('Starting remediation command execution', {
736
+ requestId,
737
+ sessionId: session.sessionId,
738
+ commandCount: finalAnalysis.remediation.actions.length
739
+ });
740
+ // Execute each remediation action
741
+ for (let i = 0; i < finalAnalysis.remediation.actions.length; i++) {
742
+ const action = finalAnalysis.remediation.actions[i];
743
+ const actionId = `action_${i + 1}`;
744
+ try {
745
+ logger.info('Executing remediation action', {
746
+ requestId,
747
+ sessionId: session.sessionId,
748
+ actionId,
749
+ command: action.command
750
+ });
751
+ // Execute the command as-is using shell
752
+ const fullCommand = action.command || '';
753
+ const { exec } = require('child_process');
754
+ const { promisify } = require('util');
755
+ const execAsync = promisify(exec);
756
+ const { stdout } = await execAsync(fullCommand);
757
+ const output = stdout;
758
+ results.push({
759
+ action: `${actionId}: ${action.description}`,
760
+ success: true,
761
+ output: output,
762
+ timestamp: new Date()
763
+ });
764
+ logger.info('Remediation action succeeded', {
765
+ requestId,
766
+ sessionId: session.sessionId,
767
+ actionId
768
+ });
769
+ }
770
+ catch (error) {
771
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
772
+ overallSuccess = false;
773
+ results.push({
774
+ action: `${actionId}: ${action.description}`,
775
+ success: false,
776
+ error: errorMessage,
777
+ timestamp: new Date()
778
+ });
779
+ logger.error('Remediation action failed', error, {
780
+ requestId,
781
+ sessionId: session.sessionId,
782
+ actionId,
783
+ command: action.command
784
+ });
785
+ }
786
+ }
787
+ // Run automatic post-execution validation if all commands succeeded
788
+ let validationResult = null;
789
+ if (overallSuccess && finalAnalysis.validationIntent) {
790
+ const validationIntent = finalAnalysis.validationIntent;
791
+ try {
792
+ logger.info('Running post-execution validation', {
793
+ requestId,
794
+ sessionId: session.sessionId,
795
+ validationIntent: validationIntent
796
+ });
797
+ // Run validation by calling main function recursively with validation intent
798
+ const executedCommands = results.map(r => r.action);
799
+ const validationInput = {
800
+ issue: validationIntent,
801
+ sessionDir: sessionDir,
802
+ executedCommands: executedCommands
803
+ };
804
+ // Recursive call to main function for validation
805
+ const validationResponse = await handleRemediateTool(validationInput);
806
+ const validationData = JSON.parse(validationResponse.content[0].text);
807
+ // If validation discovered new issues, enhance with execution context
808
+ if (validationData.status === 'awaiting_user_approval') {
809
+ logger.info('Validation discovered new issues, enhancing response with execution context', {
810
+ requestId,
811
+ sessionId: session.sessionId,
812
+ newIssueConfidence: validationData.analysis?.confidence
813
+ });
814
+ // Enhance validation response with execution context
815
+ validationData.executed = true;
816
+ validationData.results = results;
817
+ validationData.executedCommands = results.map(r => r.action);
818
+ validationData.previousExecution = {
819
+ sessionId: session.sessionId,
820
+ summary: `Previously executed ${results.length} remediation actions`,
821
+ actions: finalAnalysis.remediation.actions
822
+ };
823
+ return {
824
+ content: [
825
+ {
826
+ type: 'text',
827
+ text: JSON.stringify(validationData, null, 2)
828
+ }
829
+ ]
830
+ };
831
+ }
832
+ // Validation confirmed issue is resolved - create success response
833
+ logger.info('Validation confirmed issue is resolved, creating success response', {
834
+ requestId,
835
+ sessionId: session.sessionId,
836
+ validationStatus: validationData.status
837
+ });
838
+ // Create success response with execution context
839
+ const successResponse = {
840
+ status: 'success',
841
+ sessionId: session.sessionId,
842
+ executed: true,
843
+ results: results,
844
+ executedCommands: results.map(r => r.action),
845
+ analysis: validationData.analysis,
846
+ remediation: {
847
+ summary: `Successfully executed ${results.length} remediation actions. ${validationData.remediation.summary}`,
848
+ actions: finalAnalysis.remediation.actions,
849
+ risk: finalAnalysis.remediation.risk
850
+ },
851
+ investigation: validationData.investigation,
852
+ validationIntent: validationData.validationIntent,
853
+ guidance: `✅ REMEDIATION COMPLETE: Issue has been successfully resolved through executed commands.`,
854
+ agentInstructions: `1. Show user that the issue has been successfully resolved\n2. Display the actual kubectl commands that were executed (from remediation.actions[].command field)\n3. Show execution results with success/failure status for each command\n4. Show the validation results confirming the fix worked\n5. No further action required`,
855
+ message: `Issue successfully resolved. Executed ${results.length} remediation actions and validated the fix.`,
856
+ validation: {
857
+ success: true,
858
+ summary: 'Validation confirmed issue resolution'
859
+ }
860
+ };
861
+ return {
862
+ content: [
863
+ {
864
+ type: 'text',
865
+ text: JSON.stringify(successResponse, null, 2)
866
+ }
867
+ ]
868
+ };
869
+ }
870
+ catch (error) {
871
+ logger.warn('Post-execution validation failed', {
872
+ requestId,
873
+ sessionId: session.sessionId,
874
+ error: error instanceof Error ? error.message : 'Unknown error'
875
+ });
876
+ validationResult = {
877
+ success: false,
878
+ error: error instanceof Error ? error.message : 'Validation failed',
879
+ summary: 'Validation could not be completed automatically'
880
+ };
881
+ }
882
+ }
883
+ // Update session with execution results
884
+ updateSessionFile(sessionDir, session.sessionId, {
885
+ status: overallSuccess ? 'executed_successfully' : 'executed_with_errors',
886
+ executionResults: results
887
+ });
888
+ const response = {
889
+ status: overallSuccess ? 'success' : 'failed',
890
+ sessionId: session.sessionId,
891
+ executed: true,
892
+ results: results,
893
+ executedCommands: results.map(r => r.action),
894
+ message: overallSuccess
895
+ ? `Successfully executed ${results.length} remediation actions`
896
+ : `Executed ${results.length} actions with ${results.filter(r => !r.success).length} failures`,
897
+ validation: validationResult,
898
+ instructions: {
899
+ showExecutedCommands: true,
900
+ showActualKubectlCommands: true,
901
+ nextSteps: overallSuccess
902
+ ? validationResult
903
+ ? [
904
+ 'The following kubectl commands were executed to remediate the issue:',
905
+ ...finalAnalysis.remediation.actions.map((action, index) => ` ${index + 1}. ${action.command} ${results[index]?.success ? '✓' : '✗'}`),
906
+ 'Automatic validation has been completed - see validation results above',
907
+ 'Monitor your cluster to ensure the issue remains resolved'
908
+ ]
909
+ : [
910
+ 'The following kubectl commands were executed to remediate the issue:',
911
+ ...finalAnalysis.remediation.actions.map((action, index) => ` ${index + 1}. ${action.command} ${results[index]?.success ? '✓' : '✗'}`),
912
+ `You can verify the fix by running: remediate("Verify that ${finalAnalysis.analysis.rootCause.toLowerCase()} has been resolved")`,
913
+ 'Monitor your cluster to ensure the issue is fully resolved'
914
+ ]
915
+ : [
916
+ 'The following kubectl commands were attempted:',
917
+ ...finalAnalysis.remediation.actions.map((action, index) => ` ${index + 1}. ${action.command} ${results[index]?.success ? '✓' : '✗'}`),
918
+ 'Some remediation commands failed - check the results above',
919
+ 'Review the error messages and address any underlying issues',
920
+ 'You may need to run additional commands or investigate further'
921
+ ]
922
+ },
923
+ investigation: finalAnalysis.investigation,
924
+ analysis: finalAnalysis.analysis,
925
+ remediation: finalAnalysis.remediation
926
+ };
927
+ logger.info('Remediation execution completed', {
928
+ requestId,
929
+ sessionId: session.sessionId,
930
+ overallSuccess,
931
+ successfulActions: results.filter(r => r.success).length,
932
+ failedActions: results.filter(r => !r.success).length
933
+ });
934
+ return {
935
+ content: [
936
+ {
937
+ type: 'text',
938
+ text: JSON.stringify(response, null, 2)
939
+ }
940
+ ]
941
+ };
942
+ }
943
+ /**
944
+ * Main tool handler for remediate tool
945
+ */
946
+ async function handleRemediateTool(args) {
947
+ const requestId = `remediate_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
948
+ const logger = new error_handling_1.ConsoleLogger('RemediateTool');
949
+ try {
950
+ // Validate and get session directory
951
+ const sessionDir = (0, session_utils_1.getAndValidateSessionDirectory)(args, true);
952
+ logger.debug('Session directory validated', { requestId, sessionDir });
953
+ // Validate input
954
+ const validatedInput = validateRemediateInput(args);
955
+ // Handle choice execution if provided
956
+ if (validatedInput.executeChoice && validatedInput.sessionId) {
957
+ logger.info('Executing user choice from previous session', {
958
+ requestId,
959
+ choice: validatedInput.executeChoice,
960
+ sessionId: validatedInput.sessionId
961
+ });
962
+ return await executeUserChoice(sessionDir, validatedInput.sessionId, validatedInput.executeChoice, logger, requestId);
963
+ }
964
+ // Validate that we have an issue for new investigations
965
+ if (!validatedInput.issue) {
966
+ throw error_handling_1.ErrorHandler.createError(error_handling_1.ErrorCategory.VALIDATION, error_handling_1.ErrorSeverity.HIGH, 'Issue description is required for new investigations', { operation: 'input_validation', component: 'RemediateTool' });
967
+ }
968
+ // Generate session ID and create initial session
969
+ const sessionId = generateSessionId();
970
+ const session = {
971
+ sessionId,
972
+ issue: validatedInput.issue,
973
+ initialContext: validatedInput.context || {},
974
+ mode: validatedInput.mode || 'manual',
975
+ iterations: [],
976
+ created: new Date(),
977
+ updated: new Date(),
978
+ status: 'investigating'
979
+ };
980
+ // Write initial session file
981
+ writeSessionFile(sessionDir, sessionId, session);
982
+ logger.info('Investigation session created', { requestId, sessionId });
983
+ // Initialize Claude integration
984
+ const claudeApiKey = process.env.ANTHROPIC_API_KEY;
985
+ if (!claudeApiKey) {
986
+ throw error_handling_1.ErrorHandler.createError(error_handling_1.ErrorCategory.CONFIGURATION, error_handling_1.ErrorSeverity.HIGH, 'ANTHROPIC_API_KEY environment variable not set', {
987
+ operation: 'claude_initialization',
988
+ component: 'RemediateTool',
989
+ requestId,
990
+ suggestedActions: ['Set ANTHROPIC_API_KEY environment variable']
991
+ });
992
+ }
993
+ const claudeIntegration = new claude_1.ClaudeIntegration(claudeApiKey);
994
+ // Conduct AI-driven investigation
995
+ const finalAnalysis = await conductInvestigation(session, sessionDir, claudeIntegration, logger, requestId);
996
+ logger.info('Remediation analysis completed', {
997
+ requestId,
998
+ sessionId,
999
+ rootCause: finalAnalysis.analysis.rootCause,
1000
+ actionCount: finalAnalysis.remediation.actions.length,
1001
+ riskLevel: finalAnalysis.remediation.risk
1002
+ });
1003
+ // For resolved/non-existent issues, return success immediately without execution decision
1004
+ if (finalAnalysis.status === 'success') {
1005
+ logger.info('Issue resolved/non-existent - returning success without execution decision', {
1006
+ requestId,
1007
+ sessionId,
1008
+ status: finalAnalysis.status
1009
+ });
1010
+ // Return MCP-compliant response for resolved issues
1011
+ return {
1012
+ content: [
1013
+ {
1014
+ type: 'text',
1015
+ text: JSON.stringify(finalAnalysis, null, 2)
1016
+ }
1017
+ ]
1018
+ };
1019
+ }
1020
+ // Make execution decision based on mode and thresholds
1021
+ const executionDecision = makeExecutionDecision(validatedInput.mode || 'manual', finalAnalysis.analysis.confidence, finalAnalysis.remediation.risk, validatedInput.confidenceThreshold, validatedInput.maxRiskLevel);
1022
+ logger.info('Execution decision made', {
1023
+ requestId,
1024
+ sessionId,
1025
+ mode: validatedInput.mode,
1026
+ shouldExecute: executionDecision.shouldExecute,
1027
+ reason: executionDecision.reason,
1028
+ finalStatus: executionDecision.finalStatus
1029
+ });
1030
+ // Update the final analysis with execution decision results
1031
+ const finalResult = {
1032
+ ...finalAnalysis,
1033
+ status: executionDecision.finalStatus,
1034
+ executed: executionDecision.shouldExecute,
1035
+ fallbackReason: executionDecision.fallbackReason
1036
+ };
1037
+ // Add execution choices for manual mode (awaiting_user_approval status)
1038
+ if (executionDecision.finalStatus === 'awaiting_user_approval') {
1039
+ finalResult.executionChoices = [
1040
+ {
1041
+ id: 1,
1042
+ label: "Execute automatically via MCP",
1043
+ description: "Run the kubectl commands shown above automatically via MCP\n",
1044
+ risk: finalAnalysis.remediation.risk
1045
+ },
1046
+ {
1047
+ id: 2,
1048
+ label: "Execute via agent",
1049
+ description: "STEP 1: Execute the kubectl commands using your Bash tool\nSTEP 2: Call the remediate tool again for validation with the provided validation message\n",
1050
+ risk: finalAnalysis.remediation.risk // Same risk - same commands being executed
1051
+ }
1052
+ ];
1053
+ }
1054
+ // Execute remediation actions if automatic mode approves it
1055
+ if (executionDecision.shouldExecute) {
1056
+ // Update session object with final analysis for execution
1057
+ session.finalAnalysis = finalAnalysis;
1058
+ // Execute commands and return the complete result (includes post-execution validation)
1059
+ return await executeRemediationCommands(session, sessionDir, logger, requestId);
1060
+ }
1061
+ // Return MCP-compliant response
1062
+ return {
1063
+ content: [
1064
+ {
1065
+ type: 'text',
1066
+ text: JSON.stringify(finalResult, null, 2)
1067
+ }
1068
+ ]
1069
+ };
1070
+ }
1071
+ catch (error) {
1072
+ if (error instanceof Error && 'category' in error) {
1073
+ // Re-throw ErrorHandler errors
1074
+ throw error;
1075
+ }
1076
+ // Wrap unexpected errors
1077
+ throw error_handling_1.ErrorHandler.createError(error_handling_1.ErrorCategory.UNKNOWN, error_handling_1.ErrorSeverity.HIGH, `Remediate tool failed: ${error instanceof Error ? error.message : 'Unknown error'}`, {
1078
+ operation: 'remediate_tool_execution',
1079
+ component: 'RemediateTool',
1080
+ requestId,
1081
+ input: { issue: args.issue, mode: args.mode }
1082
+ });
1083
+ }
1084
+ }
1085
+ /**
1086
+ * Make execution decision based on mode and thresholds
1087
+ */
1088
+ function makeExecutionDecision(mode, confidence, risk, confidenceThreshold = 0.8, maxRiskLevel = 'low') {
1089
+ // Manual mode always requires approval
1090
+ if (mode === 'manual') {
1091
+ return {
1092
+ shouldExecute: false,
1093
+ reason: 'Manual mode selected - requiring user approval',
1094
+ finalStatus: 'awaiting_user_approval'
1095
+ };
1096
+ }
1097
+ // Automatic mode: check thresholds
1098
+ const riskLevels = { low: 1, medium: 2, high: 3 };
1099
+ const actualRiskLevel = riskLevels[risk];
1100
+ const maxRiskLevelNum = riskLevels[maxRiskLevel];
1101
+ // Check confidence threshold
1102
+ if (confidence < confidenceThreshold) {
1103
+ return {
1104
+ shouldExecute: false,
1105
+ reason: `Confidence ${confidence.toFixed(2)} below threshold ${confidenceThreshold.toFixed(2)}`,
1106
+ finalStatus: 'success',
1107
+ fallbackReason: `Analysis confidence (${Math.round(confidence * 100)}%) is below the required threshold (${Math.round(confidenceThreshold * 100)}%). Manual review recommended.`
1108
+ };
1109
+ }
1110
+ // Check risk level
1111
+ if (actualRiskLevel > maxRiskLevelNum) {
1112
+ return {
1113
+ shouldExecute: false,
1114
+ reason: `Risk level ${risk} exceeds maximum ${maxRiskLevel}`,
1115
+ finalStatus: 'success',
1116
+ fallbackReason: `Remediation risk level (${risk}) exceeds the maximum allowed level (${maxRiskLevel}). Manual approval required.`
1117
+ };
1118
+ }
1119
+ // All conditions met for automatic execution
1120
+ return {
1121
+ shouldExecute: true,
1122
+ reason: `Automatic execution approved - confidence ${confidence.toFixed(2)} >= ${confidenceThreshold.toFixed(2)}, risk ${risk} <= ${maxRiskLevel}`,
1123
+ finalStatus: 'success'
1124
+ };
1125
+ }
1126
+ /**
1127
+ * Validate remediate input according to schema
1128
+ */
1129
+ function validateRemediateInput(args) {
1130
+ try {
1131
+ // Basic validation using our schema
1132
+ const validated = {
1133
+ issue: args.issue ? exports.REMEDIATE_TOOL_INPUT_SCHEMA.issue.parse(args.issue) : undefined,
1134
+ context: args.context ? exports.REMEDIATE_TOOL_INPUT_SCHEMA.context.parse(args.context) : undefined,
1135
+ mode: args.mode ? exports.REMEDIATE_TOOL_INPUT_SCHEMA.mode.parse(args.mode) : 'manual',
1136
+ confidenceThreshold: args.confidenceThreshold !== undefined ?
1137
+ exports.REMEDIATE_TOOL_INPUT_SCHEMA.confidenceThreshold.parse(args.confidenceThreshold) : 0.8,
1138
+ maxRiskLevel: args.maxRiskLevel ?
1139
+ exports.REMEDIATE_TOOL_INPUT_SCHEMA.maxRiskLevel.parse(args.maxRiskLevel) : 'low',
1140
+ executeChoice: args.executeChoice !== undefined ?
1141
+ exports.REMEDIATE_TOOL_INPUT_SCHEMA.executeChoice.parse(args.executeChoice) : undefined,
1142
+ sessionId: args.sessionId ? exports.REMEDIATE_TOOL_INPUT_SCHEMA.sessionId.parse(args.sessionId) : undefined
1143
+ };
1144
+ return validated;
1145
+ }
1146
+ catch (error) {
1147
+ throw error_handling_1.ErrorHandler.createError(error_handling_1.ErrorCategory.VALIDATION, error_handling_1.ErrorSeverity.MEDIUM, `Invalid input: ${error instanceof Error ? error.message : 'Unknown validation error'}`, {
1148
+ operation: 'input_validation',
1149
+ component: 'RemediateTool',
1150
+ input: args,
1151
+ suggestedActions: [
1152
+ 'Check that issue is a non-empty string',
1153
+ 'Verify mode is either "manual" or "automatic"',
1154
+ 'Ensure context follows expected structure if provided'
1155
+ ]
1156
+ });
1157
+ }
1158
+ }