@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.
- package/README.md +33 -5
- package/dist/interfaces/mcp.d.ts +7 -1
- package/dist/interfaces/mcp.d.ts.map +1 -1
- package/dist/interfaces/mcp.js +69 -22
- package/dist/interfaces/openapi-generator.d.ts +110 -0
- package/dist/interfaces/openapi-generator.d.ts.map +1 -0
- package/dist/interfaces/openapi-generator.js +468 -0
- package/dist/interfaces/rest-api.d.ts +124 -0
- package/dist/interfaces/rest-api.d.ts.map +1 -0
- package/dist/interfaces/rest-api.js +341 -0
- package/dist/interfaces/rest-registry.d.ts +108 -0
- package/dist/interfaces/rest-registry.d.ts.map +1 -0
- package/dist/interfaces/rest-registry.js +187 -0
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +6 -1
- package/dist/tools/remediate.d.ts +173 -0
- package/dist/tools/remediate.d.ts.map +1 -0
- package/dist/tools/remediate.js +1158 -0
- package/package.json +7 -6
- package/prompts/remediate-final-analysis.md +243 -0
- package/prompts/remediate-investigation.md +196 -0
- package/shared-prompts/deploy.md +23 -0
- package/shared-prompts/prd-create.md +1 -1
- package/shared-prompts/remediate.md +44 -0
- package/shared-prompts/setup.md +0 -23
|
@@ -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
|
+
}
|