UNPKG

snow-flow

Version:

Snow-Flow v3.2.0: Complete ServiceNow Enterprise Suite with 180+ MCP Tools. ATF Testing, Knowledge Management, Service Catalog, Change Management with CAB scheduling, Virtual Agent chatbots with NLU, Performance Analytics KPIs, Flow Designer automation, A

1,125 lines (1,119 loc) 89.3 kB
"use strict"; /** * Base MCP Server with Agent Integration * Provides common functionality for all MCP servers in the agent ecosystem */ 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.BaseMCPServer = void 0; const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js"); const servicenow_client_js_1 = require("../../utils/servicenow-client.js"); const snow_oauth_js_1 = require("../../utils/snow-oauth.js"); const logger_js_1 = require("../../utils/logger.js"); const agent_context_provider_js_1 = require("./agent-context-provider.js"); const mcp_memory_manager_js_1 = require("./mcp-memory-manager.js"); const mcp_resource_manager_js_1 = require("./mcp-resource-manager.js"); class BaseMCPServer { constructor(name, version = '1.0.0') { this.server = new index_js_1.Server({ name, version, }, { capabilities: { tools: {}, resources: {}, }, }); this.client = new servicenow_client_js_1.ServiceNowClient(); this.oauth = new snow_oauth_js_1.ServiceNowOAuth(); this.logger = new logger_js_1.Logger(name); this.contextProvider = new agent_context_provider_js_1.AgentContextProvider(); this.memory = mcp_memory_manager_js_1.MCPMemoryManager.getInstance(); this.resourceManager = new mcp_resource_manager_js_1.MCPResourceManager(name); } /** * Execute a tool with agent context tracking */ async executeWithAgentContext(toolName, args, operation) { // Extract agent context const agentContext = this.contextProvider.extractAgentContext(args); // Log operation start this.logger.info(`Executing ${toolName}`, { agent_id: agentContext.agent_id, session_id: agentContext.session_id }); // Execute with context tracking const result = await this.contextProvider.executeWithContext({ ...agentContext, operation_name: toolName, mcp_server: this.server.serverInfo?.name || 'unknown' }, async () => operation(agentContext)); if (!result.success) { return { content: [ { type: 'text', text: `❌ Operation failed: ${result.error}` } ], metadata: { agent_id: agentContext.agent_id, session_id: agentContext.session_id, duration_ms: result.duration_ms } }; } return result.data; } /** * MANDATORY authentication and connection validation before ServiceNow operations */ async validateServiceNowConnection() { try { this.logger.info('🔍 Validating ServiceNow connection...'); // 1. Check if we have valid credentials const credentials = await this.oauth.loadCredentials(); if (!credentials) { return { success: false, error: 'No ServiceNow credentials found. Set up .env file and run "snow-flow auth login".' }; } // 2. Check if we have access token (OAuth session) if (!credentials.accessToken) { return { success: false, error: 'OAuth authentication required. Run "snow-flow auth login" to authenticate.' }; } // 3. Check if token is still valid const isAuth = await this.oauth.isAuthenticated(); if (!isAuth) { this.logger.info('🔄 Token expired, attempting refresh...'); // Try to refresh token const refreshResult = await this.oauth.refreshAccessToken(); if (!refreshResult.success) { return { success: false, error: 'OAuth token expired and refresh failed. Run "snow-flow auth login" to re-authenticate.' }; } this.logger.info('✅ Token refreshed successfully'); } // 4. Test actual ServiceNow connection this.logger.info('🔧 About to call testConnection', { clientType: this.client.constructor.name, clientMethods: Object.getOwnPropertyNames(Object.getPrototypeOf(this.client)), hasTestConnection: typeof this.client.testConnection === 'function', hasMakeRequest: typeof this.client.makeRequest === 'function' }); try { const connectionTest = await this.client.testConnection(); if (!connectionTest.success) { return { success: false, error: `ServiceNow connection failed: ${connectionTest.error}` }; } } catch (testError) { this.logger.error('🔧 testConnection error:', { error: testError.message, stack: testError.stack, clientInfo: { type: this.client.constructor.name, methods: Object.getOwnPropertyNames(Object.getPrototypeOf(this.client)) } }); throw testError; } this.logger.info('✅ ServiceNow connection validated'); return { success: true }; } catch (error) { this.logger.error('ServiceNow connection validation failed', error); return { success: false, error: error instanceof Error ? error.message : String(error) }; } } /** * Check authentication with proper error handling (deprecated - use validateServiceNowConnection) */ async checkAuthentication() { const result = await this.validateServiceNowConnection(); return result.success; } /** * Create standard authentication error response with detailed instructions */ createAuthenticationError(error) { const errorText = error || 'Authentication validation failed'; return { content: [ { type: 'text', text: `❌ ServiceNow Connection Failed ${errorText} 🔧 To fix this: 1. Ensure .env file has OAuth credentials: SNOW_INSTANCE=your-instance.service-now.com SNOW_CLIENT_ID=your_oauth_client_id SNOW_CLIENT_SECRET=your_oauth_client_secret 2. Authenticate with ServiceNow: snow-flow auth login 3. If you still get errors, run diagnostics: snow_auth_diagnostics() 💡 To get OAuth credentials: • ServiceNow: System OAuth > Application Registry > New OAuth Application • Redirect URI: http://localhost:3005/callback (configurable via SNOW_REDIRECT_* env vars) • Scopes: useraccount write admin` } ] }; } /** * Smart artifact discovery - check for existing artifacts before creating new ones */ async discoverExistingArtifacts(type, name, searchTerms) { try { this.logger.info(`🔍 Discovering existing ${type} artifacts...`); const suggestions = []; let artifacts = []; // Search by name const nameSearch = await this.client.searchRecords(this.getTableForType(type), `nameLIKE${name}`, 5); if (nameSearch.success && nameSearch.data?.result?.length > 0) { artifacts = nameSearch.data.result; suggestions.push(`Found ${artifacts.length} existing ${type}(s) with similar names`); suggestions.push(`Consider reusing: ${artifacts.map(a => a.name).join(', ')}`); } // Search by additional terms if provided if (searchTerms && searchTerms.length > 0) { for (const term of searchTerms) { const termSearch = await this.client.searchRecords(this.getTableForType(type), `nameLIKE${term}^ORdescriptionLIKE${term}`, 3); if (termSearch.success && termSearch.data?.result?.length > 0) { const newArtifacts = termSearch.data.result.filter((new_artifact) => !artifacts.some(existing => existing.sys_id === new_artifact.sys_id)); artifacts.push(...newArtifacts); if (newArtifacts.length > 0) { suggestions.push(`Found ${newArtifacts.length} related ${type}(s) for "${term}"`); } } } } return { found: artifacts.length > 0, artifacts, suggestions }; } catch (error) { this.logger.warn('Artifact discovery failed', error); return { found: false, artifacts: [], suggestions: [] }; } } /** * Ensure active Update Set for tracking changes */ async ensureUpdateSet(context, purpose) { try { this.logger.info('📦 Ensuring active Update Set...'); // Check current update set const currentUpdateSet = await this.client.getCurrentUpdateSet(); if (currentUpdateSet.success && currentUpdateSet.data) { this.logger.info(`✅ Using existing Update Set: ${currentUpdateSet.data.name}`); return currentUpdateSet.data.sys_id; } // Create new update set const updateSetName = purpose ? `Snow-Flow ${purpose} - ${new Date().toISOString().split('T')[0]}` : `Snow-Flow Changes - ${new Date().toISOString().split('T')[0]}`; this.logger.info(`📦 Creating new Update Set: ${updateSetName}`); const newUpdateSet = await this.client.createUpdateSet({ name: updateSetName, description: `Automated changes from Snow-Flow Agent (${context.agent_id})`, state: 'in_progress' }); if (newUpdateSet.success && newUpdateSet.data) { // Set it as current await this.client.setCurrentUpdateSet(newUpdateSet.data.sys_id); this.logger.info(`✅ Update Set created and activated: ${newUpdateSet.data.sys_id}`); return newUpdateSet.data.sys_id; } this.logger.warn('⚠️ Could not create Update Set, changes will not be tracked'); return null; } catch (error) { this.logger.warn('Update Set creation failed', error); return null; } } /** * Automatically track artifact in Update Set */ async trackArtifact(sysId, type, name, updateSetId) { try { if (!updateSetId) { this.logger.warn('No Update Set ID provided for artifact tracking'); return; } this.logger.info(`📋 Tracking ${type} artifact: ${name} (${sysId})`); // Add artifact to update set tracking (this is handled automatically by ServiceNow // when artifacts are created/modified, but we can log it for visibility) this.logger.info(`✅ Artifact tracked in Update Set: ${updateSetId}`); } catch (error) { this.logger.warn('Artifact tracking failed', error); } } /** * Get ServiceNow table name for artifact type */ getTableForType(type) { const tableMap = { 'widget': 'sp_widget', 'flow': 'sys_hub_flow', 'subflow': 'sys_hub_flow', 'action': 'sys_hub_action_type_definition', 'script': 'sys_script_include', 'business_rule': 'sys_script', 'table': 'sys_db_object', 'application': 'sys_app' }; return tableMap[type] || 'sys_metadata'; } /** * Report progress during long operations */ async reportProgress(context, progress, phase) { await this.contextProvider.reportProgress(context, progress, phase); } /** * Store artifact with agent tracking */ async storeArtifact(context, artifact) { await this.contextProvider.storeArtifact(context, artifact); } /** * Notify handoff to another agent */ async notifyHandoff(context, to_agent, artifact_info) { await this.contextProvider.notifyHandoff(context, to_agent, artifact_info); } /** * Request Queen intervention for issues */ async requestQueenIntervention(context, issue) { await this.contextProvider.requestQueenIntervention(context, issue); } /** * Get session context for coordination */ async getSessionContext(session_id) { return await this.contextProvider.getSessionContext(session_id); } /** * Create success response with metadata */ createSuccessResponse(message, data, metadata) { const content = [ { type: 'text', text: `✅ ${message}` } ]; if (data) { content.push({ type: 'text', text: JSON.stringify(data, null, 2) }); } return { content, metadata }; } /** * Create error response with metadata */ createErrorResponse(message, error, metadata) { const content = [ { type: 'text', text: `❌ ${message}` } ]; if (error) { content.push({ type: 'text', text: `Error details: ${error instanceof Error ? error.message : String(error)}` }); } return { content, metadata }; } /** * Enhanced error handling with intelligent recovery strategies */ async handleServiceNowError(error, operation, context, fallbackOptions) { this.logger.error(`ServiceNow operation failed: ${operation}`, error); const errorMessage = error instanceof Error ? error.message : String(error); const errorCode = this.extractErrorCode(errorMessage); // Categorize error and determine recovery strategy const errorCategory = this.categorizeError(errorMessage, errorCode); switch (errorCategory) { case 'authentication': return this.handleAuthenticationError(error, operation, context); case 'permissions': return this.handlePermissionError(error, operation, context, fallbackOptions); case 'network': return this.handleNetworkError(error, operation, context, fallbackOptions); case 'validation': return this.handleValidationError(error, operation, context); case 'scope': return this.handleScopeError(error, operation, context, fallbackOptions); default: return this.handleGenericError(error, operation, context, fallbackOptions); } } /** * Categorize error for appropriate recovery strategy */ categorizeError(errorMessage, errorCode) { const msgLower = errorMessage.toLowerCase(); // Authentication errors if (msgLower.includes('unauthorized') || msgLower.includes('invalid_grant') || msgLower.includes('authentication') || errorCode === '401' || errorCode === '403') { return 'authentication'; } // Permission errors if (msgLower.includes('insufficient privileges') || msgLower.includes('access denied') || msgLower.includes('acl')) { return 'permissions'; } // Network errors if (msgLower.includes('network') || msgLower.includes('timeout') || msgLower.includes('connection') || msgLower.includes('econnrefused')) { return 'network'; } // Validation errors if (msgLower.includes('invalid') || msgLower.includes('required field') || msgLower.includes('constraint') || msgLower.includes('validation')) { return 'validation'; } // Scope errors if (msgLower.includes('scope') || msgLower.includes('application') || msgLower.includes('global')) { return 'scope'; } return 'generic'; } /** * Handle authentication errors with automatic recovery */ async handleAuthenticationError(error, operation, context) { this.logger.info('🔄 Attempting automatic authentication recovery...'); // Try to refresh token automatically try { const refreshResult = await this.oauth.refreshAccessToken(); if (refreshResult.success) { this.logger.info('✅ Token refreshed successfully, retry operation'); // Store recovery in context for coordination await this.memory.updateSharedContext({ session_id: context.session_id, context_key: `auth_recovery_${operation}`, context_value: JSON.stringify({ recovery_action: 'token_refresh', timestamp: new Date().toISOString(), operation: operation }), created_by_agent: context.agent_id }); return this.createSuccessResponse('Authentication recovered - token refreshed', { recovery_action: 'token_refresh', next_step: 'retry_operation', can_continue: true }); } } catch (refreshError) { this.logger.warn('Token refresh failed', refreshError); } // Request Queen intervention for authentication issues await this.requestQueenIntervention(context, { type: 'authentication_failure', priority: 'high', description: `Authentication failed for ${operation}`, attempted_solutions: ['token_refresh'] }); return this.createAuthenticationError(`Authentication failed during ${operation}. Automatic recovery failed.`); } /** * Handle permission errors with scope escalation */ async handlePermissionError(error, operation, context, fallbackOptions) { const errorMessage = error instanceof Error ? error.message : String(error); // Try scope escalation if enabled if (fallbackOptions?.enableScopeEscalation) { this.logger.info('🔝 Attempting scope escalation...'); try { // Try to escalate to global scope const escalationResult = await this.escalateToGlobalScope(operation, context); if (escalationResult.success) { return this.createSuccessResponse('Permission issue resolved via scope escalation', escalationResult); } } catch (escalationError) { this.logger.warn('Scope escalation failed', escalationError); } } // Request permission elevation await this.requestQueenIntervention(context, { type: 'permission_elevation_needed', priority: 'high', description: `Insufficient privileges for ${operation}: ${errorMessage}`, attempted_solutions: fallbackOptions?.enableScopeEscalation ? ['scope_escalation'] : [] }); return this.createErrorResponse(`Permission error during ${operation}`, { error: errorMessage, recovery_options: [ '1. Request admin privileges from Queen Agent', '2. Try scope escalation to global', '3. Create manual Update Set instructions', '4. Use alternative deployment approach' ], next_steps: [ 'Contact ServiceNow admin for elevated permissions', 'Or use snow_deploy with fallback_strategy="manual_steps"' ] }); } /** * Handle network errors with intelligent retry */ async handleNetworkError(error, operation, context, fallbackOptions) { if (fallbackOptions?.enableRetry) { this.logger.info('🔄 Network error - attempting retry with backoff...'); // Implement exponential backoff retry const maxRetries = 3; for (let attempt = 1; attempt <= maxRetries; attempt++) { await this.delay(Math.pow(2, attempt) * 1000); // Exponential backoff try { // Test connection const connectionTest = await this.client.testConnection(); if (connectionTest.success) { this.logger.info(`✅ Network recovered after ${attempt} attempts`); return this.createSuccessResponse('Network error recovered - connection restored', { recovery_action: 'network_retry', attempts: attempt, can_continue: true }); } } catch (retryError) { this.logger.warn(`Retry attempt ${attempt} failed`, retryError); } } } return this.createErrorResponse(`Network error during ${operation}`, { error: error instanceof Error ? error.message : String(error), recovery_options: [ '1. Check ServiceNow instance availability', '2. Verify network connectivity', '3. Check firewall/proxy settings', '4. Retry operation in a few minutes' ], next_steps: [ 'Wait for network recovery and retry', 'Or check ServiceNow instance status' ] }); } /** * Handle validation errors with correction suggestions */ async handleValidationError(error, operation, context) { const errorMessage = error instanceof Error ? error.message : String(error); // Extract field information from validation error const fieldInfo = this.extractFieldInfoFromError(errorMessage); return this.createErrorResponse(`Validation error during ${operation}`, { error: errorMessage, field_issues: fieldInfo, recovery_options: [ '1. Fix required fields and retry', '2. Check field constraints and formats', '3. Use snow_validate_deployment() first', '4. Review ServiceNow table schema' ], next_steps: [ 'Update configuration with correct field values', 'Validate against ServiceNow table requirements' ] }); } /** * Handle scope errors with automatic fallback */ async handleScopeError(error, operation, context, fallbackOptions) { if (fallbackOptions?.enableScopeEscalation) { this.logger.info('🌐 Scope error - attempting global scope fallback...'); try { const scopeFallback = await this.fallbackToGlobalScope(operation, context); if (scopeFallback.success) { return this.createSuccessResponse('Scope error resolved - using global scope', scopeFallback); } } catch (scopeError) { this.logger.warn('Global scope fallback failed', scopeError); } } return this.createErrorResponse(`Scope error during ${operation}`, { error: error instanceof Error ? error.message : String(error), recovery_options: [ '1. Deploy to global scope instead', '2. Create scoped application first', '3. Use manual deployment approach', '4. Check application scope permissions' ], next_steps: [ 'Try snow_deploy with scope_strategy="global"', 'Or create application scope manually first' ] }); } /** * Handle generic errors with comprehensive guidance */ async handleGenericError(error, operation, context, fallbackOptions) { const errorMessage = error instanceof Error ? error.message : String(error); // Generate contextual recovery options based on operation type const recoveryOptions = this.generateRecoveryOptions(operation, errorMessage); // Store error for pattern analysis await this.memory.updateSharedContext({ session_id: context.session_id, context_key: `error_${operation}_${Date.now()}`, context_value: JSON.stringify({ error: errorMessage, operation: operation, timestamp: new Date().toISOString(), recovery_attempted: Object.keys(fallbackOptions || {}) }), created_by_agent: context.agent_id }); return this.createErrorResponse(`Unexpected error during ${operation}`, { error: errorMessage, recovery_options: recoveryOptions, next_steps: [ 'Review error details and try alternative approach', 'Use snow_deployment_debug() for diagnostics', 'Contact Queen Agent for intervention' ], fallback_available: fallbackOptions?.enableManualSteps || false }); } /** * Helper methods for error handling */ extractErrorCode(errorMessage) { const codeMatch = errorMessage.match(/(\d{3})/); return codeMatch ? codeMatch[1] : undefined; } extractFieldInfoFromError(errorMessage) { const fieldMatch = errorMessage.match(/field[:\s]+([a-z_]+)/i); const requiredMatch = errorMessage.match(/required/i); return { field: fieldMatch ? fieldMatch[1] : null, is_required: !!requiredMatch, raw_message: errorMessage }; } generateRecoveryOptions(operation, errorMessage) { const options = [ '1. Review error message and fix configuration', '2. Check ServiceNow instance status and permissions' ]; if (operation.includes('deploy')) { options.push('3. Use snow_validate_deployment() before retrying'); options.push('4. Try manual deployment with Update Set'); } if (operation.includes('search') || operation.includes('find')) { options.push('3. Try alternative search terms'); options.push('4. Use snow_comprehensive_search() for broader search'); } options.push('5. Request Queen Agent intervention for complex issues'); return options; } async escalateToGlobalScope(operation, context) { this.logger.info(`🔝 Attempting permission escalation for ${operation}`); try { // Enhanced authentication validation const authValidation = await this.validateServiceNowConnection(); if (!authValidation.success) { return { success: false, reason: 'Authentication validation failed before escalation', recovery_action: 'Run: snow-flow auth login', auth_error: authValidation.error }; } // Get current user information with enhanced OAuth scope handling this.logger.info('🔍 Analyzing current user permissions and OAuth scopes...'); let currentUser = null; let currentRoles = []; let oAuthScopes = []; try { // Get current user details const userResponse = await this.client.makeRequest({ method: 'GET', endpoint: '/api/now/v2/table/sys_user', params: { sysparm_query: 'active=true^user_name=javascript:gs.getUserName()', sysparm_fields: 'sys_id,user_name,name,email,active,locked_out,roles', sysparm_limit: 1 } }); if (userResponse.data?.result?.[0]) { currentUser = userResponse.data.result[0]; this.logger.info(`👤 Current user: ${currentUser.name} (${currentUser.user_name})`); } // Get detailed role information const rolesResponse = await this.client.makeRequest({ method: 'GET', endpoint: '/api/now/v2/table/sys_user_has_role', params: { sysparm_query: `user.user_name=${currentUser?.user_name || 'javascript:gs.getUserName()'}`, sysparm_fields: 'role.name,role.description,state,inherited', sysparm_limit: 100 } }); if (rolesResponse.data?.result) { currentRoles = rolesResponse.data.result .filter((r) => r.state === 'active') .map((r) => r['role.name']) .filter(Boolean); this.logger.info(`🛡️ Active roles: ${currentRoles.join(', ')}`); } // Check OAuth token scopes if available try { // Attempt to get token info if method exists if (typeof this.oauth.getTokenInfo === 'function') { const tokenInfo = await this.oauth.getTokenInfo(); if (tokenInfo?.scope) { oAuthScopes = tokenInfo.scope.split(' '); this.logger.info(`🔑 OAuth scopes: ${oAuthScopes.join(', ')}`); } } else { this.logger.debug('OAuth getTokenInfo method not available'); } } catch (scopeError) { this.logger.debug('Could not retrieve OAuth scope information', { error: scopeError }); } } catch (roleError) { this.logger.warn('Could not fetch complete user information', { error: roleError }); // Continue with limited information } // Enhanced role _analysis based on operation type const requiredRoles = this.determineRequiredRoles(operation); const requiredScopes = this.determineRequiredOAuthScopes(operation); const missingRoles = requiredRoles.filter(role => !currentRoles.includes(role)); const missingScopes = requiredScopes.filter(scope => !oAuthScopes.includes(scope)); this.logger.info(`📊 Permission Analysis:`, { required_roles: requiredRoles, current_roles: currentRoles, missing_roles: missingRoles, required_scopes: requiredScopes, current_scopes: oAuthScopes, missing_scopes: missingScopes }); // Check if user has sufficient permissions if (missingRoles.length === 0 && missingScopes.length === 0) { // User has all required permissions, issue might be elsewhere return { success: true, reason: 'User has all required roles and scopes - permission issue may be configuration related', current_roles: currentRoles, current_scopes: oAuthScopes, diagnostic_actions: [ 'Check ACL rules for the specific table/operation', 'Verify application scope permissions', 'Check domain separation settings', 'Review field-level security', 'Validate write permissions for target table' ] }; } // Enhanced automatic role assignment with better error handling let autoAssignmentAttempted = false; const canAutoAssign = currentRoles.includes('admin') || currentRoles.includes('user_admin') || currentRoles.includes('system_administrator'); if (canAutoAssign && missingRoles.length > 0) { this.logger.info('🤖 Attempting automatic role assignment...'); try { const assignmentResult = await this.attemptEnhancedRoleAssignment(currentUser?.sys_id, missingRoles, context); if (assignmentResult.success) { autoAssignmentAttempted = true; // Update context with successful assignment await this.memory.updateSharedContext({ session_id: context.session_id, context_key: `permission_escalation_${operation}`, context_value: JSON.stringify({ escalation_type: 'automatic_role_assignment', assigned_roles: assignmentResult.assigned_roles, timestamp: new Date().toISOString(), operation: operation }), created_by_agent: context.agent_id }); return { success: true, reason: 'Roles automatically assigned successfully', assigned_roles: assignmentResult.assigned_roles, assignment_details: assignmentResult.details, recommended_action: 'Retry the original operation - new permissions are active' }; } } catch (assignError) { this.logger.warn('Automatic role assignment failed', { error: assignError }); autoAssignmentAttempted = true; // We tried } } // Enhanced OAuth scope elevation attempt if (missingScopes.length > 0) { this.logger.info('🔑 Attempting OAuth scope elevation...'); try { const scopeElevationResult = await this.attemptOAuthScopeElevation(missingScopes, operation, context); if (scopeElevationResult.success) { return { success: true, reason: 'OAuth scopes elevated successfully', elevated_scopes: scopeElevationResult.elevated_scopes, recommended_action: 'Retry the original operation with elevated OAuth permissions' }; } } catch (scopeError) { this.logger.warn('OAuth scope elevation failed', { error: scopeError }); } } // Request Queen intervention for complex permission issues await this.requestQueenIntervention(context, { type: 'permission_escalation_required', priority: 'high', description: `Permission escalation needed for ${operation}. Missing roles: ${missingRoles.join(', ')}. Missing OAuth scopes: ${missingScopes.join(', ')}.`, attempted_solutions: [ ...(autoAssignmentAttempted ? ['automatic_role_assignment'] : []), ...(missingScopes.length > 0 ? ['oauth_scope_elevation'] : []) ] }); // Comprehensive manual escalation instructions const manualSteps = [ `1. ServiceNow Administrator Actions Required:`, ` • Navigate to User Administration > Users`, ` • Find user: ${currentUser?.name || 'Current User'} (${currentUser?.user_name || 'unknown'})`, ` • Add missing roles: ${missingRoles.join(', ')}`, ...missingScopes.length > 0 ? [ `2. OAuth Application Configuration:`, ` • Update OAuth application scopes to include: ${missingScopes.join(', ')}`, ` • Re-authorize application with elevated permissions` ] : [], `${missingScopes.length > 0 ? '3' : '2'}. Verification Steps:`, ` • Test permissions: snow_validate_live_connection({ test_level: "permissions" })`, ` • Retry operation: ${operation}` ]; return { success: false, reason: 'Manual permission escalation required', current_user: currentUser, current_roles: currentRoles, missing_roles: missingRoles, current_oauth_scopes: oAuthScopes, missing_oauth_scopes: missingScopes, escalation_required: true, auto_assignment_attempted: autoAssignmentAttempted, manual_steps: manualSteps, estimated_time: `${5 + (missingRoles.length * 2)}–${10 + (missingRoles.length * 3)} minutes with admin access`, priority: missingRoles.includes('admin') || missingRoles.includes('system_administrator') ? 'critical' : 'high' }; } catch (error) { this.logger.error('🚨 Permission escalation process failed', { error, operation, context }); return { success: false, reason: 'Critical error during permission escalation', error: error instanceof Error ? error.message : String(error), error_type: this.categorizeError(error instanceof Error ? error.message : String(error)), recovery_actions: [ '1. Verify ServiceNow connection: snow_validate_live_connection()', '2. Check authentication status: snow_auth_diagnostics()', '3. Confirm API access is enabled in ServiceNow', '4. Contact ServiceNow administrator for manual role assignment', '5. Review system logs for additional error details' ], escalation_required: true, contact_admin: true }; } } /** * Determine required roles based on operation type */ determineRequiredRoles(operation) { const roles = []; // Base roles for most operations if (operation.includes('deploy') || operation.includes('create') || operation.includes('update')) { roles.push('rest_service', 'web_service_admin'); } // Widget operations if (operation.includes('widget')) { roles.push('sp_admin', 'sp_portal_admin'); } // Flow operations if (operation.includes('flow') || operation.includes('workflow')) { roles.push('flow_designer', 'admin'); } // Application operations if (operation.includes('application') || operation.includes('app')) { roles.push('app_creator', 'admin'); } // Update set operations if (operation.includes('update_set')) { roles.push('admin'); } // Global scope operations if (operation.includes('global')) { roles.push('admin', 'system_administrator'); } return Array.from(new Set(roles)); // Remove duplicates } /** * Determine required OAuth scopes based on operation type */ determineRequiredOAuthScopes(operation) { const scopes = ['useraccount']; // Base scope for most operations // Operations that require write access if (operation.includes('deploy') || operation.includes('create') || operation.includes('update')) { scopes.push('write'); } // Administrative operations if (operation.includes('admin') || operation.includes('global') || operation.includes('system')) { scopes.push('admin'); } // Flow Designer operations may need specific scopes if (operation.includes('flow') || operation.includes('workflow')) { scopes.push('write', 'admin'); } return Array.from(new Set(scopes)); // Remove duplicates } /** * Enhanced role assignment with better validation */ async attemptEnhancedRoleAssignment(userSysId, roles, context) { try { const assignedRoles = []; const assignmentDetails = []; for (const roleName of roles) { try { // Get the role sys_id with enhanced validation const roleResponse = await this.client.makeRequest({ method: 'GET', endpoint: '/api/now/v2/table/sys_user_role', params: { sysparm_query: `name=${roleName}^active=true`, sysparm_fields: 'sys_id,name,description,can_delegate', sysparm_limit: 1 } }); if (roleResponse.data?.result?.[0]) { const role = roleResponse.data.result[0]; // Check if user already has this role const existingRoleResponse = await this.client.makeRequest({ method: 'GET', endpoint: '/api/now/v2/table/sys_user_has_role', params: { sysparm_query: `user=${userSysId}^role=${role.sys_id}`, sysparm_limit: 1 } }); if (existingRoleResponse.data?.result?.length > 0) { this.logger.info(`User already has role: ${roleName}`); assignedRoles.push(roleName); continue; } // Attempt to assign role const assignmentResponse = await this.client.makeRequest({ method: 'POST', endpoint: '/api/now/v2/table/sys_user_has_role', data: { user: userSysId, role: role.sys_id, state: 'active', granted_by: 'snow_flow_automation' } }); if (assignmentResponse.data?.result) { assignedRoles.push(roleName); assignmentDetails.push({ role: roleName, sys_id: assignmentResponse.data.result.sys_id, status: 'assigned' }); this.logger.info(`Successfully assigned role: ${roleName}`); } } else { this.logger.warn(`Role not found: ${roleName}`); } } catch (roleError) { this.logger.warn(`Failed to assign role ${roleName}`, { error: roleError }); assignmentDetails.push({ role: roleName, status: 'failed', error: roleError instanceof Error ? roleError.message : String(roleError) }); } } return { success: assignedRoles.length > 0, assigned_roles: assignedRoles, details: assignmentDetails }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : String(error) }; } } /** * Attempt OAuth scope elevation */ async attemptOAuthScopeElevation(missingScopes, operation, context) { try { this.logger.info('Requesting OAuth scope elevation...', { missing_scopes: missingScopes }); // In most ServiceNow OAuth implementations, scope elevation requires // re-authorization through the OAuth flow. We can't dynamically elevate // scopes without user interaction. // Check if we can refresh token with additional scopes let currentToken = null; try { if (typeof this.oauth.getTokenInfo === 'function') { currentToken = await this.oauth.getTokenInfo(); } } catch (error) { this.logger.debug('Could not get current token info', { error }); } if (!currentToken) { return { success: false, error: 'No active OAuth token found or method not available' }; } // Attempt token refresh - this typically won't add new scopes // but might refresh existing elevated permissions const refreshResult = await this.oauth.refreshAccessToken(); if (!refreshResult.success) { return { success: false, error: 'Failed to refresh OAuth token' }; } // Check if the refresh gave us the scopes we need let newTokenInfo = null; try { if (typeof this.oauth.getTokenInfo === 'function') { newTokenInfo = await this.oauth.getTokenInfo(); } } catch (error) { this.logger.debug('Could not get new token info after refresh', { error }); } const newScopes = newTokenInfo?.scope?.split(' ') || []; const actuallyMissing = missingScopes.filter(scope => !newScopes.includes(scope)); if (actuallyMissing.length === 0) { return { success: true, elevated_scopes: missingScopes }; } // For missing scopes, we need manual re-authorization return { success: false, error: `OAuth scopes require manual re-authorization: ${actuallyMissing.join(', ')}` }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : String(error) }; } } /** * Enhanced deployment context extraction */ async extractEnhancedDeploymentContext(operation, context) { try { // Extract from agent context const contextData = context; // Enhanced artifact type detection const artifactType = this.extractArtifactType(operation); const artifactName = contextData.artifact_name || this.extractArtifactName(operation) || `fallback_${artifactType}_${Date.now()}`; // Determine current scope from various sources let currentScope = contextData.current_scope || 'scoped_application'; if (contextData.deployment_config?.sys_scope) { currentScope = contextData.deployment_config.sys_scope; } // Analyze original error for better global scope strategy const originalError = contextData.original_error || 'Scoped dep