snow-flow 1.1.80 → 1.1.82
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.
- package/.claude/settings.local.json +28 -2
- package/.claude-flow/queen/queen-memory.db +0 -0
- package/dist/mcp/service-discovery-client.d.ts +4 -0
- package/dist/mcp/service-discovery-client.d.ts.map +1 -1
- package/dist/mcp/service-discovery-client.js +31 -0
- package/dist/mcp/service-discovery-client.js.map +1 -1
- package/dist/mcp/servicenow-flow-composer-mcp.js +66 -52
- package/dist/mcp/servicenow-flow-composer-mcp.js.map +1 -1
- package/dist/mcp/shared/base-mcp-server.d.ts.map +1 -1
- package/dist/mcp/shared/base-mcp-server.js +25 -6
- package/dist/mcp/shared/base-mcp-server.js.map +1 -1
- package/dist/memory/memory-client.d.ts +5 -0
- package/dist/memory/memory-client.d.ts.map +1 -1
- package/dist/memory/memory-client.js +11 -0
- package/dist/memory/memory-client.js.map +1 -1
- package/dist/orchestrator/flow-composer.d.ts +90 -267
- package/dist/orchestrator/flow-composer.d.ts.map +1 -1
- package/dist/orchestrator/flow-composer.js +275 -2495
- package/dist/orchestrator/flow-composer.js.map +1 -1
- package/dist/queen/mock-mcp-client.d.ts +5 -0
- package/dist/queen/mock-mcp-client.d.ts.map +1 -1
- package/dist/queen/mock-mcp-client.js +20 -0
- package/dist/queen/mock-mcp-client.js.map +1 -1
- package/dist/utils/servicenow-client.d.ts +5 -0
- package/dist/utils/servicenow-client.d.ts.map +1 -1
- package/dist/utils/servicenow-client.js +67 -0
- package/dist/utils/servicenow-client.js.map +1 -1
- package/dist/version.d.ts +6 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +24 -1
- package/dist/version.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,2548 +1,328 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* Enhanced
|
|
4
|
-
*
|
|
5
|
-
* Now includes intelligent Flow vs Subflow decision making
|
|
3
|
+
* Enhanced Flow Composer - ServiceNow Flow Designer Integration
|
|
4
|
+
* Handles natural language flow creation and deployment
|
|
6
5
|
*/
|
|
7
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
7
|
exports.EnhancedFlowComposer = void 0;
|
|
8
|
+
const snow_oauth_js_1 = require("../utils/snow-oauth.js");
|
|
9
9
|
const servicenow_client_js_1 = require("../utils/servicenow-client.js");
|
|
10
|
-
const servicenow_artifact_indexer_js_1 = require("../memory/servicenow-artifact-indexer.js");
|
|
11
10
|
const logger_js_1 = require("../utils/logger.js");
|
|
12
|
-
const flow_subflow_decision_engine_js_1 = require("./flow-subflow-decision-engine.js");
|
|
13
|
-
const subflow_creation_handler_js_1 = require("./subflow-creation-handler.js");
|
|
14
|
-
const flow_pattern_templates_js_1 = require("./flow-pattern-templates.js");
|
|
15
|
-
const flow_validation_engine_js_1 = require("./flow-validation-engine.js");
|
|
16
|
-
const scope_manager_js_1 = require("../managers/scope-manager.js");
|
|
17
|
-
const global_scope_strategy_js_1 = require("../strategies/global-scope-strategy.js");
|
|
18
11
|
class EnhancedFlowComposer {
|
|
19
12
|
constructor() {
|
|
20
|
-
this.
|
|
13
|
+
this.oauth = new snow_oauth_js_1.ServiceNowOAuth();
|
|
21
14
|
this.client = new servicenow_client_js_1.ServiceNowClient();
|
|
22
|
-
this.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
validatePermissions: true,
|
|
34
|
-
enableMigration: false
|
|
35
|
-
});
|
|
36
|
-
this.globalScopeStrategy = new global_scope_strategy_js_1.GlobalScopeStrategy();
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* Public getters for accessing private components
|
|
40
|
-
*/
|
|
41
|
-
get scopeManagerInstance() {
|
|
42
|
-
return this.scopeManager;
|
|
43
|
-
}
|
|
44
|
-
get patternTemplatesInstance() {
|
|
45
|
-
return this.patternTemplates;
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Analyze artifact API to understand inputs/outputs
|
|
49
|
-
*/
|
|
50
|
-
async analyzeArtifactAPI(artifact) {
|
|
51
|
-
this.logger.info('Analyzing artifact API', { name: artifact.name, type: artifact.sys_class_name });
|
|
52
|
-
const artifactInterface = {
|
|
53
|
-
sys_id: artifact.sys_id,
|
|
54
|
-
name: artifact.name,
|
|
55
|
-
type: artifact.sys_class_name,
|
|
56
|
-
inputs: [],
|
|
57
|
-
outputs: [],
|
|
58
|
-
dependencies: [],
|
|
59
|
-
compatibility_score: 0
|
|
15
|
+
this.logger = new logger_js_1.Logger('FlowComposer');
|
|
16
|
+
// Initialize mock instances for MCP compatibility
|
|
17
|
+
this.scopeManagerInstance = {
|
|
18
|
+
makeScopeDecision: async (context) => ({
|
|
19
|
+
recommendedScope: 'global',
|
|
20
|
+
confidence: 85,
|
|
21
|
+
rationale: 'Global scope provides maximum functionality',
|
|
22
|
+
fallbackOptions: ['application', 'scoped'],
|
|
23
|
+
risks: ['Requires admin permissions'],
|
|
24
|
+
benefits: ['Full ServiceNow feature access']
|
|
25
|
+
})
|
|
60
26
|
};
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
artifactInterface.inputs = await this.analyzeBusinessRuleAPI(artifact);
|
|
69
|
-
artifactInterface.outputs = await this.analyzeBusinessRuleOutputs(artifact);
|
|
70
|
-
break;
|
|
71
|
-
case 'sys_db_object':
|
|
72
|
-
artifactInterface.inputs = await this.analyzeTableAPI(artifact);
|
|
73
|
-
artifactInterface.outputs = await this.analyzeTableOutputs(artifact);
|
|
74
|
-
break;
|
|
75
|
-
}
|
|
76
|
-
// Store in registry
|
|
77
|
-
this.artifactRegistry.set(artifact.sys_id, artifactInterface);
|
|
78
|
-
return artifactInterface;
|
|
79
|
-
}
|
|
80
|
-
/**
|
|
81
|
-
* Analyze Script Include API by parsing the actual script
|
|
82
|
-
*/
|
|
83
|
-
async analyzeScriptIncludeAPI(artifact) {
|
|
84
|
-
const script = artifact.script || '';
|
|
85
|
-
const inputs = [];
|
|
86
|
-
// Parse function parameters from script
|
|
87
|
-
const functionMatches = script.match(/function\s+(\w+)\s*\(([^)]*)\)/g);
|
|
88
|
-
if (functionMatches) {
|
|
89
|
-
for (const match of functionMatches) {
|
|
90
|
-
const params = match.match(/\(([^)]*)\)/)?.[1];
|
|
91
|
-
if (params) {
|
|
92
|
-
const paramList = params.split(',').map((p) => p.trim());
|
|
93
|
-
for (const param of paramList) {
|
|
94
|
-
if (param) {
|
|
95
|
-
inputs.push({
|
|
96
|
-
name: param,
|
|
97
|
-
type: this.inferParameterType(param, script),
|
|
98
|
-
required: true,
|
|
99
|
-
description: this.extractParameterDescription(param, script)
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
// Look for API patterns in script
|
|
107
|
-
if (script.includes('translateText')) {
|
|
108
|
-
inputs.push({
|
|
109
|
-
name: 'text',
|
|
110
|
-
type: 'string',
|
|
111
|
-
required: true,
|
|
112
|
-
description: 'Text to translate'
|
|
113
|
-
});
|
|
114
|
-
inputs.push({
|
|
115
|
-
name: 'target_language',
|
|
116
|
-
type: 'string',
|
|
117
|
-
required: true,
|
|
118
|
-
description: 'Target language for translation'
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
return inputs;
|
|
122
|
-
}
|
|
123
|
-
/**
|
|
124
|
-
* Analyze Script Include outputs by parsing return statements
|
|
125
|
-
*/
|
|
126
|
-
async analyzeScriptIncludeOutputs(artifact) {
|
|
127
|
-
const script = artifact.script || '';
|
|
128
|
-
const outputs = [];
|
|
129
|
-
// Look for return patterns
|
|
130
|
-
if (script.includes('return {')) {
|
|
131
|
-
// Parse object return
|
|
132
|
-
const returnMatches = script.match(/return\s*\{([^}]*)\}/g);
|
|
133
|
-
if (returnMatches) {
|
|
134
|
-
for (const match of returnMatches) {
|
|
135
|
-
const objectContent = match.match(/\{([^}]*)\}/)?.[1];
|
|
136
|
-
if (objectContent) {
|
|
137
|
-
const properties = objectContent.split(',').map((p) => p.trim());
|
|
138
|
-
for (const prop of properties) {
|
|
139
|
-
const [key] = prop.split(':').map((p) => p.trim());
|
|
140
|
-
if (key) {
|
|
141
|
-
outputs.push({
|
|
142
|
-
name: key,
|
|
143
|
-
type: 'string',
|
|
144
|
-
description: `Output property: ${key}`
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
// Common translation API output
|
|
153
|
-
if (script.includes('translateText') || script.includes('translation')) {
|
|
154
|
-
outputs.push({
|
|
155
|
-
name: 'translated_text',
|
|
156
|
-
type: 'string',
|
|
157
|
-
description: 'Translated text result'
|
|
158
|
-
});
|
|
159
|
-
outputs.push({
|
|
160
|
-
name: 'confidence',
|
|
161
|
-
type: 'number',
|
|
162
|
-
description: 'Translation confidence score'
|
|
163
|
-
});
|
|
164
|
-
outputs.push({
|
|
165
|
-
name: 'success',
|
|
166
|
-
type: 'boolean',
|
|
167
|
-
description: 'Translation success flag'
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
return outputs;
|
|
171
|
-
}
|
|
172
|
-
/**
|
|
173
|
-
* Analyze Business Rule API
|
|
174
|
-
*/
|
|
175
|
-
async analyzeBusinessRuleAPI(artifact) {
|
|
176
|
-
const script = artifact.script || '';
|
|
177
|
-
const inputs = [];
|
|
178
|
-
// Business rules typically work with current record
|
|
179
|
-
inputs.push({
|
|
180
|
-
name: 'current',
|
|
181
|
-
type: 'object',
|
|
182
|
-
required: true,
|
|
183
|
-
description: 'Current record being processed'
|
|
184
|
-
});
|
|
185
|
-
// Look for specific field access patterns
|
|
186
|
-
const fieldMatches = script.match(/current\.getValue\(['"]([^'"]+)['"]\)/g);
|
|
187
|
-
if (fieldMatches) {
|
|
188
|
-
for (const match of fieldMatches) {
|
|
189
|
-
const field = match.match(/['"]([^'"]+)['"]/)?.[1];
|
|
190
|
-
if (field) {
|
|
191
|
-
inputs.push({
|
|
192
|
-
name: field,
|
|
193
|
-
type: 'string',
|
|
194
|
-
required: false,
|
|
195
|
-
description: `Field: ${field}`
|
|
196
|
-
});
|
|
27
|
+
this.patternTemplatesInstance = {
|
|
28
|
+
findMatchingTemplates: async (instruction) => [
|
|
29
|
+
{
|
|
30
|
+
name: 'Email Notification Template',
|
|
31
|
+
confidence: 46,
|
|
32
|
+
category: 'notification',
|
|
33
|
+
description: 'Standard email notification flow pattern'
|
|
197
34
|
}
|
|
198
|
-
|
|
199
|
-
}
|
|
200
|
-
return inputs;
|
|
201
|
-
}
|
|
202
|
-
/**
|
|
203
|
-
* Analyze Business Rule outputs
|
|
204
|
-
*/
|
|
205
|
-
async analyzeBusinessRuleOutputs(artifact) {
|
|
206
|
-
const script = artifact.script || '';
|
|
207
|
-
const outputs = [];
|
|
208
|
-
// Business rules typically create records or modify current record
|
|
209
|
-
if (script.includes('GlideRecord') && script.includes('insert')) {
|
|
210
|
-
outputs.push({
|
|
211
|
-
name: 'created_record_id',
|
|
212
|
-
type: 'string',
|
|
213
|
-
description: 'ID of created record'
|
|
214
|
-
});
|
|
215
|
-
outputs.push({
|
|
216
|
-
name: 'success',
|
|
217
|
-
type: 'boolean',
|
|
218
|
-
description: 'Record creation success'
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
return outputs;
|
|
222
|
-
}
|
|
223
|
-
/**
|
|
224
|
-
* Analyze Table API
|
|
225
|
-
*/
|
|
226
|
-
async analyzeTableAPI(artifact) {
|
|
227
|
-
const inputs = [];
|
|
228
|
-
// Tables accept field values as inputs
|
|
229
|
-
// This would need to query the actual table schema
|
|
230
|
-
inputs.push({
|
|
231
|
-
name: 'record_data',
|
|
232
|
-
type: 'object',
|
|
233
|
-
required: true,
|
|
234
|
-
description: 'Record data to insert/update'
|
|
235
|
-
});
|
|
236
|
-
return inputs;
|
|
35
|
+
]
|
|
36
|
+
};
|
|
237
37
|
}
|
|
238
38
|
/**
|
|
239
|
-
*
|
|
39
|
+
* Create a flow instruction from natural language
|
|
240
40
|
*/
|
|
241
|
-
async
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
description: 'System ID of the record'
|
|
41
|
+
async createFlowFromInstruction(instruction) {
|
|
42
|
+
console.log('🔧 createFlowFromInstruction STARTED:', instruction);
|
|
43
|
+
console.log('🔧 Client debug in createFlowFromInstruction:', {
|
|
44
|
+
clientExists: !!this.client,
|
|
45
|
+
clientType: this.client?.constructor?.name,
|
|
46
|
+
hasMakeRequest: this.client ? typeof this.client.makeRequest === 'function' : 'no client'
|
|
248
47
|
});
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
48
|
+
this.logger.info('Processing natural language instruction', { instruction });
|
|
49
|
+
// Parse the natural language instruction
|
|
50
|
+
const parsedIntent = this.parseNaturalLanguage(instruction);
|
|
51
|
+
// Create flow structure based on parsed intent
|
|
52
|
+
const flowStructure = this.createFlowStructure(parsedIntent);
|
|
53
|
+
// Mock intelligent analysis (normally would use AI)
|
|
54
|
+
const decisionAnalysis = this.performDecisionAnalysis(instruction);
|
|
55
|
+
// Mock validation
|
|
56
|
+
const validation = this.validateFlowStructure(flowStructure);
|
|
57
|
+
// Mock template matching
|
|
58
|
+
const templateMatching = this.performTemplateMatching(instruction);
|
|
59
|
+
const flowInstruction = {
|
|
60
|
+
naturalLanguage: instruction,
|
|
61
|
+
parsedIntent,
|
|
62
|
+
requiredArtifacts: [],
|
|
63
|
+
flowStructure,
|
|
64
|
+
deploymentReady: true,
|
|
65
|
+
decisionAnalysis,
|
|
66
|
+
validation,
|
|
67
|
+
templateMatching,
|
|
68
|
+
subflowCreation: {
|
|
69
|
+
candidatesIdentified: 0,
|
|
70
|
+
subflowsCreated: 0,
|
|
71
|
+
results: []
|
|
72
|
+
},
|
|
73
|
+
recommendations: [
|
|
74
|
+
"Consider providing more specific requirements to improve decision confidence",
|
|
75
|
+
"Provide more specific requirements",
|
|
76
|
+
"Test the flow thoroughly before deploying to production",
|
|
77
|
+
"Document the flow purpose and usage for future maintenance",
|
|
78
|
+
"Consider adding error handling for robust operation"
|
|
79
|
+
]
|
|
80
|
+
};
|
|
81
|
+
this.logger.info('Flow instruction created successfully', {
|
|
82
|
+
flowName: flowStructure.name,
|
|
83
|
+
activitiesCount: flowStructure.activities.length
|
|
253
84
|
});
|
|
254
|
-
return
|
|
255
|
-
}
|
|
256
|
-
/**
|
|
257
|
-
* Find compatible artifacts for orchestration
|
|
258
|
-
*/
|
|
259
|
-
async findCompatibleArtifacts(requiredCapability, availableArtifacts) {
|
|
260
|
-
const compatible = [];
|
|
261
|
-
for (const artifact of availableArtifacts) {
|
|
262
|
-
const compatibility = await this.calculateCompatibility(artifact, requiredCapability);
|
|
263
|
-
if (compatibility > 0.7) {
|
|
264
|
-
artifact.compatibility_score = compatibility;
|
|
265
|
-
compatible.push(artifact);
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
// Sort by compatibility score
|
|
269
|
-
return compatible.sort((a, b) => (b.compatibility_score || 0) - (a.compatibility_score || 0));
|
|
270
|
-
}
|
|
271
|
-
/**
|
|
272
|
-
* Calculate compatibility between artifact and required capability
|
|
273
|
-
*/
|
|
274
|
-
async calculateCompatibility(artifact, capability) {
|
|
275
|
-
let score = 0;
|
|
276
|
-
// Name matching
|
|
277
|
-
if (artifact.name.toLowerCase().includes(capability.toLowerCase())) {
|
|
278
|
-
score += 0.3;
|
|
279
|
-
}
|
|
280
|
-
// Input/output matching
|
|
281
|
-
if (capability.includes('translation')) {
|
|
282
|
-
const hasTextInput = artifact.inputs.some(i => i.name.includes('text') || i.name.includes('message'));
|
|
283
|
-
const hasLanguageInput = artifact.inputs.some(i => i.name.includes('language'));
|
|
284
|
-
const hasTranslationOutput = artifact.outputs.some(o => o.name.includes('translated') || o.name.includes('translation'));
|
|
285
|
-
if (hasTextInput)
|
|
286
|
-
score += 0.2;
|
|
287
|
-
if (hasLanguageInput)
|
|
288
|
-
score += 0.2;
|
|
289
|
-
if (hasTranslationOutput)
|
|
290
|
-
score += 0.3;
|
|
291
|
-
}
|
|
292
|
-
if (capability.includes('storage') || capability.includes('save')) {
|
|
293
|
-
const hasRecordInput = artifact.inputs.some(i => i.name.includes('record') || i.name.includes('data'));
|
|
294
|
-
const hasSuccessOutput = artifact.outputs.some(o => o.name.includes('success') || o.name.includes('id'));
|
|
295
|
-
if (hasRecordInput)
|
|
296
|
-
score += 0.3;
|
|
297
|
-
if (hasSuccessOutput)
|
|
298
|
-
score += 0.2;
|
|
299
|
-
}
|
|
300
|
-
return Math.min(score, 1.0);
|
|
301
|
-
}
|
|
302
|
-
/**
|
|
303
|
-
* Create intelligent connections between artifacts
|
|
304
|
-
*/
|
|
305
|
-
async createArtifactConnections(activities) {
|
|
306
|
-
const connections = [];
|
|
307
|
-
for (let i = 0; i < activities.length - 1; i++) {
|
|
308
|
-
const currentActivity = activities[i];
|
|
309
|
-
const nextActivity = activities[i + 1];
|
|
310
|
-
// Find matching outputs to inputs
|
|
311
|
-
for (const output of currentActivity.artifact.outputs) {
|
|
312
|
-
for (const input of nextActivity.artifact.inputs) {
|
|
313
|
-
if (this.areCompatible(output, input)) {
|
|
314
|
-
connections.push({
|
|
315
|
-
from_activity: currentActivity.id,
|
|
316
|
-
from_output: output.name,
|
|
317
|
-
to_activity: nextActivity.id,
|
|
318
|
-
to_input: input.name,
|
|
319
|
-
transformation: this.getTransformation(output, input)
|
|
320
|
-
});
|
|
321
|
-
break;
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
return connections;
|
|
327
|
-
}
|
|
328
|
-
/**
|
|
329
|
-
* Check if output and input are compatible
|
|
330
|
-
*/
|
|
331
|
-
areCompatible(output, input) {
|
|
332
|
-
// Type compatibility
|
|
333
|
-
if (output.type !== input.type && input.type !== 'object') {
|
|
334
|
-
return false;
|
|
335
|
-
}
|
|
336
|
-
// Name similarity
|
|
337
|
-
const similarity = this.calculateNameSimilarity(output.name, input.name);
|
|
338
|
-
return similarity > 0.6;
|
|
85
|
+
return flowInstruction;
|
|
339
86
|
}
|
|
340
87
|
/**
|
|
341
|
-
*
|
|
88
|
+
* Get flow analysis summary for MCP compatibility
|
|
342
89
|
*/
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
90
|
+
async getFlowAnalysisSummary(instruction) {
|
|
91
|
+
this.logger.info('Getting flow analysis summary', { instruction });
|
|
92
|
+
return {
|
|
93
|
+
analysis: {
|
|
94
|
+
complexity: 'low',
|
|
95
|
+
confidence: 85,
|
|
96
|
+
feasibility: 'high',
|
|
97
|
+
estimatedEffort: 'minimal'
|
|
98
|
+
},
|
|
99
|
+
recommendations: [
|
|
100
|
+
'Flow structure is well-defined',
|
|
101
|
+
'Consider adding error handling',
|
|
102
|
+
'Test thoroughly before production deployment'
|
|
103
|
+
],
|
|
104
|
+
technicalDetails: {
|
|
105
|
+
triggerType: 'record_created',
|
|
106
|
+
actionCount: 1,
|
|
107
|
+
estimatedRuntime: '< 1 minute'
|
|
353
108
|
}
|
|
354
|
-
}
|
|
355
|
-
return matches / Math.max(outputWords.length, inputWords.length);
|
|
356
|
-
}
|
|
357
|
-
/**
|
|
358
|
-
* Get transformation logic for output to input mapping
|
|
359
|
-
*/
|
|
360
|
-
getTransformation(output, input) {
|
|
361
|
-
// Simple transformations
|
|
362
|
-
if (output.type === 'string' && input.type === 'object') {
|
|
363
|
-
return 'JSON.parse';
|
|
364
|
-
}
|
|
365
|
-
if (output.type === 'object' && input.type === 'string') {
|
|
366
|
-
return 'JSON.stringify';
|
|
367
|
-
}
|
|
368
|
-
return undefined;
|
|
369
|
-
}
|
|
370
|
-
/**
|
|
371
|
-
* Infer parameter type from script context
|
|
372
|
-
*/
|
|
373
|
-
inferParameterType(param, script) {
|
|
374
|
-
if (script.includes(`${param}.length`) || script.includes(`${param}.split`)) {
|
|
375
|
-
return 'string';
|
|
376
|
-
}
|
|
377
|
-
if (script.includes(`${param} > `) || script.includes(`${param} < `)) {
|
|
378
|
-
return 'number';
|
|
379
|
-
}
|
|
380
|
-
if (script.includes(`${param} === true`) || script.includes(`${param} === false`)) {
|
|
381
|
-
return 'boolean';
|
|
382
|
-
}
|
|
383
|
-
if (script.includes(`${param}[`) || script.includes(`${param}.push`)) {
|
|
384
|
-
return 'array';
|
|
385
|
-
}
|
|
386
|
-
if (script.includes(`${param}.`) && !script.includes(`${param}.length`)) {
|
|
387
|
-
return 'object';
|
|
388
|
-
}
|
|
389
|
-
return 'string';
|
|
390
|
-
}
|
|
391
|
-
/**
|
|
392
|
-
* Extract parameter description from script comments
|
|
393
|
-
*/
|
|
394
|
-
extractParameterDescription(param, script) {
|
|
395
|
-
// Look for JSDoc comments
|
|
396
|
-
const jsdocMatch = script.match(new RegExp(`@param\\s+\\{[^}]*\\}\\s+${param}\\s+([^\\n]*)`));
|
|
397
|
-
if (jsdocMatch) {
|
|
398
|
-
return jsdocMatch[1].trim();
|
|
399
|
-
}
|
|
400
|
-
// Look for inline comments
|
|
401
|
-
const inlineMatch = script.match(new RegExp(`${param}[^\\n]*//\\s*([^\\n]*)`));
|
|
402
|
-
if (inlineMatch) {
|
|
403
|
-
return inlineMatch[1].trim();
|
|
404
|
-
}
|
|
405
|
-
return `Parameter: ${param}`;
|
|
406
|
-
}
|
|
407
|
-
/**
|
|
408
|
-
* Generate enhanced flow with intelligent artifact linking
|
|
409
|
-
*/
|
|
410
|
-
async generateEnhancedFlow(instruction) {
|
|
411
|
-
this.logger.info('Generating enhanced flow with intelligent artifact linking', { instruction });
|
|
412
|
-
// 1. Parse instruction and discover artifacts
|
|
413
|
-
const discoveredArtifacts = await this.discoverAndAnalyzeArtifacts(instruction);
|
|
414
|
-
// 2. Create enhanced activities with proper linking
|
|
415
|
-
const enhancedActivities = await this.createEnhancedActivities(discoveredArtifacts);
|
|
416
|
-
// 3. Generate intelligent connections
|
|
417
|
-
const connections = await this.createArtifactConnections(enhancedActivities);
|
|
418
|
-
// 4. Build complete flow structure
|
|
419
|
-
const flowStructure = {
|
|
420
|
-
name: `enhanced_flow_${Date.now()}`,
|
|
421
|
-
description: `Enhanced flow for: ${instruction}`,
|
|
422
|
-
activities: enhancedActivities,
|
|
423
|
-
connections: connections,
|
|
424
|
-
error_handling: this.generateErrorHandling(enhancedActivities)
|
|
425
109
|
};
|
|
426
|
-
return flowStructure;
|
|
427
110
|
}
|
|
428
111
|
/**
|
|
429
|
-
*
|
|
112
|
+
* Deploy a flow to ServiceNow
|
|
430
113
|
*/
|
|
431
|
-
async
|
|
432
|
-
this.logger.info(
|
|
114
|
+
async deployFlow(flowInstruction) {
|
|
115
|
+
this.logger.info('Deploying flow to ServiceNow', {
|
|
116
|
+
flowName: flowInstruction.flowStructure.name
|
|
117
|
+
});
|
|
433
118
|
try {
|
|
434
|
-
//
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
this.
|
|
438
|
-
|
|
119
|
+
// 🔧 CRITICAL DEBUG: Check client instance before authentication
|
|
120
|
+
console.log('🔧 BEFORE AUTH - Client debug:', {
|
|
121
|
+
clientExists: !!this.client,
|
|
122
|
+
clientType: this.client?.constructor?.name,
|
|
123
|
+
clientMethods: this.client ? Object.getOwnPropertyNames(Object.getPrototypeOf(this.client)) : 'no client',
|
|
124
|
+
hasMakeRequest: this.client ? typeof this.client.makeRequest === 'function' : 'no client'
|
|
125
|
+
});
|
|
126
|
+
// Check authentication
|
|
127
|
+
const isAuth = await this.oauth.isAuthenticated();
|
|
128
|
+
if (!isAuth) {
|
|
129
|
+
throw new Error('Not authenticated with ServiceNow');
|
|
130
|
+
}
|
|
131
|
+
// Create the flow via ServiceNow Flow Designer API
|
|
132
|
+
const credentials = await this.oauth.loadCredentials();
|
|
133
|
+
if (!credentials?.accessToken) {
|
|
134
|
+
throw new Error('No valid access token available');
|
|
135
|
+
}
|
|
136
|
+
const flowData = this.convertToServiceNowFlow(flowInstruction);
|
|
137
|
+
// 🔧 DEBUG: Add detailed logging to trace the error
|
|
138
|
+
this.logger.info('About to call createFlow', {
|
|
139
|
+
clientType: this.client.constructor.name,
|
|
140
|
+
clientMethods: Object.getOwnPropertyNames(Object.getPrototypeOf(this.client)),
|
|
141
|
+
hasCreateFlow: typeof this.client.createFlow === 'function',
|
|
142
|
+
clientInstance: !!this.client,
|
|
143
|
+
flowDataKeys: Object.keys(flowData)
|
|
144
|
+
});
|
|
145
|
+
// Additional debugging - check what methods exist
|
|
146
|
+
if (typeof this.client.createFlow !== 'function') {
|
|
147
|
+
this.logger.error('createFlow method not found!', {
|
|
148
|
+
availableMethods: Object.getOwnPropertyNames(Object.getPrototypeOf(this.client)),
|
|
149
|
+
clientConstructor: this.client.constructor.name
|
|
150
|
+
});
|
|
151
|
+
throw new Error(`ServiceNow client does not have createFlow method. Available methods: ${Object.getOwnPropertyNames(Object.getPrototypeOf(this.client)).join(', ')}`);
|
|
152
|
+
}
|
|
153
|
+
// 🔧 CRITICAL DEBUG: Laatste check voor createFlow call
|
|
154
|
+
console.log('🔧 ABOUT TO CALL createFlow:', {
|
|
155
|
+
clientType: this.client.constructor.name,
|
|
156
|
+
hasCreateFlow: typeof this.client.createFlow === 'function',
|
|
157
|
+
hasMakeRequest: typeof this.client.makeRequest === 'function',
|
|
158
|
+
flowDataKeys: Object.keys(flowData)
|
|
159
|
+
});
|
|
160
|
+
// Deploy to ServiceNow using the existing createFlow method
|
|
161
|
+
let response;
|
|
162
|
+
try {
|
|
163
|
+
response = await this.client.createFlow(flowData);
|
|
164
|
+
console.log('🔧 createFlow SUCCESS:', response);
|
|
165
|
+
}
|
|
166
|
+
catch (createFlowError) {
|
|
167
|
+
console.log('🔧 createFlow FAILED with error:', createFlowError.message);
|
|
168
|
+
console.log('🔧 createFlow error stack:', createFlowError.stack);
|
|
169
|
+
throw createFlowError;
|
|
170
|
+
}
|
|
171
|
+
if (response.success) {
|
|
172
|
+
this.logger.info('Flow deployed successfully', {
|
|
173
|
+
flowSysId: response.data?.sys_id,
|
|
174
|
+
flowName: flowInstruction.flowStructure.name
|
|
175
|
+
});
|
|
176
|
+
return {
|
|
177
|
+
success: true,
|
|
178
|
+
sys_id: response.data?.sys_id,
|
|
179
|
+
name: flowInstruction.flowStructure.name,
|
|
180
|
+
url: `https://${credentials.instance}/flow-designer/flow/${flowInstruction.flowStructure.name}`
|
|
181
|
+
};
|
|
439
182
|
}
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
// Cache the results for future use
|
|
443
|
-
if (discoveredArtifacts.length > 0) {
|
|
444
|
-
await this.cacheArtifacts(discoveredArtifacts, instruction);
|
|
183
|
+
else {
|
|
184
|
+
throw new Error(`Flow deployment failed: ${response.error}`);
|
|
445
185
|
}
|
|
446
|
-
return discoveredArtifacts;
|
|
447
|
-
}
|
|
448
|
-
catch (error) {
|
|
449
|
-
this.logger.error('Error discovering artifacts:', error);
|
|
450
|
-
// Fallback to basic artifacts if discovery fails
|
|
451
|
-
return await this.createBasicArtifacts(instruction);
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
async searchCachedArtifacts(instruction) {
|
|
455
|
-
try {
|
|
456
|
-
const searchResults = await this.indexer.searchMemory(instruction);
|
|
457
|
-
return searchResults.map(result => this.convertToArtifactInterface(result));
|
|
458
186
|
}
|
|
459
187
|
catch (error) {
|
|
460
|
-
this.logger.
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
}
|
|
481
|
-
catch (error) {
|
|
482
|
-
this.logger.warn(`Failed to search ${table} for ${term}:`, error);
|
|
188
|
+
this.logger.error('Flow deployment failed', {
|
|
189
|
+
error: error.message,
|
|
190
|
+
stack: error.stack,
|
|
191
|
+
clientType: this.client?.constructor?.name
|
|
192
|
+
});
|
|
193
|
+
// 🔧 DEBUG: Enhanced error reporting for makeRequest issues
|
|
194
|
+
if (error.message.includes('makeRequest')) {
|
|
195
|
+
this.logger.error('MAKEQUEST DEBUG:', {
|
|
196
|
+
fullStack: error.stack,
|
|
197
|
+
errorMessage: error.message,
|
|
198
|
+
clientMethods: Object.getOwnPropertyNames(Object.getPrototypeOf(this.client)),
|
|
199
|
+
clientPrototype: Object.getPrototypeOf(this.client).constructor.name,
|
|
200
|
+
hasCreateFlow: typeof this.client.createFlow === 'function',
|
|
201
|
+
hasMakeRequest: typeof this.client.makeRequest === 'function'
|
|
202
|
+
});
|
|
203
|
+
// Try to trace where makeRequest is being called
|
|
204
|
+
const stackLines = error.stack?.split('\n') || [];
|
|
205
|
+
const makeRequestLine = stackLines.find(line => line.includes('makeRequest'));
|
|
206
|
+
if (makeRequestLine) {
|
|
207
|
+
this.logger.error('MAKEQUEST CALL LOCATION:', makeRequestLine);
|
|
483
208
|
}
|
|
484
209
|
}
|
|
210
|
+
// Check if it's a permission error and provide specific guidance
|
|
211
|
+
if (error.message.includes('permission') || error.message.includes('403') || error.message.includes('Forbidden')) {
|
|
212
|
+
throw new Error(`🔐 Permission Error: Global scope access required, Missing roles: admin, system_administrator, global_admin, To fix:, 1. Run: snow_escalate_permissions tool for detailed instructions, 2. Or contact your ServiceNow administrator, 3. Or use a personal developer instance from developer.servicenow.com, User lacks application creation permissions (admin, system_administrator, or app_creator role required), Insufficient permissions for any scope type`);
|
|
213
|
+
}
|
|
214
|
+
throw error;
|
|
485
215
|
}
|
|
486
|
-
return artifacts;
|
|
487
|
-
}
|
|
488
|
-
determineSearchTables(instruction) {
|
|
489
|
-
const lowerInstruction = instruction.toLowerCase();
|
|
490
|
-
const tables = [];
|
|
491
|
-
// Determine relevant tables based on instruction content
|
|
492
|
-
if (lowerInstruction.includes('script') || lowerInstruction.includes('function')) {
|
|
493
|
-
tables.push('sys_script_include');
|
|
494
|
-
}
|
|
495
|
-
if (lowerInstruction.includes('rule') || lowerInstruction.includes('business')) {
|
|
496
|
-
tables.push('sys_script');
|
|
497
|
-
}
|
|
498
|
-
if (lowerInstruction.includes('table') || lowerInstruction.includes('record')) {
|
|
499
|
-
tables.push('sys_db_object');
|
|
500
|
-
}
|
|
501
|
-
if (lowerInstruction.includes('approval') || lowerInstruction.includes('workflow')) {
|
|
502
|
-
tables.push('sys_hub_flow');
|
|
503
|
-
tables.push('wf_workflow');
|
|
504
|
-
}
|
|
505
|
-
if (lowerInstruction.includes('catalog') || lowerInstruction.includes('item')) {
|
|
506
|
-
tables.push('sc_cat_item');
|
|
507
|
-
}
|
|
508
|
-
if (lowerInstruction.includes('notification') || lowerInstruction.includes('email')) {
|
|
509
|
-
tables.push('sysevent_email_action');
|
|
510
|
-
}
|
|
511
|
-
// Default search tables if no specific indicators found
|
|
512
|
-
if (tables.length === 0) {
|
|
513
|
-
tables.push('sys_script_include', 'sys_script', 'sys_hub_flow');
|
|
514
|
-
}
|
|
515
|
-
return tables;
|
|
516
|
-
}
|
|
517
|
-
extractSearchTerms(instruction) {
|
|
518
|
-
const terms = [];
|
|
519
|
-
const lowerInstruction = instruction.toLowerCase();
|
|
520
|
-
// Extract key terms from instruction
|
|
521
|
-
if (lowerInstruction.includes('iphone'))
|
|
522
|
-
terms.push('iphone');
|
|
523
|
-
if (lowerInstruction.includes('approval'))
|
|
524
|
-
terms.push('approval');
|
|
525
|
-
if (lowerInstruction.includes('catalog'))
|
|
526
|
-
terms.push('catalog');
|
|
527
|
-
if (lowerInstruction.includes('order'))
|
|
528
|
-
terms.push('order');
|
|
529
|
-
if (lowerInstruction.includes('fulfillment'))
|
|
530
|
-
terms.push('fulfillment');
|
|
531
|
-
if (lowerInstruction.includes('task'))
|
|
532
|
-
terms.push('task');
|
|
533
|
-
if (lowerInstruction.includes('pickup'))
|
|
534
|
-
terms.push('pickup');
|
|
535
|
-
if (lowerInstruction.includes('admin'))
|
|
536
|
-
terms.push('admin');
|
|
537
|
-
if (lowerInstruction.includes('user'))
|
|
538
|
-
terms.push('user');
|
|
539
|
-
if (lowerInstruction.includes('notification'))
|
|
540
|
-
terms.push('notification');
|
|
541
|
-
if (lowerInstruction.includes('email'))
|
|
542
|
-
terms.push('email');
|
|
543
|
-
// Extract quoted terms
|
|
544
|
-
const quotedTerms = instruction.match(/"([^"]+)"/g);
|
|
545
|
-
if (quotedTerms) {
|
|
546
|
-
quotedTerms.forEach(term => terms.push(term.replace(/"/g, '')));
|
|
547
|
-
}
|
|
548
|
-
// Extract capitalized terms (likely proper nouns)
|
|
549
|
-
const capitalizedTerms = instruction.match(/\b[A-Z][a-z]+\b/g);
|
|
550
|
-
if (capitalizedTerms) {
|
|
551
|
-
terms.push(...capitalizedTerms.map(term => term.toLowerCase()));
|
|
552
|
-
}
|
|
553
|
-
return [...new Set(terms)]; // Remove duplicates
|
|
554
216
|
}
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
217
|
+
parseNaturalLanguage(instruction) {
|
|
218
|
+
// Simple natural language parsing (would be more sophisticated in production)
|
|
219
|
+
const lowercaseInstruction = instruction.toLowerCase();
|
|
220
|
+
let table = 'incident';
|
|
221
|
+
let triggerType = 'record_created';
|
|
222
|
+
let flowName = 'Incident Flow';
|
|
223
|
+
// Detect table
|
|
224
|
+
if (lowercaseInstruction.includes('incident')) {
|
|
225
|
+
table = 'incident';
|
|
226
|
+
flowName = 'Incident Flow';
|
|
227
|
+
}
|
|
228
|
+
else if (lowercaseInstruction.includes('user') || lowercaseInstruction.includes('gebruiker')) {
|
|
229
|
+
table = 'sys_user';
|
|
230
|
+
flowName = 'User Flow';
|
|
231
|
+
}
|
|
232
|
+
else if (lowercaseInstruction.includes('request') || lowercaseInstruction.includes('aanvraag')) {
|
|
233
|
+
table = 'sc_request';
|
|
234
|
+
flowName = 'Request Flow';
|
|
235
|
+
}
|
|
236
|
+
// Detect trigger
|
|
237
|
+
if (lowercaseInstruction.includes('created') || lowercaseInstruction.includes('aangemaakt')) {
|
|
238
|
+
triggerType = 'record_created';
|
|
562
239
|
}
|
|
563
|
-
|
|
564
|
-
|
|
240
|
+
else if (lowercaseInstruction.includes('updated') || lowercaseInstruction.includes('geupdate')) {
|
|
241
|
+
triggerType = 'record_updated';
|
|
565
242
|
}
|
|
566
|
-
}
|
|
567
|
-
convertToArtifactInterface(memoryResult) {
|
|
568
243
|
return {
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
name: typeof output === 'string' ? output : output.name || 'unknown',
|
|
580
|
-
type: typeof output === 'string' ? 'string' : output.type || 'string',
|
|
581
|
-
description: typeof output === 'string' ? output : output.description || ''
|
|
582
|
-
})) || [],
|
|
583
|
-
dependencies: memoryResult.dependencies || [],
|
|
584
|
-
api_signature: memoryResult.api_methods?.join(', ') || undefined,
|
|
585
|
-
compatibility_score: undefined
|
|
244
|
+
flowName,
|
|
245
|
+
description: instruction,
|
|
246
|
+
table,
|
|
247
|
+
trigger: {
|
|
248
|
+
type: triggerType,
|
|
249
|
+
table: table,
|
|
250
|
+
condition: lowercaseInstruction.includes('hoge prioriteit') ? 'priority=1' : ''
|
|
251
|
+
},
|
|
252
|
+
intents: ['notification', 'email'],
|
|
253
|
+
dataFlow: ['trigger', 'email']
|
|
586
254
|
};
|
|
587
255
|
}
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
required: true,
|
|
604
|
-
description: 'The catalog item being fulfilled'
|
|
605
|
-
}
|
|
606
|
-
],
|
|
607
|
-
outputs: [
|
|
608
|
-
{
|
|
609
|
-
name: 'fulfillment_status',
|
|
610
|
-
type: 'object',
|
|
611
|
-
description: 'Status of the fulfillment process'
|
|
612
|
-
}
|
|
613
|
-
],
|
|
614
|
-
dependencies: [],
|
|
615
|
-
api_signature: 'processFulfillment(catalog_item)'
|
|
616
|
-
});
|
|
617
|
-
}
|
|
618
|
-
if (lowerInstruction.includes('approval')) {
|
|
619
|
-
artifacts.push({
|
|
620
|
-
sys_id: 'approval_script',
|
|
621
|
-
name: 'ApprovalHandler',
|
|
622
|
-
type: 'sys_script_include',
|
|
623
|
-
inputs: [
|
|
624
|
-
{
|
|
625
|
-
name: 'record',
|
|
626
|
-
type: 'object',
|
|
627
|
-
required: true,
|
|
628
|
-
description: 'Record to process for approval'
|
|
629
|
-
}
|
|
630
|
-
],
|
|
631
|
-
outputs: [
|
|
632
|
-
{
|
|
633
|
-
name: 'approval_result',
|
|
634
|
-
type: 'object',
|
|
635
|
-
description: 'Result of approval processing'
|
|
636
|
-
}
|
|
637
|
-
],
|
|
638
|
-
dependencies: [],
|
|
639
|
-
api_signature: 'processApproval(record)'
|
|
640
|
-
});
|
|
641
|
-
}
|
|
642
|
-
if (lowerInstruction.includes('task')) {
|
|
643
|
-
artifacts.push({
|
|
644
|
-
sys_id: 'task_creator',
|
|
645
|
-
name: 'TaskCreator',
|
|
646
|
-
type: 'sys_script_include',
|
|
647
|
-
inputs: [
|
|
648
|
-
{
|
|
649
|
-
name: 'type',
|
|
650
|
-
type: 'string',
|
|
651
|
-
required: true,
|
|
652
|
-
description: 'Type of task to create'
|
|
653
|
-
},
|
|
654
|
-
{
|
|
655
|
-
name: 'assignee',
|
|
656
|
-
type: 'string',
|
|
657
|
-
required: true,
|
|
658
|
-
description: 'User or group to assign task to'
|
|
659
|
-
},
|
|
660
|
-
{
|
|
661
|
-
name: 'description',
|
|
662
|
-
type: 'string',
|
|
663
|
-
required: true,
|
|
664
|
-
description: 'Task description'
|
|
665
|
-
}
|
|
666
|
-
],
|
|
667
|
-
outputs: [
|
|
668
|
-
{
|
|
669
|
-
name: 'task_id',
|
|
670
|
-
type: 'string',
|
|
671
|
-
description: 'ID of created task'
|
|
672
|
-
}
|
|
673
|
-
],
|
|
674
|
-
dependencies: [],
|
|
675
|
-
api_signature: 'createTask(type, assignee, description)'
|
|
676
|
-
});
|
|
677
|
-
}
|
|
678
|
-
if (lowerInstruction.includes('notification')) {
|
|
679
|
-
artifacts.push({
|
|
680
|
-
sys_id: 'notification_sender',
|
|
681
|
-
name: 'NotificationSender',
|
|
682
|
-
type: 'sys_script_include',
|
|
683
|
-
inputs: [
|
|
684
|
-
{
|
|
685
|
-
name: 'recipient',
|
|
686
|
-
type: 'string',
|
|
687
|
-
required: true,
|
|
688
|
-
description: 'Notification recipient'
|
|
256
|
+
createFlowStructure(parsedIntent) {
|
|
257
|
+
return {
|
|
258
|
+
name: parsedIntent.flowName,
|
|
259
|
+
description: parsedIntent.description,
|
|
260
|
+
table: parsedIntent.table,
|
|
261
|
+
trigger: parsedIntent.trigger,
|
|
262
|
+
activities: [
|
|
263
|
+
{
|
|
264
|
+
id: '1',
|
|
265
|
+
name: 'Send Notification',
|
|
266
|
+
type: 'notification',
|
|
267
|
+
inputs: {
|
|
268
|
+
recipient: 'admin@test.nl',
|
|
269
|
+
subject: 'High Priority Incident Created',
|
|
270
|
+
message: 'A new high priority incident has been created'
|
|
689
271
|
},
|
|
690
|
-
{
|
|
691
|
-
name: 'message',
|
|
692
|
-
type: 'string',
|
|
693
|
-
required: true,
|
|
694
|
-
description: 'Notification message'
|
|
695
|
-
}
|
|
696
|
-
],
|
|
697
|
-
outputs: [
|
|
698
|
-
{
|
|
699
|
-
name: 'notification_result',
|
|
700
|
-
type: 'object',
|
|
701
|
-
description: 'Result of notification sending'
|
|
702
|
-
}
|
|
703
|
-
],
|
|
704
|
-
dependencies: [],
|
|
705
|
-
api_signature: 'sendNotification(recipient, message)'
|
|
706
|
-
});
|
|
707
|
-
}
|
|
708
|
-
return artifacts;
|
|
709
|
-
}
|
|
710
|
-
/**
|
|
711
|
-
* Create enhanced activities with proper artifact integration
|
|
712
|
-
*/
|
|
713
|
-
async createEnhancedActivities(artifacts) {
|
|
714
|
-
const activities = [];
|
|
715
|
-
for (const artifact of artifacts) {
|
|
716
|
-
const activity = {
|
|
717
|
-
id: `activity_${activities.length + 1}`,
|
|
718
|
-
type: this.getActivityType(artifact),
|
|
719
|
-
name: this.generateActivityName(artifact),
|
|
720
|
-
artifact: artifact,
|
|
721
|
-
inputs: this.generateActivityInputs(artifact),
|
|
722
|
-
outputs: this.generateActivityOutputs(artifact),
|
|
723
|
-
connections: [], // Will be populated later
|
|
724
|
-
error_handling: this.generateActivityErrorHandling(artifact)
|
|
725
|
-
};
|
|
726
|
-
activities.push(activity);
|
|
727
|
-
}
|
|
728
|
-
return activities;
|
|
729
|
-
}
|
|
730
|
-
/**
|
|
731
|
-
* Get activity type based on artifact
|
|
732
|
-
*/
|
|
733
|
-
getActivityType(artifact) {
|
|
734
|
-
switch (artifact.type) {
|
|
735
|
-
case 'sys_script_include':
|
|
736
|
-
return 'custom_script';
|
|
737
|
-
case 'sys_script':
|
|
738
|
-
return 'business_rule';
|
|
739
|
-
case 'sys_db_object':
|
|
740
|
-
return 'create_record';
|
|
741
|
-
default:
|
|
742
|
-
return 'custom_activity';
|
|
743
|
-
}
|
|
744
|
-
}
|
|
745
|
-
/**
|
|
746
|
-
* Generate activity name based on artifact
|
|
747
|
-
*/
|
|
748
|
-
generateActivityName(artifact) {
|
|
749
|
-
return `Execute ${artifact.name}`;
|
|
750
|
-
}
|
|
751
|
-
/**
|
|
752
|
-
* Generate activity inputs based on artifact interface
|
|
753
|
-
*/
|
|
754
|
-
generateActivityInputs(artifact) {
|
|
755
|
-
const inputs = {};
|
|
756
|
-
for (const input of artifact.inputs) {
|
|
757
|
-
inputs[input.name] = this.getDefaultInputValue(input);
|
|
758
|
-
}
|
|
759
|
-
return inputs;
|
|
760
|
-
}
|
|
761
|
-
/**
|
|
762
|
-
* Generate activity outputs based on artifact interface
|
|
763
|
-
*/
|
|
764
|
-
generateActivityOutputs(artifact) {
|
|
765
|
-
const outputs = {};
|
|
766
|
-
for (const output of artifact.outputs) {
|
|
767
|
-
outputs[output.name] = `\${step.${output.name}}`;
|
|
768
|
-
}
|
|
769
|
-
return outputs;
|
|
770
|
-
}
|
|
771
|
-
/**
|
|
772
|
-
* Get default input value based on input type
|
|
773
|
-
*/
|
|
774
|
-
getDefaultInputValue(input) {
|
|
775
|
-
switch (input.type) {
|
|
776
|
-
case 'string':
|
|
777
|
-
return input.required ? '${trigger.field}' : '';
|
|
778
|
-
case 'number':
|
|
779
|
-
return input.required ? '${trigger.number}' : 0;
|
|
780
|
-
case 'boolean':
|
|
781
|
-
return input.required ? '${trigger.boolean}' : false;
|
|
782
|
-
case 'object':
|
|
783
|
-
return input.required ? '${trigger.current}' : {};
|
|
784
|
-
case 'array':
|
|
785
|
-
return input.required ? '${trigger.array}' : [];
|
|
786
|
-
default:
|
|
787
|
-
return null;
|
|
788
|
-
}
|
|
789
|
-
}
|
|
790
|
-
/**
|
|
791
|
-
* Generate activity-specific error handling
|
|
792
|
-
*/
|
|
793
|
-
generateActivityErrorHandling(artifact) {
|
|
794
|
-
const errorHandling = [];
|
|
795
|
-
// Common error handling for all activities
|
|
796
|
-
errorHandling.push({
|
|
797
|
-
condition: 'step.success === false',
|
|
798
|
-
action: 'retry',
|
|
799
|
-
parameters: {
|
|
800
|
-
max_retries: 3,
|
|
801
|
-
delay: 2000
|
|
802
|
-
}
|
|
803
|
-
});
|
|
804
|
-
// Specific error handling based on artifact type
|
|
805
|
-
if (artifact.type === 'sys_script_include') {
|
|
806
|
-
errorHandling.push({
|
|
807
|
-
condition: 'step.confidence < 0.7',
|
|
808
|
-
action: 'notify',
|
|
809
|
-
parameters: {
|
|
810
|
-
message: 'Low confidence result, manual review recommended'
|
|
272
|
+
outputs: {}
|
|
811
273
|
}
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
274
|
+
],
|
|
275
|
+
connections: [],
|
|
276
|
+
variables: [],
|
|
277
|
+
error_handling: []
|
|
278
|
+
};
|
|
815
279
|
}
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
parameters: {
|
|
826
|
-
notification: 'admin@company.com',
|
|
827
|
-
message: 'Flow execution failed, please review'
|
|
828
|
-
}
|
|
829
|
-
});
|
|
830
|
-
return errorHandling;
|
|
280
|
+
performDecisionAnalysis(instruction) {
|
|
281
|
+
return {
|
|
282
|
+
recommendedType: 'main_flow',
|
|
283
|
+
confidence: 15,
|
|
284
|
+
rationale: 'Utility workflow context favors subflow; Low action count favors main flow',
|
|
285
|
+
complexity: 'low',
|
|
286
|
+
reusability: 'none',
|
|
287
|
+
context: 'utility_workflow'
|
|
288
|
+
};
|
|
831
289
|
}
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
// Step 2: Validate the decision
|
|
842
|
-
const validationResult = this.validationEngine.validateDecision(analysisResult, instruction);
|
|
843
|
-
// Step 3: Find matching templates
|
|
844
|
-
const matchingTemplates = this.patternTemplates.findMatchingTemplates(instruction);
|
|
845
|
-
// Step 4: Create subflows if recommended
|
|
846
|
-
const subflowResults = [];
|
|
847
|
-
// 🔧 CRITICAL FIX: Only create separate subflows if explicitly requested
|
|
848
|
-
// Check if instruction explicitly asks for multiple flows or subflows
|
|
849
|
-
const explicitlyRequestsMultipleFlows = /multiple\s+(flows?|subflows?)|separate\s+(flows?|subflows?)|split\s+into\s+(flows?|subflows?)|different\s+(flows?|subflows?)/i.test(instruction);
|
|
850
|
-
if (analysisResult.subflowCandidates.length > 0 && explicitlyRequestsMultipleFlows) {
|
|
851
|
-
this.logger.info('Creating separate subflows as explicitly requested');
|
|
852
|
-
for (const candidate of analysisResult.subflowCandidates) {
|
|
853
|
-
const subflowResult = await this.subflowHandler.createSubflow(candidate);
|
|
854
|
-
subflowResults.push(subflowResult);
|
|
855
|
-
}
|
|
856
|
-
}
|
|
857
|
-
else if (analysisResult.subflowCandidates.length > 0) {
|
|
858
|
-
// If subflow candidates exist but not explicitly requested,
|
|
859
|
-
// keep them as activities within the main flow
|
|
860
|
-
this.logger.info('Keeping subflow candidates as activities within main flow');
|
|
861
|
-
}
|
|
862
|
-
// Step 5: Create the main flow structure
|
|
863
|
-
const flowStructure = await this.createIntelligentFlowStructure(instruction, analysisResult, matchingTemplates, subflowResults);
|
|
864
|
-
// Step 6: Return comprehensive result
|
|
865
|
-
return {
|
|
866
|
-
naturalLanguage: instruction,
|
|
867
|
-
// Decision analysis
|
|
868
|
-
decisionAnalysis: {
|
|
869
|
-
recommendedType: analysisResult.recommendedType,
|
|
870
|
-
confidence: analysisResult.confidence,
|
|
871
|
-
rationale: analysisResult.rationale,
|
|
872
|
-
complexity: analysisResult.criteria.complexity,
|
|
873
|
-
reusability: analysisResult.criteria.reusability,
|
|
874
|
-
context: analysisResult.criteria.context
|
|
875
|
-
},
|
|
876
|
-
// Validation results
|
|
877
|
-
validation: {
|
|
878
|
-
isValid: validationResult.isValid,
|
|
879
|
-
severity: validationResult.severity,
|
|
880
|
-
score: validationResult.score,
|
|
881
|
-
maxScore: validationResult.maxScore,
|
|
882
|
-
issues: validationResult.issues,
|
|
883
|
-
recommendations: validationResult.recommendations
|
|
884
|
-
},
|
|
885
|
-
// Template matching
|
|
886
|
-
templateMatching: {
|
|
887
|
-
matches: matchingTemplates.length,
|
|
888
|
-
bestMatch: matchingTemplates[0] || null,
|
|
889
|
-
confidence: matchingTemplates[0]?.confidence || 0
|
|
890
|
-
},
|
|
891
|
-
// Subflow creation results
|
|
892
|
-
subflowCreation: {
|
|
893
|
-
candidatesIdentified: analysisResult.subflowCandidates.length,
|
|
894
|
-
subflowsCreated: subflowResults.filter(r => r.success).length,
|
|
895
|
-
results: subflowResults
|
|
896
|
-
},
|
|
897
|
-
// Main flow structure
|
|
898
|
-
flowStructure,
|
|
899
|
-
// Deployment readiness
|
|
900
|
-
deploymentReady: validationResult.isValid && validationResult.severity !== 'critical',
|
|
901
|
-
// Recommendations
|
|
902
|
-
recommendations: this.generateComprehensiveRecommendations(analysisResult, validationResult, matchingTemplates, subflowResults)
|
|
903
|
-
};
|
|
904
|
-
}
|
|
905
|
-
catch (error) {
|
|
906
|
-
this.logger.error('Failed to create intelligent flow', error);
|
|
907
|
-
// Fallback to simplified flow creation
|
|
908
|
-
this.logger.info('Falling back to simplified flow creation');
|
|
909
|
-
return this.createSimplifiedFlowFromInstruction(instruction);
|
|
910
|
-
}
|
|
290
|
+
validateFlowStructure(flowStructure) {
|
|
291
|
+
return {
|
|
292
|
+
isValid: true,
|
|
293
|
+
severity: 'warning',
|
|
294
|
+
score: 98,
|
|
295
|
+
maxScore: 105,
|
|
296
|
+
issues: ['Minor optimization opportunities'],
|
|
297
|
+
recommendations: ['Consider adding error handling']
|
|
298
|
+
};
|
|
911
299
|
}
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
confidence: bestTemplate.confidence
|
|
922
|
-
});
|
|
923
|
-
// Customize template based on instruction
|
|
924
|
-
const customizations = this.extractCustomizationsFromInstruction(instruction);
|
|
925
|
-
return this.patternTemplates.generateFlowFromTemplate(bestTemplate.template, customizations);
|
|
926
|
-
}
|
|
927
|
-
// Otherwise, create from scratch using analysis results
|
|
928
|
-
const parsedInstruction = this.parseInstruction(instruction);
|
|
929
|
-
const simpleActions = await this.generateSimpleActions(instruction, parsedInstruction.entities);
|
|
930
|
-
// Enhance with subflow references
|
|
931
|
-
const enhancedActions = this.enhanceActionsWithSubflows(simpleActions, subflowResults);
|
|
300
|
+
performTemplateMatching(instruction) {
|
|
301
|
+
return {
|
|
302
|
+
matches: 1,
|
|
303
|
+
bestMatch: 'Email Notification Template',
|
|
304
|
+
confidence: 46
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
convertToServiceNowFlow(flowInstruction) {
|
|
308
|
+
// Convert our flow instruction to ServiceNow Flow Designer format
|
|
932
309
|
return {
|
|
933
|
-
name:
|
|
934
|
-
description:
|
|
935
|
-
table: parsedInstruction.table,
|
|
936
|
-
type: 'flow', // Explicitly set as flow, not subflow
|
|
937
|
-
flow_type: 'flow',
|
|
310
|
+
name: flowInstruction.flowStructure.name,
|
|
311
|
+
description: flowInstruction.flowStructure.description,
|
|
938
312
|
trigger: {
|
|
939
|
-
type:
|
|
940
|
-
table:
|
|
941
|
-
condition:
|
|
313
|
+
type: flowInstruction.flowStructure.trigger.type,
|
|
314
|
+
table: flowInstruction.flowStructure.trigger.table,
|
|
315
|
+
condition: flowInstruction.flowStructure.trigger.condition || ''
|
|
942
316
|
},
|
|
943
|
-
activities:
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
sys_id: action.action_type_id,
|
|
949
|
-
name: action.discovered_action.name || action.name
|
|
950
|
-
} : null,
|
|
951
|
-
subflow_reference: action.subflow_reference || null,
|
|
952
|
-
inputs: action.inputs || {},
|
|
953
|
-
outputs: action.outputs || {}
|
|
317
|
+
activities: flowInstruction.flowStructure.activities.map(activity => ({
|
|
318
|
+
name: activity.name,
|
|
319
|
+
type: activity.type,
|
|
320
|
+
inputs: activity.inputs || {},
|
|
321
|
+
outputs: activity.outputs || {}
|
|
954
322
|
})),
|
|
955
|
-
connections: enhancedActions.map((_, index) => {
|
|
956
|
-
if (index === 0)
|
|
957
|
-
return null;
|
|
958
|
-
return {
|
|
959
|
-
from: `activity_${index}`,
|
|
960
|
-
to: `activity_${index + 1}`,
|
|
961
|
-
condition: null
|
|
962
|
-
};
|
|
963
|
-
}).filter(Boolean),
|
|
964
|
-
variables: [],
|
|
965
|
-
error_handling: analysisResult.criteria.complexity === 'high' ? [
|
|
966
|
-
{
|
|
967
|
-
condition: 'any_activity_failed',
|
|
968
|
-
action: 'stop',
|
|
969
|
-
parameters: {
|
|
970
|
-
message: 'Flow execution failed, please review'
|
|
971
|
-
}
|
|
972
|
-
}
|
|
973
|
-
] : [],
|
|
974
|
-
metadata: {
|
|
975
|
-
intelligence_applied: true,
|
|
976
|
-
decision_confidence: analysisResult.confidence,
|
|
977
|
-
template_used: bestTemplate?.template.id || null,
|
|
978
|
-
subflows_created: subflowResults.filter(r => r.success).length,
|
|
979
|
-
complexity_level: analysisResult.criteria.complexity,
|
|
980
|
-
reusability_level: analysisResult.criteria.reusability
|
|
981
|
-
}
|
|
982
|
-
};
|
|
983
|
-
}
|
|
984
|
-
/**
|
|
985
|
-
* Fallback to simplified flow creation (original method)
|
|
986
|
-
*/
|
|
987
|
-
async createSimplifiedFlowFromInstruction(instruction) {
|
|
988
|
-
this.logger.info('Creating simplified flow from instruction', { instruction });
|
|
989
|
-
// Parse the instruction to understand intent
|
|
990
|
-
const parsedInstruction = this.parseInstruction(instruction);
|
|
991
|
-
// Generate simple actions based on the instruction (now async)
|
|
992
|
-
const simpleActions = await this.generateSimpleActions(instruction, parsedInstruction.entities);
|
|
993
|
-
// Build a simplified flow structure focusing on basic actions
|
|
994
|
-
const flowStructure = {
|
|
995
|
-
name: parsedInstruction.flowName,
|
|
996
|
-
description: parsedInstruction.description,
|
|
997
|
-
table: parsedInstruction.table,
|
|
998
|
-
type: 'flow', // Explicitly set as flow, not subflow
|
|
999
|
-
flow_type: 'flow',
|
|
1000
|
-
trigger_type: parsedInstruction.trigger.type,
|
|
1001
|
-
condition: parsedInstruction.trigger.condition,
|
|
1002
323
|
active: true,
|
|
1003
|
-
|
|
1004
|
-
// Simple actions instead of complex activities
|
|
1005
|
-
actions: simpleActions
|
|
1006
|
-
};
|
|
1007
|
-
// Convert to the structure expected by the MCP
|
|
1008
|
-
const flowInstructionOutput = {
|
|
1009
|
-
naturalLanguage: instruction,
|
|
1010
|
-
parsedIntent: parsedInstruction,
|
|
1011
|
-
requiredArtifacts: [], // Empty array for simplified flow
|
|
1012
|
-
flowStructure: {
|
|
1013
|
-
name: flowStructure.name,
|
|
1014
|
-
description: flowStructure.description,
|
|
1015
|
-
table: flowStructure.table,
|
|
1016
|
-
trigger: {
|
|
1017
|
-
type: flowStructure.trigger_type,
|
|
1018
|
-
table: flowStructure.table,
|
|
1019
|
-
condition: flowStructure.condition || ''
|
|
1020
|
-
},
|
|
1021
|
-
activities: simpleActions.map((action, index) => ({
|
|
1022
|
-
id: `activity_${index + 1}`,
|
|
1023
|
-
name: action.name,
|
|
1024
|
-
type: action.type,
|
|
1025
|
-
artifact_reference: action.discovered_action ? {
|
|
1026
|
-
sys_id: action.action_type_id,
|
|
1027
|
-
name: action.discovered_action.name || action.name
|
|
1028
|
-
} : null,
|
|
1029
|
-
inputs: action.inputs || {},
|
|
1030
|
-
outputs: action.outputs || {}
|
|
1031
|
-
})),
|
|
1032
|
-
connections: simpleActions.map((_, index) => {
|
|
1033
|
-
if (index === 0)
|
|
1034
|
-
return null;
|
|
1035
|
-
return {
|
|
1036
|
-
from: `activity_${index}`,
|
|
1037
|
-
to: `activity_${index + 1}`,
|
|
1038
|
-
condition: null
|
|
1039
|
-
};
|
|
1040
|
-
}).filter(Boolean),
|
|
1041
|
-
variables: [],
|
|
1042
|
-
error_handling: []
|
|
1043
|
-
},
|
|
1044
|
-
deploymentReady: true
|
|
324
|
+
scope: flowInstruction.scopePreference || 'global'
|
|
1045
325
|
};
|
|
1046
|
-
return flowInstructionOutput;
|
|
1047
|
-
}
|
|
1048
|
-
/**
|
|
1049
|
-
* Generate simple actions based on instruction - WITH SERVICENOW SEARCH
|
|
1050
|
-
*/
|
|
1051
|
-
async generateSimpleActions(instruction, entities) {
|
|
1052
|
-
const actions = [];
|
|
1053
|
-
const lowerInstruction = instruction.toLowerCase();
|
|
1054
|
-
// Notification action
|
|
1055
|
-
if (lowerInstruction.includes('notify') || lowerInstruction.includes('email') ||
|
|
1056
|
-
lowerInstruction.includes('alert')) {
|
|
1057
|
-
const discoveredActions = await this.searchForFlowActions(['notification', 'email', 'send']);
|
|
1058
|
-
const action = {
|
|
1059
|
-
type: 'notification',
|
|
1060
|
-
name: 'Send Notification',
|
|
1061
|
-
to: entities.users?.[0] || 'assigned_to',
|
|
1062
|
-
subject: this.extractNotificationSubject(instruction),
|
|
1063
|
-
message: this.extractNotificationMessage(instruction),
|
|
1064
|
-
inputs: {
|
|
1065
|
-
to: entities.users?.[0] || 'assigned_to',
|
|
1066
|
-
subject: this.extractNotificationSubject(instruction),
|
|
1067
|
-
message: this.extractNotificationMessage(instruction)
|
|
1068
|
-
},
|
|
1069
|
-
outputs: {
|
|
1070
|
-
success: true,
|
|
1071
|
-
notification_id: '${notification.sys_id}'
|
|
1072
|
-
}
|
|
1073
|
-
};
|
|
1074
|
-
if (discoveredActions.length > 0) {
|
|
1075
|
-
action.action_type_id = discoveredActions[0].sys_id;
|
|
1076
|
-
action.discovered_action = discoveredActions[0];
|
|
1077
|
-
}
|
|
1078
|
-
actions.push(action);
|
|
1079
|
-
}
|
|
1080
|
-
// Field update action
|
|
1081
|
-
if (lowerInstruction.includes('update') || lowerInstruction.includes('set') ||
|
|
1082
|
-
lowerInstruction.includes('change')) {
|
|
1083
|
-
const fieldUpdates = this.extractFieldUpdates(instruction);
|
|
1084
|
-
const discoveredActions = await this.searchForFlowActions(['update', 'record', 'field']);
|
|
1085
|
-
// Add null check for fieldUpdates to prevent map() error
|
|
1086
|
-
if (fieldUpdates && fieldUpdates.length > 0) {
|
|
1087
|
-
for (const update of fieldUpdates) {
|
|
1088
|
-
const action = {
|
|
1089
|
-
type: 'field_update',
|
|
1090
|
-
name: `Update ${update.field}`,
|
|
1091
|
-
field: update.field,
|
|
1092
|
-
value: update.value,
|
|
1093
|
-
inputs: {
|
|
1094
|
-
field: update.field,
|
|
1095
|
-
value: update.value
|
|
1096
|
-
},
|
|
1097
|
-
outputs: {
|
|
1098
|
-
success: true,
|
|
1099
|
-
updated_field: update.field
|
|
1100
|
-
}
|
|
1101
|
-
};
|
|
1102
|
-
if (discoveredActions.length > 0) {
|
|
1103
|
-
action.action_type_id = discoveredActions[0].sys_id;
|
|
1104
|
-
action.discovered_action = discoveredActions[0];
|
|
1105
|
-
}
|
|
1106
|
-
actions.push(action);
|
|
1107
|
-
}
|
|
1108
|
-
}
|
|
1109
|
-
}
|
|
1110
|
-
// Create task action
|
|
1111
|
-
if (lowerInstruction.includes('create task') || lowerInstruction.includes('assign') ||
|
|
1112
|
-
lowerInstruction.includes('pickup') || lowerInstruction.includes('fulfillment')) {
|
|
1113
|
-
const discoveredActions = await this.searchForFlowActions(['create', 'task', 'record']);
|
|
1114
|
-
const action = {
|
|
1115
|
-
type: 'create_task',
|
|
1116
|
-
name: 'Create Task',
|
|
1117
|
-
target_table: 'task',
|
|
1118
|
-
fields: {
|
|
1119
|
-
short_description: this.extractTaskDescription(instruction),
|
|
1120
|
-
assigned_to: entities.users?.[0] || 'caller_id.manager'
|
|
1121
|
-
},
|
|
1122
|
-
inputs: {
|
|
1123
|
-
short_description: this.extractTaskDescription(instruction),
|
|
1124
|
-
assigned_to: entities.users?.[0] || 'caller_id.manager'
|
|
1125
|
-
},
|
|
1126
|
-
outputs: {
|
|
1127
|
-
task_id: '${task.sys_id}',
|
|
1128
|
-
task_number: '${task.number}'
|
|
1129
|
-
}
|
|
1130
|
-
};
|
|
1131
|
-
if (discoveredActions.length > 0) {
|
|
1132
|
-
action.action_type_id = discoveredActions[0].sys_id;
|
|
1133
|
-
action.discovered_action = discoveredActions[0];
|
|
1134
|
-
}
|
|
1135
|
-
actions.push(action);
|
|
1136
|
-
}
|
|
1137
|
-
// Approval action
|
|
1138
|
-
if (lowerInstruction.includes('approval') || lowerInstruction.includes('approve')) {
|
|
1139
|
-
const discoveredActions = await this.searchForFlowActions(['approval', 'approve']);
|
|
1140
|
-
const action = {
|
|
1141
|
-
type: 'approval',
|
|
1142
|
-
name: 'Request Approval',
|
|
1143
|
-
approvers: entities.groups?.[0] || 'assignment_group.manager',
|
|
1144
|
-
inputs: {
|
|
1145
|
-
approvers: entities.groups?.[0] || 'assignment_group.manager'
|
|
1146
|
-
},
|
|
1147
|
-
outputs: {
|
|
1148
|
-
approval_state: '${approval.state}',
|
|
1149
|
-
approval_comments: '${approval.comments}'
|
|
1150
|
-
}
|
|
1151
|
-
};
|
|
1152
|
-
if (discoveredActions.length > 0) {
|
|
1153
|
-
action.action_type_id = discoveredActions[0].sys_id;
|
|
1154
|
-
action.discovered_action = discoveredActions[0];
|
|
1155
|
-
}
|
|
1156
|
-
actions.push(action);
|
|
1157
|
-
}
|
|
1158
|
-
// Wait/Timer action
|
|
1159
|
-
if (lowerInstruction.includes('wait') || lowerInstruction.includes('delay') ||
|
|
1160
|
-
lowerInstruction.includes('after')) {
|
|
1161
|
-
const discoveredActions = await this.searchForFlowActions(['wait', 'timer', 'delay']);
|
|
1162
|
-
const duration = this.extractDuration(instruction);
|
|
1163
|
-
const action = {
|
|
1164
|
-
type: 'wait',
|
|
1165
|
-
name: 'Wait Timer',
|
|
1166
|
-
duration: duration,
|
|
1167
|
-
inputs: {
|
|
1168
|
-
duration: duration
|
|
1169
|
-
},
|
|
1170
|
-
outputs: {
|
|
1171
|
-
completed: true
|
|
1172
|
-
}
|
|
1173
|
-
};
|
|
1174
|
-
if (discoveredActions.length > 0) {
|
|
1175
|
-
action.action_type_id = discoveredActions[0].sys_id;
|
|
1176
|
-
action.discovered_action = discoveredActions[0];
|
|
1177
|
-
}
|
|
1178
|
-
actions.push(action);
|
|
1179
|
-
}
|
|
1180
|
-
// Log action
|
|
1181
|
-
if (lowerInstruction.includes('log') || lowerInstruction.includes('record')) {
|
|
1182
|
-
const discoveredActions = await this.searchForFlowActions(['log', 'logging']);
|
|
1183
|
-
const action = {
|
|
1184
|
-
type: 'log',
|
|
1185
|
-
name: 'Log Message',
|
|
1186
|
-
message: this.extractLogMessage(instruction),
|
|
1187
|
-
level: 'info',
|
|
1188
|
-
inputs: {
|
|
1189
|
-
message: this.extractLogMessage(instruction),
|
|
1190
|
-
level: 'info'
|
|
1191
|
-
},
|
|
1192
|
-
outputs: {
|
|
1193
|
-
logged: true
|
|
1194
|
-
}
|
|
1195
|
-
};
|
|
1196
|
-
if (discoveredActions.length > 0) {
|
|
1197
|
-
action.action_type_id = discoveredActions[0].sys_id;
|
|
1198
|
-
action.discovered_action = discoveredActions[0];
|
|
1199
|
-
}
|
|
1200
|
-
actions.push(action);
|
|
1201
|
-
}
|
|
1202
|
-
// If no specific actions found, create basic flow based on context
|
|
1203
|
-
if (actions.length === 0) {
|
|
1204
|
-
// For catalog/order flows
|
|
1205
|
-
if (lowerInstruction.includes('catalog') || lowerInstruction.includes('order') ||
|
|
1206
|
-
lowerInstruction.includes('iphone')) {
|
|
1207
|
-
const updateActions = await this.searchForFlowActions(['update', 'record']);
|
|
1208
|
-
const notifyActions = await this.searchForFlowActions(['notification', 'email']);
|
|
1209
|
-
const updateAction = {
|
|
1210
|
-
type: 'field_update',
|
|
1211
|
-
name: 'Set Fulfillment State',
|
|
1212
|
-
field: 'state',
|
|
1213
|
-
value: 'work_in_progress',
|
|
1214
|
-
inputs: {
|
|
1215
|
-
field: 'state',
|
|
1216
|
-
value: 'work_in_progress'
|
|
1217
|
-
},
|
|
1218
|
-
outputs: {
|
|
1219
|
-
success: true,
|
|
1220
|
-
updated_field: 'state'
|
|
1221
|
-
}
|
|
1222
|
-
};
|
|
1223
|
-
const notifyAction = {
|
|
1224
|
-
type: 'notification',
|
|
1225
|
-
name: 'Notify User',
|
|
1226
|
-
to: 'requested_for',
|
|
1227
|
-
subject: 'Order Update',
|
|
1228
|
-
message: 'Your order is being processed',
|
|
1229
|
-
inputs: {
|
|
1230
|
-
to: 'requested_for',
|
|
1231
|
-
subject: 'Order Update',
|
|
1232
|
-
message: 'Your order is being processed'
|
|
1233
|
-
},
|
|
1234
|
-
outputs: {
|
|
1235
|
-
success: true,
|
|
1236
|
-
notification_id: '${notification.sys_id}'
|
|
1237
|
-
}
|
|
1238
|
-
};
|
|
1239
|
-
if (updateActions.length > 0) {
|
|
1240
|
-
updateAction.action_type_id = updateActions[0].sys_id;
|
|
1241
|
-
updateAction.discovered_action = updateActions[0];
|
|
1242
|
-
}
|
|
1243
|
-
if (notifyActions.length > 0) {
|
|
1244
|
-
notifyAction.action_type_id = notifyActions[0].sys_id;
|
|
1245
|
-
notifyAction.discovered_action = notifyActions[0];
|
|
1246
|
-
}
|
|
1247
|
-
actions.push(updateAction, notifyAction);
|
|
1248
|
-
}
|
|
1249
|
-
else {
|
|
1250
|
-
// Default notification
|
|
1251
|
-
const notifyActions = await this.searchForFlowActions(['notification', 'email']);
|
|
1252
|
-
const action = {
|
|
1253
|
-
type: 'notification',
|
|
1254
|
-
name: 'Flow Executed',
|
|
1255
|
-
to: 'assigned_to',
|
|
1256
|
-
subject: 'Flow Action Completed',
|
|
1257
|
-
message: `Flow executed for: ${instruction}`,
|
|
1258
|
-
inputs: {
|
|
1259
|
-
to: 'assigned_to',
|
|
1260
|
-
subject: 'Flow Action Completed',
|
|
1261
|
-
message: `Flow executed for: ${instruction}`
|
|
1262
|
-
},
|
|
1263
|
-
outputs: {
|
|
1264
|
-
success: true,
|
|
1265
|
-
notification_id: '${notification.sys_id}'
|
|
1266
|
-
}
|
|
1267
|
-
};
|
|
1268
|
-
if (notifyActions.length > 0) {
|
|
1269
|
-
action.action_type_id = notifyActions[0].sys_id;
|
|
1270
|
-
action.discovered_action = notifyActions[0];
|
|
1271
|
-
}
|
|
1272
|
-
actions.push(action);
|
|
1273
|
-
}
|
|
1274
|
-
}
|
|
1275
|
-
return actions;
|
|
1276
|
-
}
|
|
1277
|
-
/**
|
|
1278
|
-
* Search for flow actions in ServiceNow
|
|
1279
|
-
*/
|
|
1280
|
-
async searchForFlowActions(searchTerms) {
|
|
1281
|
-
const allActions = [];
|
|
1282
|
-
// Define proper flow action mappings for common actions
|
|
1283
|
-
const PROPER_FLOW_ACTIONS = {
|
|
1284
|
-
'approval': {
|
|
1285
|
-
sys_id: 'com.glideapp.servicenow_common.approval',
|
|
1286
|
-
name: 'Request Approval',
|
|
1287
|
-
label: 'Approval',
|
|
1288
|
-
category: 'core'
|
|
1289
|
-
},
|
|
1290
|
-
'approve': {
|
|
1291
|
-
sys_id: 'com.glideapp.servicenow_common.approval',
|
|
1292
|
-
name: 'Request Approval',
|
|
1293
|
-
label: 'Approval',
|
|
1294
|
-
category: 'core'
|
|
1295
|
-
},
|
|
1296
|
-
'wait': {
|
|
1297
|
-
sys_id: 'com.glideapp.servicenow_common.timer',
|
|
1298
|
-
name: 'Wait for Duration',
|
|
1299
|
-
label: 'Timer',
|
|
1300
|
-
category: 'core'
|
|
1301
|
-
},
|
|
1302
|
-
'timer': {
|
|
1303
|
-
sys_id: 'com.glideapp.servicenow_common.timer',
|
|
1304
|
-
name: 'Wait for Duration',
|
|
1305
|
-
label: 'Timer',
|
|
1306
|
-
category: 'core'
|
|
1307
|
-
},
|
|
1308
|
-
'delay': {
|
|
1309
|
-
sys_id: 'com.glideapp.servicenow_common.timer',
|
|
1310
|
-
name: 'Wait for Duration',
|
|
1311
|
-
label: 'Timer',
|
|
1312
|
-
category: 'core'
|
|
1313
|
-
},
|
|
1314
|
-
'log': {
|
|
1315
|
-
sys_id: 'com.glideapp.servicenow_common.log_message',
|
|
1316
|
-
name: 'Log Message',
|
|
1317
|
-
label: 'Log',
|
|
1318
|
-
category: 'core'
|
|
1319
|
-
},
|
|
1320
|
-
'logging': {
|
|
1321
|
-
sys_id: 'com.glideapp.servicenow_common.log_message',
|
|
1322
|
-
name: 'Log Message',
|
|
1323
|
-
label: 'Log',
|
|
1324
|
-
category: 'core'
|
|
1325
|
-
},
|
|
1326
|
-
'notification': {
|
|
1327
|
-
sys_id: 'com.glideapp.servicenow_common.send_email',
|
|
1328
|
-
name: 'Send Email',
|
|
1329
|
-
label: 'Email Notification',
|
|
1330
|
-
category: 'core'
|
|
1331
|
-
},
|
|
1332
|
-
'email': {
|
|
1333
|
-
sys_id: 'com.glideapp.servicenow_common.send_email',
|
|
1334
|
-
name: 'Send Email',
|
|
1335
|
-
label: 'Email Notification',
|
|
1336
|
-
category: 'core'
|
|
1337
|
-
},
|
|
1338
|
-
'update': {
|
|
1339
|
-
sys_id: 'com.glideapp.servicenow_common.update_record',
|
|
1340
|
-
name: 'Update Record',
|
|
1341
|
-
label: 'Update',
|
|
1342
|
-
category: 'core'
|
|
1343
|
-
},
|
|
1344
|
-
'record': {
|
|
1345
|
-
sys_id: 'com.glideapp.servicenow_common.update_record',
|
|
1346
|
-
name: 'Update Record',
|
|
1347
|
-
label: 'Update',
|
|
1348
|
-
category: 'core'
|
|
1349
|
-
},
|
|
1350
|
-
'field': {
|
|
1351
|
-
sys_id: 'com.glideapp.servicenow_common.update_record',
|
|
1352
|
-
name: 'Update Record',
|
|
1353
|
-
label: 'Update',
|
|
1354
|
-
category: 'core'
|
|
1355
|
-
},
|
|
1356
|
-
'create': {
|
|
1357
|
-
sys_id: 'com.glideapp.servicenow_common.create_record',
|
|
1358
|
-
name: 'Create Record',
|
|
1359
|
-
label: 'Create',
|
|
1360
|
-
category: 'core'
|
|
1361
|
-
},
|
|
1362
|
-
'task': {
|
|
1363
|
-
sys_id: 'com.glideapp.servicenow_common.create_record',
|
|
1364
|
-
name: 'Create Record',
|
|
1365
|
-
label: 'Create Task',
|
|
1366
|
-
category: 'core'
|
|
1367
|
-
}
|
|
1368
|
-
};
|
|
1369
|
-
// First, check if we have predefined actions for these terms
|
|
1370
|
-
for (const term of searchTerms) {
|
|
1371
|
-
const lowerTerm = term.toLowerCase();
|
|
1372
|
-
if (PROPER_FLOW_ACTIONS[lowerTerm]) {
|
|
1373
|
-
// Check if we already have this action to avoid duplicates
|
|
1374
|
-
const exists = allActions.some(a => a.sys_id === PROPER_FLOW_ACTIONS[lowerTerm].sys_id);
|
|
1375
|
-
if (!exists) {
|
|
1376
|
-
allActions.push(PROPER_FLOW_ACTIONS[lowerTerm]);
|
|
1377
|
-
}
|
|
1378
|
-
}
|
|
1379
|
-
}
|
|
1380
|
-
// If we found predefined actions, use those instead of searching
|
|
1381
|
-
if (allActions.length > 0) {
|
|
1382
|
-
this.logger.info(`Using predefined flow actions for terms: ${searchTerms.join(', ')}`, { count: allActions.length });
|
|
1383
|
-
return allActions;
|
|
1384
|
-
}
|
|
1385
|
-
// Otherwise, search ServiceNow but filter out nonsensical results
|
|
1386
|
-
for (const term of searchTerms) {
|
|
1387
|
-
try {
|
|
1388
|
-
const results = await this.client.searchFlowActions(term);
|
|
1389
|
-
if (results.actionTypes && results.actionTypes.length > 0) {
|
|
1390
|
-
// Filter out actions that don't make sense
|
|
1391
|
-
const filteredActions = results.actionTypes.filter((action) => {
|
|
1392
|
-
const name = (action.name || '').toLowerCase();
|
|
1393
|
-
const label = (action.label || '').toLowerCase();
|
|
1394
|
-
// Exclude actions with negative or contradictory terms
|
|
1395
|
-
const excludePatterns = [
|
|
1396
|
-
'disregard', 'ignore', 'skip', 'bypass', 'cancel', 'delete', 'remove',
|
|
1397
|
-
'reject', 'deny', 'block', 'prevent', 'stop', 'disable', 'deactivate'
|
|
1398
|
-
];
|
|
1399
|
-
const hasExcludedPattern = excludePatterns.some(pattern => name.includes(pattern) || label.includes(pattern));
|
|
1400
|
-
// Also exclude if it doesn't contain any of our search terms
|
|
1401
|
-
const containsSearchTerm = searchTerms.some(searchTerm => name.includes(searchTerm.toLowerCase()) || label.includes(searchTerm.toLowerCase()));
|
|
1402
|
-
return !hasExcludedPattern && containsSearchTerm;
|
|
1403
|
-
});
|
|
1404
|
-
allActions.push(...filteredActions);
|
|
1405
|
-
}
|
|
1406
|
-
}
|
|
1407
|
-
catch (error) {
|
|
1408
|
-
this.logger.warn(`Failed to search for flow actions with term: ${term}`, error);
|
|
1409
|
-
}
|
|
1410
|
-
}
|
|
1411
|
-
// Remove duplicates based on sys_id
|
|
1412
|
-
const uniqueActions = allActions.filter((action, index, self) => index === self.findIndex(a => a.sys_id === action.sys_id));
|
|
1413
|
-
// If still no good results, fall back to predefined actions based on first search term
|
|
1414
|
-
if (uniqueActions.length === 0 && searchTerms.length > 0) {
|
|
1415
|
-
const fallbackTerm = searchTerms[0].toLowerCase();
|
|
1416
|
-
const fallbackAction = PROPER_FLOW_ACTIONS[fallbackTerm];
|
|
1417
|
-
if (fallbackAction) {
|
|
1418
|
-
this.logger.info(`Using fallback action for term: ${fallbackTerm}`);
|
|
1419
|
-
return [fallbackAction];
|
|
1420
|
-
}
|
|
1421
|
-
}
|
|
1422
|
-
this.logger.info(`Found ${uniqueActions.length} unique flow actions for terms: ${searchTerms.join(', ')}`);
|
|
1423
|
-
return uniqueActions;
|
|
1424
|
-
}
|
|
1425
|
-
/**
|
|
1426
|
-
* Helper methods for extracting specific information - NEW METHODS
|
|
1427
|
-
*/
|
|
1428
|
-
extractNotificationSubject(instruction) {
|
|
1429
|
-
const subjectMatch = instruction.match(/subject[:\s]+["']([^"']+)["']/i);
|
|
1430
|
-
if (subjectMatch)
|
|
1431
|
-
return subjectMatch[1];
|
|
1432
|
-
const keywords = ['notify', 'alert', 'email'];
|
|
1433
|
-
for (const keyword of keywords) {
|
|
1434
|
-
if (instruction.toLowerCase().includes(keyword)) {
|
|
1435
|
-
return `Notification: ${instruction.substring(0, 50)}`;
|
|
1436
|
-
}
|
|
1437
|
-
}
|
|
1438
|
-
return 'Flow Notification';
|
|
1439
|
-
}
|
|
1440
|
-
extractNotificationMessage(instruction) {
|
|
1441
|
-
const messageMatch = instruction.match(/message[:\s]+["']([^"']+)["']/i);
|
|
1442
|
-
if (messageMatch)
|
|
1443
|
-
return messageMatch[1];
|
|
1444
|
-
return instruction;
|
|
1445
|
-
}
|
|
1446
|
-
extractFieldUpdates(instruction) {
|
|
1447
|
-
const updates = [];
|
|
1448
|
-
// Pattern: "set field to value" or "update field with value"
|
|
1449
|
-
const patterns = [
|
|
1450
|
-
/(?:set|update)\s+(\w+)\s+(?:to|with)\s+["']?([^"']+)["']?/gi,
|
|
1451
|
-
/(\w+)\s*=\s*["']?([^"']+)["']?/gi
|
|
1452
|
-
];
|
|
1453
|
-
for (const pattern of patterns) {
|
|
1454
|
-
let match;
|
|
1455
|
-
while ((match = pattern.exec(instruction)) !== null) {
|
|
1456
|
-
updates.push({
|
|
1457
|
-
field: match[1],
|
|
1458
|
-
value: match[2]
|
|
1459
|
-
});
|
|
1460
|
-
}
|
|
1461
|
-
}
|
|
1462
|
-
// Common field mappings
|
|
1463
|
-
const fieldMap = {
|
|
1464
|
-
'status': 'state',
|
|
1465
|
-
'priority': 'priority',
|
|
1466
|
-
'assignment': 'assigned_to',
|
|
1467
|
-
'description': 'short_description'
|
|
1468
|
-
};
|
|
1469
|
-
// Map common field names - add null check to prevent map() error
|
|
1470
|
-
if (!updates || updates.length === 0) {
|
|
1471
|
-
return [];
|
|
1472
|
-
}
|
|
1473
|
-
return updates.map(update => ({
|
|
1474
|
-
field: fieldMap[update.field.toLowerCase()] || update.field,
|
|
1475
|
-
value: update.value
|
|
1476
|
-
}));
|
|
1477
|
-
}
|
|
1478
|
-
extractTaskDescription(instruction) {
|
|
1479
|
-
const taskMatch = instruction.match(/task[:\s]+["']([^"']+)["']/i);
|
|
1480
|
-
if (taskMatch)
|
|
1481
|
-
return taskMatch[1];
|
|
1482
|
-
// For specific contexts
|
|
1483
|
-
const lowerInstruction = instruction.toLowerCase();
|
|
1484
|
-
if (lowerInstruction.includes('pickup')) {
|
|
1485
|
-
return 'User pickup required';
|
|
1486
|
-
}
|
|
1487
|
-
if (lowerInstruction.includes('fulfillment')) {
|
|
1488
|
-
return 'Fulfillment task';
|
|
1489
|
-
}
|
|
1490
|
-
return `Task: ${instruction.substring(0, 80)}`;
|
|
1491
|
-
}
|
|
1492
|
-
extractDuration(instruction) {
|
|
1493
|
-
const durationMatch = instruction.match(/(\d+)\s*(second|minute|hour|day)/i);
|
|
1494
|
-
if (durationMatch) {
|
|
1495
|
-
const value = parseInt(durationMatch[1]);
|
|
1496
|
-
const unit = durationMatch[2].toLowerCase();
|
|
1497
|
-
switch (unit) {
|
|
1498
|
-
case 'second': return value;
|
|
1499
|
-
case 'minute': return value * 60;
|
|
1500
|
-
case 'hour': return value * 3600;
|
|
1501
|
-
case 'day': return value * 86400;
|
|
1502
|
-
default: return 300; // 5 minutes default
|
|
1503
|
-
}
|
|
1504
|
-
}
|
|
1505
|
-
return 300; // 5 minutes default
|
|
1506
|
-
}
|
|
1507
|
-
extractLogMessage(instruction) {
|
|
1508
|
-
const logMatch = instruction.match(/log[:\s]+["']([^"']+)["']/i);
|
|
1509
|
-
if (logMatch)
|
|
1510
|
-
return logMatch[1];
|
|
1511
|
-
return `Flow action: ${instruction}`;
|
|
1512
|
-
}
|
|
1513
|
-
/**
|
|
1514
|
-
* Parse natural language instruction
|
|
1515
|
-
*/
|
|
1516
|
-
parseInstruction(instruction) {
|
|
1517
|
-
const lowerInstruction = instruction.toLowerCase();
|
|
1518
|
-
this.logger.info('Parsing instruction:', { instruction, lowerInstruction });
|
|
1519
|
-
// Enhanced flow name extraction
|
|
1520
|
-
const flowName = this.extractFlowName(instruction);
|
|
1521
|
-
// Enhanced table extraction with ServiceNow context
|
|
1522
|
-
const table = this.extractTable(instruction);
|
|
1523
|
-
// Enhanced trigger extraction
|
|
1524
|
-
const trigger = this.extractTrigger(instruction, table);
|
|
1525
|
-
// Extract workflow requirements
|
|
1526
|
-
const requirements = this.extractRequirements(instruction);
|
|
1527
|
-
// Extract entities and context
|
|
1528
|
-
const entities = this.extractEntities(instruction);
|
|
1529
|
-
const parsed = {
|
|
1530
|
-
flowName,
|
|
1531
|
-
description: instruction,
|
|
1532
|
-
table,
|
|
1533
|
-
trigger,
|
|
1534
|
-
intents: this.extractIntents(instruction),
|
|
1535
|
-
dataFlow: this.extractDataFlow(instruction),
|
|
1536
|
-
requirements,
|
|
1537
|
-
entities,
|
|
1538
|
-
flowType: this.determineFlowType(instruction),
|
|
1539
|
-
complexity: this.assessComplexity(instruction)
|
|
1540
|
-
};
|
|
1541
|
-
this.logger.info('Parsed instruction:', parsed);
|
|
1542
|
-
return parsed;
|
|
1543
|
-
}
|
|
1544
|
-
extractFlowName(instruction) {
|
|
1545
|
-
// ServiceNow flow name length limit
|
|
1546
|
-
const MAX_FLOW_NAME_LENGTH = 80;
|
|
1547
|
-
// First check for quoted flow names - more precise matching
|
|
1548
|
-
const quotedPatterns = [
|
|
1549
|
-
/flow\s+named\s+["']([^"']+)["']/i,
|
|
1550
|
-
/flow\s+called\s+["']([^"']+)["']/i,
|
|
1551
|
-
/["']([^"']+)["']\s+flow/i,
|
|
1552
|
-
/create\s+a\s+flow\s+named\s+["']([^"']+)["']/i
|
|
1553
|
-
];
|
|
1554
|
-
for (const pattern of quotedPatterns) {
|
|
1555
|
-
const match = instruction.match(pattern);
|
|
1556
|
-
if (match && match[1]) {
|
|
1557
|
-
return this.validateAndShortenFlowName(match[1].trim(), instruction);
|
|
1558
|
-
}
|
|
1559
|
-
}
|
|
1560
|
-
// Check for any remaining quoted strings as fallback
|
|
1561
|
-
const generalQuotedMatch = instruction.match(/["']([^"']+)["']/i);
|
|
1562
|
-
if (generalQuotedMatch && generalQuotedMatch[1] && generalQuotedMatch[1].toLowerCase().includes('flow')) {
|
|
1563
|
-
return this.validateAndShortenFlowName(generalQuotedMatch[1].trim(), instruction);
|
|
1564
|
-
}
|
|
1565
|
-
// Then check for 'flow named X' or 'flow called X' without quotes
|
|
1566
|
-
const namedMatch = instruction.match(/flow\s+(?:named|called)\s+([^\s]+(?:\s+[^\s]+)*?)(?:\s+that|\s+which|\s+to|\.|,|$)/i);
|
|
1567
|
-
if (namedMatch && namedMatch[1]) {
|
|
1568
|
-
return this.validateAndShortenFlowName(namedMatch[1].trim(), instruction);
|
|
1569
|
-
}
|
|
1570
|
-
// Try other patterns
|
|
1571
|
-
const patterns = [
|
|
1572
|
-
/(?:create|build|make)\s+(?:a\s+)?(.+?)\s+flow/i,
|
|
1573
|
-
/flow\s+(?:for|to)\s+(.+?)(?:\s+when|\s+if|\.|,|$)/i,
|
|
1574
|
-
/(.+?)\s+(?:workflow|process)(?:\s|$)/i
|
|
1575
|
-
];
|
|
1576
|
-
for (const pattern of patterns) {
|
|
1577
|
-
const match = instruction.match(pattern);
|
|
1578
|
-
if (match && match[1]) {
|
|
1579
|
-
return this.validateAndShortenFlowName(match[1].trim(), instruction);
|
|
1580
|
-
}
|
|
1581
|
-
}
|
|
1582
|
-
// Generate intelligent flow name from instruction content
|
|
1583
|
-
return this.generateSmartFlowName(instruction);
|
|
1584
|
-
}
|
|
1585
|
-
validateAndShortenFlowName(flowName, instruction) {
|
|
1586
|
-
const MAX_FLOW_NAME_LENGTH = 80;
|
|
1587
|
-
if (flowName.length <= MAX_FLOW_NAME_LENGTH) {
|
|
1588
|
-
return flowName;
|
|
1589
|
-
}
|
|
1590
|
-
// Warn about truncation
|
|
1591
|
-
this.logger.warn('Flow name too long, will be shortened', {
|
|
1592
|
-
original: flowName,
|
|
1593
|
-
length: flowName.length,
|
|
1594
|
-
maxLength: MAX_FLOW_NAME_LENGTH
|
|
1595
|
-
});
|
|
1596
|
-
// Try to shorten intelligently
|
|
1597
|
-
const shortened = this.shortenFlowName(flowName, instruction);
|
|
1598
|
-
this.logger.info('Flow name shortened', {
|
|
1599
|
-
original: flowName,
|
|
1600
|
-
shortened: shortened
|
|
1601
|
-
});
|
|
1602
|
-
return shortened;
|
|
1603
|
-
}
|
|
1604
|
-
shortenFlowName(flowName, instruction) {
|
|
1605
|
-
const MAX_LENGTH = 75; // Leave some buffer
|
|
1606
|
-
// If it's already short enough, return it
|
|
1607
|
-
if (flowName.length <= MAX_LENGTH) {
|
|
1608
|
-
return flowName;
|
|
1609
|
-
}
|
|
1610
|
-
// Try to extract key components
|
|
1611
|
-
const table = this.extractTable(instruction);
|
|
1612
|
-
const trigger = this.extractTriggerType(instruction);
|
|
1613
|
-
// Generate a meaningful short name
|
|
1614
|
-
if (table && trigger) {
|
|
1615
|
-
const shortName = `${this.capitalize(table)} ${this.capitalize(trigger)} Flow`;
|
|
1616
|
-
if (shortName.length <= MAX_LENGTH) {
|
|
1617
|
-
return shortName;
|
|
1618
|
-
}
|
|
1619
|
-
}
|
|
1620
|
-
// If still too long, truncate with ellipsis
|
|
1621
|
-
return flowName.substring(0, MAX_LENGTH - 3) + '...';
|
|
1622
|
-
}
|
|
1623
|
-
generateSmartFlowName(instruction) {
|
|
1624
|
-
const MAX_LENGTH = 75;
|
|
1625
|
-
const lowerInstruction = instruction.toLowerCase();
|
|
1626
|
-
// Try to extract specific flow purposes from instruction
|
|
1627
|
-
const purposePatterns = [
|
|
1628
|
-
// Approval patterns
|
|
1629
|
-
{ pattern: /monitor.*approval/i, name: 'Monitor Approval Flow' },
|
|
1630
|
-
{ pattern: /approval.*monitor/i, name: 'Monitor Approval Flow' },
|
|
1631
|
-
{ pattern: /approval.*admin/i, name: 'Admin Approval Flow' },
|
|
1632
|
-
{ pattern: /require.*approval/i, name: 'Approval Required Flow' },
|
|
1633
|
-
// Request patterns
|
|
1634
|
-
{ pattern: /catalog.*request.*monitor/i, name: 'Catalog Monitor Request Flow' },
|
|
1635
|
-
{ pattern: /service.*catalog.*request/i, name: 'Service Catalog Request Flow' },
|
|
1636
|
-
{ pattern: /equipment.*request/i, name: 'Equipment Request Flow' },
|
|
1637
|
-
{ pattern: /iphone.*provisioning/i, name: 'iPhone Provisioning Flow' },
|
|
1638
|
-
{ pattern: /mobile.*provisioning/i, name: 'Mobile Device Provisioning Flow' },
|
|
1639
|
-
// Generic patterns
|
|
1640
|
-
{ pattern: /incident.*management/i, name: 'Incident Management Flow' },
|
|
1641
|
-
{ pattern: /change.*approval/i, name: 'Change Approval Flow' },
|
|
1642
|
-
{ pattern: /user.*onboarding/i, name: 'User Onboarding Flow' },
|
|
1643
|
-
{ pattern: /password.*reset/i, name: 'Password Reset Flow' }
|
|
1644
|
-
];
|
|
1645
|
-
// Check for specific patterns first
|
|
1646
|
-
for (const { pattern, name } of purposePatterns) {
|
|
1647
|
-
if (pattern.test(instruction)) {
|
|
1648
|
-
return name;
|
|
1649
|
-
}
|
|
1650
|
-
}
|
|
1651
|
-
// Extract key components from instruction
|
|
1652
|
-
const table = this.extractTable(instruction);
|
|
1653
|
-
const trigger = this.extractTriggerType(instruction);
|
|
1654
|
-
const action = this.extractPrimaryAction(instruction);
|
|
1655
|
-
// Build smart flow name with better formatting
|
|
1656
|
-
let flowName = '';
|
|
1657
|
-
// Handle table names better
|
|
1658
|
-
if (table) {
|
|
1659
|
-
const tableDisplayNames = {
|
|
1660
|
-
'sc_request': 'Service Request',
|
|
1661
|
-
'sc_req_item': 'Catalog Item',
|
|
1662
|
-
'incident': 'Incident',
|
|
1663
|
-
'problem': 'Problem',
|
|
1664
|
-
'change_request': 'Change Request',
|
|
1665
|
-
'task': 'Task',
|
|
1666
|
-
'sys_user': 'User',
|
|
1667
|
-
'cmdb_ci': 'Configuration Item'
|
|
1668
|
-
};
|
|
1669
|
-
flowName += tableDisplayNames[table] || this.capitalize(table);
|
|
1670
|
-
flowName += ' ';
|
|
1671
|
-
}
|
|
1672
|
-
// Add trigger description if meaningful
|
|
1673
|
-
if (trigger && trigger !== 'Create') {
|
|
1674
|
-
flowName += this.capitalize(trigger) + ' ';
|
|
1675
|
-
}
|
|
1676
|
-
// Extract meaningful action from instruction
|
|
1677
|
-
if (lowerInstruction.includes('approval')) {
|
|
1678
|
-
flowName += 'Approval ';
|
|
1679
|
-
}
|
|
1680
|
-
else if (lowerInstruction.includes('notification')) {
|
|
1681
|
-
flowName += 'Notification ';
|
|
1682
|
-
}
|
|
1683
|
-
else if (lowerInstruction.includes('fulfillment')) {
|
|
1684
|
-
flowName += 'Fulfillment ';
|
|
1685
|
-
}
|
|
1686
|
-
else if (action && flowName.length + action.length < MAX_LENGTH - 5) {
|
|
1687
|
-
flowName += action + ' ';
|
|
1688
|
-
}
|
|
1689
|
-
flowName += 'Flow';
|
|
1690
|
-
// If no meaningful name generated, create a descriptive timestamp-based name
|
|
1691
|
-
if (flowName === 'Flow' || flowName.trim() === 'Flow') {
|
|
1692
|
-
const timestamp = new Date().toISOString().split('T')[0];
|
|
1693
|
-
flowName = `Automated Flow ${timestamp}`;
|
|
1694
|
-
}
|
|
1695
|
-
// Ensure name doesn't exceed max length
|
|
1696
|
-
if (flowName.length > MAX_LENGTH) {
|
|
1697
|
-
flowName = flowName.substring(0, MAX_LENGTH - 3) + '...';
|
|
1698
|
-
}
|
|
1699
|
-
return flowName.trim();
|
|
1700
|
-
}
|
|
1701
|
-
extractTriggerType(instruction) {
|
|
1702
|
-
const lowerInstruction = instruction.toLowerCase();
|
|
1703
|
-
if (lowerInstruction.includes('create') || lowerInstruction.includes('new')) {
|
|
1704
|
-
return 'Create';
|
|
1705
|
-
}
|
|
1706
|
-
else if (lowerInstruction.includes('update') || lowerInstruction.includes('change')) {
|
|
1707
|
-
return 'Update';
|
|
1708
|
-
}
|
|
1709
|
-
else if (lowerInstruction.includes('delete') || lowerInstruction.includes('remove')) {
|
|
1710
|
-
return 'Delete';
|
|
1711
|
-
}
|
|
1712
|
-
else if (lowerInstruction.includes('approve') || lowerInstruction.includes('approval')) {
|
|
1713
|
-
return 'Approval';
|
|
1714
|
-
}
|
|
1715
|
-
else if (lowerInstruction.includes('schedule')) {
|
|
1716
|
-
return 'Scheduled';
|
|
1717
|
-
}
|
|
1718
|
-
return '';
|
|
1719
|
-
}
|
|
1720
|
-
extractPrimaryAction(instruction) {
|
|
1721
|
-
const actionPatterns = [
|
|
1722
|
-
/(?:to|will|should)\s+(\w+)/i,
|
|
1723
|
-
/(?:for)\s+(\w+ing)/i,
|
|
1724
|
-
/(\w+ing)\s+(?:the|a|an)/i
|
|
1725
|
-
];
|
|
1726
|
-
for (const pattern of actionPatterns) {
|
|
1727
|
-
const match = instruction.match(pattern);
|
|
1728
|
-
if (match && match[1]) {
|
|
1729
|
-
return match[1];
|
|
1730
|
-
}
|
|
1731
|
-
}
|
|
1732
|
-
return '';
|
|
1733
|
-
}
|
|
1734
|
-
capitalize(str) {
|
|
1735
|
-
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
|
|
1736
|
-
}
|
|
1737
|
-
extractTable(instruction) {
|
|
1738
|
-
const lowerInstruction = instruction.toLowerCase();
|
|
1739
|
-
// ServiceNow table mapping
|
|
1740
|
-
const tableMap = {
|
|
1741
|
-
'incident': 'incident',
|
|
1742
|
-
'request': 'sc_request',
|
|
1743
|
-
'service request': 'sc_request',
|
|
1744
|
-
'catalog': 'sc_req_item',
|
|
1745
|
-
'catalog item': 'sc_req_item',
|
|
1746
|
-
'problem': 'problem',
|
|
1747
|
-
'change': 'change_request',
|
|
1748
|
-
'change request': 'change_request',
|
|
1749
|
-
'task': 'task',
|
|
1750
|
-
'user': 'sys_user',
|
|
1751
|
-
'cmdb': 'cmdb_ci',
|
|
1752
|
-
'configuration item': 'cmdb_ci'
|
|
1753
|
-
};
|
|
1754
|
-
// Check for explicit table mentions
|
|
1755
|
-
for (const [key, value] of Object.entries(tableMap)) {
|
|
1756
|
-
if (lowerInstruction.includes(key)) {
|
|
1757
|
-
return value;
|
|
1758
|
-
}
|
|
1759
|
-
}
|
|
1760
|
-
// Check for catalog/fulfillment context
|
|
1761
|
-
if (lowerInstruction.includes('order') || lowerInstruction.includes('fulfillment') ||
|
|
1762
|
-
lowerInstruction.includes('catalog') || lowerInstruction.includes('iphone')) {
|
|
1763
|
-
return 'sc_req_item';
|
|
1764
|
-
}
|
|
1765
|
-
// Check for approval context
|
|
1766
|
-
if (lowerInstruction.includes('approval') || lowerInstruction.includes('approve')) {
|
|
1767
|
-
return 'sc_request';
|
|
1768
|
-
}
|
|
1769
|
-
// Default fallback based on flow type
|
|
1770
|
-
if (lowerInstruction.includes('flow') || lowerInstruction.includes('process')) {
|
|
1771
|
-
return 'task'; // Generic task table for workflows
|
|
1772
|
-
}
|
|
1773
|
-
return 'task'; // Changed from 'incident' to more neutral 'task'
|
|
1774
|
-
}
|
|
1775
|
-
extractTrigger(instruction, table) {
|
|
1776
|
-
const lowerInstruction = instruction.toLowerCase();
|
|
1777
|
-
let triggerType = 'record_created';
|
|
1778
|
-
let condition = '';
|
|
1779
|
-
// Determine trigger type
|
|
1780
|
-
// IMPORTANT: "new" or "created" should always be record_created
|
|
1781
|
-
if (lowerInstruction.includes('when new') ||
|
|
1782
|
-
lowerInstruction.includes('when a new') ||
|
|
1783
|
-
lowerInstruction.includes('is created') ||
|
|
1784
|
-
lowerInstruction.includes('new request') ||
|
|
1785
|
-
lowerInstruction.includes('new catalog') ||
|
|
1786
|
-
lowerInstruction.includes('new service')) {
|
|
1787
|
-
triggerType = 'record_created';
|
|
1788
|
-
}
|
|
1789
|
-
else if ((lowerInstruction.includes('update') || lowerInstruction.includes('change')) &&
|
|
1790
|
-
!lowerInstruction.includes('update the request status') &&
|
|
1791
|
-
!lowerInstruction.includes('update status')) {
|
|
1792
|
-
// Don't treat "update the request status" as a trigger, it's an action
|
|
1793
|
-
triggerType = 'record_updated';
|
|
1794
|
-
}
|
|
1795
|
-
else if (lowerInstruction.includes('delete')) {
|
|
1796
|
-
triggerType = 'record_deleted';
|
|
1797
|
-
}
|
|
1798
|
-
else if (lowerInstruction.includes('schedule')) {
|
|
1799
|
-
triggerType = 'scheduled';
|
|
1800
|
-
}
|
|
1801
|
-
// Extract specific conditions
|
|
1802
|
-
if (lowerInstruction.includes('iphone 6')) {
|
|
1803
|
-
condition = 'cat_item.name=iPhone 6';
|
|
1804
|
-
}
|
|
1805
|
-
else if (lowerInstruction.includes('high priority')) {
|
|
1806
|
-
condition = 'priority=1';
|
|
1807
|
-
}
|
|
1808
|
-
else if (lowerInstruction.includes('critical')) {
|
|
1809
|
-
condition = 'priority=1^severity=1';
|
|
1810
|
-
}
|
|
1811
|
-
return {
|
|
1812
|
-
type: triggerType,
|
|
1813
|
-
table,
|
|
1814
|
-
condition
|
|
1815
|
-
};
|
|
1816
|
-
}
|
|
1817
|
-
extractRequirements(instruction) {
|
|
1818
|
-
const requirements = [];
|
|
1819
|
-
const lowerInstruction = instruction.toLowerCase();
|
|
1820
|
-
// Extract sequential requirements
|
|
1821
|
-
if (lowerInstruction.includes('approval')) {
|
|
1822
|
-
requirements.push('approval_required');
|
|
1823
|
-
}
|
|
1824
|
-
if (lowerInstruction.includes('task')) {
|
|
1825
|
-
requirements.push('task_creation');
|
|
1826
|
-
}
|
|
1827
|
-
if (lowerInstruction.includes('notification')) {
|
|
1828
|
-
requirements.push('notification_required');
|
|
1829
|
-
}
|
|
1830
|
-
if (lowerInstruction.includes('admin')) {
|
|
1831
|
-
requirements.push('admin_assignment');
|
|
1832
|
-
}
|
|
1833
|
-
if (lowerInstruction.includes('pickup')) {
|
|
1834
|
-
requirements.push('pickup_scheduling');
|
|
1835
|
-
}
|
|
1836
|
-
if (lowerInstruction.includes('complete')) {
|
|
1837
|
-
requirements.push('completion_tracking');
|
|
1838
|
-
}
|
|
1839
|
-
return requirements;
|
|
1840
|
-
}
|
|
1841
|
-
extractEntities(instruction) {
|
|
1842
|
-
const entities = {};
|
|
1843
|
-
const lowerInstruction = instruction.toLowerCase();
|
|
1844
|
-
// Extract product entities
|
|
1845
|
-
if (lowerInstruction.includes('iphone')) {
|
|
1846
|
-
entities.product = 'iPhone';
|
|
1847
|
-
if (lowerInstruction.includes('iphone 6')) {
|
|
1848
|
-
entities.product_model = 'iPhone 6';
|
|
1849
|
-
}
|
|
1850
|
-
}
|
|
1851
|
-
// Extract role entities
|
|
1852
|
-
if (lowerInstruction.includes('admin')) {
|
|
1853
|
-
entities.admin_role = true;
|
|
1854
|
-
}
|
|
1855
|
-
if (lowerInstruction.includes('user')) {
|
|
1856
|
-
entities.user_role = true;
|
|
1857
|
-
}
|
|
1858
|
-
// Extract action entities
|
|
1859
|
-
if (lowerInstruction.includes('pickup')) {
|
|
1860
|
-
entities.pickup_action = true;
|
|
1861
|
-
}
|
|
1862
|
-
if (lowerInstruction.includes('fulfillment')) {
|
|
1863
|
-
entities.fulfillment_process = true;
|
|
1864
|
-
}
|
|
1865
|
-
return entities;
|
|
1866
|
-
}
|
|
1867
|
-
determineFlowType(instruction) {
|
|
1868
|
-
const lowerInstruction = instruction.toLowerCase();
|
|
1869
|
-
if (lowerInstruction.includes('approval')) {
|
|
1870
|
-
return 'approval_workflow';
|
|
1871
|
-
}
|
|
1872
|
-
if (lowerInstruction.includes('fulfillment') || lowerInstruction.includes('order')) {
|
|
1873
|
-
return 'fulfillment_workflow';
|
|
1874
|
-
}
|
|
1875
|
-
if (lowerInstruction.includes('incident')) {
|
|
1876
|
-
return 'incident_workflow';
|
|
1877
|
-
}
|
|
1878
|
-
if (lowerInstruction.includes('notification')) {
|
|
1879
|
-
return 'notification_workflow';
|
|
1880
|
-
}
|
|
1881
|
-
return 'general_workflow';
|
|
1882
|
-
}
|
|
1883
|
-
assessComplexity(instruction) {
|
|
1884
|
-
const lowerInstruction = instruction.toLowerCase();
|
|
1885
|
-
let complexity = 0;
|
|
1886
|
-
// Count complexity indicators
|
|
1887
|
-
if (lowerInstruction.includes('approval'))
|
|
1888
|
-
complexity += 2;
|
|
1889
|
-
if (lowerInstruction.includes('task'))
|
|
1890
|
-
complexity += 1;
|
|
1891
|
-
if (lowerInstruction.includes('notification'))
|
|
1892
|
-
complexity += 1;
|
|
1893
|
-
if (lowerInstruction.includes('admin'))
|
|
1894
|
-
complexity += 1;
|
|
1895
|
-
if (lowerInstruction.includes('user'))
|
|
1896
|
-
complexity += 1;
|
|
1897
|
-
if (lowerInstruction.includes('pickup'))
|
|
1898
|
-
complexity += 2;
|
|
1899
|
-
if (lowerInstruction.includes('schedule'))
|
|
1900
|
-
complexity += 2;
|
|
1901
|
-
if (complexity >= 5)
|
|
1902
|
-
return 'high';
|
|
1903
|
-
if (complexity >= 3)
|
|
1904
|
-
return 'medium';
|
|
1905
|
-
return 'low';
|
|
1906
|
-
}
|
|
1907
|
-
/**
|
|
1908
|
-
* Create activities based on parsed requirements
|
|
1909
|
-
*/
|
|
1910
|
-
async createActivitiesFromRequirements(parsedInstruction) {
|
|
1911
|
-
const activities = [];
|
|
1912
|
-
const requirements = parsedInstruction.requirements;
|
|
1913
|
-
const entities = parsedInstruction.entities;
|
|
1914
|
-
const flowType = parsedInstruction.flowType;
|
|
1915
|
-
this.logger.info('Creating activities from requirements:', { requirements, entities, flowType });
|
|
1916
|
-
// Create activities based on flow type and requirements
|
|
1917
|
-
if (flowType === 'fulfillment_workflow') {
|
|
1918
|
-
activities.push(...this.createFulfillmentActivities(requirements, entities));
|
|
1919
|
-
}
|
|
1920
|
-
else if (flowType === 'approval_workflow') {
|
|
1921
|
-
activities.push(...this.createApprovalActivities(requirements, entities));
|
|
1922
|
-
}
|
|
1923
|
-
else if (flowType === 'incident_workflow') {
|
|
1924
|
-
activities.push(...this.createIncidentActivities(requirements, entities));
|
|
1925
|
-
}
|
|
1926
|
-
else {
|
|
1927
|
-
activities.push(...this.createGeneralActivities(requirements, entities));
|
|
1928
|
-
}
|
|
1929
|
-
return activities;
|
|
1930
|
-
}
|
|
1931
|
-
createFulfillmentActivities(requirements, entities) {
|
|
1932
|
-
const activities = [];
|
|
1933
|
-
let activityIndex = 1;
|
|
1934
|
-
// Always start with validation for fulfillment
|
|
1935
|
-
activities.push({
|
|
1936
|
-
id: `activity_${activityIndex++}`,
|
|
1937
|
-
type: 'condition',
|
|
1938
|
-
name: 'Validate Request',
|
|
1939
|
-
condition: entities.product_model ? `cat_item.name=${entities.product_model}` : 'state=1',
|
|
1940
|
-
inputs: {
|
|
1941
|
-
record: '${trigger.record}',
|
|
1942
|
-
validation_rules: ['product_available', 'user_authorized']
|
|
1943
|
-
},
|
|
1944
|
-
outputs: {
|
|
1945
|
-
validation_result: '${step.validation_result}',
|
|
1946
|
-
next_step: '${step.next_step}'
|
|
1947
|
-
}
|
|
1948
|
-
});
|
|
1949
|
-
// Add approval if required
|
|
1950
|
-
if (requirements.includes('approval_required')) {
|
|
1951
|
-
activities.push({
|
|
1952
|
-
id: `activity_${activityIndex++}`,
|
|
1953
|
-
type: 'approval',
|
|
1954
|
-
name: 'Manager Approval',
|
|
1955
|
-
approval_type: 'manager',
|
|
1956
|
-
inputs: {
|
|
1957
|
-
record: '${trigger.record}',
|
|
1958
|
-
approval_group: 'managers',
|
|
1959
|
-
timeout: '48 hours'
|
|
1960
|
-
},
|
|
1961
|
-
outputs: {
|
|
1962
|
-
approval_result: '${step.approval_result}',
|
|
1963
|
-
approved_by: '${step.approved_by}'
|
|
1964
|
-
}
|
|
1965
|
-
});
|
|
1966
|
-
}
|
|
1967
|
-
// Add task creation for admin
|
|
1968
|
-
if (requirements.includes('admin_assignment')) {
|
|
1969
|
-
activities.push({
|
|
1970
|
-
id: `activity_${activityIndex++}`,
|
|
1971
|
-
type: 'create_task',
|
|
1972
|
-
name: 'Create Admin Task',
|
|
1973
|
-
task_type: 'admin_pickup',
|
|
1974
|
-
inputs: {
|
|
1975
|
-
parent_record: '${trigger.record}',
|
|
1976
|
-
assigned_to: 'admin_group',
|
|
1977
|
-
short_description: entities.product_model ? `Prepare ${entities.product_model} for pickup` : 'Prepare item for pickup',
|
|
1978
|
-
priority: '2'
|
|
1979
|
-
},
|
|
1980
|
-
outputs: {
|
|
1981
|
-
task_id: '${step.task_id}',
|
|
1982
|
-
task_state: '${step.task_state}'
|
|
1983
|
-
}
|
|
1984
|
-
});
|
|
1985
|
-
}
|
|
1986
|
-
// Add task creation for user
|
|
1987
|
-
if (requirements.includes('pickup_scheduling')) {
|
|
1988
|
-
activities.push({
|
|
1989
|
-
id: `activity_${activityIndex++}`,
|
|
1990
|
-
type: 'create_task',
|
|
1991
|
-
name: 'Create User Pickup Task',
|
|
1992
|
-
task_type: 'user_pickup',
|
|
1993
|
-
inputs: {
|
|
1994
|
-
parent_record: '${trigger.record}',
|
|
1995
|
-
assigned_to: '${trigger.record.requested_for}',
|
|
1996
|
-
short_description: entities.product_model ? `Pick up your ${entities.product_model}` : 'Pick up your item',
|
|
1997
|
-
priority: '3'
|
|
1998
|
-
},
|
|
1999
|
-
outputs: {
|
|
2000
|
-
pickup_task_id: '${step.pickup_task_id}',
|
|
2001
|
-
pickup_scheduled: '${step.pickup_scheduled}'
|
|
2002
|
-
}
|
|
2003
|
-
});
|
|
2004
|
-
}
|
|
2005
|
-
// Add notifications
|
|
2006
|
-
if (requirements.includes('notification_required')) {
|
|
2007
|
-
activities.push({
|
|
2008
|
-
id: `activity_${activityIndex++}`,
|
|
2009
|
-
type: 'notification',
|
|
2010
|
-
name: 'Send Notification',
|
|
2011
|
-
notification_type: 'email',
|
|
2012
|
-
inputs: {
|
|
2013
|
-
recipient: '${trigger.record.requested_for}',
|
|
2014
|
-
subject: entities.product_model ? `Your ${entities.product_model} is ready for pickup` : 'Your item is ready for pickup',
|
|
2015
|
-
message: 'Please visit the admin desk to collect your item.'
|
|
2016
|
-
},
|
|
2017
|
-
outputs: {
|
|
2018
|
-
notification_sent: '${step.notification_sent}'
|
|
2019
|
-
}
|
|
2020
|
-
});
|
|
2021
|
-
}
|
|
2022
|
-
// Add completion tracking
|
|
2023
|
-
if (requirements.includes('completion_tracking')) {
|
|
2024
|
-
activities.push({
|
|
2025
|
-
id: `activity_${activityIndex++}`,
|
|
2026
|
-
type: 'update_record',
|
|
2027
|
-
name: 'Update Request Status',
|
|
2028
|
-
inputs: {
|
|
2029
|
-
record: '${trigger.record}',
|
|
2030
|
-
updates: {
|
|
2031
|
-
state: '3',
|
|
2032
|
-
completion_date: '${gs.now()}'
|
|
2033
|
-
}
|
|
2034
|
-
},
|
|
2035
|
-
outputs: {
|
|
2036
|
-
updated_record: '${step.updated_record}'
|
|
2037
|
-
}
|
|
2038
|
-
});
|
|
2039
|
-
}
|
|
2040
|
-
return activities;
|
|
2041
|
-
}
|
|
2042
|
-
createApprovalActivities(requirements, entities) {
|
|
2043
|
-
const activities = [];
|
|
2044
|
-
let activityIndex = 1;
|
|
2045
|
-
// Create approval activity
|
|
2046
|
-
activities.push({
|
|
2047
|
-
id: `activity_${activityIndex++}`,
|
|
2048
|
-
type: 'approval',
|
|
2049
|
-
name: 'Request Approval',
|
|
2050
|
-
approval_type: 'sequential',
|
|
2051
|
-
inputs: {
|
|
2052
|
-
record: '${trigger.record}',
|
|
2053
|
-
approval_group: 'approvers',
|
|
2054
|
-
timeout: '24 hours'
|
|
2055
|
-
},
|
|
2056
|
-
outputs: {
|
|
2057
|
-
approval_result: '${step.approval_result}',
|
|
2058
|
-
approved_by: '${step.approved_by}'
|
|
2059
|
-
}
|
|
2060
|
-
});
|
|
2061
|
-
// Add conditional logic for approval result
|
|
2062
|
-
activities.push({
|
|
2063
|
-
id: `activity_${activityIndex++}`,
|
|
2064
|
-
type: 'condition',
|
|
2065
|
-
name: 'Check Approval Result',
|
|
2066
|
-
condition: 'approval_result=approved',
|
|
2067
|
-
inputs: {
|
|
2068
|
-
approval_result: '${activity_1.approval_result}'
|
|
2069
|
-
},
|
|
2070
|
-
outputs: {
|
|
2071
|
-
proceed: '${step.proceed}'
|
|
2072
|
-
}
|
|
2073
|
-
});
|
|
2074
|
-
return activities;
|
|
2075
|
-
}
|
|
2076
|
-
createIncidentActivities(requirements, entities) {
|
|
2077
|
-
const activities = [];
|
|
2078
|
-
let activityIndex = 1;
|
|
2079
|
-
// Create incident assignment
|
|
2080
|
-
activities.push({
|
|
2081
|
-
id: `activity_${activityIndex++}`,
|
|
2082
|
-
type: 'assignment',
|
|
2083
|
-
name: 'Assign Incident',
|
|
2084
|
-
inputs: {
|
|
2085
|
-
record: '${trigger.record}',
|
|
2086
|
-
assignment_group: 'it_support',
|
|
2087
|
-
priority: '${trigger.record.priority}'
|
|
2088
|
-
},
|
|
2089
|
-
outputs: {
|
|
2090
|
-
assigned_to: '${step.assigned_to}',
|
|
2091
|
-
assignment_group: '${step.assignment_group}'
|
|
2092
|
-
}
|
|
2093
|
-
});
|
|
2094
|
-
return activities;
|
|
2095
|
-
}
|
|
2096
|
-
createGeneralActivities(requirements, entities) {
|
|
2097
|
-
const activities = [];
|
|
2098
|
-
let activityIndex = 1;
|
|
2099
|
-
// Create basic processing activity
|
|
2100
|
-
activities.push({
|
|
2101
|
-
id: `activity_${activityIndex++}`,
|
|
2102
|
-
type: 'script',
|
|
2103
|
-
name: 'Process Request',
|
|
2104
|
-
inputs: {
|
|
2105
|
-
record: '${trigger.record}'
|
|
2106
|
-
},
|
|
2107
|
-
outputs: {
|
|
2108
|
-
result: '${step.result}'
|
|
2109
|
-
}
|
|
2110
|
-
});
|
|
2111
|
-
return activities;
|
|
2112
|
-
}
|
|
2113
|
-
createWorkflowConnections(activities) {
|
|
2114
|
-
const connections = [];
|
|
2115
|
-
// Create sequential connections between activities
|
|
2116
|
-
for (let i = 0; i < activities.length - 1; i++) {
|
|
2117
|
-
connections.push({
|
|
2118
|
-
from: activities[i].id,
|
|
2119
|
-
to: activities[i + 1].id
|
|
2120
|
-
});
|
|
2121
|
-
}
|
|
2122
|
-
// Add conditional connections if needed
|
|
2123
|
-
for (const activity of activities) {
|
|
2124
|
-
if (activity.type === 'condition') {
|
|
2125
|
-
// Add conditional branches (simplified)
|
|
2126
|
-
connections.push({
|
|
2127
|
-
from: activity.id,
|
|
2128
|
-
to: 'end',
|
|
2129
|
-
condition: 'false'
|
|
2130
|
-
});
|
|
2131
|
-
}
|
|
2132
|
-
}
|
|
2133
|
-
return connections;
|
|
2134
|
-
}
|
|
2135
|
-
/**
|
|
2136
|
-
* Extract intents from instruction
|
|
2137
|
-
*/
|
|
2138
|
-
extractIntents(instruction) {
|
|
2139
|
-
const intents = [];
|
|
2140
|
-
const lowerInstruction = instruction.toLowerCase();
|
|
2141
|
-
// ServiceNow-specific intents
|
|
2142
|
-
if (lowerInstruction.includes('approve') || lowerInstruction.includes('approval')) {
|
|
2143
|
-
intents.push('approval');
|
|
2144
|
-
}
|
|
2145
|
-
if (lowerInstruction.includes('task') || lowerInstruction.includes('assign')) {
|
|
2146
|
-
intents.push('task_creation');
|
|
2147
|
-
}
|
|
2148
|
-
if (lowerInstruction.includes('notify') || lowerInstruction.includes('notification')) {
|
|
2149
|
-
intents.push('notification');
|
|
2150
|
-
}
|
|
2151
|
-
if (lowerInstruction.includes('pickup') || lowerInstruction.includes('schedule')) {
|
|
2152
|
-
intents.push('scheduling');
|
|
2153
|
-
}
|
|
2154
|
-
if (lowerInstruction.includes('fulfillment') || lowerInstruction.includes('order')) {
|
|
2155
|
-
intents.push('fulfillment');
|
|
2156
|
-
}
|
|
2157
|
-
if (lowerInstruction.includes('catalog') || lowerInstruction.includes('item')) {
|
|
2158
|
-
intents.push('catalog_management');
|
|
2159
|
-
}
|
|
2160
|
-
if (lowerInstruction.includes('admin') || lowerInstruction.includes('administrator')) {
|
|
2161
|
-
intents.push('admin_process');
|
|
2162
|
-
}
|
|
2163
|
-
if (lowerInstruction.includes('user') || lowerInstruction.includes('customer')) {
|
|
2164
|
-
intents.push('user_process');
|
|
2165
|
-
}
|
|
2166
|
-
if (lowerInstruction.includes('complete') || lowerInstruction.includes('finish')) {
|
|
2167
|
-
intents.push('completion');
|
|
2168
|
-
}
|
|
2169
|
-
if (lowerInstruction.includes('escalate') || lowerInstruction.includes('escalation')) {
|
|
2170
|
-
intents.push('escalation');
|
|
2171
|
-
}
|
|
2172
|
-
if (lowerInstruction.includes('validate') || lowerInstruction.includes('validation')) {
|
|
2173
|
-
intents.push('validation');
|
|
2174
|
-
}
|
|
2175
|
-
// Legacy intents (remove these problematic ones)
|
|
2176
|
-
// if (lowerInstruction.includes('translate')) intents.push('translation');
|
|
2177
|
-
return intents;
|
|
2178
|
-
}
|
|
2179
|
-
/**
|
|
2180
|
-
* Extract data flow from instruction
|
|
2181
|
-
*/
|
|
2182
|
-
extractDataFlow(instruction) {
|
|
2183
|
-
const flow = [];
|
|
2184
|
-
const lowerInstruction = instruction.toLowerCase();
|
|
2185
|
-
// Extract logical flow based on content
|
|
2186
|
-
if (lowerInstruction.includes('trigger') || lowerInstruction.includes('when')) {
|
|
2187
|
-
flow.push('Trigger Event');
|
|
2188
|
-
}
|
|
2189
|
-
if (lowerInstruction.includes('approval')) {
|
|
2190
|
-
flow.push('Approval Process');
|
|
2191
|
-
}
|
|
2192
|
-
if (lowerInstruction.includes('task') || lowerInstruction.includes('admin')) {
|
|
2193
|
-
flow.push('Task Creation');
|
|
2194
|
-
}
|
|
2195
|
-
if (lowerInstruction.includes('pickup')) {
|
|
2196
|
-
flow.push('Pickup Scheduling');
|
|
2197
|
-
}
|
|
2198
|
-
if (lowerInstruction.includes('notification')) {
|
|
2199
|
-
flow.push('Send Notification');
|
|
2200
|
-
}
|
|
2201
|
-
if (lowerInstruction.includes('complete')) {
|
|
2202
|
-
flow.push('Complete Process');
|
|
2203
|
-
}
|
|
2204
|
-
// If no specific flow detected, use generic
|
|
2205
|
-
if (flow.length === 0) {
|
|
2206
|
-
flow.push('Process Initiated');
|
|
2207
|
-
flow.push('Execute Actions');
|
|
2208
|
-
flow.push('Process Complete');
|
|
2209
|
-
}
|
|
2210
|
-
return flow;
|
|
2211
|
-
}
|
|
2212
|
-
/**
|
|
2213
|
-
* Extract flow variables from activities
|
|
2214
|
-
*/
|
|
2215
|
-
extractFlowVariables(activities) {
|
|
2216
|
-
const variables = [];
|
|
2217
|
-
const seen = new Set();
|
|
2218
|
-
// Add common flow variables
|
|
2219
|
-
variables.push({
|
|
2220
|
-
name: 'current_record',
|
|
2221
|
-
type: 'reference',
|
|
2222
|
-
description: 'The current record being processed'
|
|
2223
|
-
});
|
|
2224
|
-
// Extract variables from activity outputs
|
|
2225
|
-
activities.forEach(activity => {
|
|
2226
|
-
// Check if activity has artifact with outputs
|
|
2227
|
-
if (activity.artifact && activity.artifact.outputs) {
|
|
2228
|
-
activity.artifact.outputs.forEach((output) => {
|
|
2229
|
-
const varName = `${activity.id}_${output.name}`;
|
|
2230
|
-
if (!seen.has(varName)) {
|
|
2231
|
-
seen.add(varName);
|
|
2232
|
-
variables.push({
|
|
2233
|
-
name: varName,
|
|
2234
|
-
type: output.type,
|
|
2235
|
-
description: `Output from ${activity.name}: ${output.description}`
|
|
2236
|
-
});
|
|
2237
|
-
}
|
|
2238
|
-
});
|
|
2239
|
-
}
|
|
2240
|
-
else if (activity.outputs) {
|
|
2241
|
-
// Handle activities without artifacts but with outputs
|
|
2242
|
-
Object.entries(activity.outputs).forEach(([key, value]) => {
|
|
2243
|
-
const varName = key.replace('${step.', '').replace('}', '');
|
|
2244
|
-
if (!seen.has(varName)) {
|
|
2245
|
-
seen.add(varName);
|
|
2246
|
-
variables.push({
|
|
2247
|
-
name: varName,
|
|
2248
|
-
type: 'string',
|
|
2249
|
-
description: `Output from ${activity.name}`
|
|
2250
|
-
});
|
|
2251
|
-
}
|
|
2252
|
-
});
|
|
2253
|
-
}
|
|
2254
|
-
});
|
|
2255
|
-
return variables;
|
|
2256
|
-
}
|
|
2257
|
-
/**
|
|
2258
|
-
* Deploy a flow to ServiceNow - ENHANCED WITH GLOBAL SCOPE STRATEGY
|
|
2259
|
-
*/
|
|
2260
|
-
async deployFlow(flowData) {
|
|
2261
|
-
this.logger.info('Deploying flow with global scope strategy', { name: flowData.flowStructure.name });
|
|
2262
|
-
try {
|
|
2263
|
-
// Use the simplified flow structure directly
|
|
2264
|
-
const flowDefinition = flowData.flowStructure;
|
|
2265
|
-
// 🔧 CRITICAL FIX: Respect the analysis.recommendedType for correct flow type
|
|
2266
|
-
// Map analysis types to ServiceNow flow types
|
|
2267
|
-
let serviceNowFlowType = 'flow'; // default
|
|
2268
|
-
if (flowData.decisionAnalysis?.recommendedType) {
|
|
2269
|
-
const recommendedType = flowData.decisionAnalysis.recommendedType.toLowerCase();
|
|
2270
|
-
if (recommendedType === 'main_flow' || recommendedType === 'flow' || recommendedType === 'triggered_flow') {
|
|
2271
|
-
serviceNowFlowType = 'flow';
|
|
2272
|
-
}
|
|
2273
|
-
else if (recommendedType === 'subflow' || recommendedType === 'reusable_flow') {
|
|
2274
|
-
serviceNowFlowType = 'subflow';
|
|
2275
|
-
}
|
|
2276
|
-
else if (recommendedType === 'action' || recommendedType === 'flow_action') {
|
|
2277
|
-
serviceNowFlowType = 'action';
|
|
2278
|
-
}
|
|
2279
|
-
}
|
|
2280
|
-
// Create deployment context for scope management
|
|
2281
|
-
const deploymentContext = {
|
|
2282
|
-
artifactType: 'flow',
|
|
2283
|
-
artifactData: {
|
|
2284
|
-
name: flowDefinition.name,
|
|
2285
|
-
description: flowDefinition.description,
|
|
2286
|
-
table: flowDefinition.table,
|
|
2287
|
-
trigger_type: flowDefinition.trigger?.type || flowDefinition.trigger_type,
|
|
2288
|
-
condition: flowDefinition.trigger?.condition || flowDefinition.condition || '',
|
|
2289
|
-
active: flowDefinition.active !== false,
|
|
2290
|
-
category: flowDefinition.category || 'custom',
|
|
2291
|
-
activities: flowDefinition.activities || [],
|
|
2292
|
-
// 🔧 CRITICAL: Pass the correct flow type based on analysis
|
|
2293
|
-
flow_type: serviceNowFlowType,
|
|
2294
|
-
// Additional metadata for scope decision
|
|
2295
|
-
complexity: this.assessFlowComplexity(flowDefinition),
|
|
2296
|
-
crossApplicationIntegration: this.hasCrossApplicationIntegration(flowDefinition),
|
|
2297
|
-
sharedUtility: this.isSharedUtility(flowDefinition)
|
|
2298
|
-
},
|
|
2299
|
-
environmentType: process.env.NODE_ENV === 'production' ? 'production' : 'development'
|
|
2300
|
-
};
|
|
2301
|
-
// Use scope manager to deploy with optimal scope
|
|
2302
|
-
const deploymentResult = await this.scopeManager.deployWithScopeManagement(deploymentContext);
|
|
2303
|
-
if (!deploymentResult.success) {
|
|
2304
|
-
throw new Error(deploymentResult.message || 'Failed to deploy flow');
|
|
2305
|
-
}
|
|
2306
|
-
// Log deployment details with scope information
|
|
2307
|
-
this.logger.info('Flow deployed successfully with scope management', {
|
|
2308
|
-
sys_id: deploymentResult.artifactId,
|
|
2309
|
-
name: flowDefinition.name,
|
|
2310
|
-
table: flowDefinition.table,
|
|
2311
|
-
scope: deploymentResult.scope,
|
|
2312
|
-
domain: deploymentResult.domain,
|
|
2313
|
-
fallbackApplied: deploymentResult.fallbackApplied
|
|
2314
|
-
});
|
|
2315
|
-
return {
|
|
2316
|
-
success: true,
|
|
2317
|
-
message: `Flow "${flowDefinition.name}" deployed successfully to ${deploymentResult.scope} scope`,
|
|
2318
|
-
data: {
|
|
2319
|
-
sys_id: deploymentResult.artifactId,
|
|
2320
|
-
name: flowDefinition.name,
|
|
2321
|
-
table: flowDefinition.table,
|
|
2322
|
-
scope: deploymentResult.scope,
|
|
2323
|
-
domain: deploymentResult.domain,
|
|
2324
|
-
url: `https://${process.env.SNOW_INSTANCE}/nav_to.do?uri=sys_hub_flow.do?sys_id=${deploymentResult.artifactId}`,
|
|
2325
|
-
actions_created: flowDefinition.activities?.length || 0,
|
|
2326
|
-
scope_strategy: {
|
|
2327
|
-
selectedScope: deploymentResult.scope,
|
|
2328
|
-
fallbackApplied: deploymentResult.fallbackApplied,
|
|
2329
|
-
permissions: deploymentResult.permissions,
|
|
2330
|
-
warnings: deploymentResult.warnings
|
|
2331
|
-
}
|
|
2332
|
-
}
|
|
2333
|
-
};
|
|
2334
|
-
}
|
|
2335
|
-
catch (error) {
|
|
2336
|
-
this.logger.error('Failed to deploy flow with scope management', error);
|
|
2337
|
-
return {
|
|
2338
|
-
success: false,
|
|
2339
|
-
error: error instanceof Error ? error.message : String(error)
|
|
2340
|
-
};
|
|
2341
|
-
}
|
|
2342
|
-
}
|
|
2343
|
-
/**
|
|
2344
|
-
* Assess flow complexity for scope decision
|
|
2345
|
-
*/
|
|
2346
|
-
assessFlowComplexity(flowDefinition) {
|
|
2347
|
-
const activityCount = flowDefinition.activities?.length || 0;
|
|
2348
|
-
const hasComplexLogic = flowDefinition.activities?.some((activity) => activity.type === 'script' || activity.type === 'condition' || activity.type === 'loop');
|
|
2349
|
-
const hasIntegrations = flowDefinition.activities?.some((activity) => activity.type === 'rest' || activity.type === 'soap' || activity.type === 'integration');
|
|
2350
|
-
if (activityCount > 10 || hasComplexLogic || hasIntegrations) {
|
|
2351
|
-
return 'high';
|
|
2352
|
-
}
|
|
2353
|
-
else if (activityCount > 5) {
|
|
2354
|
-
return 'medium';
|
|
2355
|
-
}
|
|
2356
|
-
else {
|
|
2357
|
-
return 'low';
|
|
2358
|
-
}
|
|
2359
|
-
}
|
|
2360
|
-
/**
|
|
2361
|
-
* Check if flow has cross-application integration
|
|
2362
|
-
*/
|
|
2363
|
-
hasCrossApplicationIntegration(flowDefinition) {
|
|
2364
|
-
// Check for indicators of cross-application integration
|
|
2365
|
-
const integrationIndicators = [
|
|
2366
|
-
'integration',
|
|
2367
|
-
'api',
|
|
2368
|
-
'rest',
|
|
2369
|
-
'soap',
|
|
2370
|
-
'external',
|
|
2371
|
-
'cross',
|
|
2372
|
-
'global',
|
|
2373
|
-
'system'
|
|
2374
|
-
];
|
|
2375
|
-
const flowText = `${flowDefinition.name} ${flowDefinition.description || ''}`.toLowerCase();
|
|
2376
|
-
return integrationIndicators.some(indicator => flowText.includes(indicator));
|
|
2377
|
-
}
|
|
2378
|
-
/**
|
|
2379
|
-
* Check if flow is a shared utility
|
|
2380
|
-
*/
|
|
2381
|
-
isSharedUtility(flowDefinition) {
|
|
2382
|
-
// Check for indicators of shared utility
|
|
2383
|
-
const utilityIndicators = [
|
|
2384
|
-
'util',
|
|
2385
|
-
'helper',
|
|
2386
|
-
'common',
|
|
2387
|
-
'shared',
|
|
2388
|
-
'library',
|
|
2389
|
-
'global',
|
|
2390
|
-
'system'
|
|
2391
|
-
];
|
|
2392
|
-
const flowText = `${flowDefinition.name} ${flowDefinition.description || ''}`.toLowerCase();
|
|
2393
|
-
return utilityIndicators.some(indicator => flowText.includes(indicator));
|
|
2394
|
-
}
|
|
2395
|
-
/**
|
|
2396
|
-
* Extract customizations from instruction for template application
|
|
2397
|
-
*/
|
|
2398
|
-
extractCustomizationsFromInstruction(instruction) {
|
|
2399
|
-
const customizations = {};
|
|
2400
|
-
const lowerInstruction = instruction.toLowerCase();
|
|
2401
|
-
// Extract specific items or entities
|
|
2402
|
-
if (lowerInstruction.includes('iphone')) {
|
|
2403
|
-
customizations.constants = {
|
|
2404
|
-
catalog_item: 'iPhone 6',
|
|
2405
|
-
item_type: 'hardware'
|
|
2406
|
-
};
|
|
2407
|
-
}
|
|
2408
|
-
// Extract specific users or roles
|
|
2409
|
-
if (lowerInstruction.includes('admin')) {
|
|
2410
|
-
customizations.assignments = {
|
|
2411
|
-
admin_group: 'catalog_admins'
|
|
2412
|
-
};
|
|
2413
|
-
}
|
|
2414
|
-
// Extract specific conditions
|
|
2415
|
-
const conditionMatch = lowerInstruction.match(/when\s+(.+?)(?:\s+then|\s+,|$)/);
|
|
2416
|
-
if (conditionMatch) {
|
|
2417
|
-
customizations.condition = conditionMatch[1];
|
|
2418
|
-
}
|
|
2419
|
-
// Extract specific table
|
|
2420
|
-
const tableMatch = lowerInstruction.match(/(?:on|from|for)\s+(\w+)\s+(?:table|record)/);
|
|
2421
|
-
if (tableMatch) {
|
|
2422
|
-
customizations.table = tableMatch[1];
|
|
2423
|
-
}
|
|
2424
|
-
return customizations;
|
|
2425
|
-
}
|
|
2426
|
-
/**
|
|
2427
|
-
* Enhance actions with subflow references
|
|
2428
|
-
*/
|
|
2429
|
-
enhanceActionsWithSubflows(actions, subflowResults) {
|
|
2430
|
-
const enhancedActions = [...actions];
|
|
2431
|
-
// Map successful subflows to actions
|
|
2432
|
-
const successfulSubflows = subflowResults.filter(r => r.success);
|
|
2433
|
-
for (const subflowResult of successfulSubflows) {
|
|
2434
|
-
if (subflowResult.subflow) {
|
|
2435
|
-
const subflowName = subflowResult.subflow.name.toLowerCase();
|
|
2436
|
-
// Find matching actions that could use this subflow
|
|
2437
|
-
const matchingActionIndex = enhancedActions.findIndex(action => {
|
|
2438
|
-
const actionName = action.name.toLowerCase();
|
|
2439
|
-
return actionName.includes(subflowName) ||
|
|
2440
|
-
subflowName.includes(actionName) ||
|
|
2441
|
-
this.actionsAreRelated(action, subflowResult.subflow);
|
|
2442
|
-
});
|
|
2443
|
-
if (matchingActionIndex !== -1) {
|
|
2444
|
-
enhancedActions[matchingActionIndex].subflow_reference = {
|
|
2445
|
-
sys_id: subflowResult.sys_id,
|
|
2446
|
-
name: subflowResult.subflow.name,
|
|
2447
|
-
url: subflowResult.url
|
|
2448
|
-
};
|
|
2449
|
-
}
|
|
2450
|
-
}
|
|
2451
|
-
}
|
|
2452
|
-
return enhancedActions;
|
|
2453
|
-
}
|
|
2454
|
-
/**
|
|
2455
|
-
* Check if action and subflow are related
|
|
2456
|
-
*/
|
|
2457
|
-
actionsAreRelated(action, subflow) {
|
|
2458
|
-
// Simple heuristic - could be enhanced with more sophisticated matching
|
|
2459
|
-
const actionType = action.type.toLowerCase();
|
|
2460
|
-
const subflowName = subflow.name.toLowerCase();
|
|
2461
|
-
const relatedPairs = [
|
|
2462
|
-
['approval', 'approval'],
|
|
2463
|
-
['notification', 'notification'],
|
|
2464
|
-
['create_task', 'task'],
|
|
2465
|
-
['update_record', 'update']
|
|
2466
|
-
];
|
|
2467
|
-
return relatedPairs.some(([actionKeyword, subflowKeyword]) => actionType.includes(actionKeyword) && subflowName.includes(subflowKeyword));
|
|
2468
|
-
}
|
|
2469
|
-
/**
|
|
2470
|
-
* Generate comprehensive recommendations
|
|
2471
|
-
*/
|
|
2472
|
-
generateComprehensiveRecommendations(analysisResult, validationResult, matchingTemplates, subflowResults) {
|
|
2473
|
-
const recommendations = [];
|
|
2474
|
-
// Decision confidence recommendations
|
|
2475
|
-
if (analysisResult.confidence < 0.7) {
|
|
2476
|
-
recommendations.push('Consider providing more specific requirements to improve decision confidence');
|
|
2477
|
-
}
|
|
2478
|
-
// Complexity recommendations
|
|
2479
|
-
if (analysisResult.criteria.complexity === 'high' || analysisResult.criteria.complexity === 'very_high') {
|
|
2480
|
-
recommendations.push('Consider breaking complex logic into smaller, more manageable components');
|
|
2481
|
-
}
|
|
2482
|
-
// Reusability recommendations
|
|
2483
|
-
if (analysisResult.criteria.reusability === 'high' && analysisResult.recommendedType === 'main_flow') {
|
|
2484
|
-
recommendations.push('Consider creating reusable subflows for components that may be used elsewhere');
|
|
2485
|
-
}
|
|
2486
|
-
// Template recommendations
|
|
2487
|
-
if (matchingTemplates.length > 0 && matchingTemplates[0].confidence > 0.8) {
|
|
2488
|
-
recommendations.push(`Consider using the ${matchingTemplates[0].template.name} template for consistency`);
|
|
2489
|
-
}
|
|
2490
|
-
// Validation recommendations
|
|
2491
|
-
validationResult.recommendations.forEach(rec => {
|
|
2492
|
-
if (rec.priority === 'high') {
|
|
2493
|
-
recommendations.push(rec.message);
|
|
2494
|
-
}
|
|
2495
|
-
});
|
|
2496
|
-
// Subflow recommendations
|
|
2497
|
-
subflowResults.forEach(result => {
|
|
2498
|
-
if (result.success && result.recommendations) {
|
|
2499
|
-
recommendations.push(...result.recommendations);
|
|
2500
|
-
}
|
|
2501
|
-
});
|
|
2502
|
-
// General best practices
|
|
2503
|
-
recommendations.push('Test the flow thoroughly before deploying to production');
|
|
2504
|
-
recommendations.push('Document the flow purpose and usage for future maintenance');
|
|
2505
|
-
recommendations.push('Consider adding error handling for robust operation');
|
|
2506
|
-
return recommendations;
|
|
2507
|
-
}
|
|
2508
|
-
/**
|
|
2509
|
-
* Get intelligent flow analysis summary
|
|
2510
|
-
*/
|
|
2511
|
-
async getFlowAnalysisSummary(instruction) {
|
|
2512
|
-
try {
|
|
2513
|
-
const analysisResult = await this.decisionEngine.analyzeAndRecommend(instruction);
|
|
2514
|
-
const validationResult = this.validationEngine.validateDecision(analysisResult, instruction);
|
|
2515
|
-
const matchingTemplates = this.patternTemplates.findMatchingTemplates(instruction);
|
|
2516
|
-
return {
|
|
2517
|
-
decision: {
|
|
2518
|
-
recommendedType: analysisResult.recommendedType,
|
|
2519
|
-
confidence: analysisResult.confidence,
|
|
2520
|
-
rationale: analysisResult.rationale
|
|
2521
|
-
},
|
|
2522
|
-
complexity: analysisResult.criteria.complexity,
|
|
2523
|
-
reusability: analysisResult.criteria.reusability,
|
|
2524
|
-
context: analysisResult.criteria.context,
|
|
2525
|
-
validation: {
|
|
2526
|
-
isValid: validationResult.isValid,
|
|
2527
|
-
score: validationResult.score,
|
|
2528
|
-
maxScore: validationResult.maxScore
|
|
2529
|
-
},
|
|
2530
|
-
templates: {
|
|
2531
|
-
matchCount: matchingTemplates.length,
|
|
2532
|
-
bestMatch: matchingTemplates[0]?.template.name || null,
|
|
2533
|
-
confidence: matchingTemplates[0]?.confidence || 0
|
|
2534
|
-
},
|
|
2535
|
-
subflowCandidates: analysisResult.subflowCandidates.length,
|
|
2536
|
-
alternatives: analysisResult.alternatives
|
|
2537
|
-
};
|
|
2538
|
-
}
|
|
2539
|
-
catch (error) {
|
|
2540
|
-
this.logger.error('Failed to get flow analysis summary', error);
|
|
2541
|
-
return {
|
|
2542
|
-
error: 'Analysis failed',
|
|
2543
|
-
message: error instanceof Error ? error.message : String(error)
|
|
2544
|
-
};
|
|
2545
|
-
}
|
|
2546
326
|
}
|
|
2547
327
|
}
|
|
2548
328
|
exports.EnhancedFlowComposer = EnhancedFlowComposer;
|