Versioning
The SDK follows semantic versioning. Within a major version (e.g.0.x), every release is backwards compatible unless a ### Breaking section says otherwise. We bump the minor version when adding new APIs, and the patch version for bug fixes.
By default customers loading from cdn.haloagents.ai/sdk/latest/ automatically get every new release. To pin to a specific version (recommended for change-controlled environments), see Pinning a specific version.
0.9.0
May 21, 2026Changed
ui: 'manual'andmanualOnPathsnow allow in-app messages and banners. Manual mode hides the floating launcher and SDK-initiated surfaces (proactive teasers, greeting), but dashboard-configured Active Messages still render. Embedders who want zero overlays can opt out withui: { inAppMessages: false, banners: false }. See Manual mode.
Added
getUserTokencallback — fetch a fresh JWT from your backend when the staticuserTokenexpires. See Identity Verification.onIdentityExpiredcallback — fires once when a JWT is expired, missing, or rejected (403). User-scoped requests pause until a fresh token arrives.- Fail-closed identity handling — the SDK no longer sends known-expired JWTs or keeps polling ticket endpoints after identity failure.
0.8.0
May 19, 2026Breaking (SDK requests)
-
/api/sdk/users/identifyand/api/sdk/companies/identifynow reject system-managed trait keys with HTTP 400. Previously these keys silently landed incustom_fields(e.g.userTraits.created_at) or, on the company side, were ignored entirely while shadowing the real columns. Both behaviors silently broke downstream consumers (segments, automations, the inbox sidebar, the AI prompt). The endpoint now returns{ error, reserved_keys: [...], docs_url }so a single 400 names every offending key in one round trip. Rejected onuserTraits:id,created_at,updated_at,first_seen,last_seen,last_contacted_at. Rejected oncompanyTraits:id,created_at,updated_at,health_score,team_size,last_contacted_at. Migration. If you were sendingtraits.created_aton either endpoint to record “when this user/company signed up in my product”, rename it totraits.signed_up_at. Same ISO 8601 value, same meaning. The new field now populates a dedicated column on both tables, so the value powers the Contacts table, the new “Customer since” column on Companies, segments, automations, broadcasts, email merge fields ({{ signed_up_at }}for users,{{ company.signed_up_at }}for companies), the AI prompt, and the MCPdata_search_users/data_search_companiesfilters. See User Traits and Company Traits. Historical data was preserved: any value previously stored incustom_fields.created_atorcustom_fields.intercom_created_atwas backfilled into the newsigned_up_atcolumn at deploy time.
Added
-
companies.signed_up_atcolumn for the customer-set “when did this company onboard in my product” date. Already existed onend_users; companies now have it too with the same field name and semantics on both endpoints. Auto-populated from Intercom (created_at), HubSpot (properties.createdate), Stripe (customer.created), and CSV import (header patterns:signed_up_at,signup_date,customer_since,member_since,create_date,created, and similar). -
Customer sincecolumn on the Companies dashboard (hidden by default; toggle on via the column chooser). The existing “Added” column was renamed to Added to Halo to make the distinction obvious: “Added to Halo” is when the row landed in our database, “Customer since” is when they actually signed up in your product. Same split appears in the company detail sidebar’s Timeline section. -
days_since_signupin onboarding rules and Slack mode-determination now preferssigned_up_at, falling back tocreated_atwhen not set. Pre-existing Intercom/HubSpot/Stripe-synced companies stop looking brand new the moment the backfill runs. -
signed_up_at_after/signed_up_at_beforefilters on thedata_search_usersanddata_search_companiesMCP tools so the AI can answer “users who signed up in the last 30 days” or “companies that became customers in Q3 2024” without scanning every row. -
signed_up_atadded to theUserTraitsandCompanyTraitsTypeScript interfaces so TypeScript consumers get autocomplete for the new field. JSDoc on both interfaces lists the reserved keys that trigger HTTP 400, so the contract is visible at autocomplete time. The pre-0.8UserTraits.created_atfield was removed from the type (the value was always orphaned incustom_fields; keeping it in the types after the route rejection would be worse than no claim).
Fixed
- Powered-by badge no longer leaks into ticket-detail and live-help views. Three of the chat panel’s view-switch methods forgot to toggle the
.ab-powered-byelement alongside messages / input / mode toggle, so the badge would either stay visible underneath the ticket detail surface or stay hidden after the user navigated back. Now follows the rest of the chat surface during view transitions.
Backwards compatibility
The breaking change is server-side (the SDK bundle has no behavior change). The CDN bundle is forward-compatible with the new rejection: no client-side code emitstraits.created_at, so a customer page loading 0.7.x after the server deploys still works as long as the host app isn’t sending the reserved keys via userTraits / companyTraits. The signed_up_at type addition is purely additive; existing code keeps compiling.
Customers loading from /sdk/latest/ automatically get 0.8.0 on the next page load. Version-pinned customers can move to /sdk/0.8.0/ at their own pace using the SRI manifest at /api/sdk/sri. Customers calling the identify endpoints directly from a backend must update before the dashboard deploy.
0.7.0
May 19, 2026Changed (default behavior)
-
autoOpenUnseennow defaults tofalse(wastrue). The chat panel no longer auto-expands on page load when the user has unread messages. The launcher’s badge dot still appears for unread messages; the panel stays closed until the user taps. This matches Intercom’s default and removes the class of “panel auto-opened over my Sign Up button on mobile” reports without requiring any customer config. Migration. Customers who relied on the old auto-expand behavior can opt back in:HaloAgents.init({ ..., ui: { autoOpenUnseen: true } }). When opting in, consider addingmanualOnPathscovering your auth/checkout flows so the panel can’t auto-open over a CTA. See Default behavior is quiet.
Added
-
onChatOpenandonChatClosecallbacks. Fire once on the closed→open and open→closed transitions of the chat panel, regardless of who initiated the transition (hostopenChat()/closeChat(), the user tapping the trigger or the panel’s X button, or the SDK auto-opening on unseen messages). Idempotent: callingopenChat()while already open does not fireonChatOpenagain. Throws inside the callback are caught and routed throughonError(if set) so a host bug cannot desync the SDK’s open/close cycle. Primary use case: hosts usingui.trigger: falseorui: 'manual'who need to keep their own “is the chat open?” state in sync with the panel. Replaces the pre-0.7MutationObserveron.ab-triggerpattern, which does not work without a trigger element. See Tracking open state in manual mode. -
Auth-page console nag.
init()prints a one-timeconsole.warnwhen it runs on a recognizable auth route (/signin,/signup,/login,/forgot-password,/oauth/*,/auth/*, etc.) AND the customer has explicitly enabledautoOpenUnseen: truewithout amanualOnPathscarve-out covering the path. Targeted at the narrow case where someone deliberately turned the loud behavior back on but forgot the carve-out. Pure dev-time signal, no telemetry, no production effect. With the new default ofautoOpenUnseen: false, this warning won’t fire for most customers.
Backwards compatibility
All changes are additive or strictly safer than 0.6.x. Existing integrations need no code changes. Customers loading from/sdk/latest/ automatically get 0.7.0; customers using the legacy unprefixed path (cdn.haloagents.ai/sdk/haloagents.umd.js) are unaffected, that path still works and serves the latest build.
0.6.0
April 28, 2026 A “production-readiness” release. No new customer-facing UI surfaces; everything here is about catching bugs earlier, debugging faster, and locking down the install path.Added
HaloAgents.getInstance().debug(). Returns a structured snapshot of the SDK’s runtime state (resolved UI flags, agent load state, session info, what’s actually mounted in the DOM, identity presence flags) and prints a human-readable version to the console. The first thing to call when something seems wrong. Safe to share with support since secrets are presence-flagged, not echoed. See Troubleshooting.HaloAgents.version. Current SDK version string. Also surfaced insidedebug()output. Useful for confirming which CDN bundle a page is actually loading.- Versioned CDN URLs. Every release is now served at both
cdn.haloagents.ai/sdk/latest/(auto-updating) andcdn.haloagents.ai/sdk/<version>/(immutable). The<version>/paths are pinnable with SRI integrity hashes for tamper-proof installs. - Strict config validation.
init()now warns (single groupedconsole.warn) on unknown config keys, deprecated keys, and obvious typos. Suggests the closest valid key when the typo is within edit distance 2 (catchesmanualOnPath,triger,userTrait, etc.).
Fixed
unmount()(called bydestroy()) now cleans up top banners and in-app message overlays in addition to the chat panel host. Without this fix, callingdestroy()collapsed the panel but left active-message chrome on the page until full reload.- Banners and in-app messages no longer leak when the widget is unmounted between the active-messages fetch starting and resolving (e.g. SPA route change calling
destroy()mid-request).
Backwards compatibility
All changes are additive or strictly safer than 0.5.x. Existing integrations need no code changes. Customers loading from/sdk/latest/ automatically get 0.6.0; customers using the legacy unprefixed path (cdn.haloagents.ai/sdk/haloagents.umd.js) are unaffected, that path still works and serves the latest build.
0.5.1
April 28, 2026Fixed
ui: 'manual'andmanualOnPathsno longer leak top banners or in-app message overlays.loadActiveMessageswas bypassing the UI suppression flags entirely, so any active message configured in the dashboard would still render across the page. Reported by orbitforms.ai.
Added
ui.bannersandui.inAppMessagesper-surface flags. Both default totrue. Impliedfalsebyui: 'manual'andmanualOnPathsmatches.onBeforeShowBanner({ id, body })andonBeforeShowInAppMessage({ id, body })host-veto callbacks. Same shape as the existingonBeforeShowProactivecallback: synchronous, returnfalseto suppress this specific message.
0.5.0
April 28, 2026Added
uiconfig field onHaloAgents.init()to suppress one or more default UI surfaces. Pass'manual'for full headless mode, or an object for per-surface flags:{ trigger, badge, proactive, greeting, autoOpenUnseen }. Replaces the brittle pattern of injecting CSS into the shadow DOM to hide internal classnames.manualOnPathsconfig field. Glob patterns that switch the widget into manual mode on matching pathnames. Lets a singleinit()call serve both marketing pages (full chrome) and an authenticated app (host-controlled launcher) without conditional initialization.onBeforeShowProactive(message)callback. Synchronous host veto run before each proactive teaser. Returnfalseto suppress this specific message.
Fixed
- The auto-open-on-unseen-messages behavior now respects
ui.autoOpenUnseen, so apps that mount Halo only behind a custom button no longer have the panel pop open unprompted when an agent replies.
0.4.0
February 2026Added
identify(userId, traits, identity?). Optional third argument lets the host rotateuserToken/userHashalongside the user id (e.g. on JWT refresh).- The SDK now forwards the configured
userToken/userHashon every request asX-User-Token/X-User-Hashheaders, not just on chat.
Security (potentially breaking for orgs with identity verification enabled)
The server-side companion to this release gates everyuser_id-scoped SDK endpoint on a valid user_token JWT (or user_hash) whenever the org has team_secrets.identity_secret configured. Previously only the chat / stream / handoff routes enforced identity; the surrounding read + write endpoints trusted whatever user_id the caller sent.
After deploying the matching server release, any widget still on <= 0.3.x will receive 403 responses on these endpoints because the older SDK does not forward the identity headers. Upgrade widgets to 0.4.0 or later (or arrange for the host page to call HaloAgents.identify() with the new identity argument) to restore those endpoints. Orgs without identity_secret configured are unaffected.
Older versions
For releases prior to 0.4.0, see the source repository’spackages/sdk/CHANGELOG.md.