@pouchy_ai/companion-sdk 0.13.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -12,6 +12,24 @@ a protocol bump is always called out explicitly here.
12
12
 
13
13
  ## [Unreleased]
14
14
 
15
+ ## [0.14.0] - 2026-07-06
16
+
17
+ ### Added
18
+
19
+ - **Opt-in voice barge-in** — `connectCall({ bargeIn: true })` keeps the
20
+ microphone OPEN while the companion speaks, so the user can interrupt
21
+ mid-utterance and both providers' native VAD interruption takes over
22
+ (OpenAI Realtime server VAD; ElevenLabs Convai interruption handling).
23
+ Default remains half-duplex (mic muted during agent speech): on phone
24
+ speakers browser echo cancellation does not fully remove the companion's
25
+ own voice, and it would barge in on itself. Enable only where AEC holds —
26
+ headphones, desktop, or devices you have verified.
27
+ - The OpenAI Realtime microphone request now asks for explicit
28
+ `echoCancellation` / `noiseSuppression` / `autoGainControl` processing
29
+ constraints (harmless in half-duplex; required for barge-in to be viable).
30
+
31
+ No wire-protocol change (`PROTOCOL_VERSION` stays `1`).
32
+
15
33
  ## [0.13.0] - 2026-07-06
16
34
 
17
35
  ### Added
package/README.md CHANGED
@@ -123,6 +123,18 @@ the [source README](../../src/lib/companion-sdk/README.md#representative-mode-
123
123
 
124
124
  ## Voice (optional dependency)
125
125
 
126
+ **Zero-code alternative:** if you just want a chat box, skip the SDK entirely —
127
+ `<iframe src="https://pouchy.ai/embed?token=…&theme=dark&accent=%23ff6b81">` is a
128
+ hosted drop-in widget with a postMessage control plane (docs:
129
+ `docs/companion-widget.md` in the repo / pouchy.ai/sdk → Drop-in widget).
130
+
131
+ `connectCall({ bargeIn: true })` opts into **full-duplex** voice: the mic stays
132
+ open while the companion speaks so the user can interrupt mid-utterance (the
133
+ providers' native interruption takes over). The default is half-duplex — on
134
+ phone speakers echo cancellation can't fully remove the companion's own voice
135
+ and it would interrupt itself. Enable it only where AEC holds (headphones,
136
+ desktop, or devices you've verified).
137
+
126
138
  `connectCall()` for the **ElevenLabs Convai** provider needs the optional peer
127
139
  dependency:
128
140
 
package/dist/call.d.ts CHANGED
@@ -46,6 +46,14 @@ export interface CompanionCallOptions {
46
46
  }) => void;
47
47
  onSpeakingChange?: (speaking: boolean) => void;
48
48
  onError?: (err: Error) => void;
49
+ /** OPT-IN full duplex: keep the mic OPEN while the companion speaks so the
50
+ * user can interrupt mid-utterance (both providers' native VAD interruption
51
+ * then works). Default false = half-duplex — the mic is muted during agent
52
+ * speech, because on phone SPEAKERS browser echo cancellation does not fully
53
+ * remove the companion's own voice and it barges in on itself (the SDK-voice
54
+ * "说一句就断了" field report). Enable only where AEC actually holds:
55
+ * headphones, desktop, or devices you have verified. */
56
+ bargeIn?: boolean;
49
57
  }
50
58
  export interface CompanionCall {
51
59
  /** Hang up. Idempotent. */
package/dist/call.js CHANGED
@@ -163,7 +163,11 @@ const SDP_TIMEOUT_MS = 8_000;
163
163
  * we've already given up on the call. */
164
164
  async function getMicWithTimeout() {
165
165
  let timedOut = false;
166
- const micPromise = navigator.mediaDevices.getUserMedia({ audio: true });
166
+ const micPromise = navigator.mediaDevices.getUserMedia({
167
+ // Explicit processing constraints: echoCancellation is what makes the
168
+ // opt-in barge-in mode viable at all, and it is harmless in half-duplex.
169
+ audio: { echoCancellation: true, noiseSuppression: true, autoGainControl: true }
170
+ });
167
171
  micPromise
168
172
  .then((m) => {
169
173
  if (timedOut)
@@ -299,8 +303,10 @@ async function openOpenAICall(creds, opts, bridge) {
299
303
  else if (msg.type === 'output_audio_buffer.started') {
300
304
  opts.onSpeakingChange?.(true);
301
305
  // Model started speaking → mute the mic so its own audio can't
302
- // echo-transcribe into a self-barge-in.
303
- muteMicForSpeech();
306
+ // echo-transcribe into a self-barge-in. bargeIn opts out (B wave):
307
+ // the mic stays open and OpenAI's server VAD handles interruption.
308
+ if (opts.bargeIn !== true)
309
+ muteMicForSpeech();
304
310
  }
305
311
  else if (msg.type === 'output_audio_buffer.stopped' ||
306
312
  msg.type === 'output_audio_buffer.cleared' ||
@@ -311,7 +317,8 @@ async function openOpenAICall(creds, opts, bridge) {
311
317
  // audio doesn't get captured the instant we unmute. Several event names
312
318
  // can end a turn; any of them re-arms the mic.
313
319
  opts.onSpeakingChange?.(false);
314
- scheduleMicUnmute();
320
+ if (opts.bargeIn !== true)
321
+ scheduleMicUnmute();
315
322
  }
316
323
  };
317
324
  const offer = await pc.createOffer();
@@ -497,6 +504,10 @@ async function openConvaiCall(creds, opts, bridge) {
497
504
  return;
498
505
  const speaking = mode === 'speaking';
499
506
  opts.onSpeakingChange?.(speaking);
507
+ // bargeIn opts out of half-duplex (B wave): the mic stays open and
508
+ // ElevenLabs' native interruption handling takes over.
509
+ if (opts.bargeIn === true)
510
+ return;
500
511
  if (speaking) {
501
512
  if (micUnmuteTimer) {
502
513
  clearTimeout(micUnmuteTimer);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pouchy_ai/companion-sdk",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
4
4
  "description": "Embed the Pouchy companion — chat, voice, tools, memory, live world-state, instant UI, and agent-to-agent messaging — in any app, game, or site.",
5
5
  "type": "module",
6
6
  "license": "SEE LICENSE IN LICENSE",