snow-flow 10.0.1-dev.488 → 10.0.1-dev.490

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.488",
3
+ "version": "10.0.1-dev.490",
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.
@@ -221,7 +241,12 @@ async function buildActionInputsForInsert(
221
241
  };
222
242
  });
223
243
 
224
- return { inputs, resolvedInputs, actionParams };
244
+ // Check for mandatory fields that are missing a value
245
+ var missingMandatory = inputs
246
+ .filter(function (inp: any) { return inp.parameter?.mandatory && !inp.value?.value; })
247
+ .map(function (inp: any) { return inp.name + ' (' + (inp.parameter?.label || inp.name) + ')'; });
248
+
249
+ return { inputs, resolvedInputs, actionParams, missingMandatory };
225
250
  }
226
251
 
227
252
  /**
@@ -353,7 +378,12 @@ async function buildFlowLogicInputsForInsert(
353
378
  variables: '[]'
354
379
  };
355
380
 
356
- return { inputs, flowLogicDefinition, resolvedInputs, inputQueryError: inputQueryError || undefined, defParamsCount: defParams.length };
381
+ // Check for mandatory fields that are missing a value
382
+ var missingMandatory = inputs
383
+ .filter(function (inp: any) { return inp.parameter?.mandatory && !inp.value?.value; })
384
+ .map(function (inp: any) { return inp.name + ' (' + (inp.parameter?.label || inp.name) + ')'; });
385
+
386
+ return { inputs, flowLogicDefinition, resolvedInputs, inputQueryError: inputQueryError || undefined, defParamsCount: defParams.length, missingMandatory };
357
387
  }
358
388
 
359
389
  // Note: reordering of existing elements is NOT possible via Table API because
@@ -1107,6 +1137,12 @@ async function addActionViaGraphQL(
1107
1137
  steps.available_inputs = inputResult.actionParams.map((p: any) => ({ element: p.element, label: p.label }));
1108
1138
  steps.resolved_inputs = inputResult.resolvedInputs;
1109
1139
 
1140
+ // Validate mandatory fields
1141
+ if (inputResult.missingMandatory && inputResult.missingMandatory.length > 0) {
1142
+ steps.missing_mandatory = inputResult.missingMandatory;
1143
+ return { success: false, error: 'Missing required inputs for ' + actionType + ': ' + inputResult.missingMandatory.join(', ') + '. These fields are mandatory in Flow Designer.', steps };
1144
+ }
1145
+
1110
1146
  // Calculate insertion order
1111
1147
  const resolvedOrder = await calculateInsertOrder(client, flowId, parentUiId, order);
1112
1148
  steps.insert_order = resolvedOrder;
@@ -1997,6 +2033,12 @@ async function addFlowLogicViaGraphQL(
1997
2033
  steps.resolved_inputs = inputResult.resolvedInputs;
1998
2034
  steps.input_query_stats = { defParamsFound: inputResult.defParamsCount, inputsBuilt: inputResult.inputs.length, error: inputResult.inputQueryError };
1999
2035
 
2036
+ // Validate mandatory fields (e.g. condition for IF/ELSEIF)
2037
+ if (inputResult.missingMandatory && inputResult.missingMandatory.length > 0) {
2038
+ steps.missing_mandatory = inputResult.missingMandatory;
2039
+ return { success: false, error: 'Missing required inputs for ' + logicType + ': ' + inputResult.missingMandatory.join(', ') + '. These fields are mandatory in Flow Designer.', steps };
2040
+ }
2041
+
2000
2042
  // ── Detect condition that needs data pill transformation ────────────
2001
2043
  // Flow Designer sets conditions via a SEPARATE UPDATE after the element is created.
2002
2044
  // Three paths:
@@ -3727,22 +3769,27 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
3727
3769
  }
3728
3770
 
3729
3771
  // ────────────────────────────────────────────────────────────────
3730
- // OPEN_FLOW — acquire Flow Designer editing lock (processflow GET)
3772
+ // OPEN_FLOW — acquire Flow Designer editing lock (safeEdit create)
3731
3773
  // ────────────────────────────────────────────────────────────────
3732
3774
  case 'open_flow': {
3733
3775
  if (!args.flow_id) throw new SnowFlowError(ErrorType.VALIDATION_ERROR, 'flow_id is required for open_flow');
3734
3776
  var openFlowId = await resolveFlowId(client, args.flow_id);
3735
3777
  var openSummary = summary();
3778
+
3779
+ // Step 1: Load flow data via processflow GET (same as UI)
3736
3780
  try {
3737
- // The processflow GET is what the Flow Designer UI calls when opening a flow for editing.
3738
- // This acquires the editing lock so subsequent GraphQL mutations can work.
3739
3781
  await client.get('/api/now/processflow/flow/' + openFlowId);
3740
- openSummary.success('Flow opened for editing').field('Flow', openFlowId)
3782
+ } catch (_) { /* best-effort — flow data load is not critical for lock acquisition */ }
3783
+
3784
+ // Step 2: Acquire editing lock via safeEdit create mutation (required for GraphQL mutations)
3785
+ var lockResult = await acquireFlowEditingLock(client, openFlowId);
3786
+ if (lockResult.success) {
3787
+ openSummary.success('Flow opened for editing (lock acquired)').field('Flow', openFlowId)
3741
3788
  .line('You can now use add_action, add_flow_logic, etc. Call close_flow when done.');
3742
3789
  return createSuccessResult({ action: 'open_flow', flow_id: openFlowId, editing_session: true }, {}, openSummary.build());
3743
- } catch (e: any) {
3744
- openSummary.error('Failed to open flow for editing: ' + (e.message || 'unknown')).field('Flow', openFlowId);
3745
- return createErrorResult('Failed to open flow for editing: ' + (e.message || 'unknown'));
3790
+ } else {
3791
+ openSummary.error('Cannot open flow: ' + (lockResult.error || 'lock acquisition failed')).field('Flow', openFlowId);
3792
+ return createErrorResult('Cannot open flow for editing: ' + (lockResult.error || 'lock acquisition failed'));
3746
3793
  }
3747
3794
  }
3748
3795