snow-flow 1.1.43 โ†’ 1.1.45

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.
@@ -482,6 +482,175 @@ class ServiceNowOperationsMCP {
482
482
  },
483
483
  required: ['action']
484
484
  }
485
+ },
486
+ {
487
+ name: 'snow_catalog_item_search',
488
+ description: 'Search for catalog items with intelligent matching and discovery',
489
+ inputSchema: {
490
+ type: 'object',
491
+ properties: {
492
+ query: {
493
+ type: 'string',
494
+ description: 'Search query (e.g., "iPhone", "laptop", "mobile device")'
495
+ },
496
+ include_inactive: {
497
+ type: 'boolean',
498
+ description: 'Include inactive catalog items',
499
+ default: false
500
+ },
501
+ fuzzy_match: {
502
+ type: 'boolean',
503
+ description: 'Enable fuzzy matching for similar items',
504
+ default: true
505
+ },
506
+ category_filter: {
507
+ type: 'string',
508
+ description: 'Filter by category name or sys_id'
509
+ },
510
+ limit: {
511
+ type: 'number',
512
+ description: 'Maximum results to return',
513
+ default: 50
514
+ },
515
+ include_variables: {
516
+ type: 'boolean',
517
+ description: 'Include catalog item variables in results',
518
+ default: false
519
+ }
520
+ },
521
+ required: ['query']
522
+ }
523
+ },
524
+ {
525
+ name: 'snow_test_flow_with_mock',
526
+ description: 'Test flows with mock data - create test users, mock catalog items, and simulate flow execution',
527
+ inputSchema: {
528
+ type: 'object',
529
+ properties: {
530
+ flow_id: {
531
+ type: 'string',
532
+ description: 'Flow sys_id or name to test'
533
+ },
534
+ create_test_user: {
535
+ type: 'boolean',
536
+ description: 'Create a test user for the flow',
537
+ default: true
538
+ },
539
+ test_user_data: {
540
+ type: 'object',
541
+ description: 'Test user details',
542
+ properties: {
543
+ user_name: { type: 'string' },
544
+ first_name: { type: 'string' },
545
+ last_name: { type: 'string' },
546
+ email: { type: 'string' },
547
+ department: { type: 'string' },
548
+ manager: { type: 'string' }
549
+ }
550
+ },
551
+ mock_catalog_items: {
552
+ type: 'boolean',
553
+ description: 'Create mock catalog items for testing',
554
+ default: true
555
+ },
556
+ mock_catalog_data: {
557
+ type: 'array',
558
+ description: 'Mock catalog items to create',
559
+ items: {
560
+ type: 'object',
561
+ properties: {
562
+ name: { type: 'string' },
563
+ short_description: { type: 'string' },
564
+ price: { type: 'string' },
565
+ variables: { type: 'array' }
566
+ }
567
+ }
568
+ },
569
+ test_inputs: {
570
+ type: 'object',
571
+ description: 'Input values to test the flow with'
572
+ },
573
+ simulate_approvals: {
574
+ type: 'boolean',
575
+ description: 'Automatically approve any approval requests',
576
+ default: true
577
+ },
578
+ cleanup_after_test: {
579
+ type: 'boolean',
580
+ description: 'Remove test data after testing',
581
+ default: true
582
+ }
583
+ },
584
+ required: ['flow_id']
585
+ }
586
+ },
587
+ {
588
+ name: 'snow_link_catalog_to_flow',
589
+ description: 'Link catalog items directly to flows - configure flow as fulfillment process',
590
+ inputSchema: {
591
+ type: 'object',
592
+ properties: {
593
+ catalog_item_id: {
594
+ type: 'string',
595
+ description: 'Catalog item sys_id or name'
596
+ },
597
+ flow_id: {
598
+ type: 'string',
599
+ description: 'Flow sys_id or name to use for fulfillment'
600
+ },
601
+ link_type: {
602
+ type: 'string',
603
+ description: 'Type of link to create',
604
+ enum: ['workflow', 'flow_catalog_process', 'process_engine'],
605
+ default: 'flow_catalog_process'
606
+ },
607
+ variable_mapping: {
608
+ type: 'array',
609
+ description: 'Map catalog variables to flow inputs',
610
+ items: {
611
+ type: 'object',
612
+ properties: {
613
+ catalog_variable: { type: 'string', description: 'Catalog variable name' },
614
+ flow_input: { type: 'string', description: 'Flow input name' },
615
+ transform: { type: 'string', description: 'Optional transformation script' }
616
+ }
617
+ }
618
+ },
619
+ trigger_condition: {
620
+ type: 'string',
621
+ description: 'Condition for when to trigger the flow',
622
+ default: 'current.stage == "request_approved"'
623
+ },
624
+ execution_options: {
625
+ type: 'object',
626
+ description: 'Flow execution options',
627
+ properties: {
628
+ run_as: {
629
+ type: 'string',
630
+ description: 'User context for flow execution',
631
+ enum: ['requester', 'system', 'fulfiller'],
632
+ default: 'system'
633
+ },
634
+ wait_for_completion: {
635
+ type: 'boolean',
636
+ description: 'Wait for flow to complete before closing request',
637
+ default: true
638
+ },
639
+ update_request_on_progress: {
640
+ type: 'boolean',
641
+ description: 'Update request item with flow progress',
642
+ default: true
643
+ }
644
+ }
645
+ },
646
+ test_link: {
647
+ type: 'boolean',
648
+ description: 'Test the link by creating a sample request',
649
+ default: false
650
+ }
651
+ },
652
+ required: ['catalog_item_id', 'flow_id']
653
+ }
485
654
  }
486
655
  ],
487
656
  };
@@ -515,6 +684,12 @@ class ServiceNowOperationsMCP {
515
684
  return await this.handlePredictiveAnalysis(args);
516
685
  case 'snow_catalog_item_manager':
517
686
  return await this.handleCatalogItemManager(args);
687
+ case 'snow_catalog_item_search':
688
+ return await this.handleCatalogItemSearch(args);
689
+ case 'snow_test_flow_with_mock':
690
+ return await this.handleTestFlowWithMock(args);
691
+ case 'snow_link_catalog_to_flow':
692
+ return await this.handleLinkCatalogToFlow(args);
518
693
  default:
519
694
  throw new types_js_1.McpError(types_js_1.ErrorCode.MethodNotFound, `Tool ${name} not found`);
520
695
  }
@@ -1597,6 +1772,725 @@ class ServiceNowOperationsMCP {
1597
1772
  };
1598
1773
  return typeMap[type] || '6'; // Default to single line text
1599
1774
  }
1775
+ async handleCatalogItemSearch(args) {
1776
+ const { query, include_inactive = false, fuzzy_match = true, category_filter, limit = 50, include_variables = false } = args;
1777
+ logger_js_1.logger.info(`Searching catalog items for: ${query}`, { fuzzy_match, category_filter, limit });
1778
+ try {
1779
+ // Build search queries
1780
+ const searchQueries = [];
1781
+ // Exact match
1782
+ searchQueries.push(`name=${query}`);
1783
+ // Contains match
1784
+ searchQueries.push(`nameLIKE${query}`);
1785
+ searchQueries.push(`short_descriptionLIKE${query}`);
1786
+ // Fuzzy matching for similar items
1787
+ if (fuzzy_match) {
1788
+ // Handle common variations
1789
+ const variations = this.generateSearchVariations(query);
1790
+ variations.forEach(variation => {
1791
+ searchQueries.push(`nameLIKE${variation}`);
1792
+ searchQueries.push(`short_descriptionLIKE${variation}`);
1793
+ });
1794
+ }
1795
+ // Add active filter
1796
+ const activeFilter = include_inactive ? '' : '^active=true';
1797
+ // Add category filter if provided
1798
+ let categoryFilter = '';
1799
+ if (category_filter) {
1800
+ // First try to find the category
1801
+ const categoryResult = await this.client.searchRecords('sc_category', `name=${category_filter}^ORsys_id=${category_filter}`, 1);
1802
+ if (categoryResult.success && categoryResult.data?.result?.length > 0) {
1803
+ categoryFilter = `^sc_categories=${categoryResult.data.result[0].sys_id}`;
1804
+ }
1805
+ }
1806
+ // Execute searches
1807
+ const allResults = new Map();
1808
+ for (const searchQuery of searchQueries) {
1809
+ const fullQuery = searchQuery + activeFilter + categoryFilter;
1810
+ const result = await this.client.searchRecords('sc_cat_item', fullQuery, limit);
1811
+ if (result.success && result.data?.result) {
1812
+ result.data.result.forEach((item) => {
1813
+ if (!allResults.has(item.sys_id)) {
1814
+ allResults.set(item.sys_id, {
1815
+ sys_id: item.sys_id,
1816
+ name: item.name,
1817
+ short_description: item.short_description,
1818
+ price: item.price,
1819
+ recurring_price: item.recurring_price,
1820
+ active: item.active,
1821
+ category: item.sc_categories?.display_value,
1822
+ catalog: item.sc_catalogs?.display_value,
1823
+ model_number: item.model,
1824
+ manufacturer: item.vendor,
1825
+ variables: []
1826
+ });
1827
+ }
1828
+ });
1829
+ }
1830
+ }
1831
+ // Get variables if requested
1832
+ if (include_variables && allResults.size > 0) {
1833
+ for (const [sys_id, item] of allResults) {
1834
+ const variablesResult = await this.client.searchRecords('item_option_new', `cat_item=${sys_id}`, 50);
1835
+ if (variablesResult.success && variablesResult.data?.result) {
1836
+ item.variables = variablesResult.data.result.map((v) => ({
1837
+ name: v.name,
1838
+ label: v.question_text,
1839
+ type: v.type,
1840
+ mandatory: v.mandatory,
1841
+ default_value: v.default_value
1842
+ }));
1843
+ }
1844
+ }
1845
+ }
1846
+ const resultsArray = Array.from(allResults.values());
1847
+ // Sort by relevance (exact matches first)
1848
+ resultsArray.sort((a, b) => {
1849
+ const aExact = a.name.toLowerCase() === query.toLowerCase();
1850
+ const bExact = b.name.toLowerCase() === query.toLowerCase();
1851
+ if (aExact && !bExact)
1852
+ return -1;
1853
+ if (!aExact && bExact)
1854
+ return 1;
1855
+ return 0;
1856
+ });
1857
+ if (resultsArray.length === 0) {
1858
+ // Provide suggestions if no results found
1859
+ const suggestions = this.generateSearchSuggestions(query);
1860
+ return {
1861
+ content: [{
1862
+ type: 'text',
1863
+ text: `โŒ No catalog items found for "${query}"\n\n` +
1864
+ `๐Ÿ’ก Suggestions:\n` +
1865
+ suggestions.map(s => `- Try searching for "${s}"`).join('\n') +
1866
+ `\n\n๐Ÿ’ก Tips:\n` +
1867
+ `- Check if the item name is spelled correctly\n` +
1868
+ `- Try searching with partial names (e.g., "iPhone" instead of "iPhone 6S")\n` +
1869
+ `- Use category filter to narrow down results\n` +
1870
+ `- Enable include_inactive if looking for retired items`
1871
+ }]
1872
+ };
1873
+ }
1874
+ // Format results
1875
+ let resultText = `๐Ÿ” Found ${resultsArray.length} catalog items matching "${query}":\n\n`;
1876
+ resultsArray.forEach((item, index) => {
1877
+ resultText += `${index + 1}. **${item.name}** (${item.sys_id})\n`;
1878
+ resultText += ` ๐Ÿ“ ${item.short_description || 'No description'}\n`;
1879
+ resultText += ` ๐Ÿ’ฐ Price: $${item.price || '0'} ${item.recurring_price ? `(+ $${item.recurring_price}/month)` : ''}\n`;
1880
+ resultText += ` ๐Ÿ“ Category: ${item.category || 'Uncategorized'}\n`;
1881
+ resultText += ` ๐Ÿ“š Catalog: ${item.catalog || 'Default'}\n`;
1882
+ if (item.model_number) {
1883
+ resultText += ` ๐Ÿ”ข Model: ${item.model_number}\n`;
1884
+ }
1885
+ if (item.manufacturer) {
1886
+ resultText += ` ๐Ÿญ Manufacturer: ${item.manufacturer}\n`;
1887
+ }
1888
+ resultText += ` โœ… Status: ${item.active ? 'Active' : 'Inactive'}\n`;
1889
+ if (item.variables && item.variables.length > 0) {
1890
+ resultText += ` ๐Ÿ“‹ Variables (${item.variables.length}):\n`;
1891
+ item.variables.forEach((v) => {
1892
+ resultText += ` - ${v.label} (${v.name}) - ${v.type}${v.mandatory ? ' *Required' : ''}\n`;
1893
+ });
1894
+ }
1895
+ resultText += '\n';
1896
+ });
1897
+ resultText += `\n๐Ÿ’ก Use snow_catalog_item_manager to create or update catalog items`;
1898
+ return {
1899
+ content: [{
1900
+ type: 'text',
1901
+ text: resultText
1902
+ }]
1903
+ };
1904
+ }
1905
+ catch (error) {
1906
+ logger_js_1.logger.error('Error searching catalog items:', error);
1907
+ throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Catalog search error: ${error}`);
1908
+ }
1909
+ }
1910
+ generateSearchVariations(query) {
1911
+ const variations = [];
1912
+ // Handle common product variations
1913
+ if (query.toLowerCase().includes('iphone')) {
1914
+ // Add variations without spaces, with different numbers
1915
+ const baseModel = query.replace(/iphone\s*/i, 'iPhone ');
1916
+ variations.push(baseModel);
1917
+ variations.push(baseModel.replace(' ', ''));
1918
+ // Try different model numbers
1919
+ const modelMatch = query.match(/\d+/);
1920
+ if (modelMatch) {
1921
+ const modelNum = parseInt(modelMatch[0]);
1922
+ variations.push(query.replace(modelNum.toString(), (modelNum + 1).toString()));
1923
+ variations.push(query.replace(modelNum.toString(), (modelNum - 1).toString()));
1924
+ }
1925
+ }
1926
+ // Handle laptop/computer variations
1927
+ if (query.toLowerCase().includes('laptop') || query.toLowerCase().includes('computer')) {
1928
+ variations.push('notebook', 'macbook', 'thinkpad', 'dell', 'hp');
1929
+ }
1930
+ // Handle phone variations
1931
+ if (query.toLowerCase().includes('phone') || query.toLowerCase().includes('mobile')) {
1932
+ variations.push('smartphone', 'android', 'samsung', 'pixel');
1933
+ }
1934
+ return variations;
1935
+ }
1936
+ generateSearchSuggestions(query) {
1937
+ const suggestions = [];
1938
+ if (query.toLowerCase().includes('iphone')) {
1939
+ suggestions.push('iPhone', 'Apple iPhone', 'iOS device', 'Apple mobile');
1940
+ }
1941
+ else if (query.toLowerCase().includes('laptop')) {
1942
+ suggestions.push('notebook', 'computer', 'workstation', 'MacBook', 'ThinkPad');
1943
+ }
1944
+ else if (query.toLowerCase().includes('software')) {
1945
+ suggestions.push('license', 'application', 'subscription', 'SaaS');
1946
+ }
1947
+ else {
1948
+ // Generic suggestions
1949
+ suggestions.push(query.split(' ')[0], // First word only
1950
+ query.substring(0, Math.floor(query.length / 2)), // First half
1951
+ 'hardware', 'software', 'equipment', 'device');
1952
+ }
1953
+ return [...new Set(suggestions)].filter(s => s && s !== query);
1954
+ }
1955
+ async handleTestFlowWithMock(args) {
1956
+ const { flow_id, create_test_user = true, test_user_data, mock_catalog_items = true, mock_catalog_data, test_inputs = {}, simulate_approvals = true, cleanup_after_test = true } = args;
1957
+ logger_js_1.logger.info(`Testing flow ${flow_id} with mock data`, {
1958
+ create_test_user,
1959
+ mock_catalog_items,
1960
+ simulate_approvals,
1961
+ cleanup_after_test
1962
+ });
1963
+ const testResults = {
1964
+ flow_id,
1965
+ test_run_id: `test_${Date.now()}`,
1966
+ created_data: {
1967
+ test_user: null,
1968
+ catalog_items: [],
1969
+ test_requests: []
1970
+ },
1971
+ execution_results: {
1972
+ status: 'pending',
1973
+ start_time: new Date().toISOString(),
1974
+ end_time: null,
1975
+ duration_ms: 0,
1976
+ execution_id: null,
1977
+ steps_executed: [],
1978
+ errors: [],
1979
+ approvals_simulated: []
1980
+ },
1981
+ cleanup_status: null
1982
+ };
1983
+ try {
1984
+ // Step 1: Find the flow
1985
+ const flowResult = await this.client.searchRecords('sys_hub_flow', `sys_id=${flow_id}^ORname=${flow_id}`, 1);
1986
+ if (!flowResult.success || !flowResult.data?.result?.length) {
1987
+ throw new Error(`Flow not found: ${flow_id}`);
1988
+ }
1989
+ const flow = flowResult.data.result[0];
1990
+ testResults.flow_id = flow.sys_id;
1991
+ // Step 2: Create test user if requested
1992
+ if (create_test_user) {
1993
+ const userData = test_user_data || {
1994
+ user_name: `test_user_${Date.now()}`,
1995
+ first_name: 'Test',
1996
+ last_name: 'User',
1997
+ email: `test_${Date.now()}@example.com`,
1998
+ department: 'IT',
1999
+ active: true
2000
+ };
2001
+ const userResult = await this.client.createRecord('sys_user', userData);
2002
+ if (userResult.success && userResult.data?.result) {
2003
+ testResults.created_data.test_user = {
2004
+ sys_id: userResult.data.result.sys_id,
2005
+ user_name: userData.user_name,
2006
+ email: userData.email
2007
+ };
2008
+ logger_js_1.logger.info('Created test user:', testResults.created_data.test_user);
2009
+ }
2010
+ }
2011
+ // Step 3: Create mock catalog items if requested
2012
+ if (mock_catalog_items) {
2013
+ const catalogData = mock_catalog_data || [
2014
+ {
2015
+ name: 'Test iPhone 6S',
2016
+ short_description: 'Test mobile device for flow testing',
2017
+ price: '699.00',
2018
+ active: true,
2019
+ billable: true
2020
+ },
2021
+ {
2022
+ name: 'Test Laptop',
2023
+ short_description: 'Test laptop for equipment provisioning',
2024
+ price: '1299.00',
2025
+ active: true,
2026
+ billable: true
2027
+ }
2028
+ ];
2029
+ for (const item of catalogData) {
2030
+ const catalogResult = await this.client.createRecord('sc_cat_item', item);
2031
+ if (catalogResult.success && catalogResult.data?.result) {
2032
+ testResults.created_data.catalog_items.push({
2033
+ sys_id: catalogResult.data.result.sys_id,
2034
+ name: item.name,
2035
+ price: item.price
2036
+ });
2037
+ logger_js_1.logger.info('Created mock catalog item:', item.name);
2038
+ }
2039
+ }
2040
+ }
2041
+ // Step 4: Execute the flow with test data
2042
+ try {
2043
+ // Prepare test inputs
2044
+ const flowInputs = {
2045
+ ...test_inputs,
2046
+ test_mode: true,
2047
+ test_run_id: testResults.test_run_id
2048
+ };
2049
+ // If we created a test user, use them as the requested_for
2050
+ if (testResults.created_data.test_user) {
2051
+ flowInputs.requested_for = testResults.created_data.test_user.sys_id;
2052
+ flowInputs.opened_by = testResults.created_data.test_user.sys_id;
2053
+ }
2054
+ // If we created catalog items, use the first one
2055
+ if (testResults.created_data.catalog_items.length > 0) {
2056
+ flowInputs.cat_item = testResults.created_data.catalog_items[0].sys_id;
2057
+ }
2058
+ // Execute flow via REST API
2059
+ const executeResult = await this.executeFlowViaAPI(flow.sys_id, flowInputs);
2060
+ if (executeResult.success) {
2061
+ testResults.execution_results.status = 'running';
2062
+ testResults.execution_results.execution_id = executeResult.execution_id;
2063
+ // Monitor flow execution
2064
+ const executionStatus = await this.monitorFlowExecution(executeResult.execution_id, simulate_approvals);
2065
+ testResults.execution_results = {
2066
+ ...testResults.execution_results,
2067
+ ...executionStatus
2068
+ };
2069
+ }
2070
+ else {
2071
+ testResults.execution_results.status = 'failed';
2072
+ testResults.execution_results.errors.push(executeResult.error || 'Unknown execution error');
2073
+ }
2074
+ }
2075
+ catch (execError) {
2076
+ testResults.execution_results.status = 'error';
2077
+ testResults.execution_results.errors.push(execError.message);
2078
+ logger_js_1.logger.error('Flow execution error:', execError);
2079
+ }
2080
+ // Step 5: Cleanup if requested
2081
+ if (cleanup_after_test) {
2082
+ const cleanupResults = {
2083
+ user_deleted: false,
2084
+ catalog_items_deleted: 0,
2085
+ requests_cancelled: 0
2086
+ };
2087
+ // Delete test user
2088
+ if (testResults.created_data.test_user) {
2089
+ const deleteUserResult = await this.client.deleteRecord('sys_user', testResults.created_data.test_user.sys_id);
2090
+ cleanupResults.user_deleted = deleteUserResult.success;
2091
+ }
2092
+ // Delete catalog items
2093
+ for (const item of testResults.created_data.catalog_items) {
2094
+ const deleteItemResult = await this.client.deleteRecord('sc_cat_item', item.sys_id);
2095
+ if (deleteItemResult.success) {
2096
+ cleanupResults.catalog_items_deleted++;
2097
+ }
2098
+ }
2099
+ testResults.cleanup_status = cleanupResults;
2100
+ }
2101
+ // Format results
2102
+ const resultText = this.formatTestResults(testResults);
2103
+ return {
2104
+ content: [{
2105
+ type: 'text',
2106
+ text: resultText
2107
+ }]
2108
+ };
2109
+ }
2110
+ catch (error) {
2111
+ logger_js_1.logger.error('Error testing flow with mock data:', error);
2112
+ testResults.execution_results.status = 'error';
2113
+ testResults.execution_results.errors.push(error.message);
2114
+ return {
2115
+ content: [{
2116
+ type: 'text',
2117
+ text: `โŒ Flow test failed\n\nError: ${error.message}\n\nTest Results:\n${JSON.stringify(testResults, null, 2)}`
2118
+ }]
2119
+ };
2120
+ }
2121
+ }
2122
+ async executeFlowViaAPI(flowId, inputs) {
2123
+ try {
2124
+ // Use the ServiceNow Flow API to execute the flow
2125
+ const result = await this.client.createRecord('sys_flow_context', {
2126
+ flow: flowId,
2127
+ inputs: JSON.stringify(inputs),
2128
+ state: 'executing'
2129
+ });
2130
+ if (result.success && result.data?.result) {
2131
+ return {
2132
+ success: true,
2133
+ execution_id: result.data.result.sys_id
2134
+ };
2135
+ }
2136
+ return {
2137
+ success: false,
2138
+ error: 'Failed to start flow execution'
2139
+ };
2140
+ }
2141
+ catch (error) {
2142
+ logger_js_1.logger.error('Error executing flow via API:', error);
2143
+ return {
2144
+ success: false,
2145
+ error: error.message
2146
+ };
2147
+ }
2148
+ }
2149
+ async monitorFlowExecution(executionId, simulateApprovals) {
2150
+ const maxWaitTime = 60000; // 60 seconds
2151
+ const pollInterval = 2000; // 2 seconds
2152
+ const startTime = Date.now();
2153
+ const executionResults = {
2154
+ status: 'running',
2155
+ steps_executed: [],
2156
+ approvals_simulated: [],
2157
+ errors: [],
2158
+ end_time: null,
2159
+ duration_ms: 0
2160
+ };
2161
+ while (Date.now() - startTime < maxWaitTime) {
2162
+ try {
2163
+ // Check flow context status
2164
+ const contextResult = await this.client.getRecord('sys_flow_context', executionId);
2165
+ if (!contextResult.success || !contextResult.data?.result) {
2166
+ executionResults.errors.push('Failed to get flow context');
2167
+ break;
2168
+ }
2169
+ const context = contextResult.data.result;
2170
+ executionResults.status = context.state;
2171
+ // Check for completed state
2172
+ if (context.state === 'complete' || context.state === 'cancelled' || context.state === 'error') {
2173
+ executionResults.end_time = context.sys_updated_on;
2174
+ executionResults.duration_ms = Date.now() - startTime;
2175
+ break;
2176
+ }
2177
+ // Check for approvals if simulating
2178
+ if (simulateApprovals) {
2179
+ const approvalResult = await this.client.searchRecords('sysapproval_approver', `source_table=sys_flow_context^document_id=${executionId}^state=requested`, 10);
2180
+ if (approvalResult.success && approvalResult.data?.result?.length > 0) {
2181
+ for (const approval of approvalResult.data.result) {
2182
+ // Auto-approve
2183
+ const approveResult = await this.client.updateRecord('sysapproval_approver', approval.sys_id, {
2184
+ state: 'approved',
2185
+ comments: 'Auto-approved by test framework'
2186
+ });
2187
+ if (approveResult.success) {
2188
+ executionResults.approvals_simulated.push({
2189
+ sys_id: approval.sys_id,
2190
+ approver: approval.approver.display_value,
2191
+ approved_at: new Date().toISOString()
2192
+ });
2193
+ }
2194
+ }
2195
+ }
2196
+ }
2197
+ // Wait before next poll
2198
+ await new Promise(resolve => setTimeout(resolve, pollInterval));
2199
+ }
2200
+ catch (error) {
2201
+ executionResults.errors.push(error.message);
2202
+ executionResults.status = 'error';
2203
+ break;
2204
+ }
2205
+ }
2206
+ if (executionResults.status === 'running') {
2207
+ executionResults.status = 'timeout';
2208
+ executionResults.errors.push('Flow execution timed out after 60 seconds');
2209
+ }
2210
+ return executionResults;
2211
+ }
2212
+ formatTestResults(testResults) {
2213
+ let text = `๐Ÿงช Flow Test Results\n${'='.repeat(50)}\n\n`;
2214
+ text += `**Flow ID:** ${testResults.flow_id}\n`;
2215
+ text += `**Test Run ID:** ${testResults.test_run_id}\n\n`;
2216
+ // Test Data Created
2217
+ text += `๐Ÿ“‹ **Test Data Created:**\n`;
2218
+ if (testResults.created_data.test_user) {
2219
+ text += ` โœ… Test User: ${testResults.created_data.test_user.user_name} (${testResults.created_data.test_user.sys_id})\n`;
2220
+ }
2221
+ if (testResults.created_data.catalog_items.length > 0) {
2222
+ text += ` โœ… Catalog Items: ${testResults.created_data.catalog_items.length} created\n`;
2223
+ testResults.created_data.catalog_items.forEach((item) => {
2224
+ text += ` - ${item.name} ($${item.price})\n`;
2225
+ });
2226
+ }
2227
+ // Execution Results
2228
+ text += `\n๐Ÿš€ **Execution Results:**\n`;
2229
+ text += ` Status: ${testResults.execution_results.status === 'complete' ? 'โœ…' : 'โŒ'} ${testResults.execution_results.status}\n`;
2230
+ if (testResults.execution_results.start_time) {
2231
+ text += ` Start Time: ${testResults.execution_results.start_time}\n`;
2232
+ }
2233
+ if (testResults.execution_results.end_time) {
2234
+ text += ` End Time: ${testResults.execution_results.end_time}\n`;
2235
+ }
2236
+ if (testResults.execution_results.duration_ms) {
2237
+ text += ` Duration: ${(testResults.execution_results.duration_ms / 1000).toFixed(2)}s\n`;
2238
+ }
2239
+ if (testResults.execution_results.approvals_simulated.length > 0) {
2240
+ text += `\n โœ… Approvals Simulated: ${testResults.execution_results.approvals_simulated.length}\n`;
2241
+ testResults.execution_results.approvals_simulated.forEach((approval) => {
2242
+ text += ` - ${approval.approver} at ${approval.approved_at}\n`;
2243
+ });
2244
+ }
2245
+ if (testResults.execution_results.errors.length > 0) {
2246
+ text += `\n โŒ Errors:\n`;
2247
+ testResults.execution_results.errors.forEach((error) => {
2248
+ text += ` - ${error}\n`;
2249
+ });
2250
+ }
2251
+ // Cleanup Status
2252
+ if (testResults.cleanup_status) {
2253
+ text += `\n๐Ÿงน **Cleanup Status:**\n`;
2254
+ text += ` User Deleted: ${testResults.cleanup_status.user_deleted ? 'โœ…' : 'โŒ'}\n`;
2255
+ text += ` Catalog Items Deleted: ${testResults.cleanup_status.catalog_items_deleted}\n`;
2256
+ text += ` Requests Cancelled: ${testResults.cleanup_status.requests_cancelled}\n`;
2257
+ }
2258
+ // Recommendations
2259
+ text += `\n๐Ÿ’ก **Recommendations:**\n`;
2260
+ if (testResults.execution_results.status === 'complete') {
2261
+ text += ` โœ… Flow executed successfully! Ready for production use.\n`;
2262
+ }
2263
+ else if (testResults.execution_results.status === 'timeout') {
2264
+ text += ` โš ๏ธ Flow execution timed out. Consider:\n`;
2265
+ text += ` - Checking for infinite loops\n`;
2266
+ text += ` - Reviewing wait conditions\n`;
2267
+ text += ` - Optimizing flow performance\n`;
2268
+ }
2269
+ else {
2270
+ text += ` โŒ Flow execution failed. Please:\n`;
2271
+ text += ` - Review error messages above\n`;
2272
+ text += ` - Check flow configuration\n`;
2273
+ text += ` - Verify all dependencies exist\n`;
2274
+ }
2275
+ return text;
2276
+ }
2277
+ async handleLinkCatalogToFlow(args) {
2278
+ const { catalog_item_id, flow_id, link_type = 'flow_catalog_process', variable_mapping = [], trigger_condition = 'current.stage == "request_approved"', execution_options = {}, test_link = false } = args;
2279
+ logger_js_1.logger.info(`Linking catalog item ${catalog_item_id} to flow ${flow_id}`, {
2280
+ link_type,
2281
+ variable_mapping_count: variable_mapping.length
2282
+ });
2283
+ const linkResults = {
2284
+ catalog_item: null,
2285
+ flow: null,
2286
+ link_created: false,
2287
+ link_details: null,
2288
+ variable_mappings: [],
2289
+ test_results: null,
2290
+ errors: []
2291
+ };
2292
+ try {
2293
+ // Step 1: Find the catalog item
2294
+ const catalogResult = await this.client.searchRecords('sc_cat_item', `sys_id=${catalog_item_id}^ORname=${catalog_item_id}`, 1);
2295
+ if (!catalogResult.success || !catalogResult.data?.result?.length) {
2296
+ throw new Error(`Catalog item not found: ${catalog_item_id}`);
2297
+ }
2298
+ linkResults.catalog_item = {
2299
+ sys_id: catalogResult.data.result[0].sys_id,
2300
+ name: catalogResult.data.result[0].name,
2301
+ category: catalogResult.data.result[0].sc_categories?.display_value
2302
+ };
2303
+ // Step 2: Find the flow
2304
+ const flowResult = await this.client.searchRecords('sys_hub_flow', `sys_id=${flow_id}^ORname=${flow_id}`, 1);
2305
+ if (!flowResult.success || !flowResult.data?.result?.length) {
2306
+ throw new Error(`Flow not found: ${flow_id}`);
2307
+ }
2308
+ linkResults.flow = {
2309
+ sys_id: flowResult.data.result[0].sys_id,
2310
+ name: flowResult.data.result[0].name,
2311
+ type: flowResult.data.result[0].type
2312
+ };
2313
+ // Step 3: Create the link based on link_type
2314
+ switch (link_type) {
2315
+ case 'flow_catalog_process': {
2316
+ // Modern approach: Use Flow Designer catalog process
2317
+ const processData = {
2318
+ catalog_item: linkResults.catalog_item.sys_id,
2319
+ flow: linkResults.flow.sys_id,
2320
+ active: true,
2321
+ condition: trigger_condition,
2322
+ run_as: execution_options.run_as || 'system',
2323
+ wait_for_completion: execution_options.wait_for_completion !== false
2324
+ };
2325
+ const processResult = await this.client.createRecord('sc_cat_item_producer', processData);
2326
+ if (processResult.success && processResult.data?.result) {
2327
+ linkResults.link_created = true;
2328
+ linkResults.link_details = {
2329
+ type: 'flow_catalog_process',
2330
+ sys_id: processResult.data.result.sys_id,
2331
+ table: 'sc_cat_item_producer'
2332
+ };
2333
+ // Create variable mappings
2334
+ for (const mapping of variable_mapping) {
2335
+ const mappingData = {
2336
+ producer: processResult.data.result.sys_id,
2337
+ catalog_variable: mapping.catalog_variable,
2338
+ flow_input: mapping.flow_input,
2339
+ transform_script: mapping.transform || ''
2340
+ };
2341
+ const mappingResult = await this.client.createRecord('sc_cat_item_producer_mapping', mappingData);
2342
+ if (mappingResult.success) {
2343
+ linkResults.variable_mappings.push({
2344
+ catalog_variable: mapping.catalog_variable,
2345
+ flow_input: mapping.flow_input,
2346
+ mapping_sys_id: mappingResult.data?.result?.sys_id
2347
+ });
2348
+ }
2349
+ }
2350
+ }
2351
+ break;
2352
+ }
2353
+ case 'workflow': {
2354
+ // Legacy approach: Direct workflow assignment
2355
+ const updateData = {
2356
+ workflow: linkResults.flow.sys_id,
2357
+ no_order: false,
2358
+ no_proceed_checkout: false,
2359
+ no_quantity: false,
2360
+ no_delivery_time: false
2361
+ };
2362
+ const updateResult = await this.client.updateRecord('sc_cat_item', linkResults.catalog_item.sys_id, updateData);
2363
+ if (updateResult.success) {
2364
+ linkResults.link_created = true;
2365
+ linkResults.link_details = {
2366
+ type: 'workflow',
2367
+ field: 'workflow',
2368
+ value: linkResults.flow.sys_id
2369
+ };
2370
+ }
2371
+ break;
2372
+ }
2373
+ case 'process_engine': {
2374
+ // Process Engine approach
2375
+ const engineData = {
2376
+ catalog_item: linkResults.catalog_item.sys_id,
2377
+ engine_type: 'flow',
2378
+ engine_id: linkResults.flow.sys_id,
2379
+ active: true,
2380
+ order: 100,
2381
+ condition: trigger_condition
2382
+ };
2383
+ const engineResult = await this.client.createRecord('sc_process_flow', engineData);
2384
+ if (engineResult.success && engineResult.data?.result) {
2385
+ linkResults.link_created = true;
2386
+ linkResults.link_details = {
2387
+ type: 'process_engine',
2388
+ sys_id: engineResult.data.result.sys_id,
2389
+ table: 'sc_process_flow'
2390
+ };
2391
+ }
2392
+ break;
2393
+ }
2394
+ }
2395
+ // Step 4: Test the link if requested
2396
+ if (test_link && linkResults.link_created) {
2397
+ logger_js_1.logger.info('Testing catalog-flow link with sample request');
2398
+ const testRequestData = {
2399
+ cat_item: linkResults.catalog_item.sys_id,
2400
+ requested_for: 'admin', // Default to admin user
2401
+ quantity: 1,
2402
+ comments: `Test request for catalog-flow link verification [${new Date().toISOString()}]`
2403
+ };
2404
+ // Add any mapped variables with test values
2405
+ for (const mapping of linkResults.variable_mappings) {
2406
+ testRequestData[`variables.${mapping.catalog_variable}`] = 'Test Value';
2407
+ }
2408
+ const testResult = await this.client.createRecord('sc_request', testRequestData);
2409
+ if (testResult.success && testResult.data?.result) {
2410
+ linkResults.test_results = {
2411
+ request_created: true,
2412
+ request_number: testResult.data.result.number,
2413
+ request_sys_id: testResult.data.result.sys_id,
2414
+ status: 'Check the request in ServiceNow to verify flow execution'
2415
+ };
2416
+ }
2417
+ else {
2418
+ linkResults.test_results = {
2419
+ request_created: false,
2420
+ error: 'Failed to create test request'
2421
+ };
2422
+ }
2423
+ }
2424
+ // Format results
2425
+ let resultText = `๐Ÿ”— Catalog-Flow Link Results\n${'='.repeat(50)}\n\n`;
2426
+ resultText += `**Catalog Item:** ${linkResults.catalog_item.name} (${linkResults.catalog_item.sys_id})\n`;
2427
+ resultText += `**Flow:** ${linkResults.flow.name} (${linkResults.flow.sys_id})\n`;
2428
+ resultText += `**Link Type:** ${link_type}\n\n`;
2429
+ if (linkResults.link_created) {
2430
+ resultText += `โœ… **Link Created Successfully!**\n`;
2431
+ resultText += ` Type: ${linkResults.link_details.type}\n`;
2432
+ if (linkResults.link_details.sys_id) {
2433
+ resultText += ` Record: ${linkResults.link_details.table} (${linkResults.link_details.sys_id})\n`;
2434
+ }
2435
+ if (linkResults.variable_mappings.length > 0) {
2436
+ resultText += `\n๐Ÿ“‹ **Variable Mappings Created:**\n`;
2437
+ linkResults.variable_mappings.forEach(mapping => {
2438
+ resultText += ` - ${mapping.catalog_variable} โ†’ ${mapping.flow_input}\n`;
2439
+ });
2440
+ }
2441
+ if (linkResults.test_results) {
2442
+ resultText += `\n๐Ÿงช **Test Results:**\n`;
2443
+ if (linkResults.test_results.request_created) {
2444
+ resultText += ` โœ… Test request created: ${linkResults.test_results.request_number}\n`;
2445
+ resultText += ` ๐Ÿ“ ${linkResults.test_results.status}\n`;
2446
+ }
2447
+ else {
2448
+ resultText += ` โŒ ${linkResults.test_results.error}\n`;
2449
+ }
2450
+ }
2451
+ resultText += `\n๐Ÿ’ก **Next Steps:**\n`;
2452
+ resultText += `1. Test the catalog item by creating a request\n`;
2453
+ resultText += `2. Verify the flow executes with correct inputs\n`;
2454
+ resultText += `3. Monitor the request fulfillment process\n`;
2455
+ if (link_type === 'flow_catalog_process') {
2456
+ resultText += `\n๐Ÿ“ **Note:** Using modern Flow Designer catalog process\n`;
2457
+ resultText += ` - Flow will trigger on: ${trigger_condition}\n`;
2458
+ resultText += ` - Execution context: ${execution_options.run_as || 'system'}\n`;
2459
+ }
2460
+ }
2461
+ else {
2462
+ resultText += `โŒ **Failed to create link**\n`;
2463
+ if (linkResults.errors.length > 0) {
2464
+ resultText += `\nErrors:\n`;
2465
+ linkResults.errors.forEach(error => {
2466
+ resultText += ` - ${error}\n`;
2467
+ });
2468
+ }
2469
+ }
2470
+ return {
2471
+ content: [{
2472
+ type: 'text',
2473
+ text: resultText
2474
+ }]
2475
+ };
2476
+ }
2477
+ catch (error) {
2478
+ logger_js_1.logger.error('Error linking catalog to flow:', error);
2479
+ linkResults.errors.push(error.message);
2480
+ return {
2481
+ content: [{
2482
+ type: 'text',
2483
+ text: `โŒ Failed to link catalog to flow\n\nError: ${error.message}\n\n` +
2484
+ `๐Ÿ’ก Troubleshooting:\n` +
2485
+ `1. Verify catalog item exists: ${catalog_item_id}\n` +
2486
+ `2. Verify flow exists: ${flow_id}\n` +
2487
+ `3. Check permissions for creating ${link_type} links\n` +
2488
+ `4. Ensure flow is active and published\n\n` +
2489
+ `Results: ${JSON.stringify(linkResults, null, 2)}`
2490
+ }]
2491
+ };
2492
+ }
2493
+ }
1600
2494
  getServiceNowUrl() {
1601
2495
  // This would need to get the instance URL from credentials
1602
2496
  return 'https://instance.service-now.com';