snow-flow 10.0.1-dev.460 → 10.0.1-dev.462

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.460",
3
+ "version": "10.0.1-dev.462",
4
4
  "name": "snow-flow",
5
5
  "description": "Snow-Flow - ServiceNow Multi-Agent Development Framework powered by AI",
6
6
  "license": "Elastic-2.0",
@@ -206,20 +206,39 @@ async function buildFlowLogicInputsForInsert(
206
206
  defId: string,
207
207
  defRecord: { name?: string; type?: string; description?: string; order?: string; attributes?: string; compilation_class?: string; quiescence?: string; visible?: string; category?: string; connected_to?: string },
208
208
  userValues?: Record<string, string>
209
- ): Promise<{ inputs: any[]; flowLogicDefinition: any; resolvedInputs: Record<string, string> }> {
209
+ ): Promise<{ inputs: any[]; flowLogicDefinition: any; resolvedInputs: Record<string, string>; inputQueryError?: string; defParamsCount: number }> {
210
210
  // Query sys_hub_flow_logic_input for this definition's inputs (separate table from sys_hub_action_input)
211
+ // Field names verified from actual sys_hub_flow_logic_input XML schema
211
212
  var defParams: any[] = [];
213
+ var inputQueryError = '';
212
214
  try {
213
215
  var resp = await client.get('/api/now/table/sys_hub_flow_logic_input', {
214
216
  params: {
215
217
  sysparm_query: 'model=' + defId,
216
- sysparm_fields: 'sys_id,element,label,internal_type,mandatory,default_value,order,max_length,hint,read_only,extended,data_structure,reference,reference_display,ref_qual,choice_option,table_name,column_name,use_dependent,dependent_on,show_ref_finder,local,attributes,sys_class_name',
218
+ sysparm_fields: 'sys_id,element,label,internal_type,mandatory,default_value,order,max_length,hint,read_only,attributes,sys_class_name,reference,choice,dependent,dependent_on_field,use_dependent_field,column_label',
217
219
  sysparm_display_value: 'false',
218
220
  sysparm_limit: 50
219
221
  }
220
222
  });
221
223
  defParams = resp.data.result || [];
222
- } catch (_) {}
224
+ } catch (e: any) {
225
+ inputQueryError = e.message || 'unknown error';
226
+ // Fallback: try with minimal fields
227
+ try {
228
+ var resp2 = await client.get('/api/now/table/sys_hub_flow_logic_input', {
229
+ params: {
230
+ sysparm_query: 'model=' + defId,
231
+ sysparm_fields: 'sys_id,element,label,internal_type,mandatory,order,max_length,attributes',
232
+ sysparm_display_value: 'false',
233
+ sysparm_limit: 50
234
+ }
235
+ });
236
+ defParams = resp2.data.result || [];
237
+ inputQueryError = '';
238
+ } catch (e2: any) {
239
+ inputQueryError += '; fallback also failed: ' + (e2.message || '');
240
+ }
241
+ }
223
242
 
224
243
  // Fuzzy-match user-provided values to actual field names
225
244
  var resolvedInputs: Record<string, string> = {};
@@ -304,7 +323,7 @@ async function buildFlowLogicInputsForInsert(
304
323
  variables: '[]'
305
324
  };
306
325
 
307
- return { inputs, flowLogicDefinition, resolvedInputs };
326
+ return { inputs, flowLogicDefinition, resolvedInputs, inputQueryError: inputQueryError || undefined, defParamsCount: defParams.length };
308
327
  }
309
328
 
310
329
  // Note: reordering of existing elements is NOT possible via Table API because
@@ -346,6 +365,216 @@ async function calculateInsertOrder(
346
365
  return 1;
347
366
  }
348
367
 
368
+ /**
369
+ * Flatten an attributes object { key: "val" } into comma-separated "key=val," string (matching UI format).
370
+ * If already a string, returns as-is.
371
+ */
372
+ function flattenAttributes(attrs: any): string {
373
+ if (!attrs || typeof attrs === 'string') return attrs || '';
374
+ return Object.entries(attrs).map(([k, v]) => k + '=' + v).join(',') + ',';
375
+ }
376
+
377
+ /**
378
+ * Build full trigger input and output objects for the INSERT mutation by fetching from the
379
+ * triggerpicker API (/api/now/hub/triggerpicker/{id}) — the same endpoint Flow Designer UI uses.
380
+ *
381
+ * The UI sends ALL inputs with full parameter definitions (choices, defaults, attributes) and
382
+ * ALL outputs in a single INSERT mutation. This function replicates that format exactly.
383
+ *
384
+ * Fallback: if the triggerpicker API fails, queries sys_hub_trigger_input / sys_hub_trigger_output
385
+ * via the Table API (same approach as buildActionInputsForInsert / buildFlowLogicInputsForInsert).
386
+ */
387
+ async function buildTriggerInputsForInsert(
388
+ client: any,
389
+ trigDefId: string,
390
+ userTable?: string,
391
+ userCondition?: string
392
+ ): Promise<{ inputs: any[]; outputs: any[]; error?: string }> {
393
+ var apiInputs: any[] = [];
394
+ var apiOutputs: any[] = [];
395
+ var fetchError = '';
396
+
397
+ // Strategy 1: triggerpicker API (primary — same as Flow Designer UI)
398
+ try {
399
+ var tpResp = await client.get('/api/now/hub/triggerpicker/' + trigDefId, {
400
+ params: { sysparm_transaction_scope: 'global' },
401
+ headers: { Accept: 'application/json' }
402
+ });
403
+ var tpData = tpResp.data?.result || tpResp.data;
404
+ if (tpData && typeof tpData === 'object') {
405
+ apiInputs = tpData.inputs || tpData.trigger_inputs || [];
406
+ apiOutputs = tpData.outputs || tpData.trigger_outputs || [];
407
+ }
408
+ } catch (tpErr: any) {
409
+ fetchError = 'triggerpicker: ' + (tpErr.message || 'unknown');
410
+ }
411
+
412
+ // Strategy 2: Table API fallback (query sys_hub_trigger_input / sys_hub_trigger_output)
413
+ if (apiInputs.length === 0) {
414
+ try {
415
+ var tiResp = await client.get('/api/now/table/sys_hub_trigger_input', {
416
+ params: {
417
+ sysparm_query: 'model=' + trigDefId,
418
+ sysparm_fields: 'sys_id,element,label,internal_type,mandatory,default_value,order,max_length,hint,read_only,attributes,reference,reference_display,choice,dependent_on_field,use_dependent_field',
419
+ sysparm_display_value: 'false',
420
+ sysparm_limit: 50
421
+ }
422
+ });
423
+ var tableInputs = tiResp.data.result || [];
424
+ apiInputs = tableInputs.map(function (rec: any) {
425
+ return {
426
+ id: str(rec.sys_id), name: str(rec.element), label: str(rec.label) || str(rec.element),
427
+ type: str(rec.internal_type) || 'string',
428
+ type_label: TYPE_LABELS[str(rec.internal_type) || 'string'] || str(rec.internal_type),
429
+ mandatory: str(rec.mandatory) === 'true',
430
+ order: parseInt(str(rec.order) || '0', 10),
431
+ maxsize: parseInt(str(rec.max_length) || '4000', 10),
432
+ hint: str(rec.hint), defaultValue: str(rec.default_value),
433
+ reference: str(rec.reference), reference_display: str(rec.reference_display),
434
+ use_dependent: str(rec.use_dependent_field) === 'true',
435
+ dependent_on: str(rec.dependent_on_field),
436
+ attributes: str(rec.attributes)
437
+ };
438
+ });
439
+ fetchError = '';
440
+ } catch (tiErr: any) {
441
+ fetchError += '; table_api_inputs: ' + (tiErr.message || 'unknown');
442
+ }
443
+ }
444
+ if (apiOutputs.length === 0) {
445
+ try {
446
+ var toResp = await client.get('/api/now/table/sys_hub_trigger_output', {
447
+ params: {
448
+ sysparm_query: 'model=' + trigDefId,
449
+ sysparm_fields: 'sys_id,element,label,internal_type,mandatory,order,max_length,hint,attributes,reference,reference_display,use_dependent_field,dependent_on_field',
450
+ sysparm_display_value: 'false',
451
+ sysparm_limit: 50
452
+ }
453
+ });
454
+ var tableOutputs = toResp.data.result || [];
455
+ apiOutputs = tableOutputs.map(function (rec: any) {
456
+ return {
457
+ id: str(rec.sys_id), name: str(rec.element), label: str(rec.label) || str(rec.element),
458
+ type: str(rec.internal_type) || 'string',
459
+ type_label: TYPE_LABELS[str(rec.internal_type) || 'string'] || str(rec.internal_type),
460
+ mandatory: str(rec.mandatory) === 'true',
461
+ order: parseInt(str(rec.order) || '0', 10),
462
+ maxsize: parseInt(str(rec.max_length) || '200', 10),
463
+ hint: str(rec.hint), reference: str(rec.reference), reference_display: str(rec.reference_display),
464
+ use_dependent: str(rec.use_dependent_field) === 'true',
465
+ dependent_on: str(rec.dependent_on_field),
466
+ attributes: str(rec.attributes)
467
+ };
468
+ });
469
+ } catch (_) {}
470
+ }
471
+
472
+ // Transform inputs into GraphQL mutation format (matching exact UI structure)
473
+ var inputs = apiInputs.map(function (inp: any) {
474
+ var paramType = inp.type || 'string';
475
+ var name = inp.name || '';
476
+ var label = inp.label || name;
477
+ var attrs = typeof inp.attributes === 'object' ? flattenAttributes(inp.attributes) : (inp.attributes || '');
478
+
479
+ // Determine value: user-provided > default
480
+ var value = '';
481
+ if (name === 'table' && userTable) value = userTable;
482
+ else if (name === 'condition') value = userCondition || '^EQ';
483
+ else if (inp.defaultValue) value = inp.defaultValue;
484
+
485
+ var parameter: any = {
486
+ id: inp.id || '', label: label, name: name, type: paramType,
487
+ type_label: inp.type_label || TYPE_LABELS[paramType] || paramType,
488
+ order: inp.order || 0, extended: inp.extended || false,
489
+ mandatory: inp.mandatory || false, readonly: inp.readonly || false,
490
+ maxsize: inp.maxsize || 4000, data_structure: '',
491
+ reference: inp.reference || '', reference_display: inp.reference_display || '',
492
+ ref_qual: inp.ref_qual || '', choiceOption: inp.choiceOption || '',
493
+ table: '', columnName: '', defaultValue: inp.defaultValue || '',
494
+ use_dependent: inp.use_dependent || false, dependent_on: inp.dependent_on || '',
495
+ internal_link: inp.internal_link || '', show_ref_finder: inp.show_ref_finder || false,
496
+ local: inp.local || false, attributes: attrs, sys_class_name: '', children: []
497
+ };
498
+ if (inp.hint) parameter.hint = inp.hint;
499
+ if (inp.defaultDisplayValue) parameter.defaultDisplayValue = inp.defaultDisplayValue;
500
+ if (inp.choices) parameter.choices = inp.choices;
501
+ if (inp.defaultChoices) parameter.defaultChoices = inp.defaultChoices;
502
+
503
+ var inputObj: any = {
504
+ name: name, label: label, internalType: paramType,
505
+ mandatory: inp.mandatory || false, order: inp.order || 0,
506
+ valueSysId: '', field_name: name, type: paramType, children: [],
507
+ displayValue: { value: '' },
508
+ value: value ? { schemaless: false, schemalessValue: '', value: value } : { value: '' },
509
+ parameter: parameter
510
+ };
511
+
512
+ // Add choiceList for choice-type inputs (top-level, matching UI format)
513
+ if (inp.choices && Array.isArray(inp.choices)) {
514
+ inputObj.choiceList = inp.choices.map(function (c: any) {
515
+ return { label: c.label, value: c.value };
516
+ });
517
+ }
518
+
519
+ return inputObj;
520
+ });
521
+
522
+ // Transform outputs into GraphQL mutation format
523
+ var outputs = apiOutputs.map(function (out: any) {
524
+ var paramType = out.type || 'string';
525
+ var name = out.name || '';
526
+ var label = out.label || name;
527
+ var attrs = typeof out.attributes === 'object' ? flattenAttributes(out.attributes) : (out.attributes || '');
528
+
529
+ var parameter: any = {
530
+ id: out.id || '', label: label, name: name, type: paramType,
531
+ type_label: out.type_label || TYPE_LABELS[paramType] || paramType,
532
+ hint: out.hint || '', order: out.order || 0, extended: out.extended || false,
533
+ mandatory: out.mandatory || false, readonly: out.readonly || false,
534
+ maxsize: out.maxsize || 200, data_structure: '',
535
+ reference: out.reference || '', reference_display: out.reference_display || '',
536
+ ref_qual: '', choiceOption: '', table: '', columnName: '', defaultValue: '',
537
+ use_dependent: out.use_dependent || false, dependent_on: out.dependent_on || '',
538
+ internal_link: out.internal_link || '', show_ref_finder: false, local: false,
539
+ attributes: attrs, sys_class_name: ''
540
+ };
541
+
542
+ // Build children for complex types (like array.object)
543
+ var children: any[] = [];
544
+ var paramChildren: any[] = [];
545
+ if (out.children && Array.isArray(out.children)) {
546
+ children = out.children.map(function (child: any) {
547
+ return { id: '', name: child.name || '', scriptActive: false, children: [], value: { value: '' }, script: null };
548
+ });
549
+ paramChildren = out.children.map(function (child: any) {
550
+ return {
551
+ id: '', label: child.label || child.name || '', name: child.name || '',
552
+ type: child.type || 'string', type_label: child.type_label || TYPE_LABELS[child.type || 'string'] || 'String',
553
+ hint: '', order: child.order || 0, extended: false, mandatory: false, readonly: false, maxsize: 0,
554
+ data_structure: '', reference: '', reference_display: '', ref_qual: '', choiceOption: '',
555
+ table: '', columnName: '', defaultValue: '', defaultDisplayValue: '',
556
+ use_dependent: false, dependent_on: false, show_ref_finder: false, local: false,
557
+ attributes: '', sys_class_name: '',
558
+ uiDisplayType: child.uiDisplayType || child.type || 'string',
559
+ uiDisplayTypeLabel: child.type_label || 'String',
560
+ internal_link: '', value: '', display_value: '', scriptActive: false,
561
+ parent: out.id || '',
562
+ fieldFacetMap: 'uiTypeLabel=' + (child.type_label || 'String') + ',',
563
+ children: [], script: null
564
+ };
565
+ });
566
+ }
567
+ parameter.children = paramChildren;
568
+
569
+ return {
570
+ name: name, value: '', displayValue: '', type: paramType,
571
+ order: out.order || 0, label: label, children: children, parameter: parameter
572
+ };
573
+ });
574
+
575
+ return { inputs, outputs, error: fetchError || undefined };
576
+ }
577
+
349
578
  async function addTriggerViaGraphQL(
350
579
  client: any,
351
580
  flowId: string,
@@ -413,8 +642,13 @@ async function addTriggerViaGraphQL(
413
642
  }
414
643
  if (!trigDefId) return { success: false, error: 'Trigger definition not found for: ' + triggerType, steps };
415
644
 
645
+ // Build full trigger inputs and outputs from triggerpicker API (matching UI format)
646
+ var triggerData = await buildTriggerInputsForInsert(client, trigDefId!, table, condition);
647
+ steps.trigger_data = { inputCount: triggerData.inputs.length, outputCount: triggerData.outputs.length, error: triggerData.error };
648
+
416
649
  const triggerResponseFields = 'triggerInstances { inserts { sysId uiUniqueIdentifier __typename } updates deletes __typename }';
417
650
  try {
651
+ // Single INSERT with full inputs and outputs (matching UI behavior — no separate UPDATE needed)
418
652
  const insertResult = await executeFlowPatchMutation(client, {
419
653
  flowId: flowId,
420
654
  triggerInstances: {
@@ -426,8 +660,8 @@ async function addTriggerViaGraphQL(
426
660
  type: trigType,
427
661
  hasDynamicOutputs: false,
428
662
  metadata: '{"predicates":[]}',
429
- inputs: [],
430
- outputs: []
663
+ inputs: triggerData.inputs,
664
+ outputs: triggerData.outputs
431
665
  }]
432
666
  }
433
667
  }, triggerResponseFields);
@@ -436,30 +670,6 @@ async function addTriggerViaGraphQL(
436
670
  steps.insert = { success: !!triggerId, triggerId };
437
671
  if (!triggerId) return { success: false, steps, error: 'GraphQL trigger INSERT returned no trigger ID' };
438
672
 
439
- if (table) {
440
- const updateInputs: any[] = [
441
- {
442
- name: 'table',
443
- displayField: 'number',
444
- displayValue: { schemaless: false, schemalessValue: '', value: table.charAt(0).toUpperCase() + table.slice(1) },
445
- value: { schemaless: false, schemalessValue: '', value: table }
446
- },
447
- {
448
- name: 'condition',
449
- displayValue: { schemaless: false, schemalessValue: '', value: condition || '^EQ' }
450
- }
451
- ];
452
- try {
453
- await executeFlowPatchMutation(client, {
454
- flowId: flowId,
455
- triggerInstances: { update: [{ id: triggerId, inputs: updateInputs }] }
456
- }, triggerResponseFields);
457
- steps.update = { success: true, table, condition: condition || '^EQ' };
458
- } catch (e: any) {
459
- steps.update = { success: false, error: e.message };
460
- }
461
- }
462
-
463
673
  return { success: true, triggerId, steps };
464
674
  } catch (e: any) {
465
675
  steps.insert = { success: false, error: e.message };
@@ -467,6 +677,18 @@ async function addTriggerViaGraphQL(
467
677
  }
468
678
  }
469
679
 
680
+ // Common short-name aliases for action types — maps user-friendly names to ServiceNow internal names
681
+ const ACTION_TYPE_ALIASES: Record<string, string[]> = {
682
+ script: ['script_step', 'run_script', 'Run Script'],
683
+ log: ['log_message', 'Log Message', 'Log'],
684
+ create_record: ['Create Record'],
685
+ update_record: ['Update Record'],
686
+ notification: ['send_notification', 'send_email', 'Send Notification', 'Send Email'],
687
+ field_update: ['set_field_values', 'Set Field Values'],
688
+ wait: ['wait_for', 'Wait For Duration', 'Wait'],
689
+ approval: ['ask_for_approval', 'create_approval', 'Ask for Approval'],
690
+ };
691
+
470
692
  async function addActionViaGraphQL(
471
693
  client: any,
472
694
  flowId: string,
@@ -479,7 +701,7 @@ async function addActionViaGraphQL(
479
701
  ): Promise<{ success: boolean; actionId?: string; steps?: any; error?: string }> {
480
702
  const steps: any = {};
481
703
 
482
- // Dynamically look up action definition in sys_hub_action_type_snapshot
704
+ // Dynamically look up action definition in sys_hub_action_type_snapshot and sys_hub_action_type_definition
483
705
  // Prefer global/core actions over spoke-specific ones (e.g. core "Update Record" vs spoke-specific "Update Record")
484
706
  const snapshotFields = 'sys_id,internal_name,name,sys_scope,sys_package';
485
707
  let actionDefId: string | null = null;
@@ -507,41 +729,66 @@ async function addActionViaGraphQL(
507
729
  return candidates[0];
508
730
  };
509
731
 
510
- for (const field of ['internal_name', 'name']) {
511
- if (actionDefId) break;
512
- try {
513
- const resp = await client.get('/api/now/table/sys_hub_action_type_snapshot', {
514
- params: { sysparm_query: field + '=' + actionType, sysparm_fields: snapshotFields, sysparm_limit: 10 }
515
- });
516
- const results = resp.data.result || [];
517
- if (results.length > 1) {
518
- steps.def_lookup_candidates = results.map((r: any) => ({ sys_id: r.sys_id, internal_name: str(r.internal_name), name: str(r.name), scope: str(r.sys_scope), package: str(r.sys_package) }));
732
+ // Helper: search a table for action definitions by exact match and LIKE
733
+ const searchTable = async (tableName: string, searchTerms: string[]): Promise<void> => {
734
+ for (var si = 0; si < searchTerms.length && !actionDefId; si++) {
735
+ var term = searchTerms[si];
736
+ // Exact match on internal_name and name
737
+ for (const field of ['internal_name', 'name']) {
738
+ if (actionDefId) break;
739
+ try {
740
+ const resp = await client.get('/api/now/table/' + tableName, {
741
+ params: { sysparm_query: field + '=' + term, sysparm_fields: snapshotFields, sysparm_limit: 10 }
742
+ });
743
+ const results = resp.data.result || [];
744
+ if (results.length > 1) {
745
+ steps.def_lookup_candidates = results.map((r: any) => ({ sys_id: r.sys_id, internal_name: str(r.internal_name), name: str(r.name), scope: str(r.sys_scope), package: str(r.sys_package) }));
746
+ }
747
+ const found = pickBest(results);
748
+ if (found?.sys_id) {
749
+ actionDefId = found.sys_id;
750
+ steps.def_lookup = { id: found.sys_id, internal_name: str(found.internal_name), name: str(found.name), scope: str(found.sys_scope), package: str(found.sys_package), matched: tableName + ':' + field + '=' + term };
751
+ }
752
+ } catch (_) {}
519
753
  }
520
- const found = pickBest(results);
521
- if (found?.sys_id) {
522
- actionDefId = found.sys_id;
523
- steps.def_lookup = { id: found.sys_id, internal_name: str(found.internal_name), name: str(found.name), scope: str(found.sys_scope), package: str(found.sys_package), matched: field + '=' + actionType };
754
+ // LIKE search
755
+ if (!actionDefId) {
756
+ try {
757
+ const resp = await client.get('/api/now/table/' + tableName, {
758
+ params: {
759
+ sysparm_query: 'internal_nameLIKE' + term + '^ORnameLIKE' + term,
760
+ sysparm_fields: snapshotFields, sysparm_limit: 10
761
+ }
762
+ });
763
+ const results = resp.data.result || [];
764
+ if (results.length > 0 && !steps.def_lookup_fallback_candidates) {
765
+ steps.def_lookup_fallback_candidates = results.map((r: any) => ({ sys_id: r.sys_id, internal_name: str(r.internal_name), name: str(r.name), scope: str(r.sys_scope), package: str(r.sys_package) }));
766
+ }
767
+ const found = pickBest(results);
768
+ if (found?.sys_id) {
769
+ actionDefId = found.sys_id;
770
+ steps.def_lookup = { id: found.sys_id, internal_name: str(found.internal_name), name: str(found.name), scope: str(found.sys_scope), package: str(found.sys_package), matched: tableName + ':LIKE ' + term };
771
+ }
772
+ } catch (_) {}
524
773
  }
525
- } catch (_) {}
526
- }
774
+ }
775
+ };
776
+
777
+ // Build search terms: original actionType + any alias variations
778
+ var searchTerms = [actionType];
779
+ var aliases = ACTION_TYPE_ALIASES[actionType.toLowerCase()];
780
+ if (aliases) searchTerms = searchTerms.concat(aliases);
781
+
782
+ // Search 1: sys_hub_action_type_snapshot (published action snapshots)
783
+ await searchTable('sys_hub_action_type_snapshot', searchTerms);
784
+
785
+ // Search 2: sys_hub_action_type_definition (action definitions — includes built-in/native actions)
527
786
  if (!actionDefId) {
528
- try {
529
- const resp = await client.get('/api/now/table/sys_hub_action_type_snapshot', {
530
- params: {
531
- sysparm_query: 'internal_nameLIKE' + actionType + '^ORnameLIKE' + actionType,
532
- sysparm_fields: snapshotFields, sysparm_limit: 10
533
- }
534
- });
535
- const results = resp.data.result || [];
536
- steps.def_lookup_fallback_candidates = results.map((r: any) => ({ sys_id: r.sys_id, internal_name: str(r.internal_name), name: str(r.name), scope: str(r.sys_scope), package: str(r.sys_package) }));
537
- const found = pickBest(results);
538
- if (found?.sys_id) {
539
- actionDefId = found.sys_id;
540
- steps.def_lookup = { id: found.sys_id, internal_name: str(found.internal_name), name: str(found.name), scope: str(found.sys_scope), package: str(found.sys_package), matched: 'LIKE ' + actionType };
541
- }
542
- } catch (_) {}
787
+ steps.snapshot_not_found = true;
788
+ await searchTable('sys_hub_action_type_definition', searchTerms);
543
789
  }
544
- if (!actionDefId) return { success: false, error: 'Action definition not found for: ' + actionType, steps };
790
+
791
+ if (!actionDefId) return { success: false, error: 'Action definition not found for: ' + actionType + ' (searched snapshot + definition tables with terms: ' + searchTerms.join(', ') + ')', steps };
545
792
 
546
793
  // Build full input objects with parameter definitions (matching UI format)
547
794
  const inputResult = await buildActionInputsForInsert(client, actionDefId, inputs);
@@ -662,6 +909,7 @@ async function addFlowLogicViaGraphQL(
662
909
  const inputResult = await buildFlowLogicInputsForInsert(client, defId, defRecord, inputs);
663
910
  steps.available_inputs = inputResult.inputs.map((i: any) => ({ name: i.name, label: i.parameter?.label }));
664
911
  steps.resolved_inputs = inputResult.resolvedInputs;
912
+ steps.input_query_stats = { defParamsFound: inputResult.defParamsCount, inputsBuilt: inputResult.inputs.length, error: inputResult.inputQueryError };
665
913
 
666
914
  // Calculate insertion order
667
915
  const resolvedOrder = await calculateInsertOrder(client, flowId, parentUiId, order);
@@ -1130,8 +1378,7 @@ export const toolDefinition: MCPToolDefinition = {
1130
1378
  },
1131
1379
  action_type: {
1132
1380
  type: 'string',
1133
- enum: ['log', 'create_record', 'update_record', 'notification', 'script', 'field_update', 'wait', 'approval'],
1134
- description: 'Action type to add (for add_action)',
1381
+ description: 'Action type to add (for add_action). Looked up dynamically by internal_name or name in sys_hub_action_type_snapshot and sys_hub_action_type_definition. Common short names: log, script, create_record, update_record, notification, field_update, wait, approval. You can also use the exact ServiceNow internal name (e.g. "sn_fd.script_step", "global.update_record") or display name (e.g. "Run Script", "Update Record").',
1135
1382
  default: 'log'
1136
1383
  },
1137
1384
  action_name: {