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
|
@@ -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,
|
|
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.
|
|
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.
|
|
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
|
-
'
|
|
2610
|
-
'WORKFLOW — call a legacy workflow.
|
|
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 (
|
|
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
|
-
|
|
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
|
-
}
|
|
3722
|
-
openSummary.error('
|
|
3723
|
-
return createErrorResult('
|
|
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
|
|