snow-flow 10.0.1-dev.465 → 10.0.1-dev.466

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.465",
3
+ "version": "10.0.1-dev.466",
4
4
  "name": "snow-flow",
5
5
  "description": "Snow-Flow - ServiceNow Multi-Agent Development Framework powered by AI",
6
6
  "license": "Elastic-2.0",
@@ -1210,94 +1210,76 @@ function parseEncodedQuery(query: string): { prefix: string; field: string; oper
1210
1210
  }
1211
1211
 
1212
1212
  /**
1213
- * Transform an encoded query condition value to use data pill references.
1214
- * Replaces field names with {{dataPillBase.fieldName}} syntax.
1213
+ * Check if a condition value looks like a standard ServiceNow encoded query.
1214
+ * Standard encoded queries use: field_name=value^field_name2!=value2
1215
1215
  *
1216
- * Example: "category=inquiry" "{{Created or Updated_1.current.category}}=inquiry"
1216
+ * Returns false for JavaScript expressions, scripts, fd_data references, etc.
1217
+ * which should be passed through as-is without data pill transformation.
1218
+ */
1219
+ function isStandardEncodedQuery(condition: string): boolean {
1220
+ if (!condition) return false;
1221
+ // Parentheses indicate function calls or grouping expressions
1222
+ if (/[()]/.test(condition)) return false;
1223
+ // Method calls like .toString(, .replace(, .match(
1224
+ if (/\.\w+\(/.test(condition)) return false;
1225
+ // Regex patterns like /[
1226
+ if (/\/\[/.test(condition)) return false;
1227
+ // JS equality operators == or ===
1228
+ if (/===?/.test(condition)) return false;
1229
+ // JS modulo, logical AND/OR
1230
+ if (/%/.test(condition)) return false;
1231
+ if (/&&|\|\|/.test(condition)) return false;
1232
+ // Flow Designer internal variable references
1233
+ if (condition.startsWith('fd_data.')) return false;
1234
+ // Already contains data pill references (already transformed)
1235
+ if (condition.includes('{{')) return false;
1236
+ return true;
1237
+ }
1238
+
1239
+ /**
1240
+ * Transform an encoded query condition into Flow Designer data pill format.
1241
+ *
1242
+ * The UI format uses a record-level data pill prepended to the encoded query:
1243
+ * "category=inquiry" → "{{Created or Updated_1.current}}category=inquiry"
1244
+ *
1245
+ * The record pill tells Flow Designer which record/table the condition applies to.
1246
+ * The encoded query after the pill is the actual filter.
1217
1247
  */
1218
1248
  function transformConditionToDataPills(conditionValue: string, dataPillBase: string): string {
1219
1249
  if (!conditionValue || !dataPillBase) return conditionValue;
1220
-
1221
- var clauses = parseEncodedQuery(conditionValue);
1222
- if (clauses.length === 0) return conditionValue;
1223
-
1224
- var result = '';
1225
- for (var i = 0; i < clauses.length; i++) {
1226
- var clause = clauses[i];
1227
- result += clause.prefix;
1228
- result += '{{' + dataPillBase + '.' + clause.field + '}}';
1229
- result += clause.operator;
1230
- result += clause.value;
1231
- }
1232
-
1233
- return result;
1250
+ // Prepend the record-level data pill to the encoded query
1251
+ return '{{' + dataPillBase + '}}' + conditionValue;
1234
1252
  }
1235
1253
 
1236
1254
  /**
1237
- * Build labelCache entries for data pills used in flow logic conditions.
1238
- * Each unique data pill reference needs a labelCache entry so the Flow Designer UI
1239
- * can display the data pill label correctly.
1255
+ * Build a single record-level labelCache entry for the data pill used in a condition.
1256
+ * The UI registers the record pill with the condition input, matching the captured format:
1240
1257
  *
1241
- * The entry format matches the captured UI mutation format.
1258
+ * { name: "Created or Updated_1.current",
1259
+ * label: "Trigger - Record Created or Updated➛Incident Record",
1260
+ * reference: "incident", type: "reference", base_type: "reference",
1261
+ * usedInstances: [{ uiUniqueIdentifier, inputName: "condition" }] }
1242
1262
  */
1243
1263
  function buildConditionLabelCache(
1244
- conditionValue: string,
1245
1264
  dataPillBase: string,
1246
1265
  triggerName: string,
1247
1266
  table: string,
1248
1267
  tableLabel: string,
1249
1268
  logicUiId: string
1250
1269
  ): any[] {
1251
- if (!conditionValue || !dataPillBase) return [];
1252
-
1253
- var clauses = parseEncodedQuery(conditionValue);
1254
- if (clauses.length === 0) return [];
1255
-
1256
- // Build a label cache entry for each unique field-level data pill
1257
- var seen: Record<string, boolean> = {};
1258
- var entries: any[] = [];
1259
-
1260
- // Also register the record-level data pill (the base: TriggerName_1.current)
1261
- // This is needed for the condition builder to understand the context
1262
- if (!seen[dataPillBase]) {
1263
- seen[dataPillBase] = true;
1264
- entries.push({
1265
- name: dataPillBase,
1266
- label: 'Trigger - Record ' + triggerName + '\u27a1' + tableLabel + ' Record',
1267
- reference: table,
1268
- reference_display: tableLabel,
1269
- type: 'reference',
1270
- base_type: 'reference',
1271
- attributes: '',
1272
- usedInstances: [{ uiUniqueIdentifier: logicUiId, inputName: 'condition' }],
1273
- choices: {}
1274
- });
1275
- }
1276
-
1277
- for (var i = 0; i < clauses.length; i++) {
1278
- var field = clauses[i].field;
1279
- if (!field) continue;
1280
- var pillName = dataPillBase + '.' + field;
1281
- if (seen[pillName]) continue;
1282
- seen[pillName] = true;
1283
-
1284
- // Capitalize field name for label
1285
- var fieldLabel = field.replace(/_/g, ' ').replace(/\b\w/g, function (c: string) { return c.toUpperCase(); });
1286
-
1287
- entries.push({
1288
- name: pillName,
1289
- label: 'Trigger - Record ' + triggerName + '\u27a1' + tableLabel + ' Record\u27a1' + fieldLabel,
1290
- reference: '',
1291
- reference_display: '',
1292
- type: 'string',
1293
- base_type: 'string',
1294
- attributes: '',
1295
- usedInstances: [{ uiUniqueIdentifier: logicUiId, inputName: 'condition' }],
1296
- choices: {}
1297
- });
1298
- }
1299
-
1300
- return entries;
1270
+ if (!dataPillBase) return [];
1271
+
1272
+ return [{
1273
+ name: dataPillBase,
1274
+ label: 'Trigger - Record ' + triggerName + '\u27a1' + tableLabel + ' Record',
1275
+ reference: table,
1276
+ reference_display: tableLabel,
1277
+ type: 'reference',
1278
+ base_type: 'reference',
1279
+ attributes: '',
1280
+ usedInstances: [{ uiUniqueIdentifier: logicUiId, inputName: 'condition' }],
1281
+ choices: {}
1282
+ }];
1301
1283
  }
1302
1284
 
1303
1285
  // ── DATA PILL SUPPORT FOR RECORD ACTIONS (Update/Create Record) ──────
@@ -1547,31 +1529,32 @@ async function addFlowLogicViaGraphQL(
1547
1529
  steps.resolved_inputs = inputResult.resolvedInputs;
1548
1530
  steps.input_query_stats = { defParamsFound: inputResult.defParamsCount, inputsBuilt: inputResult.inputs.length, error: inputResult.inputQueryError };
1549
1531
 
1550
- // ── Data pill transformation for condition inputs ───────────────────
1551
- // Flow Designer conditions require data pill references ({{TriggerName_1.current.field}})
1552
- // instead of bare field names. Transform encoded query conditions and build labelCache.
1532
+ // ── Detect condition that needs data pill transformation ────────────
1533
+ // Flow Designer sets conditions via a SEPARATE UPDATE after the element is created.
1534
+ // The condition format uses record-level data pill: {{TriggerName_1.current}}encodedQuery
1535
+ // Non-standard conditions (JS expressions, fd_data refs) are passed through as-is.
1553
1536
  const uuid = generateUUID();
1554
- var labelCacheEntries: any[] = [];
1555
-
1556
1537
  var conditionInput = inputResult.inputs.find(function (inp: any) { return inp.name === 'condition'; });
1557
1538
  var rawCondition = conditionInput?.value?.value || '';
1558
- if (rawCondition && rawCondition !== '^EQ') {
1559
- var triggerInfo = await getFlowTriggerInfo(client, flowId);
1560
- steps.trigger_info = { dataPillBase: triggerInfo.dataPillBase, triggerName: triggerInfo.triggerName, table: triggerInfo.table, tableLabel: triggerInfo.tableLabel, error: triggerInfo.error };
1561
-
1562
- if (triggerInfo.dataPillBase) {
1563
- // Transform condition value: "category=inquiry" → "{{Created or Updated_1.current.category}}=inquiry"
1564
- var transformedCondition = transformConditionToDataPills(rawCondition, triggerInfo.dataPillBase);
1565
- conditionInput.value = { schemaless: false, schemalessValue: '', value: transformedCondition };
1566
- steps.condition_transform = { original: rawCondition, transformed: transformedCondition };
1567
-
1568
- // Build labelCache entries for each data pill used in the condition
1569
- labelCacheEntries = buildConditionLabelCache(
1570
- rawCondition, triggerInfo.dataPillBase, triggerInfo.triggerName,
1571
- triggerInfo.tableRef, triggerInfo.tableLabel, uuid
1572
- );
1573
- steps.label_cache = { count: labelCacheEntries.length, pills: labelCacheEntries.map(function (e: any) { return e.name; }) };
1539
+ var needsConditionUpdate = false;
1540
+ var conditionTriggerInfo: any = null;
1541
+
1542
+ if (rawCondition && rawCondition !== '^EQ' && isStandardEncodedQuery(rawCondition)) {
1543
+ conditionTriggerInfo = await getFlowTriggerInfo(client, flowId);
1544
+ steps.trigger_info = {
1545
+ dataPillBase: conditionTriggerInfo.dataPillBase, triggerName: conditionTriggerInfo.triggerName,
1546
+ table: conditionTriggerInfo.table, tableLabel: conditionTriggerInfo.tableLabel, error: conditionTriggerInfo.error
1547
+ };
1548
+ if (conditionTriggerInfo.dataPillBase) {
1549
+ needsConditionUpdate = true;
1550
+ // Clear condition in INSERT — it will be set via separate UPDATE with labelCache
1551
+ conditionInput.value = { schemaless: false, schemalessValue: '', value: '' };
1552
+ steps.condition_strategy = 'two_step';
1574
1553
  }
1554
+ } else if (rawCondition && rawCondition !== '^EQ') {
1555
+ // Non-standard condition (JS expression, fd_data ref, etc.) — pass through as-is
1556
+ steps.condition_strategy = 'passthrough';
1557
+ steps.condition_not_encoded_query = true;
1575
1558
  }
1576
1559
 
1577
1560
  // Calculate insertion order
@@ -1608,23 +1591,55 @@ async function addFlowLogicViaGraphQL(
1608
1591
  }
1609
1592
  };
1610
1593
 
1611
- // Add labelCache entries for data pill references in conditions
1612
- if (labelCacheEntries.length > 0) {
1613
- flowPatch.labelCache = { insert: labelCacheEntries };
1614
- }
1615
-
1616
1594
  // Add parent flow logic update signal (tells GraphQL the parent was modified)
1617
1595
  if (parentUiId) {
1618
1596
  flowPatch.flowLogics.update = [{ uiUniqueIdentifier: parentUiId, type: 'flowlogic' }];
1619
1597
  }
1620
1598
 
1621
1599
  try {
1600
+ // Step 1: INSERT the flow logic element (with empty condition if data pill transform is needed)
1622
1601
  const result = await executeFlowPatchMutation(client, flowPatch, logicResponseFields);
1623
1602
  const logicId = result?.flowLogics?.inserts?.[0]?.sysId;
1624
1603
  const returnedUuid = result?.flowLogics?.inserts?.[0]?.uiUniqueIdentifier || uuid;
1625
1604
  steps.insert = { success: !!logicId, logicId, uuid: returnedUuid };
1626
1605
  if (!logicId) return { success: false, steps, error: 'GraphQL flow logic INSERT returned no ID' };
1627
1606
 
1607
+ // Step 2: UPDATE condition with data pill + labelCache (separate mutation, matching UI behavior)
1608
+ // The Flow Designer UI always sets conditions in a separate UPDATE after creating the element.
1609
+ if (needsConditionUpdate && conditionTriggerInfo) {
1610
+ var dataPillBase = conditionTriggerInfo.dataPillBase;
1611
+ var transformedCondition = transformConditionToDataPills(rawCondition, dataPillBase);
1612
+ var labelCacheEntries = buildConditionLabelCache(
1613
+ dataPillBase, conditionTriggerInfo.triggerName,
1614
+ conditionTriggerInfo.tableRef, conditionTriggerInfo.tableLabel, returnedUuid
1615
+ );
1616
+
1617
+ steps.condition_transform = { original: rawCondition, transformed: transformedCondition };
1618
+ steps.label_cache = { count: labelCacheEntries.length, pills: labelCacheEntries.map(function (e: any) { return e.name; }) };
1619
+
1620
+ try {
1621
+ var updatePatch: any = {
1622
+ flowId: flowId,
1623
+ labelCache: { insert: labelCacheEntries },
1624
+ flowLogics: {
1625
+ update: [{
1626
+ uiUniqueIdentifier: returnedUuid,
1627
+ type: 'flowlogic',
1628
+ inputs: [{
1629
+ name: 'condition',
1630
+ value: { schemaless: false, schemalessValue: '', value: transformedCondition }
1631
+ }]
1632
+ }]
1633
+ }
1634
+ };
1635
+ await executeFlowPatchMutation(client, updatePatch, logicResponseFields);
1636
+ steps.condition_update = { success: true };
1637
+ } catch (ue: any) {
1638
+ steps.condition_update = { success: false, error: ue.message };
1639
+ // Element was created successfully — condition update failure is non-fatal
1640
+ }
1641
+ }
1642
+
1628
1643
  return { success: true, logicId, uiUniqueIdentifier: returnedUuid, steps };
1629
1644
  } catch (e: any) {
1630
1645
  steps.insert = { success: false, error: e.message };