UNPKG

@vfarcic/dot-ai

Version:

AI-powered development productivity platform that enhances software development workflows through intelligent automation and AI-driven assistance

317 lines (316 loc) 14.2 kB
"use strict"; /** * Operate Tool - AI-powered Kubernetes application operations */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.OPERATE_TOOL_INPUT_SCHEMA = exports.OPERATE_TOOL_DESCRIPTION = exports.OPERATE_TOOL_NAME = void 0; exports.embedContext = embedContext; exports.formatPatterns = formatPatterns; exports.formatPolicies = formatPolicies; exports.formatCapabilities = formatCapabilities; exports.operate = operate; exports.handleOperateTool = handleOperateTool; const zod_1 = require("zod"); const error_handling_1 = require("../core/error-handling"); const generic_session_manager_1 = require("../core/generic-session-manager"); const request_context_1 = require("../interfaces/request-context"); const rbac_1 = require("../core/rbac"); const pattern_vector_service_1 = require("../core/pattern-vector-service"); const policy_vector_service_1 = require("../core/policy-vector-service"); const capability_vector_service_1 = require("../core/capability-vector-service"); const index_1 = require("../core/index"); // Tool metadata for direct MCP registration exports.OPERATE_TOOL_NAME = 'operate'; exports.OPERATE_TOOL_DESCRIPTION = 'AI-powered Kubernetes application operations tool for Day 2 operations. Handles updates, scaling, enhancements, rollbacks, and deletions through natural language intents. Analyzes current state, applies organizational patterns and policies, validates changes via dry-run, and executes approved operations safely.'; // Zod schema for MCP registration exports.OPERATE_TOOL_INPUT_SCHEMA = { intent: zod_1.z .string() .min(1) .max(2000) .optional() .describe('User intent for operation: "update X to Y", "scale Z", "make W HA", etc.'), sessionId: zod_1.z .string() .optional() .describe('Session ID from previous operate call'), executeChoice: zod_1.z .number() .min(1) .max(1) .optional() .describe('Execute approved changes (1=execute)'), refinedIntent: zod_1.z .string() .min(1) .max(2000) .optional() .describe('Clarified intent if user wants to provide more details'), interaction_id: zod_1.z .string() .optional() .describe('INTERNAL ONLY - Do not populate. Used for evaluation dataset generation.'), }; // Session manager instance const sessionManager = new generic_session_manager_1.GenericSessionManager('opr'); // Initialize logger const logger = new error_handling_1.ConsoleLogger('OperateTool'); /** * Embed context (patterns, policies, capabilities) for AI analysis * @param intent User's operational intent * @returns Embedded context with patterns, policies, and capabilities * @throws Error if capabilities are not available (mandatory) */ async function embedContext(intent, logger) { const context = { patterns: [], policies: [], capabilities: [], }; // Search for relevant patterns (optional - non-blocking) try { const patternService = new pattern_vector_service_1.PatternVectorService(); const patternResults = await patternService.searchPatterns(intent, { limit: 5, }); context.patterns = patternResults.map(result => result.data); logger.info(`Found ${context.patterns.length} relevant organizational patterns`); } catch (error) { logger.warn('Pattern search failed, continuing without patterns', { error, }); } // Search for relevant policies (optional - non-blocking) try { const policyService = new policy_vector_service_1.PolicyVectorService(); const policyResults = await policyService.searchPolicyIntents(intent, { limit: 5, }); context.policies = policyResults.map(result => result.data); logger.info(`Found ${context.policies.length} relevant organizational policies`); } catch (error) { logger.warn('Policy search failed, continuing without policies', { error }); } // Search for relevant cluster capabilities (MANDATORY) try { // Use QDRANT_CAPABILITIES_COLLECTION env var for collection name // Integration tests set this to 'capabilities-policies' (pre-populated test data) // Production uses default 'capabilities' collection const collectionName = process.env.QDRANT_CAPABILITIES_COLLECTION || 'capabilities'; const capabilityService = new capability_vector_service_1.CapabilityVectorService(collectionName); const capabilityResults = await capabilityService.searchCapabilities(intent, { limit: 50 }); if (capabilityResults.length === 0) { throw new Error(`No cluster capabilities found for intent "${intent}". Please scan your cluster first:\n` + `Run: manageOrgData({ dataType: "capabilities", operation: "scan" })\n` + `Note: Capabilities are required to understand what resources and operators are available in the cluster.`); } context.capabilities = capabilityResults.map(result => result.data); logger.info(`Found ${context.capabilities.length} relevant cluster capabilities`); } catch (error) { // If it's our specific "no capabilities" error, re-throw it if (error instanceof Error && error.message.includes('No cluster capabilities found')) { throw error; } // Otherwise, it's a capability service initialization or retrieval error throw new Error(`Capability service not available for intent "${intent}". Please scan your cluster first:\n` + `Run: manageOrgData({ dataType: "capabilities", operation: "scan" })\n` + `Note: Vector DB is required for capability-based operations.\n` + `Error: ${error instanceof Error ? error.message : String(error)}`, { cause: error }); } return context; } /** * Format patterns for template placeholder */ function formatPatterns(patterns) { if (patterns.length === 0) { return 'No organizational patterns found matching this intent.'; } let formatted = ''; patterns.forEach((pattern, index) => { formatted += `### Pattern ${index + 1}: ${pattern.description}\n\n`; formatted += `**Triggers:** ${pattern.triggers.join(', ')}\n\n`; formatted += `**Suggested Resources:** ${pattern.suggestedResources.join(', ')}\n\n`; formatted += `**Rationale:** ${pattern.rationale}\n\n`; if (index < patterns.length - 1) { formatted += '---\n\n'; } }); return formatted; } /** * Format policies for template placeholder */ function formatPolicies(policies) { if (policies.length === 0) { return 'No organizational policies found matching this intent.'; } let formatted = ''; policies.forEach((policy, index) => { formatted += `### Policy ${index + 1}: ${policy.description}\n\n`; if (policy.triggers && policy.triggers.length > 0) { formatted += `**Applies to:** ${policy.triggers.join(', ')}\n\n`; } formatted += `**Rationale:** ${policy.rationale}\n\n`; if (index < policies.length - 1) { formatted += '---\n\n'; } }); return formatted; } /** * Format capabilities for template placeholder * Capabilities are already ordered by relevance from vector search */ function formatCapabilities(capabilities) { if (capabilities.length === 0) { return 'No custom capabilities detected. Only standard Kubernetes resources available.'; } // List capabilities in order received (most relevant first from vector search) let formatted = ''; capabilities.forEach(cap => { const apiInfo = cap.apiVersion || cap.group || 'core'; formatted += `- **${cap.resourceName}** (${apiInfo}): ${cap.description || 'Custom resource'}\n`; if (cap.capabilities && cap.capabilities.length > 0) { formatted += ` Capabilities: ${cap.capabilities.join(', ')}\n`; } }); return formatted; } /** * Main operate tool entry point * * PRD #343: pluginManager is required - all kubectl operations go through plugin. */ async function operate(args, pluginManager) { try { // Route 1: Execute approved operation if (args.sessionId && args.executeChoice) { // Import and delegate to execution workflow (PRD #359: uses unified registry) const { executeOperations } = await Promise.resolve().then(() => __importStar(require('./operate-execution'))); return await executeOperations(args.sessionId, logger, sessionManager); } // Route 2: Refine intent with more context if (args.sessionId && args.refinedIntent) { // Import and delegate to analysis workflow with refined intent const { analyzeIntent } = await Promise.resolve().then(() => __importStar(require('./operate-analysis'))); return await analyzeIntent(args.refinedIntent, logger, sessionManager, pluginManager, args.sessionId, args.interaction_id); } // Route 3: New operation analysis if (args.intent) { // Import and delegate to analysis workflow const { analyzeIntent } = await Promise.resolve().then(() => __importStar(require('./operate-analysis'))); return await analyzeIntent(args.intent, logger, sessionManager, pluginManager, undefined, args.interaction_id); } // Invalid input throw error_handling_1.ErrorHandler.createError(error_handling_1.ErrorCategory.VALIDATION, error_handling_1.ErrorSeverity.HIGH, 'Invalid input: must provide either intent (for new operation) or sessionId + executeChoice (for execution)', { operation: 'operate', component: 'OperateTool', }); } catch (error) { const errorMsg = error instanceof Error ? error.message : String(error); logger.error(`Operate tool error: ${errorMsg}`); return { status: 'failed', sessionId: args.sessionId || 'unknown', message: `Operation failed: ${errorMsg}`, }; } } /** * MCP handler for operate tool * Wraps the main operate function with consistent return format * * PRD #343: pluginManager is required - all kubectl operations go through plugin. */ async function handleOperateTool(args, pluginManager) { // PRD #392 Milestone 2: execution route requires 'apply' verb if (args.sessionId && args.executeChoice) { const identity = (0, request_context_1.getCurrentIdentity)(); const rbacResult = await (0, rbac_1.checkToolAccess)(identity, { toolName: 'operate', verb: 'apply', }); if (!rbacResult.allowed) { return { content: [ { type: 'text', text: JSON.stringify({ error: 'FORBIDDEN', message: `Access denied: executing operations requires 'apply' permission on 'operate'. You can analyze and plan operations, but applying changes requires additional authorization.`, tool: 'operate', user: identity?.email, }), }, ], }; } } const result = await operate(args, pluginManager); // PRD #392 Milestone 2: If analysis complete, check apply permission to adjust guidance if (result.status === 'awaiting_user_approval') { const identity = (0, request_context_1.getCurrentIdentity)(); const applyResult = await (0, rbac_1.checkToolAccess)(identity, { toolName: 'operate', verb: 'apply', }); if (!applyResult.allowed) { result.message = `Operational proposal generated successfully. Executing operations requires 'apply' permission on 'operate', which is not granted for the current user. Review the proposed changes and apply them manually using kubectl or your GitOps workflow.`; result.agentInstructions = `Review the proposed changes. To apply them, use kubectl or push to Git — executing via operate requires 'apply' permission.`; } } // Build content blocks - JSON for REST API, agent instruction for MCP agents const content = [ { type: 'text', text: JSON.stringify(result, null, 2), }, ]; // Add agent instruction block if visualization URL is present const agentDisplayBlock = (0, index_1.buildAgentDisplayBlock)({ visualizationUrl: result.visualizationUrl, }); if (agentDisplayBlock) { content.push(agentDisplayBlock); } return { content }; }