snow-flow 10.0.1-dev.451 → 10.0.1-dev.453

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.451",
3
+ "version": "10.0.1-dev.453",
4
4
  "name": "snow-flow",
5
5
  "description": "Snow-Flow - ServiceNow Multi-Agent Development Framework powered by AI",
6
6
  "license": "Elastic-2.0",
@@ -72,6 +72,10 @@ async function executeFlowPatchMutation(
72
72
  return resp.data?.data?.global?.snFlowDesigner?.flow || resp.data;
73
73
  }
74
74
 
75
+ /** Safely extract a string from a ServiceNow Table API value (handles reference objects like {value, link}). */
76
+ const str = (val: any): string =>
77
+ typeof val === 'object' && val !== null ? (val.display_value || val.value || '') : (val || '');
78
+
75
79
  // Type label mapping for parameter definitions
76
80
  const TYPE_LABELS: Record<string, string> = {
77
81
  string: 'String', integer: 'Integer', boolean: 'True/False', choice: 'Choice',
@@ -106,56 +110,59 @@ async function buildActionInputsForInsert(
106
110
  // Fuzzy-match user-provided values to actual field names
107
111
  var resolvedInputs: Record<string, string> = {};
108
112
  if (userValues) {
109
- var paramElements = actionParams.map(function (p: any) { return p.element; });
113
+ var paramElements = actionParams.map(function (p: any) { return str(p.element); });
110
114
  for (var [key, value] of Object.entries(userValues)) {
111
115
  if (paramElements.includes(key)) {
112
116
  resolvedInputs[key] = value;
113
117
  continue;
114
118
  }
115
119
  var match = actionParams.find(function (p: any) {
116
- return p.element.endsWith('_' + key) || p.element === key || (p.label || '').toLowerCase() === key.toLowerCase();
120
+ var el = str(p.element);
121
+ return el.endsWith('_' + key) || el === key || str(p.label).toLowerCase() === key.toLowerCase();
117
122
  });
118
- if (match) resolvedInputs[match.element] = value;
123
+ if (match) resolvedInputs[str(match.element)] = value;
119
124
  else resolvedInputs[key] = value;
120
125
  }
121
126
  }
122
127
 
123
128
  // Build full input objects with parameter definitions (matching UI format)
129
+ // Use str() on all fields — the Table API may return reference fields as objects {value, link}
124
130
  var inputs = actionParams.map(function (rec: any) {
125
- var paramType = rec.internal_type || 'string';
126
- var userVal = resolvedInputs[rec.element] || '';
131
+ var paramType = str(rec.internal_type) || 'string';
132
+ var element = str(rec.element);
133
+ var userVal = resolvedInputs[element] || '';
127
134
  return {
128
- id: rec.sys_id,
129
- name: rec.element,
135
+ id: str(rec.sys_id),
136
+ name: element,
130
137
  children: [],
131
138
  displayValue: { value: '' },
132
139
  value: { schemaless: false, schemalessValue: '', value: userVal },
133
140
  parameter: {
134
- id: rec.sys_id,
135
- label: rec.label || rec.element,
136
- name: rec.element,
141
+ id: str(rec.sys_id),
142
+ label: str(rec.label) || element,
143
+ name: element,
137
144
  type: paramType,
138
145
  type_label: TYPE_LABELS[paramType] || paramType.charAt(0).toUpperCase() + paramType.slice(1),
139
- hint: rec.hint || '',
140
- order: parseInt(rec.order || '0', 10),
141
- extended: rec.extended === 'true',
142
- mandatory: rec.mandatory === 'true',
143
- readonly: rec.read_only === 'true',
144
- maxsize: parseInt(rec.max_length || '8000', 10),
145
- data_structure: rec.data_structure || '',
146
- reference: rec.reference || '',
147
- reference_display: rec.reference_display || '',
148
- ref_qual: rec.ref_qual || '',
149
- choiceOption: rec.choice_option || '',
150
- table: rec.table_name || '',
151
- columnName: rec.column_name || '',
152
- defaultValue: rec.default_value || '',
153
- use_dependent: rec.use_dependent === 'true',
154
- dependent_on: rec.dependent_on || '',
155
- show_ref_finder: rec.show_ref_finder === 'true',
156
- local: rec.local === 'true',
157
- attributes: rec.attributes || '',
158
- sys_class_name: rec.sys_class_name || '',
146
+ hint: str(rec.hint),
147
+ order: parseInt(str(rec.order) || '0', 10),
148
+ extended: str(rec.extended) === 'true',
149
+ mandatory: str(rec.mandatory) === 'true',
150
+ readonly: str(rec.read_only) === 'true',
151
+ maxsize: parseInt(str(rec.max_length) || '8000', 10),
152
+ data_structure: str(rec.data_structure),
153
+ reference: str(rec.reference),
154
+ reference_display: str(rec.reference_display),
155
+ ref_qual: str(rec.ref_qual),
156
+ choiceOption: str(rec.choice_option),
157
+ table: str(rec.table_name),
158
+ columnName: str(rec.column_name),
159
+ defaultValue: str(rec.default_value),
160
+ use_dependent: str(rec.use_dependent) === 'true',
161
+ dependent_on: str(rec.dependent_on),
162
+ show_ref_finder: str(rec.show_ref_finder) === 'true',
163
+ local: str(rec.local) === 'true',
164
+ attributes: str(rec.attributes),
165
+ sys_class_name: str(rec.sys_class_name),
159
166
  children: []
160
167
  }
161
168
  };
@@ -188,8 +195,8 @@ async function findElementsToReorder(
188
195
  }
189
196
  });
190
197
  for (var rec of (resp.data.result || [])) {
191
- var uuid = rec.ui_unique_identifier;
192
- var curOrder = parseInt(rec.order || '0', 10);
198
+ var uuid = str(rec.ui_unique_identifier);
199
+ var curOrder = parseInt(str(rec.order) || '0', 10);
193
200
  if (uuid && curOrder >= targetOrder) {
194
201
  flowLogicUpdates.push({ order: String(curOrder + 1), uiUniqueIdentifier: uuid, type: 'flowlogic' });
195
202
  }
@@ -206,8 +213,8 @@ async function findElementsToReorder(
206
213
  }
207
214
  });
208
215
  for (var rec2 of (resp2.data.result || [])) {
209
- var uuid2 = rec2.ui_unique_identifier;
210
- var curOrder2 = parseInt(rec2.order || '0', 10);
216
+ var uuid2 = str(rec2.ui_unique_identifier);
217
+ var curOrder2 = parseInt(str(rec2.order) || '0', 10);
211
218
  if (uuid2 && curOrder2 >= targetOrder) {
212
219
  actionUpdates.push({ order: String(curOrder2 + 1), uiUniqueIdentifier: uuid2, type: 'action' });
213
220
  }
@@ -224,8 +231,8 @@ async function findElementsToReorder(
224
231
  }
225
232
  });
226
233
  for (var rec3 of (resp3.data.result || [])) {
227
- var uuid3 = rec3.ui_unique_identifier;
228
- var curOrder3 = parseInt(rec3.order || '0', 10);
234
+ var uuid3 = str(rec3.ui_unique_identifier);
235
+ var curOrder3 = parseInt(str(rec3.order) || '0', 10);
229
236
  if (uuid3 && curOrder3 >= targetOrder) {
230
237
  subflowUpdates.push({ order: String(curOrder3 + 1), uiUniqueIdentifier: uuid3, type: 'subflow' });
231
238
  }
@@ -247,62 +254,78 @@ async function calculateInsertOrder(
247
254
  ): Promise<{ insertOrder: number; reorders: { flowLogicUpdates: any[]; actionUpdates: any[]; subflowUpdates: any[] } }> {
248
255
  var noReorders = { flowLogicUpdates: [], actionUpdates: [], subflowUpdates: [] };
249
256
 
250
- // Explicit order: bump elements at that position
251
- if (explicitOrder) {
252
- var reorders = await findElementsToReorder(client, flowId, explicitOrder);
253
- return { insertOrder: explicitOrder, reorders };
254
- }
255
-
256
- // No parent: append at end, no bumping needed
257
- if (!parentUiId) {
258
- var nextOrder = await getNextOrder(client, flowId);
259
- return { insertOrder: nextOrder, reorders: noReorders };
260
- }
261
-
262
- // Parent specified: find parent's order, then find max child order
263
- var parentSysId = '';
264
- var parentOrder = 0;
265
- try {
266
- var pResp = await client.get('/api/now/table/sys_hub_flow_logic', {
267
- params: {
268
- sysparm_query: 'flow=' + flowId + '^ui_unique_identifier=' + parentUiId,
269
- sysparm_fields: 'sys_id,order',
270
- sysparm_limit: 1
271
- }
272
- });
273
- var found = pResp.data.result?.[0];
274
- if (found) {
275
- parentSysId = found.sys_id;
276
- parentOrder = parseInt(found.order || '0', 10);
277
- }
278
- } catch (_) {}
279
-
280
- if (!parentSysId) {
281
- // Fallback: append at end
282
- var fallbackOrder = await getNextOrder(client, flowId);
283
- return { insertOrder: fallbackOrder, reorders: noReorders };
284
- }
285
-
286
- // Find max order of existing children of this parent
287
- var maxChildOrder = parentOrder;
288
- for (var table of ['sys_hub_action_instance', 'sys_hub_flow_logic', 'sys_hub_sub_flow_instance']) {
257
+ // ── Parent specified: ALWAYS compute global order from parent context ──
258
+ // Flow Designer uses GLOBAL ordering for ALL elements (not per-parent).
259
+ // When a parent is specified, any explicit "order" is ignored because callers
260
+ // typically pass a local/relative order (1, 2, 3) which would conflict with
261
+ // elements already at those global positions.
262
+ if (parentUiId) {
263
+ var parentSysId = '';
264
+ var parentOrder = 0;
289
265
  try {
290
- var cResp = await client.get('/api/now/table/' + table, {
266
+ var pResp = await client.get('/api/now/table/sys_hub_flow_logic', {
291
267
  params: {
292
- sysparm_query: 'flow=' + flowId + '^parent=' + parentSysId + '^ORDERBYDESCorder',
293
- sysparm_fields: 'order',
268
+ sysparm_query: 'flow=' + flowId + '^ui_unique_identifier=' + parentUiId,
269
+ sysparm_fields: 'sys_id,order',
294
270
  sysparm_limit: 1
295
271
  }
296
272
  });
297
- var childOrder = parseInt(cResp.data.result?.[0]?.order || '0', 10);
298
- if (childOrder > maxChildOrder) maxChildOrder = childOrder;
273
+ var found = pResp.data.result?.[0];
274
+ if (found) {
275
+ parentSysId = str(found.sys_id);
276
+ parentOrder = parseInt(str(found.order) || '0', 10);
277
+ }
299
278
  } catch (_) {}
279
+
280
+ if (!parentSysId) {
281
+ // Fallback: append at end
282
+ var fallbackOrder = await getNextOrder(client, flowId);
283
+ return { insertOrder: fallbackOrder, reorders: noReorders };
284
+ }
285
+
286
+ // Find max order of existing children of this parent (query by both sys_id and UUID)
287
+ var maxChildOrder = parentOrder;
288
+ for (var table of ['sys_hub_action_instance', 'sys_hub_flow_logic', 'sys_hub_sub_flow_instance']) {
289
+ try {
290
+ var cResp = await client.get('/api/now/table/' + table, {
291
+ params: {
292
+ sysparm_query: 'flow=' + flowId + '^parent=' + parentSysId + '^ORDERBYDESCorder',
293
+ sysparm_fields: 'order',
294
+ sysparm_limit: 1
295
+ }
296
+ });
297
+ var childOrder = parseInt(str(cResp.data.result?.[0]?.order) || '0', 10);
298
+ if (childOrder > maxChildOrder) maxChildOrder = childOrder;
299
+ } catch (_) {}
300
+ // Also try querying by parent UUID (GraphQL may store UUID in parent field)
301
+ try {
302
+ var cResp2 = await client.get('/api/now/table/' + table, {
303
+ params: {
304
+ sysparm_query: 'flow=' + flowId + '^parent=' + parentUiId + '^ORDERBYDESCorder',
305
+ sysparm_fields: 'order',
306
+ sysparm_limit: 1
307
+ }
308
+ });
309
+ var childOrder2 = parseInt(str(cResp2.data.result?.[0]?.order) || '0', 10);
310
+ if (childOrder2 > maxChildOrder) maxChildOrder = childOrder2;
311
+ } catch (_) {}
312
+ }
313
+
314
+ // Insert after last child; bump everything at that position
315
+ var insertOrder = maxChildOrder + 1;
316
+ var reorderInfo = await findElementsToReorder(client, flowId, insertOrder);
317
+ return { insertOrder: insertOrder, reorders: reorderInfo };
318
+ }
319
+
320
+ // ── Top-level with explicit order: bump elements at that position ──
321
+ if (explicitOrder) {
322
+ var reorders = await findElementsToReorder(client, flowId, explicitOrder);
323
+ return { insertOrder: explicitOrder, reorders };
300
324
  }
301
325
 
302
- // Insert after last child; bump everything at that position
303
- var insertOrder = maxChildOrder + 1;
304
- var reorderInfo = await findElementsToReorder(client, flowId, insertOrder);
305
- return { insertOrder: insertOrder, reorders: reorderInfo };
326
+ // ── Top-level without order: append at end ──
327
+ var nextOrder = await getNextOrder(client, flowId);
328
+ return { insertOrder: nextOrder, reorders: noReorders };
306
329
  }
307
330
 
308
331
  async function addTriggerViaGraphQL(
@@ -326,9 +349,6 @@ async function addTriggerViaGraphQL(
326
349
  else if (triggerType.endsWith('e')) variations.push(triggerType + 'd');
327
350
  else variations.push(triggerType + 'ed', triggerType + 'd');
328
351
 
329
- // Resolve reference fields that may be objects {display_value, link} or plain strings
330
- const str = (val: any) => typeof val === 'object' && val !== null ? (val.display_value || val.value || '') : (val || '');
331
-
332
352
  const assignFound = (found: any, matched: string) => {
333
353
  trigDefId = found.sys_id;
334
354
  trigName = str(found.name) || triggerType;
@@ -625,7 +645,7 @@ async function addFlowLogicViaGraphQL(
625
645
  insert: [{
626
646
  order: String(resolvedOrder),
627
647
  uiUniqueIdentifier: uuid,
628
- parent: '',
648
+ parent: parentUiId || '',
629
649
  metadata: '{"predicates":[]}',
630
650
  flowSysId: flowId,
631
651
  generationSource: '',
@@ -637,10 +657,18 @@ async function addFlowLogicViaGraphQL(
637
657
  }
638
658
  };
639
659
 
640
- // Merge reorder updates into flowLogics.update
641
- if (orderCalc.reorders.flowLogicUpdates.length > 0) {
660
+ // Merge reorder updates + parent update into flowLogics.update
661
+ var flowLogicUpdatesForPatch: any[] = orderCalc.reorders.flowLogicUpdates.slice();
662
+ if (parentUiId) {
663
+ // Signal the parent flow logic was modified (same as action insert does)
664
+ var parentAlreadyInList = flowLogicUpdatesForPatch.some(function (u: any) { return u.uiUniqueIdentifier === parentUiId; });
665
+ if (!parentAlreadyInList) {
666
+ flowLogicUpdatesForPatch.push({ uiUniqueIdentifier: parentUiId, type: 'flowlogic' });
667
+ }
668
+ }
669
+ if (flowLogicUpdatesForPatch.length > 0) {
642
670
  if (!flowPatch.flowLogics.update) flowPatch.flowLogics.update = [];
643
- flowPatch.flowLogics.update = flowPatch.flowLogics.update.concat(orderCalc.reorders.flowLogicUpdates);
671
+ flowPatch.flowLogics.update = flowPatch.flowLogics.update.concat(flowLogicUpdatesForPatch);
644
672
  }
645
673
  if (orderCalc.reorders.actionUpdates.length > 0) {
646
674
  flowPatch.actions = { update: orderCalc.reorders.actionUpdates };