snow-flow-test 10.0.1-test.159 → 10.0.1-test.161

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.159",
3
+ "version": "10.0.1-test.161",
4
4
  "name": "snow-flow-test",
5
5
  "description": "Snow-Flow Test - ServiceNow Multi-Agent Development Framework",
6
6
  "license": "Elastic-2.0",
@@ -413,6 +413,169 @@ async function addFlowLogicViaGraphQL(
413
413
  }
414
414
  }
415
415
 
416
+ // ── SUBFLOW CALL (invoke a subflow as a step) ────────────────────────
417
+
418
+ async function addSubflowCallViaGraphQL(
419
+ client: any,
420
+ flowId: string,
421
+ subflowId: string,
422
+ inputs?: Record<string, string>,
423
+ order?: number,
424
+ parentUiId?: string
425
+ ): Promise<{ success: boolean; callId?: string; steps?: any; error?: string }> {
426
+ const steps: any = {};
427
+
428
+ // Resolve subflow: look up by sys_id, name, or internal_name in sys_hub_flow
429
+ let subflowSysId = isSysId(subflowId) ? subflowId : null;
430
+ let subflowName = '';
431
+ if (!subflowSysId) {
432
+ for (const field of ['name', 'internal_name']) {
433
+ if (subflowSysId) break;
434
+ try {
435
+ const resp = await client.get('/api/now/table/sys_hub_flow', {
436
+ params: {
437
+ sysparm_query: field + '=' + subflowId + '^type=subflow',
438
+ sysparm_fields: 'sys_id,name,internal_name',
439
+ sysparm_limit: 1
440
+ }
441
+ });
442
+ const found = resp.data.result?.[0];
443
+ if (found?.sys_id) {
444
+ subflowSysId = found.sys_id;
445
+ subflowName = found.name || subflowId;
446
+ steps.subflow_lookup = { id: found.sys_id, name: found.name, internal_name: found.internal_name, matched: field + '=' + subflowId };
447
+ }
448
+ } catch (_) {}
449
+ }
450
+ // LIKE fallback
451
+ if (!subflowSysId) {
452
+ try {
453
+ const resp = await client.get('/api/now/table/sys_hub_flow', {
454
+ params: {
455
+ sysparm_query: 'nameLIKE' + subflowId + '^type=subflow',
456
+ sysparm_fields: 'sys_id,name,internal_name',
457
+ sysparm_limit: 5
458
+ }
459
+ });
460
+ const results = resp.data.result || [];
461
+ steps.subflow_lookup_candidates = results.map((r: any) => ({ sys_id: r.sys_id, name: r.name, internal_name: r.internal_name }));
462
+ if (results[0]?.sys_id) {
463
+ subflowSysId = results[0].sys_id;
464
+ subflowName = results[0].name || subflowId;
465
+ steps.subflow_lookup = { id: results[0].sys_id, name: results[0].name, matched: 'LIKE ' + subflowId };
466
+ }
467
+ } catch (_) {}
468
+ }
469
+ }
470
+ if (!subflowSysId) return { success: false, error: 'Subflow not found: ' + subflowId, steps };
471
+
472
+ if (!subflowName) subflowName = subflowId;
473
+
474
+ const uuid = generateUUID();
475
+ const subflowResponseFields = 'subflows { inserts { sysId uiUniqueIdentifier __typename } updates deletes __typename }';
476
+ try {
477
+ const result = await executeFlowPatchMutation(client, {
478
+ flowId: flowId,
479
+ subflows: {
480
+ insert: [{
481
+ metadata: '{"predicates":[]}',
482
+ flowSysId: flowId,
483
+ generationSource: '',
484
+ name: subflowName,
485
+ order: String(order || 1),
486
+ parent: parentUiId || '',
487
+ subflowSysId: subflowSysId,
488
+ uiUniqueIdentifier: uuid,
489
+ type: 'subflow',
490
+ parentUiId: parentUiId || '',
491
+ inputs: []
492
+ }]
493
+ }
494
+ }, subflowResponseFields);
495
+
496
+ const callId = result?.subflows?.inserts?.[0]?.sysId;
497
+ steps.insert = { success: !!callId, callId, uuid };
498
+ if (!callId) return { success: false, steps, error: 'GraphQL subflow INSERT returned no ID' };
499
+
500
+ // Update with input values if provided
501
+ if (inputs && Object.keys(inputs).length > 0) {
502
+ const updateInputs = Object.entries(inputs).map(([name, value]) => ({
503
+ name,
504
+ value: { schemaless: false, schemalessValue: '', value: String(value) }
505
+ }));
506
+ try {
507
+ await executeFlowPatchMutation(client, {
508
+ flowId: flowId,
509
+ subflows: { update: [{ uiUniqueIdentifier: uuid, type: 'subflow', inputs: updateInputs }] }
510
+ }, subflowResponseFields);
511
+ steps.value_update = { success: true, inputs: updateInputs.map(i => i.name) };
512
+ } catch (e: any) {
513
+ steps.value_update = { success: false, error: e.message };
514
+ }
515
+ }
516
+
517
+ return { success: true, callId, steps };
518
+ } catch (e: any) {
519
+ steps.insert = { success: false, error: e.message };
520
+ return { success: false, steps, error: 'GraphQL subflow INSERT failed: ' + e.message };
521
+ }
522
+ }
523
+
524
+ // ── GENERIC UPDATE/DELETE for any flow element ───────────────────────
525
+
526
+ const elementGraphQLMap: Record<string, { key: string; type: string; responseFields: string }> = {
527
+ action: { key: 'actions', type: 'action', responseFields: 'actions { inserts { sysId uiUniqueIdentifier __typename } updates deletes __typename }' },
528
+ trigger: { key: 'triggerInstances', type: 'trigger', responseFields: 'triggerInstances { inserts { sysId uiUniqueIdentifier __typename } updates deletes __typename }' },
529
+ flowlogic: { key: 'flowLogics', type: 'flowlogic', responseFields: 'flowLogics { inserts { sysId uiUniqueIdentifier __typename } updates deletes __typename }' },
530
+ subflow: { key: 'subflows', type: 'subflow', responseFields: 'subflows { inserts { sysId uiUniqueIdentifier __typename } updates deletes __typename }' },
531
+ };
532
+
533
+ async function updateElementViaGraphQL(
534
+ client: any,
535
+ flowId: string,
536
+ elementType: string,
537
+ elementId: string,
538
+ inputs: Record<string, string>
539
+ ): Promise<{ success: boolean; steps?: any; error?: string }> {
540
+ const config = elementGraphQLMap[elementType];
541
+ if (!config) return { success: false, error: 'Unknown element type: ' + elementType };
542
+
543
+ const updateInputs = Object.entries(inputs).map(([name, value]) => ({
544
+ name,
545
+ value: { schemaless: false, schemalessValue: '', value: String(value) }
546
+ }));
547
+
548
+ try {
549
+ await executeFlowPatchMutation(client, {
550
+ flowId,
551
+ [config.key]: { update: [{ uiUniqueIdentifier: elementId, type: config.type, inputs: updateInputs }] }
552
+ }, config.responseFields);
553
+ return { success: true, steps: { element: elementId, type: elementType, inputs: updateInputs.map(i => i.name) } };
554
+ } catch (e: any) {
555
+ return { success: false, error: e.message, steps: { element: elementId, type: elementType } };
556
+ }
557
+ }
558
+
559
+ async function deleteElementViaGraphQL(
560
+ client: any,
561
+ flowId: string,
562
+ elementType: string,
563
+ elementIds: string[]
564
+ ): Promise<{ success: boolean; steps?: any; error?: string }> {
565
+ const config = elementGraphQLMap[elementType];
566
+ if (!config) return { success: false, error: 'Unknown element type: ' + elementType };
567
+
568
+ try {
569
+ await executeFlowPatchMutation(client, {
570
+ flowId,
571
+ [config.key]: { delete: elementIds }
572
+ }, config.responseFields);
573
+ return { success: true, steps: { deleted: elementIds, type: elementType } };
574
+ } catch (e: any) {
575
+ return { success: false, error: e.message, steps: { elementIds, type: elementType } };
576
+ }
577
+ }
578
+
416
579
  async function createFlowViaProcessFlowAPI(
417
580
  client: any,
418
581
  params: {
@@ -521,8 +684,14 @@ export const toolDefinition: MCPToolDefinition = {
521
684
  properties: {
522
685
  action: {
523
686
  type: 'string',
524
- enum: ['create', 'create_subflow', 'list', 'get', 'update', 'activate', 'deactivate', 'delete', 'publish', 'add_trigger', 'update_trigger', 'add_action', 'add_flow_logic'],
525
- description: 'Action to perform. Use add_trigger/add_action/add_flow_logic to add elements, update_trigger to change an existing trigger. Flow logic types: IF, FOR_EACH, DO_UNTIL, SWITCH.'
687
+ enum: [
688
+ 'create', 'create_subflow', 'list', 'get', 'update', 'activate', 'deactivate', 'delete', 'publish',
689
+ 'add_trigger', 'update_trigger', 'delete_trigger',
690
+ 'add_action', 'update_action', 'delete_action',
691
+ 'add_flow_logic', 'update_flow_logic', 'delete_flow_logic',
692
+ 'add_subflow', 'update_subflow', 'delete_subflow'
693
+ ],
694
+ description: 'Action to perform. add_*/update_*/delete_* for triggers, actions, flow_logic, subflows. update_trigger replaces the trigger type. update_action/update_flow_logic/update_subflow change input values. delete_* removes elements by element_id.'
526
695
  },
527
696
 
528
697
  flow_id: {
@@ -618,7 +787,15 @@ export const toolDefinition: MCPToolDefinition = {
618
787
  },
619
788
  parent_ui_id: {
620
789
  type: 'string',
621
- description: 'Parent UI unique identifier for nesting flow logic blocks (e.g. placing actions inside an If block)'
790
+ description: 'Parent UI unique identifier for nesting elements inside flow logic blocks (e.g. placing actions/subflows inside an If block)'
791
+ },
792
+ subflow_id: {
793
+ type: 'string',
794
+ description: 'Subflow sys_id or name to call as a step (for add_subflow action). Looked up in sys_hub_flow where type=subflow.'
795
+ },
796
+ element_id: {
797
+ type: 'string',
798
+ description: 'Element sys_id or uiUniqueIdentifier for update_*/delete_* actions. For delete_* this can also be a comma-separated list of IDs.'
622
799
  },
623
800
  type: {
624
801
  type: 'string',
@@ -1592,6 +1769,96 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
1592
1769
  : createErrorResult(addLogicResult.error || 'Failed to add flow logic');
1593
1770
  }
1594
1771
 
1772
+ // ────────────────────────────────────────────────────────────────
1773
+ // ADD_SUBFLOW — call an existing subflow as a step in the flow
1774
+ // ────────────────────────────────────────────────────────────────
1775
+ case 'add_subflow': {
1776
+ if (!args.flow_id) {
1777
+ throw new SnowFlowError(ErrorType.VALIDATION_ERROR, 'flow_id is required for add_subflow');
1778
+ }
1779
+ if (!args.subflow_id) {
1780
+ throw new SnowFlowError(ErrorType.VALIDATION_ERROR, 'subflow_id is required for add_subflow (sys_id or name of the subflow to call)');
1781
+ }
1782
+ var addSubFlowId = await resolveFlowId(client, args.flow_id);
1783
+ var addSubSubflowId = args.subflow_id;
1784
+ var addSubInputs = args.action_inputs || args.inputs || {};
1785
+ var addSubOrder = args.order;
1786
+ var addSubParentUiId = args.parent_ui_id || '';
1787
+
1788
+ var addSubResult = await addSubflowCallViaGraphQL(client, addSubFlowId, addSubSubflowId, addSubInputs, addSubOrder, addSubParentUiId);
1789
+
1790
+ var addSubSummary = summary();
1791
+ if (addSubResult.success) {
1792
+ addSubSummary
1793
+ .success('Subflow call added via GraphQL')
1794
+ .field('Flow', addSubFlowId)
1795
+ .field('Subflow', addSubSubflowId)
1796
+ .field('Call ID', addSubResult.callId || 'unknown');
1797
+ } else {
1798
+ addSubSummary.error('Failed to add subflow call: ' + (addSubResult.error || 'unknown'));
1799
+ }
1800
+
1801
+ return addSubResult.success
1802
+ ? createSuccessResult({ action: 'add_subflow', ...addSubResult }, {}, addSubSummary.build())
1803
+ : createErrorResult(addSubResult.error || 'Failed to add subflow call');
1804
+ }
1805
+
1806
+ // ────────────────────────────────────────────────────────────────
1807
+ // UPDATE_ACTION / UPDATE_FLOW_LOGIC / UPDATE_SUBFLOW
1808
+ // ────────────────────────────────────────────────────────────────
1809
+ case 'update_action':
1810
+ case 'update_flow_logic':
1811
+ case 'update_subflow': {
1812
+ if (!args.flow_id) throw new SnowFlowError(ErrorType.VALIDATION_ERROR, 'flow_id is required');
1813
+ if (!args.element_id) throw new SnowFlowError(ErrorType.VALIDATION_ERROR, 'element_id is required (sys_id or uiUniqueIdentifier of the element)');
1814
+ var updElemFlowId = await resolveFlowId(client, args.flow_id);
1815
+ var updElemType = action === 'update_action' ? 'action' : action === 'update_flow_logic' ? 'flowlogic' : 'subflow';
1816
+ var updElemInputs = args.action_inputs || args.logic_inputs || args.inputs || {};
1817
+
1818
+ var updElemResult = await updateElementViaGraphQL(client, updElemFlowId, updElemType, args.element_id, updElemInputs);
1819
+
1820
+ var updElemSummary = summary();
1821
+ if (updElemResult.success) {
1822
+ updElemSummary.success('Element updated').field('Type', updElemType).field('Element', args.element_id);
1823
+ } else {
1824
+ updElemSummary.error('Failed to update element: ' + (updElemResult.error || 'unknown'));
1825
+ }
1826
+ return updElemResult.success
1827
+ ? createSuccessResult({ action, ...updElemResult }, {}, updElemSummary.build())
1828
+ : createErrorResult(updElemResult.error || 'Failed to update element');
1829
+ }
1830
+
1831
+ // ────────────────────────────────────────────────────────────────
1832
+ // DELETE_ACTION / DELETE_FLOW_LOGIC / DELETE_SUBFLOW / DELETE_TRIGGER
1833
+ // ────────────────────────────────────────────────────────────────
1834
+ case 'delete_action':
1835
+ case 'delete_flow_logic':
1836
+ case 'delete_subflow':
1837
+ case 'delete_trigger': {
1838
+ if (!args.flow_id) throw new SnowFlowError(ErrorType.VALIDATION_ERROR, 'flow_id is required');
1839
+ if (!args.element_id) throw new SnowFlowError(ErrorType.VALIDATION_ERROR, 'element_id is required (sys_id(s) to delete, comma-separated for multiple)');
1840
+ var delElemFlowId = await resolveFlowId(client, args.flow_id);
1841
+ var delElemType = action === 'delete_action' ? 'action'
1842
+ : action === 'delete_flow_logic' ? 'flowlogic'
1843
+ : action === 'delete_subflow' ? 'subflow'
1844
+ : 'trigger';
1845
+ var delElemIds = String(args.element_id).split(',').map((id: string) => id.trim());
1846
+
1847
+ // Map 'trigger' to the correct GraphQL key
1848
+ var delGraphQLType = delElemType === 'trigger' ? 'trigger' : delElemType;
1849
+ var delResult = await deleteElementViaGraphQL(client, delElemFlowId, delGraphQLType, delElemIds);
1850
+
1851
+ var delSummary = summary();
1852
+ if (delResult.success) {
1853
+ delSummary.success('Element(s) deleted').field('Type', delElemType).field('Deleted', delElemIds.join(', '));
1854
+ } else {
1855
+ delSummary.error('Failed to delete element: ' + (delResult.error || 'unknown'));
1856
+ }
1857
+ return delResult.success
1858
+ ? createSuccessResult({ action, ...delResult }, {}, delSummary.build())
1859
+ : createErrorResult(delResult.error || 'Failed to delete element');
1860
+ }
1861
+
1595
1862
  default:
1596
1863
  throw new SnowFlowError(ErrorType.VALIDATION_ERROR, 'Unknown action: ' + action);
1597
1864
  }