snow-flow 10.0.1-dev.461 → 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.461",
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",
@@ -365,6 +365,216 @@ async function calculateInsertOrder(
365
365
  return 1;
366
366
  }
367
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
+
368
578
  async function addTriggerViaGraphQL(
369
579
  client: any,
370
580
  flowId: string,
@@ -432,8 +642,13 @@ async function addTriggerViaGraphQL(
432
642
  }
433
643
  if (!trigDefId) return { success: false, error: 'Trigger definition not found for: ' + triggerType, steps };
434
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
+
435
649
  const triggerResponseFields = 'triggerInstances { inserts { sysId uiUniqueIdentifier __typename } updates deletes __typename }';
436
650
  try {
651
+ // Single INSERT with full inputs and outputs (matching UI behavior — no separate UPDATE needed)
437
652
  const insertResult = await executeFlowPatchMutation(client, {
438
653
  flowId: flowId,
439
654
  triggerInstances: {
@@ -445,8 +660,8 @@ async function addTriggerViaGraphQL(
445
660
  type: trigType,
446
661
  hasDynamicOutputs: false,
447
662
  metadata: '{"predicates":[]}',
448
- inputs: [],
449
- outputs: []
663
+ inputs: triggerData.inputs,
664
+ outputs: triggerData.outputs
450
665
  }]
451
666
  }
452
667
  }, triggerResponseFields);
@@ -455,31 +670,6 @@ async function addTriggerViaGraphQL(
455
670
  steps.insert = { success: !!triggerId, triggerId };
456
671
  if (!triggerId) return { success: false, steps, error: 'GraphQL trigger INSERT returned no trigger ID' };
457
672
 
458
- if (table) {
459
- const updateInputs: any[] = [
460
- {
461
- name: 'table',
462
- displayField: 'number',
463
- displayValue: { schemaless: false, schemalessValue: '', value: table.charAt(0).toUpperCase() + table.slice(1) },
464
- value: { schemaless: false, schemalessValue: '', value: table }
465
- },
466
- {
467
- name: 'condition',
468
- displayValue: { schemaless: false, schemalessValue: '', value: condition || '^EQ' },
469
- value: { schemaless: false, schemalessValue: '', value: condition || '^EQ' }
470
- }
471
- ];
472
- try {
473
- await executeFlowPatchMutation(client, {
474
- flowId: flowId,
475
- triggerInstances: { update: [{ id: triggerId, inputs: updateInputs }] }
476
- }, triggerResponseFields);
477
- steps.update = { success: true, table, condition: condition || '^EQ' };
478
- } catch (e: any) {
479
- steps.update = { success: false, error: e.message };
480
- }
481
- }
482
-
483
673
  return { success: true, triggerId, steps };
484
674
  } catch (e: any) {
485
675
  steps.insert = { success: false, error: e.message };
@@ -487,6 +677,18 @@ async function addTriggerViaGraphQL(
487
677
  }
488
678
  }
489
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
+
490
692
  async function addActionViaGraphQL(
491
693
  client: any,
492
694
  flowId: string,
@@ -499,7 +701,7 @@ async function addActionViaGraphQL(
499
701
  ): Promise<{ success: boolean; actionId?: string; steps?: any; error?: string }> {
500
702
  const steps: any = {};
501
703
 
502
- // 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
503
705
  // Prefer global/core actions over spoke-specific ones (e.g. core "Update Record" vs spoke-specific "Update Record")
504
706
  const snapshotFields = 'sys_id,internal_name,name,sys_scope,sys_package';
505
707
  let actionDefId: string | null = null;
@@ -527,41 +729,66 @@ async function addActionViaGraphQL(
527
729
  return candidates[0];
528
730
  };
529
731
 
530
- for (const field of ['internal_name', 'name']) {
531
- if (actionDefId) break;
532
- try {
533
- const resp = await client.get('/api/now/table/sys_hub_action_type_snapshot', {
534
- params: { sysparm_query: field + '=' + actionType, sysparm_fields: snapshotFields, sysparm_limit: 10 }
535
- });
536
- const results = resp.data.result || [];
537
- if (results.length > 1) {
538
- 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 (_) {}
539
753
  }
540
- const found = pickBest(results);
541
- if (found?.sys_id) {
542
- actionDefId = found.sys_id;
543
- 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 (_) {}
544
773
  }
545
- } catch (_) {}
546
- }
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)
547
786
  if (!actionDefId) {
548
- try {
549
- const resp = await client.get('/api/now/table/sys_hub_action_type_snapshot', {
550
- params: {
551
- sysparm_query: 'internal_nameLIKE' + actionType + '^ORnameLIKE' + actionType,
552
- sysparm_fields: snapshotFields, sysparm_limit: 10
553
- }
554
- });
555
- const results = resp.data.result || [];
556
- 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) }));
557
- const found = pickBest(results);
558
- if (found?.sys_id) {
559
- actionDefId = found.sys_id;
560
- 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 };
561
- }
562
- } catch (_) {}
787
+ steps.snapshot_not_found = true;
788
+ await searchTable('sys_hub_action_type_definition', searchTerms);
563
789
  }
564
- 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 };
565
792
 
566
793
  // Build full input objects with parameter definitions (matching UI format)
567
794
  const inputResult = await buildActionInputsForInsert(client, actionDefId, inputs);
@@ -1151,8 +1378,7 @@ export const toolDefinition: MCPToolDefinition = {
1151
1378
  },
1152
1379
  action_type: {
1153
1380
  type: 'string',
1154
- enum: ['log', 'create_record', 'update_record', 'notification', 'script', 'field_update', 'wait', 'approval'],
1155
- 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").',
1156
1382
  default: 'log'
1157
1383
  },
1158
1384
  action_name: {