# Changelog

## 0.22.0

- Playback-health telemetry: the player counts mid-reply underruns (queue drained at the
  speaker) and, at each reply's `done`, the client sends
  `{"type":"client_event","event":"playback_report", underruns, starved_ms, max_gap_ms, turn_id}`
  over WebSocket transports. No behaviour change: buffering is untouched; the report is
  record-only on the server and sizes the jitter buffer from real sessions. Not sent over
  WebRTC, where the browser owns the jitter buffer.

## 0.21.1

- Removed `playAcknowledgements` and the `ack` frame handler: the server does not send assistant
  backchannel clips yet (roadmap, not shipped), so the option was a no-op and the docs overstated
  it. Passing the old option is still silently accepted.

## 0.21.0 - 2026-08-30

- `mode.end_call` (default `false`) declares the managed `end_call(farewell)` tool so the agent can
  end the session. When it calls the tool, Converse speaks only the farewell, sends
  `session_end_requested` with that `farewell`, and closes after a short grace unless the user
  speaks. Without the flag the agent cannot end the session; the host ends it with `wrap_up` or by
  closing. While enabled, the name `end_call` is reserved, like `web_search` (`invalid_tools`). Pass-through only:
  validation and the default are the only client changes.

## 0.20.0 - 2026-08-28

- `mode.modality` selects `"voice"` (the default) or `"text"`. Text sessions use the same
  instructions, tools, history and event lifecycle without microphone or playback setup.
- `sendText(text)` commits one `input_text` user turn in text mode and returns whether it was
  written to the live connection. In voice sessions `sendText` is unchanged: still the user-role
  `injectContext` shorthand returning the acknowledgement promise, so existing voice
  integrations keep working. Text rejects microphone capture and the WebRTC transport so
  integrations cannot accidentally open an unused media pipeline.

## 0.19.1 - 2026-08-22

- Tool declarations: `expected_duration` (`"instant"` / `"seconds"` / `"long"`) replaces
  `wait_for_tool` in the docs. It says what the caller should hear on a tool turn (the answer
  directly, or an acknowledgement first); left out, Converse learns from observed results.
  `wait_for_tool: true` still passes through as a deprecated alias of `"instant"` during alpha and
  will be removed at beta. Pass-through only: no client code changed.

## 0.19.0 - 2026-08-22

- `ambience` constructor option (`'thinking'` DEFAULT; `'off'`, `'continuous'`; or an object with
  `mode` plus `afterS`/`fadeInS`/`fadeOutS`/`level`) and `client.setAmbience(mode)`: a soft
  generative bed rendered in the SDK and mixed THROUGH the SDK player, so it sits in the echo
  canceller's far-end reference on every transport and on WebKit/iOS (where the old playground
  bed on its own AudioContext had to be disabled). `'continuous'` plays it under the whole call
  from the first reply; `'thinking'` plays it only while Converse is blocking on a tool result with
  nothing to say - fading in after ~1.5 s of silence and out again under the reply's first
  syllables, a real crossfade - so a caller waiting on a slow backend hears "still working"
  instead of dead air. The thinking sound is ON BY DEFAULT from this release (it plays nothing
  unless a tool wait runs long); pass `ambience: 'off'` to keep the old silence. Bed-only audio
  is never queued ahead of a reply and never counts toward
  barge `discarded_ms`. Same musical design and constants as the server-mixed WebRTC
  `background_audio` bed (the two renderers share the score, not the samples). WebSocket
  transport only: over webrtc the SDK player is not in the audio path, so the local ambience
  stays silent and `mode.background_audio` is the option there.
- New server event `working` (`active: true|false`): Converse is blocking on a tool result
  (client tools, web_search, think_deeply) with nothing audible, or that wait ended. Drives the
  thinking sound; also usable for a "working..." UI state.
- `StreamingPlayer.setUnderlay(bed)` / `resumeUnderlay()`: the underlay path the ambience uses.
  Audio is now resampled at schedule time rather than at enqueue (no wire or API change).

## 0.18.0 - 2026-08-18

- `mode.background_audio` (default `false`): a server-mixed background underscore that plays for
  the whole call, so the silence between turns feels connected. **Requires
  `transport: 'webrtc'`** — the bed rides the server's playout track, the only downlink that runs
  continuously between turns. The SDK drops the field with a console warning rather than sending
  it on any non-WebRTC session, which matters because WebKit downgrades `webrtc` to `ws` for you;
  without that, asking for both would cost the whole session on iOS instead of just the music.

## 0.17.0 - 2026-08-17

- **Breaking (inert):** removed the reversible-playback protocol. The broker stopped sending
  `playback_pause`/`playback_resume` when the ink-decider barge fallback was deleted, so this
  client half has been dead code since. `StreamingPlayer.pause()`, `.resume()` and `.paused` are
  gone, custom players no longer need them, and start frames no longer advertise
  `playback_pause_v1` (`client.capabilities` is now always `[]`). Barge handling is unchanged:
  `interrupted` still fade-clears and reports `discarded_ms`/`remaining_ms`.

## 0.16.0 - 2026-08-16

- Interactions accept a `resolver` binding (`{tool, args, option_args, answer_arg}`): once the ask
  has been voiced, subsequent user turns are constrained to an explicit transition
  (resolve / clarify / supersede / cancel) via the broker-managed `interaction_transition` tool,
  and `resolve` executes the bound client tool with host-declared arguments — the model picks the
  option, never the arguments. See docs/client-tool-protocol.md §3a.

## 0.15.0 - 2026-08-16

- `tool_choice` lands in `ConverseMode` and as `setToolChoice(choice, { oneShot })` — the familiar
  OpenAI/Gemini restriction vocabulary (`"auto"` | `"none"` | `"required"` | `{allowed: [...]}` |
  `{tool: "..."}`). `required`/`allowed`/`tool` constrain the first planning round of each user
  turn; `none` withholds declared client tools while broker protocol tools stay available;
  `oneShot` reverts after the next user turn. Unknown names are rejected server-side with the new
  `invalid_tool_choice` error code; `setTools` resets the choice to `"auto"`.
## 0.14.1 - 2026-08-16

- `sendToolInteractionUpdate` normalizes whitespace-padded interaction ids the same way the server
  does, so the ack (which echoes the stripped id) correlates instead of surfacing as a timeout;
  ack correlation is additionally state-matched, so a server answer arriving after a client-side
  timeout can no longer be handed to the next same-id caller.

## 0.14.0 - 2026-08-16

- Interactions now carry a stable identity: pass `interaction.id` in `sendToolPartialResult` (or
  read the broker-derived id from `tool_job_narration.interaction_ids`), then close an ask without
  completing its parent call via `sendToolInteractionUpdate(id, interactionId, state, { note })`
  (`state`: `resolved` | `cancelled` | `superseded`). Queued or actively-speaking narration for it
  stops, the model is told not to act on it, and the returned promise resolves with the server's
  deterministic `tool_interaction_update_ack` (late/duplicate/unknown updates come back
  `applied: false` with a stable `reason`). Add `interactionState(interactionId)`; narration and
  interaction state caches now reset on connection loss (a resumed session implicitly supersedes
  any open interaction — re-raise it if still needed).

## 0.13.1 - 2026-08-15

- `sendToolPartialResult(id, content, { interaction })` marks a partial as needing a user decision
  (docs/client-tool-protocol.md §3a): unlike `reply: true` alone, it is never silently dropped when
  the floor is busy — it queues and preempts pending completion narration, with a far more
  persistent delivery retry. Add `narrationState(jobId)` and `waitForNarrationState(jobId, states,
  { timeoutMs })` to track its queued/started/superseded/cancelled lifecycle via the new
  `tool_job_narration` server frame.

## 0.13.0 - 2026-08-13

- `startMic()` now resolves only after the first AudioWorklet frame. If an opened capture produces
  no frames within the bounded startup window, the SDK fully releases it and reacquires once; a
  repeated stall rejects with the structured `capture_stalled` error. Silent frames remain valid.
- Add `warming_up`, `listening`, `recovering`, and `failed` capture lifecycle events so host apps can
  render status without implementing their own retry or readiness fallback.
- Add audio-input enumeration and selection with `getInputDevices()`, `setInputDevice(deviceId)`,
  the `inputDeviceId` option/property, `devices_changed`, and `input_device_changed`. Active capture
  follows relevant `devicechange` events through guarded track restart.
- Make `stopMic()` a cancellation barrier and harden stop/retry, overlapping device-switch, and
  delayed WebRTC track-replacement races without leaking tracks, worklets, or AudioContexts.

## 0.12.3 - 2026-08-11

- Tool result, progress, deferred, partial-result, and cancellation methods now return whether a
  live transport accepted the frame, allowing durable bridges to retain controls across reconnects.

## 0.12.2 - 2026-08-11

- Keep a newer successful connection authoritative when an older failed WebSocket delivers its
  close event late, so explicit capacity retries cannot clear the live transport.

## 0.12.1 - 2026-08-11

- Bound correlated injection acknowledgement waits and reject with cleanup when an older or
  mismatched broker never returns `inject_context_ack`.

## 0.12.0 - 2026-08-11

- `injectContext(text, {messageId, role, reply})` now returns the broker's authoritative
  accepted/rejected acknowledgement. The client-generated ID and `input_source: "text"` are
  echoed on the canonical final `asr` event; spoken transcripts carry `input_source: "voice"`.
- `sendText(text, {messageId})` exposes the same correlation and acknowledgement contract.

## 0.11.0 - 2026-08-11

- Add `sendText(text)` as the concise alias for a typed user turn that should receive a reply.
  Typed and spoken turns use the same `asr` event contract, including a stable `turn_id`.

## 0.10.0 - 2026-08-10

- Add a supported, versioned Browser SDK resume-state API for full page reloads:
  `resumeState`, `exportResumeState()`, `importResumeState()`, and the `resume_state` event. The
  state rotates with each server token and clears on terminal or rejected sessions, so apps can
  safely keep a tab-scoped `sessionStorage` copy without reading private SDK fields.
- Add `injectContext(text, {role, reply})` as the supported Browser SDK surface for the existing
  `inject_context` protocol frame, including proactive host announcements with `reply: true`.

## 0.9.0 - 2026-08-09

- Add `mode.silence_nudge_s` / `mode.silence_end_s` to override the broker's two-stage idle
  policy (check-in nudge, then sign-off + end) per session — useful for benchmark harnesses or
  flows with long think-time. Both default to the broker's env-configured values (10s/20s) when
  omitted; the broker also falls back to those defaults if either value is non-positive or
  `silence_end_s` does not exceed `silence_nudge_s`.

## 0.8.0 - 2026-08-08

- License Trelis-authored Browser SDK code under Apache 2.0; bundled AEC components remain under
  the third-party terms reproduced in the package.
- Keep assistant playback on the browser's unity-gain path, with no SDK limiter, software boost,
  output-route switch, or `navigator.audioSession` manipulation.
- Document that device volume, physical routing, and mobile full-duplex attenuation are controlled
  by the browser and operating system; native integration is required for routing guarantees.
- Document the Browser SDK support matrix across Chromium, Brave, Firefox and Safari, including
  the automatic WebKit fallback from experimental WebRTC to WebSocket.

## 0.7.0 - 2026-08-07

- Add `sendToolDeferred(id, {handle, statusLabel})` for jobs that outlive their originating voice
  turn. Eligible tools opt in with `deferred`, `deferred_timeout`, and `notify_on_complete`.
- Deferred jobs remain addressable by their original call ID or host handle for progress,
  cancellation, and exactly one terminal result. The SDK forwards `tool_deferred_ack` and
  `tool_deferred_resume` events through the normal typed and catch-all event APIs.
- Tool interruption is unchanged: barge-in never cancels deferred work; explicit cancellation does.

## 0.6.0 - 2026-08-06

- **Hidden-tab playback fix.** Browsers throttle `setInterval` to ~1 Hz in hidden tabs while the
  AudioContext keeps running, so the player's 0.2 s scheduling horizon starved playback into
  0.2 s bursts with ~0.8 s gaps whenever the user wasn't looking at the page. The player now
  commits a 2.5 s horizon while `document.visibilityState === 'hidden'` (and tops up immediately
  on `visibilitychange`), keeping playback gapless; the short barge-friendly horizon is unchanged
  in visible tabs, and barge/clear still stops committed-but-unplayed sources at any horizon.
- **Tool replies from anywhere.** `ConverseClient` gains `sendToolResult(id, content)`,
  `sendToolProgress(id, note)`, `sendToolPartialResult(id, content, {reply})` and
  `sendToolCancel(id)`, mirroring the Python SDK. Listen for `tool_call` events and answer them
  from the page or relay them to a backend — no raw-socket sidecar needed.
- `binaryToFloat32` and `toWebSocketUrl` are re-exported from the package root for raw-socket
  integrations.
- Server-side (deployed independently): the per-tool `timeout` ceiling rises from 120 s to 600 s
  for long-running agentic tools.

## 0.5.0 - 2026-08-06

- **Downlink audio is PCM16 on the wire — required update.** The server's downlink default
  changed from Float32 to PCM16 (bandwidth-halving negotiation, `start.audio.output_encoding`).
  0.4.5 and earlier decode the downlink as Float32 and therefore produce noise or decode errors
  against current servers. This release requests `output_encoding: "pcm16"` explicitly in the
  start frame, decodes Int16, and fails `connect()` loudly if the server's `ready.audio` field
  announces a format other than pcm16 at 16 kHz instead of playing garbage.
- `transport: 'webrtc'` option (experimental): carries the session over a WebRTC peer connection
  with a native remote audio track; the WS protocol remains the default and recommended path.
  Includes bounded ICE gathering, the `webrtc_connect_failed` error, and mic dropout/resampler
  fixes from the field-test rounds.

## 0.4.5 - 2026-07-30

- **Remove the speaker/earpiece output option entirely (`audioOutputMode`, `setAudioOutputMode`,
  and the 0.4.1 `<audio>`-element sink)** — settled by an 11-configuration on-device experiment
  (iPhone 17, Chrome + Safari): modern iOS routes web audio to the loudspeakers in every reachable
  configuration, "earpiece AND speaker at once" is normal iPhone stereo playback (the receiver is
  the second stereo speaker), and no configuration reaches earpiece-only. Web pages have no output
  routing to control, so the SDK ships none: playback is a plain
  `AudioContext` → master gain → `destination` path on every platform. The
  never-touch-`navigator.audioSession` regression guard remains.

## 0.4.4 - 2026-07-30

- **Revert 0.4.3's `navigator.audioSession` usage — field-falsified on real iPhones (Chrome and
  Safari).** Setting `type = 'playback'` (speaker mode) before capture made `getUserMedia` fail
  with a mic-permission error — a broken mic in the DEFAULT mode — and `'play-and-record'`
  (earpiece mode) still produced earpiece+loudspeaker dual output. Speaker routing is back on the
  0.4.2 `<audio>`-element sink (mic works; known-imperfect dual output remains the open bug). A
  regression test now pins that the SDK never touches `navigator.audioSession`.
  `setAudioOutputMode()` (live switching) and the speaker/earpiece option remain.

## 0.4.3 - 2026-07-30

- `ConverseClient.setAudioOutputMode(mode)`: the 0.4.2 speaker/earpiece choice is now live-switchable
  mid-session (previously took effect on the next `Start` only) — re-wires `StreamingPlayer`'s output
  route and, on WebKit, sets `navigator.audioSession.type` (`'playback'` for speaker, `'play-and-record'`
  for earpiece; Safari 16.4+, feature-detected, a no-op elsewhere). This directly targets the
  documented WebKit routing decision the `<audio>`-element sink workaround (0.4.1) could only work
  around indirectly — field testing on iPhone showed the 0.4.1 fix alone produced simultaneous
  earpiece + loudspeaker output rather than a clean switch, and `navigator.audioSession.type` is the
  platform's own API for this exact "mic is live, but I want loudspeaker anyway" case.
- The two mechanisms are mutually exclusive, not stacked: where `navigator.audioSession` exists
  (Safari 16.4+), the 0.4.1 `<audio>`-element sink route is skipped entirely (`hasAudioSessionApi()`
  in `aec.js`) — running both at once would leave two live output paths fighting over the same
  routing decision, the same shape that produced the dual-output field result. The element sink
  remains only as the pre-16.4 WebKit fallback.

## 0.4.2 - 2026-07-30

- Add the `audioOutputMode` (`'speaker' | 'earpiece'`, default `'speaker'`) `StreamingPlayer`
  constructor option so apps can explicitly choose between the loudspeaker route (0.4.1) and the
  platform's own call-audio/earpiece routing on iOS/WebKit, instead of only ever getting one. No
  effect on platforms without that fork (desktop, Android).

## 0.4.1 - 2026-07-30

- Route assistant playback through a sink `<audio>` element (instead of `AudioContext.destination`
  directly) on iOS/WebKit, to avoid the call-audio session routing playback to the earpiece
  receiver instead of the loudspeaker while a mic stream is active (WebKit bug 218012). Pending
  on-device confirmation.

## 0.4.0 - 2026-07-29

- Publish the package publicly on npm with installation and authentication guidance.

- Advertise reversible playback only for players that implement pause and resume, and report the
  actual SDK-owned microphone/AEC frontend across reconnects for echo diagnostics.
- Make web search opt-in by default.
- Add `setMicEnabled(enabled)` to gate SDK-owned microphone tracks without reopening capture.
- Add the default-on `playAcknowledgements` constructor option so half-duplex integrations can
  suppress automatic backchannel playback while retaining `ack` and `audio` event dispatch.
- Add `setVoice(voice)` to switch character voice from the next reply and reassert it on reconnect.
- Add `sendAmbienceState(active)` to report the client's ambience state on the session timeline.

## 0.3.1 - 2026-07-16

- Enable web search by default while preserving an explicit `webSearch: false` opt-out.

## 0.3.0 - 2026-07-16

- Add the opt-in `webSearch` session capability.

## 0.2.1 - 2026-07-15

- Gate `playback_pause_v1` to desktop Chromium and Firefox until physical WebKit validation.

## 0.2.0 - 2026-07-15

- Add synchronized processed/raw uplink framing for raw-assisted barge detection.
- Keep WebKit on one physical raw capture teed through SDK WASM AEC.
- Add reversible `playback_pause` / `playback_resume` handling for backchannels.
- Add AEC configuration plumbing and explicit desktop/WebKit engine controls.
- Fail closed to processed-only audio when raw capture or classification is unavailable.

## 0.1.0

- Initial browser SDK extracted from the Converse web client.
