@astermind/cybernetic-chatbot-client 2.2.30 → 2.2.34

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.
@@ -2663,6 +2663,68 @@ LJ5AZXvOhHaXdHzMuYKX5BpK4w7TqbPvJ6QPvKmLKvHh1VKcUJ6mJQgJJw==
2663
2663
 
2664
2664
  // src/agentic/SiteMapDiscovery.ts
2665
2665
  // Multi-source sitemap discovery and merging for agentic navigation
2666
+ // Zero-config mode: automatically discovers routes on any site
2667
+ // ==================== SMART DEFAULTS ====================
2668
+ /**
2669
+ * Comprehensive default selectors that work on most sites
2670
+ * Covers semantic HTML, common CSS patterns, and framework conventions
2671
+ */
2672
+ const DEFAULT_NAV_SELECTORS = [
2673
+ // Semantic HTML5 navigation
2674
+ 'nav a[href]',
2675
+ '[role="navigation"] a[href]',
2676
+ 'header a[href]',
2677
+ // Common CSS class patterns
2678
+ '.nav a[href]',
2679
+ '.navbar a[href]',
2680
+ '.navigation a[href]',
2681
+ '.sidebar a[href]',
2682
+ '.side-nav a[href]',
2683
+ '.sidenav a[href]',
2684
+ '.menu a[href]',
2685
+ '.main-menu a[href]',
2686
+ '.top-menu a[href]',
2687
+ // Data attributes
2688
+ '[data-nav] a[href]',
2689
+ '[data-menu] a[href]',
2690
+ '[data-navigation] a[href]',
2691
+ // ARIA patterns
2692
+ '[aria-label*="navigation" i] a[href]',
2693
+ '[aria-label*="menu" i] a[href]',
2694
+ // Breadcrumbs
2695
+ '[aria-label*="breadcrumb" i] a[href]',
2696
+ '.breadcrumb a[href]',
2697
+ '.breadcrumbs a[href]',
2698
+ // Generic internal links (fallback - caught last)
2699
+ 'a[href^="/"]',
2700
+ ];
2701
+ /**
2702
+ * Default paths to exclude from discovery
2703
+ * Covers common auth, API, and framework-internal routes
2704
+ */
2705
+ const DEFAULT_EXCLUDE_PATTERNS = [
2706
+ // Authentication routes
2707
+ '/login', '/logout', '/signin', '/signout', '/sign-in', '/sign-out',
2708
+ '/register', '/signup', '/sign-up', '/forgot-password', '/reset-password',
2709
+ '/auth/*', '/oauth/*', '/callback/*', '/sso/*',
2710
+ // API and framework internals
2711
+ '/api/*', '/_next/*', '/_nuxt/*', '/__*',
2712
+ // Special links
2713
+ 'javascript:*', 'mailto:*', 'tel:*', '#*',
2714
+ ];
2715
+ /**
2716
+ * Default maximum number of links to scan from DOM
2717
+ * Prevents performance issues on sites with many links
2718
+ */
2719
+ const DEFAULT_MAX_DOM_LINKS = 200;
2720
+ /**
2721
+ * Default cache TTL for localStorage (5 minutes)
2722
+ */
2723
+ const DEFAULT_CACHE_TTL = 5 * 60 * 1000;
2724
+ /**
2725
+ * Cache version - increment when cache format changes
2726
+ */
2727
+ const CACHE_VERSION = '1';
2666
2728
  /**
2667
2729
  * Multi-source sitemap discovery and management
2668
2730
  *
@@ -2670,6 +2732,9 @@ LJ5AZXvOhHaXdHzMuYKX5BpK4w7TqbPvJ6QPvKmLKvHh1VKcUJ6mJQgJJw==
2670
2732
  * 1. Static props (explicit configuration)
2671
2733
  * 2. Framework auto-discovery (React Router, Vue Router, etc.)
2672
2734
  * 3. Backend API (tenant-specific sitemaps)
2735
+ *
2736
+ * Zero-config mode: When agentic is enabled but no siteMapConfig provided,
2737
+ * automatically discovers routes using smart defaults.
2673
2738
  */
2674
2739
  class SiteMapDiscovery {
2675
2740
  constructor(config, apiUrl, apiKey) {
@@ -2679,6 +2744,8 @@ LJ5AZXvOhHaXdHzMuYKX5BpK4w7TqbPvJ6QPvKmLKvHh1VKcUJ6mJQgJJw==
2679
2744
  this.backendCache = null;
2680
2745
  this.isInitialized = false;
2681
2746
  this.initPromise = null;
2747
+ this.detectedFramework = 'generic';
2748
+ this.popstateHandler = null;
2682
2749
  this.config = config;
2683
2750
  this.apiUrl = apiUrl;
2684
2751
  this.apiKey = apiKey;
@@ -2687,6 +2754,7 @@ LJ5AZXvOhHaXdHzMuYKX5BpK4w7TqbPvJ6QPvKmLKvHh1VKcUJ6mJQgJJw==
2687
2754
  this.staticEntries = this.enhanceEntries(config.static, 'props');
2688
2755
  }
2689
2756
  }
2757
+ // ==================== INITIALIZATION ====================
2690
2758
  /**
2691
2759
  * Initialize all sitemap sources
2692
2760
  * Call this during widget initialization or first query based on loadStrategy
@@ -2701,8 +2769,19 @@ LJ5AZXvOhHaXdHzMuYKX5BpK4w7TqbPvJ6QPvKmLKvHh1VKcUJ6mJQgJJw==
2701
2769
  this.isInitialized = true;
2702
2770
  }
2703
2771
  async performInitialization() {
2772
+ // Wait for DOM to be ready before discovery
2773
+ await this.waitForDOMReady();
2774
+ // Try to load from localStorage cache first
2775
+ const cachedEntries = this.loadFromLocalStorage();
2776
+ if (cachedEntries) {
2777
+ this.discoveredEntries = cachedEntries;
2778
+ console.log(`[SiteMapDiscovery] Loaded ${cachedEntries.length} entries from cache`);
2779
+ // Still run discovery in background to refresh cache
2780
+ this.refreshInBackground();
2781
+ return;
2782
+ }
2704
2783
  const tasks = [];
2705
- // Source 2: Framework auto-discovery
2784
+ // Source 2: Framework auto-discovery (enabled by default)
2706
2785
  if (this.config.discovery?.enabled !== false) {
2707
2786
  tasks.push(this.discoverFromFramework());
2708
2787
  }
@@ -2716,6 +2795,8 @@ LJ5AZXvOhHaXdHzMuYKX5BpK4w7TqbPvJ6QPvKmLKvHh1VKcUJ6mJQgJJw==
2716
2795
  const { entries, source } = result.value;
2717
2796
  if (source === 'discovery') {
2718
2797
  this.discoveredEntries = entries;
2798
+ // Save to localStorage
2799
+ this.saveToLocalStorage(entries);
2719
2800
  }
2720
2801
  else if (source === 'backend') {
2721
2802
  this.backendEntries = entries;
@@ -2725,9 +2806,144 @@ LJ5AZXvOhHaXdHzMuYKX5BpK4w7TqbPvJ6QPvKmLKvHh1VKcUJ6mJQgJJw==
2725
2806
  console.warn('[SiteMapDiscovery] Source failed:', result.reason);
2726
2807
  }
2727
2808
  }
2809
+ // Set up popstate listener for SPA navigation
2810
+ this.setupPopstateListener();
2728
2811
  const total = this.staticEntries.length + this.discoveredEntries.length + this.backendEntries.length;
2729
2812
  console.log(`[SiteMapDiscovery] Initialized with ${total} total entries (${this.staticEntries.length} static, ${this.discoveredEntries.length} discovered, ${this.backendEntries.length} backend)`);
2730
2813
  }
2814
+ /**
2815
+ * Wait for DOM to be ready before scanning
2816
+ */
2817
+ async waitForDOMReady() {
2818
+ if (typeof document === 'undefined')
2819
+ return;
2820
+ if (document.readyState === 'complete' || document.readyState === 'interactive') {
2821
+ // DOM is ready, but give frameworks a microtask to mount
2822
+ await new Promise(resolve => setTimeout(resolve, 0));
2823
+ return;
2824
+ }
2825
+ return new Promise((resolve) => {
2826
+ document.addEventListener('DOMContentLoaded', () => resolve(), { once: true });
2827
+ });
2828
+ }
2829
+ /**
2830
+ * Set up popstate listener to re-discover on SPA navigation
2831
+ */
2832
+ setupPopstateListener() {
2833
+ if (typeof window === 'undefined')
2834
+ return;
2835
+ this.popstateHandler = () => {
2836
+ // Debounce re-discovery
2837
+ setTimeout(() => {
2838
+ this.refreshInBackground();
2839
+ }, 100);
2840
+ };
2841
+ window.addEventListener('popstate', this.popstateHandler);
2842
+ }
2843
+ /**
2844
+ * Refresh discovery in background without blocking
2845
+ */
2846
+ async refreshInBackground() {
2847
+ try {
2848
+ const result = await this.discoverFromFramework();
2849
+ if (result.entries.length > 0) {
2850
+ this.discoveredEntries = result.entries;
2851
+ this.saveToLocalStorage(result.entries);
2852
+ }
2853
+ }
2854
+ catch (error) {
2855
+ // Silent fail for background refresh
2856
+ console.debug('[SiteMapDiscovery] Background refresh failed:', error);
2857
+ }
2858
+ }
2859
+ // ==================== LOCALSTORAGE CACHING ====================
2860
+ /**
2861
+ * Get localStorage cache key for current origin
2862
+ */
2863
+ getCacheKey() {
2864
+ if (typeof window === 'undefined')
2865
+ return 'astermind-sitemap';
2866
+ return `astermind-sitemap-${window.location.origin}`;
2867
+ }
2868
+ /**
2869
+ * Load discovered entries from localStorage cache
2870
+ */
2871
+ loadFromLocalStorage() {
2872
+ if (typeof localStorage === 'undefined')
2873
+ return null;
2874
+ try {
2875
+ const key = this.getCacheKey();
2876
+ const cached = localStorage.getItem(key);
2877
+ if (!cached)
2878
+ return null;
2879
+ const data = JSON.parse(cached);
2880
+ // Check version
2881
+ if (data.version !== CACHE_VERSION) {
2882
+ localStorage.removeItem(key);
2883
+ return null;
2884
+ }
2885
+ // Check TTL
2886
+ const ttl = this.config.discovery?.cacheTtl ?? DEFAULT_CACHE_TTL;
2887
+ if (Date.now() - data.timestamp > ttl) {
2888
+ localStorage.removeItem(key);
2889
+ return null;
2890
+ }
2891
+ this.detectedFramework = data.framework;
2892
+ return data.entries;
2893
+ }
2894
+ catch (error) {
2895
+ console.debug('[SiteMapDiscovery] Failed to load from cache:', error);
2896
+ return null;
2897
+ }
2898
+ }
2899
+ /**
2900
+ * Save discovered entries to localStorage cache
2901
+ */
2902
+ saveToLocalStorage(entries) {
2903
+ if (typeof localStorage === 'undefined')
2904
+ return;
2905
+ if (this.config.discovery?.cacheRoutes === false)
2906
+ return;
2907
+ try {
2908
+ const key = this.getCacheKey();
2909
+ const data = {
2910
+ entries,
2911
+ url: typeof window !== 'undefined' ? window.location.href : '',
2912
+ timestamp: Date.now(),
2913
+ framework: this.detectedFramework,
2914
+ version: CACHE_VERSION
2915
+ };
2916
+ localStorage.setItem(key, JSON.stringify(data));
2917
+ }
2918
+ catch (error) {
2919
+ // localStorage might be full or disabled
2920
+ console.debug('[SiteMapDiscovery] Failed to save to cache:', error);
2921
+ }
2922
+ }
2923
+ /**
2924
+ * Clear localStorage cache
2925
+ */
2926
+ clearCache() {
2927
+ if (typeof localStorage === 'undefined')
2928
+ return;
2929
+ localStorage.removeItem(this.getCacheKey());
2930
+ }
2931
+ // ==================== CLEANUP ====================
2932
+ /**
2933
+ * Dispose of resources (event listeners, etc.)
2934
+ */
2935
+ dispose() {
2936
+ if (this.popstateHandler && typeof window !== 'undefined') {
2937
+ window.removeEventListener('popstate', this.popstateHandler);
2938
+ this.popstateHandler = null;
2939
+ }
2940
+ }
2941
+ /**
2942
+ * Get detected framework name
2943
+ */
2944
+ getDetectedFramework() {
2945
+ return this.detectedFramework;
2946
+ }
2731
2947
  /**
2732
2948
  * Get merged sitemap entries with deduplication
2733
2949
  */
@@ -2754,6 +2970,7 @@ LJ5AZXvOhHaXdHzMuYKX5BpK4w7TqbPvJ6QPvKmLKvHh1VKcUJ6mJQgJJw==
2754
2970
  try {
2755
2971
  // Detect framework if not specified
2756
2972
  const framework = config.framework || this.detectFramework();
2973
+ this.detectedFramework = framework;
2757
2974
  let routes = [];
2758
2975
  switch (framework) {
2759
2976
  case 'react-router':
@@ -2937,26 +3154,31 @@ LJ5AZXvOhHaXdHzMuYKX5BpK4w7TqbPvJ6QPvKmLKvHh1VKcUJ6mJQgJJw==
2937
3154
  }
2938
3155
  /**
2939
3156
  * Discover routes by scanning DOM navigation elements
3157
+ * Uses smart defaults when no selectors provided
2940
3158
  */
2941
3159
  discoverFromDOM(selectors) {
2942
3160
  const routes = [];
2943
3161
  if (typeof document === 'undefined')
2944
3162
  return routes;
2945
- const defaultSelectors = [
2946
- 'nav a[href]',
2947
- '[role="navigation"] a[href]',
2948
- '.nav a[href]',
2949
- '.navigation a[href]',
2950
- '.sidebar a[href]',
2951
- '.menu a[href]',
2952
- 'header a[href]'
2953
- ];
2954
- const allSelectors = selectors || defaultSelectors;
3163
+ // Use provided selectors or smart defaults
3164
+ const allSelectors = selectors || DEFAULT_NAV_SELECTORS;
3165
+ // Get max links limit from config or use default
3166
+ const maxLinks = this.config.discovery?.maxDomLinks ?? DEFAULT_MAX_DOM_LINKS;
3167
+ // Get exclude patterns (merge user-provided with defaults)
3168
+ const userExcludes = this.config.discovery?.excludePaths || [];
3169
+ const allExcludes = [...DEFAULT_EXCLUDE_PATTERNS, ...userExcludes];
2955
3170
  const seen = new Set();
3171
+ let linkCount = 0;
2956
3172
  for (const selector of allSelectors) {
3173
+ // Stop if we've hit the limit
3174
+ if (linkCount >= maxLinks)
3175
+ break;
2957
3176
  try {
2958
3177
  const links = document.querySelectorAll(selector);
2959
3178
  for (const link of links) {
3179
+ // Stop if we've hit the limit
3180
+ if (linkCount >= maxLinks)
3181
+ break;
2960
3182
  const href = link.getAttribute('href');
2961
3183
  if (!href)
2962
3184
  continue;
@@ -2965,14 +3187,31 @@ LJ5AZXvOhHaXdHzMuYKX5BpK4w7TqbPvJ6QPvKmLKvHh1VKcUJ6mJQgJJw==
2965
3187
  continue;
2966
3188
  }
2967
3189
  // Normalize path
2968
- const path = href.startsWith('/') ? href : `/${href}`;
2969
- // Skip duplicates, anchors, and special links
2970
- if (seen.has(path) || path.startsWith('/#') || path === '/' || path.includes('javascript:')) {
2971
- continue;
3190
+ let path = href;
3191
+ if (href.startsWith('http')) {
3192
+ try {
3193
+ path = new URL(href).pathname;
3194
+ }
3195
+ catch {
3196
+ continue;
3197
+ }
3198
+ }
3199
+ else if (!href.startsWith('/')) {
3200
+ path = `/${href}`;
2972
3201
  }
2973
- seen.add(path);
2974
3202
  // Clean path (remove query string and hash)
2975
3203
  const cleanPath = path.split('?')[0].split('#')[0];
3204
+ // Skip duplicates
3205
+ if (seen.has(cleanPath))
3206
+ continue;
3207
+ // Skip root path
3208
+ if (cleanPath === '/')
3209
+ continue;
3210
+ // Skip paths matching exclude patterns
3211
+ if (this.shouldExcludeRoute(cleanPath, allExcludes))
3212
+ continue;
3213
+ seen.add(cleanPath);
3214
+ linkCount++;
2976
3215
  routes.push({
2977
3216
  path: cleanPath,
2978
3217
  name: link.textContent?.trim() || this.pathToName(cleanPath)
@@ -2983,6 +3222,7 @@ LJ5AZXvOhHaXdHzMuYKX5BpK4w7TqbPvJ6QPvKmLKvHh1VKcUJ6mJQgJJw==
2983
3222
  // Invalid selector - skip
2984
3223
  }
2985
3224
  }
3225
+ console.log(`[SiteMapDiscovery] DOM scan found ${routes.length} routes (limit: ${maxLinks})`);
2986
3226
  return routes;
2987
3227
  }
2988
3228
  // ==================== SOURCE 3: BACKEND API ====================
@@ -3270,9 +3510,18 @@ LJ5AZXvOhHaXdHzMuYKX5BpK4w7TqbPvJ6QPvKmLKvHh1VKcUJ6mJQgJJw==
3270
3510
  this.modalIndex = new Map();
3271
3511
  // Build indexes for fast lookup
3272
3512
  this.buildIndexes();
3273
- // Initialize multi-source sitemap discovery if configured
3274
- if (config.siteMapConfig) {
3275
- this.siteMapDiscovery = new SiteMapDiscovery(config.siteMapConfig, apiUrl, apiKey);
3513
+ // Auto-enable sitemap discovery when agentic is enabled but no explicit config provided
3514
+ // This enables zero-config mode: just set enabled: true and discovery works automatically
3515
+ const siteMapConfig = config.siteMapConfig ?? (config.enabled && !config.siteMap?.length
3516
+ ? { discovery: { enabled: true } }
3517
+ : undefined);
3518
+ // Initialize multi-source sitemap discovery
3519
+ if (siteMapConfig) {
3520
+ this.siteMapDiscovery = new SiteMapDiscovery(siteMapConfig, apiUrl, apiKey);
3521
+ // Log zero-config mode activation
3522
+ if (!config.siteMapConfig && config.enabled) {
3523
+ console.log('[CyberneticIntentClassifier] Zero-config mode: auto-discovery enabled');
3524
+ }
3276
3525
  }
3277
3526
  }
3278
3527
  /**