snow-flow 10.0.41 → 10.0.42

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.41",
3
+ "version": "10.0.42",
4
4
  "name": "snow-flow",
5
5
  "description": "Snow-Flow - ServiceNow Multi-Agent Development Framework powered by AI",
6
6
  "license": "Elastic-2.0",
@@ -54,17 +54,69 @@ function generateUUID(): string {
54
54
  }
55
55
 
56
56
  /**
57
- * Get the current max global order from the flow's version payload.
57
+ * Get the current max global order from the flow's live state.
58
58
  *
59
59
  * IMPORTANT: Flow Designer elements (actions, flow logic, subflows) are NOT stored as
60
60
  * individual records in sys_hub_action_instance / sys_hub_flow_logic / sys_hub_sub_flow_instance.
61
- * They only exist inside the sys_hub_flow_version.payload (managed by the GraphQL API).
61
+ * They only exist inside the version payload managed by the GraphQL API.
62
62
  * Table API queries on these tables will always return 0 results.
63
63
  *
64
- * This function reads the version payload to determine the current max order.
65
- * If the payload can't be read/parsed, it returns 0 (caller should use explicit order).
64
+ * Strategy (most reliable first):
65
+ * 1. processflow API always returns real-time state, even right after mutations
66
+ * 2. sys_hub_flow_version.payload — fallback, may be stale after rapid mutations
67
+ * 3. Return 0 (caller should use explicit order)
66
68
  */
67
69
  async function getMaxOrderFromVersion(client: any, flowId: string): Promise<number> {
70
+ // Helper: recursively extract all "order" values from a nested structure
71
+ const findMaxOrder = (obj: any): number => {
72
+ if (!obj || typeof obj !== 'object') return 0;
73
+ let max = 0;
74
+ if (obj.order !== undefined) {
75
+ const o = parseInt(String(obj.order), 10);
76
+ if (!isNaN(o) && o > max) max = o;
77
+ }
78
+ if (Array.isArray(obj)) {
79
+ for (let i = 0; i < obj.length; i++) {
80
+ const v = findMaxOrder(obj[i]);
81
+ if (v > max) max = v;
82
+ }
83
+ } else {
84
+ const vals = Object.values(obj);
85
+ for (let i = 0; i < vals.length; i++) {
86
+ const v = findMaxOrder(vals[i]);
87
+ if (v > max) max = v;
88
+ }
89
+ }
90
+ return max;
91
+ };
92
+
93
+ // Strategy 1: processflow API (real-time, same as Flow Designer UI)
94
+ try {
95
+ const pfResp = await client.get('/api/now/processflow/flow/' + flowId);
96
+ const pfRaw = pfResp.data;
97
+ if (typeof pfRaw === 'string') {
98
+ // XML — extract all order="N" and <order>N</order> values
99
+ let max = 0;
100
+ const orderAttrRx = /\border="(\d+)"/g;
101
+ const orderElemRx = /<order>(\d+)<\/order>/g;
102
+ let m;
103
+ while ((m = orderAttrRx.exec(pfRaw)) !== null) {
104
+ const v = parseInt(m[1], 10);
105
+ if (v > max) max = v;
106
+ }
107
+ while ((m = orderElemRx.exec(pfRaw)) !== null) {
108
+ const v = parseInt(m[1], 10);
109
+ if (v > max) max = v;
110
+ }
111
+ if (max > 0) return max;
112
+ } else if (pfRaw && typeof pfRaw === 'object') {
113
+ const data = pfRaw.result?.data || pfRaw.result || pfRaw.data || pfRaw;
114
+ const max = findMaxOrder(data);
115
+ if (max > 0) return max;
116
+ }
117
+ } catch (_) {}
118
+
119
+ // Strategy 2: sys_hub_flow_version.payload (may be stale after rapid mutations)
68
120
  try {
69
121
  const resp = await client.get('/api/now/table/sys_hub_flow_version', {
70
122
  params: {
@@ -74,25 +126,14 @@ async function getMaxOrderFromVersion(client: any, flowId: string): Promise<numb
74
126
  }
75
127
  });
76
128
  const payload = resp.data.result?.[0]?.payload;
77
- if (!payload) return 0;
78
- // Payload may be JSON containing flow elements with order fields
79
- const parsed = typeof payload === 'string' ? JSON.parse(payload) : payload;
80
- // Extract max order from any structure — search recursively for "order" values
81
- let maxOrder = 0;
82
- const findOrders = (obj: any) => {
83
- if (!obj || typeof obj !== 'object') return;
84
- if (obj.order !== undefined) {
85
- const o = parseInt(String(obj.order), 10);
86
- if (!isNaN(o) && o > maxOrder) maxOrder = o;
87
- }
88
- if (Array.isArray(obj)) obj.forEach(findOrders);
89
- else Object.values(obj).forEach(findOrders);
90
- };
91
- findOrders(parsed);
92
- return maxOrder;
93
- } catch (_) {
94
- return 0;
95
- }
129
+ if (payload) {
130
+ const parsed = typeof payload === 'string' ? JSON.parse(payload) : payload;
131
+ const max = findMaxOrder(parsed);
132
+ if (max > 0) return max;
133
+ }
134
+ } catch (_) {}
135
+
136
+ return 0;
96
137
  }
97
138
 
98
139
  async function executeFlowPatchMutation(
@@ -1446,6 +1487,8 @@ async function addActionViaGraphQL(
1446
1487
  // ── Rewrite shorthand pills in generic action inputs (e.g. Log "message") ────
1447
1488
  // Non-record inputs (anything other than record/table_name/values) that contain
1448
1489
  // {{trigger.current.X}} need rewriting to {{Created or Updated_1.current.X}} + labelCache.
1490
+ // Also handle inputs that already have full pill references (e.g. {{Created or Updated_1.current.caller_id}})
1491
+ // — these still need labelCache entries or they render as empty grey pills.
1449
1492
  var PILL_SHORTHANDS_ACTION = ['trigger.current', 'current', 'trigger_record', 'trigger.record'];
1450
1493
  var RECORD_INPUTS = ['record', 'table_name', 'values'];
1451
1494
  var genericPillInputs: { name: string; fields: string[]; isRecordLevel: boolean }[] = [];
@@ -1460,28 +1503,35 @@ async function addActionViaGraphQL(
1460
1503
  var gpHasShorthand = PILL_SHORTHANDS_ACTION.some(function (sh) {
1461
1504
  return gpVal.includes('{{' + sh + '.') || gpVal.includes('{{' + sh + '}}');
1462
1505
  });
1463
- if (!gpHasShorthand) continue;
1464
-
1465
- // Get trigger info if not already fetched
1466
- if (!actionTriggerInfo) {
1467
- actionTriggerInfo = await getFlowTriggerInfo(client, flowId);
1468
- steps.action_trigger_info = {
1469
- dataPillBase: actionTriggerInfo.dataPillBase, triggerName: actionTriggerInfo.triggerName,
1470
- table: actionTriggerInfo.table, tableLabel: actionTriggerInfo.tableLabel
1471
- };
1506
+
1507
+ // If it has shorthand pills, we need trigger info to rewrite them.
1508
+ // If it has ANY pill references (even non-shorthand), we still need trigger info for labelCache.
1509
+ if (gpHasShorthand || gpVal.includes('{{')) {
1510
+ // Get trigger info if not already fetched
1511
+ if (!actionTriggerInfo) {
1512
+ actionTriggerInfo = await getFlowTriggerInfo(client, flowId);
1513
+ steps.action_trigger_info = {
1514
+ dataPillBase: actionTriggerInfo.dataPillBase, triggerName: actionTriggerInfo.triggerName,
1515
+ table: actionTriggerInfo.table, tableLabel: actionTriggerInfo.tableLabel
1516
+ };
1517
+ }
1518
+ if (!actionTriggerInfo.dataPillBase) continue;
1472
1519
  }
1473
- if (!actionTriggerInfo.dataPillBase) continue;
1474
1520
 
1475
1521
  var gpPillBase = actionTriggerInfo.dataPillBase;
1476
1522
  var gpOrigVal = gpVal;
1477
- for (var gsi = 0; gsi < PILL_SHORTHANDS_ACTION.length; gsi++) {
1478
- var gsh = PILL_SHORTHANDS_ACTION[gsi];
1479
- gpVal = gpVal.split('{{' + gsh + '.').join('{{' + gpPillBase + '.');
1480
- gpVal = gpVal.split('{{' + gsh + '}}').join('{{' + gpPillBase + '}}');
1523
+
1524
+ // Rewrite shorthand pills to full dataPillBase
1525
+ if (gpHasShorthand) {
1526
+ for (var gsi = 0; gsi < PILL_SHORTHANDS_ACTION.length; gsi++) {
1527
+ var gsh = PILL_SHORTHANDS_ACTION[gsi];
1528
+ gpVal = gpVal.split('{{' + gsh + '.').join('{{' + gpPillBase + '.');
1529
+ gpVal = gpVal.split('{{' + gsh + '}}').join('{{' + gpPillBase + '}}');
1530
+ }
1531
+ gpInput.value.value = gpVal;
1481
1532
  }
1482
- gpInput.value.value = gpVal;
1483
1533
 
1484
- // Extract field names from pills for labelCache
1534
+ // Extract field names from ALL pills in the value for labelCache
1485
1535
  var gpPillFields: string[] = [];
1486
1536
  var gpIsRecordLevel = false;
1487
1537
  var gpPillRx = /\{\{([^}]+)\}\}/g;
@@ -1495,8 +1545,14 @@ async function addActionViaGraphQL(
1495
1545
  }
1496
1546
  }
1497
1547
 
1498
- genericPillInputs.push({ name: gpInput.name, fields: gpPillFields, isRecordLevel: gpIsRecordLevel });
1499
- steps['pill_rewrite_' + gpInput.name] = { original: gpOrigVal, rewritten: gpVal };
1548
+ if (gpPillFields.length > 0 || gpIsRecordLevel) {
1549
+ genericPillInputs.push({ name: gpInput.name, fields: gpPillFields, isRecordLevel: gpIsRecordLevel });
1550
+ if (gpOrigVal !== gpVal) {
1551
+ steps['pill_rewrite_' + gpInput.name] = { original: gpOrigVal, rewritten: gpVal };
1552
+ } else {
1553
+ steps['pill_labelcache_' + gpInput.name] = { fields: gpPillFields, isRecordLevel: gpIsRecordLevel };
1554
+ }
1555
+ }
1500
1556
  }
1501
1557
 
1502
1558
  // For record actions: clear data pill values from INSERT — they'll be set via separate UPDATE
@@ -2614,11 +2670,12 @@ async function addFlowLogicViaGraphQL(
2614
2670
  [/(\}\})\s+ends\s+with\s+/gi, '$1ENDSWITH'],
2615
2671
  [/(\}\})\s+greater\s+than\s+/gi, '$1>'],
2616
2672
  [/(\}\})\s+less\s+than\s+/gi, '$1<'],
2617
- // Symbol operators: == / === / != / !== / >= / <= / > / < with optional spaces
2618
- [/(\}\})\s*===?\s*/g, '$1='],
2619
- [/(\}\})\s*!==?\s*/g, '$1!='],
2673
+ // Symbol operators: = / == / === / != / !== / >= / <= / > / < with optional spaces
2674
+ // IMPORTANT: >= and <= must come BEFORE single > and < to avoid partial matches
2620
2675
  [/(\}\})\s*>=\s*/g, '$1>='],
2621
2676
  [/(\}\})\s*<=\s*/g, '$1<='],
2677
+ [/(\}\})\s*!={1,2}\s*/g, '$1!='],
2678
+ [/(\}\})\s*={1,3}\s*/g, '$1='],
2622
2679
  [/(\}\})\s*>\s*/g, '$1>'],
2623
2680
  [/(\}\})\s*<\s*/g, '$1<'],
2624
2681
  ];
@@ -2637,8 +2694,11 @@ async function addFlowLogicViaGraphQL(
2637
2694
  var hasShorthandPills = rawCondition.includes('{{') && PILL_SHORTHANDS.some(function (sh) {
2638
2695
  return rawCondition.includes('{{' + sh + '.') || rawCondition.includes('{{' + sh + '}}');
2639
2696
  });
2697
+ // Also detect conditions that already contain full data pill references (non-shorthand)
2698
+ // e.g. {{Created or Updated_1.current.priority}}=1 — these still need two-step + labelCache
2699
+ var hasFullPillRefs = rawCondition.includes('{{') && !hasShorthandPills;
2640
2700
 
2641
- if (rawCondition && rawCondition !== '^EQ' && (isStandardEncodedQuery(rawCondition) || hasShorthandPills)) {
2701
+ if (rawCondition && rawCondition !== '^EQ' && (isStandardEncodedQuery(rawCondition) || hasShorthandPills || hasFullPillRefs)) {
2642
2702
  conditionTriggerInfo = await getFlowTriggerInfo(client, flowId);
2643
2703
  steps.trigger_info = {
2644
2704
  dataPillBase: conditionTriggerInfo.dataPillBase, triggerName: conditionTriggerInfo.triggerName,
@@ -2674,6 +2734,7 @@ async function addFlowLogicViaGraphQL(
2674
2734
  // ── Rewrite shorthand pills in non-condition inputs (e.g. FOR_EACH "items") ────
2675
2735
  // These inputs may contain {{trigger.current}} or {{current.field}} that need rewriting
2676
2736
  // to the full dataPillBase (e.g. {{Created or Updated_1.current}}) + labelCache for rendering.
2737
+ // Also handle inputs that already have full pill references — they still need labelCache.
2677
2738
  var nonConditionPillInputs: { name: string; fields: string[]; isRecordLevel: boolean }[] = [];
2678
2739
  for (var nci = 0; nci < inputResult.inputs.length; nci++) {
2679
2740
  var ncInput = inputResult.inputs[nci];
@@ -2684,9 +2745,8 @@ async function addFlowLogicViaGraphQL(
2684
2745
  var ncHasShorthand = PILL_SHORTHANDS.some(function (sh) {
2685
2746
  return ncVal.includes('{{' + sh + '.') || ncVal.includes('{{' + sh + '}}');
2686
2747
  });
2687
- if (!ncHasShorthand) continue;
2688
2748
 
2689
- // Get trigger info if not already fetched
2749
+ // Get trigger info for shorthand rewriting OR for labelCache building (even non-shorthand pills)
2690
2750
  if (!conditionTriggerInfo) {
2691
2751
  conditionTriggerInfo = await getFlowTriggerInfo(client, flowId);
2692
2752
  steps.trigger_info = {
@@ -2699,14 +2759,18 @@ async function addFlowLogicViaGraphQL(
2699
2759
 
2700
2760
  var ncPillBase = conditionTriggerInfo.dataPillBase;
2701
2761
  var ncOrigVal = ncVal;
2702
- for (var si2 = 0; si2 < PILL_SHORTHANDS.length; si2++) {
2703
- var sh2 = PILL_SHORTHANDS[si2];
2704
- ncVal = ncVal.split('{{' + sh2 + '.').join('{{' + ncPillBase + '.');
2705
- ncVal = ncVal.split('{{' + sh2 + '}}').join('{{' + ncPillBase + '}}');
2762
+
2763
+ // Rewrite shorthand pills to full dataPillBase
2764
+ if (ncHasShorthand) {
2765
+ for (var si2 = 0; si2 < PILL_SHORTHANDS.length; si2++) {
2766
+ var sh2 = PILL_SHORTHANDS[si2];
2767
+ ncVal = ncVal.split('{{' + sh2 + '.').join('{{' + ncPillBase + '.');
2768
+ ncVal = ncVal.split('{{' + sh2 + '}}').join('{{' + ncPillBase + '}}');
2769
+ }
2770
+ ncInput.value.value = ncVal;
2706
2771
  }
2707
- ncInput.value.value = ncVal;
2708
2772
 
2709
- // Extract field names from pills for labelCache
2773
+ // Extract field names from ALL pills in the value for labelCache
2710
2774
  var ncPillFields: string[] = [];
2711
2775
  var ncIsRecordLevel = false;
2712
2776
  var ncPillRx = /\{\{([^}]+)\}\}/g;
@@ -2721,8 +2785,14 @@ async function addFlowLogicViaGraphQL(
2721
2785
  }
2722
2786
  }
2723
2787
 
2724
- nonConditionPillInputs.push({ name: ncInput.name, fields: ncPillFields, isRecordLevel: ncIsRecordLevel });
2725
- steps['pill_rewrite_' + ncInput.name] = { original: ncOrigVal, rewritten: ncVal };
2788
+ if (ncPillFields.length > 0 || ncIsRecordLevel) {
2789
+ nonConditionPillInputs.push({ name: ncInput.name, fields: ncPillFields, isRecordLevel: ncIsRecordLevel });
2790
+ if (ncOrigVal !== ncVal) {
2791
+ steps['pill_rewrite_' + ncInput.name] = { original: ncOrigVal, rewritten: ncVal };
2792
+ } else {
2793
+ steps['pill_labelcache_' + ncInput.name] = { fields: ncPillFields, isRecordLevel: ncIsRecordLevel };
2794
+ }
2795
+ }
2726
2796
  }
2727
2797
 
2728
2798
  // Calculate insertion order