snow-flow 10.0.1-dev.449 → 10.0.1-dev.451

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
- "version": "10.0.1-dev.449",
3
+ "version": "10.0.1-dev.451",
4
4
  "name": "snow-flow",
5
5
  "description": "Snow-Flow - ServiceNow Multi-Agent Development Framework powered by AI",
6
6
  "license": "Elastic-2.0",
@@ -38,15 +38,14 @@ function generateUUID(): string {
38
38
  });
39
39
  }
40
40
 
41
- async function getNextOrder(client: any, flowId: string, parentId?: string): Promise<number> {
41
+ async function getNextOrder(client: any, flowId: string): Promise<number> {
42
42
  let maxOrder = 0;
43
- // Query elements at the same nesting level (same parent)
44
- const parentFilter = parentId ? '^parent=' + parentId : '^parentISEMPTY';
43
+ // Order is global across all elements in the flow (not per-parent)
45
44
  for (const table of ['sys_hub_action_instance', 'sys_hub_flow_logic', 'sys_hub_sub_flow_instance']) {
46
45
  try {
47
46
  const resp = await client.get('/api/now/table/' + table, {
48
47
  params: {
49
- sysparm_query: 'flow=' + flowId + parentFilter + '^ORDERBYDESCorder',
48
+ sysparm_query: 'flow=' + flowId + '^ORDERBYDESCorder',
50
49
  sysparm_fields: 'order',
51
50
  sysparm_limit: 1
52
51
  }
@@ -73,6 +72,239 @@ async function executeFlowPatchMutation(
73
72
  return resp.data?.data?.global?.snFlowDesigner?.flow || resp.data;
74
73
  }
75
74
 
75
+ // Type label mapping for parameter definitions
76
+ const TYPE_LABELS: Record<string, string> = {
77
+ string: 'String', integer: 'Integer', boolean: 'True/False', choice: 'Choice',
78
+ reference: 'Reference', object: 'Object', glide_date_time: 'Date/Time',
79
+ glide_date: 'Date', decimal: 'Decimal', conditions: 'Conditions',
80
+ glide_list: 'List', html: 'HTML', script: 'Script', url: 'URL',
81
+ };
82
+
83
+ /**
84
+ * Build full action input objects matching the Flow Designer UI format.
85
+ * The UI sends inputs WITH parameter definitions in the INSERT mutation (not empty inputs + separate UPDATE).
86
+ */
87
+ async function buildActionInputsForInsert(
88
+ client: any,
89
+ actionDefId: string,
90
+ userValues?: Record<string, string>
91
+ ): Promise<{ inputs: any[]; resolvedInputs: Record<string, string>; actionParams: any[] }> {
92
+ // Query sys_hub_action_input with full field set
93
+ var actionParams: any[] = [];
94
+ try {
95
+ var resp = await client.get('/api/now/table/sys_hub_action_input', {
96
+ params: {
97
+ sysparm_query: 'model=' + actionDefId,
98
+ sysparm_fields: 'sys_id,element,label,internal_type,mandatory,default_value,order,max_length,hint,read_only,extended,data_structure,reference,reference_display,ref_qual,choice_option,table_name,column_name,use_dependent,dependent_on,show_ref_finder,local,attributes,sys_class_name',
99
+ sysparm_display_value: 'false',
100
+ sysparm_limit: 50
101
+ }
102
+ });
103
+ actionParams = resp.data.result || [];
104
+ } catch (_) {}
105
+
106
+ // Fuzzy-match user-provided values to actual field names
107
+ var resolvedInputs: Record<string, string> = {};
108
+ if (userValues) {
109
+ var paramElements = actionParams.map(function (p: any) { return p.element; });
110
+ for (var [key, value] of Object.entries(userValues)) {
111
+ if (paramElements.includes(key)) {
112
+ resolvedInputs[key] = value;
113
+ continue;
114
+ }
115
+ var match = actionParams.find(function (p: any) {
116
+ return p.element.endsWith('_' + key) || p.element === key || (p.label || '').toLowerCase() === key.toLowerCase();
117
+ });
118
+ if (match) resolvedInputs[match.element] = value;
119
+ else resolvedInputs[key] = value;
120
+ }
121
+ }
122
+
123
+ // Build full input objects with parameter definitions (matching UI format)
124
+ var inputs = actionParams.map(function (rec: any) {
125
+ var paramType = rec.internal_type || 'string';
126
+ var userVal = resolvedInputs[rec.element] || '';
127
+ return {
128
+ id: rec.sys_id,
129
+ name: rec.element,
130
+ children: [],
131
+ displayValue: { value: '' },
132
+ value: { schemaless: false, schemalessValue: '', value: userVal },
133
+ parameter: {
134
+ id: rec.sys_id,
135
+ label: rec.label || rec.element,
136
+ name: rec.element,
137
+ type: paramType,
138
+ type_label: TYPE_LABELS[paramType] || paramType.charAt(0).toUpperCase() + paramType.slice(1),
139
+ hint: rec.hint || '',
140
+ order: parseInt(rec.order || '0', 10),
141
+ extended: rec.extended === 'true',
142
+ mandatory: rec.mandatory === 'true',
143
+ readonly: rec.read_only === 'true',
144
+ maxsize: parseInt(rec.max_length || '8000', 10),
145
+ data_structure: rec.data_structure || '',
146
+ reference: rec.reference || '',
147
+ reference_display: rec.reference_display || '',
148
+ ref_qual: rec.ref_qual || '',
149
+ choiceOption: rec.choice_option || '',
150
+ table: rec.table_name || '',
151
+ columnName: rec.column_name || '',
152
+ defaultValue: rec.default_value || '',
153
+ use_dependent: rec.use_dependent === 'true',
154
+ dependent_on: rec.dependent_on || '',
155
+ show_ref_finder: rec.show_ref_finder === 'true',
156
+ local: rec.local === 'true',
157
+ attributes: rec.attributes || '',
158
+ sys_class_name: rec.sys_class_name || '',
159
+ children: []
160
+ }
161
+ };
162
+ });
163
+
164
+ return { inputs, resolvedInputs, actionParams };
165
+ }
166
+
167
+ /**
168
+ * Find all flow elements at order >= targetOrder and build GraphQL update payloads to bump their order by 1.
169
+ * This is needed when inserting an element inside a flow logic block (e.g. action inside If block).
170
+ * The UI does this to keep the Else block (and other subsequent elements) after the new element.
171
+ */
172
+ async function findElementsToReorder(
173
+ client: any,
174
+ flowId: string,
175
+ targetOrder: number
176
+ ): Promise<{ flowLogicUpdates: any[]; actionUpdates: any[]; subflowUpdates: any[] }> {
177
+ var flowLogicUpdates: any[] = [];
178
+ var actionUpdates: any[] = [];
179
+ var subflowUpdates: any[] = [];
180
+
181
+ // Flow logic blocks
182
+ try {
183
+ var resp = await client.get('/api/now/table/sys_hub_flow_logic', {
184
+ params: {
185
+ sysparm_query: 'flow=' + flowId + '^order>=' + targetOrder,
186
+ sysparm_fields: 'sys_id,order,ui_unique_identifier',
187
+ sysparm_limit: 100
188
+ }
189
+ });
190
+ for (var rec of (resp.data.result || [])) {
191
+ var uuid = rec.ui_unique_identifier;
192
+ var curOrder = parseInt(rec.order || '0', 10);
193
+ if (uuid && curOrder >= targetOrder) {
194
+ flowLogicUpdates.push({ order: String(curOrder + 1), uiUniqueIdentifier: uuid, type: 'flowlogic' });
195
+ }
196
+ }
197
+ } catch (_) {}
198
+
199
+ // Action instances
200
+ try {
201
+ var resp2 = await client.get('/api/now/table/sys_hub_action_instance', {
202
+ params: {
203
+ sysparm_query: 'flow=' + flowId + '^order>=' + targetOrder,
204
+ sysparm_fields: 'sys_id,order,ui_unique_identifier',
205
+ sysparm_limit: 100
206
+ }
207
+ });
208
+ for (var rec2 of (resp2.data.result || [])) {
209
+ var uuid2 = rec2.ui_unique_identifier;
210
+ var curOrder2 = parseInt(rec2.order || '0', 10);
211
+ if (uuid2 && curOrder2 >= targetOrder) {
212
+ actionUpdates.push({ order: String(curOrder2 + 1), uiUniqueIdentifier: uuid2, type: 'action' });
213
+ }
214
+ }
215
+ } catch (_) {}
216
+
217
+ // Subflow instances
218
+ try {
219
+ var resp3 = await client.get('/api/now/table/sys_hub_sub_flow_instance', {
220
+ params: {
221
+ sysparm_query: 'flow=' + flowId + '^order>=' + targetOrder,
222
+ sysparm_fields: 'sys_id,order,ui_unique_identifier',
223
+ sysparm_limit: 100
224
+ }
225
+ });
226
+ for (var rec3 of (resp3.data.result || [])) {
227
+ var uuid3 = rec3.ui_unique_identifier;
228
+ var curOrder3 = parseInt(rec3.order || '0', 10);
229
+ if (uuid3 && curOrder3 >= targetOrder) {
230
+ subflowUpdates.push({ order: String(curOrder3 + 1), uiUniqueIdentifier: uuid3, type: 'subflow' });
231
+ }
232
+ }
233
+ } catch (_) {}
234
+
235
+ return { flowLogicUpdates, actionUpdates, subflowUpdates };
236
+ }
237
+
238
+ /**
239
+ * Calculate the insert order for an element being added inside a parent flow logic block.
240
+ * Returns the order right after the last child of the parent, and reorder info for elements that need bumping.
241
+ */
242
+ async function calculateInsertOrder(
243
+ client: any,
244
+ flowId: string,
245
+ parentUiId?: string,
246
+ explicitOrder?: number
247
+ ): Promise<{ insertOrder: number; reorders: { flowLogicUpdates: any[]; actionUpdates: any[]; subflowUpdates: any[] } }> {
248
+ var noReorders = { flowLogicUpdates: [], actionUpdates: [], subflowUpdates: [] };
249
+
250
+ // Explicit order: bump elements at that position
251
+ if (explicitOrder) {
252
+ var reorders = await findElementsToReorder(client, flowId, explicitOrder);
253
+ return { insertOrder: explicitOrder, reorders };
254
+ }
255
+
256
+ // No parent: append at end, no bumping needed
257
+ if (!parentUiId) {
258
+ var nextOrder = await getNextOrder(client, flowId);
259
+ return { insertOrder: nextOrder, reorders: noReorders };
260
+ }
261
+
262
+ // Parent specified: find parent's order, then find max child order
263
+ var parentSysId = '';
264
+ var parentOrder = 0;
265
+ try {
266
+ var pResp = await client.get('/api/now/table/sys_hub_flow_logic', {
267
+ params: {
268
+ sysparm_query: 'flow=' + flowId + '^ui_unique_identifier=' + parentUiId,
269
+ sysparm_fields: 'sys_id,order',
270
+ sysparm_limit: 1
271
+ }
272
+ });
273
+ var found = pResp.data.result?.[0];
274
+ if (found) {
275
+ parentSysId = found.sys_id;
276
+ parentOrder = parseInt(found.order || '0', 10);
277
+ }
278
+ } catch (_) {}
279
+
280
+ if (!parentSysId) {
281
+ // Fallback: append at end
282
+ var fallbackOrder = await getNextOrder(client, flowId);
283
+ return { insertOrder: fallbackOrder, reorders: noReorders };
284
+ }
285
+
286
+ // Find max order of existing children of this parent
287
+ var maxChildOrder = parentOrder;
288
+ for (var table of ['sys_hub_action_instance', 'sys_hub_flow_logic', 'sys_hub_sub_flow_instance']) {
289
+ try {
290
+ var cResp = await client.get('/api/now/table/' + table, {
291
+ params: {
292
+ sysparm_query: 'flow=' + flowId + '^parent=' + parentSysId + '^ORDERBYDESCorder',
293
+ sysparm_fields: 'order',
294
+ sysparm_limit: 1
295
+ }
296
+ });
297
+ var childOrder = parseInt(cResp.data.result?.[0]?.order || '0', 10);
298
+ if (childOrder > maxChildOrder) maxChildOrder = childOrder;
299
+ } catch (_) {}
300
+ }
301
+
302
+ // Insert after last child; bump everything at that position
303
+ var insertOrder = maxChildOrder + 1;
304
+ var reorderInfo = await findElementsToReorder(client, flowId, insertOrder);
305
+ return { insertOrder: insertOrder, reorders: reorderInfo };
306
+ }
307
+
76
308
  async function addTriggerViaGraphQL(
77
309
  client: any,
78
310
  flowId: string,
@@ -210,7 +442,6 @@ async function addActionViaGraphQL(
210
442
 
211
443
  // Dynamically look up action definition in sys_hub_action_type_snapshot
212
444
  let actionDefId: string | null = null;
213
- // Try exact match on internal_name first, then name
214
445
  for (const field of ['internal_name', 'name']) {
215
446
  if (actionDefId) break;
216
447
  try {
@@ -224,7 +455,6 @@ async function addActionViaGraphQL(
224
455
  }
225
456
  } catch (_) {}
226
457
  }
227
- // Fallback: LIKE search on both fields
228
458
  if (!actionDefId) {
229
459
  try {
230
460
  const resp = await client.get('/api/now/table/sys_hub_action_type_snapshot', {
@@ -243,89 +473,71 @@ async function addActionViaGraphQL(
243
473
  }
244
474
  if (!actionDefId) return { success: false, error: 'Action definition not found for: ' + actionType, steps };
245
475
 
246
- // Look up available input fields from sys_hub_action_input
247
- let actionParams: { element: string; label: string; mandatory: boolean; default_value: string; internal_type: string }[] = [];
248
- try {
249
- const resp = await client.get('/api/now/table/sys_hub_action_input', {
250
- params: {
251
- sysparm_query: 'model=' + actionDefId,
252
- sysparm_fields: 'sys_id,element,label,mandatory,default_value,internal_type',
253
- sysparm_display_value: 'false',
254
- sysparm_limit: 50
255
- }
256
- });
257
- actionParams = (resp.data.result || []).map((r: any) => ({
258
- element: r.element,
259
- label: r.label,
260
- mandatory: r.mandatory === 'true' || r.mandatory === true,
261
- default_value: r.default_value || '',
262
- internal_type: r.internal_type || ''
263
- }));
264
- steps.available_inputs = actionParams;
265
- } catch (_) {}
476
+ // Build full input objects with parameter definitions (matching UI format)
477
+ const inputResult = await buildActionInputsForInsert(client, actionDefId, inputs);
478
+ steps.available_inputs = inputResult.actionParams.map((p: any) => ({ element: p.element, label: p.label }));
479
+ steps.resolved_inputs = inputResult.resolvedInputs;
480
+
481
+ // Calculate insertion order and find elements that need reordering
482
+ const orderCalc = await calculateInsertOrder(client, flowId, parentUiId, order);
483
+ const resolvedOrder = orderCalc.insertOrder;
484
+ steps.insert_order = resolvedOrder;
485
+ if (orderCalc.reorders.flowLogicUpdates.length > 0 || orderCalc.reorders.actionUpdates.length > 0 || orderCalc.reorders.subflowUpdates.length > 0) {
486
+ steps.reordered_elements = {
487
+ flowLogics: orderCalc.reorders.flowLogicUpdates.length,
488
+ actions: orderCalc.reorders.actionUpdates.length,
489
+ subflows: orderCalc.reorders.subflowUpdates.length
490
+ };
491
+ }
266
492
 
267
- // Match provided inputs to actual field names (fuzzy: "message" → "log_message", "level" → "log_level")
268
- const resolvedInputs: Record<string, string> = {};
269
- if (inputs) {
270
- const paramElements = actionParams.map(p => p.element);
271
- for (const [key, value] of Object.entries(inputs)) {
272
- // Exact match first
273
- if (paramElements.includes(key)) {
274
- resolvedInputs[key] = value;
275
- continue;
276
- }
277
- // Try to find a param whose element ends with the key or contains it
278
- const match = actionParams.find(p => p.element.endsWith('_' + key) || p.element === key || p.label.toLowerCase() === key.toLowerCase());
279
- if (match) {
280
- resolvedInputs[match.element] = value;
281
- } else {
282
- resolvedInputs[key] = value;
283
- }
493
+ const uuid = generateUUID();
494
+ const actionResponseFields = 'actions { inserts { sysId uiUniqueIdentifier __typename } updates deletes __typename }' +
495
+ ' flowLogics { inserts { sysId uiUniqueIdentifier __typename } updates deletes __typename }' +
496
+ ' subflows { inserts { sysId uiUniqueIdentifier __typename } updates deletes __typename }';
497
+
498
+ // Build mutation payload — single INSERT with full inputs (matching UI behavior)
499
+ const flowPatch: any = {
500
+ flowId: flowId,
501
+ actions: {
502
+ insert: [{
503
+ actionTypeSysId: actionDefId,
504
+ metadata: '{"predicates":[]}',
505
+ flowSysId: flowId,
506
+ generationSource: '',
507
+ order: String(resolvedOrder),
508
+ parent: parentUiId || '',
509
+ uiUniqueIdentifier: uuid,
510
+ type: 'action',
511
+ parentUiId: parentUiId || '',
512
+ inputs: inputResult.inputs
513
+ }]
514
+ }
515
+ };
516
+
517
+ // Merge reorder updates + parent flow logic update into the mutation
518
+ var flowLogicUpdates: any[] = orderCalc.reorders.flowLogicUpdates.slice();
519
+ if (parentUiId) {
520
+ // Add parent update (no order change, just signals the parent was modified)
521
+ // Avoid duplicating if the parent is already in the reorder list
522
+ var parentAlreadyIncluded = flowLogicUpdates.some(function (u: any) { return u.uiUniqueIdentifier === parentUiId; });
523
+ if (!parentAlreadyIncluded) {
524
+ flowLogicUpdates.push({ uiUniqueIdentifier: parentUiId, type: 'flowlogic' });
284
525
  }
285
- steps.resolved_inputs = resolvedInputs;
526
+ }
527
+ if (flowLogicUpdates.length > 0) {
528
+ flowPatch.flowLogics = { update: flowLogicUpdates };
529
+ }
530
+ if (orderCalc.reorders.actionUpdates.length > 0) {
531
+ flowPatch.actions.update = orderCalc.reorders.actionUpdates;
532
+ }
533
+ if (orderCalc.reorders.subflowUpdates.length > 0) {
534
+ flowPatch.subflows = { update: orderCalc.reorders.subflowUpdates };
286
535
  }
287
536
 
288
- const uuid = generateUUID();
289
- const resolvedOrder = order || await getNextOrder(client, flowId, parentUiId);
290
- const actionResponseFields = 'actions { inserts { sysId uiUniqueIdentifier __typename } updates deletes __typename }';
291
537
  try {
292
- const result = await executeFlowPatchMutation(client, {
293
- flowId: flowId,
294
- actions: {
295
- insert: [{
296
- actionTypeSysId: actionDefId,
297
- metadata: '{"predicates":[]}',
298
- flowSysId: flowId,
299
- generationSource: '',
300
- order: String(resolvedOrder),
301
- parent: parentUiId || '',
302
- uiUniqueIdentifier: uuid,
303
- type: 'action',
304
- parentUiId: parentUiId || '',
305
- inputs: []
306
- }]
307
- }
308
- }, actionResponseFields);
309
-
538
+ const result = await executeFlowPatchMutation(client, flowPatch, actionResponseFields);
310
539
  const actionId = result?.actions?.inserts?.[0]?.sysId;
311
540
  steps.insert = { success: !!actionId, actionId, uuid };
312
-
313
- if (actionId && Object.keys(resolvedInputs).length > 0) {
314
- const updateInputs = Object.entries(resolvedInputs).map(([name, value]) => ({
315
- name,
316
- value: { schemaless: false, schemalessValue: '', value: String(value) }
317
- }));
318
- try {
319
- await executeFlowPatchMutation(client, {
320
- flowId: flowId,
321
- actions: { update: [{ uiUniqueIdentifier: uuid, type: 'action', inputs: updateInputs }] }
322
- }, actionResponseFields);
323
- steps.value_update = { success: true, inputs: updateInputs.map(i => i.name) };
324
- } catch (e: any) {
325
- steps.value_update = { success: false, error: e.message };
326
- }
327
- }
328
-
329
541
  return { success: true, actionId: actionId || undefined, steps };
330
542
  } catch (e: any) {
331
543
  steps.insert = { success: false, error: e.message };
@@ -386,49 +598,63 @@ async function addFlowLogicViaGraphQL(
386
598
  }
387
599
  if (!defId) return { success: false, error: 'Flow logic definition not found for: ' + logicType, steps };
388
600
 
601
+ // Calculate insertion order with reordering
602
+ const orderCalc = await calculateInsertOrder(client, flowId, parentUiId, order);
603
+ const resolvedOrder = orderCalc.insertOrder;
604
+ steps.insert_order = resolvedOrder;
605
+
389
606
  const uuid = generateUUID();
390
- const resolvedOrder = order || await getNextOrder(client, flowId, parentUiId);
391
- const logicResponseFields = 'flowLogics { inserts { sysId uiUniqueIdentifier __typename } updates deletes __typename }';
392
- try {
393
- const result = await executeFlowPatchMutation(client, {
394
- flowId: flowId,
395
- flowLogics: {
396
- insert: [{
397
- order: String(resolvedOrder),
398
- uiUniqueIdentifier: uuid,
399
- parent: '',
400
- metadata: '{"predicates":[]}',
401
- flowSysId: flowId,
402
- generationSource: '',
403
- definitionId: defId,
404
- type: 'flowlogic',
405
- parentUiId: parentUiId || '',
406
- inputs: []
407
- }]
408
- }
409
- }, logicResponseFields);
607
+ const logicResponseFields = 'flowLogics { inserts { sysId uiUniqueIdentifier __typename } updates deletes __typename }' +
608
+ ' actions { inserts { sysId uiUniqueIdentifier __typename } updates deletes __typename }' +
609
+ ' subflows { inserts { sysId uiUniqueIdentifier __typename } updates deletes __typename }';
610
+
611
+ // Build flow logic input objects (for INSERT — matching UI format with id, name, value, parameter)
612
+ var logicInputObjects: any[] = [];
613
+ if (inputs && Object.keys(inputs).length > 0) {
614
+ logicInputObjects = Object.entries(inputs).map(function ([name, value]) {
615
+ return {
616
+ name: name,
617
+ value: { schemaless: false, schemalessValue: '', value: String(value) }
618
+ };
619
+ });
620
+ }
621
+
622
+ var flowPatch: any = {
623
+ flowId: flowId,
624
+ flowLogics: {
625
+ insert: [{
626
+ order: String(resolvedOrder),
627
+ uiUniqueIdentifier: uuid,
628
+ parent: '',
629
+ metadata: '{"predicates":[]}',
630
+ flowSysId: flowId,
631
+ generationSource: '',
632
+ definitionId: defId,
633
+ type: 'flowlogic',
634
+ parentUiId: parentUiId || '',
635
+ inputs: logicInputObjects
636
+ }]
637
+ }
638
+ };
639
+
640
+ // Merge reorder updates into flowLogics.update
641
+ if (orderCalc.reorders.flowLogicUpdates.length > 0) {
642
+ if (!flowPatch.flowLogics.update) flowPatch.flowLogics.update = [];
643
+ flowPatch.flowLogics.update = flowPatch.flowLogics.update.concat(orderCalc.reorders.flowLogicUpdates);
644
+ }
645
+ if (orderCalc.reorders.actionUpdates.length > 0) {
646
+ flowPatch.actions = { update: orderCalc.reorders.actionUpdates };
647
+ }
648
+ if (orderCalc.reorders.subflowUpdates.length > 0) {
649
+ flowPatch.subflows = { update: orderCalc.reorders.subflowUpdates };
650
+ }
410
651
 
652
+ try {
653
+ const result = await executeFlowPatchMutation(client, flowPatch, logicResponseFields);
411
654
  const logicId = result?.flowLogics?.inserts?.[0]?.sysId;
412
655
  steps.insert = { success: !!logicId, logicId, uuid };
413
656
  if (!logicId) return { success: false, steps, error: 'GraphQL flow logic INSERT returned no ID' };
414
657
 
415
- // Update with input values if provided
416
- if (inputs && Object.keys(inputs).length > 0) {
417
- const updateInputs = Object.entries(inputs).map(([name, value]) => ({
418
- name,
419
- value: { schemaless: false, schemalessValue: '', value: String(value) }
420
- }));
421
- try {
422
- await executeFlowPatchMutation(client, {
423
- flowId: flowId,
424
- flowLogics: { update: [{ uiUniqueIdentifier: uuid, type: 'flowlogic', inputs: updateInputs }] }
425
- }, logicResponseFields);
426
- steps.value_update = { success: true, inputs: updateInputs.map(i => i.name) };
427
- } catch (e: any) {
428
- steps.value_update = { success: false, error: e.message };
429
- }
430
- }
431
-
432
658
  return { success: true, logicId, steps };
433
659
  } catch (e: any) {
434
660
  steps.insert = { success: false, error: e.message };
@@ -494,50 +720,67 @@ async function addSubflowCallViaGraphQL(
494
720
 
495
721
  if (!subflowName) subflowName = subflowId;
496
722
 
723
+ // Calculate insertion order with reordering
724
+ const orderCalc = await calculateInsertOrder(client, flowId, parentUiId, order);
725
+ const resolvedOrder = orderCalc.insertOrder;
726
+ steps.insert_order = resolvedOrder;
727
+
497
728
  const uuid = generateUUID();
498
- const resolvedOrder = order || await getNextOrder(client, flowId, parentUiId);
499
- const subflowResponseFields = 'subflows { inserts { sysId uiUniqueIdentifier __typename } updates deletes __typename }';
500
- try {
501
- const result = await executeFlowPatchMutation(client, {
502
- flowId: flowId,
503
- subflows: {
504
- insert: [{
505
- metadata: '{"predicates":[]}',
506
- flowSysId: flowId,
507
- generationSource: '',
508
- name: subflowName,
509
- order: String(resolvedOrder),
510
- parent: parentUiId || '',
511
- subflowSysId: subflowSysId,
512
- uiUniqueIdentifier: uuid,
513
- type: 'subflow',
514
- parentUiId: parentUiId || '',
515
- inputs: []
516
- }]
517
- }
518
- }, subflowResponseFields);
729
+ const subflowResponseFields = 'subflows { inserts { sysId uiUniqueIdentifier __typename } updates deletes __typename }' +
730
+ ' flowLogics { inserts { sysId uiUniqueIdentifier __typename } updates deletes __typename }' +
731
+ ' actions { inserts { sysId uiUniqueIdentifier __typename } updates deletes __typename }';
732
+
733
+ // Build subflow input objects for INSERT
734
+ var subInputObjects: any[] = [];
735
+ if (inputs && Object.keys(inputs).length > 0) {
736
+ subInputObjects = Object.entries(inputs).map(function ([name, value]) {
737
+ return { name: name, value: { schemaless: false, schemalessValue: '', value: String(value) } };
738
+ });
739
+ }
519
740
 
520
- const callId = result?.subflows?.inserts?.[0]?.sysId;
521
- steps.insert = { success: !!callId, callId, uuid };
522
- if (!callId) return { success: false, steps, error: 'GraphQL subflow INSERT returned no ID' };
741
+ const subPatch: any = {
742
+ flowId: flowId,
743
+ subflows: {
744
+ insert: [{
745
+ metadata: '{"predicates":[]}',
746
+ flowSysId: flowId,
747
+ generationSource: '',
748
+ name: subflowName,
749
+ order: String(resolvedOrder),
750
+ parent: parentUiId || '',
751
+ subflowSysId: subflowSysId,
752
+ uiUniqueIdentifier: uuid,
753
+ type: 'subflow',
754
+ parentUiId: parentUiId || '',
755
+ inputs: subInputObjects
756
+ }]
757
+ }
758
+ };
523
759
 
524
- // Update with input values if provided
525
- if (inputs && Object.keys(inputs).length > 0) {
526
- const updateInputs = Object.entries(inputs).map(([name, value]) => ({
527
- name,
528
- value: { schemaless: false, schemalessValue: '', value: String(value) }
529
- }));
530
- try {
531
- await executeFlowPatchMutation(client, {
532
- flowId: flowId,
533
- subflows: { update: [{ uiUniqueIdentifier: uuid, type: 'subflow', inputs: updateInputs }] }
534
- }, subflowResponseFields);
535
- steps.value_update = { success: true, inputs: updateInputs.map(i => i.name) };
536
- } catch (e: any) {
537
- steps.value_update = { success: false, error: e.message };
538
- }
760
+ // Merge reorder updates + parent flow logic update
761
+ var subFlowLogicUpdates: any[] = orderCalc.reorders.flowLogicUpdates.slice();
762
+ if (parentUiId) {
763
+ var parentIncluded = subFlowLogicUpdates.some(function (u: any) { return u.uiUniqueIdentifier === parentUiId; });
764
+ if (!parentIncluded) {
765
+ subFlowLogicUpdates.push({ uiUniqueIdentifier: parentUiId, type: 'flowlogic' });
539
766
  }
767
+ }
768
+ if (subFlowLogicUpdates.length > 0) {
769
+ subPatch.flowLogics = { update: subFlowLogicUpdates };
770
+ }
771
+ if (orderCalc.reorders.actionUpdates.length > 0) {
772
+ subPatch.actions = { update: orderCalc.reorders.actionUpdates };
773
+ }
774
+ if (orderCalc.reorders.subflowUpdates.length > 0) {
775
+ if (!subPatch.subflows.update) subPatch.subflows.update = [];
776
+ subPatch.subflows.update = subPatch.subflows.update.concat(orderCalc.reorders.subflowUpdates);
777
+ }
540
778
 
779
+ try {
780
+ const result = await executeFlowPatchMutation(client, subPatch, subflowResponseFields);
781
+ const callId = result?.subflows?.inserts?.[0]?.sysId;
782
+ steps.insert = { success: !!callId, callId, uuid };
783
+ if (!callId) return { success: false, steps, error: 'GraphQL subflow INSERT returned no ID' };
541
784
  return { success: true, callId, steps };
542
785
  } catch (e: any) {
543
786
  steps.insert = { success: false, error: e.message };