snow-flow 10.0.1-dev.467 → 10.0.1-dev.469

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.467",
3
+ "version": "10.0.1-dev.469",
4
4
  "name": "snow-flow",
5
5
  "description": "Snow-Flow - ServiceNow Multi-Agent Development Framework powered by AI",
6
6
  "license": "Elastic-2.0",
@@ -1009,12 +1009,30 @@ async function addActionViaGraphQL(
1009
1009
  inputResult.actionParams, uuid
1010
1010
  );
1011
1011
  steps.record_action = recordActionResult.steps;
1012
+ var hasRecordPills = recordActionResult.labelCacheEntries.length > 0;
1013
+
1014
+ // For record actions: clear data pill values from INSERT — they'll be set via separate UPDATE
1015
+ // (Flow Designer's GraphQL API ignores labelCache during INSERT, it only works with UPDATE)
1016
+ var insertInputs = recordActionResult.inputs;
1017
+ if (hasRecordPills) {
1018
+ // Clone inputs and clear data pill values for INSERT
1019
+ insertInputs = recordActionResult.inputs.map(function (inp: any) {
1020
+ if (inp.name === 'record' && inp.value?.value?.startsWith('{{')) {
1021
+ return { ...inp, value: { schemaless: false, schemalessValue: '', value: '' } };
1022
+ }
1023
+ if (inp.name === 'values' && inp.value?.value?.includes('{{')) {
1024
+ return { ...inp, value: { schemaless: false, schemalessValue: '', value: '' } };
1025
+ }
1026
+ return inp;
1027
+ });
1028
+ steps.record_action_strategy = 'two_step';
1029
+ }
1012
1030
 
1013
1031
  const actionResponseFields = 'actions { inserts { sysId uiUniqueIdentifier __typename } updates deletes __typename }' +
1014
1032
  ' flowLogics { inserts { sysId uiUniqueIdentifier __typename } updates deletes __typename }' +
1015
1033
  ' subflows { inserts { sysId uiUniqueIdentifier __typename } updates deletes __typename }';
1016
1034
 
1017
- // Build mutation payload — single INSERT with full inputs (matching UI behavior)
1035
+ // Build mutation payload — INSERT with inputs (data pill values cleared for record actions)
1018
1036
  const flowPatch: any = {
1019
1037
  flowId: flowId,
1020
1038
  actions: {
@@ -1028,25 +1046,62 @@ async function addActionViaGraphQL(
1028
1046
  uiUniqueIdentifier: uuid,
1029
1047
  type: 'action',
1030
1048
  parentUiId: parentUiId || '',
1031
- inputs: recordActionResult.inputs
1049
+ inputs: insertInputs
1032
1050
  }]
1033
1051
  }
1034
1052
  };
1035
1053
 
1036
- // Add labelCache entries for data pill references in record actions
1037
- if (recordActionResult.labelCacheEntries.length > 0) {
1038
- flowPatch.labelCache = { insert: recordActionResult.labelCacheEntries };
1039
- }
1040
-
1041
1054
  // Add parent flow logic update signal (tells GraphQL the parent was modified)
1042
1055
  if (parentUiId) {
1043
1056
  flowPatch.flowLogics = { update: [{ uiUniqueIdentifier: parentUiId, type: 'flowlogic' }] };
1044
1057
  }
1045
1058
 
1046
1059
  try {
1060
+ // Step 1: INSERT the action element
1047
1061
  const result = await executeFlowPatchMutation(client, flowPatch, actionResponseFields);
1048
1062
  const actionId = result?.actions?.inserts?.[0]?.sysId;
1049
1063
  steps.insert = { success: !!actionId, actionId, uuid };
1064
+ if (!actionId) return { success: false, steps, error: 'GraphQL action INSERT returned no ID' };
1065
+
1066
+ // Step 2: UPDATE with data pill values + labelCache (separate mutation, matching UI behavior)
1067
+ // The Flow Designer UI always sets data pill references via a separate UPDATE.
1068
+ if (hasRecordPills) {
1069
+ var updateInputs: any[] = [];
1070
+ // Collect all inputs that have data pill values
1071
+ for (var ri = 0; ri < recordActionResult.inputs.length; ri++) {
1072
+ var inp = recordActionResult.inputs[ri];
1073
+ var val = inp.value?.value || '';
1074
+ if (inp.name === 'record' && val.startsWith('{{')) {
1075
+ updateInputs.push({ name: 'record', value: { schemaless: false, schemalessValue: '', value: val } });
1076
+ } else if (inp.name === 'table_name') {
1077
+ updateInputs.push({ name: 'table_name', displayValue: inp.displayValue || { value: '' }, value: inp.value || { value: '' } });
1078
+ } else if (inp.name === 'values' && val) {
1079
+ updateInputs.push({ name: 'values', value: { schemaless: false, schemalessValue: '', value: val } });
1080
+ }
1081
+ }
1082
+
1083
+ if (updateInputs.length > 0) {
1084
+ try {
1085
+ var updatePatch: any = {
1086
+ flowId: flowId,
1087
+ labelCache: { insert: recordActionResult.labelCacheEntries },
1088
+ actions: {
1089
+ update: [{
1090
+ uiUniqueIdentifier: uuid,
1091
+ type: 'action',
1092
+ inputs: updateInputs
1093
+ }]
1094
+ }
1095
+ };
1096
+ await executeFlowPatchMutation(client, updatePatch, actionResponseFields);
1097
+ steps.record_update = { success: true, inputCount: updateInputs.length };
1098
+ } catch (ue: any) {
1099
+ steps.record_update = { success: false, error: ue.message };
1100
+ // Action was created — update failure is non-fatal
1101
+ }
1102
+ }
1103
+ }
1104
+
1050
1105
  return { success: true, actionId: actionId || undefined, steps };
1051
1106
  } catch (e: any) {
1052
1107
  steps.insert = { success: false, error: e.message };
@@ -1268,58 +1323,81 @@ function transformConditionToDataPills(conditionValue: string, dataPillBase: str
1268
1323
  * Build labelCache entries for field-level data pills used in flow logic conditions.
1269
1324
  *
1270
1325
  * Each field referenced in the condition needs a labelCache entry so the Flow Designer UI
1271
- * can display the data pill correctly. Also registers the record-level parent pill.
1326
+ * can display the data pill correctly. Queries sys_dictionary for actual field types/labels.
1327
+ *
1328
+ * The UI only sends field-level pills (NOT a record-level parent pill).
1329
+ * Each entry includes parent_table_name and column_name for the UI to resolve the field.
1272
1330
  *
1273
- * Example for "category=software":
1274
- * - Record pill: { name: "Created or Updated_1.current", type: "reference", reference: "incident" }
1275
- * - Field pill: { name: "Created or Updated_1.current.category", type: "string" }
1331
+ * Example for "category=inquiry" on incident table:
1332
+ * - { name: "Created or Updated_1.current.category", type: "choice", parent_table_name: "incident", column_name: "category" }
1276
1333
  */
1277
- function buildConditionLabelCache(
1334
+ async function buildConditionLabelCache(
1335
+ client: any,
1278
1336
  conditionValue: string,
1279
1337
  dataPillBase: string,
1280
1338
  triggerName: string,
1281
1339
  table: string,
1282
1340
  tableLabel: string,
1283
1341
  logicUiId: string
1284
- ): any[] {
1342
+ ): Promise<any[]> {
1285
1343
  if (!dataPillBase) return [];
1286
1344
 
1287
1345
  var clauses = parseEncodedQuery(conditionValue);
1288
- var entries: any[] = [];
1289
- var seen: Record<string, boolean> = {};
1290
-
1291
- // Register the record-level parent pill (always needed as context)
1292
- entries.push({
1293
- name: dataPillBase,
1294
- label: 'Trigger - Record ' + triggerName + '\u27a1' + tableLabel + ' Record',
1295
- reference: table,
1296
- reference_display: tableLabel,
1297
- type: 'reference',
1298
- base_type: 'reference',
1299
- attributes: '',
1300
- usedInstances: [{ uiUniqueIdentifier: logicUiId, inputName: 'condition' }],
1301
- choices: {}
1302
- });
1303
- seen[dataPillBase] = true;
1346
+ if (clauses.length === 0) return [];
1304
1347
 
1305
- // Register each field-level pill
1348
+ // Collect unique field names
1349
+ var uniqueFields: string[] = [];
1350
+ var seen: Record<string, boolean> = {};
1306
1351
  for (var i = 0; i < clauses.length; i++) {
1307
1352
  var field = clauses[i].field;
1308
- if (!field) continue;
1309
- var pillName = dataPillBase + '.' + field;
1310
- if (seen[pillName]) continue;
1311
- seen[pillName] = true;
1353
+ if (field && !seen[field]) {
1354
+ seen[field] = true;
1355
+ uniqueFields.push(field);
1356
+ }
1357
+ }
1358
+ if (uniqueFields.length === 0) return [];
1312
1359
 
1313
- var fieldLabel = field.replace(/_/g, ' ').replace(/\b\w/g, function (c: string) { return c.toUpperCase(); });
1360
+ // Batch-query sys_dictionary for field metadata (type, label)
1361
+ var fieldMeta: Record<string, { type: string; label: string }> = {};
1362
+ try {
1363
+ var dictResp = await client.get('/api/now/table/sys_dictionary', {
1364
+ params: {
1365
+ sysparm_query: 'name=' + table + '^elementIN' + uniqueFields.join(','),
1366
+ sysparm_fields: 'element,column_label,internal_type',
1367
+ sysparm_display_value: 'false',
1368
+ sysparm_limit: uniqueFields.length + 5
1369
+ }
1370
+ });
1371
+ var dictResults = dictResp.data.result || [];
1372
+ for (var d = 0; d < dictResults.length; d++) {
1373
+ var rec = dictResults[d];
1374
+ var elName = str(rec.element);
1375
+ var intType = str(rec.internal_type?.value || rec.internal_type || 'string');
1376
+ var colLabel = str(rec.column_label);
1377
+ if (elName) fieldMeta[elName] = { type: intType, label: colLabel };
1378
+ }
1379
+ } catch (_) {
1380
+ // Fallback: use "string" type and generated labels if dictionary lookup fails
1381
+ }
1382
+
1383
+ // Build field-level labelCache entries (no record-level parent — UI doesn't send one)
1384
+ var entries: any[] = [];
1385
+ for (var j = 0; j < uniqueFields.length; j++) {
1386
+ var f = uniqueFields[j];
1387
+ var meta = fieldMeta[f];
1388
+ var fType = meta ? meta.type : 'string';
1389
+ var fLabel = meta ? meta.label : f.replace(/_/g, ' ').replace(/\b\w/g, function (c: string) { return c.toUpperCase(); });
1390
+ var pillName = dataPillBase + '.' + f;
1314
1391
 
1315
1392
  entries.push({
1316
1393
  name: pillName,
1317
- label: 'Trigger - Record ' + triggerName + '\u27a1' + tableLabel + ' Record\u27a1' + fieldLabel,
1394
+ label: 'Trigger - Record ' + triggerName + '\u27a1' + tableLabel + ' Record\u27a1' + fLabel,
1318
1395
  reference: '',
1319
- reference_display: '',
1320
- type: 'string',
1321
- base_type: 'string',
1322
- attributes: '',
1396
+ reference_display: fLabel,
1397
+ type: fType,
1398
+ base_type: fType,
1399
+ parent_table_name: table,
1400
+ column_name: f,
1323
1401
  usedInstances: [{ uiUniqueIdentifier: logicUiId, inputName: 'condition' }],
1324
1402
  choices: {}
1325
1403
  });
@@ -1655,8 +1733,8 @@ async function addFlowLogicViaGraphQL(
1655
1733
  if (needsConditionUpdate && conditionTriggerInfo) {
1656
1734
  var dataPillBase = conditionTriggerInfo.dataPillBase;
1657
1735
  var transformedCondition = transformConditionToDataPills(rawCondition, dataPillBase);
1658
- var labelCacheEntries = buildConditionLabelCache(
1659
- rawCondition, dataPillBase, conditionTriggerInfo.triggerName,
1736
+ var labelCacheEntries = await buildConditionLabelCache(
1737
+ client, rawCondition, dataPillBase, conditionTriggerInfo.triggerName,
1660
1738
  conditionTriggerInfo.tableRef, conditionTriggerInfo.tableLabel, returnedUuid
1661
1739
  );
1662
1740
 
@@ -1673,8 +1751,13 @@ async function addFlowLogicViaGraphQL(
1673
1751
  type: 'flowlogic',
1674
1752
  inputs: [{
1675
1753
  name: 'condition',
1754
+ displayValue: { value: '' },
1676
1755
  value: { schemaless: false, schemalessValue: '', value: transformedCondition }
1677
- }]
1756
+ }],
1757
+ flowLogicDefinition: {
1758
+ inputs: [{ name: 'condition_name', attributes: 'use_basic_input=true,' }],
1759
+ variables: 'undefined'
1760
+ }
1678
1761
  }]
1679
1762
  }
1680
1763
  };