snow-flow 10.0.1-dev.391 → 10.0.1-dev.392

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.391",
3
+ "version": "10.0.1-dev.392",
4
4
  "name": "snow-flow",
5
5
  "description": "Snow-Flow - ServiceNow Multi-Agent Development Framework powered by AI",
6
6
  "license": "Elastic-2.0",
@@ -35,7 +35,6 @@ function isSysId(value: string): boolean {
35
35
 
36
36
  var FLOW_FACTORY_API_NAME = 'Snow-Flow Flow Factory';
37
37
  var FLOW_FACTORY_API_ID = 'flow_factory';
38
- var FLOW_FACTORY_NAMESPACE = 'x_snflw';
39
38
  var FLOW_FACTORY_CACHE_TTL = 300000; // 5 minutes
40
39
 
41
40
  var _flowFactoryCache: { apiSysId: string; namespace: string; timestamp: number } | null = null;
@@ -48,7 +47,14 @@ var _bootstrapPromise: Promise<{ namespace: string; apiSysId: string }> | null =
48
47
  */
49
48
  var FLOW_FACTORY_SCRIPT = [
50
49
  '(function process(/*RESTAPIRequest*/ request, /*RESTAPIResponse*/ response) {',
51
- ' var body = request.body.data;',
50
+ ' var body;',
51
+ ' try { body = JSON.parse(request.body.dataString); }',
52
+ ' catch(e) { body = request.body.data; }',
53
+ ' if (!body || typeof body !== "object") {',
54
+ ' response.setStatus(400);',
55
+ ' response.setBody({ success: false, error: "Invalid request body: " + typeof body });',
56
+ ' return;',
57
+ ' }',
52
58
  ' var result = { success: false, steps: {} };',
53
59
  '',
54
60
  ' try {',
@@ -101,6 +107,9 @@ var FLOW_FACTORY_SCRIPT = [
101
107
  ' ver.setValue("version", "1.0");',
102
108
  ' ver.setValue("state", shouldActivate ? "published" : "draft");',
103
109
  ' ver.setValue("active", true);',
110
+ ' ver.setValue("compile_state", "compiled");',
111
+ ' ver.setValue("is_current", true);',
112
+ ' if (shouldActivate) ver.setValue("published_flow", flowSysId);',
104
113
  ' if (body.flow_definition) {',
105
114
  ' ver.setValue("flow_definition", typeof body.flow_definition === "string" ? body.flow_definition : JSON.stringify(body.flow_definition));',
106
115
  ' }',
@@ -229,12 +238,97 @@ var FLOW_FACTORY_SCRIPT = [
229
238
  '})(request, response);'
230
239
  ].join('\n');
231
240
 
241
+ /**
242
+ * Probe the ServiceNow instance to discover the correct URL namespace for
243
+ * the Flow Factory Scripted REST API. Sends GET requests to candidate
244
+ * namespaces — a 405 (Method Not Allowed) or 401/403 confirms the namespace
245
+ * exists, while 404 means wrong namespace.
246
+ */
247
+ async function probeFlowFactoryNamespace(
248
+ client: any,
249
+ apiSysId: string,
250
+ instanceUrl: string
251
+ ): Promise<string | null> {
252
+ // Build candidate list from multiple sources
253
+ var candidates: string[] = [];
254
+
255
+ // 1. Read back the record's namespace field
256
+ try {
257
+ var nsResp = await client.get('/api/now/table/sys_ws_definition/' + apiSysId, {
258
+ params: { sysparm_fields: 'sys_id,api_id,namespace', sysparm_display_value: 'all' }
259
+ });
260
+ var record = nsResp.data.result || {};
261
+ var ns = record.namespace;
262
+ if (ns) {
263
+ if (typeof ns === 'object') {
264
+ var dv = ns.display_value || '';
265
+ var val = ns.value || '';
266
+ if (dv && /^(x_|sn_)/.test(dv)) candidates.push(dv);
267
+ if (val && /^(x_|sn_)/.test(val)) candidates.push(val);
268
+ } else if (typeof ns === 'string' && ns.length > 0 && ns !== 'Global' && ns !== 'global') {
269
+ candidates.push(ns);
270
+ }
271
+ }
272
+ } catch (_) {}
273
+
274
+ // 2. Company code from sys_properties
275
+ try {
276
+ var compResp = await client.get('/api/now/table/sys_properties', {
277
+ params: {
278
+ sysparm_query: 'name=glide.appcreator.company.code',
279
+ sysparm_fields: 'value',
280
+ sysparm_limit: 1
281
+ }
282
+ });
283
+ var companyCode = compResp.data.result?.[0]?.value;
284
+ if (companyCode) candidates.push(companyCode);
285
+ } catch (_) {}
286
+
287
+ // 3. Instance subdomain (e.g. "dev351277" from "https://dev351277.service-now.com")
288
+ try {
289
+ var match = instanceUrl.match(/https?:\/\/([^.]+)\./);
290
+ if (match && match[1]) candidates.push(match[1]);
291
+ } catch (_) {}
292
+
293
+ // 4. Fixed fallbacks
294
+ candidates.push('now', 'global');
295
+
296
+ // Deduplicate
297
+ var seen: Record<string, boolean> = {};
298
+ var unique: string[] = [];
299
+ for (var i = 0; i < candidates.length; i++) {
300
+ if (!seen[candidates[i]]) {
301
+ seen[candidates[i]] = true;
302
+ unique.push(candidates[i]);
303
+ }
304
+ }
305
+
306
+ // 5. Probe each candidate — GET on a POST-only endpoint
307
+ for (var j = 0; j < unique.length; j++) {
308
+ try {
309
+ await client.get('/api/' + unique[j] + '/' + FLOW_FACTORY_API_ID + '/create');
310
+ return unique[j]; // 200 = endpoint exists (unlikely but valid)
311
+ } catch (probeError: any) {
312
+ var status = probeError.response?.status;
313
+ if (status === 405 || status === 401 || status === 403) {
314
+ return unique[j]; // Namespace correct — method or auth rejected
315
+ }
316
+ // 404 = wrong namespace, try next
317
+ }
318
+ }
319
+
320
+ return null; // No namespace matched — factory unreachable
321
+ }
322
+
232
323
  /**
233
324
  * Ensure the Flow Factory Scripted REST API exists on the ServiceNow instance.
234
325
  * Idempotent — checks cache first, then instance, deploys only if missing.
235
- * Uses a concurrency lock to prevent duplicate bootstrap calls.
326
+ * Uses namespace probing (HTTP GET) instead of record field parsing.
236
327
  */
237
- async function ensureFlowFactoryAPI(client: any): Promise<{ namespace: string; apiSysId: string }> {
328
+ async function ensureFlowFactoryAPI(
329
+ client: any,
330
+ instanceUrl: string
331
+ ): Promise<{ namespace: string; apiSysId: string }> {
238
332
  // 1. Check in-memory cache
239
333
  if (_flowFactoryCache && (Date.now() - _flowFactoryCache.timestamp) < FLOW_FACTORY_CACHE_TTL) {
240
334
  return { namespace: _flowFactoryCache.namespace, apiSysId: _flowFactoryCache.apiSysId };
@@ -258,7 +352,10 @@ async function ensureFlowFactoryAPI(client: any): Promise<{ namespace: string; a
258
352
 
259
353
  if (checkResp.data.result && checkResp.data.result.length > 0) {
260
354
  var existing = checkResp.data.result[0];
261
- var ns = resolveNamespaceFromRecord(existing);
355
+ var ns = await probeFlowFactoryNamespace(client, existing.sys_id, instanceUrl);
356
+ if (!ns) {
357
+ throw new Error('Flow Factory API exists (sys_id=' + existing.sys_id + ') but namespace could not be resolved via HTTP probing');
358
+ }
262
359
  _flowFactoryCache = { apiSysId: existing.sys_id, namespace: ns, timestamp: Date.now() };
263
360
  return { namespace: ns, apiSysId: existing.sys_id };
264
361
  }
@@ -270,7 +367,7 @@ async function ensureFlowFactoryAPI(client: any): Promise<{ namespace: string; a
270
367
  active: true,
271
368
  short_description: 'Bootstrapped by Snow-Flow MCP for reliable Flow Designer creation via GlideRecord',
272
369
  is_versioned: false,
273
- enforce_acl: 'no',
370
+ enforce_acl: 'false',
274
371
  requires_authentication: true
275
372
  });
276
373
 
@@ -290,7 +387,7 @@ async function ensureFlowFactoryAPI(client: any): Promise<{ namespace: string; a
290
387
  short_description: 'Create a flow or subflow with GlideRecord (triggers all BRs + version record)',
291
388
  operation_script: FLOW_FACTORY_SCRIPT,
292
389
  requires_authentication: true,
293
- enforce_acl: 'no'
390
+ enforce_acl: 'false'
294
391
  });
295
392
  } catch (opError: any) {
296
393
  // Cleanup the API definition if operation creation fails
@@ -298,11 +395,11 @@ async function ensureFlowFactoryAPI(client: any): Promise<{ namespace: string; a
298
395
  throw new Error('Failed to create Scripted REST operation: ' + (opError.message || opError));
299
396
  }
300
397
 
301
- // 6. Read back the actual namespace ServiceNow assigned
302
- var nsResp = await client.get('/api/now/table/sys_ws_definition/' + apiSysId, {
303
- params: { sysparm_fields: 'sys_id,api_id,namespace', sysparm_display_value: 'all' }
304
- });
305
- var resolvedNs = resolveNamespaceFromRecord(nsResp.data.result || {});
398
+ // 6. Probe to discover the namespace ServiceNow assigned
399
+ var resolvedNs = await probeFlowFactoryNamespace(client, apiSysId, instanceUrl);
400
+ if (!resolvedNs) {
401
+ throw new Error('Flow Factory API created (sys_id=' + apiSysId + ') but namespace could not be resolved via HTTP probing');
402
+ }
306
403
 
307
404
  _flowFactoryCache = { apiSysId: apiSysId, namespace: resolvedNs, timestamp: Date.now() };
308
405
  return { namespace: resolvedNs, apiSysId: apiSysId };
@@ -315,29 +412,6 @@ async function ensureFlowFactoryAPI(client: any): Promise<{ namespace: string; a
315
412
  return _bootstrapPromise;
316
413
  }
317
414
 
318
- /**
319
- * Extract the URL namespace from a sys_ws_definition record.
320
- * The namespace field can be a string, a reference object, or empty.
321
- * For global scope APIs the namespace is typically the instance company prefix or 'now'.
322
- */
323
- function resolveNamespaceFromRecord(record: any): string {
324
- var ns = record.namespace;
325
- if (!ns) return FLOW_FACTORY_NAMESPACE;
326
-
327
- // display_value / value pair (sysparm_display_value=all)
328
- if (typeof ns === 'object') {
329
- // display_value is the scope name like "Global" or "x_snflw" — use value for sys_id, display_value for name
330
- var dv = ns.display_value || '';
331
- // If display_value looks like a scope namespace (x_something, sn_something, now, global), use it
332
- if (dv && dv !== 'Global' && dv !== 'global') return dv;
333
- // For Global scope, fall through to default
334
- }
335
- if (typeof ns === 'string' && ns.length > 0 && ns.length < 100) {
336
- return ns;
337
- }
338
- return FLOW_FACTORY_NAMESPACE;
339
- }
340
-
341
415
  /**
342
416
  * Invalidate the Flow Factory cache (e.g. on 404 when API was deleted externally).
343
417
  */
@@ -592,8 +666,23 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
592
666
  var actionsCreated = 0;
593
667
  var varsCreated = 0;
594
668
 
669
+ // Diagnostics: track every step for debugging "flow cannot be found" issues
670
+ var diagnostics: any = {
671
+ factory_bootstrap: null,
672
+ factory_namespace: null,
673
+ factory_call: null,
674
+ table_api_used: false,
675
+ version_created: false,
676
+ version_method: null,
677
+ version_fields_set: [] as string[],
678
+ snapshot_result: null,
679
+ post_verify: null
680
+ };
681
+
595
682
  try {
596
- var factory = await ensureFlowFactoryAPI(client);
683
+ var factory = await ensureFlowFactoryAPI(client, context.instanceUrl);
684
+ diagnostics.factory_bootstrap = 'success';
685
+ diagnostics.factory_namespace = factory.namespace;
597
686
 
598
687
  var factoryPayload = {
599
688
  name: flowName,
@@ -622,7 +711,8 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
622
711
  // 404 = API was deleted externally → invalidate cache, retry once
623
712
  if (callError.response?.status === 404) {
624
713
  invalidateFlowFactoryCache();
625
- var retryFactory = await ensureFlowFactoryAPI(client);
714
+ var retryFactory = await ensureFlowFactoryAPI(client, context.instanceUrl);
715
+ diagnostics.factory_namespace = retryFactory.namespace;
626
716
  var retryEndpoint = '/api/' + retryFactory.namespace + '/' + FLOW_FACTORY_API_ID + '/create';
627
717
  factoryResp = await client.post(retryEndpoint, factoryPayload);
628
718
  } else {
@@ -632,9 +722,13 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
632
722
 
633
723
  var factoryResult = factoryResp.data?.result || factoryResp.data;
634
724
  if (factoryResult && factoryResult.success && factoryResult.flow_sys_id) {
725
+ diagnostics.factory_call = 'success';
635
726
  flowSysId = factoryResult.flow_sys_id;
636
727
  usedMethod = 'scripted_rest_api';
637
728
  versionCreated = !!factoryResult.version_created;
729
+ diagnostics.version_created = versionCreated;
730
+ diagnostics.version_method = 'factory';
731
+ diagnostics.version_fields_set = ['flow', 'name', 'version', 'state', 'active', 'compile_state', 'is_current', 'published_flow'];
638
732
 
639
733
  // Extract step details
640
734
  var steps = factoryResult.steps || {};
@@ -657,14 +751,19 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
657
751
  } catch (factoryError: any) {
658
752
  // Flow Factory unavailable — fall through to Table API
659
753
  var statusCode = factoryError.response?.status;
754
+ var factoryErrMsg = statusCode
755
+ ? 'HTTP ' + statusCode + ': ' + (factoryError.response?.data?.error?.message || factoryError.message || 'unknown')
756
+ : (factoryError.message || 'unknown');
757
+ diagnostics.factory_bootstrap = diagnostics.factory_bootstrap || ('error: ' + factoryErrMsg);
758
+ diagnostics.factory_call = diagnostics.factory_call || ('error: ' + factoryErrMsg);
660
759
  if (statusCode !== 403) {
661
- // Log non-permission errors as warnings (403 = silently skip)
662
- factoryWarnings.push('Flow Factory unavailable (' + (statusCode || factoryError.message || 'unknown') + '), using Table API fallback');
760
+ factoryWarnings.push('Flow Factory unavailable (' + factoryErrMsg + '), using Table API fallback');
663
761
  }
664
762
  }
665
763
 
666
764
  // ── Table API fallback (existing logic) ─────────────────────
667
765
  if (!flowSysId) {
766
+ diagnostics.table_api_used = true;
668
767
  var flowData: any = {
669
768
  name: flowName,
670
769
  description: flowDescription,
@@ -691,12 +790,18 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
691
790
  version: '1.0',
692
791
  state: shouldActivate ? 'published' : 'draft',
693
792
  active: true,
793
+ compile_state: 'compiled',
794
+ is_current: true,
694
795
  flow_definition: JSON.stringify(flowDefinition)
695
796
  };
797
+ if (shouldActivate) versionData.published_flow = flowSysId;
696
798
  var versionResp = await client.post('/api/now/table/sys_hub_flow_version', versionData);
697
799
  var versionSysId = versionResp.data.result?.sys_id;
698
800
  if (versionSysId) {
699
801
  versionCreated = true;
802
+ diagnostics.version_created = true;
803
+ diagnostics.version_method = 'table_api';
804
+ diagnostics.version_fields_set = ['flow', 'name', 'version', 'state', 'active', 'compile_state', 'is_current', 'published_flow'];
700
805
  // Link flow to its version
701
806
  try {
702
807
  await client.patch('/api/now/table/sys_hub_flow/' + flowSysId, {
@@ -817,7 +922,77 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
817
922
  // Best-effort snapshot
818
923
  try {
819
924
  await client.post('/api/sn_flow_designer/flow/snapshot', { flow_id: flowSysId });
820
- } catch (snapError) { /* may not exist */ }
925
+ diagnostics.snapshot_result = 'success';
926
+ } catch (snapError: any) {
927
+ diagnostics.snapshot_result = 'error: ' + (snapError.response?.status || snapError.message || 'unknown');
928
+ }
929
+ }
930
+
931
+ // ── Post-creation verification ─────────────────────────────
932
+ if (flowSysId) {
933
+ try {
934
+ var verifyResp = await client.get('/api/now/table/sys_hub_flow/' + flowSysId, {
935
+ params: { sysparm_fields: 'sys_id,name,latest_version' }
936
+ });
937
+ var verifyFlow = verifyResp.data.result;
938
+ var hasLatestVersion = !!(verifyFlow && verifyFlow.latest_version);
939
+
940
+ // Check if version record exists
941
+ var verCheckResp = await client.get('/api/now/table/sys_hub_flow_version', {
942
+ params: {
943
+ sysparm_query: 'flow=' + flowSysId,
944
+ sysparm_fields: 'sys_id,name,state,compile_state,is_current',
945
+ sysparm_limit: 1
946
+ }
947
+ });
948
+ var verRecords = verCheckResp.data.result || [];
949
+ var hasVersionRecord = verRecords.length > 0;
950
+
951
+ diagnostics.post_verify = {
952
+ flow_exists: true,
953
+ has_latest_version_ref: hasLatestVersion,
954
+ version_record_exists: hasVersionRecord,
955
+ version_details: hasVersionRecord ? {
956
+ sys_id: verRecords[0].sys_id,
957
+ state: verRecords[0].state,
958
+ compile_state: verRecords[0].compile_state,
959
+ is_current: verRecords[0].is_current
960
+ } : null
961
+ };
962
+
963
+ // If version record is missing, retry creation
964
+ if (!hasVersionRecord && !versionCreated) {
965
+ try {
966
+ var retryVersionData: any = {
967
+ flow: flowSysId,
968
+ name: '1.0',
969
+ version: '1.0',
970
+ state: shouldActivate ? 'published' : 'draft',
971
+ active: true,
972
+ compile_state: 'compiled',
973
+ is_current: true,
974
+ flow_definition: JSON.stringify(flowDefinition)
975
+ };
976
+ if (shouldActivate) retryVersionData.published_flow = flowSysId;
977
+
978
+ var retryVerResp = await client.post('/api/now/table/sys_hub_flow_version', retryVersionData);
979
+ var retryVerSysId = retryVerResp.data.result?.sys_id;
980
+ if (retryVerSysId) {
981
+ versionCreated = true;
982
+ diagnostics.version_created = true;
983
+ diagnostics.version_method = (diagnostics.version_method || 'table_api') + ' (retry)';
984
+ try {
985
+ await client.patch('/api/now/table/sys_hub_flow/' + flowSysId, { latest_version: retryVerSysId });
986
+ } catch (_) {}
987
+ factoryWarnings.push('Version record was missing — created via post-verify retry');
988
+ }
989
+ } catch (retryErr: any) {
990
+ factoryWarnings.push('Post-verify version retry failed: ' + (retryErr.message || retryErr));
991
+ }
992
+ }
993
+ } catch (verifyErr: any) {
994
+ diagnostics.post_verify = { error: verifyErr.message || 'verification failed' };
995
+ }
821
996
  }
822
997
 
823
998
  // ── Build summary ───────────────────────────────────────────
@@ -833,8 +1008,14 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
833
1008
  .field('Status', shouldActivate ? 'Published (active)' : 'Draft')
834
1009
  .field('Method', methodLabel);
835
1010
 
1011
+ if (diagnostics.factory_namespace) {
1012
+ createSummary.field('Namespace', diagnostics.factory_namespace);
1013
+ }
1014
+
836
1015
  if (versionCreated) {
837
- createSummary.field('Version', 'v1.0 created');
1016
+ createSummary.field('Version', 'v1.0 created' + (diagnostics.version_method ? ' (' + diagnostics.version_method + ')' : ''));
1017
+ } else {
1018
+ createSummary.warning('Version record NOT created — flow may show "cannot be found" in Flow Designer');
838
1019
  }
839
1020
 
840
1021
  if (!isSubflow && triggerType !== 'manual') {
@@ -851,6 +1032,24 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
851
1032
  createSummary.warning(factoryWarnings[wi]);
852
1033
  }
853
1034
 
1035
+ // Diagnostics section
1036
+ createSummary.blank().line('Diagnostics:');
1037
+ createSummary.indented('Factory bootstrap: ' + (diagnostics.factory_bootstrap || 'not attempted'));
1038
+ createSummary.indented('Factory namespace: ' + (diagnostics.factory_namespace || 'n/a'));
1039
+ createSummary.indented('Factory call: ' + (diagnostics.factory_call || 'not attempted'));
1040
+ createSummary.indented('Table API used: ' + diagnostics.table_api_used);
1041
+ createSummary.indented('Version created: ' + diagnostics.version_created + (diagnostics.version_method ? ' (' + diagnostics.version_method + ')' : ''));
1042
+ createSummary.indented('Snapshot: ' + (diagnostics.snapshot_result || 'n/a'));
1043
+ if (diagnostics.post_verify) {
1044
+ if (diagnostics.post_verify.error) {
1045
+ createSummary.indented('Post-verify: error — ' + diagnostics.post_verify.error);
1046
+ } else {
1047
+ createSummary.indented('Post-verify: flow=' + diagnostics.post_verify.flow_exists +
1048
+ ', version_record=' + diagnostics.post_verify.version_record_exists +
1049
+ ', latest_version_ref=' + diagnostics.post_verify.has_latest_version_ref);
1050
+ }
1051
+ }
1052
+
854
1053
  return createSuccessResult({
855
1054
  created: true,
856
1055
  method: usedMethod,
@@ -872,7 +1071,8 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
872
1071
  activities_created: actionsCreated,
873
1072
  activities_requested: activitiesArg.length,
874
1073
  variables_created: varsCreated,
875
- warnings: factoryWarnings.length > 0 ? factoryWarnings : undefined
1074
+ warnings: factoryWarnings.length > 0 ? factoryWarnings : undefined,
1075
+ diagnostics: diagnostics
876
1076
  }, {}, createSummary.build());
877
1077
  }
878
1078
 
@@ -1225,5 +1425,5 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
1225
1425
  }
1226
1426
  }
1227
1427
 
1228
- export const version = '2.0.0';
1428
+ export const version = '3.0.0';
1229
1429
  export const author = 'Snow-Flow Team';