B Blengi docs

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:

  1. Reads public/widget/widget.js.
  2. Computes a 12-char SHA-256 prefix.
  3. Copies the bundle to public/widget/widget.<hash>.js.
  4. 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.