snow-flow 1.3.0 → 1.3.2

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.
Files changed (42) hide show
  1. package/.claude-flow/queen/queen-memory.db +0 -0
  2. package/.env.example +249 -14
  3. package/CLAUDE.md +185 -24
  4. package/README.md +55 -5
  5. package/dist/api/error-handling.js +898 -4
  6. package/dist/api/performance-optimizer.js +3 -2
  7. package/dist/cli.js +590 -200
  8. package/dist/config/snow-flow-config.js +320 -8
  9. package/dist/health/system-health.js +288 -21
  10. package/dist/mcp/base-mcp-server.js +2 -0
  11. package/dist/mcp/http-transport-wrapper.js +65 -4
  12. package/dist/mcp/servicenow-automation-mcp-refactored.js +90 -9
  13. package/dist/mcp/servicenow-deployment-mcp-refactored.js +151 -2
  14. package/dist/mcp/servicenow-deployment-mcp.js +828 -15
  15. package/dist/mcp/servicenow-integration-mcp-refactored.js +100 -9
  16. package/dist/mcp/servicenow-intelligent-mcp.js +198 -7
  17. package/dist/mcp/servicenow-memory-mcp.js +2 -1
  18. package/dist/mcp/servicenow-operations-mcp-refactored.js +3 -2
  19. package/dist/mcp/servicenow-xml-flow-mcp.js +671 -0
  20. package/dist/mcp/shared/base-mcp-server.js +1356 -8
  21. package/dist/mcp/shared/mcp-resource-manager.js +304 -0
  22. package/dist/queen/parallel-agent-engine.js +26 -8
  23. package/dist/queen/servicenow-queen.js +47 -44
  24. package/dist/sparc/sparc-help.js +37 -26
  25. package/dist/sparc/team-sparc.js +60 -16
  26. package/dist/utils/action-type-cache.js +2 -1
  27. package/dist/utils/mcp-config-manager.js +7 -3
  28. package/dist/utils/mcp-server-manager.js +7 -3
  29. package/dist/utils/servicenow-client.js +134 -107
  30. package/dist/utils/servicenow-id-generator.js +171 -0
  31. package/dist/utils/snow-oauth.js +13 -8
  32. package/dist/utils/widget-template-generator.js +1690 -0
  33. package/dist/utils/xml-first-flow-generator.js +473 -0
  34. package/dist/version.js +7 -1
  35. package/flow-update-sets/flow_build_automated_approval_flow_with_notifications_flow.xml +203 -0
  36. package/flow-update-sets/flow_create_approval_flow_for_equipment_requests_flow.xml +206 -0
  37. package/flow-update-sets/flow_create_equipment_approval_flow_with_manager_approv_flow.xml +254 -0
  38. package/flow-update-sets/iphone_15_pro_approval_flow.xml +203 -0
  39. package/flow-update-sets/test_iphone_approval_flow_flow.xml +303 -0
  40. package/package.json +2 -1
  41. package/test-config.js +55 -0
  42. package/test-real-monitoring.js +184 -0
@@ -447,12 +447,594 @@ class FlowErrorHandler {
447
447
  }
448
448
  }
449
449
  /**
450
- * Retry original operation
450
+ * Retry original operation with exponential backoff
451
451
  */
452
452
  async retryOriginalOperation(error) {
453
- // This would contain the logic to retry the original operation
454
- // Implementation depends on the specific operation being retried
455
- throw new Error('Retry logic not implemented for this operation');
453
+ const maxRetries = error.max_retries || 3;
454
+ const currentRetryCount = error.retry_count || 0;
455
+ this.logger.info(`Attempting retry ${currentRetryCount + 1}/${maxRetries} for operation`, {
456
+ operation: error.context.operation,
457
+ error_type: error.type,
458
+ previous_attempts: currentRetryCount
459
+ });
460
+ // Check if we've exceeded max retries
461
+ if (currentRetryCount >= maxRetries) {
462
+ this.logger.error('Maximum retry attempts exceeded', {
463
+ operation: error.context.operation,
464
+ total_attempts: currentRetryCount,
465
+ final_error: error.message
466
+ });
467
+ return {
468
+ success: false,
469
+ final_state: null,
470
+ retry_exhausted: true,
471
+ total_attempts: currentRetryCount,
472
+ last_error: error.message,
473
+ suggested_actions: [
474
+ 'Review the operation configuration',
475
+ 'Check ServiceNow system status',
476
+ 'Contact administrator for manual intervention',
477
+ 'Consider alternative approach'
478
+ ]
479
+ };
480
+ }
481
+ // Calculate retry delay using exponential backoff
482
+ const baseDelay = this.retryDelays[Math.min(currentRetryCount, this.retryDelays.length - 1)];
483
+ const jitterDelay = baseDelay + Math.random() * 1000; // Add jitter to prevent thundering herd
484
+ this.logger.info(`Waiting ${Math.round(jitterDelay)}ms before retry attempt`, {
485
+ retry_count: currentRetryCount + 1,
486
+ base_delay: baseDelay,
487
+ actual_delay: Math.round(jitterDelay)
488
+ });
489
+ // Wait before retry
490
+ await this.delay(jitterDelay);
491
+ try {
492
+ // Determine retry strategy based on error type
493
+ const retryResult = await this.executeRetryBasedOnErrorType(error);
494
+ if (retryResult.success) {
495
+ this.logger.info('Retry attempt successful', {
496
+ operation: error.context.operation,
497
+ retry_count: currentRetryCount + 1,
498
+ success_after_attempts: currentRetryCount + 1
499
+ });
500
+ return {
501
+ success: true,
502
+ final_state: retryResult.data,
503
+ retry_count: currentRetryCount + 1,
504
+ recovery_method: 'retry_with_backoff'
505
+ };
506
+ }
507
+ else {
508
+ // Update error with new retry count and continue retry cycle
509
+ const updatedError = {
510
+ ...error,
511
+ retry_count: currentRetryCount + 1,
512
+ message: retryResult.error || error.message
513
+ };
514
+ // Recursively retry if not at max attempts
515
+ return await this.retryOriginalOperation(updatedError);
516
+ }
517
+ }
518
+ catch (retryError) {
519
+ this.logger.error('Error during retry attempt', {
520
+ retry_count: currentRetryCount + 1,
521
+ error: retryError instanceof Error ? retryError.message : String(retryError)
522
+ });
523
+ // Create updated error for next retry or final failure
524
+ const updatedError = {
525
+ ...error,
526
+ retry_count: currentRetryCount + 1,
527
+ message: retryError instanceof Error ? retryError.message : String(retryError),
528
+ original_error: retryError instanceof Error ? retryError : new Error(String(retryError))
529
+ };
530
+ // Continue retry cycle
531
+ return await this.retryOriginalOperation(updatedError);
532
+ }
533
+ }
534
+ /**
535
+ * Execute retry based on specific error type
536
+ */
537
+ async executeRetryBasedOnErrorType(error) {
538
+ try {
539
+ switch (error.type) {
540
+ case ErrorType.AUTHENTICATION_ERROR:
541
+ return await this.retryAuthenticationOperation(error);
542
+ case ErrorType.NETWORK_ERROR:
543
+ case ErrorType.TIMEOUT_ERROR:
544
+ return await this.retryNetworkOperation(error);
545
+ case ErrorType.RATE_LIMIT_ERROR:
546
+ return await this.retryRateLimitedOperation(error);
547
+ case ErrorType.SERVICE_UNAVAILABLE:
548
+ return await this.retryServiceUnavailableOperation(error);
549
+ case ErrorType.API_ERROR:
550
+ return await this.retryAPIOperation(error);
551
+ case ErrorType.CONFLICT_ERROR:
552
+ return await this.retryConflictOperation(error);
553
+ case ErrorType.RESOURCE_NOT_FOUND:
554
+ return await this.retryResourceOperation(error);
555
+ default:
556
+ return await this.retryGenericOperation(error);
557
+ }
558
+ }
559
+ catch (executeError) {
560
+ return {
561
+ success: false,
562
+ error: executeError instanceof Error ? executeError.message : String(executeError)
563
+ };
564
+ }
565
+ }
566
+ /**
567
+ * Retry authentication operations
568
+ */
569
+ async retryAuthenticationOperation(error) {
570
+ try {
571
+ this.logger.info('Retrying authentication operation');
572
+ // Try to refresh authentication
573
+ const credentials = this.client.credentialsInstance;
574
+ if (credentials?.refreshToken) {
575
+ // Attempt token refresh
576
+ const refreshResult = await this.client.oauth?.refreshAccessToken();
577
+ if (refreshResult?.success) {
578
+ // Retry the original operation with new token
579
+ return await this.retryWithNewAuthentication(error);
580
+ }
581
+ }
582
+ return {
583
+ success: false,
584
+ error: 'Authentication refresh failed - manual re-authentication required'
585
+ };
586
+ }
587
+ catch (authError) {
588
+ return {
589
+ success: false,
590
+ error: authError instanceof Error ? authError.message : String(authError)
591
+ };
592
+ }
593
+ }
594
+ /**
595
+ * Retry network operations
596
+ */
597
+ async retryNetworkOperation(error) {
598
+ try {
599
+ this.logger.info('Retrying network operation');
600
+ // For network errors, simply retry the original request
601
+ return await this.executeOriginalOperation(error);
602
+ }
603
+ catch (networkError) {
604
+ return {
605
+ success: false,
606
+ error: networkError instanceof Error ? networkError.message : String(networkError)
607
+ };
608
+ }
609
+ }
610
+ /**
611
+ * Retry rate limited operations with longer delay
612
+ */
613
+ async retryRateLimitedOperation(error) {
614
+ try {
615
+ this.logger.info('Retrying rate limited operation with extended delay');
616
+ // Rate limit errors need longer delays
617
+ const rateLimitDelay = 30000 + Math.random() * 10000; // 30-40 seconds
618
+ await this.delay(rateLimitDelay);
619
+ return await this.executeOriginalOperation(error);
620
+ }
621
+ catch (rateLimitError) {
622
+ return {
623
+ success: false,
624
+ error: rateLimitError instanceof Error ? rateLimitError.message : String(rateLimitError)
625
+ };
626
+ }
627
+ }
628
+ /**
629
+ * Retry service unavailable operations
630
+ */
631
+ async retryServiceUnavailableOperation(error) {
632
+ try {
633
+ this.logger.info('Retrying operation after service unavailability');
634
+ // Check service health before retry
635
+ const healthCheck = await this.performServiceHealthCheck();
636
+ if (!healthCheck.healthy) {
637
+ return {
638
+ success: false,
639
+ error: `Service still unavailable: ${healthCheck.reason}`
640
+ };
641
+ }
642
+ return await this.executeOriginalOperation(error);
643
+ }
644
+ catch (serviceError) {
645
+ return {
646
+ success: false,
647
+ error: serviceError instanceof Error ? serviceError.message : String(serviceError)
648
+ };
649
+ }
650
+ }
651
+ /**
652
+ * Retry API operations
653
+ */
654
+ async retryAPIOperation(error) {
655
+ try {
656
+ this.logger.info('Retrying API operation');
657
+ // API errors might be transient, retry with same parameters
658
+ return await this.executeOriginalOperation(error);
659
+ }
660
+ catch (apiError) {
661
+ return {
662
+ success: false,
663
+ error: apiError instanceof Error ? apiError.message : String(apiError)
664
+ };
665
+ }
666
+ }
667
+ /**
668
+ * Retry conflict operations with modified data
669
+ */
670
+ async retryConflictOperation(error) {
671
+ try {
672
+ this.logger.info('Retrying conflict operation with modifications');
673
+ // For conflicts, try to modify the operation slightly
674
+ const modifiedOperation = await this.modifyOperationForConflictResolution(error);
675
+ return await this.executeModifiedOperation(error, modifiedOperation);
676
+ }
677
+ catch (conflictError) {
678
+ return {
679
+ success: false,
680
+ error: conflictError instanceof Error ? conflictError.message : String(conflictError)
681
+ };
682
+ }
683
+ }
684
+ /**
685
+ * Retry resource operations
686
+ */
687
+ async retryResourceOperation(error) {
688
+ try {
689
+ this.logger.info('Retrying resource operation');
690
+ // For resource not found, try to create the resource first
691
+ const resourceCreated = await this.ensureResourceExists(error);
692
+ if (resourceCreated.success) {
693
+ return await this.executeOriginalOperation(error);
694
+ }
695
+ return {
696
+ success: false,
697
+ error: `Failed to create required resource: ${resourceCreated.error}`
698
+ };
699
+ }
700
+ catch (resourceError) {
701
+ return {
702
+ success: false,
703
+ error: resourceError instanceof Error ? resourceError.message : String(resourceError)
704
+ };
705
+ }
706
+ }
707
+ /**
708
+ * Generic retry operation
709
+ */
710
+ async retryGenericOperation(error) {
711
+ try {
712
+ this.logger.info('Performing generic retry operation');
713
+ // Generic retry - just execute the original operation again
714
+ return await this.executeOriginalOperation(error);
715
+ }
716
+ catch (genericError) {
717
+ return {
718
+ success: false,
719
+ error: genericError instanceof Error ? genericError.message : String(genericError)
720
+ };
721
+ }
722
+ }
723
+ /**
724
+ * Execute the original operation that failed
725
+ */
726
+ async executeOriginalOperation(error) {
727
+ try {
728
+ // This would reconstruct and execute the original operation
729
+ // For now, we'll simulate the operation based on context
730
+ const operation = error.context.operation;
731
+ const operationType = this.extractOperationType(operation);
732
+ switch (operationType) {
733
+ case 'deploy':
734
+ return await this.retryDeployOperation(error);
735
+ case 'search':
736
+ return await this.retrySearchOperation(error);
737
+ case 'create':
738
+ return await this.retryCreateOperation(error);
739
+ case 'update':
740
+ return await this.retryUpdateOperation(error);
741
+ default:
742
+ // Generic HTTP request retry
743
+ return await this.retryHTTPRequest(error);
744
+ }
745
+ }
746
+ catch (executeError) {
747
+ return {
748
+ success: false,
749
+ error: executeError instanceof Error ? executeError.message : String(executeError)
750
+ };
751
+ }
752
+ }
753
+ // Helper methods for specific operation types
754
+ async retryDeployOperation(error) {
755
+ try {
756
+ this.logger.info('Retrying deployment operation', {
757
+ component_type: error.context.component_type,
758
+ component_id: error.context.component_id
759
+ });
760
+ // Extract deployment parameters from context
761
+ const deploymentData = error.context.user_input;
762
+ if (!deploymentData) {
763
+ return { success: false, error: 'No deployment data available for retry' };
764
+ }
765
+ // Retry based on component type
766
+ switch (error.context.component_type) {
767
+ case 'widget':
768
+ return await this.retryWidgetDeployment(deploymentData, error);
769
+ case 'flow':
770
+ return await this.retryFlowDeployment(deploymentData, error);
771
+ case 'application':
772
+ return await this.retryApplicationDeployment(deploymentData, error);
773
+ case 'script':
774
+ return await this.retryScriptDeployment(deploymentData, error);
775
+ default:
776
+ return await this.retryGenericDeployment(deploymentData, error);
777
+ }
778
+ }
779
+ catch (deployError) {
780
+ return {
781
+ success: false,
782
+ error: deployError instanceof Error ? deployError.message : String(deployError)
783
+ };
784
+ }
785
+ }
786
+ async retrySearchOperation(error) {
787
+ try {
788
+ this.logger.info('Retrying search operation', { operation: error.context.operation });
789
+ const searchParams = error.context.user_input;
790
+ if (!searchParams) {
791
+ return { success: false, error: 'No search parameters available for retry' };
792
+ }
793
+ // Modify search parameters if this is a retry to avoid same failure
794
+ const modifiedParams = {
795
+ ...searchParams,
796
+ sysparm_limit: Math.min(searchParams.sysparm_limit || 100, 50), // Reduce limit
797
+ sysparm_offset: searchParams.sysparm_offset || 0,
798
+ retry_attempt: (error.retry_count || 0) + 1
799
+ };
800
+ // Perform the search request
801
+ const result = await this.client.makeRequest({
802
+ method: 'GET',
803
+ endpoint: searchParams.endpoint || '/api/now/table/sys_metadata',
804
+ params: modifiedParams
805
+ });
806
+ if (result.status === 200) {
807
+ return { success: true, data: result.data };
808
+ }
809
+ else {
810
+ return { success: false, error: `Search failed with status: ${result.status}` };
811
+ }
812
+ }
813
+ catch (searchError) {
814
+ return {
815
+ success: false,
816
+ error: searchError instanceof Error ? searchError.message : String(searchError)
817
+ };
818
+ }
819
+ }
820
+ async retryCreateOperation(error) {
821
+ try {
822
+ this.logger.info('Retrying create operation', {
823
+ table: error.context.component_type,
824
+ retry_count: error.retry_count
825
+ });
826
+ const createData = error.context.user_input;
827
+ if (!createData || !createData.table) {
828
+ return { success: false, error: 'No create data or table specified for retry' };
829
+ }
830
+ // Modify data for conflict resolution if needed
831
+ let retryData = { ...createData.data };
832
+ // If this is a conflict retry, modify unique fields
833
+ if (error.type === ErrorType.CONFLICT_ERROR && error.retry_count > 0) {
834
+ const timestamp = Date.now();
835
+ if (retryData.name) {
836
+ retryData.name = `${retryData.name}_retry_${timestamp}`;
837
+ }
838
+ if (retryData.sys_name) {
839
+ retryData.sys_name = `${retryData.sys_name}_retry_${timestamp}`;
840
+ }
841
+ }
842
+ // Perform the create request
843
+ const result = await this.client.createRecord(createData.table, retryData);
844
+ if (result && result.sys_id) {
845
+ this.logger.info('Create operation retry successful', { sys_id: result.sys_id });
846
+ return { success: true, data: result };
847
+ }
848
+ else {
849
+ return { success: false, error: 'Create operation failed - no sys_id returned' };
850
+ }
851
+ }
852
+ catch (createError) {
853
+ return {
854
+ success: false,
855
+ error: createError instanceof Error ? createError.message : String(createError)
856
+ };
857
+ }
858
+ }
859
+ async retryUpdateOperation(error) {
860
+ try {
861
+ this.logger.info('Retrying update operation', {
862
+ table: error.context.component_type,
863
+ record_id: error.context.component_id
864
+ });
865
+ const updateData = error.context.user_input;
866
+ if (!updateData || !updateData.table || !updateData.record_id) {
867
+ return { success: false, error: 'No update data, table, or record ID available for retry' };
868
+ }
869
+ // First, verify the record still exists
870
+ try {
871
+ const existingRecord = await this.client.getRecord(updateData.table, updateData.record_id);
872
+ if (!existingRecord) {
873
+ return { success: false, error: 'Record no longer exists for update' };
874
+ }
875
+ }
876
+ catch (getError) {
877
+ return { success: false, error: 'Cannot verify record existence for update retry' };
878
+ }
879
+ // Perform the update request
880
+ const result = await this.client.updateRecord(updateData.table, updateData.record_id, updateData.data);
881
+ if (result) {
882
+ this.logger.info('Update operation retry successful', { sys_id: updateData.record_id });
883
+ return { success: true, data: result };
884
+ }
885
+ else {
886
+ return { success: false, error: 'Update operation failed' };
887
+ }
888
+ }
889
+ catch (updateError) {
890
+ return {
891
+ success: false,
892
+ error: updateError instanceof Error ? updateError.message : String(updateError)
893
+ };
894
+ }
895
+ }
896
+ async retryHTTPRequest(error) {
897
+ try {
898
+ this.logger.info('Retrying HTTP request', { operation: error.context.operation });
899
+ const requestData = error.context.user_input;
900
+ if (!requestData) {
901
+ return { success: false, error: 'No request data available for HTTP retry' };
902
+ }
903
+ // Construct request with retry-safe parameters
904
+ const requestConfig = {
905
+ method: requestData.method || 'GET',
906
+ endpoint: requestData.endpoint || requestData.url,
907
+ params: requestData.params,
908
+ data: requestData.data,
909
+ timeout: Math.min((requestData.timeout || 30000) * 1.5, 60000), // Increase timeout
910
+ retry_attempt: (error.retry_count || 0) + 1
911
+ };
912
+ // Add retry headers
913
+ const headers = {
914
+ ...requestData.headers,
915
+ 'X-Retry-Attempt': String(requestConfig.retry_attempt),
916
+ 'X-Request-ID': `retry_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
917
+ };
918
+ const result = await this.client.makeRequest({
919
+ ...requestConfig,
920
+ headers
921
+ });
922
+ if (result.status >= 200 && result.status < 300) {
923
+ return { success: true, data: result.data };
924
+ }
925
+ else {
926
+ return {
927
+ success: false,
928
+ error: `HTTP request failed with status: ${result.status} - ${result.statusText}`
929
+ };
930
+ }
931
+ }
932
+ catch (httpError) {
933
+ return {
934
+ success: false,
935
+ error: httpError instanceof Error ? httpError.message : String(httpError)
936
+ };
937
+ }
938
+ }
939
+ // Additional helper methods
940
+ async retryWithNewAuthentication(error) {
941
+ // Retry with refreshed authentication
942
+ return await this.executeOriginalOperation(error);
943
+ }
944
+ async performServiceHealthCheck() {
945
+ try {
946
+ // Basic health check - try to access ServiceNow instance
947
+ const response = await this.client.makeRequest({
948
+ method: 'GET',
949
+ endpoint: '/api/now/v2/table/sys_user',
950
+ params: { sysparm_limit: 1 }
951
+ });
952
+ return { healthy: response.status === 200 };
953
+ }
954
+ catch (healthError) {
955
+ return {
956
+ healthy: false,
957
+ reason: healthError instanceof Error ? healthError.message : 'Unknown health check error'
958
+ };
959
+ }
960
+ }
961
+ async modifyOperationForConflictResolution(error) {
962
+ // Modify operation to resolve conflicts (e.g., add timestamp to name)
963
+ const timestamp = Date.now();
964
+ return {
965
+ ...error.context.system_state,
966
+ name: `${error.context.system_state?.name || 'item'}_${timestamp}`,
967
+ modified_for_conflict: true
968
+ };
969
+ }
970
+ async executeModifiedOperation(error, modifiedData) {
971
+ try {
972
+ this.logger.info('Executing modified operation for conflict resolution', {
973
+ original_operation: error.context.operation,
974
+ modifications: modifiedData.modified_for_conflict ? 'conflict_resolution' : 'standard'
975
+ });
976
+ // Execute the operation with modified data based on operation type
977
+ const operationType = this.extractOperationType(error.context.operation);
978
+ switch (operationType) {
979
+ case 'create':
980
+ const table = error.context.component_type || 'sys_metadata';
981
+ const result = await this.client.createRecord(table, modifiedData);
982
+ return { success: true, data: result };
983
+ case 'update':
984
+ if (!error.context.component_id) {
985
+ return { success: false, error: 'No component ID for update operation' };
986
+ }
987
+ const updateResult = await this.client.updateRecord(error.context.component_type || 'sys_metadata', error.context.component_id, modifiedData);
988
+ return { success: true, data: updateResult };
989
+ case 'deploy':
990
+ // For deployment operations, use the deployment data structure
991
+ return await this.retryGenericDeployment(modifiedData, error);
992
+ default:
993
+ return { success: false, error: 'Unsupported operation type for modification' };
994
+ }
995
+ }
996
+ catch (executeError) {
997
+ return {
998
+ success: false,
999
+ error: executeError instanceof Error ? executeError.message : String(executeError)
1000
+ };
1001
+ }
1002
+ }
1003
+ async ensureResourceExists(error) {
1004
+ try {
1005
+ this.logger.info('Attempting to create missing resource', {
1006
+ resource_type: error.context.component_type,
1007
+ resource_id: error.context.component_id
1008
+ });
1009
+ // Extract resource information from context
1010
+ const resourceData = error.context.user_input;
1011
+ if (!resourceData) {
1012
+ return { success: false, error: 'No resource data available for creation' };
1013
+ }
1014
+ // Determine resource type and create accordingly
1015
+ switch (error.context.component_type) {
1016
+ case 'table':
1017
+ return await this.createMissingTable(resourceData);
1018
+ case 'field':
1019
+ return await this.createMissingField(resourceData);
1020
+ case 'user':
1021
+ return await this.createMissingUser(resourceData);
1022
+ case 'group':
1023
+ return await this.createMissingGroup(resourceData);
1024
+ default:
1025
+ // Generic resource creation
1026
+ return await this.createGenericResource(resourceData);
1027
+ }
1028
+ }
1029
+ catch (resourceError) {
1030
+ return {
1031
+ success: false,
1032
+ error: resourceError instanceof Error ? resourceError.message : String(resourceError)
1033
+ };
1034
+ }
1035
+ }
1036
+ async delay(ms) {
1037
+ return new Promise(resolve => setTimeout(resolve, ms));
456
1038
  }
457
1039
  /**
458
1040
  * Create missing resource
@@ -589,5 +1171,317 @@ class FlowErrorHandler {
589
1171
  return Array.from(this.transactions.values())
590
1172
  .filter(t => t.status === 'active');
591
1173
  }
1174
+ // =====================================
1175
+ // DEPLOYMENT RETRY METHODS
1176
+ // =====================================
1177
+ /**
1178
+ * Retry widget deployment
1179
+ */
1180
+ async retryWidgetDeployment(deploymentData, error) {
1181
+ try {
1182
+ this.logger.info('Retrying widget deployment', { widget_name: deploymentData.name });
1183
+ // Construct widget data with retry modifications
1184
+ const widgetData = {
1185
+ name: deploymentData.name,
1186
+ title: deploymentData.title,
1187
+ template: deploymentData.template,
1188
+ server_script: deploymentData.server_script,
1189
+ client_script: deploymentData.client_script,
1190
+ css: deploymentData.css,
1191
+ category: deploymentData.category || 'custom',
1192
+ roles: deploymentData.roles || '',
1193
+ public: deploymentData.public !== false
1194
+ };
1195
+ // If this is a conflict retry, modify the name
1196
+ if (error.type === ErrorType.CONFLICT_ERROR && (error.retry_count || 0) > 0) {
1197
+ widgetData.name = `${widgetData.name}_retry_${Date.now()}`;
1198
+ }
1199
+ const result = await this.client.createRecord('sp_widget', widgetData);
1200
+ return { success: true, data: result };
1201
+ }
1202
+ catch (widgetError) {
1203
+ return {
1204
+ success: false,
1205
+ error: widgetError instanceof Error ? widgetError.message : String(widgetError)
1206
+ };
1207
+ }
1208
+ }
1209
+ /**
1210
+ * Retry flow deployment
1211
+ */
1212
+ async retryFlowDeployment(deploymentData, error) {
1213
+ try {
1214
+ this.logger.info('Retrying flow deployment', { flow_name: deploymentData.name });
1215
+ const flowData = {
1216
+ name: deploymentData.name,
1217
+ description: deploymentData.description,
1218
+ active: deploymentData.active !== false,
1219
+ flow_designer: deploymentData.flow_definition || '{}',
1220
+ trigger_conditions: deploymentData.trigger_conditions,
1221
+ table: deploymentData.table
1222
+ };
1223
+ // Handle conflict retry
1224
+ if (error.type === ErrorType.CONFLICT_ERROR && (error.retry_count || 0) > 0) {
1225
+ flowData.name = `${flowData.name}_retry_${Date.now()}`;
1226
+ }
1227
+ const result = await this.client.createRecord('sys_hub_flow', flowData);
1228
+ return { success: true, data: result };
1229
+ }
1230
+ catch (flowError) {
1231
+ return {
1232
+ success: false,
1233
+ error: flowError instanceof Error ? flowError.message : String(flowError)
1234
+ };
1235
+ }
1236
+ }
1237
+ /**
1238
+ * Retry application deployment
1239
+ */
1240
+ async retryApplicationDeployment(deploymentData, error) {
1241
+ try {
1242
+ this.logger.info('Retrying application deployment', { app_name: deploymentData.name });
1243
+ const appData = {
1244
+ name: deploymentData.name,
1245
+ short_description: deploymentData.short_description,
1246
+ description: deploymentData.description,
1247
+ version: deploymentData.version || '1.0.0',
1248
+ vendor: deploymentData.vendor || 'Custom',
1249
+ vendor_prefix: deploymentData.vendor_prefix,
1250
+ active: deploymentData.active !== false
1251
+ };
1252
+ // Handle conflict retry
1253
+ if (error.type === ErrorType.CONFLICT_ERROR && (error.retry_count || 0) > 0) {
1254
+ appData.name = `${appData.name}_retry_${Date.now()}`;
1255
+ }
1256
+ const result = await this.client.createRecord('sys_app', appData);
1257
+ return { success: true, data: result };
1258
+ }
1259
+ catch (appError) {
1260
+ return {
1261
+ success: false,
1262
+ error: appError instanceof Error ? appError.message : String(appError)
1263
+ };
1264
+ }
1265
+ }
1266
+ /**
1267
+ * Retry script deployment
1268
+ */
1269
+ async retryScriptDeployment(deploymentData, error) {
1270
+ try {
1271
+ this.logger.info('Retrying script deployment', { script_name: deploymentData.name });
1272
+ const scriptData = {
1273
+ name: deploymentData.name,
1274
+ script: deploymentData.script,
1275
+ description: deploymentData.description,
1276
+ api_name: deploymentData.api_name,
1277
+ client_callable: deploymentData.client_callable || false,
1278
+ active: deploymentData.active !== false
1279
+ };
1280
+ // Handle conflict retry
1281
+ if (error.type === ErrorType.CONFLICT_ERROR && (error.retry_count || 0) > 0) {
1282
+ scriptData.name = `${scriptData.name}_retry_${Date.now()}`;
1283
+ }
1284
+ const result = await this.client.createRecord('sys_script_include', scriptData);
1285
+ return { success: true, data: result };
1286
+ }
1287
+ catch (scriptError) {
1288
+ return {
1289
+ success: false,
1290
+ error: scriptError instanceof Error ? scriptError.message : String(scriptError)
1291
+ };
1292
+ }
1293
+ }
1294
+ /**
1295
+ * Generic deployment retry
1296
+ */
1297
+ async retryGenericDeployment(deploymentData, error) {
1298
+ try {
1299
+ this.logger.info('Retrying generic deployment', {
1300
+ component_type: error.context.component_type,
1301
+ data_keys: Object.keys(deploymentData)
1302
+ });
1303
+ // Determine table from component type or data
1304
+ const table = deploymentData.table ||
1305
+ this.getTableFromComponentType(error.context.component_type) ||
1306
+ 'sys_metadata';
1307
+ const result = await this.client.createRecord(table, deploymentData);
1308
+ return { success: true, data: result };
1309
+ }
1310
+ catch (genericError) {
1311
+ return {
1312
+ success: false,
1313
+ error: genericError instanceof Error ? genericError.message : String(genericError)
1314
+ };
1315
+ }
1316
+ }
1317
+ // =====================================
1318
+ // RESOURCE CREATION METHODS
1319
+ // =====================================
1320
+ /**
1321
+ * Create missing table
1322
+ */
1323
+ async createMissingTable(resourceData) {
1324
+ try {
1325
+ const tableData = {
1326
+ name: resourceData.table_name || resourceData.name,
1327
+ label: resourceData.label || resourceData.table_name,
1328
+ super_class: resourceData.super_class || 'sys_metadata',
1329
+ is_extendable: resourceData.is_extendable !== false
1330
+ };
1331
+ await this.client.createRecord('sys_db_object', tableData);
1332
+ return { success: true };
1333
+ }
1334
+ catch (error) {
1335
+ return { success: false, error: error instanceof Error ? error.message : String(error) };
1336
+ }
1337
+ }
1338
+ /**
1339
+ * Create missing field
1340
+ */
1341
+ async createMissingField(resourceData) {
1342
+ try {
1343
+ const fieldData = {
1344
+ table: resourceData.table,
1345
+ column_name: resourceData.field_name,
1346
+ column_label: resourceData.label || resourceData.field_name,
1347
+ internal_type: resourceData.type || 'string',
1348
+ max_length: resourceData.max_length || 40
1349
+ };
1350
+ await this.client.createRecord('sys_dictionary', fieldData);
1351
+ return { success: true };
1352
+ }
1353
+ catch (error) {
1354
+ return { success: false, error: error instanceof Error ? error.message : String(error) };
1355
+ }
1356
+ }
1357
+ /**
1358
+ * Create missing user
1359
+ */
1360
+ async createMissingUser(resourceData) {
1361
+ try {
1362
+ const userData = {
1363
+ user_name: resourceData.user_name,
1364
+ first_name: resourceData.first_name || 'Unknown',
1365
+ last_name: resourceData.last_name || 'User',
1366
+ email: resourceData.email,
1367
+ active: resourceData.active !== false
1368
+ };
1369
+ await this.client.createRecord('sys_user', userData);
1370
+ return { success: true };
1371
+ }
1372
+ catch (error) {
1373
+ return { success: false, error: error instanceof Error ? error.message : String(error) };
1374
+ }
1375
+ }
1376
+ /**
1377
+ * Create missing group
1378
+ */
1379
+ async createMissingGroup(resourceData) {
1380
+ try {
1381
+ const groupData = {
1382
+ name: resourceData.group_name || resourceData.name,
1383
+ description: resourceData.description || `Group ${resourceData.name}`,
1384
+ active: resourceData.active !== false
1385
+ };
1386
+ await this.client.createRecord('sys_user_group', groupData);
1387
+ return { success: true };
1388
+ }
1389
+ catch (error) {
1390
+ return { success: false, error: error instanceof Error ? error.message : String(error) };
1391
+ }
1392
+ }
1393
+ /**
1394
+ * Create generic resource
1395
+ */
1396
+ async createGenericResource(resourceData) {
1397
+ try {
1398
+ const table = resourceData.table || 'sys_metadata';
1399
+ await this.client.createRecord(table, resourceData);
1400
+ return { success: true };
1401
+ }
1402
+ catch (error) {
1403
+ return { success: false, error: error instanceof Error ? error.message : String(error) };
1404
+ }
1405
+ }
1406
+ // =====================================
1407
+ // UTILITY METHODS
1408
+ // =====================================
1409
+ /**
1410
+ * Get ServiceNow table name from component type
1411
+ */
1412
+ getTableFromComponentType(componentType) {
1413
+ if (!componentType)
1414
+ return null;
1415
+ const tableMap = {
1416
+ 'widget': 'sp_widget',
1417
+ 'flow': 'sys_hub_flow',
1418
+ 'application': 'sys_app',
1419
+ 'script': 'sys_script_include',
1420
+ 'business_rule': 'sys_script',
1421
+ 'client_script': 'sys_script_client',
1422
+ 'ui_policy': 'sys_ui_policy',
1423
+ 'ui_action': 'sys_ui_action',
1424
+ 'table': 'sys_db_object',
1425
+ 'field': 'sys_dictionary',
1426
+ 'user': 'sys_user',
1427
+ 'group': 'sys_user_group'
1428
+ };
1429
+ return tableMap[componentType.toLowerCase()] || null;
1430
+ }
1431
+ /**
1432
+ * Enhanced operation type extraction
1433
+ */
1434
+ extractOperationType(operation) {
1435
+ const op = operation.toLowerCase();
1436
+ // More specific operation detection
1437
+ if (op.includes('deploy') || op.includes('create_') || op.includes('snow_deploy'))
1438
+ return 'deploy';
1439
+ if (op.includes('search') || op.includes('find') || op.includes('query') || op.includes('discover'))
1440
+ return 'search';
1441
+ if (op.includes('create') || op.includes('insert') || op.includes('add'))
1442
+ return 'create';
1443
+ if (op.includes('update') || op.includes('modify') || op.includes('edit') || op.includes('patch'))
1444
+ return 'update';
1445
+ if (op.includes('delete') || op.includes('remove'))
1446
+ return 'delete';
1447
+ if (op.includes('get') || op.includes('fetch') || op.includes('retrieve'))
1448
+ return 'read';
1449
+ if (op.includes('authenticate') || op.includes('login') || op.includes('auth'))
1450
+ return 'auth';
1451
+ if (op.includes('http') || op.includes('request') || op.includes('api'))
1452
+ return 'http';
1453
+ return 'generic';
1454
+ }
1455
+ /**
1456
+ * Check if error type is retryable
1457
+ */
1458
+ isRetryableError(errorType) {
1459
+ const retryableErrors = [
1460
+ ErrorType.NETWORK_ERROR,
1461
+ ErrorType.TIMEOUT_ERROR,
1462
+ ErrorType.RATE_LIMIT_ERROR,
1463
+ ErrorType.SERVICE_UNAVAILABLE,
1464
+ ErrorType.API_ERROR,
1465
+ ErrorType.CONFLICT_ERROR
1466
+ ];
1467
+ return retryableErrors.includes(errorType);
1468
+ }
1469
+ /**
1470
+ * Get circuit breaker status for operation
1471
+ */
1472
+ getCircuitBreakerStatus(operation) {
1473
+ // Simple circuit breaker implementation
1474
+ const recentErrors = this.errorHistory
1475
+ .filter(error => error.context.operation === operation &&
1476
+ error.context.timestamp.getTime() > Date.now() - 300000 // Last 5 minutes
1477
+ );
1478
+ const failureCount = recentErrors.length;
1479
+ const isOpen = failureCount >= 5; // Open circuit after 5 failures
1480
+ return {
1481
+ isOpen,
1482
+ failureCount,
1483
+ lastFailureTime: recentErrors.length > 0 ? recentErrors[recentErrors.length - 1].context.timestamp : undefined
1484
+ };
1485
+ }
592
1486
  }
593
1487
  exports.FlowErrorHandler = FlowErrorHandler;