snow-flow 10.0.1-dev.424 → 10.0.1-dev.427
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/package.json +1 -1
- package/src/project/agents-template.txt +6 -0
- package/src/servicenow/servicenow-mcp-unified/shared/tool-registry.ts +2 -0
- package/src/servicenow/servicenow-mcp-unified/tools/flow-designer/snow_manage_flow.ts +493 -1311
- package/src/servicenow/servicenow-mcp-unified/tools/index.ts +1 -1
- package/src/servicenow/servicenow-mcp-unified/tools/plugins/index.ts +1 -3
- package/src/servicenow/servicenow-mcp-unified/tools/plugins/snow_plugin_manage.ts +295 -0
- package/src/servicenow/servicenow-mcp-unified/tools/plugins/snow_activate_plugin.ts +0 -47
- package/src/servicenow/servicenow-mcp-unified/tools/plugins/snow_custom_plugin.ts +0 -46
- package/src/servicenow/servicenow-mcp-unified/tools/plugins/snow_list_plugins.ts +0 -52
|
@@ -1,17 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* snow_manage_flow - Complete Flow Designer lifecycle management
|
|
3
|
-
*
|
|
4
|
-
* Create, list, get, update, activate, deactivate, delete and publish
|
|
5
|
-
* Flow Designer flows and subflows.
|
|
6
|
-
*
|
|
7
|
-
* For create/create_subflow: uses a Scheduled Job (sysauto_script) that
|
|
8
|
-
* runs GlideRecord server-side, ensuring sys_hub_flow_version records are
|
|
9
|
-
* created, flow_definition is set, and sn_fd engine registration is
|
|
10
|
-
* attempted. Falls back to Table API if the scheduled job fails.
|
|
11
|
-
*
|
|
12
|
-
* All other actions (list, get, update, activate, etc.) use the Table API.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
1
|
import { MCPToolDefinition, ServiceNowContext, ToolResult } from '../../shared/types.js';
|
|
16
2
|
import { getAuthenticatedClient } from '../../shared/auth.js';
|
|
17
3
|
import { createSuccessResult, createErrorResult, SnowFlowError, ErrorType } from '../../shared/error-handler.js';
|
|
@@ -31,1209 +17,404 @@ function isSysId(value: string): boolean {
|
|
|
31
17
|
return /^[a-f0-9]{32}$/.test(value);
|
|
32
18
|
}
|
|
33
19
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
20
|
+
// ── GraphQL Flow Designer helpers ─────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
function jsToGraphQL(val: any): string {
|
|
23
|
+
if (val === null || val === undefined) return 'null';
|
|
24
|
+
if (typeof val === 'string') return JSON.stringify(val);
|
|
25
|
+
if (typeof val === 'number' || typeof val === 'bigint') return String(val);
|
|
26
|
+
if (typeof val === 'boolean') return val ? 'true' : 'false';
|
|
27
|
+
if (Array.isArray(val)) return '[' + val.map(jsToGraphQL).join(', ') + ']';
|
|
28
|
+
if (typeof val === 'object') {
|
|
29
|
+
return '{' + Object.entries(val).map(([k, v]) => k + ': ' + jsToGraphQL(v)).join(', ') + '}';
|
|
30
|
+
}
|
|
31
|
+
return String(val);
|
|
37
32
|
}
|
|
38
33
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
*
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
* 2. Create a sysauto_script record (run_type=once, run_start=now)
|
|
48
|
-
* 3. Poll the property every 2s for up to 30s
|
|
49
|
-
* 4. Clean up both records
|
|
50
|
-
* 5. Return the result (flow sys_id, version sys_id, tier used)
|
|
51
|
-
*/
|
|
52
|
-
async function createFlowViaScheduledJob(
|
|
34
|
+
function generateUUID(): string {
|
|
35
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
|
36
|
+
const r = (Math.random() * 16) | 0;
|
|
37
|
+
return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function executeFlowPatchMutation(
|
|
53
42
|
client: any,
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
triggerTable?: string;
|
|
64
|
-
triggerCondition?: string;
|
|
65
|
-
activities?: Array<{ name: string; type?: string; inputs?: any }>;
|
|
66
|
-
inputs?: Array<{ name: string; label?: string; type?: string; mandatory?: boolean; default_value?: string }>;
|
|
67
|
-
outputs?: Array<{ name: string; label?: string; type?: string }>;
|
|
68
|
-
flowDefinition?: any;
|
|
43
|
+
flowPatch: any,
|
|
44
|
+
responseFields: string
|
|
45
|
+
): Promise<any> {
|
|
46
|
+
const mutation = 'mutation { global { snFlowDesigner { flow(flowPatch: ' +
|
|
47
|
+
jsToGraphQL(flowPatch) + ') { id ' + responseFields + ' __typename } __typename } __typename } }';
|
|
48
|
+
const resp = await client.post('/api/now/graphql', { variables: {}, query: mutation });
|
|
49
|
+
const errors = resp.data?.errors;
|
|
50
|
+
if (errors && errors.length > 0) {
|
|
51
|
+
throw new Error('GraphQL error: ' + JSON.stringify(errors[0].message || errors[0]));
|
|
69
52
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
flowSysId?: string;
|
|
73
|
-
versionSysId?: string;
|
|
74
|
-
tierUsed?: string;
|
|
75
|
-
latestVersionSet?: boolean;
|
|
76
|
-
latestVersionValue?: string;
|
|
77
|
-
steps?: any;
|
|
78
|
-
error?: string;
|
|
79
|
-
}> {
|
|
80
|
-
var resultPropName = 'snow_flow.factory_result.' + Date.now();
|
|
81
|
-
var flowDefStr = params.flowDefinition ? JSON.stringify(params.flowDefinition) : '';
|
|
82
|
-
|
|
83
|
-
// Build the server-side ES5 script
|
|
84
|
-
var script = [
|
|
85
|
-
"(function() {",
|
|
86
|
-
" var PROP = '" + escForScript(resultPropName) + "';",
|
|
87
|
-
" var r = { success: false, steps: {}, tier_used: null };",
|
|
88
|
-
" try {",
|
|
89
|
-
" var flowName = '" + escForScript(params.name) + "';",
|
|
90
|
-
" var flowDesc = '" + escForScript(params.description) + "';",
|
|
91
|
-
" var intName = '" + escForScript(params.internalName) + "';",
|
|
92
|
-
" var isSubflow = " + (params.isSubflow ? "true" : "false") + ";",
|
|
93
|
-
" var flowCat = '" + escForScript(params.category) + "';",
|
|
94
|
-
" var runAs = '" + escForScript(params.runAs) + "';",
|
|
95
|
-
" var activate = " + (params.shouldActivate ? "true" : "false") + ";",
|
|
96
|
-
" var trigType = '" + escForScript(params.triggerType || 'manual') + "';",
|
|
97
|
-
" var trigTable = '" + escForScript(params.triggerTable || '') + "';",
|
|
98
|
-
" var trigCondition = '" + escForScript(params.triggerCondition || '') + "';",
|
|
99
|
-
" var flowDefStr = '" + escForScript(flowDefStr) + "';",
|
|
100
|
-
" var activitiesJson = '" + escForScript(JSON.stringify(params.activities || [])) + "';",
|
|
101
|
-
" var inputsJson = '" + escForScript(JSON.stringify(params.inputs || [])) + "';",
|
|
102
|
-
" var outputsJson = '" + escForScript(JSON.stringify(params.outputs || [])) + "';",
|
|
103
|
-
" var activities = []; try { activities = JSON.parse(activitiesJson); } catch(e) {}",
|
|
104
|
-
" var inputs = []; try { inputs = JSON.parse(inputsJson); } catch(e) {}",
|
|
105
|
-
" var outputs = []; try { outputs = JSON.parse(outputsJson); } catch(e) {}",
|
|
106
|
-
" var flowSysId = null;",
|
|
107
|
-
" var verSysId = null;",
|
|
108
|
-
"",
|
|
109
|
-
// ── TIER 1: sn_fd.FlowDesigner API ──
|
|
110
|
-
" try {",
|
|
111
|
-
" if (typeof sn_fd !== 'undefined' && sn_fd.FlowDesigner && typeof sn_fd.FlowDesigner.createFlow === 'function') {",
|
|
112
|
-
" var fdR = sn_fd.FlowDesigner.createFlow({ name: flowName, description: flowDesc, type: isSubflow ? 'subflow' : 'flow', category: flowCat, run_as: runAs });",
|
|
113
|
-
" if (fdR) {",
|
|
114
|
-
" flowSysId = (typeof fdR === 'object' ? (fdR.sys_id || (fdR.getValue ? fdR.getValue('sys_id') : null)) : fdR) + '';",
|
|
115
|
-
" r.tier_used = 'sn_fd_api';",
|
|
116
|
-
" r.success = true;",
|
|
117
|
-
" r.steps.tier1 = { success: true };",
|
|
118
|
-
" if (activate && typeof sn_fd.FlowDesigner.publishFlow === 'function') {",
|
|
119
|
-
" try { sn_fd.FlowDesigner.publishFlow(flowSysId); r.steps.publish = { success: true }; }",
|
|
120
|
-
" catch(pe) { r.steps.publish = { success: false, error: pe + '' }; }",
|
|
121
|
-
" }",
|
|
122
|
-
" }",
|
|
123
|
-
" }",
|
|
124
|
-
" } catch(t1e) { r.steps.tier1 = { success: false, error: t1e.getMessage ? t1e.getMessage() : t1e + '' }; }",
|
|
125
|
-
"",
|
|
126
|
-
// ── TIER 2: GlideRecord ──
|
|
127
|
-
// Reference flow analysis:
|
|
128
|
-
// - latest_snapshot points to sys_hub_flow_snapshot (NOT version, NOT JSON)
|
|
129
|
-
// - flow_definition on flow record = null
|
|
130
|
-
// - latest_version = null (normal)
|
|
131
|
-
// Pipeline: flow → version → snapshot → set latest_snapshot on flow
|
|
132
|
-
" if (!flowSysId) {",
|
|
133
|
-
" try {",
|
|
134
|
-
" var f = new GlideRecord('sys_hub_flow');",
|
|
135
|
-
" f.initialize();",
|
|
136
|
-
" f.setValue('name', flowName); f.setValue('description', flowDesc);",
|
|
137
|
-
" f.setValue('internal_name', intName); f.setValue('category', flowCat);",
|
|
138
|
-
" f.setValue('run_as', runAs); f.setValue('active', false);",
|
|
139
|
-
" f.setValue('status', 'draft'); f.setValue('validated', true);",
|
|
140
|
-
" f.setValue('type', isSubflow ? 'subflow' : 'flow');",
|
|
141
|
-
" flowSysId = f.insert();",
|
|
142
|
-
" r.steps.flow_insert = { success: !!flowSysId, sys_id: flowSysId + '' };",
|
|
143
|
-
" if (flowSysId) {",
|
|
144
|
-
// Version record
|
|
145
|
-
" var v = new GlideRecord('sys_hub_flow_version');",
|
|
146
|
-
" v.initialize();",
|
|
147
|
-
" v.setValue('flow', flowSysId); v.setValue('name', '1.0');",
|
|
148
|
-
" v.setValue('version', '1.0'); v.setValue('state', 'draft');",
|
|
149
|
-
" v.setValue('active', true); v.setValue('compile_state', 'draft');",
|
|
150
|
-
" v.setValue('is_current', true);",
|
|
151
|
-
" if (flowDefStr) { v.setValue('flow_definition', flowDefStr); }",
|
|
152
|
-
" verSysId = v.insert();",
|
|
153
|
-
" r.steps.version_insert = { success: !!verSysId, sys_id: verSysId + '' };",
|
|
154
|
-
"",
|
|
155
|
-
// No snapshot/latest_snapshot — let the engine create these via Business Rule post-compile
|
|
156
|
-
" r.tier_used = 'gliderecord_scheduled'; r.success = true;",
|
|
157
|
-
" }",
|
|
158
|
-
" } catch(t2e) { r.steps.tier2 = { success: false, error: t2e.getMessage ? t2e.getMessage() : t2e + '' }; }",
|
|
159
|
-
" }",
|
|
160
|
-
"",
|
|
161
|
-
// ── Trigger, action, variable creation (runs for any tier) ──
|
|
162
|
-
" if (flowSysId) {",
|
|
163
|
-
// Trigger — precise search with prefix matching + fallback without action_type
|
|
164
|
-
" if (!isSubflow && trigType !== 'manual') {",
|
|
165
|
-
" try {",
|
|
166
|
-
" var trigDefId = null;",
|
|
167
|
-
" var trigDefName = '';",
|
|
168
|
-
" var trigSearchLog = [];",
|
|
169
|
-
"",
|
|
170
|
-
// Search 1: exact prefix 'sn_fd.trigger.' (standard Flow Designer naming)
|
|
171
|
-
" var exactNames = {",
|
|
172
|
-
" 'record_created': ['sn_fd.trigger.record_created', 'global.sn_fd.trigger.record_created'],",
|
|
173
|
-
" 'record_updated': ['sn_fd.trigger.record_updated', 'global.sn_fd.trigger.record_updated'],",
|
|
174
|
-
" 'scheduled': ['sn_fd.trigger.scheduled', 'global.sn_fd.trigger.scheduled']",
|
|
175
|
-
" };",
|
|
176
|
-
" var exactCandidates = exactNames[trigType] || [];",
|
|
177
|
-
" for (var ec = 0; ec < exactCandidates.length; ec++) {",
|
|
178
|
-
" var tgE = new GlideRecord('sys_hub_action_type_definition');",
|
|
179
|
-
" tgE.addQuery('internal_name', exactCandidates[ec]);",
|
|
180
|
-
" tgE.setLimit(1); tgE.query();",
|
|
181
|
-
" if (tgE.next()) {",
|
|
182
|
-
" trigDefId = tgE.getUniqueValue(); trigDefName = tgE.getValue('name');",
|
|
183
|
-
" trigSearchLog.push('exact:' + exactCandidates[ec] + ':found');",
|
|
184
|
-
" break;",
|
|
185
|
-
" }",
|
|
186
|
-
" trigSearchLog.push('exact:' + exactCandidates[ec] + ':not_found');",
|
|
187
|
-
" }",
|
|
188
|
-
"",
|
|
189
|
-
// Search 2: prefix STARTSWITH 'sn_fd.trigger' (catches variations)
|
|
190
|
-
" if (!trigDefId) {",
|
|
191
|
-
" var tgP = new GlideRecord('sys_hub_action_type_definition');",
|
|
192
|
-
" tgP.addQuery('internal_name', 'STARTSWITH', 'sn_fd.trigger');",
|
|
193
|
-
" tgP.setLimit(10); tgP.query();",
|
|
194
|
-
" var prefixMatches = [];",
|
|
195
|
-
" while (tgP.next()) {",
|
|
196
|
-
" prefixMatches.push(tgP.getValue('internal_name'));",
|
|
197
|
-
" if (!trigDefId && (tgP.getValue('internal_name') + '').indexOf(trigType.replace('record_', '')) > -1) {",
|
|
198
|
-
" trigDefId = tgP.getUniqueValue(); trigDefName = tgP.getValue('name');",
|
|
199
|
-
" }",
|
|
200
|
-
" }",
|
|
201
|
-
" trigSearchLog.push('prefix_sn_fd.trigger:[' + prefixMatches.join(',') + ']');",
|
|
202
|
-
" }",
|
|
203
|
-
"",
|
|
204
|
-
// Search 3 removed — sys_hub_trigger_definition returned wrong defs (Proactive Analytics, DevOps)
|
|
205
|
-
" if (!trigDefId) { trigSearchLog.push('no_exact_trigger_def_found'); }",
|
|
206
|
-
"",
|
|
207
|
-
// Create trigger instance — only set action_type if exact sn_fd.trigger.* match found
|
|
208
|
-
" var trigInst = new GlideRecord('sys_hub_trigger_instance');",
|
|
209
|
-
" trigInst.initialize();",
|
|
210
|
-
" trigInst.setValue('flow', flowSysId);",
|
|
211
|
-
" trigInst.setValue('name', trigType);",
|
|
212
|
-
" trigInst.setValue('order', 0);",
|
|
213
|
-
" trigInst.setValue('active', true);",
|
|
214
|
-
" if (trigDefId) trigInst.setValue('action_type', trigDefId);",
|
|
215
|
-
" if (trigTable) trigInst.setValue('table', trigTable);",
|
|
216
|
-
" if (trigCondition) trigInst.setValue('condition', trigCondition);",
|
|
217
|
-
" var trigId = trigInst.insert();",
|
|
218
|
-
" r.steps.trigger = {",
|
|
219
|
-
" success: !!trigId,",
|
|
220
|
-
" sys_id: trigId + '',",
|
|
221
|
-
" def_found: !!trigDefId,",
|
|
222
|
-
" def_name: trigDefName || 'none (created without action_type)',",
|
|
223
|
-
" search: trigSearchLog",
|
|
224
|
-
" };",
|
|
225
|
-
" } catch(te) { r.steps.trigger = { success: false, error: te.getMessage ? te.getMessage() : te + '' }; }",
|
|
226
|
-
" }",
|
|
227
|
-
// Actions
|
|
228
|
-
" var actionsCreated = 0;",
|
|
229
|
-
" for (var ai = 0; ai < activities.length; ai++) {",
|
|
230
|
-
" try {",
|
|
231
|
-
" var act = activities[ai];",
|
|
232
|
-
" var actDef = new GlideRecord('sys_hub_action_type_definition');",
|
|
233
|
-
" actDef.addQuery('internal_name', 'CONTAINS', act.type || 'script');",
|
|
234
|
-
" actDef.addOrCondition('name', 'CONTAINS', act.type || 'script'); actDef.query();",
|
|
235
|
-
" var actInst = new GlideRecord('sys_hub_action_instance');",
|
|
236
|
-
" actInst.initialize();",
|
|
237
|
-
" actInst.setValue('flow', flowSysId); actInst.setValue('name', act.name || 'Action ' + (ai + 1));",
|
|
238
|
-
" actInst.setValue('order', (ai + 1) * 100); actInst.setValue('active', true);",
|
|
239
|
-
" if (actDef.next()) actInst.setValue('action_type', actDef.getUniqueValue());",
|
|
240
|
-
" if (actInst.insert()) actionsCreated++;",
|
|
241
|
-
" } catch(ae) {}",
|
|
242
|
-
" }",
|
|
243
|
-
" r.steps.actions = { success: true, created: actionsCreated, requested: activities.length };",
|
|
244
|
-
// Variables (subflows)
|
|
245
|
-
" var varsCreated = 0;",
|
|
246
|
-
" if (isSubflow) {",
|
|
247
|
-
" for (var vi = 0; vi < inputs.length; vi++) {",
|
|
248
|
-
" try { var inp = inputs[vi]; var fv = new GlideRecord('sys_hub_flow_variable'); fv.initialize();",
|
|
249
|
-
" fv.setValue('flow', flowSysId); fv.setValue('name', inp.name); fv.setValue('label', inp.label || inp.name);",
|
|
250
|
-
" fv.setValue('type', inp.type || 'string'); fv.setValue('mandatory', inp.mandatory || false);",
|
|
251
|
-
" fv.setValue('default_value', inp.default_value || ''); fv.setValue('variable_type', 'input');",
|
|
252
|
-
" if (fv.insert()) varsCreated++;",
|
|
253
|
-
" } catch(ve) {}",
|
|
254
|
-
" }",
|
|
255
|
-
" for (var vo = 0; vo < outputs.length; vo++) {",
|
|
256
|
-
" try { var ot = outputs[vo]; var ov = new GlideRecord('sys_hub_flow_variable'); ov.initialize();",
|
|
257
|
-
" ov.setValue('flow', flowSysId); ov.setValue('name', ot.name); ov.setValue('label', ot.label || ot.name);",
|
|
258
|
-
" ov.setValue('type', ot.type || 'string'); ov.setValue('variable_type', 'output');",
|
|
259
|
-
" if (ov.insert()) varsCreated++;",
|
|
260
|
-
" } catch(ve) {}",
|
|
261
|
-
" }",
|
|
262
|
-
" }",
|
|
263
|
-
" r.steps.variables = { success: true, created: varsCreated };",
|
|
264
|
-
// Engine compilation delegated to Business Rule (runs in Flow Designer context)
|
|
265
|
-
" r.steps.engine = 'deferred_to_business_rule';",
|
|
266
|
-
" }",
|
|
267
|
-
"",
|
|
268
|
-
" r.flow_sys_id = flowSysId ? flowSysId + '' : null;",
|
|
269
|
-
" r.version_sys_id = verSysId ? verSysId + '' : null;",
|
|
270
|
-
// ── Final check: read back our flow's latest_snapshot to confirm ──
|
|
271
|
-
" if (flowSysId) {",
|
|
272
|
-
" var cf = new GlideRecord('sys_hub_flow');",
|
|
273
|
-
" if (cf.get(flowSysId)) {",
|
|
274
|
-
" r.latest_version_value = cf.getValue('latest_version') + '';",
|
|
275
|
-
" r.our_latest_snapshot = cf.getValue('latest_snapshot') + '';",
|
|
276
|
-
" r.our_latest_snapshot_length = (cf.getValue('latest_snapshot') + '').length;",
|
|
277
|
-
" }",
|
|
278
|
-
" }",
|
|
279
|
-
" } catch(e) { r.success = false; r.error = e.getMessage ? e.getMessage() : e + ''; }",
|
|
280
|
-
" gs.setProperty(PROP, JSON.stringify(r));",
|
|
281
|
-
"})();"
|
|
282
|
-
].join('\n');
|
|
53
|
+
return resp.data?.data?.global?.snFlowDesigner?.flow || resp.data;
|
|
54
|
+
}
|
|
283
55
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
type: 'string',
|
|
290
|
-
description: 'Snow-Flow temp result (auto-cleanup)'
|
|
291
|
-
});
|
|
292
|
-
|
|
293
|
-
// 2. Create scheduled job (run immediately)
|
|
294
|
-
var runStart = new Date().toISOString().replace('T', ' ').substring(0, 19);
|
|
295
|
-
var jobResp = await client.post('/api/now/table/sysauto_script', {
|
|
296
|
-
name: 'Snow-Flow Flow Factory (auto-cleanup)',
|
|
297
|
-
script: script,
|
|
298
|
-
run_type: 'once',
|
|
299
|
-
run_start: runStart,
|
|
300
|
-
active: true
|
|
301
|
-
});
|
|
302
|
-
var jobSysId = jobResp.data.result?.sys_id;
|
|
303
|
-
|
|
304
|
-
// 3. Poll for result (adaptive: 5×1s + 5×2s + 5×3s = 30s max)
|
|
305
|
-
for (var i = 0; i < 15; i++) {
|
|
306
|
-
var delay = i < 5 ? 1000 : i < 10 ? 2000 : 3000;
|
|
307
|
-
await new Promise(resolve => setTimeout(resolve, delay));
|
|
308
|
-
var propResp = await client.get('/api/now/table/sys_properties', {
|
|
56
|
+
async function lookupDefinitionParams(client: any, defSysId: string): Promise<any[]> {
|
|
57
|
+
const paramTables = ['sys_hub_action_input', 'sys_hub_action_input_param', 'sys_hub_action_type_param'];
|
|
58
|
+
for (const table of paramTables) {
|
|
59
|
+
try {
|
|
60
|
+
const resp = await client.get('/api/now/table/' + table, {
|
|
309
61
|
params: {
|
|
310
|
-
sysparm_query: '
|
|
311
|
-
sysparm_fields: 'sys_id,
|
|
312
|
-
sysparm_limit:
|
|
62
|
+
sysparm_query: 'model=' + defSysId + '^ORaction_type=' + defSysId,
|
|
63
|
+
sysparm_fields: 'sys_id,name,element,label,internal_type,type,type_label,order,mandatory,readonly,maxsize,data_structure,reference,reference_display,ref_qual,choice_option,column_name,default_value,use_dependent,dependent_on,internal_link,attributes,sys_class_name',
|
|
64
|
+
sysparm_limit: 50
|
|
313
65
|
}
|
|
314
66
|
});
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
67
|
+
const results = resp.data.result;
|
|
68
|
+
if (results && results.length > 0) return results;
|
|
69
|
+
} catch (_) {}
|
|
70
|
+
}
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function buildGraphQLInput(p: any, defaultValue?: any): any {
|
|
75
|
+
const name = p.name || p.element || '';
|
|
76
|
+
const type = p.internal_type || p.type || 'string';
|
|
77
|
+
const isMandatory = p.mandatory === 'true' || p.mandatory === true;
|
|
78
|
+
const order = parseInt(p.order) || 100;
|
|
79
|
+
const defVal = defaultValue !== undefined ? defaultValue : (p.default_value || '');
|
|
80
|
+
return {
|
|
81
|
+
id: p.sys_id || '',
|
|
82
|
+
name,
|
|
83
|
+
label: p.label || name,
|
|
84
|
+
internalType: type,
|
|
85
|
+
mandatory: isMandatory,
|
|
86
|
+
order,
|
|
87
|
+
valueSysId: '',
|
|
88
|
+
field_name: name,
|
|
89
|
+
type,
|
|
90
|
+
children: [],
|
|
91
|
+
displayValue: { value: '' },
|
|
92
|
+
value: type === 'conditions'
|
|
93
|
+
? { schemaless: false, schemalessValue: '', value: '^EQ' }
|
|
94
|
+
: defVal
|
|
95
|
+
? { schemaless: false, schemalessValue: '', value: String(defVal) }
|
|
96
|
+
: { value: '' },
|
|
97
|
+
parameter: {
|
|
98
|
+
id: p.sys_id || '',
|
|
99
|
+
label: p.label || name,
|
|
100
|
+
name,
|
|
101
|
+
type,
|
|
102
|
+
type_label: p.type_label || '',
|
|
103
|
+
order,
|
|
104
|
+
extended: false,
|
|
105
|
+
mandatory: isMandatory,
|
|
106
|
+
readonly: p.readonly === 'true' || p.readonly === true,
|
|
107
|
+
maxsize: parseInt(p.maxsize) || 80,
|
|
108
|
+
data_structure: p.data_structure || '',
|
|
109
|
+
reference: p.reference || '',
|
|
110
|
+
reference_display: p.reference_display || '',
|
|
111
|
+
ref_qual: p.ref_qual || '',
|
|
112
|
+
choiceOption: p.choice_option || '',
|
|
113
|
+
table: '',
|
|
114
|
+
columnName: p.column_name || '',
|
|
115
|
+
defaultValue: p.default_value || '',
|
|
116
|
+
use_dependent: p.use_dependent === 'true' || p.use_dependent === true,
|
|
117
|
+
dependent_on: p.dependent_on || '',
|
|
118
|
+
internal_link: p.internal_link || '',
|
|
119
|
+
show_ref_finder: false,
|
|
120
|
+
local: false,
|
|
121
|
+
attributes: p.attributes || '',
|
|
122
|
+
sys_class_name: p.sys_class_name || '',
|
|
123
|
+
children: []
|
|
337
124
|
}
|
|
125
|
+
};
|
|
126
|
+
}
|
|
338
127
|
|
|
339
|
-
|
|
128
|
+
async function addTriggerViaGraphQL(
|
|
129
|
+
client: any,
|
|
130
|
+
flowId: string,
|
|
131
|
+
triggerType: string,
|
|
132
|
+
table?: string,
|
|
133
|
+
condition?: string
|
|
134
|
+
): Promise<{ success: boolean; triggerId?: string; steps?: any; error?: string }> {
|
|
135
|
+
const steps: any = {};
|
|
136
|
+
|
|
137
|
+
const triggerMap: Record<string, { type: string; name: string; triggerType: string; defNames: string[] }> = {
|
|
138
|
+
'record_created': { type: 'record_create', name: 'Created', triggerType: 'Record',
|
|
139
|
+
defNames: ['sn_fd.trigger.record_create', 'global.sn_fd.trigger.record_create', 'sn_fd.trigger.record_created', 'global.sn_fd.trigger.record_created'] },
|
|
140
|
+
'record_updated': { type: 'record_update', name: 'Updated', triggerType: 'Record',
|
|
141
|
+
defNames: ['sn_fd.trigger.record_update', 'global.sn_fd.trigger.record_update', 'sn_fd.trigger.record_updated', 'global.sn_fd.trigger.record_updated'] },
|
|
142
|
+
'record_create_or_update': { type: 'record_create_or_update', name: 'Created or Updated', triggerType: 'Record',
|
|
143
|
+
defNames: ['sn_fd.trigger.record_create_or_update', 'global.sn_fd.trigger.record_create_or_update'] },
|
|
144
|
+
'scheduled': { type: 'scheduled', name: 'Scheduled', triggerType: 'Scheduled',
|
|
145
|
+
defNames: ['sn_fd.trigger.scheduled', 'global.sn_fd.trigger.scheduled'] },
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const config = triggerMap[triggerType] || triggerMap['record_create_or_update'];
|
|
149
|
+
|
|
150
|
+
let trigDefId: string | null = null;
|
|
151
|
+
for (const defName of config.defNames) {
|
|
340
152
|
try {
|
|
341
|
-
|
|
342
|
-
params: { sysparm_query: '
|
|
153
|
+
const resp = await client.get('/api/now/table/sys_hub_action_type_definition', {
|
|
154
|
+
params: { sysparm_query: 'internal_name=' + defName, sysparm_fields: 'sys_id', sysparm_limit: 1 }
|
|
343
155
|
});
|
|
344
|
-
|
|
156
|
+
trigDefId = resp.data.result?.[0]?.sys_id || null;
|
|
157
|
+
if (trigDefId) break;
|
|
345
158
|
} catch (_) {}
|
|
346
|
-
try { if (jobSysId) await client.delete('/api/now/table/sysauto_script/' + jobSysId); } catch (_) {}
|
|
347
|
-
return { success: false, error: 'Scheduled job timed out after 30s — scheduler may be slow on this instance' };
|
|
348
|
-
} catch (e: any) {
|
|
349
|
-
return { success: false, error: 'Scheduled job setup failed: ' + (e.message || e) };
|
|
350
159
|
}
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
/**
|
|
356
|
-
* After the scheduled job creates the flow records, this function:
|
|
357
|
-
* 1. Creates a temporary Business Rule on sys_hub_flow (after update)
|
|
358
|
-
* 2. PATCHes the flow record to trigger the BR
|
|
359
|
-
* 3. The BR runs in the Flow Designer engine context and tries to compile/publish
|
|
360
|
-
* 4. Reads the result from a sys_property
|
|
361
|
-
* 5. Cleans up both the BR and the property
|
|
362
|
-
*
|
|
363
|
-
* Why a BR instead of scheduled job? BRs on sys_hub_flow run in the Flow Designer
|
|
364
|
-
* application scope, which may have access to APIs (like sn_fd.FlowDesigner) that
|
|
365
|
-
* aren't available in global-scope scheduled jobs.
|
|
366
|
-
*/
|
|
367
|
-
async function tryCompileViaBusinessRule(
|
|
368
|
-
client: any,
|
|
369
|
-
flowSysId: string,
|
|
370
|
-
flowDescription: string
|
|
371
|
-
): Promise<{ success: boolean; result?: any; error?: string }> {
|
|
372
|
-
var brResultProp = 'snow_flow.br_compile.' + Date.now();
|
|
373
|
-
|
|
374
|
-
// The BR script: runs server-side in sys_hub_flow context
|
|
375
|
-
// Probes all available APIs and tries compile/publish
|
|
376
|
-
var brScript = [
|
|
377
|
-
"(function executeRule(current, previous) {",
|
|
378
|
-
" var PROP = '" + escForScript(brResultProp) + "';",
|
|
379
|
-
" var r = { fired: true, context: 'business_rule', flow_id: current.sys_id + '' };",
|
|
380
|
-
" try {",
|
|
381
|
-
// Probe sn_fd namespace
|
|
382
|
-
" if (typeof sn_fd !== 'undefined') {",
|
|
383
|
-
" r.sn_fd = 'available';",
|
|
384
|
-
" var snfdKeys = [];",
|
|
385
|
-
" try { for (var k in sn_fd) { snfdKeys.push(k + ':' + typeof sn_fd[k]); } } catch(e) {}",
|
|
386
|
-
" r.sn_fd_keys = snfdKeys;",
|
|
387
|
-
"",
|
|
388
|
-
// Try FlowDesigner (not available in scheduled job, might be available in BR)
|
|
389
|
-
" if (typeof sn_fd.FlowDesigner !== 'undefined') {",
|
|
390
|
-
" r.FlowDesigner = 'available';",
|
|
391
|
-
" var fdMethods = [];",
|
|
392
|
-
" try { for (var fm in sn_fd.FlowDesigner) { if (typeof sn_fd.FlowDesigner[fm] === 'function') fdMethods.push(fm); } } catch(e) {}",
|
|
393
|
-
" r.FlowDesigner_methods = fdMethods;",
|
|
394
|
-
" try {",
|
|
395
|
-
" sn_fd.FlowDesigner.publishFlow(current.sys_id + '');",
|
|
396
|
-
" r.publishFlow = 'success';",
|
|
397
|
-
" } catch(pe) { r.publishFlow = (pe.getMessage ? pe.getMessage() : pe + '').substring(0, 200); }",
|
|
398
|
-
" } else { r.FlowDesigner = 'unavailable'; }",
|
|
399
|
-
"",
|
|
400
|
-
// Try FlowAPI
|
|
401
|
-
" if (typeof sn_fd.FlowAPI !== 'undefined') {",
|
|
402
|
-
" r.FlowAPI = 'available';",
|
|
403
|
-
" var faMethods = [];",
|
|
404
|
-
" try { for (var am in sn_fd.FlowAPI) { if (typeof sn_fd.FlowAPI[am] === 'function') faMethods.push(am); } } catch(e) {}",
|
|
405
|
-
" r.FlowAPI_methods = faMethods;",
|
|
406
|
-
" try {",
|
|
407
|
-
" var compRes = sn_fd.FlowAPI.compile(current.sys_id + '');",
|
|
408
|
-
" r.compile = compRes ? (compRes + '').substring(0, 200) : 'success (null return)';",
|
|
409
|
-
" } catch(ce) { r.compile = 'error: ' + (ce.getMessage ? ce.getMessage() : ce + '').substring(0, 200); }",
|
|
410
|
-
" try {",
|
|
411
|
-
" var pubRes = sn_fd.FlowAPI.publish(current.sys_id + '');",
|
|
412
|
-
" r.publish = pubRes ? (pubRes + '').substring(0, 200) : 'success (null return)';",
|
|
413
|
-
" } catch(pue) { r.publish = 'error: ' + (pue.getMessage ? pue.getMessage() : pue + '').substring(0, 200); }",
|
|
414
|
-
" } else { r.FlowAPI = 'unavailable'; }",
|
|
415
|
-
"",
|
|
416
|
-
// Search for flow-related Script Includes
|
|
417
|
-
" var si = new GlideRecord('sys_script_include');",
|
|
418
|
-
" si.addQuery('name', 'CONTAINS', 'FlowDesigner');",
|
|
419
|
-
" si.addOrCondition('name', 'CONTAINS', 'FlowCompiler');",
|
|
420
|
-
" si.addOrCondition('name', 'CONTAINS', 'FlowPublish');",
|
|
421
|
-
" si.addQuery('active', true);",
|
|
422
|
-
" si.setLimit(15); si.query();",
|
|
423
|
-
" r.script_includes = [];",
|
|
424
|
-
" while (si.next()) {",
|
|
425
|
-
" r.script_includes.push(si.getValue('name') + ' [api:' + si.getValue('api_name') + ', scope:' + si.getValue('sys_scope') + ']');",
|
|
426
|
-
" }",
|
|
427
|
-
"",
|
|
428
|
-
// Try instantiating common Script Includes
|
|
429
|
-
" var tryClasses = ['FlowDesignerScriptable', 'FlowDesignerService', 'FlowDesignerPublishService',",
|
|
430
|
-
" 'FlowDesignerCompileService', 'FlowDesignerActionService', 'sn_fd.FlowDesignerScriptable'];",
|
|
431
|
-
" r.script_include_tests = {};",
|
|
432
|
-
" for (var tc = 0; tc < tryClasses.length; tc++) {",
|
|
433
|
-
" try {",
|
|
434
|
-
" var cls = eval(tryClasses[tc]);",
|
|
435
|
-
" if (typeof cls === 'function') {",
|
|
436
|
-
" var inst = new cls();",
|
|
437
|
-
" r.script_include_tests[tryClasses[tc]] = 'instantiated';",
|
|
438
|
-
// Try compile/publish methods on instance
|
|
439
|
-
" if (typeof inst.compile === 'function') {",
|
|
440
|
-
" try { inst.compile(current.sys_id + ''); r.script_include_tests[tryClasses[tc] + '.compile'] = 'success'; }",
|
|
441
|
-
" catch(e) { r.script_include_tests[tryClasses[tc] + '.compile'] = (e + '').substring(0, 100); }",
|
|
442
|
-
" }",
|
|
443
|
-
" if (typeof inst.publishFlow === 'function') {",
|
|
444
|
-
" try { inst.publishFlow(current.sys_id + ''); r.script_include_tests[tryClasses[tc] + '.publishFlow'] = 'success'; }",
|
|
445
|
-
" catch(e) { r.script_include_tests[tryClasses[tc] + '.publishFlow'] = (e + '').substring(0, 100); }",
|
|
446
|
-
" }",
|
|
447
|
-
" } else { r.script_include_tests[tryClasses[tc]] = 'not a function: ' + typeof cls; }",
|
|
448
|
-
" } catch(e) { r.script_include_tests[tryClasses[tc]] = 'error: ' + (e + '').substring(0, 100); }",
|
|
449
|
-
" }",
|
|
450
|
-
" } else { r.sn_fd = 'unavailable'; }",
|
|
451
|
-
"",
|
|
452
|
-
// Post-check: read back flow state
|
|
453
|
-
" var post = new GlideRecord('sys_hub_flow');",
|
|
454
|
-
" if (post.get(current.sys_id + '')) {",
|
|
455
|
-
" r.post_latest_version = post.getValue('latest_version') + '';",
|
|
456
|
-
" r.post_latest_snapshot = post.getValue('latest_snapshot') + '';",
|
|
457
|
-
" r.post_status = post.getValue('status') + '';",
|
|
458
|
-
" }",
|
|
459
|
-
" } catch(topE) { r.error = (topE.getMessage ? topE.getMessage() : topE + '').substring(0, 300); }",
|
|
460
|
-
" gs.setProperty(PROP, JSON.stringify(r));",
|
|
461
|
-
"})(current, previous);"
|
|
462
|
-
].join('\n');
|
|
463
|
-
|
|
464
|
-
try {
|
|
465
|
-
// 1. Create result property
|
|
466
|
-
await client.post('/api/now/table/sys_properties', {
|
|
467
|
-
name: brResultProp,
|
|
468
|
-
value: 'pending',
|
|
469
|
-
type: 'string',
|
|
470
|
-
description: 'Snow-Flow BR compile result (auto-cleanup)'
|
|
471
|
-
});
|
|
472
|
-
|
|
473
|
-
// 2. Create temporary Business Rule on sys_hub_flow
|
|
474
|
-
var brResp = await client.post('/api/now/table/sys_script', {
|
|
475
|
-
name: 'Snow-Flow Auto-Compile (temp-cleanup)',
|
|
476
|
-
collection: 'sys_hub_flow',
|
|
477
|
-
when: 'after',
|
|
478
|
-
action_insert: false,
|
|
479
|
-
action_update: true,
|
|
480
|
-
action_delete: false,
|
|
481
|
-
active: true,
|
|
482
|
-
script: brScript,
|
|
483
|
-
order: 10000,
|
|
484
|
-
filter_condition: 'sys_id=' + flowSysId
|
|
485
|
-
});
|
|
486
|
-
var brSysId = brResp.data?.result?.sys_id;
|
|
487
|
-
|
|
488
|
-
// 3. PATCH the flow record to trigger the BR (tiny description change)
|
|
489
|
-
await new Promise(resolve => setTimeout(resolve, 1000)); // Let BR register
|
|
490
|
-
await client.patch('/api/now/table/sys_hub_flow/' + flowSysId, {
|
|
491
|
-
description: (flowDescription || '').trim() + ' '
|
|
492
|
-
});
|
|
493
|
-
|
|
494
|
-
// 4. Poll for result (5 attempts, 2s each = 10s max)
|
|
495
|
-
for (var i = 0; i < 5; i++) {
|
|
496
|
-
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
497
|
-
var propResp = await client.get('/api/now/table/sys_properties', {
|
|
160
|
+
if (!trigDefId) {
|
|
161
|
+
try {
|
|
162
|
+
const resp = await client.get('/api/now/table/sys_hub_action_type_definition', {
|
|
498
163
|
params: {
|
|
499
|
-
sysparm_query: '
|
|
500
|
-
sysparm_fields: 'sys_id,
|
|
501
|
-
sysparm_limit: 1
|
|
164
|
+
sysparm_query: 'internal_nameSTARTSWITHsn_fd.trigger^internal_nameLIKE' + config.type,
|
|
165
|
+
sysparm_fields: 'sys_id,internal_name', sysparm_limit: 10
|
|
502
166
|
}
|
|
503
167
|
});
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
if (propValue && propValue !== 'pending') {
|
|
508
|
-
// Got result — clean up
|
|
509
|
-
try { if (propRecord?.sys_id) await client.delete('/api/now/table/sys_properties/' + propRecord.sys_id); } catch (_) {}
|
|
510
|
-
try { if (brSysId) await client.delete('/api/now/table/sys_script/' + brSysId); } catch (_) {}
|
|
511
|
-
|
|
512
|
-
try {
|
|
513
|
-
return { success: true, result: JSON.parse(propValue) };
|
|
514
|
-
} catch (_) {
|
|
515
|
-
return { success: true, result: { raw: propValue.substring(0, 500) } };
|
|
516
|
-
}
|
|
168
|
+
for (const r of (resp.data.result || [])) {
|
|
169
|
+
if ((r.internal_name || '').indexOf(config.type) > -1) { trigDefId = r.sys_id; break; }
|
|
517
170
|
}
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
// Timeout — clean up
|
|
521
|
-
try {
|
|
522
|
-
var cleanProp = await client.get('/api/now/table/sys_properties', {
|
|
523
|
-
params: { sysparm_query: 'name=' + brResultProp, sysparm_fields: 'sys_id', sysparm_limit: 1 }
|
|
524
|
-
});
|
|
525
|
-
if (cleanProp.data.result?.[0]?.sys_id) await client.delete('/api/now/table/sys_properties/' + cleanProp.data.result[0].sys_id);
|
|
526
171
|
} catch (_) {}
|
|
527
|
-
try { if (brSysId) await client.delete('/api/now/table/sys_script/' + brSysId); } catch (_) {}
|
|
528
|
-
|
|
529
|
-
return { success: false, error: 'Business Rule did not fire within 10s — BR may not have registered or flow update was ignored' };
|
|
530
|
-
} catch (e: any) {
|
|
531
|
-
return { success: false, error: 'BR compile setup failed: ' + (e.message || e) };
|
|
532
172
|
}
|
|
533
|
-
}
|
|
173
|
+
if (!trigDefId) return { success: false, error: 'Trigger definition not found for: ' + triggerType, steps };
|
|
174
|
+
steps.def_lookup = { id: trigDefId };
|
|
534
175
|
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
var FLOW_FACTORY_API_NAME = 'Snow-Flow Flow Factory';
|
|
538
|
-
var FLOW_FACTORY_API_ID = 'flow_factory';
|
|
539
|
-
var FLOW_FACTORY_CACHE_TTL = 300000; // 5 minutes
|
|
540
|
-
|
|
541
|
-
var _flowFactoryCache: { apiSysId: string; namespace: string; timestamp: number } | null = null;
|
|
542
|
-
var _bootstrapPromise: Promise<{ namespace: string; apiSysId: string }> | null = null;
|
|
543
|
-
|
|
544
|
-
/**
|
|
545
|
-
* ES5 GlideRecord script deployed as a Scripted REST API resource.
|
|
546
|
-
* This runs server-side on ServiceNow and triggers all Business Rules,
|
|
547
|
-
* unlike direct Table API inserts which skip sys_hub_flow_version creation.
|
|
548
|
-
*/
|
|
549
|
-
/**
|
|
550
|
-
* Discover script — GET /discover endpoint.
|
|
551
|
-
* Probes which sn_fd APIs, methods and fields are available on this instance.
|
|
552
|
-
*/
|
|
553
|
-
var FLOW_FACTORY_DISCOVER_SCRIPT = [
|
|
554
|
-
'(function process(/*RESTAPIRequest*/ request, /*RESTAPIResponse*/ response) {',
|
|
555
|
-
' var r = {',
|
|
556
|
-
' build_tag: gs.getProperty("glide.buildtag") || "unknown",',
|
|
557
|
-
' build_name: gs.getProperty("glide.buildname") || "unknown",',
|
|
558
|
-
' apis: {}, methods: {}, fields: {}',
|
|
559
|
-
' };',
|
|
560
|
-
' try { r.apis.sn_fd = typeof sn_fd !== "undefined" ? "available" : "unavailable"; } catch(e) { r.apis.sn_fd = "unavailable"; }',
|
|
561
|
-
' var apiNames = ["FlowDesigner","FlowAPI","FlowPublisher","FlowCompiler"];',
|
|
562
|
-
' for (var i = 0; i < apiNames.length; i++) {',
|
|
563
|
-
' try {',
|
|
564
|
-
' if (typeof sn_fd !== "undefined") { r.apis[apiNames[i]] = typeof sn_fd[apiNames[i]]; }',
|
|
565
|
-
' else { r.apis[apiNames[i]] = "no_sn_fd"; }',
|
|
566
|
-
' } catch(e) { r.apis[apiNames[i]] = "error:" + e; }',
|
|
567
|
-
' }',
|
|
568
|
-
' var globalNames = ["GlideFlowDesigner","FlowDesignerInternalAPI","FlowDesignerAPI"];',
|
|
569
|
-
' for (var g = 0; g < globalNames.length; g++) {',
|
|
570
|
-
' try {',
|
|
571
|
-
' var gv = this[globalNames[g]];',
|
|
572
|
-
' r.apis[globalNames[g]] = gv ? typeof gv : "undefined";',
|
|
573
|
-
' } catch(e) { r.apis[globalNames[g]] = "error:" + e; }',
|
|
574
|
-
' }',
|
|
575
|
-
' try {',
|
|
576
|
-
' if (typeof sn_fd !== "undefined" && sn_fd.FlowDesigner) {',
|
|
577
|
-
' var fd = sn_fd.FlowDesigner;',
|
|
578
|
-
' var mns = ["createFlow","publishFlow","activateFlow","compileFlow","createDraftFlow","getFlow"];',
|
|
579
|
-
' for (var m = 0; m < mns.length; m++) { r.methods[mns[m]] = typeof fd[mns[m]]; }',
|
|
580
|
-
' }',
|
|
581
|
-
' } catch(e) { r.methods._error = e + ""; }',
|
|
582
|
-
' try {',
|
|
583
|
-
' var gr = new GlideRecord("sys_hub_flow_version");',
|
|
584
|
-
' var fns = ["compiled_definition","compile_state","is_current","published_flow","flow_definition"];',
|
|
585
|
-
' for (var f = 0; f < fns.length; f++) { r.fields[fns[f]] = gr.isValidField(fns[f]); }',
|
|
586
|
-
' } catch(e) { r.fields._error = e + ""; }',
|
|
587
|
-
' try {',
|
|
588
|
-
' var br = new GlideRecord("sys_script");',
|
|
589
|
-
' br.addQuery("collection","sys_hub_flow"); br.addQuery("active",true); br.query();',
|
|
590
|
-
' r.flow_br_count = br.getRowCount();',
|
|
591
|
-
' var brv = new GlideRecord("sys_script");',
|
|
592
|
-
' brv.addQuery("collection","sys_hub_flow_version"); brv.addQuery("active",true); brv.query();',
|
|
593
|
-
' r.version_br_count = brv.getRowCount();',
|
|
594
|
-
' } catch(e) { r.br_error = e + ""; }',
|
|
595
|
-
' response.setStatus(200);',
|
|
596
|
-
' response.setBody(r);',
|
|
597
|
-
'})(request, response);'
|
|
598
|
-
].join('\n');
|
|
599
|
-
|
|
600
|
-
/**
|
|
601
|
-
* Create script — POST /create endpoint.
|
|
602
|
-
* Three-tier approach:
|
|
603
|
-
* Tier 1: sn_fd.FlowDesigner API (handles everything internally)
|
|
604
|
-
* Tier 2: GlideRecord INSERT as draft → UPDATE to published (triggers compilation BRs)
|
|
605
|
-
* Tier 3: Raw GlideRecord INSERT (last resort, may not register with engine)
|
|
606
|
-
*/
|
|
607
|
-
var FLOW_FACTORY_SCRIPT = [
|
|
608
|
-
'(function process(/*RESTAPIRequest*/ request, /*RESTAPIResponse*/ response) {',
|
|
609
|
-
// ── Body parsing (3-method cascade) ──
|
|
610
|
-
' var body = null;',
|
|
611
|
-
' var parseLog = [];',
|
|
612
|
-
' try {',
|
|
613
|
-
' var ds = request.body.dataString;',
|
|
614
|
-
' if (ds) { body = JSON.parse(ds + ""); parseLog.push("dataString:ok"); }',
|
|
615
|
-
' } catch(e1) { parseLog.push("dataString:" + e1); }',
|
|
616
|
-
' if (!body) {',
|
|
617
|
-
' try {',
|
|
618
|
-
' var d = request.body.data;',
|
|
619
|
-
' if (d && typeof d === "object") { body = d; parseLog.push("data:ok"); }',
|
|
620
|
-
' else if (d) { body = JSON.parse(d + ""); parseLog.push("data-parse:ok"); }',
|
|
621
|
-
' } catch(e2) { parseLog.push("data:" + e2); }',
|
|
622
|
-
' }',
|
|
623
|
-
' if (!body) {',
|
|
624
|
-
' try { body = JSON.parse(request.body + ""); parseLog.push("body-direct:ok"); }',
|
|
625
|
-
' catch(e3) { parseLog.push("body-direct:" + e3); }',
|
|
626
|
-
' }',
|
|
627
|
-
' if (!body || typeof body !== "object") {',
|
|
628
|
-
' response.setStatus(400);',
|
|
629
|
-
' response.setBody({ success: false, error: "No parseable body", parseLog: parseLog, bodyType: typeof request.body });',
|
|
630
|
-
' return;',
|
|
631
|
-
' }',
|
|
632
|
-
' var result = { success: false, steps: {}, tier_used: null, parseLog: parseLog };',
|
|
633
|
-
'',
|
|
634
|
-
' try {',
|
|
635
|
-
' var flowName = body.name || "Unnamed Flow";',
|
|
636
|
-
' var isSubflow = body.type === "subflow";',
|
|
637
|
-
' var flowDesc = body.description || flowName;',
|
|
638
|
-
' var flowCategory = body.category || "custom";',
|
|
639
|
-
' var runAs = body.run_as || "user";',
|
|
640
|
-
' var shouldActivate = body.activate !== false;',
|
|
641
|
-
' var triggerType = body.trigger_type || "manual";',
|
|
642
|
-
' var triggerTable = body.trigger_table || "";',
|
|
643
|
-
' var triggerCondition = body.trigger_condition || "";',
|
|
644
|
-
' var activities = body.activities || [];',
|
|
645
|
-
' var inputs = body.inputs || [];',
|
|
646
|
-
' var outputs = body.outputs || [];',
|
|
647
|
-
' var flowDef = body.flow_definition;',
|
|
648
|
-
' var flowDefStr = flowDef ? (typeof flowDef === "string" ? flowDef : JSON.stringify(flowDef)) : "";',
|
|
649
|
-
' var intName = flowName.toLowerCase().replace(/[^a-z0-9_]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "");',
|
|
650
|
-
'',
|
|
651
|
-
' var flowSysId = null;',
|
|
652
|
-
' var verSysId = null;',
|
|
653
|
-
'',
|
|
654
|
-
// ── TIER 1: sn_fd.FlowDesigner API ──
|
|
655
|
-
' try {',
|
|
656
|
-
' if (typeof sn_fd !== "undefined" && sn_fd.FlowDesigner && typeof sn_fd.FlowDesigner.createFlow === "function") {',
|
|
657
|
-
' var fdResult = sn_fd.FlowDesigner.createFlow({ name: flowName, description: flowDesc, type: isSubflow ? "subflow" : "flow", category: flowCategory, run_as: runAs });',
|
|
658
|
-
' if (fdResult) {',
|
|
659
|
-
' flowSysId = (typeof fdResult === "object" ? fdResult.sys_id || fdResult.getValue("sys_id") : fdResult) + "";',
|
|
660
|
-
' result.tier_used = "sn_fd_api";',
|
|
661
|
-
' result.steps.tier1 = { success: true, api: "sn_fd.FlowDesigner.createFlow" };',
|
|
662
|
-
' if (shouldActivate && typeof sn_fd.FlowDesigner.publishFlow === "function") {',
|
|
663
|
-
' try { sn_fd.FlowDesigner.publishFlow(flowSysId); result.steps.tier1_publish = { success: true }; }',
|
|
664
|
-
' catch(pe) { result.steps.tier1_publish = { success: false, error: pe + "" }; }',
|
|
665
|
-
' }',
|
|
666
|
-
' }',
|
|
667
|
-
' }',
|
|
668
|
-
' } catch(t1e) { result.steps.tier1 = { success: false, error: t1e.getMessage ? t1e.getMessage() : t1e + "" }; }',
|
|
669
|
-
'',
|
|
670
|
-
// ── TIER 2: GlideRecord INSERT as draft → UPDATE to published ──
|
|
671
|
-
' if (!flowSysId) {',
|
|
672
|
-
' try {',
|
|
673
|
-
' var flow = new GlideRecord("sys_hub_flow");',
|
|
674
|
-
' flow.initialize();',
|
|
675
|
-
' flow.setValue("name", flowName);',
|
|
676
|
-
' flow.setValue("description", flowDesc);',
|
|
677
|
-
' flow.setValue("internal_name", intName);',
|
|
678
|
-
' flow.setValue("category", flowCategory);',
|
|
679
|
-
' flow.setValue("run_as", runAs);',
|
|
680
|
-
' flow.setValue("active", false);',
|
|
681
|
-
' flow.setValue("status", "draft");',
|
|
682
|
-
' flow.setValue("validated", true);',
|
|
683
|
-
' flow.setValue("type", isSubflow ? "subflow" : "flow");',
|
|
684
|
-
' if (flowDefStr) { flow.setValue("flow_definition", flowDefStr); flow.setValue("latest_snapshot", flowDefStr); }',
|
|
685
|
-
' flowSysId = flow.insert();',
|
|
686
|
-
' result.steps.flow_insert = { success: !!flowSysId, sys_id: flowSysId + "", as_draft: true };',
|
|
687
|
-
'',
|
|
688
|
-
' if (flowSysId) {',
|
|
689
|
-
' var ver = new GlideRecord("sys_hub_flow_version");',
|
|
690
|
-
' ver.initialize();',
|
|
691
|
-
' ver.setValue("flow", flowSysId);',
|
|
692
|
-
' ver.setValue("name", "1.0");',
|
|
693
|
-
' ver.setValue("version", "1.0");',
|
|
694
|
-
' ver.setValue("state", "draft");',
|
|
695
|
-
' ver.setValue("active", true);',
|
|
696
|
-
' ver.setValue("compile_state", "draft");',
|
|
697
|
-
' ver.setValue("is_current", true);',
|
|
698
|
-
' if (flowDefStr) ver.setValue("flow_definition", flowDefStr);',
|
|
699
|
-
' verSysId = ver.insert();',
|
|
700
|
-
' result.steps.version_insert = { success: !!verSysId, sys_id: verSysId + "", as_draft: true };',
|
|
701
|
-
'',
|
|
702
|
-
' if (verSysId) {',
|
|
703
|
-
' var linkUpd = new GlideRecord("sys_hub_flow");',
|
|
704
|
-
' if (linkUpd.get(flowSysId)) { linkUpd.setValue("latest_version", verSysId); linkUpd.update(); }',
|
|
705
|
-
' }',
|
|
706
|
-
'',
|
|
707
|
-
' if (shouldActivate) {',
|
|
708
|
-
' var flowPub = new GlideRecord("sys_hub_flow");',
|
|
709
|
-
' if (flowPub.get(flowSysId)) {',
|
|
710
|
-
' flowPub.setValue("status", "published");',
|
|
711
|
-
' flowPub.setValue("active", true);',
|
|
712
|
-
' flowPub.update();',
|
|
713
|
-
' result.steps.flow_publish_update = { success: true };',
|
|
714
|
-
' }',
|
|
715
|
-
' if (verSysId) {',
|
|
716
|
-
' var verPub = new GlideRecord("sys_hub_flow_version");',
|
|
717
|
-
' if (verPub.get(verSysId)) {',
|
|
718
|
-
' verPub.setValue("state", "published");',
|
|
719
|
-
' verPub.setValue("compile_state", "compiled");',
|
|
720
|
-
' verPub.setValue("published_flow", flowSysId);',
|
|
721
|
-
' verPub.update();',
|
|
722
|
-
' result.steps.version_publish_update = { success: true };',
|
|
723
|
-
' }',
|
|
724
|
-
' }',
|
|
725
|
-
' }',
|
|
726
|
-
' result.tier_used = "gliderecord_draft_then_publish";',
|
|
727
|
-
' result.success = true;',
|
|
728
|
-
' }',
|
|
729
|
-
' } catch(t2e) { result.steps.tier2 = { success: false, error: t2e.getMessage ? t2e.getMessage() : t2e + "" }; }',
|
|
730
|
-
' }',
|
|
731
|
-
'',
|
|
732
|
-
// ── TIER 3: Raw GlideRecord INSERT (last resort) ──
|
|
733
|
-
' if (!flowSysId) {',
|
|
734
|
-
' try {',
|
|
735
|
-
' var rawFlow = new GlideRecord("sys_hub_flow");',
|
|
736
|
-
' rawFlow.initialize();',
|
|
737
|
-
' rawFlow.setValue("name", flowName);',
|
|
738
|
-
' rawFlow.setValue("description", flowDesc);',
|
|
739
|
-
' rawFlow.setValue("internal_name", intName);',
|
|
740
|
-
' rawFlow.setValue("category", flowCategory);',
|
|
741
|
-
' rawFlow.setValue("run_as", runAs);',
|
|
742
|
-
' rawFlow.setValue("active", shouldActivate);',
|
|
743
|
-
' rawFlow.setValue("status", shouldActivate ? "published" : "draft");',
|
|
744
|
-
' rawFlow.setValue("validated", true);',
|
|
745
|
-
' rawFlow.setValue("type", isSubflow ? "subflow" : "flow");',
|
|
746
|
-
' if (flowDefStr) { rawFlow.setValue("flow_definition", flowDefStr); rawFlow.setValue("latest_snapshot", flowDefStr); }',
|
|
747
|
-
' flowSysId = rawFlow.insert();',
|
|
748
|
-
' if (flowSysId) {',
|
|
749
|
-
' var rawVer = new GlideRecord("sys_hub_flow_version");',
|
|
750
|
-
' rawVer.initialize();',
|
|
751
|
-
' rawVer.setValue("flow", flowSysId);',
|
|
752
|
-
' rawVer.setValue("name", "1.0"); rawVer.setValue("version", "1.0");',
|
|
753
|
-
' rawVer.setValue("state", shouldActivate ? "published" : "draft");',
|
|
754
|
-
' rawVer.setValue("active", true); rawVer.setValue("compile_state", "compiled");',
|
|
755
|
-
' rawVer.setValue("is_current", true);',
|
|
756
|
-
' if (shouldActivate) rawVer.setValue("published_flow", flowSysId);',
|
|
757
|
-
' if (flowDefStr) rawVer.setValue("flow_definition", flowDefStr);',
|
|
758
|
-
' verSysId = rawVer.insert();',
|
|
759
|
-
' if (verSysId) {',
|
|
760
|
-
' var rawLink = new GlideRecord("sys_hub_flow");',
|
|
761
|
-
' if (rawLink.get(flowSysId)) { rawLink.setValue("latest_version", verSysId); rawLink.update(); }',
|
|
762
|
-
' }',
|
|
763
|
-
' result.tier_used = "gliderecord_raw";',
|
|
764
|
-
' result.success = true;',
|
|
765
|
-
' }',
|
|
766
|
-
' } catch(t3e) { result.steps.tier3 = { success: false, error: t3e.getMessage ? t3e.getMessage() : t3e + "" }; }',
|
|
767
|
-
' }',
|
|
768
|
-
'',
|
|
769
|
-
// ── Common: trigger, actions, variables ──
|
|
770
|
-
' if (flowSysId) {',
|
|
771
|
-
' result.flow_sys_id = flowSysId + "";',
|
|
772
|
-
' result.version_sys_id = verSysId ? verSysId + "" : null;',
|
|
773
|
-
' result.version_created = !!verSysId;',
|
|
774
|
-
'',
|
|
775
|
-
' if (!isSubflow && triggerType !== "manual") {',
|
|
776
|
-
' try {',
|
|
777
|
-
' var triggerMap = { "record_created": "sn_fd.trigger.record_created", "record_updated": "sn_fd.trigger.record_updated", "scheduled": "sn_fd.trigger.scheduled" };',
|
|
778
|
-
' var trigIntName = triggerMap[triggerType] || "";',
|
|
779
|
-
' if (trigIntName) {',
|
|
780
|
-
' var trigDef = new GlideRecord("sys_hub_action_type_definition");',
|
|
781
|
-
' trigDef.addQuery("internal_name", trigIntName); trigDef.query();',
|
|
782
|
-
' if (trigDef.next()) {',
|
|
783
|
-
' var trigInst = new GlideRecord("sys_hub_trigger_instance");',
|
|
784
|
-
' trigInst.initialize();',
|
|
785
|
-
' trigInst.setValue("flow", flowSysId); trigInst.setValue("action_type", trigDef.getUniqueValue());',
|
|
786
|
-
' trigInst.setValue("name", triggerType); trigInst.setValue("order", 0); trigInst.setValue("active", true);',
|
|
787
|
-
' if (triggerTable) trigInst.setValue("table", triggerTable);',
|
|
788
|
-
' if (triggerCondition) trigInst.setValue("condition", triggerCondition);',
|
|
789
|
-
' var trigSysId = trigInst.insert();',
|
|
790
|
-
' result.steps.trigger = { success: !!trigSysId, sys_id: trigSysId + "" };',
|
|
791
|
-
' } else { result.steps.trigger = { success: false, error: "Trigger def not found: " + trigIntName }; }',
|
|
792
|
-
' }',
|
|
793
|
-
' } catch(te) { result.steps.trigger = { success: false, error: te.getMessage ? te.getMessage() : te + "" }; }',
|
|
794
|
-
' }',
|
|
795
|
-
'',
|
|
796
|
-
' var actionsCreated = 0;',
|
|
797
|
-
' for (var ai = 0; ai < activities.length; ai++) {',
|
|
798
|
-
' try {',
|
|
799
|
-
' var act = activities[ai];',
|
|
800
|
-
' var actDef = new GlideRecord("sys_hub_action_type_definition");',
|
|
801
|
-
' actDef.addQuery("internal_name", "CONTAINS", act.type || "script");',
|
|
802
|
-
' actDef.addOrCondition("name", "CONTAINS", act.type || "script"); actDef.query();',
|
|
803
|
-
' var actInst = new GlideRecord("sys_hub_action_instance");',
|
|
804
|
-
' actInst.initialize();',
|
|
805
|
-
' actInst.setValue("flow", flowSysId); actInst.setValue("name", act.name || "Action " + (ai + 1));',
|
|
806
|
-
' actInst.setValue("order", (ai + 1) * 100); actInst.setValue("active", true);',
|
|
807
|
-
' if (actDef.next()) actInst.setValue("action_type", actDef.getUniqueValue());',
|
|
808
|
-
' if (actInst.insert()) actionsCreated++;',
|
|
809
|
-
' } catch(ae) {}',
|
|
810
|
-
' }',
|
|
811
|
-
' result.steps.actions = { success: true, created: actionsCreated, requested: activities.length };',
|
|
812
|
-
'',
|
|
813
|
-
' var varsCreated = 0;',
|
|
814
|
-
' if (isSubflow) {',
|
|
815
|
-
' for (var vi = 0; vi < inputs.length; vi++) {',
|
|
816
|
-
' try { var inp = inputs[vi]; var fv = new GlideRecord("sys_hub_flow_variable"); fv.initialize();',
|
|
817
|
-
' fv.setValue("flow",flowSysId); fv.setValue("name",inp.name); fv.setValue("label",inp.label||inp.name);',
|
|
818
|
-
' fv.setValue("type",inp.type||"string"); fv.setValue("mandatory",inp.mandatory||false);',
|
|
819
|
-
' fv.setValue("default_value",inp.default_value||""); fv.setValue("variable_type","input");',
|
|
820
|
-
' if (fv.insert()) varsCreated++;',
|
|
821
|
-
' } catch(ve) {}',
|
|
822
|
-
' }',
|
|
823
|
-
' for (var vo = 0; vo < outputs.length; vo++) {',
|
|
824
|
-
' try { var out = outputs[vo]; var ov = new GlideRecord("sys_hub_flow_variable"); ov.initialize();',
|
|
825
|
-
' ov.setValue("flow",flowSysId); ov.setValue("name",out.name); ov.setValue("label",out.label||out.name);',
|
|
826
|
-
' ov.setValue("type",out.type||"string"); ov.setValue("variable_type","output");',
|
|
827
|
-
' if (ov.insert()) varsCreated++;',
|
|
828
|
-
' } catch(ve) {}',
|
|
829
|
-
' }',
|
|
830
|
-
' }',
|
|
831
|
-
' result.steps.variables = { success: true, created: varsCreated };',
|
|
832
|
-
' }',
|
|
833
|
-
'',
|
|
834
|
-
' if (result.success) response.setStatus(201);',
|
|
835
|
-
' else response.setStatus(500);',
|
|
836
|
-
'',
|
|
837
|
-
' } catch (e) {',
|
|
838
|
-
' result.success = false;',
|
|
839
|
-
' result.error = e.getMessage ? e.getMessage() : e + "";',
|
|
840
|
-
' response.setStatus(500);',
|
|
841
|
-
' }',
|
|
842
|
-
' response.setBody(result);',
|
|
843
|
-
'})(request, response);'
|
|
844
|
-
].join('\n');
|
|
845
|
-
|
|
846
|
-
/**
|
|
847
|
-
* Resolve the REST API namespace for a sys_ws_definition record.
|
|
848
|
-
*
|
|
849
|
-
* Collects namespace candidates from multiple sources, then VERIFIES each
|
|
850
|
-
* one via HTTP before accepting it. This prevents returning invalid values
|
|
851
|
-
* like scope sys_ids or numeric identifiers that aren't valid URL namespaces.
|
|
852
|
-
*
|
|
853
|
-
* Verification: GET /api/{candidate}/{api_id}/discover
|
|
854
|
-
* - 200 = correct namespace, /discover endpoint works
|
|
855
|
-
* - 401/403 = correct namespace, auth issue
|
|
856
|
-
* - 405 = correct namespace, wrong HTTP method (endpoint exists)
|
|
857
|
-
* - 400/404 = wrong namespace or API not registered yet
|
|
858
|
-
*/
|
|
859
|
-
async function resolveFactoryNamespace(
|
|
860
|
-
client: any,
|
|
861
|
-
apiSysId: string,
|
|
862
|
-
instanceUrl: string
|
|
863
|
-
): Promise<string | null> {
|
|
864
|
-
// ── Collect namespace candidates (ordered by likelihood) ──
|
|
865
|
-
var candidates: string[] = [];
|
|
176
|
+
const params = await lookupDefinitionParams(client, trigDefId);
|
|
177
|
+
steps.params_found = params.length;
|
|
866
178
|
|
|
867
|
-
|
|
868
|
-
candidates.push('global');
|
|
179
|
+
const gqlInputs = params.map((p: any) => buildGraphQLInput(p));
|
|
869
180
|
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
}
|
|
877
|
-
});
|
|
878
|
-
var scopeStr = dotWalkResp.data.result?.['namespace.scope'];
|
|
879
|
-
if (scopeStr && typeof scopeStr === 'string' && scopeStr.length > 1) {
|
|
880
|
-
candidates.push(scopeStr);
|
|
881
|
-
}
|
|
882
|
-
} catch (_) {}
|
|
883
|
-
|
|
884
|
-
// Read namespace display_value (might be scope string itself)
|
|
885
|
-
try {
|
|
886
|
-
var nsResp = await client.get('/api/now/table/sys_ws_definition/' + apiSysId, {
|
|
887
|
-
params: { sysparm_fields: 'namespace', sysparm_display_value: 'true' }
|
|
888
|
-
});
|
|
889
|
-
var nsDisplay = nsResp.data.result?.namespace;
|
|
890
|
-
if (typeof nsDisplay === 'string' && nsDisplay.length > 1) {
|
|
891
|
-
candidates.push(nsDisplay === 'Global' ? 'global' : nsDisplay);
|
|
892
|
-
}
|
|
893
|
-
} catch (_) {}
|
|
894
|
-
|
|
895
|
-
// OOB namespace
|
|
896
|
-
candidates.push('now');
|
|
181
|
+
if (gqlInputs.length === 0 && config.triggerType === 'Record') {
|
|
182
|
+
gqlInputs.push(
|
|
183
|
+
buildGraphQLInput({ name: 'table', label: 'Table', internal_type: 'table_name', mandatory: 'true', order: '1' }),
|
|
184
|
+
buildGraphQLInput({ name: 'condition', label: 'Condition', internal_type: 'conditions', mandatory: 'false', order: '100', use_dependent: 'true', dependent_on: 'table' })
|
|
185
|
+
);
|
|
186
|
+
}
|
|
897
187
|
|
|
898
|
-
|
|
188
|
+
const triggerResponseFields = 'triggerInstances { inserts { sysId uiUniqueIdentifier __typename } updates deletes __typename }';
|
|
899
189
|
try {
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
190
|
+
const insertResult = await executeFlowPatchMutation(client, {
|
|
191
|
+
flowId: flowId,
|
|
192
|
+
triggerInstances: {
|
|
193
|
+
insert: [{
|
|
194
|
+
flowSysId: flowId,
|
|
195
|
+
name: config.name,
|
|
196
|
+
triggerType: config.triggerType,
|
|
197
|
+
triggerDefinitionId: trigDefId,
|
|
198
|
+
type: config.type,
|
|
199
|
+
hasDynamicOutputs: false,
|
|
200
|
+
metadata: '{"predicates":[]}',
|
|
201
|
+
inputs: gqlInputs,
|
|
202
|
+
outputs: []
|
|
203
|
+
}]
|
|
204
|
+
}
|
|
205
|
+
}, triggerResponseFields);
|
|
206
|
+
|
|
207
|
+
const triggerId = insertResult?.triggerInstances?.inserts?.[0]?.sysId;
|
|
208
|
+
steps.insert = { success: !!triggerId, triggerId };
|
|
209
|
+
if (!triggerId) return { success: false, steps, error: 'GraphQL trigger INSERT returned no trigger ID' };
|
|
210
|
+
|
|
211
|
+
if (table) {
|
|
212
|
+
const updateInputs: any[] = [
|
|
213
|
+
{
|
|
214
|
+
name: 'table',
|
|
215
|
+
displayField: 'number',
|
|
216
|
+
displayValue: { schemaless: false, schemalessValue: '', value: table.charAt(0).toUpperCase() + table.slice(1) },
|
|
217
|
+
value: { schemaless: false, schemalessValue: '', value: table }
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
name: 'condition',
|
|
221
|
+
displayValue: { schemaless: false, schemalessValue: '', value: condition || '^EQ' }
|
|
222
|
+
}
|
|
223
|
+
];
|
|
224
|
+
try {
|
|
225
|
+
await executeFlowPatchMutation(client, {
|
|
226
|
+
flowId: flowId,
|
|
227
|
+
triggerInstances: { update: [{ id: triggerId, inputs: updateInputs }] }
|
|
228
|
+
}, triggerResponseFields);
|
|
229
|
+
steps.update = { success: true, table, condition: condition || '^EQ' };
|
|
230
|
+
} catch (e: any) {
|
|
231
|
+
steps.update = { success: false, error: e.message };
|
|
905
232
|
}
|
|
906
|
-
});
|
|
907
|
-
var companyCode = compResp.data.result?.[0]?.value;
|
|
908
|
-
if (companyCode) candidates.push(companyCode);
|
|
909
|
-
} catch (_) {}
|
|
910
|
-
|
|
911
|
-
// Instance subdomain (e.g. "dev354059")
|
|
912
|
-
try {
|
|
913
|
-
var match = instanceUrl.match(/https?:\/\/([^.]+)\./);
|
|
914
|
-
if (match && match[1]) candidates.push(match[1]);
|
|
915
|
-
} catch (_) {}
|
|
916
|
-
|
|
917
|
-
// ── Deduplicate ──
|
|
918
|
-
var seen: Record<string, boolean> = {};
|
|
919
|
-
var unique: string[] = [];
|
|
920
|
-
for (var i = 0; i < candidates.length; i++) {
|
|
921
|
-
var lower = candidates[i].toLowerCase();
|
|
922
|
-
if (!seen[lower]) {
|
|
923
|
-
seen[lower] = true;
|
|
924
|
-
unique.push(lower);
|
|
925
233
|
}
|
|
926
|
-
}
|
|
927
234
|
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
try {
|
|
933
|
-
var discoverResp = await client.get('/api/' + ns + '/' + FLOW_FACTORY_API_ID + '/discover');
|
|
934
|
-
if (discoverResp.status === 200 || discoverResp.data) return ns;
|
|
935
|
-
} catch (discoverErr: any) {
|
|
936
|
-
var ds = discoverErr.response?.status;
|
|
937
|
-
if (ds === 401 || ds === 403 || ds === 405) return ns;
|
|
938
|
-
// 400/404 = wrong namespace or not registered yet
|
|
939
|
-
}
|
|
940
|
-
// Check 2: GET /create (POST-only, expect 405 for correct namespace)
|
|
941
|
-
try {
|
|
942
|
-
await client.get('/api/' + ns + '/' + FLOW_FACTORY_API_ID + '/create');
|
|
943
|
-
return ns; // 200 = unexpected but valid
|
|
944
|
-
} catch (createErr: any) {
|
|
945
|
-
var cs = createErr.response?.status;
|
|
946
|
-
if (cs === 405 || cs === 401 || cs === 403) return ns;
|
|
947
|
-
// 400/404 = wrong namespace (ServiceNow may return 400 instead of 405)
|
|
948
|
-
}
|
|
949
|
-
// Check 3: POST /create with empty body — distinguishes "wrong namespace" from
|
|
950
|
-
// "correct namespace but script validation error". Wrong namespace returns
|
|
951
|
-
// 400 with "Requested URI does not represent any resource". Our script returns
|
|
952
|
-
// a different error body (e.g. {success:false, error:"..."}).
|
|
953
|
-
try {
|
|
954
|
-
await client.post('/api/' + ns + '/' + FLOW_FACTORY_API_ID + '/create', {});
|
|
955
|
-
return ns; // Unexpected success, namespace confirmed
|
|
956
|
-
} catch (postErr: any) {
|
|
957
|
-
var pe = postErr.response;
|
|
958
|
-
if (!pe) continue;
|
|
959
|
-
if (pe.status === 401 || pe.status === 403) return ns;
|
|
960
|
-
// Check error body — "Requested URI" = wrong namespace, anything else = our script
|
|
961
|
-
var errStr = JSON.stringify(pe.data || '');
|
|
962
|
-
if (!errStr.includes('Requested URI')) return ns;
|
|
963
|
-
}
|
|
235
|
+
return { success: true, triggerId, steps };
|
|
236
|
+
} catch (e: any) {
|
|
237
|
+
steps.insert = { success: false, error: e.message };
|
|
238
|
+
return { success: false, steps, error: 'GraphQL trigger INSERT failed: ' + e.message };
|
|
964
239
|
}
|
|
965
|
-
|
|
966
|
-
return null; // No candidate verified — API may not be registered yet
|
|
967
240
|
}
|
|
968
241
|
|
|
969
|
-
|
|
970
|
-
* Ensure the Flow Factory Scripted REST API exists on the ServiceNow instance.
|
|
971
|
-
* Idempotent — checks cache first, then instance, deploys only if missing.
|
|
972
|
-
*
|
|
973
|
-
* Namespace resolution strategy:
|
|
974
|
-
* 1. Dot-walk to sys_scope.scope from the API record (deterministic, no HTTP probing)
|
|
975
|
-
* 2. Read namespace field with display_value=all (fallback)
|
|
976
|
-
* 3. Company code from sys_properties (fallback)
|
|
977
|
-
* 4. HTTP probing to /discover and /create endpoints (last resort)
|
|
978
|
-
*
|
|
979
|
-
* Stale API detection: if the API exists but has no /discover endpoint
|
|
980
|
-
* (created by an older tool version), it is deleted and redeployed.
|
|
981
|
-
*/
|
|
982
|
-
async function ensureFlowFactoryAPI(
|
|
242
|
+
async function addActionViaGraphQL(
|
|
983
243
|
client: any,
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
244
|
+
flowId: string,
|
|
245
|
+
actionType: string,
|
|
246
|
+
actionName: string,
|
|
247
|
+
inputs?: Record<string, string>,
|
|
248
|
+
order?: number
|
|
249
|
+
): Promise<{ success: boolean; actionId?: string; steps?: any; error?: string }> {
|
|
250
|
+
const steps: any = {};
|
|
251
|
+
|
|
252
|
+
const actionTypeNames: Record<string, string[]> = {
|
|
253
|
+
'log': ['sn_fd.action.log', 'global.sn_fd.action.log', 'global.log'],
|
|
254
|
+
'create_record': ['sn_fd.action.create_record', 'global.sn_fd.action.create_record'],
|
|
255
|
+
'update_record': ['sn_fd.action.update_record', 'global.sn_fd.action.update_record'],
|
|
256
|
+
'notification': ['sn_fd.action.send_notification', 'global.sn_fd.action.send_notification'],
|
|
257
|
+
'script': ['sn_fd.action.script', 'global.sn_fd.action.script', 'global.script_action'],
|
|
258
|
+
'field_update': ['sn_fd.action.field_update', 'global.sn_fd.action.field_update'],
|
|
259
|
+
'wait': ['sn_fd.action.wait', 'global.sn_fd.action.wait'],
|
|
260
|
+
'approval': ['sn_fd.action.create_approval', 'global.sn_fd.action.create_approval'],
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
let actionDefId: string | null = null;
|
|
264
|
+
const candidates = actionTypeNames[actionType] || [];
|
|
265
|
+
for (const name of candidates) {
|
|
266
|
+
try {
|
|
267
|
+
const resp = await client.get('/api/now/table/sys_hub_action_type_definition', {
|
|
268
|
+
params: { sysparm_query: 'internal_name=' + name, sysparm_fields: 'sys_id', sysparm_limit: 1 }
|
|
269
|
+
});
|
|
270
|
+
actionDefId = resp.data.result?.[0]?.sys_id || null;
|
|
271
|
+
if (actionDefId) break;
|
|
272
|
+
} catch (_) {}
|
|
994
273
|
}
|
|
995
|
-
|
|
996
|
-
_bootstrapPromise = (async () => {
|
|
274
|
+
if (!actionDefId) {
|
|
997
275
|
try {
|
|
998
|
-
|
|
999
|
-
var checkResp = await client.get('/api/now/table/sys_ws_definition', {
|
|
276
|
+
const resp = await client.get('/api/now/table/sys_hub_action_type_definition', {
|
|
1000
277
|
params: {
|
|
1001
|
-
sysparm_query: '
|
|
1002
|
-
sysparm_fields: 'sys_id,
|
|
1003
|
-
sysparm_limit: 1
|
|
278
|
+
sysparm_query: 'internal_nameLIKE' + actionType + '^ORnameLIKE' + actionType,
|
|
279
|
+
sysparm_fields: 'sys_id,internal_name', sysparm_limit: 5
|
|
1004
280
|
}
|
|
1005
281
|
});
|
|
282
|
+
actionDefId = resp.data.result?.[0]?.sys_id || null;
|
|
283
|
+
} catch (_) {}
|
|
284
|
+
}
|
|
285
|
+
if (!actionDefId) return { success: false, error: 'Action definition not found for: ' + actionType, steps };
|
|
286
|
+
steps.def_lookup = { id: actionDefId };
|
|
1006
287
|
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
var ns = await resolveFactoryNamespace(client, existing.sys_id, instanceUrl);
|
|
1010
|
-
|
|
1011
|
-
if (ns) {
|
|
1012
|
-
// Verify the API has v5 endpoints (check /discover exists)
|
|
1013
|
-
var hasDiscover = false;
|
|
1014
|
-
try {
|
|
1015
|
-
var verifyResp = await client.get('/api/' + ns + '/' + FLOW_FACTORY_API_ID + '/discover');
|
|
1016
|
-
hasDiscover = verifyResp.status === 200 || !!verifyResp.data;
|
|
1017
|
-
} catch (verifyErr: any) {
|
|
1018
|
-
var vs = verifyErr.response?.status;
|
|
1019
|
-
// 401/403 = endpoint exists but auth issue; 405 = exists but wrong method
|
|
1020
|
-
hasDiscover = vs === 401 || vs === 403 || vs === 405;
|
|
1021
|
-
}
|
|
1022
|
-
|
|
1023
|
-
if (hasDiscover) {
|
|
1024
|
-
_flowFactoryCache = { apiSysId: existing.sys_id, namespace: ns, timestamp: Date.now() };
|
|
1025
|
-
return { namespace: ns, apiSysId: existing.sys_id };
|
|
1026
|
-
}
|
|
1027
|
-
// /discover missing → stale API from older version, fall through to delete
|
|
1028
|
-
}
|
|
1029
|
-
|
|
1030
|
-
// Delete stale API and redeploy with current scripts
|
|
1031
|
-
invalidateFlowFactoryCache();
|
|
1032
|
-
try {
|
|
1033
|
-
await client.delete('/api/now/table/sys_ws_definition/' + existing.sys_id);
|
|
1034
|
-
} catch (_) {
|
|
1035
|
-
// If delete fails, try deployment anyway — will error on duplicate api_id
|
|
1036
|
-
}
|
|
1037
|
-
// Brief pause to let ServiceNow finalize the delete
|
|
1038
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
1039
|
-
}
|
|
288
|
+
const params = await lookupDefinitionParams(client, actionDefId);
|
|
289
|
+
steps.params_found = params.length;
|
|
1040
290
|
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
short_description: 'Bootstrapped by Snow-Flow MCP for reliable Flow Designer creation via GlideRecord',
|
|
1047
|
-
is_versioned: false,
|
|
1048
|
-
enforce_acl: 'false',
|
|
1049
|
-
requires_authentication: true
|
|
1050
|
-
});
|
|
291
|
+
const gqlInputs = params.map((p: any) => {
|
|
292
|
+
const pName = p.name || p.element || '';
|
|
293
|
+
const providedValue = inputs?.[pName];
|
|
294
|
+
return buildGraphQLInput(p, providedValue);
|
|
295
|
+
});
|
|
1051
296
|
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
297
|
+
const uuid = generateUUID();
|
|
298
|
+
const actionResponseFields = 'actions { inserts { sysId uiUniqueIdentifier __typename } updates deletes __typename }';
|
|
299
|
+
try {
|
|
300
|
+
const result = await executeFlowPatchMutation(client, {
|
|
301
|
+
flowId: flowId,
|
|
302
|
+
actions: {
|
|
303
|
+
insert: [{
|
|
304
|
+
actionTypeSysId: actionDefId,
|
|
305
|
+
metadata: '{"predicates":[]}',
|
|
306
|
+
flowSysId: flowId,
|
|
307
|
+
generationSource: '',
|
|
308
|
+
order: String(order || 1),
|
|
309
|
+
parent: '',
|
|
310
|
+
uiUniqueIdentifier: uuid,
|
|
311
|
+
type: 'action',
|
|
312
|
+
parentUiId: '',
|
|
313
|
+
inputs: gqlInputs
|
|
314
|
+
}]
|
|
1055
315
|
}
|
|
316
|
+
}, actionResponseFields);
|
|
1056
317
|
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
await client.post('/api/now/table/sys_ws_operation', {
|
|
1060
|
-
web_service_definition: apiSysId,
|
|
1061
|
-
http_method: 'POST',
|
|
1062
|
-
name: 'create',
|
|
1063
|
-
active: true,
|
|
1064
|
-
relative_path: '/create',
|
|
1065
|
-
short_description: 'Create a flow/subflow via 3-tier approach: sn_fd API → draft+publish → raw GlideRecord',
|
|
1066
|
-
operation_script: FLOW_FACTORY_SCRIPT,
|
|
1067
|
-
requires_authentication: true,
|
|
1068
|
-
enforce_acl: 'false'
|
|
1069
|
-
});
|
|
1070
|
-
} catch (opError: any) {
|
|
1071
|
-
// Cleanup the API definition if operation creation fails
|
|
1072
|
-
try { await client.delete('/api/now/table/sys_ws_definition/' + apiSysId); } catch (_) {}
|
|
1073
|
-
throw new Error('Failed to create Scripted REST operation: ' + (opError.message || opError));
|
|
1074
|
-
}
|
|
318
|
+
const actionId = result?.actions?.inserts?.[0]?.sysId;
|
|
319
|
+
steps.insert = { success: !!actionId, actionId, uuid };
|
|
1075
320
|
|
|
1076
|
-
|
|
321
|
+
if (actionId && inputs && Object.keys(inputs).length > 0) {
|
|
322
|
+
const updateInputs = Object.entries(inputs).map(([name, value]) => ({
|
|
323
|
+
name,
|
|
324
|
+
value: { schemaless: false, schemalessValue: '', value: String(value) }
|
|
325
|
+
}));
|
|
1077
326
|
try {
|
|
1078
|
-
await client
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
operation_script: FLOW_FACTORY_DISCOVER_SCRIPT,
|
|
1086
|
-
requires_authentication: true,
|
|
1087
|
-
enforce_acl: 'false'
|
|
1088
|
-
});
|
|
1089
|
-
} catch (_) {
|
|
1090
|
-
// Non-fatal: create endpoint is more important than discover
|
|
1091
|
-
}
|
|
1092
|
-
|
|
1093
|
-
// 6. Wait for ServiceNow REST framework to register endpoints
|
|
1094
|
-
await new Promise(resolve => setTimeout(resolve, 4000));
|
|
1095
|
-
|
|
1096
|
-
// 7. Resolve namespace via HTTP-verified probing
|
|
1097
|
-
var resolvedNs = await resolveFactoryNamespace(client, apiSysId, instanceUrl);
|
|
1098
|
-
|
|
1099
|
-
// 8. If resolution failed, wait longer and retry (some instances are slow)
|
|
1100
|
-
if (!resolvedNs) {
|
|
1101
|
-
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
1102
|
-
resolvedNs = await resolveFactoryNamespace(client, apiSysId, instanceUrl);
|
|
1103
|
-
}
|
|
1104
|
-
if (!resolvedNs) {
|
|
1105
|
-
throw new Error('Flow Factory deployed (sys_id=' + apiSysId + ') but no namespace candidate could be verified via HTTP after 9s. Candidates tried: global, dot-walk scope, display_value, now, company code, subdomain.');
|
|
327
|
+
await executeFlowPatchMutation(client, {
|
|
328
|
+
flowId: flowId,
|
|
329
|
+
actions: { update: [{ uiUniqueIdentifier: uuid, type: 'action', inputs: updateInputs }] }
|
|
330
|
+
}, actionResponseFields);
|
|
331
|
+
steps.value_update = { success: true };
|
|
332
|
+
} catch (e: any) {
|
|
333
|
+
steps.value_update = { success: false, error: e.message };
|
|
1106
334
|
}
|
|
1107
|
-
|
|
1108
|
-
_flowFactoryCache = { apiSysId: apiSysId, namespace: resolvedNs, timestamp: Date.now() };
|
|
1109
|
-
return { namespace: resolvedNs, apiSysId: apiSysId };
|
|
1110
|
-
|
|
1111
|
-
} finally {
|
|
1112
|
-
_bootstrapPromise = null;
|
|
1113
335
|
}
|
|
1114
|
-
})();
|
|
1115
|
-
|
|
1116
|
-
return _bootstrapPromise;
|
|
1117
|
-
}
|
|
1118
|
-
|
|
1119
|
-
/**
|
|
1120
|
-
* Invalidate the Flow Factory cache (e.g. on 404 when API was deleted externally).
|
|
1121
|
-
*/
|
|
1122
|
-
function invalidateFlowFactoryCache(): void {
|
|
1123
|
-
_flowFactoryCache = null;
|
|
1124
|
-
_discoveryCache = null;
|
|
1125
|
-
}
|
|
1126
|
-
|
|
1127
|
-
/**
|
|
1128
|
-
* Call the /discover endpoint on the Flow Factory API to learn which
|
|
1129
|
-
* sn_fd APIs and methods are available on the target ServiceNow instance.
|
|
1130
|
-
* Results are cached alongside the factory cache.
|
|
1131
|
-
*/
|
|
1132
|
-
var _discoveryCache: any = null;
|
|
1133
336
|
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
if (_discoveryCache) return _discoveryCache;
|
|
1139
|
-
try {
|
|
1140
|
-
var discoverEndpoint = '/api/' + namespace + '/' + FLOW_FACTORY_API_ID + '/discover';
|
|
1141
|
-
var resp = await client.get(discoverEndpoint);
|
|
1142
|
-
_discoveryCache = resp.data?.result || resp.data || null;
|
|
1143
|
-
return _discoveryCache;
|
|
1144
|
-
} catch (_) {
|
|
1145
|
-
return null;
|
|
337
|
+
return { success: true, actionId: actionId || undefined, steps };
|
|
338
|
+
} catch (e: any) {
|
|
339
|
+
steps.insert = { success: false, error: e.message };
|
|
340
|
+
return { success: false, steps, error: 'GraphQL action INSERT failed: ' + e.message };
|
|
1146
341
|
}
|
|
1147
342
|
}
|
|
1148
343
|
|
|
1149
|
-
|
|
1150
|
-
* After creating a flow (via any method), try to register it with the
|
|
1151
|
-
* Flow Designer engine by calling its built-in compile / publish / activate
|
|
1152
|
-
* REST endpoints. Without this step the flow record exists in the DB but
|
|
1153
|
-
* Flow Designer cannot open it ("Your flow cannot be found").
|
|
1154
|
-
*
|
|
1155
|
-
* Tries multiple known endpoint patterns because the namespace/path changed
|
|
1156
|
-
* across ServiceNow releases. Returns diagnostics on which attempts were
|
|
1157
|
-
* made and what succeeded.
|
|
1158
|
-
*/
|
|
1159
|
-
async function registerFlowWithEngine(
|
|
344
|
+
async function createFlowViaProcessFlowAPI(
|
|
1160
345
|
client: any,
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
async function tryPost(label: string, url: string, body?: any): Promise<boolean> {
|
|
1168
|
-
try {
|
|
1169
|
-
await client.post(url, body || {});
|
|
1170
|
-
attempts.push(label + ': success');
|
|
1171
|
-
return true;
|
|
1172
|
-
} catch (e: any) {
|
|
1173
|
-
var s = e.response?.status || 'err';
|
|
1174
|
-
var errBody = '';
|
|
1175
|
-
try { errBody = JSON.stringify(e.response?.data || '').substring(0, 150); } catch (_) {}
|
|
1176
|
-
attempts.push(label + ': ' + s + (errBody ? ' ' + errBody : ''));
|
|
1177
|
-
return false;
|
|
1178
|
-
}
|
|
346
|
+
params: {
|
|
347
|
+
name: string;
|
|
348
|
+
description: string;
|
|
349
|
+
isSubflow: boolean;
|
|
350
|
+
runAs: string;
|
|
351
|
+
shouldActivate: boolean;
|
|
1179
352
|
}
|
|
353
|
+
): Promise<{
|
|
354
|
+
success: boolean;
|
|
355
|
+
flowSysId?: string;
|
|
356
|
+
versionCreated?: boolean;
|
|
357
|
+
flowData?: any;
|
|
358
|
+
error?: string;
|
|
359
|
+
}> {
|
|
360
|
+
try {
|
|
361
|
+
var flowResp = await client.post(
|
|
362
|
+
'/api/now/processflow/flow',
|
|
363
|
+
{
|
|
364
|
+
access: 'public',
|
|
365
|
+
description: params.description || '',
|
|
366
|
+
flowPriority: 'MEDIUM',
|
|
367
|
+
name: params.name,
|
|
368
|
+
protection: '',
|
|
369
|
+
runAs: params.runAs || 'user',
|
|
370
|
+
runWithRoles: { value: '', displayValue: '' },
|
|
371
|
+
scope: 'global',
|
|
372
|
+
scopeDisplayName: '',
|
|
373
|
+
scopeName: '',
|
|
374
|
+
security: { can_read: true, can_write: true },
|
|
375
|
+
status: 'draft',
|
|
376
|
+
type: params.isSubflow ? 'subflow' : 'flow',
|
|
377
|
+
userHasRolesAssignedToFlow: true,
|
|
378
|
+
active: false,
|
|
379
|
+
deleted: false
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
params: {
|
|
383
|
+
param_only_properties: 'true',
|
|
384
|
+
sysparm_transaction_scope: 'global'
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
);
|
|
1180
388
|
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
'/api/sn_fd/designer/flow/' + flowSysId + '/publish',
|
|
1186
|
-
'/api/sn_flow_designer/flow/' + flowSysId + '/publish',
|
|
1187
|
-
];
|
|
1188
|
-
for (var pi = 0; pi < publishPaths.length; pi++) {
|
|
1189
|
-
if (await tryPost('publish[' + pi + ']', publishPaths[pi])) {
|
|
1190
|
-
return { success: true, method: 'publish', attempts: attempts };
|
|
389
|
+
var flowResult = flowResp.data?.result?.data;
|
|
390
|
+
if (!flowResult?.id) {
|
|
391
|
+
var errDetail = flowResp.data?.result?.errorMessage || 'no flow id returned';
|
|
392
|
+
return { success: false, error: 'ProcessFlow API: ' + errDetail };
|
|
1191
393
|
}
|
|
1192
|
-
}
|
|
1193
394
|
|
|
1194
|
-
|
|
1195
|
-
if (shouldActivate) {
|
|
1196
|
-
var activatePaths = [
|
|
1197
|
-
'/api/sn_fd/flow/' + flowSysId + '/activate',
|
|
1198
|
-
'/api/sn_fd/designer/flow/' + flowSysId + '/activate',
|
|
1199
|
-
'/api/sn_flow_designer/flow/' + flowSysId + '/activate',
|
|
1200
|
-
];
|
|
1201
|
-
for (var ai = 0; ai < activatePaths.length; ai++) {
|
|
1202
|
-
if (await tryPost('activate[' + ai + ']', activatePaths[ai])) {
|
|
1203
|
-
return { success: true, method: 'activate', attempts: attempts };
|
|
1204
|
-
}
|
|
1205
|
-
}
|
|
1206
|
-
}
|
|
395
|
+
var flowSysId = flowResult.id;
|
|
1207
396
|
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
return { success: true, method: 'checkout+checkin', attempts: attempts };
|
|
397
|
+
var versionCreated = false;
|
|
398
|
+
try {
|
|
399
|
+
await client.post(
|
|
400
|
+
'/api/now/processflow/versioning/create_version',
|
|
401
|
+
{ item_sys_id: flowSysId, type: 'Autosave', annotation: '', favorite: false },
|
|
402
|
+
{ params: { sysparm_transaction_scope: 'global' } }
|
|
403
|
+
);
|
|
404
|
+
versionCreated = true;
|
|
405
|
+
} catch (_) {
|
|
1218
406
|
}
|
|
1219
|
-
}
|
|
1220
407
|
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
if (await tryPost('snapshot-alt', '/api/sn_fd/flow/' + flowSysId + '/snapshot')) {
|
|
1227
|
-
return { success: true, method: 'snapshot-alt', attempts: attempts };
|
|
408
|
+
return { success: true, flowSysId, versionCreated, flowData: flowResult };
|
|
409
|
+
} catch (e: any) {
|
|
410
|
+
var msg = e.message || '';
|
|
411
|
+
try { msg += ' — ' + JSON.stringify(e.response?.data || '').substring(0, 200); } catch (_) {}
|
|
412
|
+
return { success: false, error: 'ProcessFlow API: ' + msg };
|
|
1228
413
|
}
|
|
1229
|
-
|
|
1230
|
-
return { success: false, method: 'none', attempts: attempts };
|
|
1231
414
|
}
|
|
1232
415
|
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
* string it is returned as-is.
|
|
1236
|
-
*/
|
|
416
|
+
// ── resolve helpers ───────────────────────────────────────────────────
|
|
417
|
+
|
|
1237
418
|
async function resolveFlowId(client: any, flowId: string): Promise<string> {
|
|
1238
419
|
if (isSysId(flowId)) return flowId;
|
|
1239
420
|
|
|
@@ -1268,17 +449,15 @@ export const toolDefinition: MCPToolDefinition = {
|
|
|
1268
449
|
properties: {
|
|
1269
450
|
action: {
|
|
1270
451
|
type: 'string',
|
|
1271
|
-
enum: ['create', 'create_subflow', 'list', 'get', 'update', 'activate', 'deactivate', 'delete', 'publish'],
|
|
452
|
+
enum: ['create', 'create_subflow', 'list', 'get', 'update', 'activate', 'deactivate', 'delete', 'publish', 'add_trigger', 'add_action'],
|
|
1272
453
|
description: 'Action to perform'
|
|
1273
454
|
},
|
|
1274
455
|
|
|
1275
|
-
// ── shared identifiers ──
|
|
1276
456
|
flow_id: {
|
|
1277
457
|
type: 'string',
|
|
1278
458
|
description: 'Flow sys_id or name (required for get, update, activate, deactivate, delete, publish)'
|
|
1279
459
|
},
|
|
1280
460
|
|
|
1281
|
-
// ── create / create_subflow params ──
|
|
1282
461
|
name: {
|
|
1283
462
|
type: 'string',
|
|
1284
463
|
description: 'Flow name (required for create / create_subflow)'
|
|
@@ -1359,8 +538,6 @@ export const toolDefinition: MCPToolDefinition = {
|
|
|
1359
538
|
description: 'Activate flow after creation (default: true)',
|
|
1360
539
|
default: true
|
|
1361
540
|
},
|
|
1362
|
-
|
|
1363
|
-
// ── list params ──
|
|
1364
541
|
type: {
|
|
1365
542
|
type: 'string',
|
|
1366
543
|
enum: ['flow', 'subflow', 'all'],
|
|
@@ -1377,8 +554,20 @@ export const toolDefinition: MCPToolDefinition = {
|
|
|
1377
554
|
description: 'Max results for list',
|
|
1378
555
|
default: 50
|
|
1379
556
|
},
|
|
1380
|
-
|
|
1381
|
-
|
|
557
|
+
action_type: {
|
|
558
|
+
type: 'string',
|
|
559
|
+
enum: ['log', 'create_record', 'update_record', 'notification', 'script', 'field_update', 'wait', 'approval'],
|
|
560
|
+
description: 'Action type to add (for add_action)',
|
|
561
|
+
default: 'log'
|
|
562
|
+
},
|
|
563
|
+
action_name: {
|
|
564
|
+
type: 'string',
|
|
565
|
+
description: 'Display name for the action (for add_action)'
|
|
566
|
+
},
|
|
567
|
+
action_inputs: {
|
|
568
|
+
type: 'object',
|
|
569
|
+
description: 'Key-value pairs for action inputs (e.g. {log_message: "test", log_level: "info"})'
|
|
570
|
+
},
|
|
1382
571
|
update_fields: {
|
|
1383
572
|
type: 'object',
|
|
1384
573
|
description: 'Fields to update (for update action)',
|
|
@@ -1405,7 +594,7 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
|
|
|
1405
594
|
switch (action) {
|
|
1406
595
|
|
|
1407
596
|
// ────────────────────────────────────────────────────────────────
|
|
1408
|
-
// CREATE
|
|
597
|
+
// CREATE
|
|
1409
598
|
// ────────────────────────────────────────────────────────────────
|
|
1410
599
|
case 'create':
|
|
1411
600
|
case 'create_subflow': {
|
|
@@ -1468,7 +657,7 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
|
|
|
1468
657
|
delete flowDefinition.trigger;
|
|
1469
658
|
}
|
|
1470
659
|
|
|
1471
|
-
// ── Pipeline:
|
|
660
|
+
// ── Pipeline: ProcessFlow API (primary) → Table API (fallback) ──
|
|
1472
661
|
var flowSysId: string | null = null;
|
|
1473
662
|
var usedMethod = 'table_api';
|
|
1474
663
|
var versionCreated = false;
|
|
@@ -1477,82 +666,86 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
|
|
|
1477
666
|
var actionsCreated = 0;
|
|
1478
667
|
var varsCreated = 0;
|
|
1479
668
|
|
|
1480
|
-
// Diagnostics
|
|
669
|
+
// Diagnostics
|
|
1481
670
|
var diagnostics: any = {
|
|
671
|
+
processflow_api: null,
|
|
1482
672
|
table_api_used: false,
|
|
1483
673
|
version_created: false,
|
|
1484
674
|
version_method: null,
|
|
1485
675
|
post_verify: null
|
|
1486
676
|
};
|
|
1487
677
|
|
|
1488
|
-
// ──
|
|
1489
|
-
//
|
|
1490
|
-
//
|
|
1491
|
-
// Also attempts sn_fd engine registration server-side.
|
|
678
|
+
// ── ProcessFlow API (primary — same REST endpoint as Flow Designer UI) ──
|
|
679
|
+
// Uses /api/now/processflow/flow to create engine-registered flows,
|
|
680
|
+
// then /api/now/processflow/versioning/create_version for versioning.
|
|
1492
681
|
try {
|
|
1493
|
-
var
|
|
682
|
+
var pfResult = await createFlowViaProcessFlowAPI(client, {
|
|
1494
683
|
name: flowName,
|
|
1495
684
|
description: flowDescription,
|
|
1496
|
-
internalName: sanitizeInternalName(flowName),
|
|
1497
685
|
isSubflow: isSubflow,
|
|
1498
|
-
category: flowCategory,
|
|
1499
686
|
runAs: flowRunAs,
|
|
1500
|
-
shouldActivate: shouldActivate
|
|
1501
|
-
triggerType: triggerType,
|
|
1502
|
-
triggerTable: flowTable,
|
|
1503
|
-
triggerCondition: triggerCondition,
|
|
1504
|
-
activities: activitiesArg.map(function (act: any, idx: number) {
|
|
1505
|
-
return { name: act.name, type: act.type || 'script', inputs: act.inputs || {} };
|
|
1506
|
-
}),
|
|
1507
|
-
inputs: inputsArg,
|
|
1508
|
-
outputs: outputsArg,
|
|
1509
|
-
flowDefinition: flowDefinition
|
|
687
|
+
shouldActivate: shouldActivate
|
|
1510
688
|
});
|
|
1511
|
-
diagnostics.
|
|
1512
|
-
success:
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
latestVersionValue: scheduledResult.latestVersionValue,
|
|
1516
|
-
steps: scheduledResult.steps,
|
|
1517
|
-
error: scheduledResult.error
|
|
689
|
+
diagnostics.processflow_api = {
|
|
690
|
+
success: pfResult.success,
|
|
691
|
+
versionCreated: pfResult.versionCreated,
|
|
692
|
+
error: pfResult.error
|
|
1518
693
|
};
|
|
1519
|
-
if (
|
|
1520
|
-
flowSysId =
|
|
1521
|
-
usedMethod = '
|
|
1522
|
-
versionCreated = !!
|
|
694
|
+
if (pfResult.success && pfResult.flowSysId) {
|
|
695
|
+
flowSysId = pfResult.flowSysId;
|
|
696
|
+
usedMethod = 'processflow_api';
|
|
697
|
+
versionCreated = !!pfResult.versionCreated;
|
|
1523
698
|
diagnostics.version_created = versionCreated;
|
|
1524
|
-
diagnostics.version_method = '
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
699
|
+
diagnostics.version_method = 'processflow_api';
|
|
700
|
+
}
|
|
701
|
+
} catch (pfErr: any) {
|
|
702
|
+
diagnostics.processflow_api = { error: pfErr.message || 'unknown' };
|
|
703
|
+
factoryWarnings.push('ProcessFlow API failed: ' + (pfErr.message || pfErr));
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// ── Triggers, actions, variables for ProcessFlow-created flows ──
|
|
707
|
+
// Uses GraphQL mutations (same as Flow Designer UI) for proper engine registration
|
|
708
|
+
if (flowSysId && usedMethod === 'processflow_api') {
|
|
709
|
+
if (!isSubflow && triggerType !== 'manual') {
|
|
710
|
+
try {
|
|
711
|
+
var pfTrigResult = await addTriggerViaGraphQL(client, flowSysId, triggerType, flowTable, triggerCondition);
|
|
712
|
+
triggerCreated = pfTrigResult.success;
|
|
713
|
+
diagnostics.trigger_graphql = pfTrigResult;
|
|
714
|
+
} catch (_) { /* best-effort */ }
|
|
715
|
+
}
|
|
716
|
+
for (var pfai = 0; pfai < activitiesArg.length; pfai++) {
|
|
717
|
+
try {
|
|
718
|
+
var pfAct = activitiesArg[pfai];
|
|
719
|
+
var pfActResult = await addActionViaGraphQL(client, flowSysId, pfAct.type || 'log', pfAct.name || ('Action ' + (pfai + 1)), pfAct.inputs, pfai + 1);
|
|
720
|
+
if (pfActResult.success) actionsCreated++;
|
|
721
|
+
diagnostics['action_' + pfai] = pfActResult;
|
|
722
|
+
} catch (_) { /* best-effort */ }
|
|
723
|
+
}
|
|
724
|
+
if (isSubflow) {
|
|
725
|
+
for (var pfvi = 0; pfvi < inputsArg.length; pfvi++) {
|
|
726
|
+
try {
|
|
727
|
+
var pfInp = inputsArg[pfvi];
|
|
728
|
+
await client.post('/api/now/table/sys_hub_flow_variable', {
|
|
729
|
+
flow: flowSysId, name: pfInp.name, label: pfInp.label || pfInp.name,
|
|
730
|
+
type: pfInp.type || 'string', mandatory: pfInp.mandatory || false,
|
|
731
|
+
default_value: pfInp.default_value || '', variable_type: 'input'
|
|
732
|
+
});
|
|
733
|
+
varsCreated++;
|
|
734
|
+
} catch (_) {}
|
|
1539
735
|
}
|
|
1540
|
-
|
|
1541
|
-
|
|
736
|
+
for (var pfvo = 0; pfvo < outputsArg.length; pfvo++) {
|
|
737
|
+
try {
|
|
738
|
+
var pfOut = outputsArg[pfvo];
|
|
739
|
+
await client.post('/api/now/table/sys_hub_flow_variable', {
|
|
740
|
+
flow: flowSysId, name: pfOut.name, label: pfOut.label || pfOut.name,
|
|
741
|
+
type: pfOut.type || 'string', variable_type: 'output'
|
|
742
|
+
});
|
|
743
|
+
varsCreated++;
|
|
744
|
+
} catch (_) {}
|
|
1542
745
|
}
|
|
1543
746
|
}
|
|
1544
|
-
} catch (schedErr: any) {
|
|
1545
|
-
diagnostics.scheduled_job = { error: schedErr.message || 'unknown' };
|
|
1546
|
-
factoryWarnings.push('Scheduled job failed: ' + (schedErr.message || schedErr));
|
|
1547
747
|
}
|
|
1548
748
|
|
|
1549
|
-
// BR compile + engine REST registration skipped:
|
|
1550
|
-
// - BR: FlowDesigner unavailable in all contexts, FlowAPI.compile returns error,
|
|
1551
|
-
// all Script Includes (FlowDesignerScriptable etc.) don't exist on PDI instances
|
|
1552
|
-
// - REST: all sn_fd endpoints return 400 "Requested URI does not represent any resource"
|
|
1553
|
-
// Both add ~20s combined and provide zero value. Flow records are functional
|
|
1554
|
-
// (listable, queryable, deletable) but cannot be opened in Flow Designer UI.
|
|
1555
|
-
|
|
1556
749
|
// ── Table API fallback (last resort) ─────────────────────────
|
|
1557
750
|
if (!flowSysId) {
|
|
1558
751
|
diagnostics.table_api_used = true;
|
|
@@ -1626,91 +819,24 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
|
|
|
1626
819
|
factoryWarnings.push('sys_hub_flow_version creation failed: ' + (verError.message || verError));
|
|
1627
820
|
}
|
|
1628
821
|
|
|
1629
|
-
// Create trigger
|
|
1630
|
-
// Use precise prefix search, then fallback to creating without action_type
|
|
822
|
+
// Create trigger via GraphQL (same method as Flow Designer UI)
|
|
1631
823
|
if (!isSubflow && triggerType !== 'manual') {
|
|
1632
824
|
try {
|
|
1633
|
-
var
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
var exactTrigNames: Record<string, string[]> = {
|
|
1637
|
-
'record_created': ['sn_fd.trigger.record_created', 'global.sn_fd.trigger.record_created'],
|
|
1638
|
-
'record_updated': ['sn_fd.trigger.record_updated', 'global.sn_fd.trigger.record_updated'],
|
|
1639
|
-
'scheduled': ['sn_fd.trigger.scheduled', 'global.sn_fd.trigger.scheduled']
|
|
1640
|
-
};
|
|
1641
|
-
var exactCands = exactTrigNames[triggerType] || [];
|
|
1642
|
-
for (var eci = 0; eci < exactCands.length && !triggerDefId; eci++) {
|
|
1643
|
-
var exactResp = await client.get('/api/now/table/sys_hub_action_type_definition', {
|
|
1644
|
-
params: {
|
|
1645
|
-
sysparm_query: 'internal_name=' + exactCands[eci],
|
|
1646
|
-
sysparm_fields: 'sys_id',
|
|
1647
|
-
sysparm_limit: 1
|
|
1648
|
-
}
|
|
1649
|
-
});
|
|
1650
|
-
triggerDefId = exactResp.data.result?.[0]?.sys_id || null;
|
|
1651
|
-
}
|
|
1652
|
-
|
|
1653
|
-
// Search 2: STARTSWITH sn_fd.trigger
|
|
1654
|
-
if (!triggerDefId) {
|
|
1655
|
-
var prefixResp = await client.get('/api/now/table/sys_hub_action_type_definition', {
|
|
1656
|
-
params: {
|
|
1657
|
-
sysparm_query: 'internal_nameSTARTSWITHsn_fd.trigger',
|
|
1658
|
-
sysparm_fields: 'sys_id,internal_name',
|
|
1659
|
-
sysparm_limit: 10
|
|
1660
|
-
}
|
|
1661
|
-
});
|
|
1662
|
-
var prefixResults = prefixResp.data.result || [];
|
|
1663
|
-
for (var pri = 0; pri < prefixResults.length && !triggerDefId; pri++) {
|
|
1664
|
-
if ((prefixResults[pri].internal_name || '').indexOf(triggerType.replace('record_', '')) > -1) {
|
|
1665
|
-
triggerDefId = prefixResults[pri].sys_id;
|
|
1666
|
-
}
|
|
1667
|
-
}
|
|
1668
|
-
}
|
|
1669
|
-
|
|
1670
|
-
// Create trigger instance (with or without action_type)
|
|
1671
|
-
var triggerData: any = {
|
|
1672
|
-
flow: flowSysId,
|
|
1673
|
-
name: triggerType,
|
|
1674
|
-
order: 0,
|
|
1675
|
-
active: true
|
|
1676
|
-
};
|
|
1677
|
-
if (triggerDefId) triggerData.action_type = triggerDefId;
|
|
1678
|
-
if (flowTable) triggerData.table = flowTable;
|
|
1679
|
-
if (triggerCondition) triggerData.condition = triggerCondition;
|
|
1680
|
-
|
|
1681
|
-
await client.post('/api/now/table/sys_hub_trigger_instance', triggerData);
|
|
1682
|
-
triggerCreated = true;
|
|
825
|
+
var taTrigResult = await addTriggerViaGraphQL(client, flowSysId, triggerType, flowTable, triggerCondition);
|
|
826
|
+
triggerCreated = taTrigResult.success;
|
|
827
|
+
diagnostics.trigger_graphql = taTrigResult;
|
|
1683
828
|
} catch (triggerError) {
|
|
1684
829
|
// Best-effort
|
|
1685
830
|
}
|
|
1686
831
|
}
|
|
1687
832
|
|
|
1688
|
-
// Create
|
|
833
|
+
// Create actions via GraphQL (same method as Flow Designer UI)
|
|
1689
834
|
for (var ai = 0; ai < activitiesArg.length; ai++) {
|
|
1690
835
|
var activity = activitiesArg[ai];
|
|
1691
836
|
try {
|
|
1692
|
-
var
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
var actionDefResp = await client.get('/api/now/table/sys_hub_action_type_definition', {
|
|
1696
|
-
params: {
|
|
1697
|
-
sysparm_query: actionTypeQuery,
|
|
1698
|
-
sysparm_fields: 'sys_id,name,internal_name',
|
|
1699
|
-
sysparm_limit: 1
|
|
1700
|
-
}
|
|
1701
|
-
});
|
|
1702
|
-
|
|
1703
|
-
var actionDefId = actionDefResp.data.result?.[0]?.sys_id;
|
|
1704
|
-
var instanceData: any = {
|
|
1705
|
-
flow: flowSysId,
|
|
1706
|
-
name: activity.name,
|
|
1707
|
-
order: (ai + 1) * 100,
|
|
1708
|
-
active: true
|
|
1709
|
-
};
|
|
1710
|
-
if (actionDefId) instanceData.action_type = actionDefId;
|
|
1711
|
-
|
|
1712
|
-
await client.post('/api/now/table/sys_hub_action_instance', instanceData);
|
|
1713
|
-
actionsCreated++;
|
|
837
|
+
var taActResult = await addActionViaGraphQL(client, flowSysId, activity.type || 'log', activity.name || ('Action ' + (ai + 1)), activity.inputs, ai + 1);
|
|
838
|
+
if (taActResult.success) actionsCreated++;
|
|
839
|
+
diagnostics['action_' + ai] = taActResult;
|
|
1714
840
|
} catch (actError) {
|
|
1715
841
|
// Best-effort
|
|
1716
842
|
}
|
|
@@ -1815,11 +941,9 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
|
|
|
1815
941
|
}
|
|
1816
942
|
|
|
1817
943
|
// ── Build summary ───────────────────────────────────────────
|
|
1818
|
-
var methodLabel = usedMethod
|
|
1819
|
-
? '
|
|
1820
|
-
:
|
|
1821
|
-
? 'Scripted REST API (GlideRecord)'
|
|
1822
|
-
: 'Table API' + (factoryWarnings.length > 0 ? ' (fallback)' : '');
|
|
944
|
+
var methodLabel = usedMethod === 'processflow_api'
|
|
945
|
+
? 'ProcessFlow API (Flow Designer engine)'
|
|
946
|
+
: 'Table API' + (factoryWarnings.length > 0 ? ' (fallback)' : '');
|
|
1823
947
|
|
|
1824
948
|
var createSummary = summary()
|
|
1825
949
|
.success('Created ' + (isSubflow ? 'subflow' : 'flow') + ': ' + flowName)
|
|
@@ -1855,17 +979,13 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
|
|
|
1855
979
|
|
|
1856
980
|
// Diagnostics section
|
|
1857
981
|
createSummary.blank().line('Diagnostics:');
|
|
1858
|
-
if (diagnostics.
|
|
1859
|
-
var
|
|
1860
|
-
createSummary.indented('
|
|
1861
|
-
if (
|
|
982
|
+
if (diagnostics.processflow_api) {
|
|
983
|
+
var pf = diagnostics.processflow_api;
|
|
984
|
+
createSummary.indented('ProcessFlow API: ' + (pf.success ? 'success' : 'failed') + (pf.versionCreated ? ' (version created)' : ''));
|
|
985
|
+
if (pf.error) createSummary.indented(' Error: ' + pf.error);
|
|
1862
986
|
}
|
|
1863
|
-
createSummary.indented('Table API used: ' + diagnostics.table_api_used);
|
|
987
|
+
createSummary.indented('Table API fallback used: ' + diagnostics.table_api_used);
|
|
1864
988
|
createSummary.indented('Version created: ' + diagnostics.version_created + (diagnostics.version_method ? ' (' + diagnostics.version_method + ')' : ''));
|
|
1865
|
-
createSummary.indented('Engine compile: skipped (not available on this instance — FlowDesigner unavailable, REST 400s, no Script Includes)');
|
|
1866
|
-
if (diagnostics.latest_version_auto_set !== undefined) {
|
|
1867
|
-
createSummary.indented('latest_version: ' + (diagnostics.latest_version_auto_set ? 'set' : 'null'));
|
|
1868
|
-
}
|
|
1869
989
|
if (diagnostics.post_verify) {
|
|
1870
990
|
if (diagnostics.post_verify.error) {
|
|
1871
991
|
createSummary.indented('Post-verify: error — ' + diagnostics.post_verify.error);
|
|
@@ -2231,6 +1351,68 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
|
|
|
2231
1351
|
}, {}, deleteSummary.build());
|
|
2232
1352
|
}
|
|
2233
1353
|
|
|
1354
|
+
// ────────────────────────────────────────────────────────────────
|
|
1355
|
+
// ADD_TRIGGER
|
|
1356
|
+
// ────────────────────────────────────────────────────────────────
|
|
1357
|
+
case 'add_trigger': {
|
|
1358
|
+
if (!args.flow_id) {
|
|
1359
|
+
throw new SnowFlowError(ErrorType.VALIDATION_ERROR, 'flow_id is required for add_trigger');
|
|
1360
|
+
}
|
|
1361
|
+
var addTrigFlowId = await resolveFlowId(client, args.flow_id);
|
|
1362
|
+
var addTrigType = args.trigger_type || 'record_create_or_update';
|
|
1363
|
+
var addTrigTable = args.table || '';
|
|
1364
|
+
var addTrigCondition = args.trigger_condition || '';
|
|
1365
|
+
|
|
1366
|
+
var addTrigResult = await addTriggerViaGraphQL(client, addTrigFlowId, addTrigType, addTrigTable, addTrigCondition);
|
|
1367
|
+
|
|
1368
|
+
var addTrigSummary = summary();
|
|
1369
|
+
if (addTrigResult.success) {
|
|
1370
|
+
addTrigSummary
|
|
1371
|
+
.success('Trigger added via GraphQL')
|
|
1372
|
+
.field('Flow', addTrigFlowId)
|
|
1373
|
+
.field('Type', addTrigType)
|
|
1374
|
+
.field('Trigger ID', addTrigResult.triggerId || 'unknown');
|
|
1375
|
+
if (addTrigTable) addTrigSummary.field('Table', addTrigTable);
|
|
1376
|
+
} else {
|
|
1377
|
+
addTrigSummary.error('Failed to add trigger: ' + (addTrigResult.error || 'unknown'));
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
return addTrigResult.success
|
|
1381
|
+
? createSuccessResult({ action: 'add_trigger', ...addTrigResult }, {}, addTrigSummary.build())
|
|
1382
|
+
: createErrorResult(addTrigResult.error || 'Failed to add trigger');
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
// ────────────────────────────────────────────────────────────────
|
|
1386
|
+
// ADD_ACTION
|
|
1387
|
+
// ────────────────────────────────────────────────────────────────
|
|
1388
|
+
case 'add_action': {
|
|
1389
|
+
if (!args.flow_id) {
|
|
1390
|
+
throw new SnowFlowError(ErrorType.VALIDATION_ERROR, 'flow_id is required for add_action');
|
|
1391
|
+
}
|
|
1392
|
+
var addActFlowId = await resolveFlowId(client, args.flow_id);
|
|
1393
|
+
var addActType = args.action_type || 'log';
|
|
1394
|
+
var addActName = args.action_name || args.name || addActType;
|
|
1395
|
+
var addActInputs = args.action_inputs || args.inputs || {};
|
|
1396
|
+
|
|
1397
|
+
var addActResult = await addActionViaGraphQL(client, addActFlowId, addActType, addActName, addActInputs);
|
|
1398
|
+
|
|
1399
|
+
var addActSummary = summary();
|
|
1400
|
+
if (addActResult.success) {
|
|
1401
|
+
addActSummary
|
|
1402
|
+
.success('Action added via GraphQL')
|
|
1403
|
+
.field('Flow', addActFlowId)
|
|
1404
|
+
.field('Type', addActType)
|
|
1405
|
+
.field('Name', addActName)
|
|
1406
|
+
.field('Action ID', addActResult.actionId || 'unknown');
|
|
1407
|
+
} else {
|
|
1408
|
+
addActSummary.error('Failed to add action: ' + (addActResult.error || 'unknown'));
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
return addActResult.success
|
|
1412
|
+
? createSuccessResult({ action: 'add_action', ...addActResult }, {}, addActSummary.build())
|
|
1413
|
+
: createErrorResult(addActResult.error || 'Failed to add action');
|
|
1414
|
+
}
|
|
1415
|
+
|
|
2234
1416
|
default:
|
|
2235
1417
|
throw new SnowFlowError(ErrorType.VALIDATION_ERROR, 'Unknown action: ' + action);
|
|
2236
1418
|
}
|
|
@@ -2251,5 +1433,5 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
|
|
|
2251
1433
|
}
|
|
2252
1434
|
}
|
|
2253
1435
|
|
|
2254
|
-
export const version = '
|
|
1436
|
+
export const version = '6.0.0';
|
|
2255
1437
|
export const author = 'Snow-Flow Team';
|