snow-flow 10.0.1-dev.472 → 10.0.1-dev.474

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.472",
3
+ "version": "10.0.1-dev.474",
4
4
  "name": "snow-flow",
5
5
  "description": "Snow-Flow - ServiceNow Multi-Agent Development Framework powered by AI",
6
6
  "license": "Elastic-2.0",
@@ -1142,10 +1142,11 @@ async function addActionViaGraphQL(
1142
1142
  async function getFlowTriggerInfo(
1143
1143
  client: any,
1144
1144
  flowId: string
1145
- ): Promise<{ dataPillBase: string; triggerName: string; table: string; tableLabel: string; tableRef: string; error?: string }> {
1145
+ ): Promise<{ dataPillBase: string; triggerName: string; table: string; tableLabel: string; tableRef: string; error?: string; debug?: any }> {
1146
1146
  var triggerName = '';
1147
1147
  var table = '';
1148
1148
  var tableLabel = '';
1149
+ var debug: any = {};
1149
1150
 
1150
1151
  try {
1151
1152
  // Read the flow version payload which contains all flow elements
@@ -1156,37 +1157,97 @@ async function getFlowTriggerInfo(
1156
1157
  sysparm_limit: 1
1157
1158
  }
1158
1159
  });
1159
- var payload = resp.data.result?.[0]?.payload;
1160
+ var versionRecord = resp.data.result?.[0];
1161
+ debug.version_found = !!versionRecord;
1162
+ debug.version_sys_id = versionRecord?.sys_id || null;
1163
+ var payload = versionRecord?.payload;
1164
+ debug.payload_exists = !!payload;
1165
+ debug.payload_type = typeof payload;
1160
1166
  if (payload) {
1161
1167
  var parsed = typeof payload === 'string' ? JSON.parse(payload) : payload;
1168
+ debug.payload_keys = Object.keys(parsed);
1162
1169
  // The payload contains triggerInstances array with trigger data
1163
1170
  var triggerInstances = parsed.triggerInstances || parsed.trigger_instances || [];
1171
+ debug.triggerInstances_found = Array.isArray(triggerInstances) ? triggerInstances.length : 'not_array';
1164
1172
  if (!Array.isArray(triggerInstances)) {
1165
1173
  // Some payloads nest elements differently
1166
1174
  if (parsed.elements) {
1167
1175
  triggerInstances = (Array.isArray(parsed.elements) ? parsed.elements : []).filter(
1168
1176
  (e: any) => e.type === 'trigger' || e.elementType === 'trigger'
1169
1177
  );
1178
+ debug.elements_trigger_count = triggerInstances.length;
1170
1179
  }
1171
1180
  }
1172
1181
  if (triggerInstances.length > 0) {
1173
1182
  var trigger = triggerInstances[0];
1183
+ debug.trigger_keys = Object.keys(trigger);
1184
+ debug.trigger_name_field = trigger.name;
1185
+ debug.trigger_triggerName_field = trigger.triggerName;
1174
1186
  triggerName = trigger.name || trigger.triggerName || '';
1175
1187
  // Look for table in trigger inputs
1176
1188
  if (trigger.inputs && Array.isArray(trigger.inputs)) {
1189
+ debug.trigger_input_names = trigger.inputs.map(function (inp: any) { return inp.name; });
1177
1190
  for (var ti = 0; ti < trigger.inputs.length; ti++) {
1178
1191
  if (trigger.inputs[ti].name === 'table') {
1179
1192
  table = trigger.inputs[ti].value?.value || trigger.inputs[ti].value || '';
1180
1193
  break;
1181
1194
  }
1182
1195
  }
1196
+ } else {
1197
+ debug.trigger_inputs = trigger.inputs ? typeof trigger.inputs : 'undefined';
1183
1198
  }
1184
1199
  }
1200
+ } else {
1201
+ debug.payload_raw = 'null_or_empty';
1185
1202
  }
1186
- } catch (_) {}
1203
+ } catch (e: any) {
1204
+ debug.error = e.message;
1205
+ }
1206
+
1207
+ // Fallback: if version payload didn't have trigger, try reading from GraphQL flow state
1208
+ if (!triggerName || !table) {
1209
+ debug.fallback_graphql = 'attempting';
1210
+ try {
1211
+ var gqlQuery = 'query { global { snFlowDesigner { designTimeFlow(sysId: "' + flowId + '") { triggerInstances { name type inputs { name value { value } } } __typename } __typename } __typename } }';
1212
+ var gqlResp = await client.post('/api/now/graphql', { variables: {}, query: gqlQuery });
1213
+ var dtFlow = gqlResp.data?.data?.global?.snFlowDesigner?.designTimeFlow;
1214
+ var gqlTriggers = dtFlow?.triggerInstances || [];
1215
+ debug.fallback_graphql_triggers = gqlTriggers.length;
1216
+ if (gqlTriggers.length > 0) {
1217
+ var gqlTrig = gqlTriggers[0];
1218
+ debug.fallback_graphql_trigger = { name: gqlTrig.name, type: gqlTrig.type, inputCount: gqlTrig.inputs?.length };
1219
+ if (!triggerName && gqlTrig.name) triggerName = gqlTrig.name;
1220
+ if (!table && gqlTrig.inputs) {
1221
+ for (var gi = 0; gi < gqlTrig.inputs.length; gi++) {
1222
+ if (gqlTrig.inputs[gi].name === 'table') {
1223
+ table = gqlTrig.inputs[gi].value?.value || '';
1224
+ break;
1225
+ }
1226
+ }
1227
+ }
1228
+ }
1229
+ } catch (gqlErr: any) {
1230
+ debug.fallback_graphql = 'error: ' + gqlErr.message;
1231
+ }
1232
+ }
1233
+
1234
+ // Fallback 2: look up trigger definition that belongs to this flow
1235
+ if (!triggerName) {
1236
+ debug.fallback_trigdef = 'attempting';
1237
+ try {
1238
+ // Query sys_hub_trigger_type_definition to find triggers available for this flow
1239
+ // The trigger name in the definition matches the data pill prefix
1240
+ var trigResp = await client.get('/api/now/table/sys_hub_flow', {
1241
+ params: { sysparm_query: 'sys_id=' + flowId, sysparm_fields: 'sys_id,name,table', sysparm_limit: 1 }
1242
+ });
1243
+ var flowRec = trigResp.data.result?.[0];
1244
+ debug.fallback_flow_record = { table: flowRec?.table };
1245
+ if (flowRec?.table && !table) table = str(flowRec.table);
1246
+ } catch (_) {}
1247
+ }
1187
1248
 
1188
1249
  // Look up table label for display in label cache
1189
- if (table) {
1250
+ if (table && !tableLabel) {
1190
1251
  try {
1191
1252
  var labelResp = await client.get('/api/now/table/sys_db_object', {
1192
1253
  params: {
@@ -1199,17 +1260,16 @@ async function getFlowTriggerInfo(
1199
1260
  tableLabel = str(labelResp.data.result?.[0]?.label) || '';
1200
1261
  } catch (_) {}
1201
1262
  if (!tableLabel) {
1202
- // Fallback: capitalize the table name
1203
1263
  tableLabel = table.charAt(0).toUpperCase() + table.slice(1).replace(/_/g, ' ');
1204
1264
  }
1205
1265
  }
1206
1266
 
1207
1267
  if (!triggerName) {
1208
- return { dataPillBase: '', triggerName: '', table: table, tableLabel: tableLabel, tableRef: table, error: 'Could not determine trigger name from flow version payload' };
1268
+ return { dataPillBase: '', triggerName: '', table: table, tableLabel: tableLabel, tableRef: table, error: 'Could not determine trigger name from flow version payload or GraphQL', debug };
1209
1269
  }
1210
1270
 
1211
1271
  var dataPillBase = triggerName + '_1.current';
1212
- return { dataPillBase, triggerName, table, tableLabel, tableRef: table };
1272
+ return { dataPillBase, triggerName, table, tableLabel, tableRef: table, debug };
1213
1273
  }
1214
1274
 
1215
1275
  /**
@@ -1359,18 +1419,24 @@ async function buildConditionLabelCache(
1359
1419
  triggerName: string,
1360
1420
  table: string,
1361
1421
  tableLabel: string,
1362
- logicUiId: string
1422
+ logicUiId: string,
1423
+ explicitFields?: string[]
1363
1424
  ): Promise<any[]> {
1364
1425
  if (!dataPillBase) return [];
1365
1426
 
1366
- var clauses = parseEncodedQuery(conditionValue);
1367
- if (clauses.length === 0) return [];
1427
+ // Collect unique field names — either from explicit list or by parsing encoded query
1428
+ if (!explicitFields) {
1429
+ var clauses = parseEncodedQuery(conditionValue);
1430
+ if (clauses.length === 0) return [];
1431
+ explicitFields = clauses.map(function (c) { return c.field; }).filter(function (f) { return !!f; });
1432
+ }
1433
+ if (explicitFields.length === 0) return [];
1368
1434
 
1369
- // Collect unique field names
1435
+ // De-duplicate field names
1370
1436
  var uniqueFields: string[] = [];
1371
1437
  var seen: Record<string, boolean> = {};
1372
- for (var i = 0; i < clauses.length; i++) {
1373
- var field = clauses[i].field;
1438
+ for (var i = 0; i < explicitFields.length; i++) {
1439
+ var field = explicitFields[i];
1374
1440
  if (field && !seen[field]) {
1375
1441
  seen[field] = true;
1376
1442
  uniqueFields.push(field);
@@ -1474,7 +1540,8 @@ async function transformActionInputsForRecordAction(
1474
1540
  triggerName: triggerInfo.triggerName,
1475
1541
  table: triggerInfo.table,
1476
1542
  tableLabel: triggerInfo.tableLabel,
1477
- error: triggerInfo.error
1543
+ error: triggerInfo.error,
1544
+ debug: triggerInfo.debug
1478
1545
  };
1479
1546
 
1480
1547
  var dataPillBase = triggerInfo.dataPillBase; // e.g. "Created or Updated_1.current"
@@ -1733,22 +1800,46 @@ async function addFlowLogicViaGraphQL(
1733
1800
 
1734
1801
  // ── Detect condition that needs data pill transformation ────────────
1735
1802
  // Flow Designer sets conditions via a SEPARATE UPDATE after the element is created.
1736
- // The condition format uses record-level data pill: {{TriggerName_1.current}}encodedQuery
1737
- // Non-standard conditions (JS expressions, fd_data refs) are passed through as-is.
1803
+ // Three paths:
1804
+ // 1. Standard encoded query (category=software) transform fields to {{dataPillBase.field}}
1805
+ // 2. Contains {{shorthand}} like {{trigger.current.X}} → rewrite to {{dataPillBase.X}} + labelCache
1806
+ // 3. Non-standard (JS expression, fd_data ref) → passthrough
1738
1807
  const uuid = generateUUID();
1739
1808
  var conditionInput = inputResult.inputs.find(function (inp: any) { return inp.name === 'condition'; });
1740
1809
  var rawCondition = conditionInput?.value?.value || '';
1741
1810
  var needsConditionUpdate = false;
1742
1811
  var conditionTriggerInfo: any = null;
1743
1812
 
1744
- if (rawCondition && rawCondition !== '^EQ' && isStandardEncodedQuery(rawCondition)) {
1813
+ // Shorthand patterns that need rewriting to the real data pill base
1814
+ // e.g. {{trigger.current.category}} → {{Created or Updated_1.current.category}}
1815
+ var PILL_SHORTHANDS = ['trigger.current', 'current', 'trigger_record', 'trigger.record'];
1816
+ var hasShorthandPills = rawCondition.includes('{{') && PILL_SHORTHANDS.some(function (sh) {
1817
+ return rawCondition.includes('{{' + sh + '.') || rawCondition.includes('{{' + sh + '}}');
1818
+ });
1819
+
1820
+ if (rawCondition && rawCondition !== '^EQ' && (isStandardEncodedQuery(rawCondition) || hasShorthandPills)) {
1745
1821
  conditionTriggerInfo = await getFlowTriggerInfo(client, flowId);
1746
1822
  steps.trigger_info = {
1747
1823
  dataPillBase: conditionTriggerInfo.dataPillBase, triggerName: conditionTriggerInfo.triggerName,
1748
- table: conditionTriggerInfo.table, tableLabel: conditionTriggerInfo.tableLabel, error: conditionTriggerInfo.error
1824
+ table: conditionTriggerInfo.table, tableLabel: conditionTriggerInfo.tableLabel, error: conditionTriggerInfo.error,
1825
+ debug: conditionTriggerInfo.debug
1749
1826
  };
1750
1827
  if (conditionTriggerInfo.dataPillBase) {
1751
1828
  needsConditionUpdate = true;
1829
+
1830
+ // If condition has shorthand pills, rewrite them to real data pill base first
1831
+ if (hasShorthandPills) {
1832
+ var pillBase = conditionTriggerInfo.dataPillBase;
1833
+ for (var si = 0; si < PILL_SHORTHANDS.length; si++) {
1834
+ var sh = PILL_SHORTHANDS[si];
1835
+ // Replace {{trigger.current.field}} → {{Created or Updated_1.current.field}}
1836
+ rawCondition = rawCondition.split('{{' + sh + '.').join('{{' + pillBase + '.');
1837
+ // Replace {{trigger.current}} → {{Created or Updated_1.current}}
1838
+ rawCondition = rawCondition.split('{{' + sh + '}}').join('{{' + pillBase + '}}');
1839
+ }
1840
+ steps.shorthand_rewrite = { original: conditionInput?.value?.value, rewritten: rawCondition };
1841
+ }
1842
+
1752
1843
  // Clear condition in INSERT — it will be set via separate UPDATE with labelCache
1753
1844
  conditionInput.value = { schemaless: false, schemalessValue: '', value: '' };
1754
1845
  steps.condition_strategy = 'two_step';
@@ -1810,11 +1901,34 @@ async function addFlowLogicViaGraphQL(
1810
1901
  // The Flow Designer UI always sets conditions in a separate UPDATE after creating the element.
1811
1902
  if (needsConditionUpdate && conditionTriggerInfo) {
1812
1903
  var dataPillBase = conditionTriggerInfo.dataPillBase;
1813
- var transformedCondition = transformConditionToDataPills(rawCondition, dataPillBase);
1814
- var labelCacheEntries = await buildConditionLabelCache(
1815
- client, rawCondition, dataPillBase, conditionTriggerInfo.triggerName,
1816
- conditionTriggerInfo.tableRef, conditionTriggerInfo.tableLabel, returnedUuid
1817
- );
1904
+ var transformedCondition: string;
1905
+ var labelCacheEntries: any[];
1906
+
1907
+ if (rawCondition.includes('{{')) {
1908
+ // Condition already contains data pill references (after shorthand rewrite)
1909
+ // Use as-is and extract field names from {{pill.field}} patterns for labelCache
1910
+ transformedCondition = rawCondition;
1911
+ var pillFields: string[] = [];
1912
+ var pillRx = /\{\{([^}]+)\}\}/g;
1913
+ var pm;
1914
+ while ((pm = pillRx.exec(rawCondition)) !== null) {
1915
+ var pParts = pm[1].split('.');
1916
+ if (pParts.length > 2) pillFields.push(pParts[pParts.length - 1]);
1917
+ }
1918
+ // Build labelCache using extracted field names
1919
+ labelCacheEntries = await buildConditionLabelCache(
1920
+ client, rawCondition, dataPillBase, conditionTriggerInfo.triggerName,
1921
+ conditionTriggerInfo.tableRef, conditionTriggerInfo.tableLabel, returnedUuid,
1922
+ pillFields
1923
+ );
1924
+ } else {
1925
+ // Plain encoded query — transform to data pill format
1926
+ transformedCondition = transformConditionToDataPills(rawCondition, dataPillBase);
1927
+ labelCacheEntries = await buildConditionLabelCache(
1928
+ client, rawCondition, dataPillBase, conditionTriggerInfo.triggerName,
1929
+ conditionTriggerInfo.tableRef, conditionTriggerInfo.tableLabel, returnedUuid
1930
+ );
1931
+ }
1818
1932
 
1819
1933
  steps.condition_transform = { original: rawCondition, transformed: transformedCondition };
1820
1934
  steps.label_cache = { count: labelCacheEntries.length, pills: labelCacheEntries.map(function (e: any) { return e.name; }) };