snow-flow 2.0.2 → 2.0.3

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 (173) hide show
  1. package/{.claude/mcp-config.json → .mcp.json} +13 -12
  2. package/.releaserc.json +47 -0
  3. package/README.md +6 -0
  4. package/package.json +1 -1
  5. package/.claude/claude-settings.json +0 -15
  6. package/.claude/commands/analysis/README.md +0 -9
  7. package/.claude/commands/automation/README.md +0 -9
  8. package/.claude/commands/github/README.md +0 -11
  9. package/.claude/commands/helpers/standard-checkpoint-hooks.sh +0 -179
  10. package/.claude/commands/hooks/README.md +0 -11
  11. package/.claude/commands/monitoring/README.md +0 -9
  12. package/.claude/commands/optimization/README.md +0 -9
  13. package/.claude/commands/training/README.md +0 -9
  14. package/.claude/commands/workflows/README.md +0 -9
  15. package/.claude/config.json +0 -36
  16. package/.claude/helpers/github-setup.sh +0 -28
  17. package/.claude/helpers/quick-start.sh +0 -19
  18. package/.claude/helpers/setup-mcp.sh +0 -18
  19. package/.claude/settings.json +0 -114
  20. package/.claude/settings.local.json +0 -33
  21. package/.claude-flow/metrics/agent-metrics.json +0 -1
  22. package/.claude-flow/metrics/performance.json +0 -9
  23. package/.claude-flow/metrics/system-metrics.json +0 -15986
  24. package/.claude-flow/metrics/task-metrics.json +0 -10
  25. package/.roo/README.md +0 -402
  26. package/.roo/mcp-list.txt +0 -257
  27. package/.roo/mcp.json +0 -32
  28. package/.roo/workflows/basic-tdd.json +0 -32
  29. package/.roomodes +0 -201
  30. package/CLAUDE.md +0 -393
  31. package/bin/snow-flow.js +0 -3
  32. package/dist/cli.js.bak +0 -2052
  33. package/dist/config/snow-flow-config.d.ts +0 -1492
  34. package/dist/config/snow-flow-config.js +0 -938
  35. package/scripts/create-env.js +0 -91
  36. package/scripts/postinstall.js +0 -30
  37. package/scripts/register-mcp-servers.js +0 -210
  38. package/scripts/reset-mcp-servers.js +0 -246
  39. package/scripts/setup-mcp.js +0 -106
  40. package/scripts/update-version.js +0 -31
  41. package/src/agents/README.md +0 -192
  42. package/src/agents/base-agent.ts +0 -251
  43. package/src/agents/coordinator.ts +0 -971
  44. package/src/agents/index.ts +0 -18
  45. package/src/agents/queen-403-handler.ts +0 -230
  46. package/src/agents/queen-agent.ts +0 -1399
  47. package/src/agents/script-writer-agent.ts +0 -769
  48. package/src/agents/security-agent.ts +0 -726
  49. package/src/agents/widget-creator-agent.ts +0 -568
  50. package/src/api/error-handling.ts +0 -1758
  51. package/src/api/natural-language-mapper.ts +0 -630
  52. package/src/api/performance-optimizer.ts +0 -891
  53. package/src/api/transaction-manager.ts +0 -754
  54. package/src/cli/deploy-artifact.ts +0 -304
  55. package/src/cli/snow-flow-cli-integration.ts +0 -434
  56. package/src/cli.ts +0 -4455
  57. package/src/cli.ts.bak +0 -5616
  58. package/src/compliance/advanced-compliance-system.ts +0 -1268
  59. package/src/compliance/index.ts +0 -19
  60. package/src/config/snow-flow-config.ts +0 -1019
  61. package/src/documentation/index.ts +0 -16
  62. package/src/documentation/self-documenting-system.ts +0 -1421
  63. package/src/dynamic-version.ts +0 -44
  64. package/src/healing/index.ts +0 -20
  65. package/src/healing/self-healing-system.ts +0 -1435
  66. package/src/health/README.md +0 -161
  67. package/src/health/system-health.ts +0 -995
  68. package/src/index.ts +0 -60
  69. package/src/intelligence/acl-analyzer.ts +0 -377
  70. package/src/intelligence/auto-resolution-engine.ts +0 -643
  71. package/src/intelligence/gap-analysis-engine.ts +0 -488
  72. package/src/intelligence/manual-instructions-generator.ts +0 -744
  73. package/src/intelligence/mcp-coverage-analyzer.ts +0 -708
  74. package/src/intelligence/multi-pass-requirements-analyzer.ts +0 -603
  75. package/src/intelligence/performance-recommendations-engine.ts +0 -1223
  76. package/src/intelligence/requirements-analyzer.ts +0 -1148
  77. package/src/intelligence/task-analyzer.ts +0 -628
  78. package/src/managers/scope-manager.ts +0 -555
  79. package/src/mcp/advanced/servicenow-advanced-features-mcp.ts +0 -16110
  80. package/src/mcp/base-mcp-server.ts +0 -660
  81. package/src/mcp/http-transport-wrapper.ts +0 -426
  82. package/src/mcp/service-discovery-client.ts +0 -312
  83. package/src/mcp/servicenow-automation-mcp-refactored.ts +0 -678
  84. package/src/mcp/servicenow-automation-mcp.ts +0 -838
  85. package/src/mcp/servicenow-deployment-mcp-refactored.ts +0 -1292
  86. package/src/mcp/servicenow-deployment-mcp.ts +0 -7244
  87. package/src/mcp/servicenow-graph-memory-mcp-refactored.ts +0 -721
  88. package/src/mcp/servicenow-graph-memory-mcp.ts +0 -832
  89. package/src/mcp/servicenow-integration-mcp-refactored.ts +0 -655
  90. package/src/mcp/servicenow-integration-mcp.ts +0 -819
  91. package/src/mcp/servicenow-intelligent-mcp-refactored.ts +0 -977
  92. package/src/mcp/servicenow-intelligent-mcp.ts +0 -4638
  93. package/src/mcp/servicenow-mcp-server.ts +0 -728
  94. package/src/mcp/servicenow-memory-mcp.ts +0 -474
  95. package/src/mcp/servicenow-operations-mcp-refactored.ts +0 -1995
  96. package/src/mcp/servicenow-operations-mcp.ts +0 -3384
  97. package/src/mcp/servicenow-platform-development-mcp-refactored.ts +0 -611
  98. package/src/mcp/servicenow-platform-development-mcp.ts +0 -970
  99. package/src/mcp/servicenow-progressive-indexer.ts +0 -370
  100. package/src/mcp/servicenow-reporting-analytics-mcp-refactored.ts +0 -695
  101. package/src/mcp/servicenow-reporting-analytics-mcp.ts +0 -1123
  102. package/src/mcp/servicenow-security-compliance-mcp-refactored.ts +0 -642
  103. package/src/mcp/servicenow-security-compliance-mcp.ts +0 -1157
  104. package/src/mcp/servicenow-update-set-mcp-refactored.ts +0 -751
  105. package/src/mcp/servicenow-update-set-mcp.ts +0 -811
  106. package/src/mcp/shared/agent-context-provider.ts +0 -365
  107. package/src/mcp/shared/base-mcp-server.ts +0 -2463
  108. package/src/mcp/shared/mcp-memory-manager.ts +0 -540
  109. package/src/mcp/shared/mcp-resource-manager.ts +0 -367
  110. package/src/mcp/snow-flow-mcp.ts +0 -911
  111. package/src/mcp/start-all-mcp-servers.ts +0 -102
  112. package/src/mcp/start-servicenow-mcp.ts +0 -67
  113. package/src/monitoring/enhanced-monitoring-system.ts +0 -1345
  114. package/src/monitoring/performance-tracker.ts +0 -777
  115. package/src/optimization/cost-optimization-engine.ts +0 -1125
  116. package/src/optimization/index.ts +0 -10
  117. package/src/queen/README.md +0 -403
  118. package/src/queen/agent-factory.ts +0 -574
  119. package/src/queen/index.ts +0 -94
  120. package/src/queen/mcp-execution-bridge.ts +0 -681
  121. package/src/queen/neural-learning.ts +0 -319
  122. package/src/queen/parallel-agent-engine.ts +0 -848
  123. package/src/queen/queen-memory-system.ts +0 -490
  124. package/src/queen/queen-memory.ts +0 -456
  125. package/src/queen/servicenow-queen.ts +0 -1029
  126. package/src/queen/types.ts +0 -87
  127. package/src/rollback/smart-rollback-system.ts +0 -866
  128. package/src/schemas/deployment.schema.json +0 -58
  129. package/src/schemas/flow.schema.json +0 -79
  130. package/src/schemas/widget.schema.json +0 -72
  131. package/src/snow-flow-system.ts +0 -541
  132. package/src/sparc/sparc-help.ts +0 -49
  133. package/src/sparc/team-sparc.ts +0 -88
  134. package/src/strategies/global-scope-strategy.ts +0 -736
  135. package/src/templates/base/application.template.json +0 -45
  136. package/src/templates/base/business_rule.template.json +0 -33
  137. package/src/templates/base/script_include.template.json +0 -18
  138. package/src/templates/base/table.template.json +0 -64
  139. package/src/templates/base/widget.template.json +0 -25
  140. package/src/templates/patterns/composite.incident-management.template.json +0 -140
  141. package/src/templates/patterns/widget.dashboard.template.json +0 -238
  142. package/src/templates/patterns/widget.datatable.template.json +0 -292
  143. package/src/testing/integration-test-suite.ts +0 -1276
  144. package/src/types/index.ts +0 -183
  145. package/src/types/neo4j-driver.d.ts +0 -23
  146. package/src/types/servicenow.types.ts +0 -307
  147. package/src/types/snow-flow.types.ts +0 -186
  148. package/src/types/todo.types.ts +0 -70
  149. package/src/utils/action-type-cache.ts +0 -286
  150. package/src/utils/agent-detector.ts +0 -573
  151. package/src/utils/artifact-tracker.ts +0 -379
  152. package/src/utils/dependency-detector.ts +0 -233
  153. package/src/utils/deployment-metadata-handler.ts +0 -518
  154. package/src/utils/error-recovery.ts +0 -680
  155. package/src/utils/logger.ts +0 -60
  156. package/src/utils/mcp-auth-middleware.ts +0 -262
  157. package/src/utils/mcp-config-manager.ts +0 -270
  158. package/src/utils/mcp-server-manager.ts +0 -446
  159. package/src/utils/mcp-tool-registry.ts +0 -298
  160. package/src/utils/migrate-snow-flow.ts +0 -98
  161. package/src/utils/oauth-html-templates.ts +0 -188
  162. package/src/utils/scope-utils.ts +0 -443
  163. package/src/utils/servicenow-client.ts +0 -3321
  164. package/src/utils/servicenow-id-generator.ts +0 -181
  165. package/src/utils/snow-memory-manager.ts +0 -605
  166. package/src/utils/snow-oauth.ts +0 -797
  167. package/src/utils/template-engine.ts +0 -463
  168. package/src/utils/theme-manager.ts +0 -295
  169. package/src/utils/unified-auth-store.ts +0 -222
  170. package/src/utils/update-set-importer.ts +0 -422
  171. package/src/utils/update-set-xml-packager.ts +0 -371
  172. package/src/utils/widget-template-generator.ts +0 -1744
  173. package/src/version.ts +0 -1950
@@ -1,3321 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * ServiceNow API Client
4
- * Handles all ServiceNow API operations with OAuth authentication
5
- */
6
-
7
- import axios, { AxiosInstance } from 'axios';
8
- import https from 'https';
9
- import { ServiceNowOAuth, ServiceNowCredentials } from './snow-oauth';
10
- import { ActionTypeCache } from './action-type-cache';
11
- import { snowFlowConfig } from '../config/snow-flow-config.js';
12
- import { widgetTemplateGenerator } from './widget-template-generator.js';
13
- import { Logger } from './logger';
14
- import { unifiedAuthStore } from './unified-auth-store.js';
15
- // Flow structure builder removed in v1.4.0
16
- // import { generateFlowComponents, createActionInstances, buildLogicChain, validateFlowComponents, generateFlowXML, FlowDefinition, ServiceNowFlowComponents, ACTION_TYPE_IDS } from './flow-structure-builder';
17
-
18
- export interface ServiceNowWidget {
19
- sys_id?: string;
20
- name: string;
21
- id: string;
22
- title: string;
23
- description: string;
24
- template: string;
25
- css: string;
26
- client_script: string;
27
- server_script: string;
28
- option_schema?: string;
29
- demo_data?: string;
30
- has_preview?: boolean;
31
- category: string;
32
- }
33
-
34
- export interface ServiceNowWorkflow {
35
- sys_id?: string;
36
- name: string;
37
- description: string;
38
- active: boolean;
39
- workflow_version: string;
40
- table?: string;
41
- condition?: string;
42
- }
43
-
44
- export interface ServiceNowApplication {
45
- sys_id?: string;
46
- name: string;
47
- scope: string;
48
- version: string;
49
- short_description: string;
50
- description: string;
51
- vendor: string;
52
- vendor_prefix: string;
53
- template?: string;
54
- logo?: string;
55
- active: boolean;
56
- }
57
-
58
- export interface ServiceNowAPIResponse<T> {
59
- success: boolean;
60
- data?: T;
61
- error?: string;
62
- result?: T[];
63
- }
64
-
65
- export class ServiceNowClient {
66
- private client: AxiosInstance;
67
- private oauth: ServiceNowOAuth;
68
- private credentials: ServiceNowCredentials | null = null;
69
- private actionTypeCache: ActionTypeCache;
70
- private logger: Logger;
71
- private deploymentTimeout: number;
72
-
73
- constructor() {
74
- this.logger = new Logger('ServiceNowClient');
75
- this.oauth = new ServiceNowOAuth();
76
- // 🔒 SSL/TLS Certificate Validation Fix: Allow self-signed certificates for ServiceNow dev instances
77
- const httpsAgent = new https.Agent({
78
- rejectUnauthorized: false, // Allow self-signed certificates for dev instances (fixes "certificate has expired" errors)
79
- checkServerIdentity: (hostname, cert) => {
80
- // Custom certificate validation for ServiceNow instances
81
- // Allow *.service-now.com and user-configured instances
82
- const validHosts = [
83
- /.*\.service-now\.com$/,
84
- /.*\.servicenow\.com$/
85
- ];
86
-
87
- // Get configured instance hostname
88
- const instanceUrl = process.env.SNOW_INSTANCE;
89
- if (instanceUrl) {
90
- try {
91
- const instanceHostname = new URL(instanceUrl.startsWith('http') ? instanceUrl : `https://${instanceUrl}`).hostname;
92
- validHosts.push(new RegExp(`^${instanceHostname.replace(/\./g, '\\.')}$`));
93
- } catch (error) {
94
- // Invalid URL format, rely on default validation
95
- }
96
- }
97
-
98
- // Check if hostname matches allowed patterns
99
- if (validHosts.some(pattern => pattern.test(hostname))) {
100
- return undefined; // Valid hostname
101
- }
102
-
103
- // For development/testing environments, allow localhost/127.0.0.1
104
- if (process.env.NODE_ENV === 'development' &&
105
- (hostname === 'localhost' || hostname === '127.0.0.1' || hostname.startsWith('192.168.'))) {
106
- return undefined;
107
- }
108
-
109
- return new Error(`Certificate hostname mismatch: ${hostname} not allowed`);
110
- },
111
- // secureProtocol removed to avoid conflict with minVersion/maxVersion
112
- ciphers: [
113
- 'ECDHE-RSA-AES128-GCM-SHA256',
114
- 'ECDHE-RSA-AES256-GCM-SHA384',
115
- 'ECDHE-RSA-AES128-SHA256',
116
- 'ECDHE-RSA-AES256-SHA384'
117
- ].join(':'), // Only allow secure ciphers
118
- honorCipherOrder: true,
119
- maxVersion: 'TLSv1.3',
120
- minVersion: 'TLSv1.2'
121
- });
122
-
123
- this.client = axios.create({
124
- timeout: snowFlowConfig.servicenow.timeout,
125
- headers: {
126
- 'Content-Type': 'application/json',
127
- 'Accept': 'application/json'
128
- },
129
- httpsAgent, // Use secure HTTPS agent with certificate validation
130
- // Additional security headers
131
- maxRedirects: 5,
132
- validateStatus: (status) => status < 400 // Reject 4xx and 5xx by default
133
- });
134
-
135
- // Store deployment timeout from environment
136
- this.deploymentTimeout = parseInt(process.env.SNOW_DEPLOYMENT_TIMEOUT || '300000'); // 5 minutes default
137
-
138
- // 🔧 CRITICAL FIX: Add makeRequest method to Axios instance to fix phantom calls
139
- // Some code expects makeRequest to exist on this.client (the Axios instance)
140
- (this.client as any).makeRequest = async (config: any) => {
141
- this.logger.debug('🔧 AXIOS makeRequest called! Config:', config);
142
- this.logger.debug('🔧 Routing to appropriate HTTP method...');
143
-
144
- // Route to the appropriate Axios method based on the request config
145
- const method = (config.method || 'GET').toLowerCase();
146
- const url = config.url || config.endpoint;
147
- const data = config.data || config.body;
148
-
149
- // CRITICAL FIX: Properly merge headers to allow content-type overrides for XML requests
150
- const requestConfig = {
151
- ...config,
152
- headers: {
153
- ...(this.client.defaults?.headers?.common || {} as any),
154
- ...(this.client.defaults.headers[method as keyof typeof this.client.defaults.headers] || {} as any),
155
- ...config.headers // This ensures custom headers (like Content-Type: application/xml) override defaults
156
- }
157
- };
158
-
159
- this.logger.debug('🔧 Final request config headers:', requestConfig.headers);
160
-
161
- switch (method) {
162
- case 'get':
163
- return this.client.get(url, { params: config.params, ...requestConfig });
164
- case 'post':
165
- return this.client.post(url, data, requestConfig);
166
- case 'put':
167
- return this.client.put(url, data, requestConfig);
168
- case 'patch':
169
- return this.client.patch(url, data, requestConfig);
170
- case 'delete':
171
- return this.client.delete(url, requestConfig);
172
- default:
173
- throw new Error(`Unsupported HTTP method: ${method}`);
174
- }
175
- };
176
-
177
- this.actionTypeCache = new ActionTypeCache(this);
178
-
179
- // Add request interceptor for authentication with automatic token refresh
180
- this.client.interceptors.request.use(async (config) => {
181
- await this.ensureAuthenticated();
182
-
183
- // Get fresh access token (automatically refreshes if expired)
184
- const accessToken = await this.oauth.getAccessToken();
185
-
186
- if (accessToken) {
187
- config.headers['Authorization'] = `Bearer ${accessToken}`;
188
- // Update local credentials with fresh token
189
- if (this.credentials) {
190
- this.credentials.accessToken = accessToken;
191
- }
192
- } else {
193
- console.warn('⚠️ No access token available - request may fail');
194
- }
195
-
196
- return config;
197
- });
198
-
199
- // Add response interceptor for error handling with retry logic
200
- this.client.interceptors.response.use(
201
- (response) => response,
202
- async (error) => {
203
- const originalRequest = error.config;
204
-
205
- // 🔴 SNOW-003 FIX: Prevent token refresh race conditions
206
- if (error.response?.status === 401 && !originalRequest._retry) {
207
- originalRequest._retry = true;
208
-
209
- // 🔴 CRITICAL: Use singleton token refresh to prevent multiple concurrent refreshes
210
- return this.handleTokenRefreshWithLock(originalRequest);
211
- }
212
-
213
- // Log other errors for debugging
214
- if (error.response?.status === 403) {
215
- console.error('❌ 403 Forbidden - Check ServiceNow permissions');
216
- } else if (error.response?.status >= 500) {
217
- console.error('❌ ServiceNow server error:', error.response.status);
218
- }
219
-
220
- return Promise.reject(error);
221
- }
222
- );
223
- }
224
-
225
- /**
226
- * Public getter for credentials
227
- */
228
- public get credentialsInstance(): ServiceNowCredentials | null {
229
- return this.credentials;
230
- }
231
-
232
- // 🔴 SNOW-003 FIX: Token refresh synchronization to prevent race conditions
233
- private tokenRefreshPromise: Promise<any> | null = null;
234
- private lastTokenRefresh: number = 0;
235
-
236
- /**
237
- * 🔴 SNOW-003 FIX: Handle token refresh with locking to prevent concurrent refreshes
238
- * This prevents cascade failures caused by multiple simultaneous token refresh attempts
239
- */
240
- private async handleTokenRefreshWithLock(originalRequest: any): Promise<any> {
241
- const now = Date.now();
242
-
243
- // 🔴 CRITICAL: If another refresh is in progress, wait for it
244
- if (this.tokenRefreshPromise) {
245
- this.logger.info('🔄 Token refresh already in progress, waiting...');
246
- try {
247
- await this.tokenRefreshPromise;
248
-
249
- // After waiting, check if we have valid credentials now
250
- if (this.credentials?.accessToken) {
251
- this.logger.info('✅ Using refreshed token from concurrent refresh');
252
- originalRequest.headers['Authorization'] = `Bearer ${this.credentials.accessToken}`;
253
- return this.client.request(originalRequest);
254
- }
255
- } catch (error) {
256
- this.logger.warn('Concurrent token refresh failed:', error);
257
- }
258
- }
259
-
260
- // 🔴 CRITICAL: Rate limit token refresh to prevent excessive API calls
261
- if (now - this.lastTokenRefresh < 5000) { // 5 second rate limit
262
- this.logger.warn('⚠️ Token refresh rate limited - too many attempts');
263
- throw new Error('Token refresh rate limited. Please wait before retrying.');
264
- }
265
-
266
- // 🔴 CRITICAL: Create new refresh promise with timeout and error handling
267
- this.tokenRefreshPromise = this.performTokenRefreshWithTimeout();
268
- this.lastTokenRefresh = now;
269
-
270
- try {
271
- this.logger.info('🔄 Starting token refresh process...');
272
- const refreshResult = await this.tokenRefreshPromise;
273
-
274
- if (refreshResult.success && refreshResult.accessToken) {
275
- this.logger.info('✅ Token refreshed successfully, retrying original request...');
276
-
277
- // Update local credentials
278
- if (this.credentials) {
279
- this.credentials.accessToken = refreshResult.accessToken;
280
- this.credentials.expiresAt = refreshResult.expiresAt;
281
- }
282
-
283
- // Update the authorization header with new token
284
- originalRequest.headers['Authorization'] = `Bearer ${refreshResult.accessToken}`;
285
-
286
- // Clear the promise since we're done
287
- this.tokenRefreshPromise = null;
288
-
289
- // Retry the original request
290
- return this.client.request(originalRequest);
291
- } else {
292
- // Clear the promise on failure
293
- this.tokenRefreshPromise = null;
294
-
295
- const errorMsg = `Token refresh failed: ${refreshResult.error || 'Unknown error'}`;
296
- this.logger.error('❌ ' + errorMsg);
297
- console.error('💡 Please run "snow-flow auth login" to re-authenticate');
298
- throw new Error(errorMsg);
299
- }
300
- } catch (error) {
301
- // Clear the promise on any error
302
- this.tokenRefreshPromise = null;
303
-
304
- this.logger.error('🔴 Token refresh process failed:', error);
305
- throw error;
306
- }
307
- }
308
-
309
- /**
310
- * 🔴 SNOW-003 FIX: Token refresh with timeout to prevent hanging requests
311
- */
312
- private async performTokenRefreshWithTimeout(): Promise<any> {
313
- const timeout = 15000; // 15 second timeout for token refresh
314
-
315
- return Promise.race([
316
- this.oauth.refreshAccessToken(),
317
- new Promise((_, reject) =>
318
- setTimeout(() => reject(new Error('Token refresh timeout')), timeout)
319
- ),
320
- ]);
321
- }
322
-
323
- /**
324
- * Ensure we have valid authentication with improved error handling
325
- */
326
- private async ensureAuthenticated(): Promise<void> {
327
- if (!this.credentials) {
328
- // Try unified auth store first (most reliable)
329
- const tokens = await unifiedAuthStore.getTokens();
330
- if (tokens) {
331
- this.credentials = {
332
- instance: tokens.instance,
333
- clientId: tokens.clientId,
334
- clientSecret: tokens.clientSecret,
335
- accessToken: tokens.accessToken,
336
- refreshToken: tokens.refreshToken,
337
- expiresAt: tokens.expiresAt
338
- };
339
- } else {
340
- // Fallback to OAuth loadCredentials
341
- this.credentials = await this.oauth.loadCredentials();
342
- }
343
- }
344
-
345
- if (!this.credentials) {
346
- console.error('❌ No ServiceNow credentials found');
347
- console.error('');
348
- console.error('🔧 To fix this:');
349
- console.error(' 1. Ensure .env file has OAuth credentials:');
350
- console.error(' SNOW_INSTANCE=your-instance.service-now.com');
351
- console.error(' SNOW_CLIENT_ID=your_oauth_client_id');
352
- console.error(' SNOW_CLIENT_SECRET=your_oauth_client_secret');
353
- console.error(' 2. Run: snow-flow auth login');
354
- console.error('');
355
- throw new Error('No ServiceNow credentials found. Set up .env file and run "snow-flow auth login".');
356
- }
357
-
358
- // Check if we have credentials but no access token (OAuth login needed)
359
- if (!this.credentials.accessToken) {
360
- console.error('🔐 OAuth authentication required');
361
- console.error('');
362
- console.error('✅ Your .env file has OAuth credentials');
363
- console.error('❌ But no active OAuth session found');
364
- console.error('');
365
- console.error('🔧 To authenticate:');
366
- console.error(' Run: snow-flow auth login');
367
- console.error(' This will open your browser for OAuth login.');
368
- console.error('');
369
- throw new Error('OAuth login required. Run "snow-flow auth login" to authenticate.');
370
- }
371
-
372
- // Check if token is valid/authenticated
373
- const isAuth = await this.oauth.isAuthenticated();
374
- if (!isAuth) {
375
- console.error('⏰ OAuth token expired');
376
- console.error('');
377
- console.error('🔄 Attempting automatic token refresh...');
378
-
379
- // Try to refresh the token
380
- const refreshResult = await this.oauth.refreshAccessToken();
381
- if (refreshResult.success && refreshResult.accessToken) {
382
- this.logger.info('✅ Token refreshed successfully');
383
- // Update local credentials
384
- this.credentials.accessToken = refreshResult.accessToken;
385
- return; // Success!
386
- }
387
-
388
- console.error('❌ Token refresh failed:', refreshResult.error);
389
- console.error('');
390
- console.error('🔧 To fix this:');
391
- console.error(' Run: snow-flow auth login');
392
- console.error(' This will re-authenticate with ServiceNow.');
393
- console.error('');
394
- throw new Error('OAuth token expired and refresh failed. Run "snow-flow auth login" to re-authenticate.');
395
- }
396
- }
397
-
398
- /**
399
- * Proactively refresh token if it's about to expire
400
- * Useful for long-running operations
401
- */
402
- public async refreshTokenIfNeeded(): Promise<boolean> {
403
- try {
404
- const tokens = await this.oauth.loadTokens();
405
- if (!tokens) return false;
406
-
407
- const expiresAt = new Date(tokens.expiresAt);
408
- const now = new Date();
409
- const fiveMinutesFromNow = new Date(now.getTime() + 5 * 60 * 1000);
410
-
411
- // If token expires in the next 5 minutes, refresh it
412
- if (expiresAt <= fiveMinutesFromNow) {
413
- this.logger.info('🔄 Token expiring soon, refreshing proactively...');
414
- const refreshResult = await this.oauth.refreshAccessToken();
415
-
416
- if (refreshResult.success) {
417
- this.logger.info('✅ Token refreshed proactively');
418
- if (this.credentials && refreshResult.accessToken) {
419
- this.credentials.accessToken = refreshResult.accessToken;
420
- }
421
- return true;
422
- } else {
423
- console.error('❌ Proactive token refresh failed');
424
- return false;
425
- }
426
- }
427
-
428
- return true; // Token still valid
429
- } catch (error) {
430
- console.error('Error checking token expiry:', error);
431
- return false;
432
- }
433
- }
434
-
435
- /**
436
- * Get base URL for ServiceNow instance
437
- */
438
- private getBaseUrl(): string {
439
- if (!this.credentials) {
440
- throw new Error('No credentials available');
441
- }
442
-
443
- // Remove trailing slash from instance URL to prevent double slashes in API calls
444
- const instance = this.credentials.instance.replace(/\/$/, '');
445
-
446
- return `https://${instance}`;
447
- }
448
-
449
- /**
450
- * Sanitize a flow name for use as internal_name
451
- */
452
- private sanitizeInternalName(name: string): string {
453
- return name
454
- .toLowerCase()
455
- .replace(/[^a-z0-9_\s]/g, '') // Remove special characters except underscores and spaces
456
- .replace(/\s+/g, '_') // Replace spaces with underscores
457
- .replace(/_+/g, '_') // Replace multiple underscores with single
458
- .replace(/^_|_$/g, '') // Remove leading/trailing underscores
459
- .substring(0, 80); // Limit length to 80 characters
460
- }
461
-
462
- /**
463
- * Validate deployment permissions and diagnose authentication issues
464
- */
465
- async validateDeploymentPermissions(): Promise<ServiceNowAPIResponse<any>> {
466
- // Ensure we have credentials before running diagnostics
467
- await this.ensureAuthenticated();
468
-
469
- const diagnostics: any = {
470
- instance_url: this.getBaseUrl(),
471
- timestamp: new Date().toISOString(),
472
- tests: {}
473
- };
474
-
475
- const tests = [
476
- {
477
- name: 'Read Access',
478
- description: 'Test basic read permissions',
479
- test: async () => {
480
- const response = await this.client.get(`${this.getBaseUrl()}/api/now/table/sys_user?sysparm_limit=1`);
481
- return { success: true, data: response.data };
482
- }
483
- },
484
- {
485
- name: 'Widget Read Access',
486
- description: 'Test Service Portal widget read access',
487
- test: async () => {
488
- const response = await this.client.get(`${this.getBaseUrl()}/api/now/table/sp_widget?sysparm_limit=1`);
489
- return { success: true, data: response.data };
490
- }
491
- },
492
- {
493
- name: 'Update Set Access',
494
- description: 'Test Update Set management permissions',
495
- test: async () => {
496
- const response = await this.client.get(`${this.getBaseUrl()}/api/now/table/sys_update_set?sysparm_limit=1`);
497
- return { success: true, data: response.data };
498
- }
499
- },
500
- {
501
- name: 'Widget Write Test',
502
- description: 'Test Service Portal widget write permissions (dry run)',
503
- test: async () => {
504
- // Try to create a minimal test widget that we'll immediately delete
505
- const testWidget = {
506
- name: `test_widget_${Date.now()}`,
507
- id: `test_${Date.now()}`,
508
- title: 'Test Widget (Will be deleted)',
509
- template: '<div>Test</div>',
510
- css: '',
511
- script: '',
512
- option_schema: '[]'
513
- };
514
-
515
- try {
516
- const createResponse = await this.client.post(`${this.getBaseUrl()}/api/now/table/sp_widget`, testWidget);
517
-
518
- // CRITICAL FIX: Add null safety for response processing
519
- if (!createResponse || !createResponse.data || !createResponse.data.result || !createResponse.data.result.sys_id) {
520
- throw new Error('Widget creation succeeded but response structure is unexpected - unable to verify sys_id');
521
- }
522
-
523
- const sys_id = createResponse.data.result.sys_id;
524
-
525
- // Immediately delete the test widget with error handling
526
- try {
527
- await this.client.delete(`${this.getBaseUrl()}/api/now/table/sp_widget/${sys_id}`);
528
- } catch (deleteError) {
529
- this.logger.warn(`Test widget created but cleanup failed: ${deleteError instanceof Error ? deleteError.message : String(deleteError)}`);
530
- // Don't fail the test just because cleanup failed
531
- }
532
-
533
- return { success: true, data: { message: 'Widget write permissions confirmed' } };
534
- } catch (error) {
535
- throw error;
536
- }
537
- }
538
- },
539
- {
540
- name: 'User Role Check',
541
- description: 'Check user roles and permissions',
542
- test: async () => {
543
- const response = await this.client.get(`${this.getBaseUrl()}/api/now/table/sys_user_role?sysparm_query=user=javascript:gs.getUserID()`);
544
-
545
- // CRITICAL FIX: Add null safety for response processing
546
- if (!response || !response.data || !response.data.result) {
547
- return { success: true, data: { roles: [], warning: 'Unable to retrieve user roles - response structure unexpected' } };
548
- }
549
-
550
- // Safely process roles with null checks
551
- const roles = Array.isArray(response.data.result)
552
- ? response.data.result.map((r: any) => {
553
- if (!r || typeof r !== 'object') return 'Unknown Role';
554
- return r.role?.display_value || r.role || 'Unknown Role';
555
- }).filter(role => role && role !== 'Unknown Role')
556
- : [];
557
-
558
- return { success: true, data: { roles } };
559
- }
560
- }
561
- ];
562
-
563
- for (const test of tests) {
564
- try {
565
- this.logger.info(`Running diagnostic: ${test.name}...`);
566
- const result = await test.test();
567
- diagnostics.tests[test.name] = {
568
- status: '✅ PASS',
569
- description: test.description,
570
- result: result.data,
571
- error: null
572
- };
573
- } catch (error) {
574
- const errorMessage = error instanceof Error ? error.message : String(error);
575
- diagnostics.tests[test.name] = {
576
- status: '❌ FAIL',
577
- description: test.description,
578
- result: null,
579
- error: errorMessage,
580
- http_status: this.extractHttpStatus(errorMessage)
581
- };
582
- }
583
- }
584
-
585
- // Analyze results with null safety
586
- const failedTests = diagnostics.tests && typeof diagnostics.tests === 'object'
587
- ? Object.values(diagnostics.tests).filter((test: any) => test && test.status && test.status.includes('FAIL'))
588
- : [];
589
- const passedTests = diagnostics.tests && typeof diagnostics.tests === 'object'
590
- ? Object.values(diagnostics.tests).filter((test: any) => test && test.status && test.status.includes('PASS'))
591
- : [];
592
-
593
- diagnostics.summary = {
594
- total_tests: tests.length || 0,
595
- passed: passedTests.length || 0,
596
- failed: failedTests.length || 0,
597
- overall_status: failedTests.length === 0 && passedTests.length > 0 ? '✅ ALL SYSTEMS GO' : '⚠️ ISSUES DETECTED'
598
- };
599
-
600
- // Generate recommendations with null safety
601
- try {
602
- diagnostics.recommendations = this.generateAuthRecommendations(diagnostics.tests);
603
- } catch (recError) {
604
- this.logger.error('Error generating recommendations:', recError);
605
- diagnostics.recommendations = ['⚠️ Unable to generate recommendations due to error'];
606
- }
607
-
608
- return {
609
- success: failedTests.length === 0,
610
- data: diagnostics,
611
- error: failedTests.length > 0 ? 'Some deployment permissions are missing' : null
612
- };
613
- }
614
-
615
- /**
616
- * Extract HTTP status from error message
617
- */
618
- private extractHttpStatus(errorMessage: string): number | null {
619
- const statusMatch = errorMessage.match(/status code (\d+)/i);
620
- return statusMatch ? parseInt(statusMatch[1], 10) : null;
621
- }
622
-
623
- /**
624
- * Generate authentication recommendations based on test results
625
- */
626
- private generateAuthRecommendations(tests: any): string[] {
627
- const recommendations = [];
628
-
629
- // CRITICAL FIX: Add comprehensive null safety checks
630
- if (!tests || typeof tests !== 'object') {
631
- recommendations.push('⚠️ Unable to analyze test results due to missing test data');
632
- return recommendations;
633
- }
634
-
635
- // Safe check for Widget Write Test
636
- const widgetTest = tests['Widget Write Test'];
637
- if (widgetTest?.status && typeof widgetTest.status === 'string' && widgetTest.status.includes('FAIL')) {
638
- const error = widgetTest.error;
639
- if (error && typeof error === 'string') {
640
- if (error.includes('403')) {
641
- recommendations.push('🔐 Widget deployment failed with 403 Forbidden. Check OAuth scopes: ensure \'useraccount\' and \'glide_system_administration\' scopes are enabled');
642
- recommendations.push('👤 Verify user has sp_portal_manager or admin role in ServiceNow');
643
- recommendations.push('🛡️ Check if instance has deployment restrictions for external applications');
644
- }
645
- if (error.includes('401')) {
646
- recommendations.push('🔑 Authentication failed. Re-run: snow-flow auth login');
647
- }
648
- } else {
649
- recommendations.push('🔐 Widget write test failed with unknown error. Check ServiceNow permissions');
650
- }
651
- }
652
-
653
- // Safe check for User Role Check
654
- const roleTest = tests['User Role Check'];
655
- if (roleTest?.status && typeof roleTest.status === 'string' && roleTest.status.includes('PASS')) {
656
- const roles = roleTest.result?.roles;
657
- if (Array.isArray(roles)) {
658
- const hasRequiredRole = roles.some((role: any) => {
659
- if (typeof role === 'string') {
660
- return role.includes('admin') || role.includes('sp_portal');
661
- }
662
- return false;
663
- });
664
-
665
- if (!hasRequiredRole) {
666
- recommendations.push('⚠️ User lacks admin or portal management roles. Contact ServiceNow admin to assign appropriate roles');
667
- }
668
- } else {
669
- recommendations.push('⚠️ Unable to verify user roles. Check if user has admin or portal management permissions');
670
- }
671
- }
672
-
673
- // Safe check for Update Set Access
674
- const updateSetTest = tests['Update Set Access'];
675
- if (updateSetTest?.status && typeof updateSetTest.status === 'string' && updateSetTest.status.includes('FAIL')) {
676
- recommendations.push('📦 Update Set access failed. Ensure user has update_set_manager or admin role');
677
- }
678
-
679
- if (recommendations.length === 0) {
680
- recommendations.push('✅ All authentication checks passed! Deployment should work correctly');
681
- }
682
-
683
- return recommendations;
684
- }
685
-
686
- /**
687
- * Test connection to ServiceNow
688
- */
689
- async testConnection(): Promise<ServiceNowAPIResponse<any>> {
690
- try {
691
- // Ensure we have credentials first
692
- await this.ensureAuthenticated();
693
-
694
- // Use the /api/now/v2/table/sys_user?sysparm_limit=1 endpoint to test
695
- // This is a more reliable endpoint that should work on all instances
696
- const response = await this.client.get(
697
- `${this.getBaseUrl()}/api/now/v2/table/sys_user?sysparm_limit=1&sysparm_query=user_name=admin`
698
- );
699
-
700
- // If we can query users, we're connected
701
- return {
702
- success: true,
703
- data: {
704
- name: 'ServiceNow Instance',
705
- user_name: 'Connected',
706
- email: `${this.credentials?.instance}`,
707
- message: 'Connection successful'
708
- }
709
- };
710
- } catch (error) {
711
- // Try a simpler endpoint if the first one fails
712
- try {
713
- const response = await this.client.get(
714
- `${this.getBaseUrl()}/api/now/table/sys_properties?sysparm_limit=1`
715
- );
716
-
717
- return {
718
- success: true,
719
- data: {
720
- name: 'ServiceNow Instance',
721
- user_name: 'Connected',
722
- email: `${this.credentials?.instance}`,
723
- message: 'Connection successful (limited access)'
724
- }
725
- };
726
- } catch (secondError) {
727
- return {
728
- success: false,
729
- error: error instanceof Error ? error.message : String(error)
730
- };
731
- }
732
- }
733
- }
734
-
735
- /**
736
- * Create a new ServiceNow widget
737
- */
738
- async createWidget(widget: ServiceNowWidget): Promise<ServiceNowAPIResponse<ServiceNowWidget>> {
739
- try {
740
- this.logger.info('🎨 Creating ServiceNow widget...');
741
- this.logger.info(`📋 Widget Name: ${widget.name}`);
742
-
743
- // Add pre-deployment validation for widgets
744
- if (!widget.name || widget.name.trim() === '') {
745
- throw new Error('Widget name is required');
746
- }
747
- if (!widget.title || widget.title.trim() === '') {
748
- throw new Error('Widget title is required');
749
- }
750
- if (!widget.template || widget.template.trim() === '') {
751
- console.warn('⚠️ Widget has no template content - generating functional template automatically');
752
-
753
- // Generate a functional template instead of deploying an empty widget
754
- const generatedWidget = widgetTemplateGenerator.generateWidget({
755
- title: widget.title,
756
- instruction: widget.description || widget.name || 'auto-generated widget',
757
- type: 'info', // Default to info widget for auto-generated templates
758
- theme: 'default',
759
- responsive: true
760
- });
761
-
762
- // Apply generated components to the widget
763
- widget.template = generatedWidget.template;
764
- if (!widget.css || widget.css.trim() === '') {
765
- widget.css = generatedWidget.css;
766
- }
767
- if (!widget.client_script || widget.client_script.trim() === '') {
768
- widget.client_script = generatedWidget.clientScript;
769
- }
770
- if (!widget.server_script || widget.server_script.trim() === '') {
771
- widget.server_script = generatedWidget.serverScript;
772
- }
773
- if (!widget.option_schema || widget.option_schema.trim() === '' || widget.option_schema === '[]') {
774
- widget.option_schema = generatedWidget.optionSchema;
775
- }
776
-
777
- this.logger.info('✅ Generated functional widget template automatically');
778
- }
779
-
780
- // Ensure we have credentials before making the API call
781
- await this.ensureAuthenticated();
782
-
783
- const response = await this.client.post(
784
- `${this.getBaseUrl()}/api/now/table/sp_widget`,
785
- {
786
- name: widget.name,
787
- id: widget.id,
788
- title: widget.title,
789
- description: widget.description,
790
- template: widget.template,
791
- css: widget.css,
792
- client_script: widget.client_script,
793
- script: widget.server_script, // Service Portal uses 'script' not 'server_script'
794
- option_schema: widget.option_schema || '[]',
795
- demo_data: widget.demo_data || '{}',
796
- has_preview: widget.has_preview || false,
797
- category: widget.category || 'custom'
798
- },
799
- {
800
- timeout: this.deploymentTimeout, // Use deployment-specific timeout
801
- headers: {
802
- 'X-Operation-Type': 'deployment' // Mark as deployment operation
803
- }
804
- }
805
- );
806
-
807
- this.logger.info('✅ Widget created successfully!');
808
- this.logger.info(`🆔 Widget ID: ${response.data.result.sys_id}`);
809
-
810
- // Add post-deployment verification
811
- await this.verifyDeployment(response.data.result.sys_id, 'widget');
812
-
813
- return {
814
- success: true,
815
- data: response.data.result
816
- };
817
- } catch (error) {
818
- console.error('❌ Failed to create widget:', error);
819
- return {
820
- success: false,
821
- error: error instanceof Error ? error.message : String(error)
822
- };
823
- }
824
- }
825
-
826
- /**
827
- * Update an existing ServiceNow widget
828
- */
829
- async updateWidget(sysId: string, widget: Partial<ServiceNowWidget>): Promise<ServiceNowAPIResponse<ServiceNowWidget>> {
830
- try {
831
- this.logger.info(`🔄 Updating widget ${sysId}...`);
832
-
833
- // Ensure we have credentials before making the API call
834
- await this.ensureAuthenticated();
835
-
836
- // Map fields for Service Portal widget API
837
- const mappedWidget: any = { ...widget };
838
- if (mappedWidget.server_script !== undefined) {
839
- mappedWidget.script = mappedWidget.server_script;
840
- delete mappedWidget.server_script;
841
- }
842
-
843
- const response = await this.client.patch(
844
- `${this.getBaseUrl()}/api/now/table/sp_widget/${sysId}`,
845
- mappedWidget,
846
- {
847
- timeout: this.deploymentTimeout, // Use deployment-specific timeout
848
- headers: {
849
- 'X-Operation-Type': 'deployment' // Mark as deployment operation
850
- }
851
- }
852
- );
853
-
854
- this.logger.info('✅ Widget updated successfully!');
855
-
856
- return {
857
- success: true,
858
- data: response.data.result
859
- };
860
- } catch (error) {
861
- console.error('❌ Failed to update widget:', error);
862
- return {
863
- success: false,
864
- error: error instanceof Error ? error.message : String(error)
865
- };
866
- }
867
- }
868
-
869
- /**
870
- * Get widget by ID
871
- */
872
- async getWidget(widgetId: string): Promise<ServiceNowAPIResponse<ServiceNowWidget>> {
873
- try {
874
- // Ensure we have credentials before making the API call
875
- await this.ensureAuthenticated();
876
-
877
- const response = await this.client.get(
878
- `${this.getBaseUrl()}/api/now/table/sp_widget?sysparm_query=id=${widgetId}`
879
- );
880
-
881
- if (response.data.result.length === 0) {
882
- return {
883
- success: false,
884
- error: 'Widget not found'
885
- };
886
- }
887
-
888
- return {
889
- success: true,
890
- data: response.data.result[0]
891
- };
892
- } catch (error) {
893
- return {
894
- success: false,
895
- error: error instanceof Error ? error.message : String(error)
896
- };
897
- }
898
- }
899
-
900
- /**
901
- * Create a new ServiceNow workflow
902
- */
903
- async createWorkflow(workflow: ServiceNowWorkflow): Promise<ServiceNowAPIResponse<ServiceNowWorkflow>> {
904
- try {
905
- this.logger.info('🔄 Creating ServiceNow workflow...');
906
- this.logger.info(`📋 Workflow Name: ${workflow.name}`);
907
-
908
- const response = await this.client.post(
909
- `${this.getBaseUrl()}/api/now/table/wf_workflow`,
910
- {
911
- name: workflow.name,
912
- description: workflow.description,
913
- active: workflow.active,
914
- workflow_version: workflow.workflow_version,
915
- table: workflow.table || '',
916
- condition: workflow.condition || ''
917
- }
918
- );
919
-
920
- this.logger.info('✅ Workflow created successfully!');
921
- this.logger.info(`🆔 Workflow ID: ${response.data.result.sys_id}`);
922
-
923
- return {
924
- success: true,
925
- data: response.data.result
926
- };
927
- } catch (error) {
928
- console.error('❌ Failed to create workflow:', error);
929
- return {
930
- success: false,
931
- error: error instanceof Error ? error.message : String(error)
932
- };
933
- }
934
- }
935
-
936
- /**
937
- * Create a new ServiceNow application
938
- */
939
- async createApplication(application: ServiceNowApplication): Promise<ServiceNowAPIResponse<ServiceNowApplication>> {
940
- try {
941
- this.logger.info('🏗️ Creating ServiceNow application...');
942
- this.logger.info(`📋 Application Name: ${application.name}`);
943
-
944
- const response = await this.client.post(
945
- `${this.getBaseUrl()}/api/now/table/sys_app`,
946
- {
947
- name: application.name,
948
- scope: application.scope,
949
- version: application.version,
950
- short_description: application.short_description,
951
- description: application.description,
952
- vendor: application.vendor,
953
- vendor_prefix: application.vendor_prefix,
954
- template: application.template || '',
955
- logo: application.logo || '',
956
- active: application.active
957
- }
958
- );
959
-
960
- this.logger.info('✅ Application created successfully!');
961
- this.logger.info(`🆔 Application ID: ${response.data.result.sys_id}`);
962
-
963
- return {
964
- success: true,
965
- data: response.data.result
966
- };
967
- } catch (error) {
968
- console.error('❌ Failed to create application:', error);
969
- return {
970
- success: false,
971
- error: error instanceof Error ? error.message : String(error)
972
- };
973
- }
974
- }
975
-
976
- /**
977
- * Execute a ServiceNow script
978
- */
979
- async executeScript(script: string): Promise<ServiceNowAPIResponse<any>> {
980
- try {
981
- this.logger.info('⚡ Executing ServiceNow script...');
982
-
983
- const response = await this.client.post(
984
- `${this.getBaseUrl()}/api/now/table/sys_script_execution`,
985
- {
986
- script: script,
987
- type: 'server'
988
- }
989
- );
990
-
991
- this.logger.info('✅ Script executed successfully!');
992
-
993
- return {
994
- success: true,
995
- data: response.data.result
996
- };
997
- } catch (error) {
998
- console.error('❌ Failed to execute script:', error);
999
- return {
1000
- success: false,
1001
- error: error instanceof Error ? error.message : String(error)
1002
- };
1003
- }
1004
- }
1005
-
1006
- /**
1007
- * Get all widgets
1008
- */
1009
- async getWidgets(): Promise<ServiceNowAPIResponse<ServiceNowWidget[]>> {
1010
- try {
1011
- const response = await this.client.get(
1012
- `${this.getBaseUrl()}/api/now/table/sp_widget?sysparm_limit=100`
1013
- );
1014
-
1015
- return {
1016
- success: true,
1017
- result: response.data.result
1018
- };
1019
- } catch (error) {
1020
- return {
1021
- success: false,
1022
- error: error instanceof Error ? error.message : String(error)
1023
- };
1024
- }
1025
- }
1026
-
1027
- /**
1028
- * Get all workflows
1029
- */
1030
- async getWorkflows(): Promise<ServiceNowAPIResponse<ServiceNowWorkflow[]>> {
1031
- try {
1032
- const response = await this.client.get(
1033
- `${this.getBaseUrl()}/api/now/table/wf_workflow?sysparm_limit=100`
1034
- );
1035
-
1036
- return {
1037
- success: true,
1038
- result: response.data.result
1039
- };
1040
- } catch (error) {
1041
- return {
1042
- success: false,
1043
- error: error instanceof Error ? error.message : String(error)
1044
- };
1045
- }
1046
- }
1047
-
1048
- /**
1049
- * Get all applications
1050
- */
1051
- async getApplications(): Promise<ServiceNowAPIResponse<ServiceNowApplication[]>> {
1052
- try {
1053
- const response = await this.client.get(
1054
- `${this.getBaseUrl()}/api/now/table/sys_app?sysparm_limit=100`
1055
- );
1056
-
1057
- return {
1058
- success: true,
1059
- result: response.data.result
1060
- };
1061
- } catch (error) {
1062
- return {
1063
- success: false,
1064
- error: error instanceof Error ? error.message : String(error)
1065
- };
1066
- }
1067
- }
1068
-
1069
- /**
1070
- * Get default flow structure from ServiceNow
1071
- */
1072
- private async getFlowDefaults(): Promise<any> {
1073
- try {
1074
- // Try to get an existing flow to see the structure
1075
- const response = await this.client.get(
1076
- `${this.getBaseUrl()}/api/now/table/sys_hub_flow?sysparm_limit=1&sysparm_fields=sys_class_name,type,status,access,source_ui,sys_domain,sys_domain_path`
1077
- );
1078
-
1079
- if (response.data.result && response.data.result.length > 0) {
1080
- const sample = response.data.result[0];
1081
- return {
1082
- sys_class_name: sample.sys_class_name || 'sys_hub_flow',
1083
- type: sample.type || 'flow',
1084
- status: sample.status || 'published',
1085
- access: sample.access || 'public',
1086
- source_ui: sample.source_ui || 'flow_designer',
1087
- sys_domain: sample.sys_domain || 'global',
1088
- sys_domain_path: sample.sys_domain_path || '/'
1089
- };
1090
- }
1091
- } catch (error) {
1092
- this.logger.warn('Could not fetch flow defaults, using minimal defaults');
1093
- }
1094
-
1095
- // Return minimal defaults if we can't get from ServiceNow
1096
- return {
1097
- sys_class_name: 'sys_hub_flow',
1098
- type: 'flow',
1099
- status: 'published',
1100
- access: 'public'
1101
- };
1102
- }
1103
-
1104
- /**
1105
- * Get instance info
1106
- */
1107
- async getInstanceInfo(): Promise<ServiceNowAPIResponse<any>> {
1108
- try {
1109
- const response = await this.client.get(
1110
- `${this.getBaseUrl()}/api/now/table/sys_properties?sysparm_query=name=instance.name`
1111
- );
1112
-
1113
- return {
1114
- success: true,
1115
- data: response.data.result[0]
1116
- };
1117
- } catch (error) {
1118
- return {
1119
- success: false,
1120
- error: error instanceof Error ? error.message : String(error)
1121
- };
1122
- }
1123
- }
1124
-
1125
- /**
1126
- * Get a specific record by sys_id
1127
- */
1128
- async getRecord(table: string, sys_id: string): Promise<any> {
1129
- try {
1130
- // Ensure we have credentials before making the API call
1131
- await this.ensureAuthenticated();
1132
-
1133
- const response = await this.client.get(
1134
- `${this.getBaseUrl()}/api/now/table/${table}/${sys_id}`
1135
- );
1136
- return response.data.result;
1137
- } catch (error) {
1138
- console.error(`Failed to get record from ${table}:`, error);
1139
- throw error;
1140
- }
1141
- }
1142
-
1143
- /**
1144
- * Get multiple records from a table
1145
- */
1146
- async getRecords(table: string, params?: any): Promise<ServiceNowAPIResponse<any[]>> {
1147
- try {
1148
- // Ensure we have credentials before making the API call
1149
- await this.ensureAuthenticated();
1150
-
1151
- const response = await this.client.get(
1152
- `${this.getBaseUrl()}/api/now/table/${table}`,
1153
- { params }
1154
- );
1155
-
1156
- return {
1157
- success: true,
1158
- data: response.data.result
1159
- };
1160
- } catch (error) {
1161
- console.error(`Failed to get records from ${table}:`, error);
1162
- return {
1163
- success: false,
1164
- error: error instanceof Error ? error.message : String(error)
1165
- };
1166
- }
1167
- }
1168
-
1169
- /**
1170
- * Search records in a table using encoded query
1171
- */
1172
- async searchRecords(table: string, query: string, limit: number = 10): Promise<ServiceNowAPIResponse<any>> {
1173
- try {
1174
- await this.ensureAuthenticated();
1175
-
1176
- const response = await this.client.get(
1177
- `${this.getBaseUrl()}/api/now/table/${table}`,
1178
- {
1179
- params: {
1180
- sysparm_query: query,
1181
- sysparm_limit: limit
1182
- }
1183
- }
1184
- );
1185
- return {
1186
- success: true,
1187
- data: {
1188
- result: response.data.result || []
1189
- }
1190
- };
1191
- } catch (error) {
1192
- console.error(`Failed to search records in ${table}:`, error);
1193
- return {
1194
- success: false,
1195
- error: error instanceof Error ? error.message : String(error)
1196
- };
1197
- }
1198
- }
1199
-
1200
- /**
1201
- * Create a record in any ServiceNow table
1202
- */
1203
- async createRecord(table: string, data: any): Promise<ServiceNowAPIResponse<any>> {
1204
- try {
1205
- await this.ensureAuthenticated();
1206
-
1207
- const response = await this.client.post(
1208
- `${this.getBaseUrl()}/api/now/table/${table}`,
1209
- data
1210
- );
1211
-
1212
- return {
1213
- success: true,
1214
- data: response.data.result
1215
- };
1216
- } catch (error) {
1217
- console.error(`Failed to create record in ${table}:`, error);
1218
- return {
1219
- success: false,
1220
- error: error instanceof Error ? error.message : String(error)
1221
- };
1222
- }
1223
- }
1224
-
1225
- /**
1226
- * Update a record in any ServiceNow table
1227
- */
1228
- async updateRecord(table: string, sysId: string, data: any): Promise<ServiceNowAPIResponse<any>> {
1229
- try {
1230
- await this.ensureAuthenticated();
1231
-
1232
- const response = await this.client.patch(
1233
- `${this.getBaseUrl()}/api/now/table/${table}/${sysId}`,
1234
- data
1235
- );
1236
-
1237
- return {
1238
- success: true,
1239
- data: response.data.result
1240
- };
1241
- } catch (error) {
1242
- console.error(`Failed to update record in ${table}:`, error);
1243
- return {
1244
- success: false,
1245
- error: error instanceof Error ? error.message : String(error)
1246
- };
1247
- }
1248
- }
1249
-
1250
- /**
1251
- * Delete a record from any ServiceNow table
1252
- */
1253
- async deleteRecord(table: string, sysId: string): Promise<ServiceNowAPIResponse<any>> {
1254
- try {
1255
- await this.ensureAuthenticated();
1256
-
1257
- const response = await this.client.delete(
1258
- `${this.getBaseUrl()}/api/now/table/${table}/${sysId}`
1259
- );
1260
-
1261
- return {
1262
- success: true,
1263
- data: response.data || {}
1264
- };
1265
- } catch (error) {
1266
- console.error(`Failed to delete record from ${table}:`, error);
1267
- return {
1268
- success: false,
1269
- error: error instanceof Error ? error.message : String(error)
1270
- };
1271
- }
1272
- }
1273
-
1274
- /**
1275
- * Search for available flow actions in ServiceNow
1276
- */
1277
- async searchFlowActions(searchTerm: string): Promise<any> {
1278
- try {
1279
- this.logger.info(`🔍 Searching for flow actions: ${searchTerm}`);
1280
-
1281
- // Search in sys_hub_action_type_base for available action types
1282
- const results = await this.searchRecords(
1283
- 'sys_hub_action_type_base',
1284
- `nameLIKE${searchTerm}^ORlabelLIKE${searchTerm}^ORdescriptionLIKE${searchTerm}`,
1285
- 20
1286
- );
1287
-
1288
- // Also search in sys_hub_action_instance for existing actions
1289
- const instanceResults = await this.searchRecords(
1290
- 'sys_hub_action_instance',
1291
- `action_nameLIKE${searchTerm}^ORdescriptionLIKE${searchTerm}`,
1292
- 10
1293
- );
1294
-
1295
- const actionTypes = results.success ? results.data.result : [];
1296
- const actionInstances = instanceResults.success ? instanceResults.data.result : [];
1297
-
1298
- this.logger.info(`✅ Found ${actionTypes.length} action types and ${actionInstances.length} action instances`);
1299
-
1300
- return {
1301
- actionTypes,
1302
- actionInstances
1303
- };
1304
- } catch (error) {
1305
- console.error('Failed to search flow actions:', error);
1306
- return { actionTypes: [], actionInstances: [] };
1307
- }
1308
- }
1309
-
1310
- /**
1311
- * Get flow action details
1312
- */
1313
- async getFlowActionDetails(actionTypeId: string): Promise<any> {
1314
- try {
1315
- const actionType = await this.getRecord('sys_hub_action_type_base', actionTypeId);
1316
-
1317
- // Get input/output variables for this action type
1318
- const inputs = await this.searchRecords(
1319
- 'sys_hub_action_input',
1320
- `action_type=${actionTypeId}`,
1321
- 50
1322
- );
1323
-
1324
- const outputs = await this.searchRecords(
1325
- 'sys_hub_action_output',
1326
- `action_type=${actionTypeId}`,
1327
- 50
1328
- );
1329
-
1330
- return {
1331
- actionType,
1332
- inputs: inputs.success ? inputs.data.result : [],
1333
- outputs: outputs.success ? outputs.data.result : []
1334
- };
1335
- } catch (error) {
1336
- console.error('Failed to get flow action details:', error);
1337
- return null;
1338
- }
1339
- }
1340
-
1341
- /**
1342
- * Validate flow definition before deployment
1343
- */
1344
- private validateFlowBeforeDeployment(flow: any): void {
1345
- const errors: string[] = [];
1346
-
1347
- if (!flow.name || flow.name.trim() === '') {
1348
- errors.push('Flow name is required');
1349
- }
1350
-
1351
- if (!flow.description || flow.description.trim() === '') {
1352
- errors.push('Flow description is required');
1353
- }
1354
-
1355
- // Validate activities/actions
1356
- const activities = flow.activities || flow.actions || flow.steps || [];
1357
- if (activities.length === 0) {
1358
- errors.push(`
1359
- No activities found in flow definition.
1360
-
1361
- 💡 Common fixes:
1362
- • Use natural language: snow_create_flow({ instruction: "..." })
1363
- • Check flow_definition format
1364
- • Ensure activities array is properly populated
1365
- `);
1366
- }
1367
-
1368
- // Validate activity structure
1369
- activities.forEach((activity: any, index: number) => {
1370
- if (!activity.name) {
1371
- errors.push(`Activity ${index + 1} missing name`);
1372
- }
1373
- if (!activity.type && !activity.operation) {
1374
- errors.push(`Activity ${index + 1} missing type or operation`);
1375
- }
1376
- });
1377
-
1378
- if (errors.length > 0) {
1379
- throw new Error(`
1380
- 🚨 Flow Validation Failed:
1381
-
1382
- ${errors.map(error => `• ${error}`).join('\n')}
1383
-
1384
- 🔧 Recommended approach:
1385
- Use snow_create_flow with natural language instead of manual JSON.
1386
- `);
1387
- }
1388
- }
1389
-
1390
- /**
1391
- * Verify deployment was successful and artifact has content
1392
- */
1393
- private async verifyDeployment(sysId: string, expectedType: string): Promise<void> {
1394
- try {
1395
- let endpoint = '';
1396
-
1397
- // Determine the correct endpoint based on artifact type
1398
- switch (expectedType) {
1399
- case 'flow':
1400
- case 'subflow':
1401
- endpoint = `/api/now/table/sys_hub_flow/${sysId}`;
1402
- break;
1403
- case 'widget':
1404
- endpoint = `/api/now/table/sp_widget/${sysId}`;
1405
- break;
1406
- case 'action':
1407
- endpoint = `/api/now/table/sys_hub_action_type_definition/${sysId}`;
1408
- break;
1409
- default:
1410
- endpoint = `/api/now/table/sys_hub_flow/${sysId}`;
1411
- }
1412
-
1413
- // Get the deployed artifact
1414
- const response = await this.client.get(`${this.getBaseUrl()}${endpoint}`);
1415
-
1416
- if (!response.data?.result) {
1417
- throw new Error(`Deployed ${expectedType} not found: ${sysId}`);
1418
- }
1419
-
1420
- const artifact = response.data.result;
1421
-
1422
- // Type-specific content validation
1423
- if (expectedType === 'flow' || expectedType === 'subflow') {
1424
- const flowDef = artifact.flow_definition ? JSON.parse(artifact.flow_definition || '{}') : {};
1425
- const activities = flowDef.activities || flowDef.actions || flowDef.steps || [];
1426
-
1427
- if (activities.length === 0) {
1428
- console.warn(`
1429
- ⚠️ ${expectedType} deployed but appears empty: ${artifact.name}
1430
-
1431
- 🔍 This usually means:
1432
- • Flow definition format was incorrect
1433
- • Activities were not properly mapped
1434
- • ServiceNow rejected the flow structure
1435
-
1436
- 💡 Try recreating with:
1437
- snow_create_flow({
1438
- instruction: "recreate ${artifact.name} with proper activities",
1439
- deploy_immediately: true
1440
- })
1441
- `);
1442
- } else {
1443
- this.logger.info(`✅ ${expectedType} has ${activities.length} activities`);
1444
- }
1445
- } else if (expectedType === 'widget') {
1446
- const hasContent = artifact.template || artifact.client_script || artifact.script;
1447
- if (!hasContent) {
1448
- console.warn(`⚠️ Widget deployed but appears to have no content: ${artifact.name}`);
1449
- }
1450
- }
1451
-
1452
- this.logger.info(`✅ Deployment verified: ${artifact.name} (${sysId})`);
1453
-
1454
- } catch (error) {
1455
- console.error(`❌ Deployment verification failed for ${sysId}:`, error);
1456
- }
1457
- }
1458
-
1459
- /**
1460
- * Check if a flow has actual content (not empty)
1461
- */
1462
- async checkFlowContent(sysId: string): Promise<{hasContent: boolean, details: any}> {
1463
- try {
1464
- const response = await this.client.get(
1465
- `${this.getBaseUrl()}/api/now/table/sys_hub_flow/${sysId}`
1466
- );
1467
-
1468
- const flow = response.data?.result;
1469
- if (!flow) {
1470
- return { hasContent: false, details: 'Flow not found' };
1471
- }
1472
-
1473
- const flowDef = JSON.parse(flow.flow_definition || '{}');
1474
- const activities = flowDef.activities || flowDef.actions || flowDef.steps || [];
1475
-
1476
- return {
1477
- hasContent: activities.length > 0,
1478
- details: {
1479
- name: flow.name,
1480
- activities_count: activities.length,
1481
- has_trigger: !!flowDef.trigger,
1482
- status: flow.status || 'unknown',
1483
- active: flow.active
1484
- }
1485
- };
1486
-
1487
- } catch (error) {
1488
- return { hasContent: false, details: `Error: ${error.message}` };
1489
- }
1490
- }
1491
-
1492
- /**
1493
- * Create a ServiceNow flow using the enhanced flow structure builder
1494
- * Generates proper sys_ids, logic chains, and all required records
1495
- */
1496
- async createFlowWithStructureBuilder(flowDefinition: any): Promise<ServiceNowAPIResponse<any>> {
1497
- try {
1498
- this.logger.info('🏗️ Creating flow with structure builder...');
1499
- this.logger.info(`📋 Flow: ${flowDefinition.name}`);
1500
-
1501
- await this.ensureAuthenticated();
1502
-
1503
- // Generate all flow components with proper structure
1504
- // const components = generateFlowComponents(flowDefinition); // Deprecated in v1.4.0
1505
- const components = {
1506
- logicChain: [],
1507
- variables: [],
1508
- flowRecord: { sys_id: 'deprecated' },
1509
- triggerInstance: { sys_id: 'deprecated' },
1510
- actionInstances: []
1511
- };
1512
-
1513
- // Validate components before deployment
1514
- // const validation = validateFlowComponents(components); // Deprecated in v1.4.0
1515
- const validation = { isValid: true, errors: [], warnings: [] };
1516
- if (!validation.isValid) {
1517
- throw new Error(`Flow validation failed: ${validation.errors.join(', ')}`);
1518
- }
1519
-
1520
- if (validation.warnings.length > 0) {
1521
- console.warn('⚠️ Flow validation warnings:');
1522
- validation.warnings.forEach(warning => console.warn(` • ${warning}`));
1523
- }
1524
-
1525
- // Deploy all components in correct order
1526
- this.logger.info('🚀 Deploying flow components...');
1527
-
1528
- // 1. Create main flow record
1529
- const flowResponse = await this.client.post(
1530
- `${this.getBaseUrl()}/api/now/table/sys_hub_flow`,
1531
- components.flowRecord
1532
- );
1533
-
1534
- if (!flowResponse.data?.result) {
1535
- throw new Error('Failed to create flow record');
1536
- }
1537
-
1538
- const flowSysId = flowResponse.data.result.sys_id;
1539
- this.logger.info(`✅ Flow record created: ${flowSysId}`);
1540
-
1541
- // 2. Create trigger instance
1542
- await this.client.post(
1543
- `${this.getBaseUrl()}/api/now/table/sys_hub_trigger_instance`,
1544
- components.triggerInstance
1545
- );
1546
- this.logger.info(`✅ Trigger created: ${components.triggerInstance.sys_id}`);
1547
-
1548
- // 3. Create action instances
1549
- for (const action of components.actionInstances) {
1550
- await this.client.post(
1551
- `${this.getBaseUrl()}/api/now/table/sys_hub_action_instance`,
1552
- action
1553
- );
1554
- }
1555
- this.logger.info(`✅ Created ${components.actionInstances.length} action instances`);
1556
-
1557
- // 4. Create logic chain (connections)
1558
- for (const logic of components.logicChain) {
1559
- await this.client.post(
1560
- `${this.getBaseUrl()}/api/now/table/sys_hub_flow_logic`,
1561
- logic
1562
- );
1563
- }
1564
- this.logger.info(`✅ Created logic chain with ${components.logicChain.length} connections`);
1565
-
1566
- // 5. Create variables
1567
- for (const variable of components.variables) {
1568
- await this.client.post(
1569
- `${this.getBaseUrl()}/api/now/table/sys_hub_flow_variable`,
1570
- variable
1571
- );
1572
- }
1573
- this.logger.info(`✅ Created ${components.variables.length} flow variables`);
1574
-
1575
- // Verify deployment
1576
- await this.verifyDeployment(flowSysId, 'flow');
1577
-
1578
- const credentials = await this.oauth.loadCredentials();
1579
- const instance = credentials?.instance?.replace(/\/$/, '');
1580
-
1581
- return {
1582
- success: true,
1583
- data: {
1584
- ...flowResponse.data.result,
1585
- sys_id: flowSysId,
1586
- url: `https://${instance}/nav_to.do?uri=sys_hub_flow.do?sys_id=${flowSysId}`,
1587
- flow_designer_url: `https://${instance}/$flow-designer.do?sysparm_nostack=true&sysparm_sys_id=${flowSysId}`,
1588
- components: {
1589
- flow_record: components.flowRecord.sys_id,
1590
- trigger_instance: components.triggerInstance.sys_id,
1591
- action_instances: components.actionInstances.length,
1592
- logic_chain_entries: components.logicChain.length,
1593
- variables: components.variables.length
1594
- },
1595
- flow_xml: '<!-- Flow XML generation deprecated in v1.4.0 -->'
1596
- }
1597
- };
1598
-
1599
- } catch (error) {
1600
- console.error('❌ Failed to create flow with structure builder:', error);
1601
- return {
1602
- success: false,
1603
- error: error instanceof Error ? error.message : String(error)
1604
- };
1605
- }
1606
- }
1607
-
1608
- /**
1609
- * Create a simple Flow Designer flow (original method)
1610
- * Focusing on basic flow creation with simple actions
1611
- */
1612
- async createFlow(flow: any): Promise<ServiceNowAPIResponse<any>> {
1613
- try {
1614
- this.logger.info('🔄 Creating Flow Designer flow...');
1615
- this.logger.info(`📋 Flow: ${flow.name}`);
1616
-
1617
- // 🔧 CRITICAL DEBUG: Check if makeRequest exists on this instance
1618
- this.logger.debug('🔧 CRITICAL DEBUG - Client methods check:', {
1619
- hasCreateFlow: typeof this.createFlow === 'function',
1620
- hasMakeRequest: typeof this.makeRequest === 'function',
1621
- clientPrototype: Object.getOwnPropertyNames(Object.getPrototypeOf(this)),
1622
- clientConstructor: this.constructor.name
1623
- });
1624
-
1625
- // Add pre-deployment validation
1626
- this.validateFlowBeforeDeployment(flow);
1627
-
1628
- // First, ensure we have an Update Set
1629
- const updateSetResult = await this.ensureUpdateSet();
1630
-
1631
- // Get dynamic defaults from ServiceNow
1632
- const flowDefaults = await this.getFlowDefaults();
1633
-
1634
- // 🔧 CRITICAL FIX: Build complete flow snapshot BEFORE creating flow record
1635
- // This prevents the "Your flow cannot be found" error caused by incremental saves
1636
-
1637
- // Prepare activities from flow definition
1638
- let activitiesToProcess = flow.activities || flow.actions || [];
1639
- if (activitiesToProcess.length === 0 && flow.flow_definition) {
1640
- try {
1641
- const flowDef = typeof flow.flow_definition === 'string' ?
1642
- JSON.parse(flow.flow_definition) : flow.flow_definition;
1643
- activitiesToProcess = flowDef.activities || flowDef.steps || [];
1644
- } catch (parseError) {
1645
- console.warn('Could not parse flow_definition for activities:', parseError);
1646
- }
1647
- }
1648
-
1649
- // Build complete flow definition in ServiceNow Flow Designer format
1650
- const flowDefinition = {
1651
- "name": flow.name,
1652
- "description": flow.description || flow.name,
1653
- "trigger": {
1654
- "type": flow.trigger_type || 'manual',
1655
- "table": flow.table || 'incident',
1656
- "condition": flow.trigger_condition || flow.condition || ''
1657
- },
1658
- "activities": activitiesToProcess.map((action, index) => ({
1659
- "name": action.name,
1660
- "label": action.label || action.name,
1661
- "type": action.type || "core",
1662
- "operation": action.operation || "script",
1663
- "inputs": action.inputs || {},
1664
- "outputs": action.outputs || {},
1665
- "order": (index + 1) * 100,
1666
- "active": true
1667
- })),
1668
- "inputs": (flow.inputs || []).map(input => ({
1669
- "name": input.name || input.id,
1670
- "label": input.label || input.name || input.id,
1671
- "type": input.type || "string",
1672
- "mandatory": input.required || false,
1673
- "default_value": input.default || ""
1674
- })),
1675
- "outputs": (flow.outputs || []).map(output => ({
1676
- "name": output.name || output.id,
1677
- "label": output.label || output.name || output.id,
1678
- "type": output.type || "string"
1679
- })),
1680
- "variables": flow.variables || [],
1681
- "version": "1.0"
1682
- };
1683
-
1684
- // Build flow structure with dynamic defaults AND complete snapshot
1685
- const flowData = {
1686
- name: flow.name,
1687
- description: flow.description || flow.name,
1688
- active: true, // Always create as active
1689
- internal_name: this.sanitizeInternalName(flow.name),
1690
- category: flow.category || 'custom',
1691
- run_as: flow.run_as || 'user', // 🔒 SEC-001 FIX: Default to 'user' to prevent privilege escalation
1692
- status: 'published', // 🔧 FIX: Ensure flow is published, not draft
1693
- validated: true, // 🔧 FIX: Mark as validated to allow activation
1694
- // Include complete flow definition to prevent corruption
1695
- flow_definition: JSON.stringify(flowDefinition),
1696
- // Additional fields for proper flow creation
1697
- latest_snapshot: JSON.stringify(flowDefinition),
1698
- model_id: flow.model_id || '',
1699
- natlang: flow.description || flow.name,
1700
- copied_from: '',
1701
- copied_from_name: '',
1702
- show_draft_actions: false,
1703
- show_triggered_flows: false,
1704
- // Merge with dynamic defaults from ServiceNow
1705
- ...flowDefaults,
1706
- // Override with any specific values from the flow parameter
1707
- ...flow.overrides,
1708
- // 🔧 CRITICAL FIX: Ensure flow type is respected and not overridden
1709
- type: flow.type || 'flow'
1710
- };
1711
-
1712
- // Create the main flow record WITH complete snapshot
1713
- const response = await this.client.post(
1714
- `${this.getBaseUrl()}/api/now/table/sys_hub_flow`,
1715
- flowData
1716
- );
1717
-
1718
- if (!response.data || !response.data.result) {
1719
- throw new Error('No response data from flow creation');
1720
- }
1721
-
1722
- const flowId = response.data.result.sys_id;
1723
- this.logger.info('✅ Flow created successfully with complete snapshot!');
1724
- this.logger.info(`🆔 Flow sys_id: ${flowId}`);
1725
-
1726
- // 🔧 CRITICAL FIX: Create action instances after flow creation
1727
- // While the flow definition contains the structure, ServiceNow also needs
1728
- // actual sys_hub_action_instance records for proper execution
1729
-
1730
- this.logger.info('📋 Flow components included in definition:');
1731
- this.logger.info(`- Trigger: ${flowDefinition.trigger.type}`);
1732
- this.logger.info(`- Activities: ${flowDefinition.activities.length}`);
1733
- this.logger.info(`- Inputs: ${flowDefinition.inputs.length}`);
1734
- this.logger.info(`- Outputs: ${flowDefinition.outputs.length}`);
1735
-
1736
- // Create action instances for each activity
1737
- if (activitiesToProcess.length > 0) {
1738
- this.logger.info('🔧 Creating action instances for activities...');
1739
- for (let i = 0; i < activitiesToProcess.length; i++) {
1740
- const activity = activitiesToProcess[i];
1741
- try {
1742
- await this.createFlowActionInstance(flowId, activity, (i + 1) * 100);
1743
- this.logger.info(`✅ Action instance created: ${activity.name}`);
1744
- } catch (activityError) {
1745
- console.warn(`⚠️ Failed to create action instance ${activity.name}:`, activityError);
1746
- // Continue with other activities even if one fails
1747
- }
1748
- }
1749
- this.logger.info(`✅ Created ${activitiesToProcess.length} action instances`);
1750
- }
1751
-
1752
- // 🔧 NEW: Only need to activate the flow since it already has complete definition
1753
- try {
1754
- this.logger.info('⚡ Activating flow...');
1755
- await this.activateFlow(flowId);
1756
- this.logger.info('✅ Flow activated successfully!');
1757
- } catch (activationError) {
1758
- console.warn('⚠️ Flow activation failed:', activationError);
1759
- // Don't fail the entire flow creation if activation fails
1760
- }
1761
-
1762
- // 🔧 CRITICAL FIX: Create actual ServiceNow records after flow creation
1763
- // The JSON definition alone is not enough - we need component records
1764
- this.logger.info('🔧 Creating actual ServiceNow flow component records...');
1765
-
1766
- try {
1767
- // 1. Create trigger instance if trigger is specified
1768
- if (flow.trigger_type && flow.trigger_type !== 'manual') {
1769
- const triggerData = {
1770
- type: flow.trigger_type,
1771
- table: flow.table || 'incident',
1772
- condition: flow.trigger_condition || flow.condition || ''
1773
- };
1774
-
1775
- const triggerResult = await this.createFlowTrigger(flowId, triggerData);
1776
- this.logger.info(`✅ Trigger created: ${triggerResult.sys_id}`);
1777
- }
1778
-
1779
- // 2. Create action instances for each activity
1780
- if (activitiesToProcess && activitiesToProcess.length > 0) {
1781
- const actionResults = [];
1782
- for (let i = 0; i < activitiesToProcess.length; i++) {
1783
- const activity = activitiesToProcess[i];
1784
- const order = (i + 1) * 100;
1785
-
1786
- try {
1787
- const actionResult = await this.createFlowActionInstance(flowId, activity, order);
1788
- actionResults.push(actionResult);
1789
- this.logger.info(`✅ Action created: ${activity.name} (${actionResult.sys_id})`);
1790
- } catch (actionError) {
1791
- console.warn(`⚠️ Failed to create action ${activity.name}:`, actionError);
1792
- }
1793
- }
1794
-
1795
- // 3. Create flow logic entries for visual representation
1796
- if (actionResults.length > 0) {
1797
- for (let i = 0; i < actionResults.length; i++) {
1798
- const action = actionResults[i];
1799
- const logicData = {
1800
- name: action.action_name,
1801
- type: 'action',
1802
- order: (i + 1) * 100,
1803
- instance: action.sys_id
1804
- };
1805
-
1806
- try {
1807
- const logicResult = await this.createFlowLogic(flowId, logicData);
1808
- this.logger.info(`✅ Flow logic created: ${logicResult.sys_id}`);
1809
- } catch (logicError) {
1810
- console.warn(`⚠️ Failed to create flow logic for ${action.action_name}:`, logicError);
1811
- }
1812
- }
1813
- }
1814
- }
1815
-
1816
- this.logger.info('✅ All flow component records created successfully!');
1817
- } catch (componentError) {
1818
- console.warn('⚠️ Some flow components may not have been created properly:', componentError);
1819
- // Don't fail the entire flow creation if component creation fails
1820
- }
1821
-
1822
- // Add post-deployment verification
1823
- await this.verifyDeployment(flowId, 'flow');
1824
-
1825
- // 🔧 CRITICAL FIX: Enhanced response with proper ServiceNow URLs and flow type
1826
- const flowRecord = response.data.result;
1827
- const credentials = await this.oauth.loadCredentials();
1828
- const instance = credentials?.instance || process.env.SNOW_INSTANCE;
1829
-
1830
- return {
1831
- success: true,
1832
- data: {
1833
- ...flowRecord,
1834
- // Enhanced response format with proper URLs
1835
- url: `https://${instance}/nav_to.do?uri=sys_hub_flow.do?sys_id=${flowRecord.sys_id}`,
1836
- flow_designer_url: `https://${instance}/$flow-designer.do?sysparm_nostack=true&sysparm_sys_id=${flowRecord.sys_id}`,
1837
- type: flowRecord.type || flow.type || 'flow', // Ensure type is included
1838
- activities_created: flowDefinition.activities.length,
1839
- variables_created: (flow.inputs?.length || 0) + (flow.outputs?.length || 0),
1840
- trigger_configured: !!flow.trigger_type,
1841
- sys_trigger_created: flow.trigger_type === 'record_created' || flow.trigger_type === 'record_updated',
1842
- component_records_created: true, // New field to indicate component records were created
1843
- has_actual_flow_instances: activitiesToProcess && activitiesToProcess.length > 0
1844
- }
1845
- };
1846
- } catch (error) {
1847
- console.error('❌ Failed to create flow:', error);
1848
- return {
1849
- success: false,
1850
- error: error instanceof Error ? error.message : String(error)
1851
- };
1852
- }
1853
- }
1854
-
1855
- /**
1856
- * Activate a flow to ensure it's published and ready
1857
- */
1858
- async activateFlow(flowSysId: string): Promise<void> {
1859
- try {
1860
- await this.ensureAuthenticated();
1861
-
1862
- // Update the flow to set it as active and published
1863
- const response = await this.client.patch(
1864
- `${this.getBaseUrl()}/api/now/table/sys_hub_flow/${flowSysId}`,
1865
- {
1866
- active: true,
1867
- status: 'published',
1868
- validated: true
1869
- }
1870
- );
1871
-
1872
- if (!response.data || !response.data.result) {
1873
- throw new Error('Failed to activate flow - no response');
1874
- }
1875
-
1876
- return response.data.result;
1877
- } catch (error) {
1878
- console.error('Failed to activate flow:', error);
1879
- throw error;
1880
- }
1881
- }
1882
-
1883
- /**
1884
- * Generate flow snapshot to prevent "Your flow cannot be found" error
1885
- * This uses the Flow Designer API to generate a proper snapshot
1886
- */
1887
- async generateFlowSnapshot(flowSysId: string): Promise<void> {
1888
- try {
1889
- await this.ensureAuthenticated();
1890
-
1891
- // Call Flow Designer API to generate snapshot
1892
- // This endpoint triggers ServiceNow to build a proper flow snapshot
1893
- const response = await this.client.post(
1894
- `${this.getBaseUrl()}/api/sn_flow_designer/flow/snapshot`,
1895
- {
1896
- flow_id: flowSysId,
1897
- generate: true
1898
- }
1899
- );
1900
-
1901
- if (!response.data || !response.data.result) {
1902
- // Try alternative endpoint if the first one fails
1903
- const altResponse = await this.client.put(
1904
- `${this.getBaseUrl()}/api/now/table/sys_hub_flow/${flowSysId}`,
1905
- {
1906
- snapshot_ready: true
1907
- }
1908
- );
1909
-
1910
- if (!altResponse.data || !altResponse.data.result) {
1911
- throw new Error('Failed to generate flow snapshot');
1912
- }
1913
- }
1914
-
1915
- return response.data?.result || true;
1916
- } catch (error) {
1917
- console.error('Failed to generate flow snapshot:', error);
1918
- // Don't throw - this is a best effort operation
1919
- }
1920
- }
1921
-
1922
- /**
1923
- * Create a Subflow in ServiceNow
1924
- */
1925
- async createSubflow(subflow: any): Promise<ServiceNowAPIResponse<any>> {
1926
- try {
1927
- this.logger.info('🔄 Creating Subflow...');
1928
- this.logger.info(`📋 Subflow: ${subflow.name}`);
1929
-
1930
- // Add pre-deployment validation
1931
- this.validateFlowBeforeDeployment(subflow);
1932
-
1933
- // Ensure Update Set
1934
- const updateSetResult = await this.ensureUpdateSet();
1935
-
1936
- // Create subflow with inputs/outputs
1937
- const subflowData = {
1938
- name: subflow.name,
1939
- description: subflow.description || `Subflow: ${subflow.name}`,
1940
- active: subflow.active !== false,
1941
- internal_name: this.sanitizeInternalName(subflow.name),
1942
- category: subflow.category || 'custom',
1943
- type: 'subflow',
1944
- inputs: JSON.stringify(subflow.inputs || []),
1945
- outputs: JSON.stringify(subflow.outputs || []),
1946
- run_as: 'user_who_calls'
1947
- };
1948
-
1949
- const response = await this.client.post(
1950
- `${this.getBaseUrl()}/api/now/table/sys_hub_flow`,
1951
- subflowData
1952
- );
1953
-
1954
- const subflowId = response.data.result.sys_id;
1955
-
1956
- // Process flow definition if provided
1957
- if (subflow.flow_definition) {
1958
- const definition = typeof subflow.flow_definition === 'string'
1959
- ? JSON.parse(subflow.flow_definition)
1960
- : subflow.flow_definition;
1961
-
1962
- // Create activities for the subflow
1963
- if (definition.activities) {
1964
- for (let i = 0; i < definition.activities.length; i++) {
1965
- const activity = definition.activities[i];
1966
- await this.createFlowActionInstance(subflowId, activity, (i + 1) * 100);
1967
- }
1968
- }
1969
- }
1970
-
1971
- // Add post-deployment verification
1972
- await this.verifyDeployment(subflowId, 'subflow');
1973
-
1974
- // 🔧 CRITICAL FIX: Enhanced response with proper ServiceNow URLs and subflow type
1975
- const subflowRecord = response.data.result;
1976
- const credentials = await this.oauth.loadCredentials();
1977
- const instance = credentials?.instance || process.env.SNOW_INSTANCE;
1978
-
1979
- return {
1980
- success: true,
1981
- data: {
1982
- ...subflowRecord,
1983
- // Enhanced response format with proper URLs
1984
- url: `https://${instance}/nav_to.do?uri=sys_hub_flow.do?sys_id=${subflowRecord.sys_id}`,
1985
- flow_designer_url: `https://${instance}/$flow-designer.do?sysparm_nostack=true&sysparm_sys_id=${subflowRecord.sys_id}`,
1986
- type: 'subflow', // Ensure correct type
1987
- activities_created: subflow.activities?.length || 0,
1988
- inputs_defined: subflow.inputs?.length || 0,
1989
- outputs_defined: subflow.outputs?.length || 0,
1990
- reusable: true
1991
- }
1992
- };
1993
- } catch (error) {
1994
- console.error('❌ Failed to create subflow:', error);
1995
- return {
1996
- success: false,
1997
- error: error instanceof Error ? error.message : String(error)
1998
- };
1999
- }
2000
- }
2001
-
2002
- /**
2003
- * Create a Flow Action (reusable component)
2004
- */
2005
- async createFlowAction(action: any): Promise<ServiceNowAPIResponse<any>> {
2006
- try {
2007
- this.logger.info('⚡ Creating Flow Action...');
2008
- this.logger.info(`📋 Action: ${action.name}`);
2009
-
2010
- // Add pre-deployment validation (adapted for actions)
2011
- if (!action.name || action.name.trim() === '') {
2012
- throw new Error('Flow Action name is required');
2013
- }
2014
- if (!action.description || action.description.trim() === '') {
2015
- throw new Error('Flow Action description is required');
2016
- }
2017
-
2018
- // Ensure Update Set
2019
- const updateSetResult = await this.ensureUpdateSet();
2020
-
2021
- // Create action with inputs/outputs
2022
- const actionData = {
2023
- name: action.name,
2024
- description: action.description || `Action: ${action.name}`,
2025
- active: action.active !== false,
2026
- internal_name: this.sanitizeInternalName(action.name),
2027
- category: action.category || 'custom',
2028
- type: 'action',
2029
- action_type: action.action_type || 'custom',
2030
- inputs: JSON.stringify(action.inputs || []),
2031
- outputs: JSON.stringify(action.outputs || []),
2032
- script: action.script || '',
2033
- accessible_from: 'all' // Make available to all flows
2034
- };
2035
-
2036
- const response = await this.client.post(
2037
- `${this.getBaseUrl()}/api/now/table/sys_hub_action_type_definition`,
2038
- actionData
2039
- );
2040
-
2041
- // Add basic verification for action creation
2042
- if (response.data.result?.sys_id) {
2043
- this.logger.info(`✅ Flow Action created: ${action.name} (${response.data.result.sys_id})`);
2044
- }
2045
-
2046
- return {
2047
- success: true,
2048
- data: response.data.result
2049
- };
2050
- } catch (error) {
2051
- console.error('❌ Failed to create flow action:', error);
2052
- return {
2053
- success: false,
2054
- error: error instanceof Error ? error.message : String(error)
2055
- };
2056
- }
2057
- }
2058
-
2059
-
2060
- /**
2061
- * Create a flow action using discovered ServiceNow action types (private method)
2062
- */
2063
- private async createFlowActionPrivate(flowId: string, action: any, order: number): Promise<any> {
2064
- try {
2065
- this.logger.info(`Creating flow action: ${action.type} - ${action.name}`);
2066
-
2067
- // If we have a discovered action type, use it
2068
- let actionTypeId = action.action_type_id;
2069
-
2070
- // If no specific action type provided, search for one
2071
- if (!actionTypeId) {
2072
- const searchResults = await this.searchFlowActions(action.type);
2073
- if (searchResults.actionTypes && searchResults.actionTypes.length > 0) {
2074
- actionTypeId = searchResults.actionTypes[0].sys_id;
2075
- this.logger.info(`📋 Using discovered action type: ${searchResults.actionTypes[0].name}`);
2076
- } else {
2077
- // Fallback to common action types
2078
- const fallbackMap: any = {
2079
- 'notification': 'com.glideapp.servicenow_common.send_email',
2080
- 'field_update': 'com.glideapp.servicenow_common.update_record',
2081
- 'create_task': 'com.glideapp.servicenow_common.create_record',
2082
- 'log': 'com.glideapp.servicenow_common.log_message',
2083
- 'wait': 'com.glideapp.servicenow_common.timer',
2084
- 'approval': 'com.glideapp.servicenow_common.approval'
2085
- };
2086
- actionTypeId = fallbackMap[action.type] || 'com.glideapp.servicenow_common.script';
2087
- this.logger.info(`📋 Using fallback action type: ${actionTypeId}`);
2088
- }
2089
- }
2090
-
2091
- // Get action details to understand inputs/outputs
2092
- const actionDetails = await this.getFlowActionDetails(actionTypeId);
2093
-
2094
- // Build inputs based on action details
2095
- const inputs = this.buildActionInputs(action, actionDetails);
2096
-
2097
- const actionData = {
2098
- flow: flowId,
2099
- action_name: action.name,
2100
- action_type: actionTypeId,
2101
- order: order * 100,
2102
- active: true,
2103
- // Use proper ServiceNow action configuration
2104
- inputs: JSON.stringify(inputs),
2105
- configuration: this.buildActionConfiguration(action)
2106
- };
2107
-
2108
- const response = await this.client.post(
2109
- `${this.getBaseUrl()}/api/now/table/sys_hub_action_instance`,
2110
- actionData
2111
- );
2112
-
2113
- this.logger.info(`✅ Flow action created: ${action.name}`);
2114
- return response.data.result;
2115
- } catch (error) {
2116
- console.error('Failed to create flow action:', error);
2117
- throw error;
2118
- }
2119
- }
2120
-
2121
- /**
2122
- * Build action inputs based on action details
2123
- */
2124
- private buildActionInputs(action: any, actionDetails: any): any {
2125
- const inputs: any = {};
2126
-
2127
- if (!actionDetails || !actionDetails.inputs) {
2128
- return inputs;
2129
- }
2130
-
2131
- // Map action properties to ServiceNow input variables
2132
- for (const input of actionDetails.inputs) {
2133
- const inputName = input.name;
2134
-
2135
- switch (action.type) {
2136
- case 'notification':
2137
- if (inputName === 'to' || inputName === 'recipient') {
2138
- inputs[inputName] = action.to || '${trigger.record.assigned_to}';
2139
- } else if (inputName === 'subject') {
2140
- inputs[inputName] = action.subject || 'Flow Notification';
2141
- } else if (inputName === 'message' || inputName === 'body') {
2142
- inputs[inputName] = action.message || 'Flow action executed';
2143
- }
2144
- break;
2145
-
2146
- case 'field_update':
2147
- if (inputName === 'record' || inputName === 'table') {
2148
- inputs[inputName] = '${trigger.record}';
2149
- } else if (inputName === 'field') {
2150
- inputs[inputName] = action.field;
2151
- } else if (inputName === 'value') {
2152
- inputs[inputName] = action.value;
2153
- }
2154
- break;
2155
-
2156
- case 'create_task':
2157
- if (inputName === 'table') {
2158
- inputs[inputName] = action.target_table || 'task';
2159
- } else if (inputName === 'fields') {
2160
- inputs[inputName] = action.fields || {};
2161
- }
2162
- break;
2163
-
2164
- case 'log':
2165
- if (inputName === 'message') {
2166
- inputs[inputName] = action.message || 'Flow action executed';
2167
- } else if (inputName === 'level') {
2168
- inputs[inputName] = action.level || 'info';
2169
- }
2170
- break;
2171
-
2172
- case 'wait':
2173
- if (inputName === 'duration') {
2174
- inputs[inputName] = action.duration || 300;
2175
- }
2176
- break;
2177
-
2178
- case 'approval':
2179
- if (inputName === 'approvers') {
2180
- inputs[inputName] = action.approvers || '${trigger.record.assigned_to.manager}';
2181
- }
2182
- break;
2183
- }
2184
- }
2185
-
2186
- return inputs;
2187
- }
2188
-
2189
- /**
2190
- * Build simple action configuration
2191
- */
2192
- private buildActionConfiguration(action: any): string {
2193
- const config: any = {
2194
- name: action.name,
2195
- type: action.type
2196
- };
2197
-
2198
- switch (action.type) {
2199
- case 'notification':
2200
- config.to = action.to || 'assigned_to';
2201
- config.subject = action.subject || 'Flow Notification';
2202
- config.body = action.message || 'A flow action has been triggered.';
2203
- break;
2204
-
2205
- case 'field_update':
2206
- config.field = action.field;
2207
- config.value = action.value;
2208
- break;
2209
-
2210
- case 'create_task':
2211
- config.table = action.target_table || 'task';
2212
- config.fields = action.fields || {};
2213
- break;
2214
-
2215
- case 'log':
2216
- config.message = action.message || 'Flow action executed';
2217
- config.level = action.level || 'info';
2218
- break;
2219
-
2220
- case 'wait':
2221
- config.duration = action.duration || 300; // 5 minutes default
2222
- break;
2223
-
2224
- case 'approval':
2225
- config.approvers = action.approvers || 'assigned_to.manager';
2226
- break;
2227
-
2228
- default:
2229
- config.script = action.script || '// Custom action';
2230
- }
2231
-
2232
- return JSON.stringify(config);
2233
- }
2234
-
2235
- /**
2236
- * Create a flow trigger instance
2237
- */
2238
- private async createFlowTrigger(flowId: string, trigger: any): Promise<any> {
2239
- try {
2240
- // Get trigger type from cache
2241
- let triggerType = await this.actionTypeCache.getTriggerType(trigger.type);
2242
-
2243
- if (!triggerType) {
2244
- console.warn(`Trigger type '${trigger.type}' not found in cache, using default`);
2245
- // Fallback to 'Created' trigger if not found
2246
- triggerType = await this.actionTypeCache.getTriggerType('Created');
2247
- if (!triggerType) {
2248
- throw new Error('Could not find any trigger types');
2249
- }
2250
- }
2251
-
2252
- const triggerTypeId = triggerType.sys_id;
2253
-
2254
- const triggerData = {
2255
- flow: flowId,
2256
- trigger_type: triggerTypeId,
2257
- table_name: trigger.table || 'incident',
2258
- condition: trigger.condition || '',
2259
- order: 0,
2260
- active: true
2261
- };
2262
-
2263
- const response = await this.client.post(
2264
- `${this.getBaseUrl()}/api/now/table/sys_hub_trigger_instance`,
2265
- triggerData
2266
- );
2267
-
2268
- // 🔧 CRITICAL FIX: Create actual sys_trigger record for proper triggered flows
2269
- // This ensures the flow can actually be triggered by record events
2270
- // Only create sys_trigger for actual record events, not for manual or scheduled triggers
2271
- if (trigger.type === 'record_created' || trigger.type === 'record_updated' ||
2272
- trigger.type === 'Created' || trigger.type === 'Updated') {
2273
- try {
2274
- const sysTriggerData = {
2275
- name: `Flow Trigger: ${trigger.table}`,
2276
- table: trigger.table || 'incident',
2277
- when: trigger.type === 'record_created' ? 'after' : 'before',
2278
- order: 100,
2279
- active: true,
2280
- condition: trigger.condition || '',
2281
- script: `
2282
- // Auto-generated trigger for Flow Designer flow
2283
- // Flow ID: ${flowId}
2284
- try {
2285
- var flowAPI = new sn_fd.FlowAPI();
2286
- var inputs = {};
2287
- inputs.table = '${trigger.table || 'incident'}';
2288
- inputs.sys_id = current.getUniqueValue();
2289
- flowAPI.startFlow('${flowId}', inputs);
2290
- gs.info('Flow ${flowId} triggered successfully for record: ' + current.getUniqueValue());
2291
- } catch (e) {
2292
- gs.error('Flow trigger error: ' + e.message);
2293
- }
2294
- `.trim(),
2295
- description: `Auto-generated trigger for Flow Designer flow: ${flowId}`
2296
- };
2297
-
2298
- await this.client.post(
2299
- `${this.getBaseUrl()}/api/now/table/sys_trigger`,
2300
- sysTriggerData
2301
- );
2302
-
2303
- this.logger.info('✅ sys_trigger record created for proper flow execution');
2304
- } catch (sysTriggerError) {
2305
- console.warn('Could not create sys_trigger record, flow may not trigger properly:', sysTriggerError);
2306
- }
2307
- }
2308
-
2309
- this.logger.info('✅ Trigger created successfully');
2310
- return response.data.result;
2311
- } catch (error) {
2312
- console.error('Failed to create flow trigger:', error);
2313
- throw error;
2314
- }
2315
- }
2316
-
2317
- /**
2318
- * Create flow variables for inputs and outputs
2319
- */
2320
- private async createFlowVariables(flowId: string, flow: any): Promise<void> {
2321
- try {
2322
- this.logger.info('📋 Creating flow variables...');
2323
-
2324
- // Process inputs
2325
- const inputs = flow.inputs || [];
2326
- if (inputs.length > 0) {
2327
- for (const input of inputs) {
2328
- const variableData = {
2329
- flow: flowId,
2330
- name: input.name || input.id,
2331
- label: input.label || input.name || input.id,
2332
- type: input.type || 'string',
2333
- input: true,
2334
- output: false,
2335
- required: input.required || false,
2336
- default_value: input.default || '',
2337
- description: input.description || `Input variable: ${input.name || input.id}`
2338
- };
2339
-
2340
- await this.client.post(
2341
- `${this.getBaseUrl()}/api/now/table/sys_hub_flow_variable`,
2342
- variableData
2343
- );
2344
- }
2345
- this.logger.info(`✅ Created ${inputs.length} input variables`);
2346
- }
2347
-
2348
- // Process outputs
2349
- const outputs = flow.outputs || [];
2350
- if (outputs.length > 0) {
2351
- for (const output of outputs) {
2352
- const variableData = {
2353
- flow: flowId,
2354
- name: output.name || output.id,
2355
- label: output.label || output.name || output.id,
2356
- type: output.type || 'string',
2357
- input: false,
2358
- output: true,
2359
- required: false,
2360
- default_value: '',
2361
- description: output.description || `Output variable: ${output.name || output.id}`
2362
- };
2363
-
2364
- await this.client.post(
2365
- `${this.getBaseUrl()}/api/now/table/sys_hub_flow_variable`,
2366
- variableData
2367
- );
2368
- }
2369
- this.logger.info(`✅ Created ${outputs.length} output variables`);
2370
- }
2371
-
2372
- // Extract variables from flow_definition if available
2373
- if (flow.flow_definition) {
2374
- try {
2375
- const flowDef = typeof flow.flow_definition === 'string' ?
2376
- JSON.parse(flow.flow_definition) : flow.flow_definition;
2377
-
2378
- if (flowDef.variables && Array.isArray(flowDef.variables)) {
2379
- for (const variable of flowDef.variables) {
2380
- const variableData = {
2381
- flow: flowId,
2382
- name: variable.name || variable.id,
2383
- label: variable.label || variable.name || variable.id,
2384
- type: variable.type || 'string',
2385
- input: variable.input || false,
2386
- output: variable.output || false,
2387
- required: variable.required || false,
2388
- default_value: variable.default || variable.value || '',
2389
- description: variable.description || `Flow variable: ${variable.name || variable.id}`
2390
- };
2391
-
2392
- await this.client.post(
2393
- `${this.getBaseUrl()}/api/now/table/sys_hub_flow_variable`,
2394
- variableData
2395
- );
2396
- }
2397
- this.logger.info(`✅ Created ${flowDef.variables.length} flow definition variables`);
2398
- }
2399
- } catch (parseError) {
2400
- console.warn('Could not parse flow_definition for variables:', parseError);
2401
- }
2402
- }
2403
-
2404
- } catch (error) {
2405
- console.error('Failed to create flow variables:', error);
2406
- throw error;
2407
- }
2408
- }
2409
-
2410
- /**
2411
- * Create a flow action instance using simplified approach
2412
- */
2413
- private async createFlowActionInstance(flowId: string, action: any, order: number): Promise<any> {
2414
- try {
2415
- this.logger.info(`Creating action instance: ${action.name}`);
2416
-
2417
- // Map action types to search terms
2418
- const actionSearchMap: any = {
2419
- 'notification': 'Send Email',
2420
- 'email': 'Send Email',
2421
- 'field_update': 'Update Record',
2422
- 'create_task': 'Create Record',
2423
- 'create_record': 'Create Record',
2424
- 'wait': 'Wait',
2425
- 'approval': 'Approval',
2426
- 'log': 'Log'
2427
- };
2428
-
2429
- const searchTerm = actionSearchMap[action.type] || action.type;
2430
- let actionType = await this.actionTypeCache.getActionType(searchTerm);
2431
-
2432
- if (!actionType) {
2433
- console.warn(`Action type '${searchTerm}' not found in cache, using Script action`);
2434
- // Fallback to Script action if not found
2435
- actionType = await this.actionTypeCache.getActionType('Script');
2436
- if (!actionType) {
2437
- throw new Error('Could not find any action types');
2438
- }
2439
- }
2440
-
2441
- const actionTypeId = actionType.sys_id;
2442
-
2443
- // Build inputs based on action type
2444
- let inputs = {};
2445
- switch (action.type) {
2446
- case 'notification':
2447
- case 'email':
2448
- inputs = {
2449
- email_to: action.to || '${trigger.assigned_to.email}',
2450
- email_subject: action.subject || 'Notification',
2451
- email_body: action.message || 'Notification from flow'
2452
- };
2453
- break;
2454
- case 'field_update':
2455
- inputs = {
2456
- table: action.table || 'current',
2457
- field: action.field || 'state',
2458
- value: action.value || ''
2459
- };
2460
- break;
2461
- case 'create_task':
2462
- case 'create_record':
2463
- inputs = {
2464
- table: action.target_table || 'task',
2465
- field_values: JSON.stringify({
2466
- short_description: action.short_description || 'Task from flow',
2467
- assigned_to: action.assigned_to || '${trigger.assigned_to}'
2468
- })
2469
- };
2470
- break;
2471
- case 'wait':
2472
- inputs = {
2473
- duration: action.duration || 300,
2474
- unit: 'seconds'
2475
- };
2476
- break;
2477
- case 'approval':
2478
- inputs = {
2479
- approvers: action.approvers || '${trigger.assigned_to.manager}',
2480
- approval_field: 'approval'
2481
- };
2482
- break;
2483
- default:
2484
- inputs = action.inputs || {};
2485
- }
2486
-
2487
- const actionData = {
2488
- flow: flowId,
2489
- action_name: action.name,
2490
- action_type: actionTypeId,
2491
- order: order,
2492
- active: true,
2493
- inputs: JSON.stringify(inputs)
2494
- };
2495
-
2496
- const response = await this.client.post(
2497
- `${this.getBaseUrl()}/api/now/table/sys_hub_action_instance`,
2498
- actionData
2499
- );
2500
-
2501
- this.logger.info(`✅ Action created: ${action.name}`);
2502
- return response.data.result;
2503
- } catch (error) {
2504
- console.error(`Failed to create action instance ${action.name}:`, error);
2505
- throw error;
2506
- }
2507
- }
2508
-
2509
- /**
2510
- * Create a flow operation (activity) for a Flow Designer flow
2511
- */
2512
- private async createFlowOperation(flowId: string, operation: any): Promise<any> {
2513
- try {
2514
- const response = await this.client.post(
2515
- `${this.getBaseUrl()}/api/now/table/sys_hub_flow_operation`,
2516
- {
2517
- flow: flowId,
2518
- name: operation.name,
2519
- type: operation.type || 'core',
2520
- action_type: operation.action_type || 'script',
2521
- order: operation.order || 100,
2522
- // Operation-specific configuration
2523
- configuration: JSON.stringify({
2524
- script: operation.script,
2525
- inputs: operation.inputs || {},
2526
- outputs: operation.outputs || {},
2527
- artifact_reference: operation.artifact_reference
2528
- })
2529
- }
2530
- );
2531
-
2532
- return response.data.result;
2533
- } catch (error) {
2534
- console.error('Failed to create flow operation:', error);
2535
- throw error;
2536
- }
2537
- }
2538
-
2539
- /**
2540
- * Create a Script Include
2541
- */
2542
- async createScriptInclude(scriptInclude: any): Promise<ServiceNowAPIResponse<any>> {
2543
- try {
2544
- this.logger.info('📝 Creating Script Include...');
2545
-
2546
- const response = await this.client.post(
2547
- `${this.getBaseUrl()}/api/now/table/sys_script_include`,
2548
- {
2549
- name: scriptInclude.name,
2550
- api_name: scriptInclude.api_name,
2551
- description: scriptInclude.description,
2552
- script: scriptInclude.script,
2553
- active: scriptInclude.active,
2554
- access: scriptInclude.access || 'public'
2555
- }
2556
- );
2557
-
2558
- this.logger.info('✅ Script Include created successfully!');
2559
-
2560
- return {
2561
- success: true,
2562
- data: response.data.result
2563
- };
2564
- } catch (error) {
2565
- console.error('❌ Failed to create script include:', error);
2566
- return {
2567
- success: false,
2568
- error: error instanceof Error ? error.message : String(error)
2569
- };
2570
- }
2571
- }
2572
-
2573
- /**
2574
- * Create a Business Rule
2575
- */
2576
- async createBusinessRule(businessRule: any): Promise<ServiceNowAPIResponse<any>> {
2577
- try {
2578
- this.logger.info('📋 Creating Business Rule...');
2579
-
2580
- const response = await this.client.post(
2581
- `${this.getBaseUrl()}/api/now/table/sys_script`,
2582
- {
2583
- name: businessRule.name,
2584
- collection: businessRule.table,
2585
- when: businessRule.when,
2586
- condition: businessRule.condition,
2587
- script: businessRule.script,
2588
- description: businessRule.description,
2589
- active: businessRule.active,
2590
- order: businessRule.order || 100
2591
- }
2592
- );
2593
-
2594
- this.logger.info('✅ Business Rule created successfully!');
2595
-
2596
- return {
2597
- success: true,
2598
- data: response.data.result
2599
- };
2600
- } catch (error) {
2601
- console.error('❌ Failed to create business rule:', error);
2602
- return {
2603
- success: false,
2604
- error: error instanceof Error ? error.message : String(error)
2605
- };
2606
- }
2607
- }
2608
-
2609
- /**
2610
- * Create a Table
2611
- */
2612
- async createTable(table: any): Promise<ServiceNowAPIResponse<any>> {
2613
- try {
2614
- this.logger.info('🗄️ Creating Table...');
2615
-
2616
- const response = await this.client.post(
2617
- `${this.getBaseUrl()}/api/now/table/sys_db_object`,
2618
- {
2619
- name: table.name,
2620
- label: table.label,
2621
- extends_table: table.extends_table || 'sys_metadata',
2622
- is_extendable: table.is_extendable !== false,
2623
- access: table.access || 'public',
2624
- create_access_controls: table.create_access_controls !== false
2625
- }
2626
- );
2627
-
2628
- this.logger.info('✅ Table created successfully!');
2629
-
2630
- return {
2631
- success: true,
2632
- data: response.data.result
2633
- };
2634
- } catch (error) {
2635
- console.error('❌ Failed to create table:', error);
2636
- return {
2637
- success: false,
2638
- error: error instanceof Error ? error.message : String(error)
2639
- };
2640
- }
2641
- }
2642
-
2643
- /**
2644
- * Create a Table Field
2645
- */
2646
- async createTableField(field: any): Promise<ServiceNowAPIResponse<any>> {
2647
- try {
2648
- this.logger.info('📊 Creating Table Field...');
2649
-
2650
- const response = await this.client.post(
2651
- `${this.getBaseUrl()}/api/now/table/sys_dictionary`,
2652
- {
2653
- name: `${field.table}.${field.element}`,
2654
- element: field.element,
2655
- column_label: field.column_label,
2656
- internal_type: field.internal_type || 'string',
2657
- max_length: field.max_length || 255,
2658
- active: true
2659
- }
2660
- );
2661
-
2662
- this.logger.info('✅ Table Field created successfully!');
2663
-
2664
- return {
2665
- success: true,
2666
- data: response.data.result
2667
- };
2668
- } catch (error) {
2669
- console.error('❌ Failed to create table field:', error);
2670
- return {
2671
- success: false,
2672
- error: error instanceof Error ? error.message : String(error)
2673
- };
2674
- }
2675
- }
2676
-
2677
- /**
2678
- * Create a new Update Set
2679
- */
2680
- async createUpdateSet(updateSet: any): Promise<ServiceNowAPIResponse<any>> {
2681
- try {
2682
- this.logger.info('📦 Creating Update Set...');
2683
-
2684
- // Ensure we have credentials before making the API call
2685
- await this.ensureAuthenticated();
2686
-
2687
- const response = await this.client.post(
2688
- `${this.getBaseUrl()}/api/now/table/sys_update_set`,
2689
- {
2690
- name: updateSet.name,
2691
- description: updateSet.description,
2692
- release_date: updateSet.release_date,
2693
- state: updateSet.state || 'in_progress',
2694
- application: updateSet.application || 'global'
2695
- }
2696
- );
2697
-
2698
- this.logger.info('✅ Update Set created successfully!');
2699
-
2700
- return {
2701
- success: true,
2702
- data: response.data.result
2703
- };
2704
- } catch (error) {
2705
- console.error('❌ Failed to create Update Set:', error);
2706
- return {
2707
- success: false,
2708
- error: error instanceof Error ? error.message : String(error)
2709
- };
2710
- }
2711
- }
2712
-
2713
- /**
2714
- * Set current Update Set for the session
2715
- */
2716
- async setCurrentUpdateSet(updateSetId: string): Promise<ServiceNowAPIResponse<any>> {
2717
- try {
2718
- this.logger.info('🔄 Setting current Update Set...');
2719
-
2720
- // Ensure we have credentials before making the API call
2721
- await this.ensureAuthenticated();
2722
-
2723
- // Use the sys_user_preference table to set the current update set
2724
- // First, check if a preference already exists
2725
- const existingPref = await this.client.get(
2726
- `${this.getBaseUrl()}/api/now/table/sys_user_preference`,
2727
- {
2728
- params: {
2729
- sysparm_query: 'name=sys_update_set^user=javascript:gs.getUserID()',
2730
- sysparm_limit: 1
2731
- }
2732
- }
2733
- );
2734
-
2735
- let response;
2736
- if (existingPref.data.result && existingPref.data.result.length > 0) {
2737
- // Update existing preference
2738
- response = await this.client.patch(
2739
- `${this.getBaseUrl()}/api/now/table/sys_user_preference/${existingPref.data.result[0].sys_id}`,
2740
- {
2741
- value: updateSetId
2742
- }
2743
- );
2744
- } else {
2745
- // Create new preference
2746
- response = await this.client.post(
2747
- `${this.getBaseUrl()}/api/now/table/sys_user_preference`,
2748
- {
2749
- name: 'sys_update_set',
2750
- value: updateSetId,
2751
- user: 'javascript:gs.getUserID()'
2752
- }
2753
- );
2754
- }
2755
-
2756
- this.logger.info('✅ Current Update Set changed successfully!');
2757
-
2758
- return {
2759
- success: true,
2760
- data: response.data.result
2761
- };
2762
- } catch (error) {
2763
- console.error('❌ Failed to set current Update Set:', error);
2764
- return {
2765
- success: false,
2766
- error: error instanceof Error ? error.message : String(error)
2767
- };
2768
- }
2769
- }
2770
-
2771
- /**
2772
- * Get current Update Set
2773
- */
2774
- async getCurrentUpdateSet(): Promise<ServiceNowAPIResponse<any>> {
2775
- try {
2776
- this.logger.info('📋 Getting current Update Set...');
2777
-
2778
- // Ensure we have credentials before making the API call
2779
- await this.ensureAuthenticated();
2780
-
2781
- // Try to get the current update set using sys_user_preference table
2782
- // This is more reliable than the UI API
2783
- try {
2784
- const response = await this.client.get(
2785
- `${this.getBaseUrl()}/api/now/table/sys_user_preference`,
2786
- {
2787
- params: {
2788
- sysparm_query: 'name=sys_update_set^user=javascript:gs.getUserID()',
2789
- sysparm_limit: 1
2790
- }
2791
- }
2792
- );
2793
-
2794
- if (response.data.result && response.data.result.length > 0) {
2795
- const updateSetId = response.data.result[0].value;
2796
-
2797
- // Get Update Set details
2798
- const updateSetResponse = await this.client.get(
2799
- `${this.getBaseUrl()}/api/now/table/sys_update_set/${updateSetId}`
2800
- );
2801
-
2802
- return {
2803
- success: true,
2804
- data: updateSetResponse.data.result
2805
- };
2806
- }
2807
- } catch (prefError) {
2808
- this.logger.info('⚠️ User preference lookup failed, trying fallback...');
2809
- }
2810
-
2811
- // Fallback: Get the most recent in-progress update set for the current user
2812
- const response = await this.client.get(
2813
- `${this.getBaseUrl()}/api/now/table/sys_update_set`,
2814
- {
2815
- params: {
2816
- sysparm_query: 'state=in_progress^sys_created_by=javascript:gs.getUserName()',
2817
- sysparm_orderby: 'sys_created_on',
2818
- sysparm_limit: 1
2819
- }
2820
- }
2821
- );
2822
-
2823
- if (response.data.result && response.data.result.length > 0) {
2824
- return {
2825
- success: true,
2826
- data: response.data.result[0]
2827
- };
2828
- }
2829
-
2830
- return {
2831
- success: false,
2832
- error: 'No current Update Set found'
2833
- };
2834
- } catch (error) {
2835
- console.error('❌ Failed to get current Update Set:', error);
2836
- return {
2837
- success: false,
2838
- error: error instanceof Error ? error.message : String(error)
2839
- };
2840
- }
2841
- }
2842
-
2843
- /**
2844
- * Get Update Set by ID
2845
- */
2846
- async getUpdateSet(updateSetId: string): Promise<ServiceNowAPIResponse<any>> {
2847
- try {
2848
- this.logger.info(`📋 Getting Update Set ${updateSetId}...`);
2849
-
2850
- // Ensure we have credentials before making the API call
2851
- await this.ensureAuthenticated();
2852
-
2853
- const response = await this.client.get(
2854
- `${this.getBaseUrl()}/api/now/table/sys_update_set/${updateSetId}`
2855
- );
2856
-
2857
- return {
2858
- success: true,
2859
- data: response.data.result
2860
- };
2861
- } catch (error) {
2862
- console.error('❌ Failed to get Update Set:', error);
2863
- return {
2864
- success: false,
2865
- error: error instanceof Error ? error.message : String(error)
2866
- };
2867
- }
2868
- }
2869
-
2870
- /**
2871
- * List Update Sets
2872
- */
2873
- async listUpdateSets(options: any): Promise<ServiceNowAPIResponse<any[]>> {
2874
- try {
2875
- this.logger.info('📋 Listing Update Sets...');
2876
-
2877
- // Ensure we have credentials before making the API call
2878
- await this.ensureAuthenticated();
2879
-
2880
- let query = 'sys_created_by=javascript:gs.getUserName()';
2881
- if (options.state) {
2882
- query += `^state=${options.state}`;
2883
- }
2884
-
2885
- const response = await this.client.get(
2886
- `${this.getBaseUrl()}/api/now/table/sys_update_set`,
2887
- {
2888
- params: {
2889
- sysparm_query: query,
2890
- sysparm_limit: options.limit || 10,
2891
- sysparm_orderby: 'sys_created_on^DESC'
2892
- }
2893
- }
2894
- );
2895
-
2896
- return {
2897
- success: true,
2898
- data: response.data.result
2899
- };
2900
- } catch (error) {
2901
- console.error('❌ Failed to list Update Sets:', error);
2902
- return {
2903
- success: false,
2904
- error: error instanceof Error ? error.message : String(error)
2905
- };
2906
- }
2907
- }
2908
-
2909
- /**
2910
- * Complete an Update Set
2911
- */
2912
- async completeUpdateSet(updateSetId: string, notes?: string): Promise<ServiceNowAPIResponse<any>> {
2913
- try {
2914
- this.logger.info('✅ Completing Update Set...');
2915
-
2916
- // Ensure we have credentials before making the API call
2917
- await this.ensureAuthenticated();
2918
-
2919
- const response = await this.client.patch(
2920
- `${this.getBaseUrl()}/api/now/table/sys_update_set/${updateSetId}`,
2921
- {
2922
- state: 'complete',
2923
- description: notes ? `${notes}\n\nCompleted: ${new Date().toISOString()}` : undefined
2924
- }
2925
- );
2926
-
2927
- this.logger.info('✅ Update Set completed successfully!');
2928
-
2929
- return {
2930
- success: true,
2931
- data: response.data.result
2932
- };
2933
- } catch (error) {
2934
- console.error('❌ Failed to complete Update Set:', error);
2935
- return {
2936
- success: false,
2937
- error: error instanceof Error ? error.message : String(error)
2938
- };
2939
- }
2940
- }
2941
-
2942
- /**
2943
- * Activate an Update Set by setting it as current
2944
- */
2945
- async activateUpdateSet(updateSetId: string): Promise<ServiceNowAPIResponse<any>> {
2946
- try {
2947
- this.logger.info('🔄 Activating Update Set...');
2948
-
2949
- // Set the update set as current
2950
- const result = await this.setCurrentUpdateSet(updateSetId);
2951
-
2952
- if (result.success) {
2953
- // Also ensure the update set is in progress state
2954
- const updateResponse = await this.client.patch(
2955
- `${this.getBaseUrl()}/api/now/table/sys_update_set/${updateSetId}`,
2956
- {
2957
- state: 'in_progress'
2958
- }
2959
- );
2960
-
2961
- this.logger.info('✅ Update Set activated successfully!');
2962
-
2963
- return {
2964
- success: true,
2965
- data: updateResponse.data.result
2966
- };
2967
- }
2968
-
2969
- return result;
2970
- } catch (error) {
2971
- console.error('❌ Failed to activate Update Set:', error);
2972
- return {
2973
- success: false,
2974
- error: error instanceof Error ? error.message : String(error)
2975
- };
2976
- }
2977
- }
2978
-
2979
- /**
2980
- * Preview Update Set changes
2981
- */
2982
- async previewUpdateSet(updateSetId: string): Promise<ServiceNowAPIResponse<any>> {
2983
- try {
2984
- this.logger.info('🔍 Previewing Update Set changes...');
2985
-
2986
- // Get Update Set details
2987
- const updateSetResponse = await this.getUpdateSet(updateSetId);
2988
- if (!updateSetResponse.success) {
2989
- return updateSetResponse;
2990
- }
2991
-
2992
- // Get Update Set changes
2993
- const response = await this.client.get(
2994
- `${this.getBaseUrl()}/api/now/table/sys_update_xml`,
2995
- {
2996
- params: {
2997
- sysparm_query: `update_set=${updateSetId}`,
2998
- sysparm_limit: 1000
2999
- }
3000
- }
3001
- );
3002
-
3003
- return {
3004
- success: true,
3005
- data: {
3006
- ...updateSetResponse.data,
3007
- changes: response.data.result
3008
- }
3009
- };
3010
- } catch (error) {
3011
- console.error('❌ Failed to preview Update Set:', error);
3012
- return {
3013
- success: false,
3014
- error: error instanceof Error ? error.message : String(error)
3015
- };
3016
- }
3017
- }
3018
-
3019
- /**
3020
- * Export Update Set as XML
3021
- */
3022
- /**
3023
- * Ensure we have an active Update Set for tracking changes
3024
- */
3025
- async ensureUpdateSet(): Promise<ServiceNowAPIResponse<any>> {
3026
- try {
3027
- // Check if we have a current Update Set
3028
- const currentUpdateSet = await this.getCurrentUpdateSet();
3029
-
3030
- if (currentUpdateSet.success && currentUpdateSet.data) {
3031
- this.logger.info(`📦 Using existing Update Set: ${currentUpdateSet.data.name}`);
3032
- return currentUpdateSet;
3033
- }
3034
-
3035
- // Create a new Update Set if none exists
3036
- const updateSetName = `Snow-Flow Changes ${new Date().toISOString().split('T')[0]}`;
3037
- this.logger.info(`📦 Creating new Update Set: ${updateSetName}`);
3038
-
3039
- const newUpdateSet = await this.createUpdateSet({
3040
- name: updateSetName,
3041
- description: 'Automated changes from Snow-Flow MCP',
3042
- state: 'in_progress'
3043
- });
3044
-
3045
- if (newUpdateSet.success && newUpdateSet.data) {
3046
- // Set it as current
3047
- await this.setCurrentUpdateSet(newUpdateSet.data.sys_id);
3048
- return newUpdateSet;
3049
- }
3050
-
3051
- // If Update Set creation fails, continue without it
3052
- console.warn('⚠️ Could not create Update Set, changes will not be tracked');
3053
- return { success: false, error: 'Update Set creation failed' };
3054
-
3055
- } catch (error) {
3056
- console.warn('⚠️ Update Set management failed:', error);
3057
- return { success: false, error: error instanceof Error ? error.message : String(error) };
3058
- }
3059
- }
3060
-
3061
- async exportUpdateSet(updateSetId: string): Promise<ServiceNowAPIResponse<any>> {
3062
- try {
3063
- this.logger.info('📤 Exporting Update Set...');
3064
-
3065
- // Get Update Set details first
3066
- const updateSetResponse = await this.getUpdateSet(updateSetId);
3067
- if (!updateSetResponse.success) {
3068
- return updateSetResponse;
3069
- }
3070
-
3071
- // Use the unload processor to export
3072
- const response = await this.client.get(
3073
- `${this.getBaseUrl()}/unload.do`,
3074
- {
3075
- params: {
3076
- sysparm_sys_id: updateSetId,
3077
- sysparm_table: 'sys_update_set',
3078
- sysparm_unload_format: 'xml'
3079
- },
3080
- responseType: 'text'
3081
- }
3082
- );
3083
-
3084
- return {
3085
- success: true,
3086
- data: {
3087
- name: updateSetResponse.data.name,
3088
- xml: response.data,
3089
- change_count: updateSetResponse.data.update_count || 0
3090
- }
3091
- };
3092
- } catch (error) {
3093
- console.error('❌ Failed to export Update Set:', error);
3094
- return {
3095
- success: false,
3096
- error: error instanceof Error ? error.message : String(error)
3097
- };
3098
- }
3099
- }
3100
-
3101
- /**
3102
- * Debug flow structure - check what's missing for Flow Designer
3103
- */
3104
- async debugFlow(flowId: string): Promise<ServiceNowAPIResponse<any>> {
3105
- try {
3106
- this.logger.info('🔍 Debugging flow structure...');
3107
-
3108
- // Get the flow record
3109
- const flowResponse = await this.client.get(
3110
- `${this.getBaseUrl()}/api/now/table/sys_hub_flow/${flowId}`
3111
- );
3112
-
3113
- if (!flowResponse.data.result) {
3114
- return { success: false, error: 'Flow not found' };
3115
- }
3116
-
3117
- const flow = flowResponse.data.result;
3118
-
3119
- // Check for sys_hub_flow_logic entries
3120
- const logicResponse = await this.client.get(
3121
- `${this.getBaseUrl()}/api/now/table/sys_hub_flow_logic?sysparm_query=flow=${flowId}`
3122
- );
3123
-
3124
- // Check for trigger instances
3125
- const triggerResponse = await this.client.get(
3126
- `${this.getBaseUrl()}/api/now/table/sys_hub_trigger_instance?sysparm_query=flow=${flowId}`
3127
- );
3128
-
3129
- // Check for action instances
3130
- const actionResponse = await this.client.get(
3131
- `${this.getBaseUrl()}/api/now/table/sys_hub_action_instance?sysparm_query=flow=${flowId}`
3132
- );
3133
-
3134
- return {
3135
- success: true,
3136
- data: {
3137
- flow: {
3138
- sys_id: flow.sys_id,
3139
- name: flow.name,
3140
- internal_name: flow.internal_name,
3141
- status: flow.status,
3142
- type: flow.type,
3143
- sys_class_name: flow.sys_class_name,
3144
- missing_fields: []
3145
- },
3146
- logic_count: logicResponse.data.result.length,
3147
- trigger_count: triggerResponse.data.result.length,
3148
- action_count: actionResponse.data.result.length,
3149
- logic_entries: logicResponse.data.result,
3150
- triggers: triggerResponse.data.result,
3151
- actions: actionResponse.data.result
3152
- }
3153
- };
3154
- } catch (error) {
3155
- return {
3156
- success: false,
3157
- error: error instanceof Error ? error.message : String(error)
3158
- };
3159
- }
3160
- }
3161
-
3162
- /**
3163
- * Create a flow logic entry (visual representation in Flow Designer)
3164
- */
3165
- private async createFlowLogic(flowId: string, logic: any): Promise<any> {
3166
- try {
3167
- const logicData = {
3168
- flow: flowId,
3169
- name: logic.name,
3170
- type: logic.type,
3171
- order: logic.order,
3172
- active: true,
3173
- instance: logic.instance
3174
- };
3175
-
3176
- const response = await this.client.post(
3177
- `${this.getBaseUrl()}/api/now/table/sys_hub_flow_logic`,
3178
- logicData
3179
- );
3180
-
3181
- return response.data.result;
3182
- } catch (error) {
3183
- console.error('Failed to create flow logic:', error);
3184
- throw error;
3185
- }
3186
- }
3187
-
3188
- /**
3189
- * Create a connection between flow logic elements
3190
- */
3191
- private async createFlowConnection(flowId: string, fromId: string, toId: string): Promise<any> {
3192
- try {
3193
- // In ServiceNow, connections are stored as part of the flow logic
3194
- // We need to update the 'from' element to point to the 'to' element
3195
- const connectionData = {
3196
- next: toId
3197
- };
3198
-
3199
- const response = await this.client.patch(
3200
- `${this.getBaseUrl()}/api/now/table/sys_hub_flow_logic/${fromId}`,
3201
- connectionData
3202
- );
3203
-
3204
- return response.data.result;
3205
- } catch (error) {
3206
- console.error('Failed to create flow connection:', error);
3207
- // Don't throw - connections might work differently in some versions
3208
- return null;
3209
- }
3210
- }
3211
-
3212
- /**
3213
- * Generic GET method for ServiceNow API calls
3214
- */
3215
- async get(endpoint: string, params?: any): Promise<any> {
3216
- try {
3217
- await this.ensureAuthenticated();
3218
- const url = endpoint.startsWith('http') ? endpoint : `${this.getBaseUrl()}${endpoint}`;
3219
- const response = await this.client.get(url, { params });
3220
- return response.data;
3221
- } catch (error) {
3222
- throw error;
3223
- }
3224
- }
3225
-
3226
- /**
3227
- * Generic POST method for ServiceNow API calls
3228
- */
3229
- async post(endpoint: string, data?: any): Promise<any> {
3230
- try {
3231
- await this.ensureAuthenticated();
3232
- const url = endpoint.startsWith('http') ? endpoint : `${this.getBaseUrl()}${endpoint}`;
3233
- const response = await this.client.post(url, data);
3234
- return response.data;
3235
- } catch (error) {
3236
- throw error;
3237
- }
3238
- }
3239
-
3240
- /**
3241
- * Generic PUT method for ServiceNow API calls
3242
- */
3243
- async put(endpoint: string, data?: any): Promise<any> {
3244
- try {
3245
- await this.ensureAuthenticated();
3246
- const url = endpoint.startsWith('http') ? endpoint : `${this.getBaseUrl()}${endpoint}`;
3247
- const response = await this.client.put(url, data);
3248
- return response.data;
3249
- } catch (error) {
3250
- throw error;
3251
- }
3252
- }
3253
-
3254
- /**
3255
- * Generic PATCH method for ServiceNow API calls
3256
- */
3257
- async patch(endpoint: string, data?: any): Promise<any> {
3258
- try {
3259
- await this.ensureAuthenticated();
3260
- const url = endpoint.startsWith('http') ? endpoint : `${this.getBaseUrl()}${endpoint}`;
3261
- const response = await this.client.patch(url, data);
3262
- return response.data;
3263
- } catch (error) {
3264
- throw error;
3265
- }
3266
- }
3267
-
3268
- /**
3269
- * Generic DELETE method for ServiceNow API calls
3270
- */
3271
- async delete(endpoint: string): Promise<any> {
3272
- try {
3273
- await this.ensureAuthenticated();
3274
- const url = endpoint.startsWith('http') ? endpoint : `${this.getBaseUrl()}${endpoint}`;
3275
- const response = await this.client.delete(url);
3276
- return response.data;
3277
- } catch (error) {
3278
- throw error;
3279
- }
3280
- }
3281
-
3282
- /**
3283
- * TEMPORARY FIX: makeRequest method to handle phantom calls
3284
- * This method provides compatibility for code that expects makeRequest
3285
- */
3286
- async makeRequest(config: any): Promise<any> {
3287
- this.logger.info('🔧 MAKEQUEST CALLED! Stack trace:', new Error().stack);
3288
- this.logger.info('🔧 TEMP FIX: makeRequest called with config:', config);
3289
- this.logger.info('🔧 This instance constructor:', this.constructor.name);
3290
- this.logger.info('🔧 This instance methods:', Object.getOwnPropertyNames(Object.getPrototypeOf(this)));
3291
-
3292
- try {
3293
- await this.ensureAuthenticated();
3294
-
3295
- // Route the request to the appropriate HTTP method
3296
- const method = (config.method || 'GET').toLowerCase();
3297
- const url = config.url || config.endpoint;
3298
- const data = config.data || config.body;
3299
-
3300
- this.logger.info(`🔧 Routing ${method.toUpperCase()} request to: ${url}`);
3301
-
3302
- switch (method) {
3303
- case 'get':
3304
- return await this.get(url, config.params);
3305
- case 'post':
3306
- return await this.post(url, data);
3307
- case 'put':
3308
- return await this.put(url, data);
3309
- case 'patch':
3310
- return await this.patch(url, data);
3311
- case 'delete':
3312
- return await this.delete(url);
3313
- default:
3314
- throw new Error(`Unsupported HTTP method: ${method}`);
3315
- }
3316
- } catch (error) {
3317
- console.error('🔧 makeRequest error:', error);
3318
- throw error;
3319
- }
3320
- }
3321
- }