vanilla-agent 1.24.0 → 1.26.0

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.
@@ -1,2 +1,2 @@
1
- "use strict";var SiteAgentInstaller=(()=>{(function(){"use strict";if(window.__siteAgentInstallerLoaded)return;window.__siteAgentInstallerLoaded=!0;let c=(()=>{let e=document.currentScript;if(!e)return{};let t={},i=e.getAttribute("data-travrse-token");i&&(t.clientToken=i);let o=e.getAttribute("data-flow-id");o&&(t.flowId=o);let l=e.getAttribute("data-api-url");return l&&(t.apiUrl=l),t})(),n={...window.siteAgentConfig||{},...c},d=n.version||"latest",g=n.cdn||"jsdelivr",f=n.autoInit!==!1,u=()=>{if(n.cssUrl&&n.jsUrl)return{cssUrl:n.cssUrl,jsUrl:n.jsUrl};let t=`/npm/vanilla-agent@${d}/dist`;return g==="unpkg"?{cssUrl:`https://unpkg.com${t}/widget.css`,jsUrl:`https://unpkg.com${t}/index.global.js`}:{cssUrl:`https://cdn.jsdelivr.net${t}/widget.css`,jsUrl:`https://cdn.jsdelivr.net${t}/index.global.js`}},{cssUrl:r,jsUrl:s}=u(),w=()=>!!document.head.querySelector("link[data-vanilla-agent]")||!!document.head.querySelector('link[href*="widget.css"]'),p=()=>!!window.AgentWidget,A=()=>new Promise((e,t)=>{if(w()){e();return}let i=document.createElement("link");i.rel="stylesheet",i.href=r,i.setAttribute("data-vanilla-agent","true"),i.onload=()=>e(),i.onerror=()=>t(new Error(`Failed to load CSS from ${r}`)),document.head.appendChild(i)}),m=()=>new Promise((e,t)=>{if(p()){e();return}let i=document.createElement("script");i.src=s,i.async=!0,i.onload=()=>e(),i.onerror=()=>t(new Error(`Failed to load JS from ${s}`)),document.head.appendChild(i)}),C=()=>{if(!window.AgentWidget||!window.AgentWidget.initAgentWidget){console.warn("AgentWidget not available. Make sure the script loaded successfully.");return}let e=n.target||"body",t={...n.config};if(n.apiUrl&&!t.apiUrl&&(t.apiUrl=n.apiUrl),n.clientToken&&!t.clientToken&&(t.clientToken=n.clientToken),n.flowId&&!t.flowId&&(t.flowId=n.flowId),!(!(t.apiUrl||t.clientToken)&&Object.keys(t).length===0))try{window.AgentWidget.initAgentWidget({target:e,config:t})}catch(o){console.error("Failed to initialize AgentWidget:",o)}},a=async()=>{try{await A(),await m(),f&&(n.config||n.apiUrl||n.clientToken)&&setTimeout(C,0)}catch(e){console.error("Failed to install AgentWidget:",e)}};document.readyState==="loading"?document.addEventListener("DOMContentLoaded",a):a()})();})();
1
+ "use strict";var SiteAgentInstaller=(()=>{(function(){"use strict";if(window.__siteAgentInstallerLoaded)return;window.__siteAgentInstallerLoaded=!0;let d=(()=>{let n=document.currentScript;if(!n)return{};let t={},i=n.getAttribute("data-config");if(i)try{let r=JSON.parse(i);r.config?Object.assign(t,r):t.config=r}catch(r){console.error("Failed to parse data-config JSON:",r)}let o=n.getAttribute("data-travrse-token");o&&(t.clientToken=o);let s=n.getAttribute("data-flow-id");s&&(t.flowId=s);let c=n.getAttribute("data-api-url");return c&&(t.apiUrl=c),t})(),e={...window.siteAgentConfig||{},...d},g=e.version||"latest",f=e.cdn||"jsdelivr",u=e.autoInit!==!1,w=()=>{if(e.cssUrl&&e.jsUrl)return{cssUrl:e.cssUrl,jsUrl:e.jsUrl};let t=`/npm/vanilla-agent@${g}/dist`;return f==="unpkg"?{cssUrl:`https://unpkg.com${t}/widget.css`,jsUrl:`https://unpkg.com${t}/index.global.js`}:{cssUrl:`https://cdn.jsdelivr.net${t}/widget.css`,jsUrl:`https://cdn.jsdelivr.net${t}/index.global.js`}},{cssUrl:a,jsUrl:l}=w(),p=()=>!!document.head.querySelector("link[data-vanilla-agent]")||!!document.head.querySelector('link[href*="widget.css"]'),m=()=>!!window.AgentWidget,A=n=>{let t=!1,i=()=>{t||(t=!0,n())},o=()=>{typeof requestIdleCallback!="undefined"?requestIdleCallback(()=>{requestAnimationFrame(()=>{requestAnimationFrame(i)})},{timeout:3e3}):setTimeout(i,300)};document.readyState==="loading"?document.addEventListener("DOMContentLoaded",o):o()},C=()=>new Promise((n,t)=>{if(p()){n();return}let i=document.createElement("link");i.rel="stylesheet",i.href=a,i.setAttribute("data-vanilla-agent","true"),i.onload=()=>n(),i.onerror=()=>t(new Error(`Failed to load CSS from ${a}`)),document.head.appendChild(i)}),h=()=>new Promise((n,t)=>{if(m()){n();return}let i=document.createElement("script");i.src=l,i.async=!0,i.onload=()=>n(),i.onerror=()=>t(new Error(`Failed to load JS from ${l}`)),document.head.appendChild(i)}),k=()=>{var o;if(!window.AgentWidget||!window.AgentWidget.initAgentWidget){console.warn("AgentWidget not available. Make sure the script loaded successfully.");return}let n=e.target||"body",t={...e.config};if(e.apiUrl&&!t.apiUrl&&(t.apiUrl=e.apiUrl),e.clientToken&&!t.clientToken&&(t.clientToken=e.clientToken),e.flowId&&!t.flowId&&(t.flowId=e.flowId),!(!(t.apiUrl||t.clientToken)&&Object.keys(t).length===0)){!t.postprocessMessage&&window.AgentWidget.markdownPostprocessor&&(t.postprocessMessage=({text:s})=>window.AgentWidget.markdownPostprocessor(s));try{window.AgentWidget.initAgentWidget({target:n,config:t,useShadowDom:(o=e.useShadowDom)!=null?o:!1})}catch(s){console.error("Failed to initialize AgentWidget:",s)}}};A(async()=>{try{await C(),await h(),u&&(e.config||e.apiUrl||e.clientToken)&&setTimeout(k,0)}catch(n){console.error("Failed to install AgentWidget:",n)}})})();})();
2
2
  //# sourceMappingURL=install.global.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/install.ts"],"sourcesContent":["/**\n * Standalone installer script for easy script tag installation\n * This script automatically loads CSS and JS, then initializes the widget\n * if configuration is provided via window.siteAgentConfig\n */\n\ninterface SiteAgentInstallConfig {\n version?: string;\n cdn?: \"unpkg\" | \"jsdelivr\";\n cssUrl?: string;\n jsUrl?: string;\n target?: string | HTMLElement;\n config?: any;\n autoInit?: boolean;\n // Client token mode options (can also be set via data attributes)\n clientToken?: string;\n flowId?: string;\n apiUrl?: string;\n}\n\ndeclare global {\n interface Window {\n siteAgentConfig?: SiteAgentInstallConfig;\n AgentWidget?: any;\n }\n}\n\n(function() {\n \"use strict\";\n\n // Prevent double installation\n if ((window as any).__siteAgentInstallerLoaded) {\n return;\n }\n (window as any).__siteAgentInstallerLoaded = true;\n\n /**\n * Read configuration from data attributes on the current script tag.\n * Supports: data-travrse-token, data-flow-id, data-api-url\n */\n const getConfigFromScript = (): Partial<SiteAgentInstallConfig> => {\n // Try to get the current script element\n const script = document.currentScript as HTMLScriptElement | null;\n if (!script) return {};\n\n const scriptConfig: Partial<SiteAgentInstallConfig> = {};\n\n // Client token from data attribute (primary method for client token mode)\n const token = script.getAttribute('data-travrse-token');\n if (token) {\n scriptConfig.clientToken = token;\n }\n\n // Optional flow ID\n const flowId = script.getAttribute('data-flow-id');\n if (flowId) {\n scriptConfig.flowId = flowId;\n }\n\n // Optional API URL override\n const apiUrl = script.getAttribute('data-api-url');\n if (apiUrl) {\n scriptConfig.apiUrl = apiUrl;\n }\n\n return scriptConfig;\n };\n\n // Get config from script attributes (must be called synchronously during script execution)\n const scriptConfig = getConfigFromScript();\n\n // Merge script attributes with window config (script attributes take precedence)\n const windowConfig: SiteAgentInstallConfig = window.siteAgentConfig || {};\n const config: SiteAgentInstallConfig = { ...windowConfig, ...scriptConfig };\n \n const version = config.version || \"latest\";\n const cdn = config.cdn || \"jsdelivr\";\n const autoInit = config.autoInit !== false; // Default to true\n\n // Determine CDN base URL\n const getCdnBase = () => {\n if (config.cssUrl && config.jsUrl) {\n return { cssUrl: config.cssUrl, jsUrl: config.jsUrl };\n }\n \n const packageName = \"vanilla-agent\";\n const basePath = `/npm/${packageName}@${version}/dist`;\n \n if (cdn === \"unpkg\") {\n return {\n cssUrl: `https://unpkg.com${basePath}/widget.css`,\n jsUrl: `https://unpkg.com${basePath}/index.global.js`\n };\n } else {\n return {\n cssUrl: `https://cdn.jsdelivr.net${basePath}/widget.css`,\n jsUrl: `https://cdn.jsdelivr.net${basePath}/index.global.js`\n };\n }\n };\n\n const { cssUrl, jsUrl } = getCdnBase();\n\n // Check if CSS is already loaded\n const isCssLoaded = () => {\n return !!document.head.querySelector('link[data-vanilla-agent]') ||\n !!document.head.querySelector(`link[href*=\"widget.css\"]`);\n };\n\n // Check if JS is already loaded\n const isJsLoaded = () => {\n return !!(window as any).AgentWidget;\n };\n\n // Load CSS\n const loadCSS = (): Promise<void> => {\n return new Promise((resolve, reject) => {\n if (isCssLoaded()) {\n resolve();\n return;\n }\n\n const link = document.createElement(\"link\");\n link.rel = \"stylesheet\";\n link.href = cssUrl;\n link.setAttribute(\"data-vanilla-agent\", \"true\");\n link.onload = () => resolve();\n link.onerror = () => reject(new Error(`Failed to load CSS from ${cssUrl}`));\n document.head.appendChild(link);\n });\n };\n\n // Load JS\n const loadJS = (): Promise<void> => {\n return new Promise((resolve, reject) => {\n if (isJsLoaded()) {\n resolve();\n return;\n }\n\n const script = document.createElement(\"script\");\n script.src = jsUrl;\n script.async = true;\n script.onload = () => resolve();\n script.onerror = () => reject(new Error(`Failed to load JS from ${jsUrl}`));\n document.head.appendChild(script);\n });\n };\n\n // Initialize widget\n const initWidget = () => {\n if (!window.AgentWidget || !window.AgentWidget.initAgentWidget) {\n console.warn(\"AgentWidget not available. Make sure the script loaded successfully.\");\n return;\n }\n\n const target = config.target || \"body\";\n // Merge top-level config options into widget config\n const widgetConfig = { ...config.config };\n \n // Merge apiUrl from top-level config into widget config if present\n if (config.apiUrl && !widgetConfig.apiUrl) {\n widgetConfig.apiUrl = config.apiUrl;\n }\n \n // Merge clientToken from top-level config into widget config if present\n if (config.clientToken && !widgetConfig.clientToken) {\n widgetConfig.clientToken = config.clientToken;\n }\n \n // Merge flowId from top-level config into widget config if present\n if (config.flowId && !widgetConfig.flowId) {\n widgetConfig.flowId = config.flowId;\n }\n\n // Only initialize if we have either apiUrl OR clientToken (or other config)\n const hasApiConfig = widgetConfig.apiUrl || widgetConfig.clientToken;\n if (!hasApiConfig && Object.keys(widgetConfig).length === 0) {\n return;\n }\n\n try {\n window.AgentWidget.initAgentWidget({\n target,\n config: widgetConfig\n });\n } catch (error) {\n console.error(\"Failed to initialize AgentWidget:\", error);\n }\n };\n\n // Main installation flow\n const install = async () => {\n try {\n await loadCSS();\n await loadJS();\n \n // Auto-init if we have config OR apiUrl OR clientToken\n const shouldAutoInit = autoInit && (\n config.config || \n config.apiUrl || \n config.clientToken\n );\n \n if (shouldAutoInit) {\n // Wait a tick to ensure AgentWidget is fully initialized\n setTimeout(initWidget, 0);\n }\n } catch (error) {\n console.error(\"Failed to install AgentWidget:\", error);\n }\n };\n\n // Start installation\n if (document.readyState === \"loading\") {\n document.addEventListener(\"DOMContentLoaded\", install);\n } else {\n install();\n }\n})();\n\n"],"mappings":"2CA2BC,UAAW,CACV,aAGA,GAAK,OAAe,2BAClB,OAED,OAAe,2BAA6B,GAmC7C,IAAMA,GA7BsB,IAAuC,CAEjE,IAAMC,EAAS,SAAS,cACxB,GAAI,CAACA,EAAQ,MAAO,CAAC,EAErB,IAAMD,EAAgD,CAAC,EAGjDE,EAAQD,EAAO,aAAa,oBAAoB,EAClDC,IACFF,EAAa,YAAcE,GAI7B,IAAMC,EAASF,EAAO,aAAa,cAAc,EAC7CE,IACFH,EAAa,OAASG,GAIxB,IAAMC,EAASH,EAAO,aAAa,cAAc,EACjD,OAAIG,IACFJ,EAAa,OAASI,GAGjBJ,CACT,GAGyC,EAInCK,EAAiC,CAAE,GADI,OAAO,iBAAmB,CAAC,EACd,GAAGL,CAAa,EAEpEM,EAAUD,EAAO,SAAW,SAC5BE,EAAMF,EAAO,KAAO,WACpBG,EAAWH,EAAO,WAAa,GAG/BI,EAAa,IAAM,CACvB,GAAIJ,EAAO,QAAUA,EAAO,MAC1B,MAAO,CAAE,OAAQA,EAAO,OAAQ,MAAOA,EAAO,KAAM,EAItD,IAAMK,EAAW,sBAAuBJ,CAAO,QAE/C,OAAIC,IAAQ,QACH,CACL,OAAQ,oBAAoBG,CAAQ,cACpC,MAAO,oBAAoBA,CAAQ,kBACrC,EAEO,CACL,OAAQ,2BAA2BA,CAAQ,cAC3C,MAAO,2BAA2BA,CAAQ,kBAC5C,CAEJ,EAEM,CAAE,OAAAC,EAAQ,MAAAC,CAAM,EAAIH,EAAW,EAG/BI,EAAc,IACX,CAAC,CAAC,SAAS,KAAK,cAAc,0BAA0B,GACxD,CAAC,CAAC,SAAS,KAAK,cAAc,0BAA0B,EAI3DC,EAAa,IACV,CAAC,CAAE,OAAe,YAIrBC,EAAU,IACP,IAAI,QAAQ,CAACC,EAASC,IAAW,CACtC,GAAIJ,EAAY,EAAG,CACjBG,EAAQ,EACR,MACF,CAEA,IAAME,EAAO,SAAS,cAAc,MAAM,EAC1CA,EAAK,IAAM,aACXA,EAAK,KAAOP,EACZO,EAAK,aAAa,qBAAsB,MAAM,EAC9CA,EAAK,OAAS,IAAMF,EAAQ,EAC5BE,EAAK,QAAU,IAAMD,EAAO,IAAI,MAAM,2BAA2BN,CAAM,EAAE,CAAC,EAC1E,SAAS,KAAK,YAAYO,CAAI,CAChC,CAAC,EAIGC,EAAS,IACN,IAAI,QAAQ,CAACH,EAASC,IAAW,CACtC,GAAIH,EAAW,EAAG,CAChBE,EAAQ,EACR,MACF,CAEA,IAAMf,EAAS,SAAS,cAAc,QAAQ,EAC9CA,EAAO,IAAMW,EACbX,EAAO,MAAQ,GACfA,EAAO,OAAS,IAAMe,EAAQ,EAC9Bf,EAAO,QAAU,IAAMgB,EAAO,IAAI,MAAM,0BAA0BL,CAAK,EAAE,CAAC,EAC1E,SAAS,KAAK,YAAYX,CAAM,CAClC,CAAC,EAIGmB,EAAa,IAAM,CACvB,GAAI,CAAC,OAAO,aAAe,CAAC,OAAO,YAAY,gBAAiB,CAC9D,QAAQ,KAAK,sEAAsE,EACnF,MACF,CAEA,IAAMC,EAAShB,EAAO,QAAU,OAE1BiB,EAAe,CAAE,GAAGjB,EAAO,MAAO,EAmBxC,GAhBIA,EAAO,QAAU,CAACiB,EAAa,SACjCA,EAAa,OAASjB,EAAO,QAI3BA,EAAO,aAAe,CAACiB,EAAa,cACtCA,EAAa,YAAcjB,EAAO,aAIhCA,EAAO,QAAU,CAACiB,EAAa,SACjCA,EAAa,OAASjB,EAAO,QAK3B,IADiBiB,EAAa,QAAUA,EAAa,cACpC,OAAO,KAAKA,CAAY,EAAE,SAAW,GAI1D,GAAI,CACF,OAAO,YAAY,gBAAgB,CACjC,OAAAD,EACA,OAAQC,CACV,CAAC,CACH,OAASC,EAAO,CACd,QAAQ,MAAM,oCAAqCA,CAAK,CAC1D,CACF,EAGMC,EAAU,SAAY,CAC1B,GAAI,CACF,MAAMT,EAAQ,EACd,MAAMI,EAAO,EAGUX,IACrBH,EAAO,QACPA,EAAO,QACPA,EAAO,cAKP,WAAWe,EAAY,CAAC,CAE5B,OAASG,EAAO,CACd,QAAQ,MAAM,iCAAkCA,CAAK,CACvD,CACF,EAGI,SAAS,aAAe,UAC1B,SAAS,iBAAiB,mBAAoBC,CAAO,EAErDA,EAAQ,CAEZ,GAAG","names":["scriptConfig","script","token","flowId","apiUrl","config","version","cdn","autoInit","getCdnBase","basePath","cssUrl","jsUrl","isCssLoaded","isJsLoaded","loadCSS","resolve","reject","link","loadJS","initWidget","target","widgetConfig","error","install"]}
1
+ {"version":3,"sources":["../src/install.ts"],"sourcesContent":["/**\n * Standalone installer script for easy script tag installation\n * This script automatically loads CSS and JS, then initializes the widget\n * if configuration is provided via window.siteAgentConfig\n */\n\ninterface SiteAgentInstallConfig {\n version?: string;\n cdn?: \"unpkg\" | \"jsdelivr\";\n cssUrl?: string;\n jsUrl?: string;\n target?: string | HTMLElement;\n config?: any;\n autoInit?: boolean;\n // Client token mode options (can also be set via data attributes)\n clientToken?: string;\n flowId?: string;\n apiUrl?: string;\n // Shadow DOM option (defaults to false for better CSS compatibility)\n useShadowDom?: boolean;\n}\n\ndeclare global {\n interface Window {\n siteAgentConfig?: SiteAgentInstallConfig;\n AgentWidget?: any;\n }\n}\n\n(function() {\n \"use strict\";\n\n // Prevent double installation\n if ((window as any).__siteAgentInstallerLoaded) {\n return;\n }\n (window as any).__siteAgentInstallerLoaded = true;\n\n /**\n * Read configuration from data attributes on the current script tag.\n * Supports: data-config (JSON), data-travrse-token, data-flow-id, data-api-url\n */\n const getConfigFromScript = (): Partial<SiteAgentInstallConfig> => {\n // Try to get the current script element\n const script = document.currentScript as HTMLScriptElement | null;\n if (!script) return {};\n\n const scriptConfig: Partial<SiteAgentInstallConfig> = {};\n\n // Full config from data-config attribute (JSON string)\n const configJson = script.getAttribute('data-config');\n if (configJson) {\n try {\n const parsedConfig = JSON.parse(configJson);\n // If it has nested 'config' property, use it; otherwise treat as widget config\n if (parsedConfig.config) {\n Object.assign(scriptConfig, parsedConfig);\n } else {\n // Treat the entire object as widget config\n scriptConfig.config = parsedConfig;\n }\n } catch (e) {\n console.error(\"Failed to parse data-config JSON:\", e);\n }\n }\n\n // Client token from data attribute (primary method for client token mode)\n const token = script.getAttribute('data-travrse-token');\n if (token) {\n scriptConfig.clientToken = token;\n }\n\n // Optional flow ID\n const flowId = script.getAttribute('data-flow-id');\n if (flowId) {\n scriptConfig.flowId = flowId;\n }\n\n // Optional API URL override\n const apiUrl = script.getAttribute('data-api-url');\n if (apiUrl) {\n scriptConfig.apiUrl = apiUrl;\n }\n\n return scriptConfig;\n };\n\n // Get config from script attributes (must be called synchronously during script execution)\n const scriptConfig = getConfigFromScript();\n\n // Merge script attributes with window config (script attributes take precedence)\n const windowConfig: SiteAgentInstallConfig = window.siteAgentConfig || {};\n const config: SiteAgentInstallConfig = { ...windowConfig, ...scriptConfig };\n \n const version = config.version || \"latest\";\n const cdn = config.cdn || \"jsdelivr\";\n const autoInit = config.autoInit !== false; // Default to true\n\n // Determine CDN base URL\n const getCdnBase = () => {\n if (config.cssUrl && config.jsUrl) {\n return { cssUrl: config.cssUrl, jsUrl: config.jsUrl };\n }\n \n const packageName = \"vanilla-agent\";\n const basePath = `/npm/${packageName}@${version}/dist`;\n \n if (cdn === \"unpkg\") {\n return {\n cssUrl: `https://unpkg.com${basePath}/widget.css`,\n jsUrl: `https://unpkg.com${basePath}/index.global.js`\n };\n } else {\n return {\n cssUrl: `https://cdn.jsdelivr.net${basePath}/widget.css`,\n jsUrl: `https://cdn.jsdelivr.net${basePath}/index.global.js`\n };\n }\n };\n\n const { cssUrl, jsUrl } = getCdnBase();\n\n // Check if CSS is already loaded\n const isCssLoaded = () => {\n return !!document.head.querySelector('link[data-vanilla-agent]') ||\n !!document.head.querySelector(`link[href*=\"widget.css\"]`);\n };\n\n // Check if JS is already loaded\n const isJsLoaded = () => {\n return !!(window as any).AgentWidget;\n };\n\n /**\n * Wait for framework hydration to complete (Next.js, Nuxt, etc.)\n * This prevents the framework from removing dynamically added CSS during reconciliation.\n * Uses requestIdleCallback + double requestAnimationFrame for reliable detection.\n */\n const waitForHydration = (callback: () => void): void => {\n let executed = false;\n \n const execute = () => {\n if (executed) return;\n executed = true;\n callback();\n };\n\n const afterDom = () => {\n // Strategy 1: Use requestIdleCallback if available (best for detecting idle after hydration)\n if (typeof requestIdleCallback !== 'undefined') {\n requestIdleCallback(() => {\n // Double requestAnimationFrame ensures at least one full paint cycle completed\n requestAnimationFrame(() => {\n requestAnimationFrame(execute);\n });\n }, { timeout: 3000 }); // Max wait 3 seconds, then proceed anyway\n } else {\n // Strategy 2: Fallback for Safari (no requestIdleCallback)\n // 300ms is typically enough for hydration on most pages\n setTimeout(execute, 300);\n }\n };\n\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', afterDom);\n } else {\n // DOM already ready, but still wait for potential hydration\n afterDom();\n }\n };\n\n // Load CSS\n const loadCSS = (): Promise<void> => {\n return new Promise((resolve, reject) => {\n if (isCssLoaded()) {\n resolve();\n return;\n }\n\n const link = document.createElement(\"link\");\n link.rel = \"stylesheet\";\n link.href = cssUrl;\n link.setAttribute(\"data-vanilla-agent\", \"true\");\n \n link.onload = () => resolve();\n link.onerror = () => reject(new Error(`Failed to load CSS from ${cssUrl}`));\n document.head.appendChild(link);\n });\n };\n\n // Load JS\n const loadJS = (): Promise<void> => {\n return new Promise((resolve, reject) => {\n if (isJsLoaded()) {\n resolve();\n return;\n }\n\n const script = document.createElement(\"script\");\n script.src = jsUrl;\n script.async = true;\n script.onload = () => resolve();\n script.onerror = () => reject(new Error(`Failed to load JS from ${jsUrl}`));\n document.head.appendChild(script);\n });\n };\n\n // Initialize widget\n const initWidget = () => {\n if (!window.AgentWidget || !window.AgentWidget.initAgentWidget) {\n console.warn(\"AgentWidget not available. Make sure the script loaded successfully.\");\n return;\n }\n\n const target = config.target || \"body\";\n // Merge top-level config options into widget config\n const widgetConfig = { ...config.config };\n \n // Merge apiUrl from top-level config into widget config if present\n if (config.apiUrl && !widgetConfig.apiUrl) {\n widgetConfig.apiUrl = config.apiUrl;\n }\n \n // Merge clientToken from top-level config into widget config if present\n if (config.clientToken && !widgetConfig.clientToken) {\n widgetConfig.clientToken = config.clientToken;\n }\n \n // Merge flowId from top-level config into widget config if present\n if (config.flowId && !widgetConfig.flowId) {\n widgetConfig.flowId = config.flowId;\n }\n\n // Only initialize if we have either apiUrl OR clientToken (or other config)\n const hasApiConfig = widgetConfig.apiUrl || widgetConfig.clientToken;\n if (!hasApiConfig && Object.keys(widgetConfig).length === 0) {\n return;\n }\n\n // Auto-apply markdown postprocessor if not explicitly set and available\n if (!widgetConfig.postprocessMessage && window.AgentWidget.markdownPostprocessor) {\n widgetConfig.postprocessMessage = ({ text }: { text: string }) => \n window.AgentWidget.markdownPostprocessor(text);\n }\n\n try {\n window.AgentWidget.initAgentWidget({\n target,\n config: widgetConfig,\n // Explicitly disable shadow DOM for better CSS compatibility with host page\n useShadowDom: config.useShadowDom ?? false\n });\n } catch (error) {\n console.error(\"Failed to initialize AgentWidget:\", error);\n }\n };\n\n // Main installation flow (called after hydration completes)\n const install = async () => {\n try {\n await loadCSS();\n await loadJS();\n \n // Auto-init if we have config OR apiUrl OR clientToken\n const shouldAutoInit = autoInit && (\n config.config || \n config.apiUrl || \n config.clientToken\n );\n \n if (shouldAutoInit) {\n // Wait a tick to ensure AgentWidget is fully initialized\n setTimeout(initWidget, 0);\n }\n } catch (error) {\n console.error(\"Failed to install AgentWidget:\", error);\n }\n };\n\n // Start installation after hydration completes\n // This prevents Next.js/Nuxt/etc. from removing dynamically added CSS\n waitForHydration(install);\n})();\n\n"],"mappings":"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","names":["scriptConfig","script","configJson","parsedConfig","e","token","flowId","apiUrl","config","version","cdn","autoInit","getCdnBase","basePath","cssUrl","jsUrl","isCssLoaded","isJsLoaded","waitForHydration","callback","executed","execute","afterDom","loadCSS","resolve","reject","link","loadJS","initWidget","_a","target","widgetConfig","text","error"]}
package/dist/widget.css CHANGED
@@ -1,7 +1,3 @@
1
- :root {
2
- color-scheme: light only;
3
- }
4
-
5
1
  #vanilla-agent-root {
6
2
  all: initial;
7
3
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", sans-serif;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vanilla-agent",
3
- "version": "1.24.0",
3
+ "version": "1.26.0",
4
4
  "description": "Themeable, plugable streaming agent widget for websites, in plain JS with support for voice input and reasoning / tool output.",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -375,3 +375,5 @@ export function createNPSFeedback(options: NPSFeedbackOptions): HTMLElement {
375
375
  return container;
376
376
  }
377
377
 
378
+
379
+
package/src/defaults.ts CHANGED
@@ -1,4 +1,82 @@
1
- import type { AgentWidgetConfig } from "./types";
1
+ import type { AgentWidgetConfig, AgentWidgetTheme } from "./types";
2
+
3
+ /**
4
+ * Default light theme colors
5
+ */
6
+ export const DEFAULT_LIGHT_THEME: AgentWidgetTheme = {
7
+ primary: "#111827",
8
+ accent: "#1d4ed8",
9
+ surface: "#ffffff",
10
+ muted: "#6b7280",
11
+ container: "#f8fafc",
12
+ border: "#f1f5f9",
13
+ divider: "#f1f5f9",
14
+ messageBorder: "#f1f5f9",
15
+ inputBackground: "#ffffff",
16
+ callToAction: "#000000",
17
+ callToActionBackground: "#ffffff",
18
+ sendButtonBackgroundColor: "#111827",
19
+ sendButtonTextColor: "#ffffff",
20
+ sendButtonBorderColor: "#60a5fa",
21
+ closeButtonColor: "#6b7280",
22
+ closeButtonBackgroundColor: "transparent",
23
+ closeButtonBorderColor: "",
24
+ clearChatIconColor: "#6b7280",
25
+ clearChatBackgroundColor: "transparent",
26
+ clearChatBorderColor: "transparent",
27
+ micIconColor: "#111827",
28
+ micBackgroundColor: "transparent",
29
+ micBorderColor: "transparent",
30
+ recordingIconColor: "#ffffff",
31
+ recordingBackgroundColor: "#ef4444",
32
+ recordingBorderColor: "transparent",
33
+ inputFontFamily: "sans-serif",
34
+ inputFontWeight: "400",
35
+ radiusSm: "0.75rem",
36
+ radiusMd: "1rem",
37
+ radiusLg: "1.5rem",
38
+ launcherRadius: "9999px",
39
+ buttonRadius: "9999px",
40
+ };
41
+
42
+ /**
43
+ * Default dark theme colors
44
+ */
45
+ export const DEFAULT_DARK_THEME: AgentWidgetTheme = {
46
+ primary: "#f9fafb",
47
+ accent: "#3b82f6",
48
+ surface: "#1f2937",
49
+ muted: "#9ca3af",
50
+ container: "#111827",
51
+ border: "#374151",
52
+ divider: "#374151",
53
+ messageBorder: "#374151",
54
+ inputBackground: "#111827",
55
+ callToAction: "#ffffff",
56
+ callToActionBackground: "#374151",
57
+ sendButtonBackgroundColor: "#3b82f6",
58
+ sendButtonTextColor: "#ffffff",
59
+ sendButtonBorderColor: "#60a5fa",
60
+ closeButtonColor: "#9ca3af",
61
+ closeButtonBackgroundColor: "transparent",
62
+ closeButtonBorderColor: "",
63
+ clearChatIconColor: "#9ca3af",
64
+ clearChatBackgroundColor: "transparent",
65
+ clearChatBorderColor: "transparent",
66
+ micIconColor: "#f9fafb",
67
+ micBackgroundColor: "transparent",
68
+ micBorderColor: "transparent",
69
+ recordingIconColor: "#ffffff",
70
+ recordingBackgroundColor: "#ef4444",
71
+ recordingBorderColor: "transparent",
72
+ inputFontFamily: "sans-serif",
73
+ inputFontWeight: "400",
74
+ radiusSm: "0.75rem",
75
+ radiusMd: "1rem",
76
+ radiusLg: "1.5rem",
77
+ launcherRadius: "9999px",
78
+ buttonRadius: "9999px",
79
+ };
2
80
 
3
81
  /**
4
82
  * Default widget configuration
@@ -8,41 +86,9 @@ export const DEFAULT_WIDGET_CONFIG: Partial<AgentWidgetConfig> = {
8
86
  apiUrl: "http://localhost:43111/api/chat/dispatch",
9
87
  // Client token mode defaults (optional, only used when clientToken is set)
10
88
  clientToken: undefined,
11
- theme: {
12
- primary: "#111827",
13
- accent: "#1d4ed8",
14
- surface: "#ffffff",
15
- muted: "#6b7280",
16
- container: "#f8fafc",
17
- border: "#f1f5f9",
18
- divider: "#f1f5f9",
19
- messageBorder: "#f1f5f9",
20
- inputBackground: "#ffffff",
21
- callToAction: "#000000",
22
- callToActionBackground: "#ffffff",
23
- sendButtonBackgroundColor: "#111827",
24
- sendButtonTextColor: "#ffffff",
25
- sendButtonBorderColor: "#60a5fa",
26
- closeButtonColor: "#6b7280",
27
- closeButtonBackgroundColor: "transparent",
28
- closeButtonBorderColor: "",
29
- clearChatIconColor: "#6b7280",
30
- clearChatBackgroundColor: "transparent",
31
- clearChatBorderColor: "transparent",
32
- micIconColor: "#111827",
33
- micBackgroundColor: "transparent",
34
- micBorderColor: "transparent",
35
- recordingIconColor: "#ffffff",
36
- recordingBackgroundColor: "#ef4444",
37
- recordingBorderColor: "transparent",
38
- inputFontFamily: "sans-serif",
39
- inputFontWeight: "400",
40
- radiusSm: "0.75rem",
41
- radiusMd: "1rem",
42
- radiusLg: "1.5rem",
43
- launcherRadius: "9999px",
44
- buttonRadius: "9999px",
45
- },
89
+ theme: DEFAULT_LIGHT_THEME,
90
+ darkTheme: DEFAULT_DARK_THEME,
91
+ colorScheme: "light",
46
92
  launcher: {
47
93
  enabled: true,
48
94
  title: "Chat Assistant",
@@ -198,6 +244,10 @@ export function mergeWithDefaults(
198
244
  ...DEFAULT_WIDGET_CONFIG.theme,
199
245
  ...config.theme,
200
246
  },
247
+ darkTheme: {
248
+ ...DEFAULT_WIDGET_CONFIG.darkTheme,
249
+ ...config.darkTheme,
250
+ },
201
251
  launcher: {
202
252
  ...DEFAULT_WIDGET_CONFIG.launcher,
203
253
  ...config.launcher,
package/src/index.ts CHANGED
@@ -105,7 +105,12 @@ export {
105
105
  } from "./utils/component-middleware";
106
106
 
107
107
  // Default configuration exports
108
- export { DEFAULT_WIDGET_CONFIG, mergeWithDefaults } from "./defaults";
108
+ export {
109
+ DEFAULT_WIDGET_CONFIG,
110
+ DEFAULT_LIGHT_THEME,
111
+ DEFAULT_DARK_THEME,
112
+ mergeWithDefaults
113
+ } from "./defaults";
109
114
 
110
115
  // Layout system exports
111
116
  export {
package/src/install.ts CHANGED
@@ -16,6 +16,8 @@ interface SiteAgentInstallConfig {
16
16
  clientToken?: string;
17
17
  flowId?: string;
18
18
  apiUrl?: string;
19
+ // Shadow DOM option (defaults to false for better CSS compatibility)
20
+ useShadowDom?: boolean;
19
21
  }
20
22
 
21
23
  declare global {
@@ -36,7 +38,7 @@ declare global {
36
38
 
37
39
  /**
38
40
  * Read configuration from data attributes on the current script tag.
39
- * Supports: data-travrse-token, data-flow-id, data-api-url
41
+ * Supports: data-config (JSON), data-travrse-token, data-flow-id, data-api-url
40
42
  */
41
43
  const getConfigFromScript = (): Partial<SiteAgentInstallConfig> => {
42
44
  // Try to get the current script element
@@ -45,6 +47,23 @@ declare global {
45
47
 
46
48
  const scriptConfig: Partial<SiteAgentInstallConfig> = {};
47
49
 
50
+ // Full config from data-config attribute (JSON string)
51
+ const configJson = script.getAttribute('data-config');
52
+ if (configJson) {
53
+ try {
54
+ const parsedConfig = JSON.parse(configJson);
55
+ // If it has nested 'config' property, use it; otherwise treat as widget config
56
+ if (parsedConfig.config) {
57
+ Object.assign(scriptConfig, parsedConfig);
58
+ } else {
59
+ // Treat the entire object as widget config
60
+ scriptConfig.config = parsedConfig;
61
+ }
62
+ } catch (e) {
63
+ console.error("Failed to parse data-config JSON:", e);
64
+ }
65
+ }
66
+
48
67
  // Client token from data attribute (primary method for client token mode)
49
68
  const token = script.getAttribute('data-travrse-token');
50
69
  if (token) {
@@ -112,6 +131,44 @@ declare global {
112
131
  return !!(window as any).AgentWidget;
113
132
  };
114
133
 
134
+ /**
135
+ * Wait for framework hydration to complete (Next.js, Nuxt, etc.)
136
+ * This prevents the framework from removing dynamically added CSS during reconciliation.
137
+ * Uses requestIdleCallback + double requestAnimationFrame for reliable detection.
138
+ */
139
+ const waitForHydration = (callback: () => void): void => {
140
+ let executed = false;
141
+
142
+ const execute = () => {
143
+ if (executed) return;
144
+ executed = true;
145
+ callback();
146
+ };
147
+
148
+ const afterDom = () => {
149
+ // Strategy 1: Use requestIdleCallback if available (best for detecting idle after hydration)
150
+ if (typeof requestIdleCallback !== 'undefined') {
151
+ requestIdleCallback(() => {
152
+ // Double requestAnimationFrame ensures at least one full paint cycle completed
153
+ requestAnimationFrame(() => {
154
+ requestAnimationFrame(execute);
155
+ });
156
+ }, { timeout: 3000 }); // Max wait 3 seconds, then proceed anyway
157
+ } else {
158
+ // Strategy 2: Fallback for Safari (no requestIdleCallback)
159
+ // 300ms is typically enough for hydration on most pages
160
+ setTimeout(execute, 300);
161
+ }
162
+ };
163
+
164
+ if (document.readyState === 'loading') {
165
+ document.addEventListener('DOMContentLoaded', afterDom);
166
+ } else {
167
+ // DOM already ready, but still wait for potential hydration
168
+ afterDom();
169
+ }
170
+ };
171
+
115
172
  // Load CSS
116
173
  const loadCSS = (): Promise<void> => {
117
174
  return new Promise((resolve, reject) => {
@@ -124,6 +181,7 @@ declare global {
124
181
  link.rel = "stylesheet";
125
182
  link.href = cssUrl;
126
183
  link.setAttribute("data-vanilla-agent", "true");
184
+
127
185
  link.onload = () => resolve();
128
186
  link.onerror = () => reject(new Error(`Failed to load CSS from ${cssUrl}`));
129
187
  document.head.appendChild(link);
@@ -179,17 +237,25 @@ declare global {
179
237
  return;
180
238
  }
181
239
 
240
+ // Auto-apply markdown postprocessor if not explicitly set and available
241
+ if (!widgetConfig.postprocessMessage && window.AgentWidget.markdownPostprocessor) {
242
+ widgetConfig.postprocessMessage = ({ text }: { text: string }) =>
243
+ window.AgentWidget.markdownPostprocessor(text);
244
+ }
245
+
182
246
  try {
183
247
  window.AgentWidget.initAgentWidget({
184
248
  target,
185
- config: widgetConfig
249
+ config: widgetConfig,
250
+ // Explicitly disable shadow DOM for better CSS compatibility with host page
251
+ useShadowDom: config.useShadowDom ?? false
186
252
  });
187
253
  } catch (error) {
188
254
  console.error("Failed to initialize AgentWidget:", error);
189
255
  }
190
256
  };
191
257
 
192
- // Main installation flow
258
+ // Main installation flow (called after hydration completes)
193
259
  const install = async () => {
194
260
  try {
195
261
  await loadCSS();
@@ -211,11 +277,8 @@ declare global {
211
277
  }
212
278
  };
213
279
 
214
- // Start installation
215
- if (document.readyState === "loading") {
216
- document.addEventListener("DOMContentLoaded", install);
217
- } else {
218
- install();
219
- }
280
+ // Start installation after hydration completes
281
+ // This prevents Next.js/Nuxt/etc. from removing dynamically added CSS
282
+ waitForHydration(install);
220
283
  })();
221
284
 
@@ -1,7 +1,3 @@
1
- :root {
2
- color-scheme: light only;
3
- }
4
-
5
1
  #vanilla-agent-root {
6
2
  all: initial;
7
3
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", sans-serif;
package/src/types.ts CHANGED
@@ -1124,6 +1124,34 @@ export type AgentWidgetConfig = {
1124
1124
  sendButtonLabel?: string;
1125
1125
  };
1126
1126
  theme?: AgentWidgetTheme;
1127
+ /**
1128
+ * Theme colors for dark mode. Applied when dark mode is detected
1129
+ * (when colorScheme is 'dark' or 'auto' with dark mode active).
1130
+ * If not provided, falls back to `theme` colors.
1131
+ *
1132
+ * @example
1133
+ * ```typescript
1134
+ * config: {
1135
+ * theme: { primary: '#111827', surface: '#ffffff' },
1136
+ * darkTheme: { primary: '#f9fafb', surface: '#1f2937' },
1137
+ * colorScheme: 'auto'
1138
+ * }
1139
+ * ```
1140
+ */
1141
+ darkTheme?: AgentWidgetTheme;
1142
+ /**
1143
+ * Color scheme mode for the widget.
1144
+ * - 'light': Always use light theme (default)
1145
+ * - 'dark': Always use dark theme
1146
+ * - 'auto': Automatically detect from page (HTML class or prefers-color-scheme)
1147
+ *
1148
+ * When 'auto', detection order:
1149
+ * 1. Check if `<html>` has 'dark' class
1150
+ * 2. Fall back to `prefers-color-scheme: dark` media query
1151
+ *
1152
+ * @default 'light'
1153
+ */
1154
+ colorScheme?: 'auto' | 'light' | 'dark';
1127
1155
  features?: AgentWidgetFeatureFlags;
1128
1156
  launcher?: AgentWidgetLauncherConfig;
1129
1157
  initialMessages?: AgentWidgetMessage[];
package/src/ui.ts CHANGED
@@ -14,7 +14,7 @@ import {
14
14
  SlotRenderer,
15
15
  AgentWidgetMessageFeedback
16
16
  } from "./types";
17
- import { applyThemeVariables } from "./utils/theme";
17
+ import { applyThemeVariables, createThemeObserver } from "./utils/theme";
18
18
  import { renderLucideIcon } from "./utils/icons";
19
19
  import { createElement } from "./utils/dom";
20
20
  import { statusCopy } from "./utils/constants";
@@ -642,6 +642,31 @@ export const createAgentExperience = (
642
642
  applyThemeVariables(mount, config);
643
643
 
644
644
  const destroyCallbacks: Array<() => void> = [];
645
+
646
+ // Set up theme observer for auto color scheme detection
647
+ let cleanupThemeObserver: (() => void) | null = null;
648
+ const setupThemeObserver = () => {
649
+ // Clean up existing observer if any
650
+ if (cleanupThemeObserver) {
651
+ cleanupThemeObserver();
652
+ cleanupThemeObserver = null;
653
+ }
654
+ // Set up new observer if colorScheme is 'auto'
655
+ if (config.colorScheme === 'auto') {
656
+ cleanupThemeObserver = createThemeObserver(() => {
657
+ // Re-apply theme when color scheme changes
658
+ applyThemeVariables(mount, config);
659
+ });
660
+ }
661
+ };
662
+ setupThemeObserver();
663
+ destroyCallbacks.push(() => {
664
+ if (cleanupThemeObserver) {
665
+ cleanupThemeObserver();
666
+ cleanupThemeObserver = null;
667
+ }
668
+ });
669
+
645
670
  const suggestionsManager = createSuggestions(suggestions);
646
671
  let closeHandler: (() => void) | null = null;
647
672
  let session: AgentWidgetSession;
@@ -1811,11 +1836,17 @@ export const createAgentExperience = (
1811
1836
  const controller: Controller = {
1812
1837
  update(nextConfig: AgentWidgetConfig) {
1813
1838
  const previousToolCallConfig = config.toolCall;
1839
+ const previousColorScheme = config.colorScheme;
1814
1840
  config = { ...config, ...nextConfig };
1815
1841
  // applyFullHeightStyles resets mount.style.cssText, so call it before applyThemeVariables
1816
1842
  applyFullHeightStyles();
1817
1843
  applyThemeVariables(mount, config);
1818
1844
 
1845
+ // Re-setup theme observer if colorScheme changed
1846
+ if (config.colorScheme !== previousColorScheme) {
1847
+ setupThemeObserver();
1848
+ }
1849
+
1819
1850
  // Update plugins
1820
1851
  const newPlugins = pluginRegistry.getForInstance(config.plugins);
1821
1852
  plugins.length = 0;