Embed the widget
Versioning & cache busting
The widget bundle ships at /widget/widget.js and is
re-built every release. To avoid stale browser / CDN caches on
buyer sites, each build also publishes a
content-hashed twin at
/widget/widget.<hash>.js plus a manifest at
/widget/manifest.json.
Default embed (auto-update)
The snippet the admin sees on /app/agents/{id}
points at the hashed file via the manifest. Each release ships a
new hash, the manifest updates on deploy, and the snippet
automatically resolves to the new file. Buyers don't need to
touch their HTML.
<script async src="https://app.example.com/widget/widget.abc123.js" data-agent="…"></script>
Manifest endpoint
Buyers who want to programmatically fetch the current bundle can
hit GET /widget/manifest.json:
{
"version": "2.0.0",
"hash": "abc123abc123",
"file": "widget.abc123abc123.js",
"url": "/widget/widget.abc123abc123.js",
"generated_at": "2026-05-16T07:11:36Z"
}
The endpoint sets
Cache-Control: public, max-age=60, must-revalidate
so the manifest itself never holds long, but the bundle file can
be served with long-lived Cache-Control headers
because the URL changes per release.
Bundle caching & startup speed
The unhashed /widget/widget.js that embed snippets load
is served with
Cache-Control: public, max-age=300, stale-while-revalidate=86400.
Repeat page views start the widget straight from the browser cache —
no blocking round-trip before the script runs — and the browser
refreshes the bundle in the background after the five-minute window.
A new release therefore reaches all visitors within at most five
minutes of deploy.
The widget also caches the agent's chrome (theme, copy, greeting, starter questions) in the visitor's browser after each successful boot. On the next page view the bar paints immediately with that real, localized chrome and becomes interactive as soon as the fresh init round-trip completes — the visitor never stares at an empty corner. Tokens and conversation state are never cached this way; they always come from the fresh init.
Pinning to a specific version
If you need to lock the bundle on the buyer's side (e.g. to
coordinate a marketing campaign), copy the
file field from the manifest and use it verbatim:
<script async src="https://app.example.com/widget/widget.abc123abc123.js"></script>
The hashed artifact remains accessible after newer versions ship — old hashes are not deleted by the build script.
Fallback
If the manifest is missing (for example a partial deploy), the
snippet falls back to
/widget/widget.js?v=<md5-prefix> so buyers
never get an empty src. We keep the unhashed
widget.js as the canonical artifact too.
Build pipeline
npm run build:widget runs the Vite build, then
invokes bin/widget-postbuild.cjs which:
- Reads
public/widget/widget.js. - Computes a 12-char SHA-256 prefix.
- Copies the bundle to
public/widget/widget.<hash>.js. - Writes
public/widget/manifest.json.
Both artifacts and the manifest are committed to the repo —
the host doesn't run npm run build, the repo
is the deployment artifact.