snow-flow 10.0.1-dev.487 → 10.0.1-dev.489

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.487",
3
+ "version": "10.0.1-dev.489",
4
4
  "name": "snow-flow",
5
5
  "description": "Snow-Flow - ServiceNow Multi-Agent Development Framework powered by AI",
6
6
  "license": "Elastic-2.0",
@@ -110,6 +110,26 @@ async function executeFlowPatchMutation(
110
110
  return resp.data?.data?.global?.snFlowDesigner?.flow || resp.data;
111
111
  }
112
112
 
113
+ /**
114
+ * Acquire the Flow Designer editing lock on a flow.
115
+ * The UI calls safeEdit(create: flowId) when opening the editor.
116
+ * This must be called before GraphQL mutations on existing flows.
117
+ */
118
+ async function acquireFlowEditingLock(client: any, flowId: string): Promise<{ success: boolean; error?: string }> {
119
+ try {
120
+ var mutation = 'mutation { global { snFlowDesigner { safeEdit(safeEditInput: {create: "' + flowId + '"}) { createResult { canEdit id editingUserDisplayName __typename } __typename } __typename } __typename } }';
121
+ var resp = await client.post('/api/now/graphql', { variables: {}, query: mutation });
122
+ var result = resp.data?.data?.global?.snFlowDesigner?.safeEdit?.createResult;
123
+ if (result?.canEdit === true || result?.canEdit === 'true') {
124
+ return { success: true };
125
+ }
126
+ var editingUser = result?.editingUserDisplayName || 'another user';
127
+ return { success: false, error: 'Flow is locked by ' + editingUser };
128
+ } catch (e: any) {
129
+ return { success: false, error: e.message || 'unknown error' };
130
+ }
131
+ }
132
+
113
133
  /**
114
134
  * Release the Flow Designer editing lock on a flow.
115
135
  * The UI calls safeEdit(delete: flowId) when closing the editor.
@@ -1917,6 +1937,27 @@ async function addFlowLogicViaGraphQL(
1917
1937
  ): Promise<{ success: boolean; logicId?: string; uiUniqueIdentifier?: string; steps?: any; error?: string }> {
1918
1938
  const steps: any = {};
1919
1939
 
1940
+ // Normalize common aliases to actual ServiceNow flow logic type values
1941
+ var LOGIC_TYPE_ALIASES: Record<string, string> = {
1942
+ 'FOR_EACH': 'FOREACH',
1943
+ 'DO_UNTIL': 'DOUNTIL',
1944
+ 'ELSE_IF': 'ELSEIF',
1945
+ 'SKIP_ITERATION': 'CONTINUE',
1946
+ 'EXIT_LOOP': 'BREAK',
1947
+ 'GO_BACK_TO': 'GOBACKTO',
1948
+ 'DYNAMIC_FLOW': 'DYNAMICFLOW',
1949
+ 'END_FLOW': 'END',
1950
+ 'GET_FLOW_OUTPUT': 'GETFLOWOUTPUT',
1951
+ 'GET_FLOW_OUTPUTS': 'GETFLOWOUTPUT',
1952
+ 'SET_FLOW_VARIABLES': 'SETFLOWVARIABLES',
1953
+ 'APPEND_FLOW_VARIABLES': 'APPENDFLOWVARIABLES',
1954
+ };
1955
+ var normalizedType = LOGIC_TYPE_ALIASES[logicType.toUpperCase()] || logicType;
1956
+ if (normalizedType !== logicType) {
1957
+ steps.type_normalized = { from: logicType, to: normalizedType };
1958
+ logicType = normalizedType;
1959
+ }
1960
+
1920
1961
  // Dynamically look up flow logic definition in sys_hub_flow_logic_definition
1921
1962
  // Fetch extra fields needed for the flowLogicDefinition object in the mutation
1922
1963
  const defFields = 'sys_id,type,name,description,order,attributes,compilation_class,quiescence,visible,category,connected_to';
@@ -1924,7 +1965,7 @@ async function addFlowLogicViaGraphQL(
1924
1965
  let defName = '';
1925
1966
  let defType = logicType;
1926
1967
  let defRecord: any = {};
1927
- // Try exact match on type (IF, ELSE, FOR_EACH, DO_UNTIL, SWITCH), then name
1968
+ // Try exact match on type (IF, ELSE, FOREACH, DOUNTIL, etc.), then name
1928
1969
  for (const field of ['type', 'name']) {
1929
1970
  if (defId) break;
1930
1971
  try {
@@ -2597,17 +2638,18 @@ export const toolDefinition: MCPToolDefinition = {
2597
2638
  },
2598
2639
  logic_type: {
2599
2640
  type: 'string',
2600
- description: 'Flow logic type for add_flow_logic. Looked up dynamically in sys_hub_flow_logic_definition. Available types: ' +
2641
+ description: 'Flow logic type for add_flow_logic. Looked up dynamically in sys_hub_flow_logic_definition. ' +
2642
+ 'Common aliases (FOR_EACH, DO_UNTIL, etc.) are auto-normalized to ServiceNow types. Available types: ' +
2601
2643
  'IF, ELSEIF, ELSE — conditional branching. Use ELSEIF (not nested ELSE+IF) for else-if branches. ELSE and ELSEIF require connected_to set to the If block\'s uiUniqueIdentifier. ' +
2602
- 'FOR_EACH, DO_UNTIL — loops. SKIP_ITERATION and EXIT_LOOP can be used inside loops. ' +
2644
+ 'FOREACH (or FOR_EACH), DOUNTIL (or DO_UNTIL) — loops. CONTINUE (skip iteration) and BREAK (exit loop) can be used inside loops. ' +
2603
2645
  'PARALLEL — execute branches in parallel. ' +
2604
2646
  'DECISION — switch/decision table. ' +
2605
2647
  'TRY — error handling (try/catch). ' +
2606
2648
  'END — End Flow (stops execution). Always add END as the last element when the flow should terminate cleanly. ' +
2607
2649
  'TIMER — Wait for a duration of time. ' +
2608
- 'GO_BACK_TO — jump back to a previous step. ' +
2609
- 'SET_FLOW_VARIABLES, APPEND_FLOW_VARIABLES, GET_FLOW_OUTPUT — flow variable management. ' +
2610
- 'WORKFLOW — call a legacy workflow. DYNAMIC_FLOW — dynamically invoke a flow. ' +
2650
+ 'GOBACKTO (or GO_BACK_TO) — jump back to a previous step. ' +
2651
+ 'SETFLOWVARIABLES, APPENDFLOWVARIABLES, GETFLOWOUTPUT — flow variable management. ' +
2652
+ 'WORKFLOW — call a legacy workflow. DYNAMICFLOW — dynamically invoke a flow. ' +
2611
2653
  'Best practice: add an END element at the end of your flow for clean termination.'
2612
2654
  },
2613
2655
  logic_inputs: {
@@ -3705,22 +3747,27 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
3705
3747
  }
3706
3748
 
3707
3749
  // ────────────────────────────────────────────────────────────────
3708
- // OPEN_FLOW — acquire Flow Designer editing lock (processflow GET)
3750
+ // OPEN_FLOW — acquire Flow Designer editing lock (safeEdit create)
3709
3751
  // ────────────────────────────────────────────────────────────────
3710
3752
  case 'open_flow': {
3711
3753
  if (!args.flow_id) throw new SnowFlowError(ErrorType.VALIDATION_ERROR, 'flow_id is required for open_flow');
3712
3754
  var openFlowId = await resolveFlowId(client, args.flow_id);
3713
3755
  var openSummary = summary();
3756
+
3757
+ // Step 1: Load flow data via processflow GET (same as UI)
3714
3758
  try {
3715
- // The processflow GET is what the Flow Designer UI calls when opening a flow for editing.
3716
- // This acquires the editing lock so subsequent GraphQL mutations can work.
3717
3759
  await client.get('/api/now/processflow/flow/' + openFlowId);
3718
- openSummary.success('Flow opened for editing').field('Flow', openFlowId)
3760
+ } catch (_) { /* best-effort — flow data load is not critical for lock acquisition */ }
3761
+
3762
+ // Step 2: Acquire editing lock via safeEdit create mutation (required for GraphQL mutations)
3763
+ var lockResult = await acquireFlowEditingLock(client, openFlowId);
3764
+ if (lockResult.success) {
3765
+ openSummary.success('Flow opened for editing (lock acquired)').field('Flow', openFlowId)
3719
3766
  .line('You can now use add_action, add_flow_logic, etc. Call close_flow when done.');
3720
3767
  return createSuccessResult({ action: 'open_flow', flow_id: openFlowId, editing_session: true }, {}, openSummary.build());
3721
- } catch (e: any) {
3722
- openSummary.error('Failed to open flow for editing: ' + (e.message || 'unknown')).field('Flow', openFlowId);
3723
- return createErrorResult('Failed to open flow for editing: ' + (e.message || 'unknown'));
3768
+ } else {
3769
+ openSummary.error('Cannot open flow: ' + (lockResult.error || 'lock acquisition failed')).field('Flow', openFlowId);
3770
+ return createErrorResult('Cannot open flow for editing: ' + (lockResult.error || 'lock acquisition failed'));
3724
3771
  }
3725
3772
  }
3726
3773