snow-flow 10.0.50 → 10.0.51

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.50",
3
+ "version": "10.0.51",
4
4
  "name": "snow-flow",
5
5
  "description": "Snow-Flow - ServiceNow Multi-Agent Development Framework powered by AI",
6
6
  "license": "Elastic-2.0",
@@ -86,6 +86,40 @@ function validateAndFixPills(value: string): string {
86
86
  return result;
87
87
  }
88
88
 
89
+ /** Canonical list of shorthand prefixes that reference the trigger's current record.
90
+ * Single source of truth — used by rewriteShorthandPills() and hasShorthandPills(). */
91
+ const PILL_SHORTHANDS = ['trigger.current', 'current', 'trigger_record', 'trigger record', 'trigger.record', 'record'];
92
+
93
+ /**
94
+ * Rewrite shorthand pill references to use the canonical data pill base.
95
+ * e.g. "{{trigger.current.priority}}" → "{{Created or Updated_1.current.priority}}"
96
+ * "{{current}}" → "{{Created or Updated_1.current}}"
97
+ * Also normalizes bare trigger.record → trigger.current for non-pill values.
98
+ */
99
+ function rewriteShorthandPills(value: string, dataPillBase: string): string {
100
+ if (!value || !dataPillBase) return value;
101
+ // Normalize bare trigger.record/trigger_record → trigger.current (non-pill values only)
102
+ if (!value.includes('{{')) {
103
+ value = value.replace(/\btrigger\.record\./g, 'trigger.current.');
104
+ value = value.replace(/\btrigger_record\./g, 'trigger.current.');
105
+ }
106
+ // Rewrite pill-wrapped shorthand references
107
+ for (var i = 0; i < PILL_SHORTHANDS.length; i++) {
108
+ var sh = PILL_SHORTHANDS[i];
109
+ value = value.split('{{' + sh + '.').join('{{' + dataPillBase + '.');
110
+ value = value.split('{{' + sh + '}}').join('{{' + dataPillBase + '}}');
111
+ }
112
+ return value;
113
+ }
114
+
115
+ /** Check if a string contains any shorthand pill references that need rewriting. */
116
+ function hasShorthandPills(value: string): boolean {
117
+ if (!value || !value.includes('{{')) return false;
118
+ return PILL_SHORTHANDS.some(function (sh) {
119
+ return value.includes('{{' + sh + '.') || value.includes('{{' + sh + '}}');
120
+ });
121
+ }
122
+
89
123
  // ── GraphQL Flow Designer helpers ─────────────────────────────────────
90
124
 
91
125
  function jsToGraphQL(val: any): string {
@@ -333,6 +367,20 @@ function deduplicateLabelCache(entries: any[]): any[] {
333
367
  return Object.values(seen);
334
368
  }
335
369
 
370
+ /** Build a standardized labelCache entry. All pill entries should use this factory to ensure consistent shape. */
371
+ function buildLabelCacheEntry(opts: {
372
+ name: string; label: string; reference: string; reference_display: string;
373
+ type: string; base_type: string; parent_table_name: string; column_name: string;
374
+ usedInstances: { uiUniqueIdentifier: string; inputName: string }[];
375
+ }): any {
376
+ return {
377
+ name: opts.name, label: opts.label, reference: opts.reference,
378
+ reference_display: opts.reference_display, type: opts.type, base_type: opts.base_type,
379
+ parent_table_name: opts.parent_table_name, column_name: opts.column_name,
380
+ usedInstances: opts.usedInstances, choices: {}
381
+ };
382
+ }
383
+
336
384
  /**
337
385
  * Fetch the existing labelCache pills from the flow's processflow data.
338
386
  * Returns a map: pill name → existing usedInstances[].
@@ -1561,7 +1609,6 @@ async function addActionViaGraphQL(
1561
1609
  // {{trigger.current.X}} need rewriting to {{Created or Updated_1.current.X}} + labelCache.
1562
1610
  // Also handle inputs that already have full pill references (e.g. {{Created or Updated_1.current.caller_id}})
1563
1611
  // — these still need labelCache entries or they render as empty grey pills.
1564
- var PILL_SHORTHANDS_ACTION = ['trigger.current', 'current', 'trigger_record', 'trigger.record'];
1565
1612
  var RECORD_INPUTS = ['record', 'table_name', 'values'];
1566
1613
  var genericPillInputs: { name: string; fields: string[]; isRecordLevel: boolean }[] = [];
1567
1614
  var actionTriggerInfo: any = null;
@@ -1572,9 +1619,7 @@ async function addActionViaGraphQL(
1572
1619
  var gpVal = validateAndFixPills(gpInput.value?.value || '');
1573
1620
  if (!gpVal.includes('{{')) continue;
1574
1621
 
1575
- var gpHasShorthand = PILL_SHORTHANDS_ACTION.some(function (sh) {
1576
- return gpVal.includes('{{' + sh + '.') || gpVal.includes('{{' + sh + '}}');
1577
- });
1622
+ var gpHasShorthand = hasShorthandPills(gpVal);
1578
1623
 
1579
1624
  // If it has shorthand pills, we need trigger info to rewrite them.
1580
1625
  // If it has ANY pill references (even non-shorthand), we still need trigger info for labelCache.
@@ -1595,11 +1640,7 @@ async function addActionViaGraphQL(
1595
1640
 
1596
1641
  // Rewrite shorthand pills to full dataPillBase
1597
1642
  if (gpHasShorthand) {
1598
- for (var gsi = 0; gsi < PILL_SHORTHANDS_ACTION.length; gsi++) {
1599
- var gsh = PILL_SHORTHANDS_ACTION[gsi];
1600
- gpVal = gpVal.split('{{' + gsh + '.').join('{{' + gpPillBase + '.');
1601
- gpVal = gpVal.split('{{' + gsh + '}}').join('{{' + gpPillBase + '}}');
1602
- }
1643
+ gpVal = rewriteShorthandPills(gpVal, gpPillBase);
1603
1644
  gpInput.value.value = gpVal;
1604
1645
  }
1605
1646
 
@@ -1765,19 +1806,14 @@ async function addActionViaGraphQL(
1765
1806
 
1766
1807
  // Record-level pill
1767
1808
  if (gpi2.isRecordLevel) {
1768
- gpLabelInserts.push({
1809
+ gpLabelInserts.push(buildLabelCacheEntry({
1769
1810
  name: gpBase,
1770
1811
  label: 'Trigger - Record ' + gpTrigName + '\u279b' + gpTableLabel + ' Record\u279b' + gpTableLabel,
1771
- reference: gpTable,
1772
- reference_display: gpTableLabel,
1773
- type: 'reference',
1774
- base_type: 'reference',
1775
- parent_table_name: gpTable,
1776
- column_name: '',
1777
- attributes: '',
1778
- choices: {},
1812
+ reference: gpTable, reference_display: gpTableLabel,
1813
+ type: 'reference', base_type: 'reference',
1814
+ parent_table_name: gpTable, column_name: '',
1779
1815
  usedInstances: [{ uiUniqueIdentifier: uuid, inputName: gpi2.name }]
1780
- });
1816
+ }));
1781
1817
  }
1782
1818
  }
1783
1819
 
@@ -2224,18 +2260,14 @@ async function buildConditionLabelCache(
2224
2260
  var pillName = dataPillBase + '.' + f;
2225
2261
  var meta = fieldMeta[f] || { type: 'string', label: f.replace(/_/g, ' ').replace(/\b\w/g, function (c: string) { return c.toUpperCase(); }), reference: '' };
2226
2262
 
2227
- inserts.push({
2263
+ inserts.push(buildLabelCacheEntry({
2228
2264
  name: pillName,
2229
2265
  label: 'Trigger - Record ' + triggerName + '\u279b' + tableLabel + ' Record\u279b' + meta.label,
2230
- reference: meta.reference,
2231
- reference_display: meta.label,
2232
- type: meta.type,
2233
- base_type: meta.type,
2234
- parent_table_name: table,
2235
- column_name: f,
2236
- usedInstances: [{ uiUniqueIdentifier: logicUiId, inputName: inputName || 'condition' }],
2237
- choices: {}
2238
- });
2266
+ reference: meta.reference, reference_display: meta.label,
2267
+ type: meta.type, base_type: meta.type,
2268
+ parent_table_name: table, column_name: f,
2269
+ usedInstances: [{ uiUniqueIdentifier: logicUiId, inputName: inputName || 'condition' }]
2270
+ }));
2239
2271
  }
2240
2272
 
2241
2273
  // Dot-walk fields — resolve each segment through reference chain
@@ -2279,18 +2311,14 @@ async function buildConditionLabelCache(
2279
2311
  }
2280
2312
 
2281
2313
  var dwLabel = labelParts.join('\u279b');
2282
- inserts.push({
2314
+ inserts.push(buildLabelCacheEntry({
2283
2315
  name: dwPillName,
2284
2316
  label: 'Trigger - Record ' + triggerName + '\u279b' + tableLabel + ' Record\u279b' + dwLabel,
2285
- reference: dwMeta.reference,
2286
- reference_display: dwMeta.label || segments[segments.length - 1],
2287
- type: dwMeta.type,
2288
- base_type: dwMeta.type,
2289
- parent_table_name: currentTbl,
2290
- column_name: segments[segments.length - 1],
2291
- usedInstances: [{ uiUniqueIdentifier: logicUiId, inputName: inputName || 'condition' }],
2292
- choices: {}
2293
- });
2317
+ reference: dwMeta.reference, reference_display: dwMeta.label || segments[segments.length - 1],
2318
+ type: dwMeta.type, base_type: dwMeta.type,
2319
+ parent_table_name: currentTbl, column_name: segments[segments.length - 1],
2320
+ usedInstances: [{ uiUniqueIdentifier: logicUiId, inputName: inputName || 'condition' }]
2321
+ }));
2294
2322
  }
2295
2323
 
2296
2324
  return inserts;
@@ -2298,9 +2326,6 @@ async function buildConditionLabelCache(
2298
2326
 
2299
2327
  // ── DATA PILL SUPPORT FOR RECORD ACTIONS (Update/Create Record) ──────
2300
2328
 
2301
- /** Shorthands that users can pass for `record` to mean "the trigger's current record". */
2302
- const RECORD_PILL_SHORTHANDS = ['current', 'trigger.current', 'trigger_record', 'trigger record', 'trigger.record', 'record'];
2303
-
2304
2329
  /**
2305
2330
  * Post-process action inputs for record-modifying actions (Update Record, Create Record).
2306
2331
  *
@@ -2355,7 +2380,7 @@ async function transformActionInputsForRecordAction(
2355
2380
  var recordInput = actionInputs.find(function (inp: any) { return inp.name === 'record'; });
2356
2381
  if (recordInput && dataPillBase) {
2357
2382
  var recordVal = validateAndFixPills(recordInput.value?.value || '');
2358
- var isShorthand = RECORD_PILL_SHORTHANDS.includes(recordVal.toLowerCase());
2383
+ var isShorthand = PILL_SHORTHANDS.includes(recordVal.toLowerCase());
2359
2384
  var isAlreadyPill = recordVal.startsWith('{{');
2360
2385
 
2361
2386
  if (isShorthand || !recordVal) {
@@ -2369,7 +2394,7 @@ async function transformActionInputsForRecordAction(
2369
2394
  } else if (isAlreadyPill) {
2370
2395
  // Check if the pill contains a shorthand that needs rewriting to the full dataPillBase
2371
2396
  var innerVal = recordVal.replace(/^\{\{/, '').replace(/\}\}$/, '').trim();
2372
- if (RECORD_PILL_SHORTHANDS.includes(innerVal.toLowerCase())) {
2397
+ if (PILL_SHORTHANDS.includes(innerVal.toLowerCase())) {
2373
2398
  var pillRef2 = '{{' + dataPillBase + '}}';
2374
2399
  recordInput.value = { schemaless: false, schemalessValue: '', value: pillRef2 };
2375
2400
  steps.record_transform = { original: recordVal, pill: pillRef2 };
@@ -2435,7 +2460,7 @@ async function transformActionInputsForRecordAction(
2435
2460
  // Check if value should be a data pill reference
2436
2461
  if (val && dataPillBase) {
2437
2462
  var valLower = val.toLowerCase();
2438
- if (RECORD_PILL_SHORTHANDS.includes(valLower)) {
2463
+ if (PILL_SHORTHANDS.includes(valLower)) {
2439
2464
  // Shorthand → record-level data pill
2440
2465
  val = '{{' + dataPillBase + '}}';
2441
2466
  usedInstances.push({ uiUniqueIdentifier: uuid, inputName: key });
@@ -2444,21 +2469,9 @@ async function transformActionInputsForRecordAction(
2444
2469
  var fieldName = valLower.startsWith('trigger.current.') ? val.substring(16) : val.substring(8);
2445
2470
  val = '{{' + dataPillBase + '.' + fieldName + '}}';
2446
2471
  usedInstances.push({ uiUniqueIdentifier: uuid, inputName: key });
2447
- } else if (val.startsWith('{{')) {
2448
- // Already a data pill — rewrite shorthands inside if needed
2449
- var PILL_SH = ['trigger.current', 'current', 'trigger_record', 'trigger.record'];
2450
- for (var psi = 0; psi < PILL_SH.length; psi++) {
2451
- val = val.split('{{' + PILL_SH[psi] + '.').join('{{' + dataPillBase + '.');
2452
- val = val.split('{{' + PILL_SH[psi] + '}}').join('{{' + dataPillBase + '}}');
2453
- }
2454
- usedInstances.push({ uiUniqueIdentifier: uuid, inputName: key });
2455
- } else if (val.includes('{{')) {
2456
- // Inline pills mixed with text: "Emergency Change: {{trigger.current.number}}"
2457
- var INLINE_SH = ['trigger.current', 'current', 'trigger_record', 'trigger.record'];
2458
- for (var isi = 0; isi < INLINE_SH.length; isi++) {
2459
- val = val.split('{{' + INLINE_SH[isi] + '.').join('{{' + dataPillBase + '.');
2460
- val = val.split('{{' + INLINE_SH[isi] + '}}').join('{{' + dataPillBase + '}}');
2461
- }
2472
+ } else if (val.startsWith('{{') || val.includes('{{')) {
2473
+ // Already a data pill or inline pills — rewrite shorthands
2474
+ val = rewriteShorthandPills(val, dataPillBase);
2462
2475
  usedInstances.push({ uiUniqueIdentifier: uuid, inputName: key });
2463
2476
  }
2464
2477
  }
@@ -2477,12 +2490,8 @@ async function transformActionInputsForRecordAction(
2477
2490
  if (dataPillBase) {
2478
2491
  var vStr = valuesInput.value?.value || '';
2479
2492
  if (vStr.includes('{{')) {
2480
- var VALS_SH = ['trigger.current', 'current', 'trigger_record', 'trigger.record'];
2481
2493
  var vOriginal = vStr;
2482
- for (var vshi = 0; vshi < VALS_SH.length; vshi++) {
2483
- vStr = vStr.split('{{' + VALS_SH[vshi] + '.').join('{{' + dataPillBase + '.');
2484
- vStr = vStr.split('{{' + VALS_SH[vshi] + '}}').join('{{' + dataPillBase + '}}');
2485
- }
2494
+ vStr = rewriteShorthandPills(vStr, dataPillBase);
2486
2495
  if (vStr !== vOriginal) {
2487
2496
  valuesInput.value.value = vStr;
2488
2497
  steps.values_pill_rewrite = { original: vOriginal, rewritten: vStr };
@@ -2507,18 +2516,14 @@ async function transformActionInputsForRecordAction(
2507
2516
  // { name: "Created or Updated_1.current", type: "reference", reference: "incident", reference_display: "Incident", ... }
2508
2517
  // UI only puts inputName: "record" entries on the record pill, NOT field-level pill usages
2509
2518
  var recordUsedInstances = usedInstances.filter(function (ui) { return ui.inputName === 'record'; });
2510
- labelCacheInserts.push({
2519
+ labelCacheInserts.push(buildLabelCacheEntry({
2511
2520
  name: dataPillBase,
2512
2521
  label: 'Trigger - Record ' + triggerInfo.triggerName + '\u279b' + tblLabel + ' Record',
2513
- reference: tableRef,
2514
- reference_display: tblLabel,
2515
- type: 'reference',
2516
- base_type: 'reference',
2517
- parent_table_name: tableRef,
2518
- column_name: '',
2519
- choices: {},
2522
+ reference: tableRef, reference_display: tblLabel,
2523
+ type: 'reference', base_type: 'reference',
2524
+ parent_table_name: tableRef, column_name: '',
2520
2525
  usedInstances: recordUsedInstances
2521
- });
2526
+ }));
2522
2527
 
2523
2528
  // Field-level data pill entries for any field references in the `values` string → INSERT (new pills)
2524
2529
  // Parse values by ^-separated segments so we can track which TARGET field uses each pill
@@ -2607,18 +2612,14 @@ async function transformActionInputsForRecordAction(
2607
2612
  label: pEntry.fieldCol.replace(/_/g, ' ').replace(/\b\w/g, function (c: string) { return c.toUpperCase(); })
2608
2613
  };
2609
2614
 
2610
- labelCacheInserts.push({
2615
+ labelCacheInserts.push(buildLabelCacheEntry({
2611
2616
  name: pName,
2612
2617
  label: 'Trigger - Record ' + triggerInfo.triggerName + '\u279b' + tblLabel + ' Record\u279b' + fMeta.label,
2613
- reference: '',
2614
- reference_display: fMeta.label,
2615
- type: fMeta.type,
2616
- base_type: fMeta.type,
2617
- parent_table_name: tableRef,
2618
- column_name: pEntry.fieldCol,
2619
- usedInstances: pEntry.usedInstances,
2620
- choices: {}
2621
- });
2618
+ reference: '', reference_display: fMeta.label,
2619
+ type: fMeta.type, base_type: fMeta.type,
2620
+ parent_table_name: tableRef, column_name: pEntry.fieldCol,
2621
+ usedInstances: pEntry.usedInstances
2622
+ }));
2622
2623
  }
2623
2624
  }
2624
2625
 
@@ -2862,15 +2863,12 @@ async function addFlowLogicViaGraphQL(
2862
2863
 
2863
2864
  // Shorthand patterns that need rewriting to the real data pill base
2864
2865
  // e.g. {{trigger.current.category}} → {{Created or Updated_1.current.category}}
2865
- var PILL_SHORTHANDS = ['trigger.current', 'current', 'trigger_record', 'trigger.record'];
2866
- var hasShorthandPills = rawCondition.includes('{{') && PILL_SHORTHANDS.some(function (sh) {
2867
- return rawCondition.includes('{{' + sh + '.') || rawCondition.includes('{{' + sh + '}}');
2868
- });
2866
+ var condHasShorthand = hasShorthandPills(rawCondition);
2869
2867
  // Also detect conditions that already contain full data pill references (non-shorthand)
2870
2868
  // e.g. {{Created or Updated_1.current.priority}}=1 — these still need two-step + labelCache
2871
- var hasFullPillRefs = rawCondition.includes('{{') && !hasShorthandPills;
2869
+ var hasFullPillRefs = rawCondition.includes('{{') && !condHasShorthand;
2872
2870
 
2873
- if (rawCondition && rawCondition !== '^EQ' && (isStandardEncodedQuery(rawCondition) || hasShorthandPills || hasFullPillRefs)) {
2871
+ if (rawCondition && rawCondition !== '^EQ' && (isStandardEncodedQuery(rawCondition) || condHasShorthand || hasFullPillRefs)) {
2874
2872
  conditionTriggerInfo = await getFlowTriggerInfo(client, flowId);
2875
2873
  steps.trigger_info = {
2876
2874
  dataPillBase: conditionTriggerInfo.dataPillBase, triggerName: conditionTriggerInfo.triggerName,
@@ -2881,15 +2879,8 @@ async function addFlowLogicViaGraphQL(
2881
2879
  needsConditionUpdate = true;
2882
2880
 
2883
2881
  // If condition has shorthand pills, rewrite them to real data pill base first
2884
- if (hasShorthandPills) {
2885
- var pillBase = conditionTriggerInfo.dataPillBase;
2886
- for (var si = 0; si < PILL_SHORTHANDS.length; si++) {
2887
- var sh = PILL_SHORTHANDS[si];
2888
- // Replace {{trigger.current.field}} → {{Created or Updated_1.current.field}}
2889
- rawCondition = rawCondition.split('{{' + sh + '.').join('{{' + pillBase + '.');
2890
- // Replace {{trigger.current}} → {{Created or Updated_1.current}}
2891
- rawCondition = rawCondition.split('{{' + sh + '}}').join('{{' + pillBase + '}}');
2892
- }
2882
+ if (condHasShorthand) {
2883
+ rawCondition = rewriteShorthandPills(rawCondition, conditionTriggerInfo.dataPillBase);
2893
2884
  steps.shorthand_rewrite = { original: conditionInput?.value?.value, rewritten: rawCondition };
2894
2885
  }
2895
2886
 
@@ -2914,9 +2905,7 @@ async function addFlowLogicViaGraphQL(
2914
2905
  var ncVal = validateAndFixPills(ncInput.value?.value || '');
2915
2906
  if (!ncVal.includes('{{')) continue;
2916
2907
 
2917
- var ncHasShorthand = PILL_SHORTHANDS.some(function (sh) {
2918
- return ncVal.includes('{{' + sh + '.') || ncVal.includes('{{' + sh + '}}');
2919
- });
2908
+ var ncHasShorthand = hasShorthandPills(ncVal);
2920
2909
 
2921
2910
  // Get trigger info for shorthand rewriting OR for labelCache building (even non-shorthand pills)
2922
2911
  if (!conditionTriggerInfo) {
@@ -2934,11 +2923,7 @@ async function addFlowLogicViaGraphQL(
2934
2923
 
2935
2924
  // Rewrite shorthand pills to full dataPillBase
2936
2925
  if (ncHasShorthand) {
2937
- for (var si2 = 0; si2 < PILL_SHORTHANDS.length; si2++) {
2938
- var sh2 = PILL_SHORTHANDS[si2];
2939
- ncVal = ncVal.split('{{' + sh2 + '.').join('{{' + ncPillBase + '.');
2940
- ncVal = ncVal.split('{{' + sh2 + '}}').join('{{' + ncPillBase + '}}');
2941
- }
2926
+ ncVal = rewriteShorthandPills(ncVal, ncPillBase);
2942
2927
  ncInput.value.value = ncVal;
2943
2928
  }
2944
2929
 
@@ -3113,7 +3098,7 @@ async function addFlowLogicViaGraphQL(
3113
3098
 
3114
3099
  // Record-level pill (e.g. {{Created or Updated_1.current}}) — add record-level labelCache entry
3115
3100
  if (ncpi.isRecordLevel) {
3116
- ncLabelInserts.push({
3101
+ ncLabelInserts.push(buildLabelCacheEntry({
3117
3102
  name: dPillBase,
3118
3103
  label: 'Trigger - Record ' + dTriggerName + '\u279b' + dTableLabel + ' Record\u279b' + dTableLabel,
3119
3104
  reference: dTable,
@@ -3122,10 +3107,8 @@ async function addFlowLogicViaGraphQL(
3122
3107
  base_type: 'reference',
3123
3108
  parent_table_name: dTable,
3124
3109
  column_name: '',
3125
- attributes: '',
3126
- choices: {},
3127
3110
  usedInstances: [{ uiUniqueIdentifier: returnedUuid, inputName: ncpi.name }]
3128
- });
3111
+ }));
3129
3112
  }
3130
3113
  }
3131
3114
 
@@ -4872,6 +4855,16 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
4872
4855
  }
4873
4856
 
4874
4857
  } catch (error: any) {
4858
+ // Safety: release lock on unexpected errors during mutation actions
4859
+ var MUTATION_ACTIONS = [
4860
+ 'add_action', 'add_flow_logic', 'add_subflow',
4861
+ 'update_action', 'update_flow_logic', 'update_subflow',
4862
+ 'delete_action', 'delete_flow_logic', 'delete_subflow', 'delete_trigger'
4863
+ ];
4864
+ if (client && args.flow_id && MUTATION_ACTIONS.indexOf(action) !== -1 && !(error instanceof SnowFlowError)) {
4865
+ try { await releaseFlowEditingLock(client, await resolveFlowId(client, args.flow_id)); } catch (_) {}
4866
+ }
4867
+
4875
4868
  if (error instanceof SnowFlowError) {
4876
4869
  return createErrorResult(error);
4877
4870
  }