Releases: SmooAI/chat-widget
Release list
v0.16.5
Patch Changes
-
eb336f4: Fix a returning visitor with a dead session pointer getting a brand-new conversation (SMOODEV-3057)
connect()probed/internal/resume-by-fingerprintonly when there was NO
persistedsessionId. A persisted-but-dead pointer cleared itself and went
straight tocreate_conversation_session, skipping the probe — so a visitor
whose stored session had ended minted a fresh conversation even when a resumable
one existed, and one visitor showed up as several inbox rows. The dead-pointer
path now falls through to the same probe.Also makes the probe legible. It swallowed every failure in a bare
catch {}
and{ resumable: false }carried no reason, so "no prior visit", "blocked by
the CRM link", "session ended" and "the lookup 500'd" were indistinguishable —
which is why this went undiagnosed. The probe now always yields a reason (the
server's when the response carries one, a derived label when it does not),
logs it atconsole.debug, and exposes it as
ConversationController.lastResumeReason. Failures stay non-fatal: a resume
probe still never breaksconnect().Dead-session recovery (
send()→recreateSession()) now runs the same probe
before minting. A session id the pod's registry has lost is often still alive in
storage, and the wrapper reads storage — so the visitor's conversation is
recovered instead of splitting in two mid-chat. Bounded as before:send()
retries this path exactly once.Every
create_conversation_sessionnow carries aresumeDiagnosticsblock in
its metadata —storage(durable/memory),pointer(none/dead/recovery) and
probe(the resume reason).metadata_jsonis persisted on the session row, so
the next duplicate pair can be explained from Postgres rather than from a
browser console nobody was watching.storage: 'memory'is the notable one: a
sandboxed iframe or privacy mode silently drops the widget to an in-memory
store, so it cannot recognise its own previous visit and every page load starts
from scratch.
v0.16.4
Patch Changes
-
f1048ba: Fix: the widget could report "Online" and then fail every send
A visitor on smoo.ai completed the pre-chat form, saw the status go "Online", and then got "We couldn't reach the chat." on every turn while the backend was entirely healthy. Three defects, all on the connect path:
connect()early-returned while a connect was in flight. The guard was right — one connect, not two — but it returned an already-resolved promise, so everyawait connect()resolved before a session existed andsend()fell through to its "not connected" throw. Four of the six call sites are fire-and-forgetvoid connect()(launcher click, pre-chat submit, full-page mount, voice hand-off), so an awaiting caller racing an in-flight one is the normal case, not an edge. Concurrent callers now share one connect and eachawaitresolves when that connect finishes.- A
create_conversation_sessionthat resolved without a session id was accepted, assigningundefinedand still flipping the status toready. That is a permanent wedge: every later send hits the missing id, callsconnect(), is early-returned by thereadystatus, and throws again. It now fails honestly and retryably. tryResume()read the session snapshot'sstatusoutside itstry/catch. The operator client returns animmediate_response'sdatawithout checking its status, so an error-status reply carrying nodataresolves asundefinedrather than rejecting — and the property read threw aTypeErrorpast the catch and out ofconnect().
Also hardened:
send()now renders the one human error sentence when a connect is genuinely fatal, instead of rejecting out ofsend()— which dropped the visitor's typed text into an empty transcript and raised an unhandled rejection on the host page. A failure reaching the optional resume probe is recoverable by starting a fresh session; a failure reaching the transport itself stays fatal.
v0.16.3
Patch Changes
-
f3e48d8: Never render a raw backend error as agent dialogue, and recover from a dead session.
A visitor on a live marketing site was shown
Error: session '<uuid>' not found
in what looks like an agent chat bubble:send()'s catch rendered
ProtocolError.messageverbatim. That was the only seam where an error frame
could reach the transcript; every failure now renders one short human sentence
(connectionErrorMessage), with the machine detail going to the connection-status
channel instead.The turn also had no recovery once the session died mid-conversation — the widget
wedged and every retry re-sent into the same dead session. ASESSION_NOT_FOUND
on send now clears the stale pointer (dropping any OTP proof bound to it),
creates a fresh session on the same socket, and re-sends the turn once, so the
visitor's message still gets answered. Matched on the protocolcodeand on that
code alone:STORAGE_ERROR,INTERNAL_ERROR, auth and rate-limit failures leave
the live session and its history intact.
v0.16.1
Patch Changes
- a5225bc: Voice: stop the agent's opening greeting from cutting itself off. Barge-in was firing on a single mic frame (~2.67ms) over the RMS threshold, so residual acoustic echo from the greeting (browser AEC is imperfect on speakers), a cough, or a noise blip would interrupt the agent mid-sentence — most visibly, the greeting never finished. Barge-in now requires ~200ms of sustained above-threshold audio (new
bargeInMinMsoption, default 200); real speech crosses it easily, transient blips don't. The run resets on any quiet frame and on each new agent utterance.
v0.16.0
Minor Changes
- 6436692: Voice: speak-and-read mode + seamless voice→text continuity (SMOODEV-2674). A speaker toggle beside the mic lets visitors turn agent speech off — sessions start STT-only (
tts:falseon the browser-voice start frame; the server skips TTS entirely) while replies still arrive as chat bubbles. Newvoice.ttsconfig sets the default. Starting voice now connects the text session first, so a voice-first visitor lands in a real conversation that continues seamlessly when they switch back to typing.
v0.15.2
Patch Changes
- eb7717c: Suggested-reply chips now render inline in the message flow, directly under the latest assistant reply (previously a fixed slot above the composer, visually detached), and the strip is removed the instant a chip is tapped. Chips remain optional shortcuts — the composer stays live.
v0.15.1
Patch Changes
- dc17f25: Fix heavy static on browser-voice TTS playback (SMOODEV-2668): re-align linear16 frames that split mid-sample (an odd-length frame made every later frame decode one byte off — pure noise), resample 16 kHz audio to the AudioContext's native rate with a proper band-limited streaming upsampler instead of forcing a 16 kHz context (whose browser-side output resampling mirrored speech energy above 8 kHz as harsh imaging static), and prime playback ~100 ms off the playhead when the queue drains so just-in-time chunks don't click.
v0.15.0
Minor Changes
- c492c54: Render the new
stream_preambleframe: when the operator streams a fast-model preamble ("what I'm about to do") ahead of the answer, show it in the assistant bubble's typing slot (muted + italic) instead of the bare typing dots, then swap in the real answer the moment it starts streaming. A late preamble frame (after the answer began) is ignored. Requires@smooai/smooth-operator≥ 1.22.15 (addsstream_preambleto the event union so the client forwards it).
v0.14.1
Patch Changes
- db9a07a: Fix a transient double-newline (extra blank line) during streaming. The streamed
token render and the finalizedresponsePartsrender now share one paragraph
separator and normalizer, so mid-stream no longer shows an extra blank line that
vanishes when the message finalizes. The finalized output is unchanged for
well-formed responses.
v0.14.0
Minor Changes
- 1295007: Voice input/output (SMOODEV-2534): mic capture + TTS playback over the browser-voice WebSocket, shipped dark behind the new
voice: { enabled, url? }config option (OFF by default — zero UI when off). AddsVoiceSession(framework-free WS lifecycle, getUserMedia → AudioWorklet/ScriptProcessor → 16 kHz linear16 mic streaming, gapless PCM playback, RMS barge-in withinterrupt+ playback flush), an Aurora Glass mic toggle in the composer with listening/speaking indicators and a live partial transcript in the input, and voice turns (transcript_final/reply_text) rendered through the normal chat message path. The text session's conversation id is passed asconversation_idso voice resumes the same thread.