snow-flow 10.0.1-dev.398 → 10.0.1-dev.399

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.398",
3
+ "version": "10.0.1-dev.399",
4
4
  "name": "snow-flow",
5
5
  "description": "Snow-Flow - ServiceNow Multi-Agent Development Framework powered by AI",
6
6
  "license": "Elastic-2.0",
@@ -344,52 +344,57 @@ var FLOW_FACTORY_SCRIPT = [
344
344
 
345
345
  /**
346
346
  * Resolve the REST API namespace for a sys_ws_definition record.
347
- * Uses dot-walking to read the scope string from the related sys_scope record,
348
- * which is deterministic and doesn't depend on endpoint registration timing.
349
- * Falls back to HTTP probing if dot-walking fails.
347
+ *
348
+ * Collects namespace candidates from multiple sources, then VERIFIES each
349
+ * one via HTTP before accepting it. This prevents returning invalid values
350
+ * like scope sys_ids or numeric identifiers that aren't valid URL namespaces.
351
+ *
352
+ * Verification: GET /api/{candidate}/{api_id}/discover
353
+ * - 200 = correct namespace, /discover endpoint works
354
+ * - 401/403 = correct namespace, auth issue
355
+ * - 405 = correct namespace, wrong HTTP method (endpoint exists)
356
+ * - 400/404 = wrong namespace or API not registered yet
350
357
  */
351
358
  async function resolveFactoryNamespace(
352
359
  client: any,
353
360
  apiSysId: string,
354
361
  instanceUrl: string
355
362
  ): Promise<string | null> {
356
- // ── Strategy 1: Dot-walk to sys_scope.scope (deterministic) ──
363
+ // ── Collect namespace candidates (ordered by likelihood) ──
364
+ var candidates: string[] = [];
365
+
366
+ // Most common for PDI / global scope custom APIs
367
+ candidates.push('global');
368
+
369
+ // Dot-walk to sys_scope.scope
357
370
  try {
358
371
  var dotWalkResp = await client.get('/api/now/table/sys_ws_definition/' + apiSysId, {
359
372
  params: {
360
- sysparm_fields: 'namespace,namespace.scope',
373
+ sysparm_fields: 'namespace.scope',
361
374
  sysparm_display_value: 'false'
362
375
  }
363
376
  });
364
377
  var scopeStr = dotWalkResp.data.result?.['namespace.scope'];
365
- if (scopeStr && typeof scopeStr === 'string' && scopeStr.length > 0) {
366
- return scopeStr;
378
+ if (scopeStr && typeof scopeStr === 'string' && scopeStr.length > 1) {
379
+ candidates.push(scopeStr);
367
380
  }
368
381
  } catch (_) {}
369
382
 
370
- // ── Strategy 2: Read namespace field with display_value=all ──
383
+ // Read namespace display_value (might be scope string itself)
371
384
  try {
372
385
  var nsResp = await client.get('/api/now/table/sys_ws_definition/' + apiSysId, {
373
- params: { sysparm_fields: 'namespace', sysparm_display_value: 'all' }
386
+ params: { sysparm_fields: 'namespace', sysparm_display_value: 'true' }
374
387
  });
375
- var ns = nsResp.data.result?.namespace;
376
- if (ns) {
377
- // If it's a reference object, extract useful values
378
- if (typeof ns === 'object') {
379
- var dv = ns.display_value || '';
380
- var val = ns.value || '';
381
- // display_value might be the scope string itself (e.g. "global", "x_snflw")
382
- if (dv && dv !== 'Global') return dv;
383
- // If display_value is "Global", the scope string is typically "global"
384
- if (dv === 'Global') return 'global';
385
- if (val && val.length > 0) return val;
386
- } else if (typeof ns === 'string' && ns.length > 0) {
387
- return ns === 'Global' ? 'global' : ns;
388
- }
388
+ var nsDisplay = nsResp.data.result?.namespace;
389
+ if (typeof nsDisplay === 'string' && nsDisplay.length > 1) {
390
+ candidates.push(nsDisplay === 'Global' ? 'global' : nsDisplay);
389
391
  }
390
392
  } catch (_) {}
391
393
 
392
- // ── Strategy 3: Company code from sys_properties ──
394
+ // OOB namespace
395
+ candidates.push('now');
396
+
397
+ // Company code from sys_properties
393
398
  try {
394
399
  var compResp = await client.get('/api/now/table/sys_properties', {
395
400
  params: {
@@ -399,27 +404,65 @@ async function resolveFactoryNamespace(
399
404
  }
400
405
  });
401
406
  var companyCode = compResp.data.result?.[0]?.value;
402
- if (companyCode) return companyCode;
407
+ if (companyCode) candidates.push(companyCode);
403
408
  } catch (_) {}
404
409
 
405
- // ── Strategy 4: HTTP probing as last resort ──
406
- var probeCandidates = ['global', 'now'];
410
+ // Instance subdomain (e.g. "dev354059")
407
411
  try {
408
412
  var match = instanceUrl.match(/https?:\/\/([^.]+)\./);
409
- if (match && match[1]) probeCandidates.push(match[1]);
413
+ if (match && match[1]) candidates.push(match[1]);
410
414
  } catch (_) {}
411
415
 
412
- for (var j = 0; j < probeCandidates.length; j++) {
416
+ // ── Deduplicate ──
417
+ var seen: Record<string, boolean> = {};
418
+ var unique: string[] = [];
419
+ for (var i = 0; i < candidates.length; i++) {
420
+ var lower = candidates[i].toLowerCase();
421
+ if (!seen[lower]) {
422
+ seen[lower] = true;
423
+ unique.push(lower);
424
+ }
425
+ }
426
+
427
+ // ── Verify each candidate via HTTP ──
428
+ for (var j = 0; j < unique.length; j++) {
429
+ var ns = unique[j];
430
+ // Check 1: GET /discover (v5 endpoint, returns 200 with discovery data)
413
431
  try {
414
- var probeResp = await client.get('/api/' + probeCandidates[j] + '/' + FLOW_FACTORY_API_ID + '/discover');
415
- if (probeResp.status === 200 || probeResp.data) return probeCandidates[j];
416
- } catch (probeErr: any) {
417
- var ps = probeErr.response?.status;
418
- if (ps === 401 || ps === 403 || ps === 405) return probeCandidates[j];
432
+ var discoverResp = await client.get('/api/' + ns + '/' + FLOW_FACTORY_API_ID + '/discover');
433
+ if (discoverResp.status === 200 || discoverResp.data) return ns;
434
+ } catch (discoverErr: any) {
435
+ var ds = discoverErr.response?.status;
436
+ if (ds === 401 || ds === 403 || ds === 405) return ns;
437
+ // 400/404 = wrong namespace or not registered yet
438
+ }
439
+ // Check 2: GET /create (POST-only, expect 405 for correct namespace)
440
+ try {
441
+ await client.get('/api/' + ns + '/' + FLOW_FACTORY_API_ID + '/create');
442
+ return ns; // 200 = unexpected but valid
443
+ } catch (createErr: any) {
444
+ var cs = createErr.response?.status;
445
+ if (cs === 405 || cs === 401 || cs === 403) return ns;
446
+ // 400/404 = wrong namespace (ServiceNow may return 400 instead of 405)
447
+ }
448
+ // Check 3: POST /create with empty body — distinguishes "wrong namespace" from
449
+ // "correct namespace but script validation error". Wrong namespace returns
450
+ // 400 with "Requested URI does not represent any resource". Our script returns
451
+ // a different error body (e.g. {success:false, error:"..."}).
452
+ try {
453
+ await client.post('/api/' + ns + '/' + FLOW_FACTORY_API_ID + '/create', {});
454
+ return ns; // Unexpected success, namespace confirmed
455
+ } catch (postErr: any) {
456
+ var pe = postErr.response;
457
+ if (!pe) continue;
458
+ if (pe.status === 401 || pe.status === 403) return ns;
459
+ // Check error body — "Requested URI" = wrong namespace, anything else = our script
460
+ var errStr = JSON.stringify(pe.data || '');
461
+ if (!errStr.includes('Requested URI')) return ns;
419
462
  }
420
463
  }
421
464
 
422
- return null;
465
+ return null; // No candidate verified — API may not be registered yet
423
466
  }
424
467
 
425
468
  /**
@@ -546,25 +589,19 @@ async function ensureFlowFactoryAPI(
546
589
  // Non-fatal: create endpoint is more important than discover
547
590
  }
548
591
 
549
- // 6. Resolve namespace read directly from the record (deterministic)
592
+ // 6. Wait for ServiceNow REST framework to register endpoints
593
+ await new Promise(resolve => setTimeout(resolve, 4000));
594
+
595
+ // 7. Resolve namespace via HTTP-verified probing
550
596
  var resolvedNs = await resolveFactoryNamespace(client, apiSysId, instanceUrl);
551
597
 
552
- // 7. If direct resolution failed, wait for REST framework and retry
598
+ // 8. If resolution failed, wait longer and retry (some instances are slow)
553
599
  if (!resolvedNs) {
554
- await new Promise(resolve => setTimeout(resolve, 3000));
600
+ await new Promise(resolve => setTimeout(resolve, 5000));
555
601
  resolvedNs = await resolveFactoryNamespace(client, apiSysId, instanceUrl);
556
602
  }
557
603
  if (!resolvedNs) {
558
- // Last resort: try 'global' (most common for PDI instances)
559
- try {
560
- var globalTest = await client.get('/api/global/' + FLOW_FACTORY_API_ID + '/discover');
561
- if (globalTest.status === 200 || globalTest.data) resolvedNs = 'global';
562
- } catch (gtErr: any) {
563
- if (gtErr.response?.status === 401 || gtErr.response?.status === 403) resolvedNs = 'global';
564
- }
565
- }
566
- if (!resolvedNs) {
567
- throw new Error('Flow Factory deployed (sys_id=' + apiSysId + ') but namespace could not be resolved. This may indicate a ServiceNow scope/permissions issue.');
604
+ throw new Error('Flow Factory deployed (sys_id=' + apiSysId + ') but no namespace candidate could be verified via HTTP after 9s. Candidates tried: global, dot-walk scope, display_value, now, company code, subdomain.');
568
605
  }
569
606
 
570
607
  _flowFactoryCache = { apiSysId: apiSysId, namespace: resolvedNs, timestamp: Date.now() };