snow-flow 10.0.1-dev.479 → 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.479",
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,6 +1931,71 @@ async function addFlowLogicViaGraphQL(
1886
1931
  var needsConditionUpdate = false;
1887
1932
  var conditionTriggerInfo: any = null;
1888
1933
 
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)) {
1963
+ DOT_NOTATION_RE.lastIndex = 0;
1964
+ rawCondition = dotProcessed.replace(DOT_NOTATION_RE, function (_m: string, prefix: string, field: string, op: string, qv1: string, qv2: string, uv: string) {
1965
+ var snOp = op;
1966
+ if (op === '==' || op === '===') snOp = '=';
1967
+ else if (op === '!=' || op === '!==') snOp = '!=';
1968
+ var val = qv1 !== undefined ? qv1 : (qv2 !== undefined ? qv2 : (uv || ''));
1969
+ return '{{' + prefix + '.' + field + '}}' + snOp + val;
1970
+ });
1971
+ // Replace JS && with ServiceNow ^ (AND separator)
1972
+ rawCondition = rawCondition.replace(/\s*&&\s*/g, '^');
1973
+ steps.dot_notation_rewrite = { original: dotOriginal, rewritten: rawCondition };
1974
+ }
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
+
1889
1999
  // Shorthand patterns that need rewriting to the real data pill base
1890
2000
  // e.g. {{trigger.current.category}} → {{Created or Updated_1.current.category}}
1891
2001
  var PILL_SHORTHANDS = ['trigger.current', 'current', 'trigger_record', 'trigger.record'];
@@ -2009,10 +2119,11 @@ async function addFlowLogicViaGraphQL(
2009
2119
  steps.label_cache = labelCacheResult.map(function (e: any) { return e.name; });
2010
2120
 
2011
2121
  try {
2012
- // Match the UI's exact format for condition UPDATE (from if-statement-update.txt):
2013
- // - labelCache.update: field-level pills with minimal name + usedInstances
2014
- // - NO labelCache.insert (record-level pill is auto-created by backend during trigger save)
2015
- // - 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
2016
2127
  var updatePatch: any = {
2017
2128
  flowId: flowId,
2018
2129
  flowLogics: {
@@ -2021,13 +2132,18 @@ async function addFlowLogicViaGraphQL(
2021
2132
  type: 'flowlogic',
2022
2133
  inputs: [{
2023
2134
  name: 'condition',
2135
+ displayValue: { value: '' },
2024
2136
  value: { schemaless: false, schemalessValue: '', value: transformedCondition }
2025
- }]
2137
+ }],
2138
+ flowLogicDefinition: {
2139
+ inputs: [{ name: 'condition_name', attributes: 'use_basic_input=true,' }],
2140
+ variables: 'undefined'
2141
+ }
2026
2142
  }]
2027
2143
  }
2028
2144
  };
2029
2145
  if (labelCacheResult.length > 0) {
2030
- updatePatch.labelCache = { update: labelCacheResult };
2146
+ updatePatch.labelCache = { insert: labelCacheResult };
2031
2147
  }
2032
2148
  // Log the exact GraphQL mutation for debugging
2033
2149
  steps.condition_update_mutation = jsToGraphQL(updatePatch);