snow-flow 10.0.1-dev.480 → 10.0.1-dev.481

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.480",
3
+ "version": "10.0.1-dev.481",
4
4
  "name": "snow-flow",
5
5
  "description": "Snow-Flow - ServiceNow Multi-Agent Development Framework powered by AI",
6
6
  "license": "Elastic-2.0",
@@ -1222,9 +1222,9 @@ async function getFlowTriggerInfo(
1222
1222
  // This API returns XML (not JSON). We parse trigger info from the XML string.
1223
1223
  try {
1224
1224
  debug.processflow_api = 'attempting';
1225
- var pfResp = await client.get('/api/now/processflow/flow/' + flowId, {
1226
- headers: { 'Accept': 'application/xml, application/json' }
1227
- });
1225
+ // Note: do NOT pass custom headers in config — some Axios interceptors freeze the config
1226
+ // object, causing "Attempted to assign to readonly property" errors.
1227
+ var pfResp = await client.get('/api/now/processflow/flow/' + flowId);
1228
1228
  var pfRaw = pfResp.data;
1229
1229
  debug.processflow_api = 'success';
1230
1230
  debug.processflow_type = typeof pfRaw;
@@ -1300,16 +1300,28 @@ async function getFlowTriggerInfo(
1300
1300
  var parsed = typeof payload === 'string' ? JSON.parse(payload) : payload;
1301
1301
  debug.version_payload_keys = Object.keys(parsed);
1302
1302
  var trigInst = parsed.triggerInstances || parsed.trigger_instances || [];
1303
+ debug.version_trigger_count = Array.isArray(trigInst) ? trigInst.length : typeof trigInst;
1303
1304
  if (Array.isArray(trigInst) && trigInst.length > 0) {
1304
- if (!triggerName) triggerName = trigInst[0].name || trigInst[0].triggerName || '';
1305
- if (!table && trigInst[0].inputs) {
1306
- for (var vi = 0; vi < trigInst[0].inputs.length; vi++) {
1307
- if (trigInst[0].inputs[vi].name === 'table') {
1308
- table = trigInst[0].inputs[vi].value?.value || '';
1305
+ var t0 = trigInst[0];
1306
+ debug.version_trigger_keys = Object.keys(t0);
1307
+ debug.version_trigger_name_field = t0.name || t0.triggerName || t0.triggerDefinitionName || '';
1308
+ if (!triggerName) triggerName = t0.name || t0.triggerName || t0.triggerDefinitionName || '';
1309
+ // Also try getting the trigger definition name (e.g. "Created or Updated")
1310
+ if (!triggerName && t0.triggerDefinition) {
1311
+ triggerName = t0.triggerDefinition.name || '';
1312
+ }
1313
+ if (!table && t0.inputs) {
1314
+ var t0Inputs = Array.isArray(t0.inputs) ? t0.inputs : [];
1315
+ for (var vi = 0; vi < t0Inputs.length; vi++) {
1316
+ if (t0Inputs[vi].name === 'table') {
1317
+ table = t0Inputs[vi].value?.value || str(t0Inputs[vi].value) || '';
1318
+ debug.version_table_input = t0Inputs[vi];
1309
1319
  break;
1310
1320
  }
1311
1321
  }
1312
1322
  }
1323
+ // Fallback: try reading table directly from trigger object
1324
+ if (!table && t0.table) table = str(t0.table);
1313
1325
  }
1314
1326
  }
1315
1327
  } catch (_) {}
@@ -1494,14 +1506,20 @@ function transformConditionToDataPills(conditionValue: string, dataPillBase: str
1494
1506
  }
1495
1507
 
1496
1508
  /**
1497
- * Build labelCache entries for field-level data pills used in flow logic conditions.
1509
+ * Build labelCache INSERT entries for field-level data pills used in flow logic conditions.
1498
1510
  *
1499
- * Returns { inserts, updates }:
1500
- * - inserts: record-level pill with full metadata (processflow XML format)
1501
- * - updates: field-level pills with minimal fields (name + usedInstances, matching UI mutation)
1511
+ * Returns an array of labelCache INSERT entries with full metadata, matching the UI's exact
1512
+ * mutation format (captured from Flow Designer network tab when editing a programmatic flow):
1502
1513
  *
1503
- * The UI uses labelCache.update for field-level condition pills (captured from network tab).
1504
- * The record-level pill is INSERTed to ensure it exists when triggers are created programmatically.
1514
+ * labelCache: { insert: [{
1515
+ * name: "Created or Updated_1.current.category",
1516
+ * label: "Trigger - Record Created or Updated➛Incident Record➛Category",
1517
+ * reference: "", reference_display: "Category",
1518
+ * type: "choice", base_type: "choice",
1519
+ * parent_table_name: "incident", column_name: "category",
1520
+ * usedInstances: [{uiUniqueIdentifier: "...", inputName: "condition"}],
1521
+ * choices: {}
1522
+ * }] }
1505
1523
  */
1506
1524
  async function buildConditionLabelCache(
1507
1525
  client: any,
@@ -1535,27 +1553,54 @@ async function buildConditionLabelCache(
1535
1553
  }
1536
1554
  if (uniqueFields.length === 0) return [];
1537
1555
 
1538
- // Build labelCache UPDATE entries for condition pills.
1539
- // Matches the UI's exact mutation format (from if-statement-update.txt):
1540
- // labelCache: { update: [{ name: "Created or Updated_1.current.category", usedInstances: [...] }] }
1541
- //
1542
- // The record-level pill (e.g. "Created or Updated_1.current") is NOT included here because:
1543
- // - The ServiceNow backend automatically creates it when the trigger is saved (INSERT → UPDATE)
1544
- // - Sending a duplicate labelCache.insert for it can cause the entire labelCache block to fail
1545
- // - The UI's condition mutation never sends a record-level pill insert (confirmed via network capture)
1546
- var updates: any[] = [];
1556
+ // Batch-query sys_dictionary for field metadata (type, label, reference)
1557
+ var fieldMeta: Record<string, { type: string; label: string; reference: string }> = {};
1558
+ try {
1559
+ var dictResp = await client.get('/api/now/table/sys_dictionary', {
1560
+ params: {
1561
+ sysparm_query: 'name=' + table + '^elementIN' + uniqueFields.join(','),
1562
+ sysparm_fields: 'element,column_label,internal_type,reference',
1563
+ sysparm_display_value: 'false',
1564
+ sysparm_limit: uniqueFields.length + 5
1565
+ }
1566
+ });
1567
+ var dictResults = dictResp.data.result || [];
1568
+ for (var d = 0; d < dictResults.length; d++) {
1569
+ var rec = dictResults[d];
1570
+ var elName = str(rec.element);
1571
+ var intType = str(rec.internal_type?.value || rec.internal_type || 'string');
1572
+ var colLabel = str(rec.column_label);
1573
+ var refTable = str(rec.reference?.value || rec.reference || '');
1574
+ if (elName) fieldMeta[elName] = { type: intType, label: colLabel, reference: refTable };
1575
+ }
1576
+ } catch (_) {
1577
+ // Fallback: use "string" type and generated labels if dictionary lookup fails
1578
+ }
1579
+
1580
+ // Build labelCache INSERT entries with full metadata for each field-level pill.
1581
+ // This matches the UI's mutation format when editing a programmatically-created flow.
1582
+ var inserts: any[] = [];
1547
1583
 
1548
1584
  for (var j = 0; j < uniqueFields.length; j++) {
1549
1585
  var f = uniqueFields[j];
1550
1586
  var pillName = dataPillBase + '.' + f;
1587
+ var meta = fieldMeta[f] || { type: 'string', label: f.replace(/_/g, ' ').replace(/\b\w/g, function (c: string) { return c.toUpperCase(); }), reference: '' };
1551
1588
 
1552
- updates.push({
1589
+ inserts.push({
1553
1590
  name: pillName,
1554
- usedInstances: [{ uiUniqueIdentifier: logicUiId, inputName: 'condition' }]
1591
+ label: 'Trigger - Record ' + triggerName + '\u279b' + tableLabel + ' Record\u279b' + meta.label,
1592
+ reference: meta.reference,
1593
+ reference_display: meta.label,
1594
+ type: meta.type,
1595
+ base_type: meta.type,
1596
+ parent_table_name: table,
1597
+ column_name: f,
1598
+ usedInstances: [{ uiUniqueIdentifier: logicUiId, inputName: 'condition' }],
1599
+ choices: {}
1555
1600
  });
1556
1601
  }
1557
1602
 
1558
- return updates;
1603
+ return inserts;
1559
1604
  }
1560
1605
 
1561
1606
  // ── DATA PILL SUPPORT FOR RECORD ACTIONS (Update/Create Record) ──────
@@ -1886,22 +1931,41 @@ async function addFlowLogicViaGraphQL(
1886
1931
  var needsConditionUpdate = false;
1887
1932
  var conditionTriggerInfo: any = null;
1888
1933
 
1889
- // Pre-process: detect JS-style dot notation conditions and convert to shorthand pill format.
1890
- // Users may write conditions like:
1891
- // "trigger.current.category = software" (single =)
1892
- // "trigger.current.category == 'software'" (JS equality)
1893
- // "current.priority = 1" (short prefix)
1894
- // These are converted to pill shorthand format that the existing logic can handle:
1895
- // "{{trigger.current.category}}=software"
1896
- var DOT_NOTATION_RE = /((?:trigger\.)?current)\.(\w+)\s*(===?|!==?|>=|<=|>|<|=)\s*(?:'([^']*)'|"([^"]*)"|(\S+))/g;
1897
- if (DOT_NOTATION_RE.test(rawCondition)) {
1934
+ // Pre-process: detect dot notation conditions and convert to shorthand pill format.
1935
+ // Supports both symbol operators and word operators:
1936
+ // "trigger.current.category = software" "{{trigger.current.category}}=software"
1937
+ // "trigger.current.category == 'software'" "{{trigger.current.category}}=software"
1938
+ // "trigger.current.category equals software" "{{trigger.current.category}}=software"
1939
+ // "trigger.current.priority != 1" → "{{trigger.current.priority}}!=1"
1940
+ // "current.active is true" → "{{current.active}}=true"
1941
+ var WORD_OP_MAP: Record<string, string> = {
1942
+ 'equals': '=', 'is': '=', 'eq': '=',
1943
+ 'not_equals': '!=', 'is not': '!=', 'neq': '!=', 'not equals': '!=',
1944
+ 'greater than': '>', 'gt': '>', 'less than': '<', 'lt': '<',
1945
+ 'greater or equals': '>=', 'gte': '>=', 'less or equals': '<=', 'lte': '<=',
1946
+ 'contains': 'LIKE', 'starts with': 'STARTSWITH', 'ends with': 'ENDSWITH',
1947
+ 'not contains': 'NOT LIKE', 'is empty': 'ISEMPTY', 'is not empty': 'ISNOTEMPTY'
1948
+ };
1949
+ // First replace word operators with symbols so the regex can match uniformly
1950
+ var dotOriginal = rawCondition;
1951
+ var dotProcessed = rawCondition;
1952
+ var WORD_OPS_SORTED = Object.keys(WORD_OP_MAP).sort(function (a, b) { return b.length - a.length; }); // longest first
1953
+ for (var wi = 0; wi < WORD_OPS_SORTED.length; wi++) {
1954
+ var wordOp = WORD_OPS_SORTED[wi];
1955
+ // Only replace word operators that appear between a dot-notation field and a value
1956
+ var wordRe = new RegExp('((?:trigger\\.)?current\\.\\w+)\\s+' + wordOp.replace(/ /g, '\\s+') + '\\s+', 'gi');
1957
+ dotProcessed = dotProcessed.replace(wordRe, function (m: string, prefix: string) {
1958
+ return prefix + WORD_OP_MAP[wordOp];
1959
+ });
1960
+ }
1961
+ var DOT_NOTATION_RE = /((?:trigger\.)?current)\.(\w+)(===?|!==?|>=|<=|>|<|=|LIKE|STARTSWITH|ENDSWITH|NOT LIKE|ISEMPTY|ISNOTEMPTY)\s*(?:'([^']*)'|"([^"]*)"|(\S*))/g;
1962
+ if (DOT_NOTATION_RE.test(dotProcessed)) {
1898
1963
  DOT_NOTATION_RE.lastIndex = 0;
1899
- var dotOriginal = rawCondition;
1900
- rawCondition = rawCondition.replace(DOT_NOTATION_RE, function (_m: string, prefix: string, field: string, op: string, qv1: string, qv2: string, uv: string) {
1964
+ rawCondition = dotProcessed.replace(DOT_NOTATION_RE, function (_m: string, prefix: string, field: string, op: string, qv1: string, qv2: string, uv: string) {
1901
1965
  var snOp = op;
1902
1966
  if (op === '==' || op === '===') snOp = '=';
1903
1967
  else if (op === '!=' || op === '!==') snOp = '!=';
1904
- var val = qv1 !== undefined ? qv1 : (qv2 !== undefined ? qv2 : uv);
1968
+ var val = qv1 !== undefined ? qv1 : (qv2 !== undefined ? qv2 : (uv || ''));
1905
1969
  return '{{' + prefix + '.' + field + '}}' + snOp + val;
1906
1970
  });
1907
1971
  // Replace JS && with ServiceNow ^ (AND separator)
@@ -1909,6 +1973,29 @@ async function addFlowLogicViaGraphQL(
1909
1973
  steps.dot_notation_rewrite = { original: dotOriginal, rewritten: rawCondition };
1910
1974
  }
1911
1975
 
1976
+ // Pre-process: convert word operators in pill-format conditions
1977
+ // e.g. "{{trigger.current.category}} equals software" → "{{trigger.current.category}}=software"
1978
+ if (rawCondition.includes('{{')) {
1979
+ var PILL_WORD_OPS: [RegExp, string][] = [
1980
+ [/(\}\})\s+not\s+equals\s+/gi, '$1!='],
1981
+ [/(\}\})\s+is\s+not\s+/gi, '$1!='],
1982
+ [/(\}\})\s+equals\s+/gi, '$1='],
1983
+ [/(\}\})\s+is\s+/gi, '$1='],
1984
+ [/(\}\})\s+contains\s+/gi, '$1LIKE'],
1985
+ [/(\}\})\s+starts\s+with\s+/gi, '$1STARTSWITH'],
1986
+ [/(\}\})\s+ends\s+with\s+/gi, '$1ENDSWITH'],
1987
+ [/(\}\})\s+greater\s+than\s+/gi, '$1>'],
1988
+ [/(\}\})\s+less\s+than\s+/gi, '$1<'],
1989
+ ];
1990
+ var pillWordOriginal = rawCondition;
1991
+ for (var pwi = 0; pwi < PILL_WORD_OPS.length; pwi++) {
1992
+ rawCondition = rawCondition.replace(PILL_WORD_OPS[pwi][0], PILL_WORD_OPS[pwi][1]);
1993
+ }
1994
+ if (rawCondition !== pillWordOriginal) {
1995
+ steps.pill_word_op_rewrite = { original: pillWordOriginal, rewritten: rawCondition };
1996
+ }
1997
+ }
1998
+
1912
1999
  // Shorthand patterns that need rewriting to the real data pill base
1913
2000
  // e.g. {{trigger.current.category}} → {{Created or Updated_1.current.category}}
1914
2001
  var PILL_SHORTHANDS = ['trigger.current', 'current', 'trigger_record', 'trigger.record'];
@@ -2032,10 +2119,11 @@ async function addFlowLogicViaGraphQL(
2032
2119
  steps.label_cache = labelCacheResult.map(function (e: any) { return e.name; });
2033
2120
 
2034
2121
  try {
2035
- // Match the UI's exact format for condition UPDATE (from if-statement-update.txt):
2036
- // - labelCache.update: field-level pills with minimal name + usedInstances
2037
- // - NO labelCache.insert (record-level pill is auto-created by backend during trigger save)
2038
- // - condition input: only name + value (NO displayValue, NO flowLogicDefinition)
2122
+ // Match the UI's exact format for condition UPDATE (captured from Flow Designer
2123
+ // when editing a programmatically-created flow):
2124
+ // - labelCache.insert: field-level pills with FULL metadata (type, parent_table_name, etc.)
2125
+ // - condition input: name + value + displayValue
2126
+ // - flowLogicDefinition: with condition_name attributes
2039
2127
  var updatePatch: any = {
2040
2128
  flowId: flowId,
2041
2129
  flowLogics: {
@@ -2044,13 +2132,18 @@ async function addFlowLogicViaGraphQL(
2044
2132
  type: 'flowlogic',
2045
2133
  inputs: [{
2046
2134
  name: 'condition',
2135
+ displayValue: { value: '' },
2047
2136
  value: { schemaless: false, schemalessValue: '', value: transformedCondition }
2048
- }]
2137
+ }],
2138
+ flowLogicDefinition: {
2139
+ inputs: [{ name: 'condition_name', attributes: 'use_basic_input=true,' }],
2140
+ variables: 'undefined'
2141
+ }
2049
2142
  }]
2050
2143
  }
2051
2144
  };
2052
2145
  if (labelCacheResult.length > 0) {
2053
- updatePatch.labelCache = { update: labelCacheResult };
2146
+ updatePatch.labelCache = { insert: labelCacheResult };
2054
2147
  }
2055
2148
  // Log the exact GraphQL mutation for debugging
2056
2149
  steps.condition_update_mutation = jsToGraphQL(updatePatch);