snow-flow-test 10.0.1-test.108 → 10.0.1-test.110

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.108",
3
+ "version": "10.0.1-test.110",
4
4
  "name": "snow-flow-test",
5
5
  "description": "Snow-Flow Test - ServiceNow Multi-Agent Development Framework",
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 };
@@ -247,32 +341,34 @@ async function ensureFlowFactoryAPI(client: any): Promise<{ namespace: string; a
247
341
 
248
342
  _bootstrapPromise = (async () => {
249
343
  try {
250
- // 3. Check if API already exists on instance
344
+ // 3. Check if API already exists on instance (query by api_id, more reliable than name)
251
345
  var checkResp = await client.get('/api/now/table/sys_ws_definition', {
252
346
  params: {
253
- sysparm_query: 'name=' + FLOW_FACTORY_API_NAME,
254
- sysparm_fields: 'sys_id,namespace.name',
347
+ sysparm_query: 'api_id=' + FLOW_FACTORY_API_ID,
348
+ sysparm_fields: 'sys_id,api_id,namespace',
255
349
  sysparm_limit: 1
256
350
  }
257
351
  });
258
352
 
259
353
  if (checkResp.data.result && checkResp.data.result.length > 0) {
260
354
  var existing = checkResp.data.result[0];
261
- var ns = (existing['namespace.name'] || existing.namespace?.display_value || FLOW_FACTORY_NAMESPACE);
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
  }
265
362
 
266
- // 4. Deploy the Scripted REST API
363
+ // 4. Deploy the Scripted REST API (do NOT set namespace — let ServiceNow assign it)
267
364
  var apiResp = await client.post('/api/now/table/sys_ws_definition', {
268
365
  name: FLOW_FACTORY_API_NAME,
269
366
  api_id: FLOW_FACTORY_API_ID,
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',
274
- requires_authentication: true,
275
- namespace: FLOW_FACTORY_NAMESPACE
370
+ enforce_acl: 'false',
371
+ requires_authentication: true
276
372
  });
277
373
 
278
374
  var apiSysId = apiResp.data.result?.sys_id;
@@ -291,7 +387,7 @@ async function ensureFlowFactoryAPI(client: any): Promise<{ namespace: string; a
291
387
  short_description: 'Create a flow or subflow with GlideRecord (triggers all BRs + version record)',
292
388
  operation_script: FLOW_FACTORY_SCRIPT,
293
389
  requires_authentication: true,
294
- enforce_acl: 'no'
390
+ enforce_acl: 'false'
295
391
  });
296
392
  } catch (opError: any) {
297
393
  // Cleanup the API definition if operation creation fails
@@ -299,18 +395,10 @@ async function ensureFlowFactoryAPI(client: any): Promise<{ namespace: string; a
299
395
  throw new Error('Failed to create Scripted REST operation: ' + (opError.message || opError));
300
396
  }
301
397
 
302
- // Resolve actual namespace may differ from requested
303
- var nsResp = await client.get('/api/now/table/sys_ws_definition/' + apiSysId, {
304
- params: { sysparm_fields: 'sys_id,namespace' }
305
- });
306
- var resolvedNs = FLOW_FACTORY_NAMESPACE;
307
- if (nsResp.data.result?.namespace) {
308
- var nsVal = nsResp.data.result.namespace;
309
- if (typeof nsVal === 'object' && nsVal.display_value) {
310
- resolvedNs = nsVal.display_value;
311
- } else if (typeof nsVal === 'string' && nsVal.length > 0) {
312
- resolvedNs = nsVal;
313
- }
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');
314
402
  }
315
403
 
316
404
  _flowFactoryCache = { apiSysId: apiSysId, namespace: resolvedNs, timestamp: Date.now() };
@@ -578,8 +666,23 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
578
666
  var actionsCreated = 0;
579
667
  var varsCreated = 0;
580
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
+
581
682
  try {
582
- var factory = await ensureFlowFactoryAPI(client);
683
+ var factory = await ensureFlowFactoryAPI(client, context.instanceUrl);
684
+ diagnostics.factory_bootstrap = 'success';
685
+ diagnostics.factory_namespace = factory.namespace;
583
686
 
584
687
  var factoryPayload = {
585
688
  name: flowName,
@@ -608,7 +711,8 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
608
711
  // 404 = API was deleted externally → invalidate cache, retry once
609
712
  if (callError.response?.status === 404) {
610
713
  invalidateFlowFactoryCache();
611
- var retryFactory = await ensureFlowFactoryAPI(client);
714
+ var retryFactory = await ensureFlowFactoryAPI(client, context.instanceUrl);
715
+ diagnostics.factory_namespace = retryFactory.namespace;
612
716
  var retryEndpoint = '/api/' + retryFactory.namespace + '/' + FLOW_FACTORY_API_ID + '/create';
613
717
  factoryResp = await client.post(retryEndpoint, factoryPayload);
614
718
  } else {
@@ -618,9 +722,13 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
618
722
 
619
723
  var factoryResult = factoryResp.data?.result || factoryResp.data;
620
724
  if (factoryResult && factoryResult.success && factoryResult.flow_sys_id) {
725
+ diagnostics.factory_call = 'success';
621
726
  flowSysId = factoryResult.flow_sys_id;
622
727
  usedMethod = 'scripted_rest_api';
623
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'];
624
732
 
625
733
  // Extract step details
626
734
  var steps = factoryResult.steps || {};
@@ -643,14 +751,19 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
643
751
  } catch (factoryError: any) {
644
752
  // Flow Factory unavailable — fall through to Table API
645
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);
646
759
  if (statusCode !== 403) {
647
- // Log non-permission errors as warnings (403 = silently skip)
648
- factoryWarnings.push('Flow Factory unavailable (' + (statusCode || factoryError.message || 'unknown') + '), using Table API fallback');
760
+ factoryWarnings.push('Flow Factory unavailable (' + factoryErrMsg + '), using Table API fallback');
649
761
  }
650
762
  }
651
763
 
652
764
  // ── Table API fallback (existing logic) ─────────────────────
653
765
  if (!flowSysId) {
766
+ diagnostics.table_api_used = true;
654
767
  var flowData: any = {
655
768
  name: flowName,
656
769
  description: flowDescription,
@@ -669,6 +782,40 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
669
782
  var createdFlow = flowResponse.data.result;
670
783
  flowSysId = createdFlow.sys_id;
671
784
 
785
+ // Create sys_hub_flow_version via Table API (critical for Flow Designer UI)
786
+ try {
787
+ var versionData: any = {
788
+ flow: flowSysId,
789
+ name: '1.0',
790
+ version: '1.0',
791
+ state: shouldActivate ? 'published' : 'draft',
792
+ active: true,
793
+ compile_state: 'compiled',
794
+ is_current: true,
795
+ flow_definition: JSON.stringify(flowDefinition)
796
+ };
797
+ if (shouldActivate) versionData.published_flow = flowSysId;
798
+ var versionResp = await client.post('/api/now/table/sys_hub_flow_version', versionData);
799
+ var versionSysId = versionResp.data.result?.sys_id;
800
+ if (versionSysId) {
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'];
805
+ // Link flow to its version
806
+ try {
807
+ await client.patch('/api/now/table/sys_hub_flow/' + flowSysId, {
808
+ latest_version: versionSysId
809
+ });
810
+ } catch (linkError) {
811
+ // Version exists but link failed — still better than no version
812
+ factoryWarnings.push('Version created but latest_version link failed');
813
+ }
814
+ }
815
+ } catch (verError: any) {
816
+ factoryWarnings.push('sys_hub_flow_version creation failed: ' + (verError.message || verError));
817
+ }
818
+
672
819
  // Create trigger instance (non-manual flows only)
673
820
  if (!isSubflow && triggerType !== 'manual') {
674
821
  try {
@@ -775,7 +922,77 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
775
922
  // Best-effort snapshot
776
923
  try {
777
924
  await client.post('/api/sn_flow_designer/flow/snapshot', { flow_id: flowSysId });
778
- } 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
+ }
779
996
  }
780
997
 
781
998
  // ── Build summary ───────────────────────────────────────────
@@ -791,8 +1008,14 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
791
1008
  .field('Status', shouldActivate ? 'Published (active)' : 'Draft')
792
1009
  .field('Method', methodLabel);
793
1010
 
1011
+ if (diagnostics.factory_namespace) {
1012
+ createSummary.field('Namespace', diagnostics.factory_namespace);
1013
+ }
1014
+
794
1015
  if (versionCreated) {
795
- 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');
796
1019
  }
797
1020
 
798
1021
  if (!isSubflow && triggerType !== 'manual') {
@@ -809,6 +1032,24 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
809
1032
  createSummary.warning(factoryWarnings[wi]);
810
1033
  }
811
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
+
812
1053
  return createSuccessResult({
813
1054
  created: true,
814
1055
  method: usedMethod,
@@ -830,7 +1071,8 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
830
1071
  activities_created: actionsCreated,
831
1072
  activities_requested: activitiesArg.length,
832
1073
  variables_created: varsCreated,
833
- warnings: factoryWarnings.length > 0 ? factoryWarnings : undefined
1074
+ warnings: factoryWarnings.length > 0 ? factoryWarnings : undefined,
1075
+ diagnostics: diagnostics
834
1076
  }, {}, createSummary.build());
835
1077
  }
836
1078
 
@@ -1183,5 +1425,5 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
1183
1425
  }
1184
1426
  }
1185
1427
 
1186
- export const version = '2.0.0';
1428
+ export const version = '3.0.0';
1187
1429
  export const author = 'Snow-Flow Team';