snow-flow 10.0.1-dev.453 → 10.0.1-dev.455

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.453",
3
+ "version": "10.0.1-dev.455",
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,23 +38,46 @@ function generateUUID(): string {
38
38
  });
39
39
  }
40
40
 
41
- async function getNextOrder(client: any, flowId: string): Promise<number> {
42
- let maxOrder = 0;
43
- // Order is global across all elements in the flow (not per-parent)
44
- for (const table of ['sys_hub_action_instance', 'sys_hub_flow_logic', 'sys_hub_sub_flow_instance']) {
45
- try {
46
- const resp = await client.get('/api/now/table/' + table, {
47
- params: {
48
- sysparm_query: 'flow=' + flowId + '^ORDERBYDESCorder',
49
- sysparm_fields: 'order',
50
- sysparm_limit: 1
51
- }
52
- });
53
- const order = parseInt(resp.data.result?.[0]?.order || '0', 10);
54
- if (order > maxOrder) maxOrder = order;
55
- } catch (_) {}
41
+ /**
42
+ * Get the current max global order from the flow's version payload.
43
+ *
44
+ * IMPORTANT: Flow Designer elements (actions, flow logic, subflows) are NOT stored as
45
+ * individual records in sys_hub_action_instance / sys_hub_flow_logic / sys_hub_sub_flow_instance.
46
+ * They only exist inside the sys_hub_flow_version.payload (managed by the GraphQL API).
47
+ * Table API queries on these tables will always return 0 results.
48
+ *
49
+ * This function reads the version payload to determine the current max order.
50
+ * If the payload can't be read/parsed, it returns 0 (caller should use explicit order).
51
+ */
52
+ async function getMaxOrderFromVersion(client: any, flowId: string): Promise<number> {
53
+ try {
54
+ const resp = await client.get('/api/now/table/sys_hub_flow_version', {
55
+ params: {
56
+ sysparm_query: 'flow=' + flowId + '^ORDERBYDESCsys_created_on',
57
+ sysparm_fields: 'sys_id,payload',
58
+ sysparm_limit: 1
59
+ }
60
+ });
61
+ const payload = resp.data.result?.[0]?.payload;
62
+ if (!payload) return 0;
63
+ // Payload may be JSON containing flow elements with order fields
64
+ const parsed = typeof payload === 'string' ? JSON.parse(payload) : payload;
65
+ // Extract max order from any structure — search recursively for "order" values
66
+ let maxOrder = 0;
67
+ const findOrders = (obj: any) => {
68
+ if (!obj || typeof obj !== 'object') return;
69
+ if (obj.order !== undefined) {
70
+ const o = parseInt(String(obj.order), 10);
71
+ if (!isNaN(o) && o > maxOrder) maxOrder = o;
72
+ }
73
+ if (Array.isArray(obj)) obj.forEach(findOrders);
74
+ else Object.values(obj).forEach(findOrders);
75
+ };
76
+ findOrders(parsed);
77
+ return maxOrder;
78
+ } catch (_) {
79
+ return 0;
56
80
  }
57
- return maxOrder + 1;
58
81
  }
59
82
 
60
83
  async function executeFlowPatchMutation(
@@ -171,161 +194,43 @@ async function buildActionInputsForInsert(
171
194
  return { inputs, resolvedInputs, actionParams };
172
195
  }
173
196
 
174
- /**
175
- * Find all flow elements at order >= targetOrder and build GraphQL update payloads to bump their order by 1.
176
- * This is needed when inserting an element inside a flow logic block (e.g. action inside If block).
177
- * The UI does this to keep the Else block (and other subsequent elements) after the new element.
178
- */
179
- async function findElementsToReorder(
180
- client: any,
181
- flowId: string,
182
- targetOrder: number
183
- ): Promise<{ flowLogicUpdates: any[]; actionUpdates: any[]; subflowUpdates: any[] }> {
184
- var flowLogicUpdates: any[] = [];
185
- var actionUpdates: any[] = [];
186
- var subflowUpdates: any[] = [];
187
-
188
- // Flow logic blocks
189
- try {
190
- var resp = await client.get('/api/now/table/sys_hub_flow_logic', {
191
- params: {
192
- sysparm_query: 'flow=' + flowId + '^order>=' + targetOrder,
193
- sysparm_fields: 'sys_id,order,ui_unique_identifier',
194
- sysparm_limit: 100
195
- }
196
- });
197
- for (var rec of (resp.data.result || [])) {
198
- var uuid = str(rec.ui_unique_identifier);
199
- var curOrder = parseInt(str(rec.order) || '0', 10);
200
- if (uuid && curOrder >= targetOrder) {
201
- flowLogicUpdates.push({ order: String(curOrder + 1), uiUniqueIdentifier: uuid, type: 'flowlogic' });
202
- }
203
- }
204
- } catch (_) {}
205
-
206
- // Action instances
207
- try {
208
- var resp2 = await client.get('/api/now/table/sys_hub_action_instance', {
209
- params: {
210
- sysparm_query: 'flow=' + flowId + '^order>=' + targetOrder,
211
- sysparm_fields: 'sys_id,order,ui_unique_identifier',
212
- sysparm_limit: 100
213
- }
214
- });
215
- for (var rec2 of (resp2.data.result || [])) {
216
- var uuid2 = str(rec2.ui_unique_identifier);
217
- var curOrder2 = parseInt(str(rec2.order) || '0', 10);
218
- if (uuid2 && curOrder2 >= targetOrder) {
219
- actionUpdates.push({ order: String(curOrder2 + 1), uiUniqueIdentifier: uuid2, type: 'action' });
220
- }
221
- }
222
- } catch (_) {}
223
-
224
- // Subflow instances
225
- try {
226
- var resp3 = await client.get('/api/now/table/sys_hub_sub_flow_instance', {
227
- params: {
228
- sysparm_query: 'flow=' + flowId + '^order>=' + targetOrder,
229
- sysparm_fields: 'sys_id,order,ui_unique_identifier',
230
- sysparm_limit: 100
231
- }
232
- });
233
- for (var rec3 of (resp3.data.result || [])) {
234
- var uuid3 = str(rec3.ui_unique_identifier);
235
- var curOrder3 = parseInt(str(rec3.order) || '0', 10);
236
- if (uuid3 && curOrder3 >= targetOrder) {
237
- subflowUpdates.push({ order: String(curOrder3 + 1), uiUniqueIdentifier: uuid3, type: 'subflow' });
238
- }
239
- }
240
- } catch (_) {}
241
-
242
- return { flowLogicUpdates, actionUpdates, subflowUpdates };
243
- }
197
+ // Note: reordering of existing elements is NOT possible via Table API because
198
+ // Flow Designer elements only exist in the version payload (managed by GraphQL).
199
+ // The caller must provide the correct global order. When inserting between existing
200
+ // elements, the caller should include the necessary sibling updates in the same
201
+ // GraphQL mutation (matching how the Flow Designer UI works).
244
202
 
245
203
  /**
246
- * Calculate the insert order for an element being added inside a parent flow logic block.
247
- * Returns the order right after the last child of the parent, and reorder info for elements that need bumping.
204
+ * Calculate the insert order for a new flow element.
205
+ *
206
+ * Flow Designer uses GLOBAL ordering: all elements (actions, flow logic, subflows)
207
+ * share a single sequential numbering (1, 2, 3, 4, 5...).
208
+ *
209
+ * IMPORTANT: Flow elements do NOT exist as individual records in the Table API.
210
+ * They only live inside the version payload managed by the GraphQL API.
211
+ * Therefore we CANNOT query Table API to find existing elements or their orders.
212
+ *
213
+ * Order computation strategy:
214
+ * 1. If explicit order is provided → use it as the global order (the caller knows best)
215
+ * 2. Otherwise → try to determine max order from version payload, return max + 1
248
216
  */
249
217
  async function calculateInsertOrder(
250
218
  client: any,
251
219
  flowId: string,
252
- parentUiId?: string,
220
+ _parentUiId?: string,
253
221
  explicitOrder?: number
254
- ): Promise<{ insertOrder: number; reorders: { flowLogicUpdates: any[]; actionUpdates: any[]; subflowUpdates: any[] } }> {
255
- var noReorders = { flowLogicUpdates: [], actionUpdates: [], subflowUpdates: [] };
256
-
257
- // ── Parent specified: ALWAYS compute global order from parent context ──
258
- // Flow Designer uses GLOBAL ordering for ALL elements (not per-parent).
259
- // When a parent is specified, any explicit "order" is ignored because callers
260
- // typically pass a local/relative order (1, 2, 3) which would conflict with
261
- // elements already at those global positions.
262
- if (parentUiId) {
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 = str(found.sys_id);
276
- parentOrder = parseInt(str(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 (query by both sys_id and UUID)
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(str(cResp.data.result?.[0]?.order) || '0', 10);
298
- if (childOrder > maxChildOrder) maxChildOrder = childOrder;
299
- } catch (_) {}
300
- // Also try querying by parent UUID (GraphQL may store UUID in parent field)
301
- try {
302
- var cResp2 = await client.get('/api/now/table/' + table, {
303
- params: {
304
- sysparm_query: 'flow=' + flowId + '^parent=' + parentUiId + '^ORDERBYDESCorder',
305
- sysparm_fields: 'order',
306
- sysparm_limit: 1
307
- }
308
- });
309
- var childOrder2 = parseInt(str(cResp2.data.result?.[0]?.order) || '0', 10);
310
- if (childOrder2 > maxChildOrder) maxChildOrder = childOrder2;
311
- } catch (_) {}
312
- }
313
-
314
- // Insert after last child; bump everything at that position
315
- var insertOrder = maxChildOrder + 1;
316
- var reorderInfo = await findElementsToReorder(client, flowId, insertOrder);
317
- return { insertOrder: insertOrder, reorders: reorderInfo };
318
- }
319
-
320
- // ── Top-level with explicit order: bump elements at that position ──
321
- if (explicitOrder) {
322
- var reorders = await findElementsToReorder(client, flowId, explicitOrder);
323
- return { insertOrder: explicitOrder, reorders };
324
- }
325
-
326
- // ── Top-level without order: append at end ──
327
- var nextOrder = await getNextOrder(client, flowId);
328
- return { insertOrder: nextOrder, reorders: noReorders };
222
+ ): Promise<number> {
223
+ // Explicit order provided: trust it as the correct global order.
224
+ // This matches how the Flow Designer UI works — it computes the correct global
225
+ // order client-side and sends it in the mutation.
226
+ if (explicitOrder) return explicitOrder;
227
+
228
+ // No explicit order: try to find max order from version payload
229
+ const maxOrder = await getMaxOrderFromVersion(client, flowId);
230
+ if (maxOrder > 0) return maxOrder + 1;
231
+
232
+ // Last resort fallback
233
+ return 1;
329
234
  }
330
235
 
331
236
  async function addTriggerViaGraphQL(
@@ -498,17 +403,9 @@ async function addActionViaGraphQL(
498
403
  steps.available_inputs = inputResult.actionParams.map((p: any) => ({ element: p.element, label: p.label }));
499
404
  steps.resolved_inputs = inputResult.resolvedInputs;
500
405
 
501
- // Calculate insertion order and find elements that need reordering
502
- const orderCalc = await calculateInsertOrder(client, flowId, parentUiId, order);
503
- const resolvedOrder = orderCalc.insertOrder;
406
+ // Calculate insertion order
407
+ const resolvedOrder = await calculateInsertOrder(client, flowId, parentUiId, order);
504
408
  steps.insert_order = resolvedOrder;
505
- if (orderCalc.reorders.flowLogicUpdates.length > 0 || orderCalc.reorders.actionUpdates.length > 0 || orderCalc.reorders.subflowUpdates.length > 0) {
506
- steps.reordered_elements = {
507
- flowLogics: orderCalc.reorders.flowLogicUpdates.length,
508
- actions: orderCalc.reorders.actionUpdates.length,
509
- subflows: orderCalc.reorders.subflowUpdates.length
510
- };
511
- }
512
409
 
513
410
  const uuid = generateUUID();
514
411
  const actionResponseFields = 'actions { inserts { sysId uiUniqueIdentifier __typename } updates deletes __typename }' +
@@ -534,24 +431,9 @@ async function addActionViaGraphQL(
534
431
  }
535
432
  };
536
433
 
537
- // Merge reorder updates + parent flow logic update into the mutation
538
- var flowLogicUpdates: any[] = orderCalc.reorders.flowLogicUpdates.slice();
434
+ // Add parent flow logic update signal (tells GraphQL the parent was modified)
539
435
  if (parentUiId) {
540
- // Add parent update (no order change, just signals the parent was modified)
541
- // Avoid duplicating if the parent is already in the reorder list
542
- var parentAlreadyIncluded = flowLogicUpdates.some(function (u: any) { return u.uiUniqueIdentifier === parentUiId; });
543
- if (!parentAlreadyIncluded) {
544
- flowLogicUpdates.push({ uiUniqueIdentifier: parentUiId, type: 'flowlogic' });
545
- }
546
- }
547
- if (flowLogicUpdates.length > 0) {
548
- flowPatch.flowLogics = { update: flowLogicUpdates };
549
- }
550
- if (orderCalc.reorders.actionUpdates.length > 0) {
551
- flowPatch.actions.update = orderCalc.reorders.actionUpdates;
552
- }
553
- if (orderCalc.reorders.subflowUpdates.length > 0) {
554
- flowPatch.subflows = { update: orderCalc.reorders.subflowUpdates };
436
+ flowPatch.flowLogics = { update: [{ uiUniqueIdentifier: parentUiId, type: 'flowlogic' }] };
555
437
  }
556
438
 
557
439
  try {
@@ -618,9 +500,8 @@ async function addFlowLogicViaGraphQL(
618
500
  }
619
501
  if (!defId) return { success: false, error: 'Flow logic definition not found for: ' + logicType, steps };
620
502
 
621
- // Calculate insertion order with reordering
622
- const orderCalc = await calculateInsertOrder(client, flowId, parentUiId, order);
623
- const resolvedOrder = orderCalc.insertOrder;
503
+ // Calculate insertion order
504
+ const resolvedOrder = await calculateInsertOrder(client, flowId, parentUiId, order);
624
505
  steps.insert_order = resolvedOrder;
625
506
 
626
507
  const uuid = generateUUID();
@@ -657,24 +538,9 @@ async function addFlowLogicViaGraphQL(
657
538
  }
658
539
  };
659
540
 
660
- // Merge reorder updates + parent update into flowLogics.update
661
- var flowLogicUpdatesForPatch: any[] = orderCalc.reorders.flowLogicUpdates.slice();
541
+ // Add parent flow logic update signal
662
542
  if (parentUiId) {
663
- // Signal the parent flow logic was modified (same as action insert does)
664
- var parentAlreadyInList = flowLogicUpdatesForPatch.some(function (u: any) { return u.uiUniqueIdentifier === parentUiId; });
665
- if (!parentAlreadyInList) {
666
- flowLogicUpdatesForPatch.push({ uiUniqueIdentifier: parentUiId, type: 'flowlogic' });
667
- }
668
- }
669
- if (flowLogicUpdatesForPatch.length > 0) {
670
- if (!flowPatch.flowLogics.update) flowPatch.flowLogics.update = [];
671
- flowPatch.flowLogics.update = flowPatch.flowLogics.update.concat(flowLogicUpdatesForPatch);
672
- }
673
- if (orderCalc.reorders.actionUpdates.length > 0) {
674
- flowPatch.actions = { update: orderCalc.reorders.actionUpdates };
675
- }
676
- if (orderCalc.reorders.subflowUpdates.length > 0) {
677
- flowPatch.subflows = { update: orderCalc.reorders.subflowUpdates };
543
+ flowPatch.flowLogics.update = [{ uiUniqueIdentifier: parentUiId, type: 'flowlogic' }];
678
544
  }
679
545
 
680
546
  try {
@@ -748,9 +614,8 @@ async function addSubflowCallViaGraphQL(
748
614
 
749
615
  if (!subflowName) subflowName = subflowId;
750
616
 
751
- // Calculate insertion order with reordering
752
- const orderCalc = await calculateInsertOrder(client, flowId, parentUiId, order);
753
- const resolvedOrder = orderCalc.insertOrder;
617
+ // Calculate insertion order
618
+ const resolvedOrder = await calculateInsertOrder(client, flowId, parentUiId, order);
754
619
  steps.insert_order = resolvedOrder;
755
620
 
756
621
  const uuid = generateUUID();
@@ -785,23 +650,9 @@ async function addSubflowCallViaGraphQL(
785
650
  }
786
651
  };
787
652
 
788
- // Merge reorder updates + parent flow logic update
789
- var subFlowLogicUpdates: any[] = orderCalc.reorders.flowLogicUpdates.slice();
653
+ // Add parent flow logic update signal
790
654
  if (parentUiId) {
791
- var parentIncluded = subFlowLogicUpdates.some(function (u: any) { return u.uiUniqueIdentifier === parentUiId; });
792
- if (!parentIncluded) {
793
- subFlowLogicUpdates.push({ uiUniqueIdentifier: parentUiId, type: 'flowlogic' });
794
- }
795
- }
796
- if (subFlowLogicUpdates.length > 0) {
797
- subPatch.flowLogics = { update: subFlowLogicUpdates };
798
- }
799
- if (orderCalc.reorders.actionUpdates.length > 0) {
800
- subPatch.actions = { update: orderCalc.reorders.actionUpdates };
801
- }
802
- if (orderCalc.reorders.subflowUpdates.length > 0) {
803
- if (!subPatch.subflows.update) subPatch.subflows.update = [];
804
- subPatch.subflows.update = subPatch.subflows.update.concat(orderCalc.reorders.subflowUpdates);
655
+ subPatch.flowLogics = { update: [{ uiUniqueIdentifier: parentUiId, type: 'flowlogic' }] };
805
656
  }
806
657
 
807
658
  try {
@@ -1094,7 +945,7 @@ export const toolDefinition: MCPToolDefinition = {
1094
945
  },
1095
946
  order: {
1096
947
  type: 'number',
1097
- description: 'Position/order of the element in the flow (for add_* actions). Auto-detected if not provided (appends after last element).'
948
+ description: 'GLOBAL position/order of the element in the flow (for add_* actions). Flow Designer uses global sequential ordering across ALL elements (1, 2, 3...). For nested elements (e.g. action inside If block), the order must be global — not relative to the parent. Example: If block at order 2, first child should be order 3, second child order 4, etc. Each add_* response includes insert_order — use the previous insert_order + 1 for the next element.'
1098
949
  },
1099
950
  type: {
1100
951
  type: 'string',