snow-flow 10.0.1-dev.477 → 10.0.1-dev.478

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.477",
3
+ "version": "10.0.1-dev.478",
4
4
  "name": "snow-flow",
5
5
  "description": "Snow-Flow - ServiceNow Multi-Agent Development Framework powered by AI",
6
6
  "license": "Elastic-2.0",
@@ -1,5 +1,16 @@
1
1
  /**
2
- * Flow Designer Tools - Export all flow designer tool modules
2
+ * Snow-Flow Flow Designer Tool
3
+ *
4
+ * DISCLAIMER:
5
+ * This tool uses both official and undocumented ServiceNow APIs to interact
6
+ * with Flow Designer. The GraphQL-based operations (snFlowDesigner) use
7
+ * internal ServiceNow APIs that are not officially documented and may change
8
+ * without notice. Use at your own risk.
9
+ *
10
+ * This tool is not affiliated with, endorsed by, or sponsored by ServiceNow, Inc.
11
+ * ServiceNow is a registered trademark of ServiceNow, Inc.
12
+ *
13
+ * A valid ServiceNow subscription and credentials are required to use this tool.
3
14
  */
4
15
 
5
16
  export { toolDefinition as snow_manage_flow_def, execute as snow_manage_flow_exec } from './snow_manage_flow.js';
@@ -859,12 +859,21 @@ async function addTriggerViaGraphQL(
859
859
  if (!trigDefId) return { success: false, error: 'Trigger definition not found for: ' + triggerType, steps };
860
860
 
861
861
  // Build full trigger inputs and outputs from triggerpicker API (matching UI format)
862
- var triggerData = await buildTriggerInputsForInsert(client, trigDefId!, trigType, table, condition);
862
+ // Pass empty table/condition values are set via separate UPDATE (two-step, matching UI)
863
+ var triggerData = await buildTriggerInputsForInsert(client, trigDefId!, trigType, undefined, undefined);
863
864
  steps.trigger_data = { inputCount: triggerData.inputs.length, outputCount: triggerData.outputs.length, source: triggerData.source, error: triggerData.error };
864
865
 
865
866
  const triggerResponseFields = 'triggerInstances { inserts { sysId uiUniqueIdentifier __typename } updates deletes __typename }';
866
867
  try {
867
- // Single INSERT with full inputs and outputs (matching UI behavior no separate UPDATE needed)
868
+ // Step 1: INSERT with empty table/condition values (matching UI behavior from trigger-query.txt)
869
+ // The UI always inserts the trigger with empty inputs first, then updates with actual values.
870
+ var insertInputs = triggerData.inputs.map(function (inp: any) {
871
+ if (inp.name === 'table' || inp.name === 'condition') {
872
+ return { ...inp, value: { value: '' }, displayValue: { value: '' } };
873
+ }
874
+ return inp;
875
+ });
876
+
868
877
  const insertResult = await executeFlowPatchMutation(client, {
869
878
  flowId: flowId,
870
879
  triggerInstances: {
@@ -876,16 +885,61 @@ async function addTriggerViaGraphQL(
876
885
  type: trigType,
877
886
  hasDynamicOutputs: false,
878
887
  metadata: '{"predicates":[]}',
879
- inputs: triggerData.inputs,
888
+ inputs: insertInputs,
880
889
  outputs: triggerData.outputs
881
890
  }]
882
891
  }
883
892
  }, triggerResponseFields);
884
893
 
885
894
  const triggerId = insertResult?.triggerInstances?.inserts?.[0]?.sysId;
886
- steps.insert = { success: !!triggerId, triggerId };
895
+ const triggerUiId = insertResult?.triggerInstances?.inserts?.[0]?.uiUniqueIdentifier;
896
+ steps.insert = { success: !!triggerId, triggerId, triggerUiId };
887
897
  if (!triggerId) return { success: false, steps, error: 'GraphQL trigger INSERT returned no trigger ID' };
888
898
 
899
+ // Step 2: UPDATE with actual table and condition values (matching UI behavior from trigger-query-2.txt)
900
+ // The UI sends: table with displayField+displayValue+value, condition with just displayValue
901
+ if (table) {
902
+ try {
903
+ var tableDisplayName = '';
904
+ try {
905
+ var tblResp = await client.get('/api/now/table/sys_db_object', {
906
+ params: { sysparm_query: 'name=' + table, sysparm_fields: 'label', sysparm_display_value: 'true', sysparm_limit: 1 }
907
+ });
908
+ tableDisplayName = str(tblResp.data.result?.[0]?.label) || '';
909
+ } catch (_) {}
910
+ if (!tableDisplayName) {
911
+ tableDisplayName = table.charAt(0).toUpperCase() + table.slice(1).replace(/_/g, ' ');
912
+ }
913
+
914
+ var trigUpdateInputs: any[] = [
915
+ {
916
+ name: 'table',
917
+ displayField: 'number',
918
+ displayValue: { schemaless: false, schemalessValue: '', value: tableDisplayName },
919
+ value: { schemaless: false, schemalessValue: '', value: table }
920
+ },
921
+ {
922
+ name: 'condition',
923
+ displayValue: { schemaless: false, schemalessValue: '', value: condition || '^EQ' }
924
+ }
925
+ ];
926
+
927
+ await executeFlowPatchMutation(client, {
928
+ flowId: flowId,
929
+ triggerInstances: {
930
+ update: [{
931
+ id: triggerId,
932
+ inputs: trigUpdateInputs
933
+ }]
934
+ }
935
+ }, triggerResponseFields);
936
+ steps.trigger_update = { success: true, table: table, tableDisplay: tableDisplayName };
937
+ } catch (updateErr: any) {
938
+ steps.trigger_update = { success: false, error: updateErr.message };
939
+ // Trigger was created — update failure is non-fatal
940
+ }
941
+ }
942
+
889
943
  return { success: true, triggerId, steps };
890
944
  } catch (e: any) {
891
945
  steps.insert = { success: false, error: e.message };
@@ -1088,7 +1142,12 @@ async function addActionViaGraphQL(
1088
1142
  var inp = recordActionResult.inputs[ri];
1089
1143
  var val = inp.value?.value || '';
1090
1144
  if (inp.name === 'record' && val.startsWith('{{')) {
1091
- updateInputs.push({ name: 'record', value: { schemaless: false, schemalessValue: '', value: val } });
1145
+ // Both value and displayValue must be set (matching processflow XML format)
1146
+ updateInputs.push({
1147
+ name: 'record',
1148
+ displayValue: { schemaless: false, schemalessValue: '', value: val },
1149
+ value: { schemaless: false, schemalessValue: '', value: val }
1150
+ });
1092
1151
  } else if (inp.name === 'table_name') {
1093
1152
  // displayValue must use full schemaless format: {schemaless, schemalessValue, value}
1094
1153
  updateInputs.push({
@@ -1119,11 +1178,7 @@ async function addActionViaGraphQL(
1119
1178
  }]
1120
1179
  }
1121
1180
  };
1122
- // Record-level pills → labelCache UPDATE (already exist from trigger)
1123
- if (recordActionResult.labelCacheUpdates.length > 0) {
1124
- updatePatch.labelCache.update = recordActionResult.labelCacheUpdates;
1125
- }
1126
- // Field-level pills → labelCache INSERT (new entries)
1181
+ // All pills → labelCache INSERT (trigger created by our code, entries may not exist yet)
1127
1182
  if (recordActionResult.labelCacheInserts.length > 0) {
1128
1183
  updatePatch.labelCache.insert = recordActionResult.labelCacheInserts;
1129
1184
  }
@@ -1441,14 +1496,12 @@ function transformConditionToDataPills(conditionValue: string, dataPillBase: str
1441
1496
  /**
1442
1497
  * Build labelCache entries for field-level data pills used in flow logic conditions.
1443
1498
  *
1444
- * Each field referenced in the condition needs a labelCache entry so the Flow Designer UI
1445
- * can display the data pill correctly. Queries sys_dictionary for actual field types/labels.
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)
1446
1502
  *
1447
- * The UI only sends field-level pills (NOT a record-level parent pill).
1448
- * Each entry includes parent_table_name and column_name for the UI to resolve the field.
1449
- *
1450
- * Example for "category=inquiry" on incident table:
1451
- * - { name: "Created or Updated_1.current.category", type: "choice", parent_table_name: "incident", column_name: "category" }
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.
1452
1505
  */
1453
1506
  async function buildConditionLabelCache(
1454
1507
  client: any,
@@ -1459,16 +1512,17 @@ async function buildConditionLabelCache(
1459
1512
  tableLabel: string,
1460
1513
  logicUiId: string,
1461
1514
  explicitFields?: string[]
1462
- ): Promise<any[]> {
1463
- if (!dataPillBase) return [];
1515
+ ): Promise<{ inserts: any[]; updates: any[] }> {
1516
+ var empty = { inserts: [], updates: [] };
1517
+ if (!dataPillBase) return empty;
1464
1518
 
1465
1519
  // Collect unique field names — either from explicit list or by parsing encoded query
1466
1520
  if (!explicitFields) {
1467
1521
  var clauses = parseEncodedQuery(conditionValue);
1468
- if (clauses.length === 0) return [];
1522
+ if (clauses.length === 0) return empty;
1469
1523
  explicitFields = clauses.map(function (c) { return c.field; }).filter(function (f) { return !!f; });
1470
1524
  }
1471
- if (explicitFields.length === 0) return [];
1525
+ if (explicitFields.length === 0) return empty;
1472
1526
 
1473
1527
  // De-duplicate field names
1474
1528
  var uniqueFields: string[] = [];
@@ -1505,30 +1559,39 @@ async function buildConditionLabelCache(
1505
1559
  // Fallback: use "string" type and generated labels if dictionary lookup fails
1506
1560
  }
1507
1561
 
1508
- // Build field-level labelCache entries (no record-level parent UI doesn't send one)
1509
- var entries: any[] = [];
1562
+ // Build labelCache entries for condition pills, split into INSERT and UPDATE:
1563
+ // - INSERT: record-level pill (must exist for actions; format from processflow XML)
1564
+ // - UPDATE: field-level pills (minimal: name + usedInstances — matching the UI's exact mutation format)
1565
+ //
1566
+ // The UI uses labelCache.update for condition pills (captured mutation shows only name + usedInstances).
1567
+ // The record-level pill is INSERTed to ensure it exists (our trigger is created via code, not UI).
1568
+ var inserts: any[] = [];
1569
+ var updates: any[] = [];
1570
+
1571
+ // Record-level pill entry → INSERT with full metadata (processflow XML format)
1572
+ inserts.push({
1573
+ name: dataPillBase,
1574
+ label: 'Trigger - Record ' + triggerName + '\u279b' + tableLabel + ' Record',
1575
+ reference: table,
1576
+ reference_display: tableLabel,
1577
+ type: 'reference',
1578
+ base_type: 'reference',
1579
+ attributes: {},
1580
+ usedInstances: []
1581
+ });
1582
+
1583
+ // Field-level pill entries → UPDATE with minimal fields (matching UI mutation)
1510
1584
  for (var j = 0; j < uniqueFields.length; j++) {
1511
1585
  var f = uniqueFields[j];
1512
- var meta = fieldMeta[f];
1513
- var fType = meta ? meta.type : 'string';
1514
- var fLabel = meta ? meta.label : f.replace(/_/g, ' ').replace(/\b\w/g, function (c: string) { return c.toUpperCase(); });
1515
1586
  var pillName = dataPillBase + '.' + f;
1516
1587
 
1517
- entries.push({
1588
+ updates.push({
1518
1589
  name: pillName,
1519
- label: 'Trigger - Record ' + triggerName + '\u27a1' + tableLabel + ' Record\u27a1' + fLabel,
1520
- reference: '',
1521
- reference_display: fLabel,
1522
- type: fType,
1523
- base_type: fType,
1524
- parent_table_name: table,
1525
- column_name: f,
1526
- usedInstances: [{ uiUniqueIdentifier: logicUiId, inputName: 'condition' }],
1527
- choices: {}
1590
+ usedInstances: [{ uiUniqueIdentifier: logicUiId, inputName: 'condition' }]
1528
1591
  });
1529
1592
  }
1530
1593
 
1531
- return entries;
1594
+ return { inserts, updates };
1532
1595
  }
1533
1596
 
1534
1597
  // ── DATA PILL SUPPORT FOR RECORD ACTIONS (Update/Create Record) ──────
@@ -1595,10 +1658,15 @@ async function transformActionInputsForRecordAction(
1595
1658
 
1596
1659
  if (isShorthand || !recordVal) {
1597
1660
  // Auto-fill with trigger's current record data pill
1598
- recordInput.value = { schemaless: false, schemalessValue: '', value: '{{' + dataPillBase + '}}' };
1661
+ // The processflow XML shows both value AND displayValue must be set to the pill reference
1662
+ var pillRef = '{{' + dataPillBase + '}}';
1663
+ recordInput.value = { schemaless: false, schemalessValue: '', value: pillRef };
1664
+ recordInput.displayValue = { schemaless: false, schemalessValue: '', value: pillRef };
1599
1665
  usedInstances.push({ uiUniqueIdentifier: uuid, inputName: 'record' });
1600
- steps.record_transform = { original: recordVal, pill: '{{' + dataPillBase + '}}' };
1666
+ steps.record_transform = { original: recordVal, pill: pillRef };
1601
1667
  } else if (isAlreadyPill) {
1668
+ // Also ensure displayValue is set for existing pill references
1669
+ recordInput.displayValue = { schemaless: false, schemalessValue: '', value: recordVal };
1602
1670
  usedInstances.push({ uiUniqueIdentifier: uuid, inputName: 'record' });
1603
1671
  }
1604
1672
  }
@@ -1684,10 +1752,10 @@ async function transformActionInputsForRecordAction(
1684
1752
  }
1685
1753
 
1686
1754
  // ── 4. Build labelCache entries for data pills ────────────────────
1687
- // The record-level pill (e.g. "Created or Updated_1.current") already exists in the
1688
- // flow's labelCache from the trigger. The UI sends a labelCache UPDATE (not INSERT)
1689
- // with just name + usedInstances to register this action's usage.
1690
- // Field-level pills (e.g. "Created or Updated_1.current.assigned_to") are new and need INSERT.
1755
+ // Based on processflow XML analysis (labelCacheAsJsonString), the record-level pill
1756
+ // format is: { name, label, reference (table), reference_display (table label), type: "reference", base_type: "reference", attributes: {} }
1757
+ // Since our trigger is created via code (not UI), the labelCache entry may not exist yet.
1758
+ // We use INSERT for the record-level pill to ensure it exists.
1691
1759
  var labelCacheUpdates: any[] = [];
1692
1760
  var labelCacheInserts: any[] = [];
1693
1761
 
@@ -1695,9 +1763,16 @@ async function transformActionInputsForRecordAction(
1695
1763
  var tableRef = triggerInfo.tableRef || triggerInfo.table || '';
1696
1764
  var tblLabel = triggerInfo.tableLabel || '';
1697
1765
 
1698
- // Record-level pill — UPDATE existing entry with new usedInstances (minimal: name + usedInstances only)
1699
- labelCacheUpdates.push({
1766
+ // Record-level pill — INSERT with full metadata matching processflow XML format:
1767
+ // { name: "Created or Updated_1.current", type: "reference", reference: "incident", reference_display: "Incident", ... }
1768
+ labelCacheInserts.push({
1700
1769
  name: dataPillBase,
1770
+ label: 'Trigger - Record ' + triggerInfo.triggerName + '\u279b' + tblLabel + ' Record',
1771
+ reference: tableRef,
1772
+ reference_display: tblLabel,
1773
+ type: 'reference',
1774
+ base_type: 'reference',
1775
+ attributes: {},
1701
1776
  usedInstances: usedInstances
1702
1777
  });
1703
1778
 
@@ -1740,22 +1815,21 @@ async function transformActionInputsForRecordAction(
1740
1815
 
1741
1816
  labelCacheInserts.push({
1742
1817
  name: fullPillName,
1743
- label: 'Trigger - Record ' + triggerInfo.triggerName + '\u27a1' + tblLabel + ' Record\u27a1' + fMeta.label,
1744
- reference: '',
1818
+ label: 'Trigger - Record ' + triggerInfo.triggerName + '\u279b' + tblLabel + ' Record\u279b' + fMeta.label,
1819
+ reference: tableRef,
1745
1820
  reference_display: fMeta.label,
1746
1821
  type: fMeta.type,
1747
1822
  base_type: fMeta.type,
1748
1823
  parent_table_name: tableRef,
1749
1824
  column_name: fieldCol,
1750
- usedInstances: [{ uiUniqueIdentifier: uuid, inputName: fieldCol }],
1751
- choices: {}
1825
+ attributes: {},
1826
+ usedInstances: [{ uiUniqueIdentifier: uuid, inputName: fieldCol }]
1752
1827
  });
1753
1828
  }
1754
1829
  }
1755
1830
  }
1756
1831
 
1757
1832
  steps.label_cache = {
1758
- updates: labelCacheUpdates.map(function (e: any) { return e.name; }),
1759
1833
  inserts: labelCacheInserts.map(function (e: any) { return e.name; }),
1760
1834
  usedInstances: usedInstances.length
1761
1835
  };
@@ -1940,7 +2014,6 @@ async function addFlowLogicViaGraphQL(
1940
2014
  if (needsConditionUpdate && conditionTriggerInfo) {
1941
2015
  var dataPillBase = conditionTriggerInfo.dataPillBase;
1942
2016
  var transformedCondition: string;
1943
- var labelCacheEntries: any[];
1944
2017
 
1945
2018
  if (rawCondition.includes('{{')) {
1946
2019
  // Condition already contains data pill references (after shorthand rewrite)
@@ -1954,7 +2027,7 @@ async function addFlowLogicViaGraphQL(
1954
2027
  if (pParts.length > 2) pillFields.push(pParts[pParts.length - 1]);
1955
2028
  }
1956
2029
  // Build labelCache using extracted field names
1957
- labelCacheEntries = await buildConditionLabelCache(
2030
+ var labelCacheResult = await buildConditionLabelCache(
1958
2031
  client, rawCondition, dataPillBase, conditionTriggerInfo.triggerName,
1959
2032
  conditionTriggerInfo.tableRef, conditionTriggerInfo.tableLabel, returnedUuid,
1960
2033
  pillFields
@@ -1962,35 +2035,43 @@ async function addFlowLogicViaGraphQL(
1962
2035
  } else {
1963
2036
  // Plain encoded query — transform to data pill format
1964
2037
  transformedCondition = transformConditionToDataPills(rawCondition, dataPillBase);
1965
- labelCacheEntries = await buildConditionLabelCache(
2038
+ var labelCacheResult = await buildConditionLabelCache(
1966
2039
  client, rawCondition, dataPillBase, conditionTriggerInfo.triggerName,
1967
2040
  conditionTriggerInfo.tableRef, conditionTriggerInfo.tableLabel, returnedUuid
1968
2041
  );
1969
2042
  }
1970
2043
 
1971
2044
  steps.condition_transform = { original: rawCondition, transformed: transformedCondition };
1972
- steps.label_cache = { count: labelCacheEntries.length, pills: labelCacheEntries.map(function (e: any) { return e.name; }) };
2045
+ steps.label_cache = {
2046
+ inserts: labelCacheResult.inserts.map(function (e: any) { return e.name; }),
2047
+ updates: labelCacheResult.updates.map(function (e: any) { return e.name; })
2048
+ };
1973
2049
 
1974
2050
  try {
2051
+ // Match the UI's exact format for condition UPDATE (from captured network tab mutation):
2052
+ // - condition input: only name + value (NO displayValue, NO flowLogicDefinition)
2053
+ // - labelCache.insert: record-level pill with full metadata (ensures it exists)
2054
+ // - labelCache.update: field-level pills with minimal name + usedInstances (matching UI)
1975
2055
  var updatePatch: any = {
1976
2056
  flowId: flowId,
1977
- labelCache: { insert: labelCacheEntries },
2057
+ labelCache: {} as any,
1978
2058
  flowLogics: {
1979
2059
  update: [{
1980
2060
  uiUniqueIdentifier: returnedUuid,
1981
2061
  type: 'flowlogic',
1982
2062
  inputs: [{
1983
2063
  name: 'condition',
1984
- displayValue: { value: '' },
1985
2064
  value: { schemaless: false, schemalessValue: '', value: transformedCondition }
1986
- }],
1987
- flowLogicDefinition: {
1988
- inputs: [{ name: 'condition_name', attributes: 'use_basic_input=true,' }],
1989
- variables: 'undefined'
1990
- }
2065
+ }]
1991
2066
  }]
1992
2067
  }
1993
2068
  };
2069
+ if (labelCacheResult.inserts.length > 0) {
2070
+ updatePatch.labelCache.insert = labelCacheResult.inserts;
2071
+ }
2072
+ if (labelCacheResult.updates.length > 0) {
2073
+ updatePatch.labelCache.update = labelCacheResult.updates;
2074
+ }
1994
2075
  // Log the exact GraphQL mutation for debugging
1995
2076
  steps.condition_update_mutation = jsToGraphQL(updatePatch);
1996
2077
  var updateResult = await executeFlowPatchMutation(client, updatePatch, logicResponseFields);