snow-flow-test 10.0.1-test.178 → 10.0.1-test.180

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-test.178",
3
+ "version": "10.0.1-test.180",
4
4
  "name": "snow-flow-test",
5
5
  "description": "Snow-Flow Test - ServiceNow Multi-Agent Development Framework",
6
6
  "license": "Elastic-2.0",
@@ -141,7 +141,7 @@ async function buildActionInputsForInsert(
141
141
  }
142
142
  var match = actionParams.find(function (p: any) {
143
143
  var el = str(p.element);
144
- return el.endsWith('_' + key) || el === key || str(p.label).toLowerCase() === key.toLowerCase();
144
+ return el.endsWith('_' + key) || el.startsWith(key + '_') || el === key || str(p.label).toLowerCase() === key.toLowerCase();
145
145
  });
146
146
  if (match) resolvedInputs[str(match.element)] = value;
147
147
  else resolvedInputs[key] = value;
@@ -1001,6 +1001,15 @@ async function addActionViaGraphQL(
1001
1001
  steps.insert_order = resolvedOrder;
1002
1002
 
1003
1003
  const uuid = generateUUID();
1004
+
1005
+ // ── Data pill transformation for record actions (Update/Create Record) ──
1006
+ // These actions need: record → data pill, table_name → displayValue, field values → packed into values string
1007
+ var recordActionResult = await transformActionInputsForRecordAction(
1008
+ client, flowId, inputResult.inputs, inputResult.resolvedInputs,
1009
+ inputResult.actionParams, uuid
1010
+ );
1011
+ steps.record_action = recordActionResult.steps;
1012
+
1004
1013
  const actionResponseFields = 'actions { inserts { sysId uiUniqueIdentifier __typename } updates deletes __typename }' +
1005
1014
  ' flowLogics { inserts { sysId uiUniqueIdentifier __typename } updates deletes __typename }' +
1006
1015
  ' subflows { inserts { sysId uiUniqueIdentifier __typename } updates deletes __typename }';
@@ -1019,11 +1028,16 @@ async function addActionViaGraphQL(
1019
1028
  uiUniqueIdentifier: uuid,
1020
1029
  type: 'action',
1021
1030
  parentUiId: parentUiId || '',
1022
- inputs: inputResult.inputs
1031
+ inputs: recordActionResult.inputs
1023
1032
  }]
1024
1033
  }
1025
1034
  };
1026
1035
 
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
+
1027
1041
  // Add parent flow logic update signal (tells GraphQL the parent was modified)
1028
1042
  if (parentUiId) {
1029
1043
  flowPatch.flowLogics = { update: [{ uiUniqueIdentifier: parentUiId, type: 'flowlogic' }] };
@@ -1040,6 +1054,427 @@ async function addActionViaGraphQL(
1040
1054
  }
1041
1055
  }
1042
1056
 
1057
+ // ── DATA PILL CONDITION HELPERS ────────────────────────────────────────
1058
+
1059
+ /**
1060
+ * Get trigger info from a flow for constructing data pill references.
1061
+ * Reads the flow version payload to find the trigger name, table, and outputs.
1062
+ *
1063
+ * Returns the data pill base (e.g., "Created or Updated_1") and table (e.g., "incident").
1064
+ * The data pill base is used as: {{dataPillBase.fieldName}} in condition values.
1065
+ */
1066
+ async function getFlowTriggerInfo(
1067
+ client: any,
1068
+ flowId: string
1069
+ ): Promise<{ dataPillBase: string; triggerName: string; table: string; tableLabel: string; tableRef: string; error?: string }> {
1070
+ var triggerName = '';
1071
+ var table = '';
1072
+ var tableLabel = '';
1073
+
1074
+ try {
1075
+ // Read the flow version payload which contains all flow elements
1076
+ var resp = await client.get('/api/now/table/sys_hub_flow_version', {
1077
+ params: {
1078
+ sysparm_query: 'flow=' + flowId + '^ORDERBYDESCsys_created_on',
1079
+ sysparm_fields: 'sys_id,payload',
1080
+ sysparm_limit: 1
1081
+ }
1082
+ });
1083
+ var payload = resp.data.result?.[0]?.payload;
1084
+ if (payload) {
1085
+ var parsed = typeof payload === 'string' ? JSON.parse(payload) : payload;
1086
+ // The payload contains triggerInstances array with trigger data
1087
+ var triggerInstances = parsed.triggerInstances || parsed.trigger_instances || [];
1088
+ if (!Array.isArray(triggerInstances)) {
1089
+ // Some payloads nest elements differently
1090
+ if (parsed.elements) {
1091
+ triggerInstances = (Array.isArray(parsed.elements) ? parsed.elements : []).filter(
1092
+ (e: any) => e.type === 'trigger' || e.elementType === 'trigger'
1093
+ );
1094
+ }
1095
+ }
1096
+ if (triggerInstances.length > 0) {
1097
+ var trigger = triggerInstances[0];
1098
+ triggerName = trigger.name || trigger.triggerName || '';
1099
+ // Look for table in trigger inputs
1100
+ if (trigger.inputs && Array.isArray(trigger.inputs)) {
1101
+ for (var ti = 0; ti < trigger.inputs.length; ti++) {
1102
+ if (trigger.inputs[ti].name === 'table') {
1103
+ table = trigger.inputs[ti].value?.value || trigger.inputs[ti].value || '';
1104
+ break;
1105
+ }
1106
+ }
1107
+ }
1108
+ }
1109
+ }
1110
+ } catch (_) {}
1111
+
1112
+ // Look up table label for display in label cache
1113
+ if (table) {
1114
+ try {
1115
+ var labelResp = await client.get('/api/now/table/sys_db_object', {
1116
+ params: {
1117
+ sysparm_query: 'name=' + table,
1118
+ sysparm_fields: 'label',
1119
+ sysparm_display_value: 'true',
1120
+ sysparm_limit: 1
1121
+ }
1122
+ });
1123
+ tableLabel = str(labelResp.data.result?.[0]?.label) || '';
1124
+ } catch (_) {}
1125
+ if (!tableLabel) {
1126
+ // Fallback: capitalize the table name
1127
+ tableLabel = table.charAt(0).toUpperCase() + table.slice(1).replace(/_/g, ' ');
1128
+ }
1129
+ }
1130
+
1131
+ if (!triggerName) {
1132
+ return { dataPillBase: '', triggerName: '', table: table, tableLabel: tableLabel, tableRef: table, error: 'Could not determine trigger name from flow version payload' };
1133
+ }
1134
+
1135
+ var dataPillBase = triggerName + '_1.current';
1136
+ return { dataPillBase, triggerName, table, tableLabel, tableRef: table };
1137
+ }
1138
+
1139
+ /**
1140
+ * Parse a ServiceNow encoded query into individual condition clauses.
1141
+ * Each clause has: prefix (^ or ^OR), field, operator, value.
1142
+ *
1143
+ * Example: "category=inquiry^priority!=1^ORshort_descriptionLIKEtest"
1144
+ * → [
1145
+ * { prefix: '', field: 'category', operator: '=', value: 'inquiry' },
1146
+ * { prefix: '^', field: 'priority', operator: '!=', value: '1' },
1147
+ * { prefix: '^OR', field: 'short_description', operator: 'LIKE', value: 'test' }
1148
+ * ]
1149
+ */
1150
+ function parseEncodedQuery(query: string): { prefix: string; field: string; operator: string; value: string }[] {
1151
+ if (!query || query === '^EQ') return [];
1152
+
1153
+ // Remove trailing ^EQ
1154
+ var q = query.replace(/\^EQ$/, '');
1155
+ if (!q) return [];
1156
+
1157
+ // Split on ^OR and ^ while keeping the separators
1158
+ var clauses: { prefix: string; raw: string }[] = [];
1159
+ var parts = q.split(/(\^OR|\^NQ|\^)/);
1160
+ var currentPrefix = '';
1161
+
1162
+ for (var i = 0; i < parts.length; i++) {
1163
+ var part = parts[i];
1164
+ if (part === '^' || part === '^OR' || part === '^NQ') {
1165
+ currentPrefix = part;
1166
+ } else if (part.length > 0) {
1167
+ clauses.push({ prefix: currentPrefix, raw: part });
1168
+ currentPrefix = '';
1169
+ }
1170
+ }
1171
+
1172
+ // Operators sorted by length descending to match longest first
1173
+ var operators = [
1174
+ 'VALCHANGES', 'CHANGESFROM', 'CHANGESTO',
1175
+ 'ISNOTEMPTY', 'ISEMPTY', 'EMPTYSTRING', 'ANYTHING',
1176
+ 'NOT LIKE', 'NOT IN', 'NSAMEAS',
1177
+ 'STARTSWITH', 'ENDSWITH', 'BETWEEN', 'INSTANCEOF',
1178
+ 'DYNAMIC', 'SAMEAS',
1179
+ 'LIKE', 'IN',
1180
+ '!=', '>=', '<=', '>', '<', '='
1181
+ ];
1182
+
1183
+ var result: { prefix: string; field: string; operator: string; value: string }[] = [];
1184
+ for (var j = 0; j < clauses.length; j++) {
1185
+ var clause = clauses[j];
1186
+ var raw = clause.raw;
1187
+ var matched = false;
1188
+
1189
+ for (var k = 0; k < operators.length; k++) {
1190
+ var op = operators[k];
1191
+ var opIdx = raw.indexOf(op);
1192
+ if (opIdx > 0) {
1193
+ result.push({
1194
+ prefix: clause.prefix,
1195
+ field: raw.substring(0, opIdx),
1196
+ operator: op,
1197
+ value: raw.substring(opIdx + op.length)
1198
+ });
1199
+ matched = true;
1200
+ break;
1201
+ }
1202
+ }
1203
+ if (!matched) {
1204
+ // Unrecognized format — keep as-is
1205
+ result.push({ prefix: clause.prefix, field: raw, operator: '', value: '' });
1206
+ }
1207
+ }
1208
+
1209
+ return result;
1210
+ }
1211
+
1212
+ /**
1213
+ * Transform an encoded query condition value to use data pill references.
1214
+ * Replaces field names with {{dataPillBase.fieldName}} syntax.
1215
+ *
1216
+ * Example: "category=inquiry" → "{{Created or Updated_1.current.category}}=inquiry"
1217
+ */
1218
+ function transformConditionToDataPills(conditionValue: string, dataPillBase: string): string {
1219
+ if (!conditionValue || !dataPillBase) return conditionValue;
1220
+
1221
+ var clauses = parseEncodedQuery(conditionValue);
1222
+ if (clauses.length === 0) return conditionValue;
1223
+
1224
+ var result = '';
1225
+ for (var i = 0; i < clauses.length; i++) {
1226
+ var clause = clauses[i];
1227
+ result += clause.prefix;
1228
+ result += '{{' + dataPillBase + '.' + clause.field + '}}';
1229
+ result += clause.operator;
1230
+ result += clause.value;
1231
+ }
1232
+
1233
+ return result;
1234
+ }
1235
+
1236
+ /**
1237
+ * Build labelCache entries for data pills used in flow logic conditions.
1238
+ * Each unique data pill reference needs a labelCache entry so the Flow Designer UI
1239
+ * can display the data pill label correctly.
1240
+ *
1241
+ * The entry format matches the captured UI mutation format.
1242
+ */
1243
+ function buildConditionLabelCache(
1244
+ conditionValue: string,
1245
+ dataPillBase: string,
1246
+ triggerName: string,
1247
+ table: string,
1248
+ tableLabel: string,
1249
+ logicUiId: string
1250
+ ): any[] {
1251
+ if (!conditionValue || !dataPillBase) return [];
1252
+
1253
+ var clauses = parseEncodedQuery(conditionValue);
1254
+ if (clauses.length === 0) return [];
1255
+
1256
+ // Build a label cache entry for each unique field-level data pill
1257
+ var seen: Record<string, boolean> = {};
1258
+ var entries: any[] = [];
1259
+
1260
+ // Also register the record-level data pill (the base: TriggerName_1.current)
1261
+ // This is needed for the condition builder to understand the context
1262
+ if (!seen[dataPillBase]) {
1263
+ seen[dataPillBase] = true;
1264
+ entries.push({
1265
+ name: dataPillBase,
1266
+ label: 'Trigger - Record ' + triggerName + '\u27a1' + tableLabel + ' Record',
1267
+ reference: table,
1268
+ reference_display: tableLabel,
1269
+ type: 'reference',
1270
+ base_type: 'reference',
1271
+ attributes: '',
1272
+ usedInstances: [{ uiUniqueIdentifier: logicUiId, inputName: 'condition' }],
1273
+ choices: {}
1274
+ });
1275
+ }
1276
+
1277
+ for (var i = 0; i < clauses.length; i++) {
1278
+ var field = clauses[i].field;
1279
+ if (!field) continue;
1280
+ var pillName = dataPillBase + '.' + field;
1281
+ if (seen[pillName]) continue;
1282
+ seen[pillName] = true;
1283
+
1284
+ // Capitalize field name for label
1285
+ var fieldLabel = field.replace(/_/g, ' ').replace(/\b\w/g, function (c: string) { return c.toUpperCase(); });
1286
+
1287
+ entries.push({
1288
+ name: pillName,
1289
+ label: 'Trigger - Record ' + triggerName + '\u27a1' + tableLabel + ' Record\u27a1' + fieldLabel,
1290
+ reference: '',
1291
+ reference_display: '',
1292
+ type: 'string',
1293
+ base_type: 'string',
1294
+ attributes: '',
1295
+ usedInstances: [{ uiUniqueIdentifier: logicUiId, inputName: 'condition' }],
1296
+ choices: {}
1297
+ });
1298
+ }
1299
+
1300
+ return entries;
1301
+ }
1302
+
1303
+ // ── DATA PILL SUPPORT FOR RECORD ACTIONS (Update/Create Record) ──────
1304
+
1305
+ /** Shorthands that users can pass for `record` to mean "the trigger's current record". */
1306
+ const RECORD_PILL_SHORTHANDS = ['current', 'trigger.current', 'trigger_record', 'trigger record'];
1307
+
1308
+ /**
1309
+ * Post-process action inputs for record-modifying actions (Update Record, Create Record).
1310
+ *
1311
+ * These actions have 3 key inputs:
1312
+ * - `record`: reference to the record → needs data pill format {{TriggerName_1.current}}
1313
+ * - `table_name`: target table → needs displayValue (e.g. "Incident")
1314
+ * - `values`: packed field=value pairs → e.g. "priority=2^state=3"
1315
+ *
1316
+ * User-provided field-value pairs that don't match defined action parameters are
1317
+ * automatically packed into the `values` string.
1318
+ *
1319
+ * Returns the transformed inputs and labelCache entries.
1320
+ */
1321
+ async function transformActionInputsForRecordAction(
1322
+ client: any,
1323
+ flowId: string,
1324
+ actionInputs: any[],
1325
+ resolvedInputs: Record<string, string>,
1326
+ actionParams: any[],
1327
+ uuid: string
1328
+ ): Promise<{ inputs: any[]; labelCacheEntries: any[]; steps: any }> {
1329
+ var steps: any = {};
1330
+
1331
+ // Detect if this is a record action: must have both `record` and `table_name` parameters
1332
+ var definedParamNames = actionParams.map(function (p: any) { return str(p.element); });
1333
+ var hasRecord = definedParamNames.includes('record');
1334
+ var hasTableName = definedParamNames.includes('table_name');
1335
+ var hasValues = definedParamNames.includes('values');
1336
+
1337
+ if (!hasRecord || !hasTableName) {
1338
+ steps.record_action = false;
1339
+ return { inputs: actionInputs, labelCacheEntries: [], steps };
1340
+ }
1341
+ steps.record_action = true;
1342
+
1343
+ // Get trigger info for data pill construction
1344
+ var triggerInfo = await getFlowTriggerInfo(client, flowId);
1345
+ steps.trigger_info = {
1346
+ dataPillBase: triggerInfo.dataPillBase,
1347
+ triggerName: triggerInfo.triggerName,
1348
+ table: triggerInfo.table,
1349
+ tableLabel: triggerInfo.tableLabel,
1350
+ error: triggerInfo.error
1351
+ };
1352
+
1353
+ var dataPillBase = triggerInfo.dataPillBase; // e.g. "Created or Updated_1.current"
1354
+ var labelCacheEntries: any[] = [];
1355
+ var usedInstances: { uiUniqueIdentifier: string; inputName: string }[] = [];
1356
+
1357
+ // ── 1. Transform `record` input to data pill ──────────────────────
1358
+ var recordInput = actionInputs.find(function (inp: any) { return inp.name === 'record'; });
1359
+ if (recordInput && dataPillBase) {
1360
+ var recordVal = recordInput.value?.value || '';
1361
+ var isShorthand = RECORD_PILL_SHORTHANDS.includes(recordVal.toLowerCase());
1362
+ var isAlreadyPill = recordVal.startsWith('{{');
1363
+
1364
+ if (isShorthand || !recordVal) {
1365
+ // Auto-fill with trigger's current record data pill
1366
+ recordInput.value = { schemaless: false, schemalessValue: '', value: '{{' + dataPillBase + '}}' };
1367
+ usedInstances.push({ uiUniqueIdentifier: uuid, inputName: 'record' });
1368
+ steps.record_transform = { original: recordVal, pill: '{{' + dataPillBase + '}}' };
1369
+ } else if (isAlreadyPill) {
1370
+ usedInstances.push({ uiUniqueIdentifier: uuid, inputName: 'record' });
1371
+ }
1372
+ }
1373
+
1374
+ // ── 2. Transform `table_name` input with displayValue ─────────────
1375
+ var tableNameInput = actionInputs.find(function (inp: any) { return inp.name === 'table_name'; });
1376
+ if (tableNameInput) {
1377
+ var tableVal = tableNameInput.value?.value || '';
1378
+ // Also accept `table` as user key (maps to table_name)
1379
+ if (!tableVal && resolvedInputs['table']) {
1380
+ tableVal = resolvedInputs['table'];
1381
+ }
1382
+ // If still empty, use trigger's table
1383
+ if (!tableVal && triggerInfo.table) {
1384
+ tableVal = triggerInfo.table;
1385
+ }
1386
+ if (tableVal) {
1387
+ // Look up display name for the table
1388
+ var tableDisplayName = triggerInfo.tableLabel || '';
1389
+ if (tableVal !== triggerInfo.table || !tableDisplayName) {
1390
+ // Different table than trigger — look up its label
1391
+ try {
1392
+ var tblResp = await client.get('/api/now/table/sys_db_object', {
1393
+ params: { sysparm_query: 'name=' + tableVal, sysparm_fields: 'label', sysparm_display_value: 'true', sysparm_limit: 1 }
1394
+ });
1395
+ tableDisplayName = str(tblResp.data.result?.[0]?.label) || tableVal.charAt(0).toUpperCase() + tableVal.slice(1).replace(/_/g, ' ');
1396
+ } catch (_) {
1397
+ tableDisplayName = tableVal.charAt(0).toUpperCase() + tableVal.slice(1).replace(/_/g, ' ');
1398
+ }
1399
+ }
1400
+ tableNameInput.value = { schemaless: false, schemalessValue: '', value: tableVal };
1401
+ tableNameInput.displayValue = { schemaless: false, schemalessValue: '', value: tableDisplayName };
1402
+ steps.table_name_transform = { value: tableVal, displayValue: tableDisplayName };
1403
+ }
1404
+ }
1405
+
1406
+ // ── 3. Pack non-parameter field values into `values` string ───────
1407
+ // Any user-provided key that is NOT a defined action parameter goes into the values string
1408
+ var valuesInput = actionInputs.find(function (inp: any) { return inp.name === 'values'; });
1409
+ if (valuesInput) {
1410
+ var fieldPairs: string[] = [];
1411
+ var existingValues = valuesInput.value?.value || '';
1412
+
1413
+ // If user already passed a pre-built values string, use it
1414
+ if (existingValues && existingValues.includes('=')) {
1415
+ fieldPairs.push(existingValues);
1416
+ }
1417
+
1418
+ // Find user-provided keys that are not defined action parameters
1419
+ for (var key of Object.keys(resolvedInputs)) {
1420
+ if (definedParamNames.includes(key)) continue;
1421
+ // Also skip table (alias for table_name) and record
1422
+ if (key === 'table' || key === 'record') continue;
1423
+
1424
+ var val = resolvedInputs[key];
1425
+
1426
+ // Check if value should be a data pill reference
1427
+ if (val && dataPillBase) {
1428
+ var valLower = val.toLowerCase();
1429
+ if (RECORD_PILL_SHORTHANDS.includes(valLower)) {
1430
+ // Shorthand → record-level data pill
1431
+ val = '{{' + dataPillBase + '}}';
1432
+ usedInstances.push({ uiUniqueIdentifier: uuid, inputName: key });
1433
+ } else if (valLower.startsWith('trigger.current.') || valLower.startsWith('current.')) {
1434
+ // Field-level data pill: "trigger.current.assigned_to" → {{dataPillBase.assigned_to}}
1435
+ var fieldName = valLower.startsWith('trigger.current.') ? val.substring(16) : val.substring(8);
1436
+ val = '{{' + dataPillBase + '.' + fieldName + '}}';
1437
+ usedInstances.push({ uiUniqueIdentifier: uuid, inputName: key });
1438
+ } else if (val.startsWith('{{')) {
1439
+ // Already a data pill
1440
+ usedInstances.push({ uiUniqueIdentifier: uuid, inputName: key });
1441
+ }
1442
+ }
1443
+
1444
+ fieldPairs.push(key + '=' + val);
1445
+ }
1446
+
1447
+ if (fieldPairs.length > 0) {
1448
+ var packedValues = fieldPairs.join('^');
1449
+ valuesInput.value = { schemaless: false, schemalessValue: '', value: packedValues };
1450
+ steps.values_transform = { packed: packedValues, fieldCount: fieldPairs.length };
1451
+ }
1452
+ }
1453
+
1454
+ // ── 4. Build labelCache entries for data pills ────────────────────
1455
+ if (dataPillBase && usedInstances.length > 0) {
1456
+ var tableRef = triggerInfo.tableRef || triggerInfo.table || '';
1457
+ var tableLabel = triggerInfo.tableLabel || '';
1458
+
1459
+ // Record-level data pill entry
1460
+ labelCacheEntries.push({
1461
+ name: dataPillBase,
1462
+ label: 'Trigger - Record ' + triggerInfo.triggerName + '\u27a1' + tableLabel + ' Record',
1463
+ reference: tableRef,
1464
+ reference_display: tableLabel,
1465
+ type: 'reference',
1466
+ base_type: 'reference',
1467
+ attributes: '',
1468
+ usedInstances: usedInstances,
1469
+ choices: {}
1470
+ });
1471
+
1472
+ steps.label_cache = { count: labelCacheEntries.length, pills: [dataPillBase], usedInstances: usedInstances.length };
1473
+ }
1474
+
1475
+ return { inputs: actionInputs, labelCacheEntries, steps };
1476
+ }
1477
+
1043
1478
  // ── FLOW LOGIC (If/Else, For Each, etc.) ─────────────────────────────
1044
1479
 
1045
1480
  async function addFlowLogicViaGraphQL(
@@ -1112,11 +1547,37 @@ async function addFlowLogicViaGraphQL(
1112
1547
  steps.resolved_inputs = inputResult.resolvedInputs;
1113
1548
  steps.input_query_stats = { defParamsFound: inputResult.defParamsCount, inputsBuilt: inputResult.inputs.length, error: inputResult.inputQueryError };
1114
1549
 
1550
+ // ── Data pill transformation for condition inputs ───────────────────
1551
+ // Flow Designer conditions require data pill references ({{TriggerName_1.current.field}})
1552
+ // instead of bare field names. Transform encoded query conditions and build labelCache.
1553
+ const uuid = generateUUID();
1554
+ var labelCacheEntries: any[] = [];
1555
+
1556
+ var conditionInput = inputResult.inputs.find(function (inp: any) { return inp.name === 'condition'; });
1557
+ var rawCondition = conditionInput?.value?.value || '';
1558
+ if (rawCondition && rawCondition !== '^EQ') {
1559
+ var triggerInfo = await getFlowTriggerInfo(client, flowId);
1560
+ steps.trigger_info = { dataPillBase: triggerInfo.dataPillBase, triggerName: triggerInfo.triggerName, table: triggerInfo.table, tableLabel: triggerInfo.tableLabel, error: triggerInfo.error };
1561
+
1562
+ if (triggerInfo.dataPillBase) {
1563
+ // Transform condition value: "category=inquiry" → "{{Created or Updated_1.current.category}}=inquiry"
1564
+ var transformedCondition = transformConditionToDataPills(rawCondition, triggerInfo.dataPillBase);
1565
+ conditionInput.value = { schemaless: false, schemalessValue: '', value: transformedCondition };
1566
+ steps.condition_transform = { original: rawCondition, transformed: transformedCondition };
1567
+
1568
+ // Build labelCache entries for each data pill used in the condition
1569
+ labelCacheEntries = buildConditionLabelCache(
1570
+ rawCondition, triggerInfo.dataPillBase, triggerInfo.triggerName,
1571
+ triggerInfo.tableRef, triggerInfo.tableLabel, uuid
1572
+ );
1573
+ steps.label_cache = { count: labelCacheEntries.length, pills: labelCacheEntries.map(function (e: any) { return e.name; }) };
1574
+ }
1575
+ }
1576
+
1115
1577
  // Calculate insertion order
1116
1578
  const resolvedOrder = await calculateInsertOrder(client, flowId, parentUiId, order);
1117
1579
  steps.insert_order = resolvedOrder;
1118
1580
 
1119
- const uuid = generateUUID();
1120
1581
  const logicResponseFields = 'flowLogics { inserts { sysId uiUniqueIdentifier __typename } updates deletes __typename }' +
1121
1582
  ' actions { inserts { sysId uiUniqueIdentifier __typename } updates deletes __typename }' +
1122
1583
  ' subflows { inserts { sysId uiUniqueIdentifier __typename } updates deletes __typename }';
@@ -1147,6 +1608,11 @@ async function addFlowLogicViaGraphQL(
1147
1608
  }
1148
1609
  };
1149
1610
 
1611
+ // Add labelCache entries for data pill references in conditions
1612
+ if (labelCacheEntries.length > 0) {
1613
+ flowPatch.labelCache = { insert: labelCacheEntries };
1614
+ }
1615
+
1150
1616
  // Add parent flow logic update signal (tells GraphQL the parent was modified)
1151
1617
  if (parentUiId) {
1152
1618
  flowPatch.flowLogics.update = [{ uiUniqueIdentifier: parentUiId, type: 'flowlogic' }];