snow-flow 8.41.8 → 8.41.14

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.
@@ -0,0 +1,2459 @@
1
+ "use strict";
2
+ /**
3
+ * snow_manage_flow - Unified Flow Designer Management via Direct Table API
4
+ *
5
+ * ⚠️ EXPERIMENTAL: Manages flows by directly manipulating Flow Designer tables.
6
+ * This approach is NOT officially supported by ServiceNow but enables programmatic flow management.
7
+ *
8
+ * Supported Actions:
9
+ * - create: Create a new flow with triggers, actions, conditions, loops, variables
10
+ * - update: Update an existing flow's properties
11
+ * - delete: Delete a flow and all related records
12
+ * - clone: Clone an existing flow
13
+ * - activate: Activate a flow
14
+ * - deactivate: Deactivate a flow
15
+ * - add_action: Add an action to an existing flow
16
+ * - remove_action: Remove an action from a flow
17
+ * - add_condition: Add a conditional logic block (if/then/else)
18
+ * - add_loop: Add a loop construct (for_each/do_until)
19
+ * - get_details: Get detailed flow structure including decompressed values
20
+ *
21
+ * Features:
22
+ * - Xanadu+ compression support (GlideCompressionUtil compatible)
23
+ * - Nested actions within conditions and loops
24
+ * - Parent-child relationship management
25
+ * - Version detection for table compatibility
26
+ *
27
+ * Flow Designer Table Structure:
28
+ * - sys_hub_flow: Main flow record
29
+ * - sys_hub_flow_snapshot: Flow version/snapshot
30
+ * - sys_hub_trigger_instance_v2: Trigger configuration
31
+ * - sys_hub_action_instance_v2: Action instances (with compressed values)
32
+ * - sys_hub_flow_logic_instance_v2: Conditions, loops (with parent_logic_instance)
33
+ * - sys_hub_flow_variable: Input/output variables
34
+ * - sys_hub_sub_flow_instance: Subflow references
35
+ *
36
+ * @version 2.0.0-experimental
37
+ * @author Snow-Flow v8.3.0 - Flow Designer Direct Table API
38
+ */
39
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
40
+ if (k2 === undefined) k2 = k;
41
+ var desc = Object.getOwnPropertyDescriptor(m, k);
42
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
43
+ desc = { enumerable: true, get: function() { return m[k]; } };
44
+ }
45
+ Object.defineProperty(o, k2, desc);
46
+ }) : (function(o, m, k, k2) {
47
+ if (k2 === undefined) k2 = k;
48
+ o[k2] = m[k];
49
+ }));
50
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
51
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
52
+ }) : function(o, v) {
53
+ o["default"] = v;
54
+ });
55
+ var __importStar = (this && this.__importStar) || (function () {
56
+ var ownKeys = function(o) {
57
+ ownKeys = Object.getOwnPropertyNames || function (o) {
58
+ var ar = [];
59
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
60
+ return ar;
61
+ };
62
+ return ownKeys(o);
63
+ };
64
+ return function (mod) {
65
+ if (mod && mod.__esModule) return mod;
66
+ var result = {};
67
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
68
+ __setModuleDefault(result, mod);
69
+ return result;
70
+ };
71
+ })();
72
+ Object.defineProperty(exports, "__esModule", { value: true });
73
+ exports.author = exports.version = exports.toolDefinition = void 0;
74
+ exports.execute = execute;
75
+ const auth_js_1 = require("../../shared/auth.js");
76
+ const error_handler_js_1 = require("../../shared/error-handler.js");
77
+ const crypto = __importStar(require("crypto"));
78
+ const zlib = __importStar(require("zlib"));
79
+ // ==================== CONSTANTS ====================
80
+ /**
81
+ * Compression header used by ServiceNow for V2 values fields
82
+ * Note: ServiceNow may not use a header - values are just gzip + base64
83
+ * We detect on read and write without header for compatibility
84
+ */
85
+ const COMPRESSED_HEADER = 'COMPRESSED:';
86
+ /**
87
+ * Flow Designer Table Structure
88
+ *
89
+ * CREATE ORDER (parent → children):
90
+ * 1. sys_hub_flow - Main flow record
91
+ * 2. sys_hub_trigger_* - Triggers (ref: flow)
92
+ * 3. sys_hub_flow_variable - Variables (ref: flow)
93
+ * 4. sys_hub_flow_logic_* - Conditions/Loops (ref: flow, parent_logic_instance)
94
+ * 5. sys_hub_action_* - Actions (ref: flow, parent_logic_instance, branch)
95
+ * 6. sys_hub_sub_flow_* - Subflow calls (ref: flow, parent_logic_instance)
96
+ * 7. sys_hub_flow_snapshot - Snapshots (ref: flow)
97
+ *
98
+ * DELETE ORDER (children → parent, hierarchical):
99
+ * 1. Actions - Children of logic
100
+ * 2. Subflow instances - Can be nested in logic
101
+ * 3. Logic instances - Deepest children first, then parents
102
+ * 4. Triggers
103
+ * 5. Variables
104
+ * 6. Snapshots
105
+ * 7. Flow - Main record last
106
+ *
107
+ * VERSION HISTORY:
108
+ * - Pre-Washington DC: V1 tables (no _v2 suffix)
109
+ * - Washington DC+: V2 tables (with _v2 suffix)
110
+ * - Xanadu+: V2 tables + compressed values (GZIP + Base64)
111
+ */
112
+ const FLOW_TABLES = {
113
+ // === CORE TABLES ===
114
+ FLOW: 'sys_hub_flow', // Main flow/subflow records
115
+ SNAPSHOT: 'sys_hub_flow_snapshot', // Version snapshots
116
+ VARIABLE: 'sys_hub_flow_variable', // Input/output/scratch variables
117
+ // === V2 TABLES (Washington DC+) ===
118
+ TRIGGER_V2: 'sys_hub_trigger_instance_v2', // Trigger instances
119
+ ACTION_V2: 'sys_hub_action_instance_v2', // Action instances (compressed values in Xanadu+)
120
+ LOGIC_V2: 'sys_hub_flow_logic_instance_v2', // Conditions, loops (supports nesting)
121
+ // === SUBFLOW TABLES ===
122
+ SUBFLOW: 'sys_hub_sub_flow_instance', // Subflow call instances
123
+ // === LEGACY V1 TABLES (Pre-Washington DC) ===
124
+ TRIGGER_V1: 'sys_hub_trigger_instance',
125
+ ACTION_V1: 'sys_hub_action_instance',
126
+ LOGIC_V1: 'sys_hub_flow_logic_instance',
127
+ // === DEFINITION TABLES (Read-only, for lookups) ===
128
+ TRIGGER_TYPE: 'sys_hub_trigger_type', // Available trigger types
129
+ ACTION_TYPE: 'sys_hub_action_type_base', // Available action types
130
+ // === ADDITIONAL TABLES (May be needed for complex flows) ===
131
+ STEP_INSTANCE: 'sys_hub_step_instance', // Step configurations
132
+ FLOW_INPUT: 'sys_hub_flow_input', // Flow inputs (legacy?)
133
+ FLOW_OUTPUT: 'sys_hub_flow_output', // Flow outputs (legacy?)
134
+ FLOW_STAGE: 'sys_hub_flow_stage' // Flow stages
135
+ };
136
+ // ==================== TOOL DEFINITION ====================
137
+ exports.toolDefinition = {
138
+ name: 'snow_manage_flow',
139
+ description: `Unified Flow Designer management via Direct Table API (EXPERIMENTAL).
140
+
141
+ ⚠️ WARNING: This tool manipulates Flow Designer tables directly.
142
+ This is NOT officially supported by ServiceNow.
143
+
144
+ Actions:
145
+ - create: Create a new flow with all components
146
+ - update: Update flow properties
147
+ - delete: Delete a flow and all related records
148
+ - clone: Clone an existing flow
149
+ - activate/deactivate: Change flow state
150
+ - add_action: Add an action to a flow
151
+ - remove_action: Remove an action
152
+ - add_condition: Add if/then/else logic
153
+ - add_loop: Add for_each/do_until loop
154
+ - add_variable: Add flow variable
155
+ - get_details: Get flow structure with decompressed values
156
+ - publish: Publish flow changes
157
+
158
+ Features:
159
+ - Xanadu+ compression support
160
+ - Nested actions in conditions/loops
161
+ - Parent-child relationship management
162
+ - Automatic version detection`,
163
+ category: 'automation',
164
+ subcategory: 'flow-designer',
165
+ use_cases: ['flow-management', 'automation', 'workflow'],
166
+ complexity: 'advanced',
167
+ frequency: 'medium',
168
+ permission: 'write',
169
+ allowedRoles: ['developer', 'admin'],
170
+ inputSchema: {
171
+ type: 'object',
172
+ properties: {
173
+ action: {
174
+ type: 'string',
175
+ enum: ['create', 'update', 'delete', 'clone', 'activate', 'deactivate',
176
+ 'add_action', 'remove_action', 'add_condition', 'add_loop',
177
+ 'add_variable', 'get_details', 'publish'],
178
+ description: 'Management action to perform'
179
+ },
180
+ flow: {
181
+ type: 'object',
182
+ description: '[create] Flow configuration',
183
+ properties: {
184
+ name: { type: 'string' },
185
+ internal_name: { type: 'string' },
186
+ description: { type: 'string' },
187
+ active: { type: 'boolean', default: false },
188
+ application_scope: { type: 'string' },
189
+ run_as: { type: 'string', enum: ['system', 'user_who_triggers', 'specific_user'] },
190
+ run_as_user: { type: 'string' }
191
+ }
192
+ },
193
+ trigger: {
194
+ type: 'object',
195
+ description: '[create] Trigger configuration'
196
+ },
197
+ actions: {
198
+ type: 'array',
199
+ description: '[create] Actions to add',
200
+ items: { type: 'object' }
201
+ },
202
+ conditions: {
203
+ type: 'array',
204
+ description: '[create] Conditional logic blocks',
205
+ items: { type: 'object' }
206
+ },
207
+ loops: {
208
+ type: 'array',
209
+ description: '[create] Loop constructs',
210
+ items: { type: 'object' }
211
+ },
212
+ variables: {
213
+ type: 'array',
214
+ description: '[create] Flow variables',
215
+ items: { type: 'object' }
216
+ },
217
+ flow_id: {
218
+ type: 'string',
219
+ description: '[update/delete/clone/activate/deactivate/get_details/add_*] Flow sys_id'
220
+ },
221
+ flow_name: {
222
+ type: 'string',
223
+ description: '[update/delete/clone/activate/deactivate/get_details/add_*] Flow name (alternative to flow_id)'
224
+ },
225
+ updates: {
226
+ type: 'object',
227
+ description: '[update] Properties to update'
228
+ },
229
+ new_name: {
230
+ type: 'string',
231
+ description: '[clone] Name for cloned flow'
232
+ },
233
+ action_config: {
234
+ type: 'object',
235
+ description: '[add_action] Action to add'
236
+ },
237
+ condition_config: {
238
+ type: 'object',
239
+ description: '[add_condition] Condition to add'
240
+ },
241
+ loop_config: {
242
+ type: 'object',
243
+ description: '[add_loop] Loop to add'
244
+ },
245
+ variable_config: {
246
+ type: 'object',
247
+ description: '[add_variable] Variable to add'
248
+ },
249
+ action_id: {
250
+ type: 'string',
251
+ description: '[remove_action] Action sys_id to remove'
252
+ },
253
+ auto_activate: {
254
+ type: 'boolean',
255
+ description: 'Activate after create/update',
256
+ default: false
257
+ },
258
+ create_snapshot: {
259
+ type: 'boolean',
260
+ description: 'Create snapshot after changes',
261
+ default: true
262
+ },
263
+ use_compression: {
264
+ type: 'boolean',
265
+ description: 'Use compression for values (auto-detected if not specified)'
266
+ },
267
+ validate_only: {
268
+ type: 'boolean',
269
+ description: 'Validate without making changes',
270
+ default: false
271
+ }
272
+ },
273
+ required: ['action']
274
+ }
275
+ };
276
+ // ==================== HELPER FUNCTIONS ====================
277
+ /**
278
+ * Generate ServiceNow-compatible sys_id
279
+ */
280
+ function generateSysId() {
281
+ return crypto.randomBytes(16).toString('hex');
282
+ }
283
+ /**
284
+ * Generate internal name from display name
285
+ */
286
+ function generateInternalName(name, prefix = 'x_snfl') {
287
+ const sanitized = name
288
+ .toLowerCase()
289
+ .replace(/[^a-z0-9]+/g, '_')
290
+ .replace(/^_+|_+$/g, '')
291
+ .substring(0, 40);
292
+ return `${prefix}_${sanitized}`;
293
+ }
294
+ /**
295
+ * Compress value for Xanadu+ versions
296
+ * Uses gzip compression + base64 encoding
297
+ *
298
+ * ServiceNow stores values as: base64(gzip(JSON))
299
+ * No header is used - the data starts directly with base64
300
+ */
301
+ function compressValue(value, useHeader = false) {
302
+ const jsonStr = typeof value === 'object' ? JSON.stringify(value) : value;
303
+ try {
304
+ // Compress with gzip
305
+ const compressed = zlib.gzipSync(Buffer.from(jsonStr, 'utf-8'));
306
+ // Encode as base64
307
+ const encoded = compressed.toString('base64');
308
+ // Return with or without header based on detection
309
+ return useHeader ? COMPRESSED_HEADER + encoded : encoded;
310
+ }
311
+ catch (error) {
312
+ console.error('Compression failed, returning uncompressed:', error);
313
+ return jsonStr;
314
+ }
315
+ }
316
+ /**
317
+ * Decompress value from Xanadu+ format
318
+ * Handles both with and without COMPRESSED: header
319
+ */
320
+ function decompressValue(value) {
321
+ if (!value) {
322
+ return value;
323
+ }
324
+ try {
325
+ let encoded = value;
326
+ // Check if it has the COMPRESSED: header
327
+ if (value.startsWith(COMPRESSED_HEADER)) {
328
+ encoded = value.substring(COMPRESSED_HEADER.length);
329
+ }
330
+ // Try to decode base64 and decompress
331
+ const compressed = Buffer.from(encoded, 'base64');
332
+ // Check if it's actually gzip compressed (magic bytes: 1f 8b)
333
+ if (compressed.length >= 2 && compressed[0] === 0x1f && compressed[1] === 0x8b) {
334
+ const decompressed = zlib.gunzipSync(compressed);
335
+ return decompressed.toString('utf-8');
336
+ }
337
+ // Not compressed, return original or try as plain base64
338
+ try {
339
+ return Buffer.from(encoded, 'base64').toString('utf-8');
340
+ }
341
+ catch {
342
+ return value;
343
+ }
344
+ }
345
+ catch (error) {
346
+ console.error('Decompression failed:', error);
347
+ return value;
348
+ }
349
+ }
350
+ /**
351
+ * Check if value is compressed (with or without header)
352
+ */
353
+ function isCompressed(value) {
354
+ if (!value)
355
+ return false;
356
+ // Check for COMPRESSED: header
357
+ if (value.startsWith(COMPRESSED_HEADER)) {
358
+ return true;
359
+ }
360
+ // Check if it looks like base64 encoded gzip data
361
+ try {
362
+ const decoded = Buffer.from(value, 'base64');
363
+ // Gzip magic bytes: 1f 8b
364
+ return decoded.length >= 2 && decoded[0] === 0x1f && decoded[1] === 0x8b;
365
+ }
366
+ catch {
367
+ return false;
368
+ }
369
+ }
370
+ /**
371
+ * Check if value uses the COMPRESSED: header format
372
+ */
373
+ function usesCompressionHeader(value) {
374
+ return value && value.startsWith(COMPRESSED_HEADER);
375
+ }
376
+ /**
377
+ * Detect ServiceNow version and capabilities
378
+ */
379
+ async function detectVersion(client) {
380
+ try {
381
+ // Check if V2 tables exist by querying them
382
+ const v2Response = await client.get('/api/now/table/sys_hub_action_instance_v2', {
383
+ params: { sysparm_limit: 1 }
384
+ });
385
+ // Check if values are compressed by looking at a sample
386
+ let useCompression = false;
387
+ let useCompressionHeader = false;
388
+ if (v2Response.data.result && v2Response.data.result.length > 0) {
389
+ const sample = v2Response.data.result[0];
390
+ if (sample.values) {
391
+ useCompression = isCompressed(sample.values);
392
+ useCompressionHeader = usesCompressionHeader(sample.values);
393
+ }
394
+ }
395
+ return {
396
+ useV2Tables: true,
397
+ useCompression,
398
+ useCompressionHeader,
399
+ version: useCompression ? 'Xanadu+' : 'Washington DC+'
400
+ };
401
+ }
402
+ catch (error) {
403
+ // V2 tables don't exist, use V1
404
+ return {
405
+ useV2Tables: false,
406
+ useCompression: false,
407
+ useCompressionHeader: false,
408
+ version: 'Pre-Washington DC'
409
+ };
410
+ }
411
+ }
412
+ /**
413
+ * Detect if a value is a data pill reference
414
+ * Data pills use {{action_name.field}} or {{trigger.field}} syntax
415
+ */
416
+ function isDataPill(value) {
417
+ if (typeof value !== 'string')
418
+ return false;
419
+ return /^\{\{[\w.]+\}\}$/.test(value) || /^\$\{[\w.]+\}$/.test(value);
420
+ }
421
+ /**
422
+ * Build a data pill reference
423
+ */
424
+ function buildDataPill(reference, displayValue) {
425
+ return {
426
+ value: reference,
427
+ displayValue: displayValue || reference,
428
+ valueType: 'pill'
429
+ };
430
+ }
431
+ /**
432
+ * Build a reference field value
433
+ */
434
+ function buildReferenceValue(sysId, displayValue, table) {
435
+ return {
436
+ value: sysId,
437
+ displayValue: displayValue,
438
+ valueType: 'reference',
439
+ parameter: {
440
+ type: 'reference',
441
+ label: displayValue,
442
+ mandatory: false,
443
+ reference: table
444
+ }
445
+ };
446
+ }
447
+ /**
448
+ * Build a script value (for Run Script action)
449
+ */
450
+ function buildScriptValue(script) {
451
+ return {
452
+ value: script,
453
+ valueType: 'script'
454
+ };
455
+ }
456
+ /**
457
+ * Build a condition value (for If/Loop conditions)
458
+ */
459
+ function buildConditionValue(condition) {
460
+ return {
461
+ value: condition,
462
+ valueType: 'condition'
463
+ };
464
+ }
465
+ /**
466
+ * Build a glide list value (multi-select)
467
+ */
468
+ function buildGlideListValue(values) {
469
+ return {
470
+ value: values.join(','),
471
+ valueType: 'glide_list'
472
+ };
473
+ }
474
+ /**
475
+ * Build values JSON for action instance - ENHANCED VERSION
476
+ * Supports: data pills, references, arrays, scripts, conditions, transforms
477
+ */
478
+ function buildActionValues(inputs, useCompression, useCompressionHeader = false) {
479
+ if (!inputs || Object.keys(inputs).length === 0) {
480
+ const emptyValue = '{}';
481
+ return useCompression ? compressValue(emptyValue, useCompressionHeader) : emptyValue;
482
+ }
483
+ const values = {};
484
+ for (const [key, value] of Object.entries(inputs)) {
485
+ // Already in Flow Designer format
486
+ if (typeof value === 'object' && value !== null && 'value' in value && 'valueType' in value) {
487
+ values[key] = value;
488
+ continue;
489
+ }
490
+ // Data pill reference ({{...}} or ${...})
491
+ if (isDataPill(value)) {
492
+ values[key] = {
493
+ value: value,
494
+ displayValue: value,
495
+ valueType: 'pill'
496
+ };
497
+ continue;
498
+ }
499
+ // Reference object with sys_id and display
500
+ if (typeof value === 'object' && value !== null && 'sys_id' in value) {
501
+ values[key] = {
502
+ value: value.sys_id,
503
+ displayValue: value.display_value || value.name || value.sys_id,
504
+ valueType: 'reference',
505
+ referenceName: value.table || ''
506
+ };
507
+ continue;
508
+ }
509
+ // Array value (e.g., for multi-select or list inputs)
510
+ if (Array.isArray(value)) {
511
+ if (value.every(v => typeof v === 'string')) {
512
+ // String array - join as comma-separated
513
+ values[key] = {
514
+ value: value.join(','),
515
+ valueType: 'glide_list'
516
+ };
517
+ }
518
+ else if (value.every(v => typeof v === 'object' && 'sys_id' in v)) {
519
+ // Array of references
520
+ values[key] = {
521
+ value: value.map(v => v.sys_id).join(','),
522
+ displayValue: value.map(v => v.display_value || v.name || v.sys_id).join(', '),
523
+ valueType: 'reference_list'
524
+ };
525
+ }
526
+ else {
527
+ // Complex array - serialize
528
+ values[key] = {
529
+ value: JSON.stringify(value),
530
+ valueType: 'array'
531
+ };
532
+ }
533
+ continue;
534
+ }
535
+ // Object with special type hints
536
+ if (typeof value === 'object' && value !== null) {
537
+ if ('_type' in value) {
538
+ // Handle special types
539
+ switch (value._type) {
540
+ case 'pill':
541
+ case 'data_pill':
542
+ values[key] = buildDataPill(value.reference, value.display);
543
+ break;
544
+ case 'reference':
545
+ values[key] = buildReferenceValue(value.sys_id, value.display || value.sys_id, value.table);
546
+ break;
547
+ case 'script':
548
+ values[key] = buildScriptValue(value.script || value.value);
549
+ break;
550
+ case 'condition':
551
+ values[key] = buildConditionValue(value.condition || value.value);
552
+ break;
553
+ case 'glide_list':
554
+ values[key] = buildGlideListValue(value.values || []);
555
+ break;
556
+ case 'transform':
557
+ // Transform function applied to a value
558
+ values[key] = {
559
+ value: value.source,
560
+ displayValue: value.display || value.source,
561
+ valueType: 'transform',
562
+ transform: {
563
+ function: value.function,
564
+ params: value.params || []
565
+ }
566
+ };
567
+ break;
568
+ default:
569
+ values[key] = {
570
+ value: JSON.stringify(value),
571
+ valueType: 'object'
572
+ };
573
+ }
574
+ continue;
575
+ }
576
+ // Generic object
577
+ values[key] = {
578
+ value: JSON.stringify(value),
579
+ valueType: 'object'
580
+ };
581
+ continue;
582
+ }
583
+ // Boolean value
584
+ if (typeof value === 'boolean') {
585
+ values[key] = {
586
+ value: String(value),
587
+ valueType: 'boolean'
588
+ };
589
+ continue;
590
+ }
591
+ // Number value
592
+ if (typeof value === 'number') {
593
+ values[key] = {
594
+ value: String(value),
595
+ valueType: Number.isInteger(value) ? 'integer' : 'decimal'
596
+ };
597
+ continue;
598
+ }
599
+ // String value (check for special patterns)
600
+ if (typeof value === 'string') {
601
+ // Check if it looks like a date/time
602
+ if (/^\d{4}-\d{2}-\d{2}/.test(value)) {
603
+ values[key] = {
604
+ value: value,
605
+ valueType: value.includes('T') ? 'glide_date_time' : 'glide_date'
606
+ };
607
+ continue;
608
+ }
609
+ // Check if it looks like a duration (e.g., "1d 2h 30m")
610
+ if (/^\d+[dhms]\s*/.test(value)) {
611
+ values[key] = {
612
+ value: value,
613
+ valueType: 'duration'
614
+ };
615
+ continue;
616
+ }
617
+ // Regular string
618
+ values[key] = {
619
+ value: value,
620
+ valueType: 'string'
621
+ };
622
+ continue;
623
+ }
624
+ // Fallback - convert to string
625
+ values[key] = {
626
+ value: String(value),
627
+ valueType: 'string'
628
+ };
629
+ }
630
+ const jsonStr = JSON.stringify(values);
631
+ return useCompression ? compressValue(jsonStr, useCompressionHeader) : jsonStr;
632
+ }
633
+ /**
634
+ * Remap sys_ids in values JSON when cloning
635
+ * Updates data pill references from old flow to new flow
636
+ */
637
+ function remapValuesForClone(valuesJson, sysIdMap, oldFlowId, newFlowId) {
638
+ if (!valuesJson)
639
+ return valuesJson;
640
+ let values;
641
+ try {
642
+ values = JSON.parse(valuesJson);
643
+ }
644
+ catch (e) {
645
+ return valuesJson;
646
+ }
647
+ // Recursively update sys_ids and references
648
+ const updateRefs = (obj) => {
649
+ if (!obj || typeof obj !== 'object')
650
+ return obj;
651
+ if (Array.isArray(obj)) {
652
+ return obj.map(item => updateRefs(item));
653
+ }
654
+ const updated = {};
655
+ for (const [key, value] of Object.entries(obj)) {
656
+ if (typeof value === 'string') {
657
+ // Check if it's a sys_id that needs remapping
658
+ if (sysIdMap.has(value)) {
659
+ updated[key] = sysIdMap.get(value);
660
+ }
661
+ // Check if it's a data pill reference
662
+ else if (value.includes(oldFlowId)) {
663
+ updated[key] = value.replace(oldFlowId, newFlowId);
664
+ }
665
+ else {
666
+ updated[key] = value;
667
+ }
668
+ }
669
+ else if (typeof value === 'object') {
670
+ updated[key] = updateRefs(value);
671
+ }
672
+ else {
673
+ updated[key] = value;
674
+ }
675
+ }
676
+ return updated;
677
+ };
678
+ const remapped = updateRefs(values);
679
+ return JSON.stringify(remapped);
680
+ }
681
+ /**
682
+ * Get trigger type sys_id from name
683
+ */
684
+ async function getTriggerTypeSysId(client, triggerType, when) {
685
+ const triggerMap = {
686
+ 'record_created': 'Created',
687
+ 'record_updated': 'Updated',
688
+ 'record_deleted': 'Deleted',
689
+ 'record_created_or_updated': 'Created or Updated',
690
+ 'scheduled': 'Scheduled',
691
+ 'api': 'REST API',
692
+ 'inbound_email': 'Inbound Email',
693
+ 'service_catalog': 'Service Catalog'
694
+ };
695
+ let searchName = triggerType;
696
+ if (triggerType === 'record' && when) {
697
+ searchName = triggerMap[`record_${when}`] || when;
698
+ }
699
+ else if (triggerMap[triggerType]) {
700
+ searchName = triggerMap[triggerType];
701
+ }
702
+ try {
703
+ // Note: sys_hub_trigger_type doesn't have 'label' or 'active' fields
704
+ const response = await client.get('/api/now/table/sys_hub_trigger_type', {
705
+ params: {
706
+ sysparm_query: `nameLIKE${searchName}`,
707
+ sysparm_limit: 1,
708
+ sysparm_fields: 'sys_id,name,description'
709
+ }
710
+ });
711
+ if (response.data.result && response.data.result.length > 0) {
712
+ return response.data.result[0].sys_id;
713
+ }
714
+ }
715
+ catch (error) {
716
+ console.error('Failed to get trigger type:', error);
717
+ }
718
+ return null;
719
+ }
720
+ /**
721
+ * Get action type sys_id from name
722
+ */
723
+ async function getActionTypeSysId(client, actionType) {
724
+ if (/^[0-9a-f]{32}$/i.test(actionType)) {
725
+ return { sys_id: actionType, name: actionType };
726
+ }
727
+ try {
728
+ // Note: sys_hub_action_type_base doesn't have 'label' or 'active' fields
729
+ const response = await client.get('/api/now/table/sys_hub_action_type_base', {
730
+ params: {
731
+ sysparm_query: `nameLIKE${actionType}`,
732
+ sysparm_limit: 1,
733
+ sysparm_fields: 'sys_id,name,description'
734
+ }
735
+ });
736
+ if (response.data.result && response.data.result.length > 0) {
737
+ const result = response.data.result[0];
738
+ return { sys_id: result.sys_id, name: result.name };
739
+ }
740
+ }
741
+ catch (error) {
742
+ console.error('Failed to get action type:', error);
743
+ }
744
+ return null;
745
+ }
746
+ /**
747
+ * Find flow by ID or name
748
+ */
749
+ async function findFlow(client, flowId, flowName) {
750
+ if (!flowId && !flowName) {
751
+ return null;
752
+ }
753
+ try {
754
+ let query = '';
755
+ if (flowId) {
756
+ query = `sys_id=${flowId}`;
757
+ }
758
+ else if (flowName) {
759
+ query = `name=${flowName}^ORinternal_name=${flowName}`;
760
+ }
761
+ const response = await client.get('/api/now/table/sys_hub_flow', {
762
+ params: {
763
+ sysparm_query: query,
764
+ sysparm_limit: 1
765
+ }
766
+ });
767
+ if (response.data.result && response.data.result.length > 0) {
768
+ return response.data.result[0];
769
+ }
770
+ }
771
+ catch (error) {
772
+ console.error('Failed to find flow:', error);
773
+ }
774
+ return null;
775
+ }
776
+ /**
777
+ * Get maximum order number for actions/logic in a flow
778
+ */
779
+ async function getMaxOrder(client, flowId, table) {
780
+ try {
781
+ const response = await client.get(`/api/now/table/${table}`, {
782
+ params: {
783
+ sysparm_query: `flow=${flowId}`,
784
+ sysparm_fields: 'order',
785
+ sysparm_orderby: 'DESCorder',
786
+ sysparm_limit: 1
787
+ }
788
+ });
789
+ if (response.data.result && response.data.result.length > 0) {
790
+ return parseInt(response.data.result[0].order || '0', 10);
791
+ }
792
+ }
793
+ catch (error) {
794
+ console.error('Failed to get max order:', error);
795
+ }
796
+ return 0;
797
+ }
798
+ // ==================== ACTION HANDLERS ====================
799
+ /**
800
+ * Create a new flow
801
+ */
802
+ async function handleCreate(args, client, versionInfo) {
803
+ const { flow: flowConfig, trigger: triggerConfig, actions: actionConfigs = [], conditions = [], loops = [], variables = [], auto_activate = false, create_snapshot = true, use_compression, validate_only = false } = args;
804
+ if (!flowConfig?.name) {
805
+ return (0, error_handler_js_1.createErrorResult)('flow.name is required for create action');
806
+ }
807
+ const useCompression = use_compression ?? versionInfo.useCompression;
808
+ const flowSysId = generateSysId();
809
+ const snapshotSysId = generateSysId();
810
+ const triggerSysId = generateSysId();
811
+ const internalName = flowConfig.internal_name || generateInternalName(flowConfig.name);
812
+ const timestamp = new Date().toISOString();
813
+ if (validate_only) {
814
+ return (0, error_handler_js_1.createSuccessResult)({
815
+ valid: true,
816
+ would_create: {
817
+ flow: { sys_id: flowSysId, name: flowConfig.name, internal_name: internalName },
818
+ snapshot: create_snapshot ? { sys_id: snapshotSysId } : null,
819
+ trigger: triggerConfig ? { sys_id: triggerSysId, type: triggerConfig.type } : null,
820
+ actions: actionConfigs.length,
821
+ conditions: conditions.length,
822
+ loops: loops.length,
823
+ variables: variables.length
824
+ },
825
+ version_info: versionInfo,
826
+ compression: useCompression
827
+ });
828
+ }
829
+ const createdRecords = [];
830
+ const errors = [];
831
+ // 1. CREATE FLOW RECORD
832
+ console.log(`Creating flow: ${flowConfig.name}`);
833
+ const flowData = {
834
+ sys_id: flowSysId,
835
+ name: flowConfig.name,
836
+ internal_name: internalName,
837
+ description: flowConfig.description || '',
838
+ active: false,
839
+ sys_class_name: 'sys_hub_flow',
840
+ type: 'flow',
841
+ flow_type: 'flow',
842
+ status: 'draft',
843
+ source_ui: 'flow_designer',
844
+ access: 'public',
845
+ run_as: flowConfig.run_as || 'system'
846
+ };
847
+ if (flowConfig.application_scope && flowConfig.application_scope !== 'global') {
848
+ flowData.sys_scope = flowConfig.application_scope;
849
+ }
850
+ if (flowConfig.run_as_user) {
851
+ flowData.run_as_user = flowConfig.run_as_user;
852
+ }
853
+ try {
854
+ const flowResponse = await client.post(`/api/now/table/${FLOW_TABLES.FLOW}`, flowData);
855
+ createdRecords.push({ table: FLOW_TABLES.FLOW, sys_id: flowSysId, name: flowConfig.name });
856
+ console.log(`✅ Created flow: ${flowSysId}`);
857
+ }
858
+ catch (error) {
859
+ return (0, error_handler_js_1.createErrorResult)(`Failed to create flow: ${error.response?.data?.error?.message || error.message}`);
860
+ }
861
+ // 2. CREATE TRIGGER
862
+ if (triggerConfig) {
863
+ console.log(`Creating trigger: ${triggerConfig.type}`);
864
+ const triggerTypeSysId = await getTriggerTypeSysId(client, triggerConfig.type, triggerConfig.when);
865
+ if (!triggerTypeSysId) {
866
+ errors.push(`Trigger type not found: ${triggerConfig.type}`);
867
+ }
868
+ else {
869
+ const triggerTable = versionInfo.useV2Tables ? FLOW_TABLES.TRIGGER_V2 : FLOW_TABLES.TRIGGER_V1;
870
+ const triggerData = {
871
+ sys_id: triggerSysId,
872
+ flow: flowSysId,
873
+ trigger_type: triggerTypeSysId,
874
+ active: true,
875
+ order: 0
876
+ };
877
+ if (triggerConfig.type === 'record') {
878
+ if (triggerConfig.table)
879
+ triggerData.table = triggerConfig.table;
880
+ if (triggerConfig.condition) {
881
+ const conditionValue = { filter_condition: { value: triggerConfig.condition } };
882
+ triggerData.values = useCompression ? compressValue(conditionValue) : JSON.stringify(conditionValue);
883
+ }
884
+ }
885
+ if (triggerConfig.type === 'scheduled' && triggerConfig.schedule) {
886
+ const scheduleValues = {};
887
+ if (triggerConfig.schedule.frequency)
888
+ scheduleValues.frequency = { value: triggerConfig.schedule.frequency };
889
+ if (triggerConfig.schedule.time)
890
+ scheduleValues.time_of_day = { value: triggerConfig.schedule.time };
891
+ if (triggerConfig.schedule.cron)
892
+ scheduleValues.cron_expression = { value: triggerConfig.schedule.cron };
893
+ triggerData.values = useCompression ? compressValue(scheduleValues) : JSON.stringify(scheduleValues);
894
+ }
895
+ try {
896
+ await client.post(`/api/now/table/${triggerTable}`, triggerData);
897
+ createdRecords.push({ table: triggerTable, sys_id: triggerSysId, type: triggerConfig.type });
898
+ console.log(`✅ Created trigger: ${triggerSysId}`);
899
+ }
900
+ catch (error) {
901
+ errors.push(`Failed to create trigger: ${error.response?.data?.error?.message || error.message}`);
902
+ }
903
+ }
904
+ }
905
+ // 3. CREATE VARIABLES
906
+ for (let i = 0; i < variables.length; i++) {
907
+ const variable = variables[i];
908
+ const variableSysId = generateSysId();
909
+ console.log(`Creating variable: ${variable.name}`);
910
+ const typeMap = {
911
+ 'string': 'string', 'integer': 'integer', 'boolean': 'boolean',
912
+ 'reference': 'reference', 'object': 'object', 'array': 'array',
913
+ 'glide_date_time': 'glide_date_time'
914
+ };
915
+ // Note: sys_hub_flow_variable uses 'model' field (not 'flow') to reference the parent flow
916
+ const variableData = {
917
+ sys_id: variableSysId,
918
+ model: flowSysId,
919
+ name: variable.name,
920
+ label: variable.label || variable.name,
921
+ description: variable.description || '',
922
+ type: typeMap[variable.type] || 'string',
923
+ direction: variable.direction,
924
+ mandatory: variable.mandatory || false,
925
+ order: i * 100
926
+ };
927
+ if (variable.default_value)
928
+ variableData.default_value = variable.default_value;
929
+ if (variable.reference_table)
930
+ variableData.reference_table = variable.reference_table;
931
+ try {
932
+ await client.post(`/api/now/table/${FLOW_TABLES.VARIABLE}`, variableData);
933
+ createdRecords.push({ table: FLOW_TABLES.VARIABLE, sys_id: variableSysId, name: variable.name });
934
+ console.log(`✅ Created variable: ${variable.name}`);
935
+ }
936
+ catch (error) {
937
+ errors.push(`Failed to create variable ${variable.name}: ${error.response?.data?.error?.message || error.message}`);
938
+ }
939
+ }
940
+ // 4. CREATE CONDITIONS (with nested actions)
941
+ const logicTable = versionInfo.useV2Tables ? FLOW_TABLES.LOGIC_V2 : FLOW_TABLES.LOGIC_V1;
942
+ const actionTable = versionInfo.useV2Tables ? FLOW_TABLES.ACTION_V2 : FLOW_TABLES.ACTION_V1;
943
+ let currentOrder = 100;
944
+ for (const condition of conditions) {
945
+ const conditionSysId = generateSysId();
946
+ console.log(`Creating condition: ${condition.name || 'Condition'}`);
947
+ const conditionData = {
948
+ sys_id: conditionSysId,
949
+ flow: flowSysId,
950
+ name: condition.name || 'If',
951
+ logic_type: 'if',
952
+ order: condition.order ?? currentOrder,
953
+ active: true
954
+ };
955
+ // Build condition values
956
+ const conditionValues = {
957
+ condition: { value: condition.condition, valueType: 'string' }
958
+ };
959
+ conditionData.values = useCompression ? compressValue(conditionValues) : JSON.stringify(conditionValues);
960
+ if (condition.parent_logic_instance) {
961
+ conditionData.parent_logic_instance = condition.parent_logic_instance;
962
+ }
963
+ try {
964
+ await client.post(`/api/now/table/${logicTable}`, conditionData);
965
+ createdRecords.push({ table: logicTable, sys_id: conditionSysId, name: condition.name || 'If', type: 'condition' });
966
+ console.log(`✅ Created condition: ${conditionSysId}`);
967
+ // Create THEN branch actions
968
+ if (condition.then_actions && condition.then_actions.length > 0) {
969
+ let thenOrder = 100;
970
+ for (const thenAction of condition.then_actions) {
971
+ const actionSysId = generateSysId();
972
+ const actionType = await getActionTypeSysId(client, thenAction.type);
973
+ if (actionType) {
974
+ const actionData = {
975
+ sys_id: actionSysId,
976
+ flow: flowSysId,
977
+ action_type: actionType.sys_id,
978
+ name: thenAction.name || actionType.name,
979
+ description: thenAction.description || '',
980
+ order: thenOrder,
981
+ active: true,
982
+ parent_logic_instance: conditionSysId,
983
+ branch: 'then'
984
+ };
985
+ if (thenAction.inputs) {
986
+ actionData.values = buildActionValues(thenAction.inputs, useCompression, versionInfo.useCompressionHeader);
987
+ }
988
+ try {
989
+ await client.post(`/api/now/table/${actionTable}`, actionData);
990
+ createdRecords.push({
991
+ table: actionTable,
992
+ sys_id: actionSysId,
993
+ name: thenAction.name || actionType.name,
994
+ parent: conditionSysId,
995
+ branch: 'then'
996
+ });
997
+ console.log(`✅ Created then-action: ${actionSysId}`);
998
+ }
999
+ catch (error) {
1000
+ errors.push(`Failed to create then-action: ${error.response?.data?.error?.message || error.message}`);
1001
+ }
1002
+ }
1003
+ thenOrder += 100;
1004
+ }
1005
+ }
1006
+ // Create ELSE branch actions
1007
+ if (condition.else_actions && condition.else_actions.length > 0) {
1008
+ let elseOrder = 100;
1009
+ for (const elseAction of condition.else_actions) {
1010
+ const actionSysId = generateSysId();
1011
+ const actionType = await getActionTypeSysId(client, elseAction.type);
1012
+ if (actionType) {
1013
+ const actionData = {
1014
+ sys_id: actionSysId,
1015
+ flow: flowSysId,
1016
+ action_type: actionType.sys_id,
1017
+ name: elseAction.name || actionType.name,
1018
+ description: elseAction.description || '',
1019
+ order: elseOrder,
1020
+ active: true,
1021
+ parent_logic_instance: conditionSysId,
1022
+ branch: 'else'
1023
+ };
1024
+ if (elseAction.inputs) {
1025
+ actionData.values = buildActionValues(elseAction.inputs, useCompression, versionInfo.useCompressionHeader);
1026
+ }
1027
+ try {
1028
+ await client.post(`/api/now/table/${actionTable}`, actionData);
1029
+ createdRecords.push({
1030
+ table: actionTable,
1031
+ sys_id: actionSysId,
1032
+ name: elseAction.name || actionType.name,
1033
+ parent: conditionSysId,
1034
+ branch: 'else'
1035
+ });
1036
+ console.log(`✅ Created else-action: ${actionSysId}`);
1037
+ }
1038
+ catch (error) {
1039
+ errors.push(`Failed to create else-action: ${error.response?.data?.error?.message || error.message}`);
1040
+ }
1041
+ }
1042
+ elseOrder += 100;
1043
+ }
1044
+ }
1045
+ }
1046
+ catch (error) {
1047
+ errors.push(`Failed to create condition: ${error.response?.data?.error?.message || error.message}`);
1048
+ }
1049
+ currentOrder += 100;
1050
+ }
1051
+ // 5. CREATE LOOPS (with nested actions)
1052
+ for (const loop of loops) {
1053
+ const loopSysId = generateSysId();
1054
+ console.log(`Creating loop: ${loop.name || loop.type}`);
1055
+ const loopData = {
1056
+ sys_id: loopSysId,
1057
+ flow: flowSysId,
1058
+ name: loop.name || (loop.type === 'for_each' ? 'For Each' : 'Do Until'),
1059
+ logic_type: loop.type,
1060
+ order: loop.order ?? currentOrder,
1061
+ active: true
1062
+ };
1063
+ // Build loop values
1064
+ const loopValues = {};
1065
+ if (loop.data_source)
1066
+ loopValues.data_source = { value: loop.data_source, valueType: 'reference' };
1067
+ if (loop.condition)
1068
+ loopValues.condition = { value: loop.condition, valueType: 'string' };
1069
+ if (loop.max_iterations)
1070
+ loopValues.max_iterations = { value: String(loop.max_iterations), valueType: 'integer' };
1071
+ if (Object.keys(loopValues).length > 0) {
1072
+ loopData.values = useCompression ? compressValue(loopValues) : JSON.stringify(loopValues);
1073
+ }
1074
+ if (loop.parent_logic_instance) {
1075
+ loopData.parent_logic_instance = loop.parent_logic_instance;
1076
+ }
1077
+ try {
1078
+ await client.post(`/api/now/table/${logicTable}`, loopData);
1079
+ createdRecords.push({ table: logicTable, sys_id: loopSysId, name: loop.name || loop.type, type: 'loop' });
1080
+ console.log(`✅ Created loop: ${loopSysId}`);
1081
+ // Create nested actions within loop
1082
+ if (loop.actions && loop.actions.length > 0) {
1083
+ let loopActionOrder = 100;
1084
+ for (const loopAction of loop.actions) {
1085
+ const actionSysId = generateSysId();
1086
+ const actionType = await getActionTypeSysId(client, loopAction.type);
1087
+ if (actionType) {
1088
+ const actionData = {
1089
+ sys_id: actionSysId,
1090
+ flow: flowSysId,
1091
+ action_type: actionType.sys_id,
1092
+ name: loopAction.name || actionType.name,
1093
+ description: loopAction.description || '',
1094
+ order: loopActionOrder,
1095
+ active: true,
1096
+ parent_logic_instance: loopSysId
1097
+ };
1098
+ if (loopAction.inputs) {
1099
+ actionData.values = buildActionValues(loopAction.inputs, useCompression, versionInfo.useCompressionHeader);
1100
+ }
1101
+ try {
1102
+ await client.post(`/api/now/table/${actionTable}`, actionData);
1103
+ createdRecords.push({
1104
+ table: actionTable,
1105
+ sys_id: actionSysId,
1106
+ name: loopAction.name || actionType.name,
1107
+ parent: loopSysId
1108
+ });
1109
+ console.log(`✅ Created loop-action: ${actionSysId}`);
1110
+ }
1111
+ catch (error) {
1112
+ errors.push(`Failed to create loop-action: ${error.response?.data?.error?.message || error.message}`);
1113
+ }
1114
+ }
1115
+ loopActionOrder += 100;
1116
+ }
1117
+ }
1118
+ }
1119
+ catch (error) {
1120
+ errors.push(`Failed to create loop: ${error.response?.data?.error?.message || error.message}`);
1121
+ }
1122
+ currentOrder += 100;
1123
+ }
1124
+ // 6. CREATE TOP-LEVEL ACTIONS (not nested in conditions/loops)
1125
+ for (let i = 0; i < actionConfigs.length; i++) {
1126
+ const actionConfig = actionConfigs[i];
1127
+ // Skip if this action is meant to be nested (has parent)
1128
+ if (actionConfig.parent_logic_instance)
1129
+ continue;
1130
+ const actionSysId = generateSysId();
1131
+ console.log(`Creating action: ${actionConfig.type}`);
1132
+ const actionType = await getActionTypeSysId(client, actionConfig.type);
1133
+ if (!actionType) {
1134
+ errors.push(`Action type not found: ${actionConfig.type}`);
1135
+ continue;
1136
+ }
1137
+ const actionData = {
1138
+ sys_id: actionSysId,
1139
+ flow: flowSysId,
1140
+ action_type: actionType.sys_id,
1141
+ name: actionConfig.name || actionType.name,
1142
+ description: actionConfig.description || '',
1143
+ order: actionConfig.order ?? currentOrder,
1144
+ active: true
1145
+ };
1146
+ if (actionConfig.inputs) {
1147
+ actionData.values = buildActionValues(actionConfig.inputs, useCompression, versionInfo.useCompressionHeader);
1148
+ }
1149
+ try {
1150
+ await client.post(`/api/now/table/${actionTable}`, actionData);
1151
+ createdRecords.push({ table: actionTable, sys_id: actionSysId, name: actionConfig.name || actionType.name });
1152
+ console.log(`✅ Created action: ${actionSysId}`);
1153
+ }
1154
+ catch (error) {
1155
+ errors.push(`Failed to create action ${actionConfig.type}: ${error.response?.data?.error?.message || error.message}`);
1156
+ }
1157
+ currentOrder += 100;
1158
+ }
1159
+ // 7. CREATE SNAPSHOT WITH FLOW DEFINITION
1160
+ // This is CRITICAL - Flow Designer requires snapshots with main_snapshot/latest_snapshot references
1161
+ if (create_snapshot) {
1162
+ console.log('Creating snapshot with flow definition...');
1163
+ // Build the flow definition structure that Flow Designer expects
1164
+ const flowDefinition = {
1165
+ sys_id: flowSysId,
1166
+ name: flowConfig.name,
1167
+ internal_name: internalName,
1168
+ description: flowConfig.description || '',
1169
+ flow_type: 'flow',
1170
+ run_as: flowConfig.run_as || 'system',
1171
+ // Include references to created components
1172
+ trigger: triggerConfig ? {
1173
+ sys_id: triggerSysId,
1174
+ type: triggerConfig.type,
1175
+ table: triggerConfig.table,
1176
+ when: triggerConfig.when
1177
+ } : null,
1178
+ actions: createdRecords
1179
+ .filter(r => r.table === actionTable)
1180
+ .map(r => ({ sys_id: r.sys_id, name: r.name })),
1181
+ logic: createdRecords
1182
+ .filter(r => r.table === logicTable)
1183
+ .map(r => ({ sys_id: r.sys_id, name: r.name, type: r.type })),
1184
+ variables: createdRecords
1185
+ .filter(r => r.table === FLOW_TABLES.VARIABLE)
1186
+ .map(r => ({ sys_id: r.sys_id, name: r.name }))
1187
+ };
1188
+ const snapshotData = {
1189
+ sys_id: snapshotSysId,
1190
+ flow: flowSysId,
1191
+ name: `${flowConfig.name} - Initial`,
1192
+ version: '1.0.0',
1193
+ active: true,
1194
+ published: false,
1195
+ // Store the flow definition - this is what Flow Designer reads
1196
+ definition: useCompression
1197
+ ? compressValue(flowDefinition, versionInfo.useCompressionHeader)
1198
+ : JSON.stringify(flowDefinition)
1199
+ };
1200
+ try {
1201
+ await client.post(`/api/now/table/${FLOW_TABLES.SNAPSHOT}`, snapshotData);
1202
+ createdRecords.push({ table: FLOW_TABLES.SNAPSHOT, sys_id: snapshotSysId, name: `${flowConfig.name} - Initial` });
1203
+ console.log(`✅ Created snapshot: ${snapshotSysId}`);
1204
+ // *** CRITICAL FIX ***
1205
+ // Update the flow record with snapshot references
1206
+ // Without this, Flow Designer shows an empty flow!
1207
+ console.log('Updating flow with snapshot references...');
1208
+ try {
1209
+ await client.patch(`/api/now/table/${FLOW_TABLES.FLOW}/${flowSysId}`, {
1210
+ main_snapshot: snapshotSysId,
1211
+ latest_snapshot: snapshotSysId
1212
+ });
1213
+ console.log(`✅ Flow updated with main_snapshot and latest_snapshot`);
1214
+ }
1215
+ catch (updateError) {
1216
+ errors.push(`Failed to update flow with snapshot references: ${updateError.response?.data?.error?.message || updateError.message}`);
1217
+ }
1218
+ }
1219
+ catch (error) {
1220
+ errors.push(`Failed to create snapshot: ${error.response?.data?.error?.message || error.message}`);
1221
+ }
1222
+ }
1223
+ // 8. ACTIVATE IF REQUESTED
1224
+ if (auto_activate && errors.length === 0) {
1225
+ console.log('Activating flow...');
1226
+ try {
1227
+ await client.patch(`/api/now/table/${FLOW_TABLES.FLOW}/${flowSysId}`, {
1228
+ active: true,
1229
+ status: 'published'
1230
+ });
1231
+ console.log(`✅ Flow activated`);
1232
+ }
1233
+ catch (error) {
1234
+ errors.push(`Failed to activate: ${error.response?.data?.error?.message || error.message}`);
1235
+ }
1236
+ }
1237
+ const instanceUrl = (client.defaults?.baseURL || '').replace('/api/now', '');
1238
+ return (0, error_handler_js_1.createSuccessResult)({
1239
+ action: 'create',
1240
+ flow: {
1241
+ sys_id: flowSysId,
1242
+ name: flowConfig.name,
1243
+ internal_name: internalName,
1244
+ active: auto_activate && errors.length === 0,
1245
+ url: `${instanceUrl}/sys_hub_flow.do?sys_id=${flowSysId}`,
1246
+ flow_designer_url: `${instanceUrl}/nav_to.do?uri=/flow-designer.do%23/flow/${flowSysId}`,
1247
+ main_snapshot: create_snapshot ? snapshotSysId : undefined,
1248
+ latest_snapshot: create_snapshot ? snapshotSysId : undefined
1249
+ },
1250
+ created_records: createdRecords,
1251
+ version_info: versionInfo,
1252
+ compression_used: useCompression,
1253
+ errors: errors.length > 0 ? errors : undefined
1254
+ });
1255
+ }
1256
+ /**
1257
+ * Update an existing flow
1258
+ */
1259
+ async function handleUpdate(args, client, versionInfo) {
1260
+ const { flow_id, flow_name, updates } = args;
1261
+ const flow = await findFlow(client, flow_id, flow_name);
1262
+ if (!flow) {
1263
+ return (0, error_handler_js_1.createErrorResult)(`Flow not found: ${flow_id || flow_name}`);
1264
+ }
1265
+ if (!updates || Object.keys(updates).length === 0) {
1266
+ return (0, error_handler_js_1.createErrorResult)('No updates provided');
1267
+ }
1268
+ try {
1269
+ await client.patch(`/api/now/table/${FLOW_TABLES.FLOW}/${flow.sys_id}`, updates);
1270
+ return (0, error_handler_js_1.createSuccessResult)({
1271
+ action: 'update',
1272
+ flow_id: flow.sys_id,
1273
+ updated_fields: Object.keys(updates)
1274
+ });
1275
+ }
1276
+ catch (error) {
1277
+ return (0, error_handler_js_1.createErrorResult)(`Failed to update flow: ${error.response?.data?.error?.message || error.message}`);
1278
+ }
1279
+ }
1280
+ /**
1281
+ * Delete a flow and all related records
1282
+ *
1283
+ * IMPORTANT: Delete order respects parent-child hierarchy:
1284
+ * 1. Actions (children of logic)
1285
+ * 2. Subflow instances (can be nested in logic)
1286
+ * 3. Logic instances - hierarchically (children before parents)
1287
+ * 4. Triggers
1288
+ * 5. Variables
1289
+ * 6. Snapshots
1290
+ * 7. Flow record (last)
1291
+ */
1292
+ async function handleDelete(args, client, versionInfo) {
1293
+ const { flow_id, flow_name } = args;
1294
+ const flow = await findFlow(client, flow_id, flow_name);
1295
+ if (!flow) {
1296
+ return (0, error_handler_js_1.createErrorResult)(`Flow not found: ${flow_id || flow_name}`);
1297
+ }
1298
+ const deletedRecords = [];
1299
+ const errors = [];
1300
+ const actionTable = versionInfo.useV2Tables ? FLOW_TABLES.ACTION_V2 : FLOW_TABLES.ACTION_V1;
1301
+ const logicTable = versionInfo.useV2Tables ? FLOW_TABLES.LOGIC_V2 : FLOW_TABLES.LOGIC_V1;
1302
+ const triggerTable = versionInfo.useV2Tables ? FLOW_TABLES.TRIGGER_V2 : FLOW_TABLES.TRIGGER_V1;
1303
+ // Helper function to delete records from a table
1304
+ const deleteFromTable = async (table, query) => {
1305
+ try {
1306
+ const response = await client.get(`/api/now/table/${table}`, {
1307
+ params: { sysparm_query: query, sysparm_fields: 'sys_id' }
1308
+ });
1309
+ if (response.data.result) {
1310
+ for (const record of response.data.result) {
1311
+ try {
1312
+ await client.delete(`/api/now/table/${table}/${record.sys_id}`);
1313
+ deletedRecords.push({ table, sys_id: record.sys_id });
1314
+ }
1315
+ catch (e) {
1316
+ errors.push(`Failed to delete ${table}/${record.sys_id}: ${e.message}`);
1317
+ }
1318
+ }
1319
+ }
1320
+ }
1321
+ catch (e) {
1322
+ // Table might not exist or be empty - this is OK
1323
+ }
1324
+ };
1325
+ console.log(`Deleting flow: ${flow.name} (${flow.sys_id})`);
1326
+ // 1. DELETE ACTIONS (children of logic instances)
1327
+ console.log('Deleting actions...');
1328
+ await deleteFromTable(actionTable, `flow=${flow.sys_id}`);
1329
+ // 2. DELETE SUBFLOW INSTANCES (can be nested in logic)
1330
+ console.log('Deleting subflow instances...');
1331
+ await deleteFromTable(FLOW_TABLES.SUBFLOW, `flow=${flow.sys_id}`);
1332
+ // 3. DELETE LOGIC INSTANCES - HIERARCHICALLY
1333
+ // First delete children (items WITH parent_logic_instance), then parents
1334
+ console.log('Deleting logic instances (hierarchically)...');
1335
+ try {
1336
+ const logicResponse = await client.get(`/api/now/table/${logicTable}`, {
1337
+ params: {
1338
+ sysparm_query: `flow=${flow.sys_id}`,
1339
+ sysparm_fields: 'sys_id,parent_logic_instance'
1340
+ }
1341
+ });
1342
+ if (logicResponse.data.result) {
1343
+ const logicItems = logicResponse.data.result;
1344
+ // Separate into children (with parent) and parents (without parent)
1345
+ const children = logicItems.filter((item) => item.parent_logic_instance);
1346
+ const parents = logicItems.filter((item) => !item.parent_logic_instance);
1347
+ // Build hierarchy depth map for proper ordering
1348
+ const depthMap = new Map();
1349
+ const getDepth = (item, visited = new Set()) => {
1350
+ if (!item.parent_logic_instance)
1351
+ return 0;
1352
+ if (visited.has(item.sys_id))
1353
+ return 0; // Prevent infinite loops
1354
+ visited.add(item.sys_id);
1355
+ const parent = logicItems.find((p) => p.sys_id === item.parent_logic_instance);
1356
+ if (!parent)
1357
+ return 1;
1358
+ return 1 + getDepth(parent, visited);
1359
+ };
1360
+ // Calculate depth for each item
1361
+ for (const item of logicItems) {
1362
+ depthMap.set(item.sys_id, getDepth(item));
1363
+ }
1364
+ // Sort by depth descending (deepest children first)
1365
+ const sortedLogic = [...logicItems].sort((a, b) => {
1366
+ const depthA = depthMap.get(a.sys_id) || 0;
1367
+ const depthB = depthMap.get(b.sys_id) || 0;
1368
+ return depthB - depthA; // Deepest first
1369
+ });
1370
+ // Delete in sorted order (deepest children first)
1371
+ for (const logic of sortedLogic) {
1372
+ try {
1373
+ await client.delete(`/api/now/table/${logicTable}/${logic.sys_id}`);
1374
+ deletedRecords.push({ table: logicTable, sys_id: logic.sys_id });
1375
+ }
1376
+ catch (e) {
1377
+ errors.push(`Failed to delete logic ${logic.sys_id}: ${e.message}`);
1378
+ }
1379
+ }
1380
+ }
1381
+ }
1382
+ catch (e) {
1383
+ errors.push(`Failed to fetch logic instances: ${e.message}`);
1384
+ }
1385
+ // 4. DELETE TRIGGERS
1386
+ console.log('Deleting triggers...');
1387
+ await deleteFromTable(triggerTable, `flow=${flow.sys_id}`);
1388
+ // 5. DELETE VARIABLES
1389
+ // Note: sys_hub_flow_variable uses 'model' field (not 'flow')
1390
+ console.log('Deleting variables...');
1391
+ await deleteFromTable(FLOW_TABLES.VARIABLE, `model=${flow.sys_id}`);
1392
+ // 6. DELETE SNAPSHOTS
1393
+ console.log('Deleting snapshots...');
1394
+ await deleteFromTable(FLOW_TABLES.SNAPSHOT, `flow=${flow.sys_id}`);
1395
+ // 7. DELETE FLOW RECORD (last)
1396
+ console.log('Deleting flow record...');
1397
+ try {
1398
+ await client.delete(`/api/now/table/${FLOW_TABLES.FLOW}/${flow.sys_id}`);
1399
+ deletedRecords.push({ table: FLOW_TABLES.FLOW, sys_id: flow.sys_id });
1400
+ console.log(`✅ Flow deleted: ${flow.sys_id}`);
1401
+ }
1402
+ catch (error) {
1403
+ return (0, error_handler_js_1.createErrorResult)(`Failed to delete flow: ${error.response?.data?.error?.message || error.message}`);
1404
+ }
1405
+ return (0, error_handler_js_1.createSuccessResult)({
1406
+ action: 'delete',
1407
+ flow_id: flow.sys_id,
1408
+ flow_name: flow.name,
1409
+ deleted_records: deletedRecords,
1410
+ delete_order: [
1411
+ 'actions',
1412
+ 'subflow_instances',
1413
+ 'logic_instances (hierarchical)',
1414
+ 'triggers',
1415
+ 'variables',
1416
+ 'snapshots',
1417
+ 'flow'
1418
+ ],
1419
+ errors: errors.length > 0 ? errors : undefined
1420
+ });
1421
+ }
1422
+ /**
1423
+ * Clone a flow - FULL IMPLEMENTATION
1424
+ * Copies ALL components: flow, triggers, actions, logic, variables, subflows
1425
+ * Maintains parent-child relationships and remaps sys_ids
1426
+ */
1427
+ async function handleClone(args, client, versionInfo) {
1428
+ const { flow_id, flow_name, new_name, use_compression, create_snapshot = true } = args;
1429
+ const sourceFlow = await findFlow(client, flow_id, flow_name);
1430
+ if (!sourceFlow) {
1431
+ return (0, error_handler_js_1.createErrorResult)(`Flow not found: ${flow_id || flow_name}`);
1432
+ }
1433
+ if (!new_name) {
1434
+ return (0, error_handler_js_1.createErrorResult)('new_name is required for clone action');
1435
+ }
1436
+ const useCompression = use_compression ?? versionInfo.useCompression;
1437
+ const triggerTable = versionInfo.useV2Tables ? FLOW_TABLES.TRIGGER_V2 : FLOW_TABLES.TRIGGER_V1;
1438
+ const actionTable = versionInfo.useV2Tables ? FLOW_TABLES.ACTION_V2 : FLOW_TABLES.ACTION_V1;
1439
+ const logicTable = versionInfo.useV2Tables ? FLOW_TABLES.LOGIC_V2 : FLOW_TABLES.LOGIC_V1;
1440
+ // Map old sys_ids to new sys_ids for remapping references
1441
+ const sysIdMap = new Map();
1442
+ const createdRecords = [];
1443
+ const errors = [];
1444
+ // Generate new IDs
1445
+ const newFlowId = generateSysId();
1446
+ const newInternalName = generateInternalName(new_name);
1447
+ sysIdMap.set(sourceFlow.sys_id, newFlowId);
1448
+ console.log(`Cloning flow: ${sourceFlow.name} -> ${new_name}`);
1449
+ // ==================== 1. CLONE FLOW RECORD ====================
1450
+ const newFlowData = {
1451
+ sys_id: newFlowId,
1452
+ name: new_name,
1453
+ internal_name: newInternalName,
1454
+ description: `Cloned from: ${sourceFlow.name}\n\n${sourceFlow.description || ''}`,
1455
+ active: false,
1456
+ sys_class_name: 'sys_hub_flow',
1457
+ type: sourceFlow.type || 'flow',
1458
+ flow_type: sourceFlow.flow_type || 'flow',
1459
+ status: 'draft',
1460
+ source_ui: 'flow_designer',
1461
+ access: sourceFlow.access || 'public',
1462
+ run_as: sourceFlow.run_as || 'system'
1463
+ };
1464
+ if (sourceFlow.sys_scope) {
1465
+ newFlowData.sys_scope = sourceFlow.sys_scope;
1466
+ }
1467
+ if (sourceFlow.run_as_user) {
1468
+ newFlowData.run_as_user = sourceFlow.run_as_user;
1469
+ }
1470
+ try {
1471
+ await client.post(`/api/now/table/${FLOW_TABLES.FLOW}`, newFlowData);
1472
+ createdRecords.push({ table: FLOW_TABLES.FLOW, sys_id: newFlowId, name: new_name, type: 'flow' });
1473
+ console.log(`✅ Cloned flow record: ${newFlowId}`);
1474
+ }
1475
+ catch (error) {
1476
+ return (0, error_handler_js_1.createErrorResult)(`Failed to create cloned flow: ${error.response?.data?.error?.message || error.message}`);
1477
+ }
1478
+ // ==================== 2. CLONE VARIABLES ====================
1479
+ // Note: sys_hub_flow_variable uses 'model' field (not 'flow')
1480
+ try {
1481
+ const variablesResponse = await client.get(`/api/now/table/${FLOW_TABLES.VARIABLE}`, {
1482
+ params: { sysparm_query: `model=${sourceFlow.sys_id}`, sysparm_orderby: 'order' }
1483
+ });
1484
+ if (variablesResponse.data.result) {
1485
+ for (const variable of variablesResponse.data.result) {
1486
+ const newVariableId = generateSysId();
1487
+ sysIdMap.set(variable.sys_id, newVariableId);
1488
+ // Note: sys_hub_flow_variable uses 'model' field (not 'flow')
1489
+ const newVariable = {
1490
+ sys_id: newVariableId,
1491
+ model: newFlowId,
1492
+ name: variable.name,
1493
+ label: variable.label,
1494
+ description: variable.description,
1495
+ type: variable.type,
1496
+ direction: variable.direction,
1497
+ mandatory: variable.mandatory,
1498
+ order: variable.order,
1499
+ default_value: variable.default_value,
1500
+ reference_table: variable.reference_table
1501
+ };
1502
+ try {
1503
+ await client.post(`/api/now/table/${FLOW_TABLES.VARIABLE}`, newVariable);
1504
+ createdRecords.push({
1505
+ table: FLOW_TABLES.VARIABLE,
1506
+ sys_id: newVariableId,
1507
+ name: variable.name,
1508
+ type: 'variable'
1509
+ });
1510
+ console.log(`✅ Cloned variable: ${variable.name}`);
1511
+ }
1512
+ catch (e) {
1513
+ errors.push(`Failed to clone variable ${variable.name}: ${e.message}`);
1514
+ }
1515
+ }
1516
+ }
1517
+ }
1518
+ catch (e) {
1519
+ errors.push(`Failed to fetch variables: ${e.message}`);
1520
+ }
1521
+ // ==================== 3. CLONE TRIGGERS ====================
1522
+ try {
1523
+ const triggersResponse = await client.get(`/api/now/table/${triggerTable}`, {
1524
+ params: { sysparm_query: `flow=${sourceFlow.sys_id}` }
1525
+ });
1526
+ if (triggersResponse.data.result) {
1527
+ for (const trigger of triggersResponse.data.result) {
1528
+ const newTriggerId = generateSysId();
1529
+ sysIdMap.set(trigger.sys_id, newTriggerId);
1530
+ // Decompress and remap values if needed
1531
+ let newValues = trigger.values;
1532
+ if (newValues) {
1533
+ if (isCompressed(newValues)) {
1534
+ const decompressed = decompressValue(newValues);
1535
+ const remapped = remapValuesForClone(decompressed, sysIdMap, sourceFlow.sys_id, newFlowId);
1536
+ newValues = useCompression ? compressValue(remapped) : remapped;
1537
+ }
1538
+ else {
1539
+ newValues = remapValuesForClone(newValues, sysIdMap, sourceFlow.sys_id, newFlowId);
1540
+ if (useCompression)
1541
+ newValues = compressValue(newValues);
1542
+ }
1543
+ }
1544
+ const newTrigger = {
1545
+ sys_id: newTriggerId,
1546
+ flow: newFlowId,
1547
+ trigger_type: trigger.trigger_type,
1548
+ table: trigger.table,
1549
+ active: trigger.active,
1550
+ order: trigger.order,
1551
+ values: newValues
1552
+ };
1553
+ try {
1554
+ await client.post(`/api/now/table/${triggerTable}`, newTrigger);
1555
+ createdRecords.push({
1556
+ table: triggerTable,
1557
+ sys_id: newTriggerId,
1558
+ type: 'trigger',
1559
+ trigger_type: trigger.trigger_type
1560
+ });
1561
+ console.log(`✅ Cloned trigger: ${newTriggerId}`);
1562
+ }
1563
+ catch (e) {
1564
+ errors.push(`Failed to clone trigger: ${e.message}`);
1565
+ }
1566
+ }
1567
+ }
1568
+ }
1569
+ catch (e) {
1570
+ errors.push(`Failed to fetch triggers: ${e.message}`);
1571
+ }
1572
+ // ==================== 4. CLONE LOGIC (CONDITIONS/LOOPS) ====================
1573
+ // First pass: create all logic instances and map sys_ids
1574
+ const logicItems = [];
1575
+ try {
1576
+ const logicResponse = await client.get(`/api/now/table/${logicTable}`, {
1577
+ params: { sysparm_query: `flow=${sourceFlow.sys_id}`, sysparm_orderby: 'order' }
1578
+ });
1579
+ if (logicResponse.data.result) {
1580
+ // First pass: generate new IDs for all logic items
1581
+ for (const logic of logicResponse.data.result) {
1582
+ const newLogicId = generateSysId();
1583
+ sysIdMap.set(logic.sys_id, newLogicId);
1584
+ logicItems.push({ ...logic, newSysId: newLogicId });
1585
+ }
1586
+ // Second pass: create logic items with remapped parent references
1587
+ for (const logic of logicItems) {
1588
+ // Decompress and remap values
1589
+ let newValues = logic.values;
1590
+ if (newValues) {
1591
+ if (isCompressed(newValues)) {
1592
+ const decompressed = decompressValue(newValues);
1593
+ const remapped = remapValuesForClone(decompressed, sysIdMap, sourceFlow.sys_id, newFlowId);
1594
+ newValues = useCompression ? compressValue(remapped) : remapped;
1595
+ }
1596
+ else {
1597
+ newValues = remapValuesForClone(newValues, sysIdMap, sourceFlow.sys_id, newFlowId);
1598
+ if (useCompression)
1599
+ newValues = compressValue(newValues);
1600
+ }
1601
+ }
1602
+ // Remap parent_logic_instance if it exists
1603
+ let newParentLogic = null;
1604
+ if (logic.parent_logic_instance) {
1605
+ newParentLogic = sysIdMap.get(logic.parent_logic_instance) || logic.parent_logic_instance;
1606
+ }
1607
+ const newLogic = {
1608
+ sys_id: logic.newSysId,
1609
+ flow: newFlowId,
1610
+ name: logic.name,
1611
+ logic_type: logic.logic_type,
1612
+ order: logic.order,
1613
+ active: logic.active,
1614
+ values: newValues,
1615
+ condition: logic.condition
1616
+ };
1617
+ if (newParentLogic) {
1618
+ newLogic.parent_logic_instance = newParentLogic;
1619
+ }
1620
+ if (logic.branch) {
1621
+ newLogic.branch = logic.branch;
1622
+ }
1623
+ try {
1624
+ await client.post(`/api/now/table/${logicTable}`, newLogic);
1625
+ createdRecords.push({
1626
+ table: logicTable,
1627
+ sys_id: logic.newSysId,
1628
+ name: logic.name,
1629
+ type: 'logic',
1630
+ logic_type: logic.logic_type
1631
+ });
1632
+ console.log(`✅ Cloned logic: ${logic.name} (${logic.logic_type})`);
1633
+ }
1634
+ catch (e) {
1635
+ errors.push(`Failed to clone logic ${logic.name}: ${e.message}`);
1636
+ }
1637
+ }
1638
+ }
1639
+ }
1640
+ catch (e) {
1641
+ errors.push(`Failed to fetch logic: ${e.message}`);
1642
+ }
1643
+ // ==================== 5. CLONE ACTIONS ====================
1644
+ try {
1645
+ const actionsResponse = await client.get(`/api/now/table/${actionTable}`, {
1646
+ params: { sysparm_query: `flow=${sourceFlow.sys_id}`, sysparm_orderby: 'order' }
1647
+ });
1648
+ if (actionsResponse.data.result) {
1649
+ // First pass: generate new IDs
1650
+ const actionItems = [];
1651
+ for (const action of actionsResponse.data.result) {
1652
+ const newActionId = generateSysId();
1653
+ sysIdMap.set(action.sys_id, newActionId);
1654
+ actionItems.push({ ...action, newSysId: newActionId });
1655
+ }
1656
+ // Second pass: create actions with remapped references
1657
+ for (const action of actionItems) {
1658
+ // Decompress and remap values
1659
+ let newValues = action.values;
1660
+ if (newValues) {
1661
+ if (isCompressed(newValues)) {
1662
+ const decompressed = decompressValue(newValues);
1663
+ const remapped = remapValuesForClone(decompressed, sysIdMap, sourceFlow.sys_id, newFlowId);
1664
+ newValues = useCompression ? compressValue(remapped) : remapped;
1665
+ }
1666
+ else {
1667
+ newValues = remapValuesForClone(newValues, sysIdMap, sourceFlow.sys_id, newFlowId);
1668
+ if (useCompression)
1669
+ newValues = compressValue(newValues);
1670
+ }
1671
+ }
1672
+ // Remap parent_logic_instance if it exists
1673
+ let newParentLogic = null;
1674
+ if (action.parent_logic_instance) {
1675
+ newParentLogic = sysIdMap.get(action.parent_logic_instance) || action.parent_logic_instance;
1676
+ }
1677
+ const newAction = {
1678
+ sys_id: action.newSysId,
1679
+ flow: newFlowId,
1680
+ action_type: action.action_type,
1681
+ name: action.name,
1682
+ description: action.description,
1683
+ order: action.order,
1684
+ active: action.active,
1685
+ values: newValues
1686
+ };
1687
+ if (newParentLogic) {
1688
+ newAction.parent_logic_instance = newParentLogic;
1689
+ }
1690
+ if (action.branch) {
1691
+ newAction.branch = action.branch;
1692
+ }
1693
+ try {
1694
+ await client.post(`/api/now/table/${actionTable}`, newAction);
1695
+ createdRecords.push({
1696
+ table: actionTable,
1697
+ sys_id: action.newSysId,
1698
+ name: action.name,
1699
+ type: 'action',
1700
+ action_type: action.action_type
1701
+ });
1702
+ console.log(`✅ Cloned action: ${action.name}`);
1703
+ }
1704
+ catch (e) {
1705
+ errors.push(`Failed to clone action ${action.name}: ${e.message}`);
1706
+ }
1707
+ }
1708
+ }
1709
+ }
1710
+ catch (e) {
1711
+ errors.push(`Failed to fetch actions: ${e.message}`);
1712
+ }
1713
+ // ==================== 6. CLONE SUBFLOW INSTANCES ====================
1714
+ try {
1715
+ const subflowsResponse = await client.get(`/api/now/table/${FLOW_TABLES.SUBFLOW}`, {
1716
+ params: { sysparm_query: `flow=${sourceFlow.sys_id}` }
1717
+ });
1718
+ if (subflowsResponse.data.result) {
1719
+ for (const subflow of subflowsResponse.data.result) {
1720
+ const newSubflowId = generateSysId();
1721
+ sysIdMap.set(subflow.sys_id, newSubflowId);
1722
+ // Decompress and remap values
1723
+ let newValues = subflow.values;
1724
+ if (newValues) {
1725
+ if (isCompressed(newValues)) {
1726
+ const decompressed = decompressValue(newValues);
1727
+ const remapped = remapValuesForClone(decompressed, sysIdMap, sourceFlow.sys_id, newFlowId);
1728
+ newValues = useCompression ? compressValue(remapped) : remapped;
1729
+ }
1730
+ else {
1731
+ newValues = remapValuesForClone(newValues, sysIdMap, sourceFlow.sys_id, newFlowId);
1732
+ if (useCompression)
1733
+ newValues = compressValue(newValues);
1734
+ }
1735
+ }
1736
+ // Remap parent_logic_instance if it exists
1737
+ let newParentLogic = null;
1738
+ if (subflow.parent_logic_instance) {
1739
+ newParentLogic = sysIdMap.get(subflow.parent_logic_instance) || subflow.parent_logic_instance;
1740
+ }
1741
+ const newSubflow = {
1742
+ sys_id: newSubflowId,
1743
+ flow: newFlowId,
1744
+ sub_flow: subflow.sub_flow, // Reference to the actual subflow definition
1745
+ name: subflow.name,
1746
+ description: subflow.description,
1747
+ order: subflow.order,
1748
+ active: subflow.active,
1749
+ values: newValues
1750
+ };
1751
+ if (newParentLogic) {
1752
+ newSubflow.parent_logic_instance = newParentLogic;
1753
+ }
1754
+ if (subflow.branch) {
1755
+ newSubflow.branch = subflow.branch;
1756
+ }
1757
+ try {
1758
+ await client.post(`/api/now/table/${FLOW_TABLES.SUBFLOW}`, newSubflow);
1759
+ createdRecords.push({
1760
+ table: FLOW_TABLES.SUBFLOW,
1761
+ sys_id: newSubflowId,
1762
+ name: subflow.name,
1763
+ type: 'subflow_instance',
1764
+ sub_flow: subflow.sub_flow
1765
+ });
1766
+ console.log(`✅ Cloned subflow instance: ${subflow.name}`);
1767
+ }
1768
+ catch (e) {
1769
+ errors.push(`Failed to clone subflow instance ${subflow.name}: ${e.message}`);
1770
+ }
1771
+ }
1772
+ }
1773
+ }
1774
+ catch (e) {
1775
+ // Subflow table might not exist or be empty
1776
+ }
1777
+ // ==================== 7. CREATE SNAPSHOT ====================
1778
+ let clonedSnapshotId;
1779
+ if (create_snapshot) {
1780
+ clonedSnapshotId = generateSysId();
1781
+ // Build flow definition for snapshot
1782
+ const flowDefinition = {
1783
+ sys_id: newFlowId,
1784
+ name: new_name,
1785
+ internal_name: newInternalName,
1786
+ cloned_from: sourceFlow.sys_id,
1787
+ // Include created components
1788
+ components: createdRecords.map(r => ({
1789
+ table: r.table,
1790
+ sys_id: r.sys_id,
1791
+ name: r.name,
1792
+ type: r.type
1793
+ }))
1794
+ };
1795
+ const snapshotData = {
1796
+ sys_id: clonedSnapshotId,
1797
+ flow: newFlowId,
1798
+ name: `${new_name} - Initial (Cloned)`,
1799
+ version: '1.0.0',
1800
+ active: true,
1801
+ published: false,
1802
+ // Include flow definition
1803
+ definition: useCompression
1804
+ ? compressValue(flowDefinition, versionInfo.useCompressionHeader)
1805
+ : JSON.stringify(flowDefinition)
1806
+ };
1807
+ try {
1808
+ await client.post(`/api/now/table/${FLOW_TABLES.SNAPSHOT}`, snapshotData);
1809
+ createdRecords.push({
1810
+ table: FLOW_TABLES.SNAPSHOT,
1811
+ sys_id: clonedSnapshotId,
1812
+ name: snapshotData.name,
1813
+ type: 'snapshot'
1814
+ });
1815
+ console.log(`✅ Created snapshot: ${clonedSnapshotId}`);
1816
+ // *** CRITICAL FIX ***
1817
+ // Update the cloned flow with snapshot references
1818
+ console.log('Updating cloned flow with snapshot references...');
1819
+ try {
1820
+ await client.patch(`/api/now/table/${FLOW_TABLES.FLOW}/${newFlowId}`, {
1821
+ main_snapshot: clonedSnapshotId,
1822
+ latest_snapshot: clonedSnapshotId
1823
+ });
1824
+ console.log(`✅ Cloned flow updated with main_snapshot and latest_snapshot`);
1825
+ }
1826
+ catch (updateError) {
1827
+ errors.push(`Failed to update cloned flow with snapshot references: ${updateError.message}`);
1828
+ }
1829
+ }
1830
+ catch (e) {
1831
+ errors.push(`Failed to create snapshot: ${e.message}`);
1832
+ }
1833
+ }
1834
+ // ==================== BUILD RESULT ====================
1835
+ const instanceUrl = (client.defaults?.baseURL || '').replace('/api/now', '');
1836
+ // Summary statistics
1837
+ const summary = {
1838
+ flow: 1,
1839
+ variables: createdRecords.filter(r => r.type === 'variable').length,
1840
+ triggers: createdRecords.filter(r => r.type === 'trigger').length,
1841
+ logic: createdRecords.filter(r => r.type === 'logic').length,
1842
+ actions: createdRecords.filter(r => r.type === 'action').length,
1843
+ subflow_instances: createdRecords.filter(r => r.type === 'subflow_instance').length,
1844
+ snapshots: createdRecords.filter(r => r.type === 'snapshot').length,
1845
+ total: createdRecords.length
1846
+ };
1847
+ return (0, error_handler_js_1.createSuccessResult)({
1848
+ action: 'clone',
1849
+ source_flow: {
1850
+ sys_id: sourceFlow.sys_id,
1851
+ name: sourceFlow.name
1852
+ },
1853
+ cloned_flow: {
1854
+ sys_id: newFlowId,
1855
+ name: new_name,
1856
+ internal_name: newInternalName,
1857
+ active: false,
1858
+ url: `${instanceUrl}/sys_hub_flow.do?sys_id=${newFlowId}`,
1859
+ flow_designer_url: `${instanceUrl}/nav_to.do?uri=/flow-designer.do%23/flow/${newFlowId}`,
1860
+ main_snapshot: clonedSnapshotId,
1861
+ latest_snapshot: clonedSnapshotId
1862
+ },
1863
+ summary,
1864
+ created_records: createdRecords,
1865
+ sys_id_mapping: Object.fromEntries(sysIdMap),
1866
+ version_info: versionInfo,
1867
+ compression_used: useCompression,
1868
+ errors: errors.length > 0 ? errors : undefined
1869
+ });
1870
+ }
1871
+ /**
1872
+ * Activate a flow
1873
+ */
1874
+ async function handleActivate(args, client) {
1875
+ const { flow_id, flow_name } = args;
1876
+ const flow = await findFlow(client, flow_id, flow_name);
1877
+ if (!flow) {
1878
+ return (0, error_handler_js_1.createErrorResult)(`Flow not found: ${flow_id || flow_name}`);
1879
+ }
1880
+ try {
1881
+ await client.patch(`/api/now/table/${FLOW_TABLES.FLOW}/${flow.sys_id}`, {
1882
+ active: true,
1883
+ status: 'published'
1884
+ });
1885
+ return (0, error_handler_js_1.createSuccessResult)({
1886
+ action: 'activate',
1887
+ flow_id: flow.sys_id,
1888
+ flow_name: flow.name,
1889
+ active: true
1890
+ });
1891
+ }
1892
+ catch (error) {
1893
+ return (0, error_handler_js_1.createErrorResult)(`Failed to activate: ${error.response?.data?.error?.message || error.message}`);
1894
+ }
1895
+ }
1896
+ /**
1897
+ * Deactivate a flow
1898
+ */
1899
+ async function handleDeactivate(args, client) {
1900
+ const { flow_id, flow_name } = args;
1901
+ const flow = await findFlow(client, flow_id, flow_name);
1902
+ if (!flow) {
1903
+ return (0, error_handler_js_1.createErrorResult)(`Flow not found: ${flow_id || flow_name}`);
1904
+ }
1905
+ try {
1906
+ await client.patch(`/api/now/table/${FLOW_TABLES.FLOW}/${flow.sys_id}`, {
1907
+ active: false
1908
+ });
1909
+ return (0, error_handler_js_1.createSuccessResult)({
1910
+ action: 'deactivate',
1911
+ flow_id: flow.sys_id,
1912
+ flow_name: flow.name,
1913
+ active: false
1914
+ });
1915
+ }
1916
+ catch (error) {
1917
+ return (0, error_handler_js_1.createErrorResult)(`Failed to deactivate: ${error.response?.data?.error?.message || error.message}`);
1918
+ }
1919
+ }
1920
+ /**
1921
+ * Add an action to an existing flow
1922
+ */
1923
+ async function handleAddAction(args, client, versionInfo) {
1924
+ const { flow_id, flow_name, action_config, use_compression } = args;
1925
+ const flow = await findFlow(client, flow_id, flow_name);
1926
+ if (!flow) {
1927
+ return (0, error_handler_js_1.createErrorResult)(`Flow not found: ${flow_id || flow_name}`);
1928
+ }
1929
+ if (!action_config?.type) {
1930
+ return (0, error_handler_js_1.createErrorResult)('action_config.type is required');
1931
+ }
1932
+ const useCompression = use_compression ?? versionInfo.useCompression;
1933
+ const actionTable = versionInfo.useV2Tables ? FLOW_TABLES.ACTION_V2 : FLOW_TABLES.ACTION_V1;
1934
+ const actionSysId = generateSysId();
1935
+ const actionType = await getActionTypeSysId(client, action_config.type);
1936
+ if (!actionType) {
1937
+ return (0, error_handler_js_1.createErrorResult)(`Action type not found: ${action_config.type}`);
1938
+ }
1939
+ const maxOrder = await getMaxOrder(client, flow.sys_id, actionTable);
1940
+ const actionData = {
1941
+ sys_id: actionSysId,
1942
+ flow: flow.sys_id,
1943
+ action_type: actionType.sys_id,
1944
+ name: action_config.name || actionType.name,
1945
+ description: action_config.description || '',
1946
+ order: action_config.order ?? (maxOrder + 100),
1947
+ active: true
1948
+ };
1949
+ if (action_config.parent_logic_instance) {
1950
+ actionData.parent_logic_instance = action_config.parent_logic_instance;
1951
+ }
1952
+ if (action_config.branch) {
1953
+ actionData.branch = action_config.branch;
1954
+ }
1955
+ if (action_config.inputs) {
1956
+ actionData.values = buildActionValues(action_config.inputs, useCompression, versionInfo.useCompressionHeader);
1957
+ }
1958
+ try {
1959
+ await client.post(`/api/now/table/${actionTable}`, actionData);
1960
+ return (0, error_handler_js_1.createSuccessResult)({
1961
+ action: 'add_action',
1962
+ flow_id: flow.sys_id,
1963
+ added_action: {
1964
+ sys_id: actionSysId,
1965
+ name: action_config.name || actionType.name,
1966
+ type: action_config.type,
1967
+ order: actionData.order
1968
+ }
1969
+ });
1970
+ }
1971
+ catch (error) {
1972
+ return (0, error_handler_js_1.createErrorResult)(`Failed to add action: ${error.response?.data?.error?.message || error.message}`);
1973
+ }
1974
+ }
1975
+ /**
1976
+ * Remove an action from a flow
1977
+ */
1978
+ async function handleRemoveAction(args, client, versionInfo) {
1979
+ const { action_id } = args;
1980
+ if (!action_id) {
1981
+ return (0, error_handler_js_1.createErrorResult)('action_id is required');
1982
+ }
1983
+ const actionTable = versionInfo.useV2Tables ? FLOW_TABLES.ACTION_V2 : FLOW_TABLES.ACTION_V1;
1984
+ try {
1985
+ await client.delete(`/api/now/table/${actionTable}/${action_id}`);
1986
+ return (0, error_handler_js_1.createSuccessResult)({
1987
+ action: 'remove_action',
1988
+ action_id,
1989
+ removed: true
1990
+ });
1991
+ }
1992
+ catch (error) {
1993
+ return (0, error_handler_js_1.createErrorResult)(`Failed to remove action: ${error.response?.data?.error?.message || error.message}`);
1994
+ }
1995
+ }
1996
+ /**
1997
+ * Add a condition to an existing flow
1998
+ */
1999
+ async function handleAddCondition(args, client, versionInfo) {
2000
+ const { flow_id, flow_name, condition_config, use_compression } = args;
2001
+ const flow = await findFlow(client, flow_id, flow_name);
2002
+ if (!flow) {
2003
+ return (0, error_handler_js_1.createErrorResult)(`Flow not found: ${flow_id || flow_name}`);
2004
+ }
2005
+ if (!condition_config?.condition) {
2006
+ return (0, error_handler_js_1.createErrorResult)('condition_config.condition is required');
2007
+ }
2008
+ const useCompression = use_compression ?? versionInfo.useCompression;
2009
+ const logicTable = versionInfo.useV2Tables ? FLOW_TABLES.LOGIC_V2 : FLOW_TABLES.LOGIC_V1;
2010
+ const conditionSysId = generateSysId();
2011
+ const maxOrder = await getMaxOrder(client, flow.sys_id, logicTable);
2012
+ const conditionData = {
2013
+ sys_id: conditionSysId,
2014
+ flow: flow.sys_id,
2015
+ name: condition_config.name || 'If',
2016
+ logic_type: 'if',
2017
+ order: condition_config.order ?? (maxOrder + 100),
2018
+ active: true
2019
+ };
2020
+ const conditionValues = { condition: { value: condition_config.condition, valueType: 'string' } };
2021
+ conditionData.values = useCompression ? compressValue(conditionValues) : JSON.stringify(conditionValues);
2022
+ if (condition_config.parent_logic_instance) {
2023
+ conditionData.parent_logic_instance = condition_config.parent_logic_instance;
2024
+ }
2025
+ try {
2026
+ await client.post(`/api/now/table/${logicTable}`, conditionData);
2027
+ return (0, error_handler_js_1.createSuccessResult)({
2028
+ action: 'add_condition',
2029
+ flow_id: flow.sys_id,
2030
+ condition: {
2031
+ sys_id: conditionSysId,
2032
+ name: condition_config.name || 'If',
2033
+ order: conditionData.order
2034
+ }
2035
+ });
2036
+ }
2037
+ catch (error) {
2038
+ return (0, error_handler_js_1.createErrorResult)(`Failed to add condition: ${error.response?.data?.error?.message || error.message}`);
2039
+ }
2040
+ }
2041
+ /**
2042
+ * Add a loop to an existing flow
2043
+ */
2044
+ async function handleAddLoop(args, client, versionInfo) {
2045
+ const { flow_id, flow_name, loop_config, use_compression } = args;
2046
+ const flow = await findFlow(client, flow_id, flow_name);
2047
+ if (!flow) {
2048
+ return (0, error_handler_js_1.createErrorResult)(`Flow not found: ${flow_id || flow_name}`);
2049
+ }
2050
+ if (!loop_config?.type) {
2051
+ return (0, error_handler_js_1.createErrorResult)('loop_config.type is required');
2052
+ }
2053
+ const useCompression = use_compression ?? versionInfo.useCompression;
2054
+ const logicTable = versionInfo.useV2Tables ? FLOW_TABLES.LOGIC_V2 : FLOW_TABLES.LOGIC_V1;
2055
+ const loopSysId = generateSysId();
2056
+ const maxOrder = await getMaxOrder(client, flow.sys_id, logicTable);
2057
+ const loopData = {
2058
+ sys_id: loopSysId,
2059
+ flow: flow.sys_id,
2060
+ name: loop_config.name || (loop_config.type === 'for_each' ? 'For Each' : 'Do Until'),
2061
+ logic_type: loop_config.type,
2062
+ order: loop_config.order ?? (maxOrder + 100),
2063
+ active: true
2064
+ };
2065
+ const loopValues = {};
2066
+ if (loop_config.data_source)
2067
+ loopValues.data_source = { value: loop_config.data_source, valueType: 'reference' };
2068
+ if (loop_config.condition)
2069
+ loopValues.condition = { value: loop_config.condition, valueType: 'string' };
2070
+ if (loop_config.max_iterations)
2071
+ loopValues.max_iterations = { value: String(loop_config.max_iterations), valueType: 'integer' };
2072
+ if (Object.keys(loopValues).length > 0) {
2073
+ loopData.values = useCompression ? compressValue(loopValues) : JSON.stringify(loopValues);
2074
+ }
2075
+ if (loop_config.parent_logic_instance) {
2076
+ loopData.parent_logic_instance = loop_config.parent_logic_instance;
2077
+ }
2078
+ try {
2079
+ await client.post(`/api/now/table/${logicTable}`, loopData);
2080
+ return (0, error_handler_js_1.createSuccessResult)({
2081
+ action: 'add_loop',
2082
+ flow_id: flow.sys_id,
2083
+ loop: {
2084
+ sys_id: loopSysId,
2085
+ name: loopData.name,
2086
+ type: loop_config.type,
2087
+ order: loopData.order
2088
+ }
2089
+ });
2090
+ }
2091
+ catch (error) {
2092
+ return (0, error_handler_js_1.createErrorResult)(`Failed to add loop: ${error.response?.data?.error?.message || error.message}`);
2093
+ }
2094
+ }
2095
+ /**
2096
+ * Add a variable to an existing flow
2097
+ */
2098
+ async function handleAddVariable(args, client) {
2099
+ const { flow_id, flow_name, variable_config } = args;
2100
+ const flow = await findFlow(client, flow_id, flow_name);
2101
+ if (!flow) {
2102
+ return (0, error_handler_js_1.createErrorResult)(`Flow not found: ${flow_id || flow_name}`);
2103
+ }
2104
+ if (!variable_config?.name || !variable_config?.type || !variable_config?.direction) {
2105
+ return (0, error_handler_js_1.createErrorResult)('variable_config requires name, type, and direction');
2106
+ }
2107
+ const variableSysId = generateSysId();
2108
+ // Get max order for variables
2109
+ // Note: sys_hub_flow_variable uses 'model' field (not 'flow')
2110
+ let maxOrder = 0;
2111
+ try {
2112
+ const response = await client.get(`/api/now/table/${FLOW_TABLES.VARIABLE}`, {
2113
+ params: {
2114
+ sysparm_query: `model=${flow.sys_id}`,
2115
+ sysparm_fields: 'order',
2116
+ sysparm_orderby: 'DESCorder',
2117
+ sysparm_limit: 1
2118
+ }
2119
+ });
2120
+ if (response.data.result && response.data.result.length > 0) {
2121
+ maxOrder = parseInt(response.data.result[0].order || '0', 10);
2122
+ }
2123
+ }
2124
+ catch (e) { }
2125
+ const variableData = {
2126
+ sys_id: variableSysId,
2127
+ model: flow.sys_id,
2128
+ name: variable_config.name,
2129
+ label: variable_config.label || variable_config.name,
2130
+ description: variable_config.description || '',
2131
+ type: variable_config.type,
2132
+ direction: variable_config.direction,
2133
+ mandatory: variable_config.mandatory || false,
2134
+ order: maxOrder + 100
2135
+ };
2136
+ if (variable_config.default_value)
2137
+ variableData.default_value = variable_config.default_value;
2138
+ if (variable_config.reference_table)
2139
+ variableData.reference_table = variable_config.reference_table;
2140
+ try {
2141
+ await client.post(`/api/now/table/${FLOW_TABLES.VARIABLE}`, variableData);
2142
+ return (0, error_handler_js_1.createSuccessResult)({
2143
+ action: 'add_variable',
2144
+ flow_id: flow.sys_id,
2145
+ variable: {
2146
+ sys_id: variableSysId,
2147
+ name: variable_config.name,
2148
+ type: variable_config.type,
2149
+ direction: variable_config.direction
2150
+ }
2151
+ });
2152
+ }
2153
+ catch (error) {
2154
+ return (0, error_handler_js_1.createErrorResult)(`Failed to add variable: ${error.response?.data?.error?.message || error.message}`);
2155
+ }
2156
+ }
2157
+ /**
2158
+ * Get detailed flow structure with decompressed values
2159
+ */
2160
+ async function handleGetDetails(args, client, versionInfo) {
2161
+ const { flow_id, flow_name } = args;
2162
+ const flow = await findFlow(client, flow_id, flow_name);
2163
+ if (!flow) {
2164
+ return (0, error_handler_js_1.createErrorResult)(`Flow not found: ${flow_id || flow_name}`);
2165
+ }
2166
+ const details = {
2167
+ flow: flow,
2168
+ trigger: null,
2169
+ actions: [],
2170
+ logic: [],
2171
+ variables: [],
2172
+ snapshots: []
2173
+ };
2174
+ // Get trigger
2175
+ const triggerTable = versionInfo.useV2Tables ? FLOW_TABLES.TRIGGER_V2 : FLOW_TABLES.TRIGGER_V1;
2176
+ try {
2177
+ const response = await client.get(`/api/now/table/${triggerTable}`, {
2178
+ params: { sysparm_query: `flow=${flow.sys_id}` }
2179
+ });
2180
+ if (response.data.result && response.data.result.length > 0) {
2181
+ const trigger = response.data.result[0];
2182
+ if (trigger.values && isCompressed(trigger.values)) {
2183
+ trigger.values_decompressed = JSON.parse(decompressValue(trigger.values));
2184
+ }
2185
+ else if (trigger.values) {
2186
+ try {
2187
+ trigger.values_parsed = JSON.parse(trigger.values);
2188
+ }
2189
+ catch (e) { }
2190
+ }
2191
+ details.trigger = trigger;
2192
+ }
2193
+ }
2194
+ catch (e) { }
2195
+ // Get actions
2196
+ const actionTable = versionInfo.useV2Tables ? FLOW_TABLES.ACTION_V2 : FLOW_TABLES.ACTION_V1;
2197
+ try {
2198
+ const response = await client.get(`/api/now/table/${actionTable}`, {
2199
+ params: { sysparm_query: `flow=${flow.sys_id}`, sysparm_orderby: 'order' }
2200
+ });
2201
+ if (response.data.result) {
2202
+ details.actions = response.data.result.map((action) => {
2203
+ if (action.values && isCompressed(action.values)) {
2204
+ action.values_decompressed = JSON.parse(decompressValue(action.values));
2205
+ }
2206
+ else if (action.values) {
2207
+ try {
2208
+ action.values_parsed = JSON.parse(action.values);
2209
+ }
2210
+ catch (e) { }
2211
+ }
2212
+ return action;
2213
+ });
2214
+ }
2215
+ }
2216
+ catch (e) { }
2217
+ // Get logic (conditions, loops)
2218
+ const logicTable = versionInfo.useV2Tables ? FLOW_TABLES.LOGIC_V2 : FLOW_TABLES.LOGIC_V1;
2219
+ try {
2220
+ const response = await client.get(`/api/now/table/${logicTable}`, {
2221
+ params: { sysparm_query: `flow=${flow.sys_id}`, sysparm_orderby: 'order' }
2222
+ });
2223
+ if (response.data.result) {
2224
+ details.logic = response.data.result.map((logic) => {
2225
+ if (logic.values && isCompressed(logic.values)) {
2226
+ logic.values_decompressed = JSON.parse(decompressValue(logic.values));
2227
+ }
2228
+ else if (logic.values) {
2229
+ try {
2230
+ logic.values_parsed = JSON.parse(logic.values);
2231
+ }
2232
+ catch (e) { }
2233
+ }
2234
+ return logic;
2235
+ });
2236
+ }
2237
+ }
2238
+ catch (e) { }
2239
+ // Get variables
2240
+ // Note: sys_hub_flow_variable uses 'model' field (not 'flow')
2241
+ try {
2242
+ const response = await client.get(`/api/now/table/${FLOW_TABLES.VARIABLE}`, {
2243
+ params: { sysparm_query: `model=${flow.sys_id}`, sysparm_orderby: 'order' }
2244
+ });
2245
+ if (response.data.result) {
2246
+ details.variables = response.data.result;
2247
+ }
2248
+ }
2249
+ catch (e) { }
2250
+ // Get snapshots
2251
+ try {
2252
+ const response = await client.get(`/api/now/table/${FLOW_TABLES.SNAPSHOT}`, {
2253
+ params: { sysparm_query: `flow=${flow.sys_id}`, sysparm_orderby: 'DESCsys_created_on', sysparm_limit: 5 }
2254
+ });
2255
+ if (response.data.result) {
2256
+ details.snapshots = response.data.result;
2257
+ }
2258
+ }
2259
+ catch (e) { }
2260
+ // Build tree structure for nested actions
2261
+ const buildTree = () => {
2262
+ const tree = [];
2263
+ const itemMap = new Map();
2264
+ // Index all items
2265
+ for (const action of details.actions) {
2266
+ itemMap.set(action.sys_id, { ...action, _type: 'action', _children: [] });
2267
+ }
2268
+ for (const logic of details.logic) {
2269
+ itemMap.set(logic.sys_id, { ...logic, _type: 'logic', _children: [] });
2270
+ }
2271
+ // Build hierarchy
2272
+ itemMap.forEach((item, id) => {
2273
+ if (item.parent_logic_instance) {
2274
+ const parent = itemMap.get(item.parent_logic_instance);
2275
+ if (parent) {
2276
+ parent._children.push(item);
2277
+ }
2278
+ else {
2279
+ tree.push(item);
2280
+ }
2281
+ }
2282
+ else {
2283
+ tree.push(item);
2284
+ }
2285
+ });
2286
+ // Sort by order
2287
+ const sortByOrder = (items) => {
2288
+ items.sort((a, b) => (a.order || 0) - (b.order || 0));
2289
+ for (const item of items) {
2290
+ if (item._children && item._children.length > 0) {
2291
+ sortByOrder(item._children);
2292
+ }
2293
+ }
2294
+ };
2295
+ sortByOrder(tree);
2296
+ return tree;
2297
+ };
2298
+ details.tree_structure = buildTree();
2299
+ details.version_info = versionInfo;
2300
+ return (0, error_handler_js_1.createSuccessResult)({
2301
+ action: 'get_details',
2302
+ ...details
2303
+ });
2304
+ }
2305
+ /**
2306
+ * Publish flow changes (create new snapshot and activate)
2307
+ */
2308
+ async function handlePublish(args, client, versionInfo) {
2309
+ const { flow_id, flow_name } = args;
2310
+ const flow = await findFlow(client, flow_id, flow_name);
2311
+ if (!flow) {
2312
+ return (0, error_handler_js_1.createErrorResult)(`Flow not found: ${flow_id || flow_name}`);
2313
+ }
2314
+ const snapshotSysId = generateSysId();
2315
+ const useCompression = versionInfo.useCompression;
2316
+ // Get existing flow components for the definition
2317
+ const actionTable = versionInfo.useV2Tables ? FLOW_TABLES.ACTION_V2 : FLOW_TABLES.ACTION_V1;
2318
+ const triggerTable = versionInfo.useV2Tables ? FLOW_TABLES.TRIGGER_V2 : FLOW_TABLES.TRIGGER_V1;
2319
+ const logicTable = versionInfo.useV2Tables ? FLOW_TABLES.LOGIC_V2 : FLOW_TABLES.LOGIC_V1;
2320
+ // Fetch existing components
2321
+ let actions = [];
2322
+ let triggers = [];
2323
+ let logic = [];
2324
+ let variables = [];
2325
+ try {
2326
+ const actionsResp = await client.get(`/api/now/table/${actionTable}`, {
2327
+ params: { sysparm_query: `flow=${flow.sys_id}`, sysparm_fields: 'sys_id,name,order' }
2328
+ });
2329
+ actions = actionsResp.data.result || [];
2330
+ }
2331
+ catch (e) { }
2332
+ try {
2333
+ const triggersResp = await client.get(`/api/now/table/${triggerTable}`, {
2334
+ params: { sysparm_query: `flow=${flow.sys_id}`, sysparm_fields: 'sys_id,trigger_type' }
2335
+ });
2336
+ triggers = triggersResp.data.result || [];
2337
+ }
2338
+ catch (e) { }
2339
+ try {
2340
+ const logicResp = await client.get(`/api/now/table/${logicTable}`, {
2341
+ params: { sysparm_query: `flow=${flow.sys_id}`, sysparm_fields: 'sys_id,name,logic_type' }
2342
+ });
2343
+ logic = logicResp.data.result || [];
2344
+ }
2345
+ catch (e) { }
2346
+ try {
2347
+ const variablesResp = await client.get(`/api/now/table/${FLOW_TABLES.VARIABLE}`, {
2348
+ params: { sysparm_query: `model=${flow.sys_id}`, sysparm_fields: 'sys_id,name,type,direction' }
2349
+ });
2350
+ variables = variablesResp.data.result || [];
2351
+ }
2352
+ catch (e) { }
2353
+ // Build flow definition
2354
+ const flowDefinition = {
2355
+ sys_id: flow.sys_id,
2356
+ name: flow.name,
2357
+ internal_name: flow.internal_name,
2358
+ published_at: new Date().toISOString(),
2359
+ triggers: triggers.map(t => ({ sys_id: t.sys_id, trigger_type: t.trigger_type })),
2360
+ actions: actions.map(a => ({ sys_id: a.sys_id, name: a.name, order: a.order })),
2361
+ logic: logic.map(l => ({ sys_id: l.sys_id, name: l.name, logic_type: l.logic_type })),
2362
+ variables: variables.map(v => ({ sys_id: v.sys_id, name: v.name, type: v.type, direction: v.direction }))
2363
+ };
2364
+ // Create new snapshot with definition
2365
+ const snapshotData = {
2366
+ sys_id: snapshotSysId,
2367
+ flow: flow.sys_id,
2368
+ name: `${flow.name} - Published ${new Date().toISOString()}`,
2369
+ version: '1.0.0',
2370
+ active: true,
2371
+ published: true,
2372
+ definition: useCompression
2373
+ ? compressValue(flowDefinition, versionInfo.useCompressionHeader)
2374
+ : JSON.stringify(flowDefinition)
2375
+ };
2376
+ try {
2377
+ await client.post(`/api/now/table/${FLOW_TABLES.SNAPSHOT}`, snapshotData);
2378
+ }
2379
+ catch (error) {
2380
+ return (0, error_handler_js_1.createErrorResult)(`Failed to create snapshot: ${error.response?.data?.error?.message || error.message}`);
2381
+ }
2382
+ // *** CRITICAL FIX ***
2383
+ // Update flow with snapshot references AND activate
2384
+ try {
2385
+ await client.patch(`/api/now/table/${FLOW_TABLES.FLOW}/${flow.sys_id}`, {
2386
+ active: true,
2387
+ status: 'published',
2388
+ main_snapshot: snapshotSysId,
2389
+ latest_snapshot: snapshotSysId
2390
+ });
2391
+ }
2392
+ catch (error) {
2393
+ return (0, error_handler_js_1.createErrorResult)(`Failed to activate flow: ${error.response?.data?.error?.message || error.message}`);
2394
+ }
2395
+ return (0, error_handler_js_1.createSuccessResult)({
2396
+ action: 'publish',
2397
+ flow_id: flow.sys_id,
2398
+ flow_name: flow.name,
2399
+ snapshot_id: snapshotSysId,
2400
+ main_snapshot: snapshotSysId,
2401
+ latest_snapshot: snapshotSysId,
2402
+ active: true,
2403
+ components_included: {
2404
+ triggers: triggers.length,
2405
+ actions: actions.length,
2406
+ logic: logic.length,
2407
+ variables: variables.length
2408
+ }
2409
+ });
2410
+ }
2411
+ // ==================== MAIN EXECUTOR ====================
2412
+ async function execute(args, context) {
2413
+ const { action } = args;
2414
+ if (!action) {
2415
+ return (0, error_handler_js_1.createErrorResult)('action is required');
2416
+ }
2417
+ try {
2418
+ const client = await (0, auth_js_1.getAuthenticatedClient)(context);
2419
+ const versionInfo = await detectVersion(client);
2420
+ console.log(`Executing flow management action: ${action}`);
2421
+ console.log(`Detected version: ${versionInfo.version}, compression: ${versionInfo.useCompression}`);
2422
+ switch (action) {
2423
+ case 'create':
2424
+ return handleCreate(args, client, versionInfo);
2425
+ case 'update':
2426
+ return handleUpdate(args, client, versionInfo);
2427
+ case 'delete':
2428
+ return handleDelete(args, client, versionInfo);
2429
+ case 'clone':
2430
+ return handleClone(args, client, versionInfo);
2431
+ case 'activate':
2432
+ return handleActivate(args, client);
2433
+ case 'deactivate':
2434
+ return handleDeactivate(args, client);
2435
+ case 'add_action':
2436
+ return handleAddAction(args, client, versionInfo);
2437
+ case 'remove_action':
2438
+ return handleRemoveAction(args, client, versionInfo);
2439
+ case 'add_condition':
2440
+ return handleAddCondition(args, client, versionInfo);
2441
+ case 'add_loop':
2442
+ return handleAddLoop(args, client, versionInfo);
2443
+ case 'add_variable':
2444
+ return handleAddVariable(args, client);
2445
+ case 'get_details':
2446
+ return handleGetDetails(args, client, versionInfo);
2447
+ case 'publish':
2448
+ return handlePublish(args, client, versionInfo);
2449
+ default:
2450
+ return (0, error_handler_js_1.createErrorResult)(`Unknown action: ${action}`);
2451
+ }
2452
+ }
2453
+ catch (error) {
2454
+ return (0, error_handler_js_1.createErrorResult)(`Flow management failed: ${error.message}`);
2455
+ }
2456
+ }
2457
+ exports.version = '2.1.0';
2458
+ exports.author = 'Snow-Flow v8.41.13 - Flow Designer Management (snapshot fix)';
2459
+ //# sourceMappingURL=snow_manage_flow.js.map