ttp-agent-sdk 2.48.16 → 2.48.18

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.
@@ -12129,7 +12129,7 @@ var TextChatSDK = /*#__PURE__*/function (_EventEmitter) {
12129
12129
 
12130
12130
  // SDK build time for debugging
12131
12131
  if (true) {
12132
- helloMessage.lastBuildTime = "2026-08-21T10:43:17.433Z";
12132
+ helloMessage.lastBuildTime = "2026-08-27T12:25:20.756Z";
12133
12133
  }
12134
12134
  try {
12135
12135
  this.ws.send(JSON.stringify(helloMessage));
@@ -21439,8 +21439,8 @@ var VoiceSDK = _v2_VoiceSDK_js__WEBPACK_IMPORTED_MODULE_0__["default"];
21439
21439
 
21440
21440
 
21441
21441
  // Version - injected at build time from package.json via webpack DefinePlugin
21442
- var VERSION = "2.48.16";
21443
- var BUILD_TIME = "2026-08-21T10:43:17.433Z";
21442
+ var VERSION = "2.48.18";
21443
+ var BUILD_TIME = "2026-08-27T12:25:20.756Z";
21444
21444
  console.log("%c TTP Agent SDK v".concat(VERSION, " (").concat(BUILD_TIME, ") "), 'background: #4f46e5; color: white; font-size: 12px; font-weight: bold; padding: 2px 6px; border-radius: 4px;');
21445
21445
 
21446
21446
  // Named exports
@@ -25615,6 +25615,7 @@ __webpack_require__.r(__webpack_exports__);
25615
25615
  /* harmony import */ var _codecs_PCMCodec_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./codecs/PCMCodec.js */ "./src/v2/codecs/PCMCodec.js");
25616
25616
  /* harmony import */ var _codecs_PCMUCodec_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./codecs/PCMUCodec.js */ "./src/v2/codecs/PCMUCodec.js");
25617
25617
  /* harmony import */ var _codecs_PCMACodec_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./codecs/PCMACodec.js */ "./src/v2/codecs/PCMACodec.js");
25618
+ /* harmony import */ var _hopPlatform_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./hopPlatform.js */ "./src/v2/hopPlatform.js");
25618
25619
  function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
25619
25620
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
25620
25621
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
@@ -25646,6 +25647,7 @@ function _setPrototypeOf(t, e) { return _setPrototypeOf = Object.setPrototypeOf
25646
25647
 
25647
25648
 
25648
25649
 
25650
+
25649
25651
  // iOS FIX: Shared AudioContext for playback that persists across AudioPlayer instances.
25650
25652
  // iOS WebKit doesn't release audio hardware synchronously when AudioContext.close() is called,
25651
25653
  // so creating a new AudioContext immediately after closing the old one can fail silently.
@@ -25745,6 +25747,7 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
25745
25747
  _this._mediaStreamDest = null;
25746
25748
  _this._htmlAudioRouteActive = false;
25747
25749
  _this._htmlAudioPlayOk = false;
25750
+ _this._htmlAudioPlayWatchdog = null;
25748
25751
  _this._loggedHopResample = false;
25749
25752
  // Greeting (and other bursts) fire many playChunk()s without await.
25750
25753
  // Serialize context creation so they cannot open two AudioContexts
@@ -27164,37 +27167,40 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
27164
27167
  }, {
27165
27168
  key: "wantsHtmlAudioPlayback",
27166
27169
  value: function wantsHtmlAudioPlayback() {
27167
- // Debug/QA override: ?ttp_hop=0|1 or localStorage 'ttp_hop' forces the route
27168
- // at the decision point, regardless of which config object reached us.
27169
- try {
27170
- var _URLSearchParams$get;
27171
- var flag = (_URLSearchParams$get = new URLSearchParams(window.location.search).get('ttp_hop')) !== null && _URLSearchParams$get !== void 0 ? _URLSearchParams$get : window.localStorage.getItem('ttp_hop');
27172
- if (flag === '0' || flag === '1') {
27173
- return flag === '1';
27174
- }
27175
- } catch (e) {/* non-browser env */}
27170
+ // Debug/QA override: ?ttp_hop=0|1, its sessionStorage mirror (survives
27171
+ // same-origin full page loads that drop the query string Borusan's
27172
+ // navigate_to_form), or localStorage 'ttp_hop'. Wins over config.
27173
+ var override = (0,_hopPlatform_js__WEBPACK_IMPORTED_MODULE_4__.readTtpHopOverride)(typeof window !== 'undefined' ? window : null);
27174
+ if (override !== null) {
27175
+ return override;
27176
+ }
27176
27177
  if (typeof this.config.htmlAudioPlayback === 'boolean') {
27177
27178
  return this.config.htmlAudioPlayback;
27178
27179
  }
27179
- // Default: the hop (AudioContext → MediaStreamDestination → HTMLAudioElement)
27180
- // gives Chrome's AEC a far-end reference, but Chrome plays that live stream
27181
- // with adaptive jitter-buffer speed control, which audibly wobbles on
27182
- // desktop Linux (PipeWire/PulseAudio). Off there, on elsewhere.
27183
- var hopOn = !this._isDesktopLinux();
27180
+ // Default: HTML hop (AudioContext → MediaStreamDestination → <audio>) gives
27181
+ // Safari/Chrome a far-end AEC reference on Apple mobile + macOS where Web
27182
+ // Audio destination is not in the echo-cancellation loop. On Windows and
27183
+ // Linux desktop, Chrome-wide AEC already covers speaker echo; the hop only
27184
+ // adds adaptive live-stream speed wobble — keep it off there (same as Linux).
27185
+ var hopOn = this._needsHtmlAudioHopForAec();
27184
27186
  if (!this._hopDefaultLogged) {
27185
27187
  this._hopDefaultLogged = true;
27186
- console.log("\uD83D\uDD0A AudioPlayer: htmlAudioPlayback default \u2192 ".concat(hopOn ? 'on (AEC far-end route)' : 'off (desktop Linux: hop causes playback speed wobble)'));
27188
+ var reason = hopOn ? 'on (mobile / macOS: AEC far-end route)' : 'off (desktop Windows/Linux: stable direct Web Audio playback)';
27189
+ console.log("\uD83D\uDD0A AudioPlayer: htmlAudioPlayback default \u2192 ".concat(reason));
27187
27190
  }
27188
27191
  return hopOn;
27189
27192
  }
27193
+
27194
+ /** Hop default on only where Web Audio destination lacks AEC far-end reference.
27195
+ * Policy lives in hopPlatform.js (pure, unit-tested); this only supplies the
27196
+ * environment strings. */
27190
27197
  }, {
27191
- key: "_isDesktopLinux",
27192
- value: function _isDesktopLinux() {
27198
+ key: "_needsHtmlAudioHopForAec",
27199
+ value: function _needsHtmlAudioHopForAec() {
27193
27200
  try {
27194
27201
  var ua = navigator.userAgent || '';
27195
- if (/Android|iPhone|iPad|iPod/i.test(ua)) return false;
27196
27202
  var platform = navigator.userAgentData && navigator.userAgentData.platform || navigator.platform || '';
27197
- return /linux/i.test(platform) || /Linux|X11/i.test(ua);
27203
+ return (0,_hopPlatform_js__WEBPACK_IMPORTED_MODULE_4__.needsHtmlAudioHopForAec)(ua, platform);
27198
27204
  } catch (e) {
27199
27205
  return false;
27200
27206
  }
@@ -27289,6 +27295,10 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
27289
27295
  }
27290
27296
  if (!this.audioContext || typeof this.audioContext.createMediaStreamDestination !== 'function') {
27291
27297
  console.warn('⚠️ AudioPlayer: MediaStreamDestination unavailable — using AudioContext.destination');
27298
+ this.emit('htmlAudioRoute', {
27299
+ state: 'fallback',
27300
+ reason: 'no_media_stream_destination'
27301
+ });
27292
27302
  return false;
27293
27303
  }
27294
27304
  try {
@@ -27306,31 +27316,72 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
27306
27316
  this._htmlAudioEl = el;
27307
27317
  this._htmlAudioRouteActive = true;
27308
27318
  this._ensureHtmlAudioPlaying();
27319
+ this._armHtmlAudioPlayWatchdog(el);
27309
27320
  console.log('🔊 AudioPlayer: Playback last-hop is HTMLAudioElement (AEC far-end route)');
27310
27321
  return true;
27311
27322
  } catch (e) {
27312
27323
  console.warn('⚠️ AudioPlayer: HTML audio route failed, falling back to destination:', e);
27313
27324
  this._teardownHtmlAudioRoute();
27325
+ this.emit('htmlAudioRoute', {
27326
+ state: 'fallback',
27327
+ reason: 'route_init_failed'
27328
+ });
27314
27329
  return false;
27315
27330
  }
27316
27331
  }
27332
+
27333
+ /**
27334
+ * play() on a MediaStream-backed element can stay pending forever on some
27335
+ * WebKit builds — neither resolving nor rejecting. If the element is still
27336
+ * paused shortly after route creation, report it: the hop never engaged and
27337
+ * AEC has no far-end reference, but audio still reaches the user via the
27338
+ * (silent-failure) direct path only if a fallback happened — so this state
27339
+ * means "route exists, playback through it unconfirmed".
27340
+ */
27341
+ }, {
27342
+ key: "_armHtmlAudioPlayWatchdog",
27343
+ value: function _armHtmlAudioPlayWatchdog(el) {
27344
+ var _this7 = this;
27345
+ this._clearHtmlAudioPlayWatchdog();
27346
+ this._htmlAudioPlayWatchdog = setTimeout(function () {
27347
+ _this7._htmlAudioPlayWatchdog = null;
27348
+ if (_this7._htmlAudioRouteActive && _this7._htmlAudioEl === el && !_this7._htmlAudioPlayOk && el.paused) {
27349
+ console.warn('⚠️ AudioPlayer: HTMLAudioElement.play() still pending after 2.5s — hop engagement unconfirmed');
27350
+ _this7.emit('htmlAudioRoute', {
27351
+ state: 'play_pending'
27352
+ });
27353
+ }
27354
+ }, 2500);
27355
+ }
27356
+ }, {
27357
+ key: "_clearHtmlAudioPlayWatchdog",
27358
+ value: function _clearHtmlAudioPlayWatchdog() {
27359
+ if (this._htmlAudioPlayWatchdog) {
27360
+ clearTimeout(this._htmlAudioPlayWatchdog);
27361
+ this._htmlAudioPlayWatchdog = null;
27362
+ }
27363
+ }
27317
27364
  }, {
27318
27365
  key: "_ensureHtmlAudioPlaying",
27319
27366
  value: function _ensureHtmlAudioPlaying() {
27320
- var _this7 = this;
27367
+ var _this8 = this;
27321
27368
  var el = this._htmlAudioEl;
27322
27369
  if (!el) return;
27323
27370
  if (!el.paused && this._htmlAudioPlayOk) return;
27324
27371
  var playResult = el.play();
27325
27372
  if (playResult && typeof playResult.then === 'function') {
27326
27373
  playResult.then(function () {
27327
- _this7._htmlAudioPlayOk = true;
27374
+ _this8._htmlAudioPlayOk = true;
27375
+ _this8._clearHtmlAudioPlayWatchdog();
27376
+ _this8.emit('htmlAudioRoute', {
27377
+ state: 'established'
27378
+ });
27328
27379
  }).catch(function (err) {
27329
- if (_this7._htmlAudioPlayOk || !el.paused) {
27380
+ if (_this8._htmlAudioPlayOk || !el.paused) {
27330
27381
  return;
27331
27382
  }
27332
27383
  console.warn('⚠️ AudioPlayer: HTMLAudioElement.play() rejected — falling back to destination:', err);
27333
- _this7._fallbackToDestination('play_rejected');
27384
+ _this8._fallbackToDestination('play_rejected');
27334
27385
  });
27335
27386
  }
27336
27387
  }
@@ -27344,10 +27395,15 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
27344
27395
  this._teardownHtmlAudioRoute();
27345
27396
  this.gainNode.connect(this.audioContext.destination);
27346
27397
  console.warn("\u26A0\uFE0F AudioPlayer: Fell back to AudioContext.destination (".concat(reason, ")"));
27398
+ this.emit('htmlAudioRoute', {
27399
+ state: 'fallback',
27400
+ reason: reason
27401
+ });
27347
27402
  }
27348
27403
  }, {
27349
27404
  key: "_teardownHtmlAudioRoute",
27350
27405
  value: function _teardownHtmlAudioRoute() {
27406
+ this._clearHtmlAudioPlayWatchdog();
27351
27407
  if (this._htmlAudioEl) {
27352
27408
  try {
27353
27409
  this._htmlAudioEl.pause();
@@ -27371,7 +27427,7 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
27371
27427
  key: "initializeAudioContext",
27372
27428
  value: (function () {
27373
27429
  var _initializeAudioContext = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee7() {
27374
- var _this8 = this;
27430
+ var _this9 = this;
27375
27431
  return _regenerator().w(function (_context8) {
27376
27432
  while (1) switch (_context8.n) {
27377
27433
  case 0:
@@ -27382,7 +27438,7 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
27382
27438
  return _context8.a(2, this._audioContextInitPromise);
27383
27439
  case 1:
27384
27440
  this._audioContextInitPromise = this._initializeAudioContextImpl().finally(function () {
27385
- _this8._audioContextInitPromise = null;
27441
+ _this9._audioContextInitPromise = null;
27386
27442
  });
27387
27443
  return _context8.a(2, this._audioContextInitPromise);
27388
27444
  }
@@ -27397,7 +27453,7 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
27397
27453
  key: "_initializeAudioContextImpl",
27398
27454
  value: function () {
27399
27455
  var _initializeAudioContextImpl2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee8() {
27400
- var _this9 = this;
27456
+ var _this0 = this;
27401
27457
  var ttsSampleRate, useHardwareRate, desiredSampleRate, contextFits, canReuseShared, setupAfterResume, ctxOpts, _t4;
27402
27458
  return _regenerator().w(function (_context9) {
27403
27459
  while (1) switch (_context9.p = _context9.n) {
@@ -27418,7 +27474,7 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
27418
27474
  _sharedPlayerSampleRate = null;
27419
27475
  }
27420
27476
  contextFits = function contextFits(rate) {
27421
- if (useHardwareRate) return _this9._isHardwarePlaybackRate(rate);
27477
+ if (useHardwareRate) return _this0._isHardwarePlaybackRate(rate);
27422
27478
  return Math.abs(rate - desiredSampleRate) <= 100;
27423
27479
  }; // Check if current instance AudioContext exists and matches
27424
27480
  if (!this.audioContext) {
@@ -27455,19 +27511,19 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
27455
27511
  console.log("\u267B\uFE0F AudioPlayer: Reusing shared AudioContext at ".concat(_sharedPlayerContext.sampleRate, "Hz (iOS-safe)"));
27456
27512
  this.audioContext = _sharedPlayerContext;
27457
27513
  setupAfterResume = function setupAfterResume() {
27458
- _this9.setupAudioContextStateMonitoring();
27459
- if (_this9.gainNode) {
27514
+ _this0.setupAudioContextStateMonitoring();
27515
+ if (_this0.gainNode) {
27460
27516
  try {
27461
- _this9.gainNode.disconnect();
27517
+ _this0.gainNode.disconnect();
27462
27518
  } catch (e) {
27463
27519
  console.warn('⚠️ AudioPlayer: Error disconnecting old GainNode:', e);
27464
27520
  }
27465
27521
  }
27466
- _this9.gainNode = _this9.audioContext.createGain();
27467
- _this9.gainNode.gain.value = 1.0;
27468
- _this9._connectGainToOutput();
27469
- if (!_this9._audioContextPrimed) {
27470
- _this9._primeAudioContext();
27522
+ _this0.gainNode = _this0.audioContext.createGain();
27523
+ _this0.gainNode.gain.value = 1.0;
27524
+ _this0._connectGainToOutput();
27525
+ if (!_this0._audioContextPrimed) {
27526
+ _this0._primeAudioContext();
27471
27527
  }
27472
27528
  };
27473
27529
  if (!(this.audioContext.state === 'suspended')) {
@@ -27659,7 +27715,7 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
27659
27715
  * Handles mic permission grants, tab switching, browser suspension, etc.
27660
27716
  */
27661
27717
  function setupAudioContextStateMonitoring() {
27662
- var _this0 = this;
27718
+ var _this1 = this;
27663
27719
  if (!this.audioContext) {
27664
27720
  return;
27665
27721
  }
@@ -27672,38 +27728,38 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
27672
27728
  // Create handler that references this.audioContext dynamically
27673
27729
  this._audioContextStateChangeHandler = function () {
27674
27730
  // Null check required because audioContext may be cleaned up while handler is queued
27675
- if (!_this0.audioContext) {
27731
+ if (!_this1.audioContext) {
27676
27732
  console.warn('⚠️ AudioPlayer: State change handler fired but AudioContext is null');
27677
27733
  return;
27678
27734
  }
27679
- console.log("\uD83C\uDFB5 AudioPlayer: AudioContext state changed to: ".concat(_this0.audioContext.state));
27680
- if (_this0.audioContext.state === 'running') {
27681
- _this0._ensureHtmlAudioPlaying();
27735
+ console.log("\uD83C\uDFB5 AudioPlayer: AudioContext state changed to: ".concat(_this1.audioContext.state));
27736
+ if (_this1.audioContext.state === 'running') {
27737
+ _this1._ensureHtmlAudioPlaying();
27682
27738
  }
27683
- if (_this0.audioContext.state === 'suspended' && _this0.isPlaying) {
27739
+ if (_this1.audioContext.state === 'suspended' && _this1.isPlaying) {
27684
27740
  // AudioContext was suspended during playback (tab switch, mic permission, etc.)
27685
27741
  console.warn('⚠️ AudioPlayer: AudioContext suspended during playback');
27686
27742
  // Note: Playback will pause automatically, but we should handle queue processing
27687
27743
  // The state change will be handled when we try to process next frame
27688
- } else if (_this0.audioContext.state === 'running' && !_this0.isPlaying && (_this0.audioQueue.length > 0 || _this0.pcmChunkQueue.length > 0 || _this0.preparedBuffer.length > 0)) {
27744
+ } else if (_this1.audioContext.state === 'running' && !_this1.isPlaying && (_this1.audioQueue.length > 0 || _this1.pcmChunkQueue.length > 0 || _this1.preparedBuffer.length > 0)) {
27689
27745
  // AudioContext resumed and we have queued frames
27690
27746
  // This handles: mic permission grant, tab switching back, browser resume, etc.
27691
27747
  console.log('✅ AudioPlayer: AudioContext resumed - resuming queue processing');
27692
27748
 
27693
27749
  // Resume queue processing if we have frames
27694
- if (_this0.audioQueue.length > 0 && !_this0.isProcessingQueue) {
27750
+ if (_this1.audioQueue.length > 0 && !_this1.isProcessingQueue) {
27695
27751
  setTimeout(function () {
27696
- return _this0.processQueue();
27752
+ return _this1.processQueue();
27697
27753
  }, 50);
27698
27754
  }
27699
- if (_this0.pcmChunkQueue.length > 0 && !_this0.isProcessingPcmQueue) {
27755
+ if (_this1.pcmChunkQueue.length > 0 && !_this1.isProcessingPcmQueue) {
27700
27756
  setTimeout(function () {
27701
- return _this0.processPcmQueue();
27757
+ return _this1.processPcmQueue();
27702
27758
  }, 50);
27703
27759
  }
27704
- if (_this0.preparedBuffer.length > 0 && !_this0.isSchedulingFrames) {
27760
+ if (_this1.preparedBuffer.length > 0 && !_this1.isSchedulingFrames) {
27705
27761
  setTimeout(function () {
27706
- return _this0.scheduleFrames();
27762
+ return _this1.scheduleFrames();
27707
27763
  }, 50);
27708
27764
  }
27709
27765
  }
@@ -27722,7 +27778,7 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
27722
27778
  key: "processQueue",
27723
27779
  value: (function () {
27724
27780
  var _processQueue = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee0() {
27725
- var _this1 = this;
27781
+ var _this10 = this;
27726
27782
  var audioBlob, wasFirstPlay, audioContext, arrayBuffer, audioBuffer, shouldEmitStart, source, _t6;
27727
27783
  return _regenerator().w(function (_context1) {
27728
27784
  while (1) switch (_context1.p = _context1.n) {
@@ -27782,22 +27838,22 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
27782
27838
  // Handle end
27783
27839
 
27784
27840
  source.onended = function () {
27785
- _this1.currentSource = null;
27786
- _this1.isProcessingQueue = false;
27841
+ _this10.currentSource = null;
27842
+ _this10.isProcessingQueue = false;
27787
27843
 
27788
27844
  // Process next chunk
27789
27845
 
27790
- if (_this1.audioQueue.length > 0) {
27846
+ if (_this10.audioQueue.length > 0) {
27791
27847
  setTimeout(function () {
27792
- return _this1.processQueue();
27848
+ return _this10.processQueue();
27793
27849
  }, 50);
27794
27850
  } else {
27795
27851
  // No more chunks - stop after delay
27796
27852
 
27797
27853
  setTimeout(function () {
27798
- if (_this1.audioQueue.length === 0 && !_this1.currentSource) {
27799
- _this1.isPlaying = false;
27800
- _this1.emit('playbackStopped');
27854
+ if (_this10.audioQueue.length === 0 && !_this10.currentSource) {
27855
+ _this10.isPlaying = false;
27856
+ _this10.emit('playbackStopped');
27801
27857
  }
27802
27858
  }, 100);
27803
27859
  }
@@ -27820,7 +27876,7 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
27820
27876
  if (this.audioQueue.length > 0) {
27821
27877
  this.isProcessingQueue = false;
27822
27878
  setTimeout(function () {
27823
- return _this1.processQueue();
27879
+ return _this10.processQueue();
27824
27880
  }, 100);
27825
27881
  } else {
27826
27882
  this.isPlaying = false;
@@ -28043,7 +28099,7 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
28043
28099
  }, {
28044
28100
  key: "markNewSentence",
28045
28101
  value: function markNewSentence(text, synced, segmentId) {
28046
- var _this10 = this;
28102
+ var _this11 = this;
28047
28103
  var wasStopped = this._isStopped;
28048
28104
  var isCurrentlyPlaying = this.isPlaying || this.scheduledSources.size > 0;
28049
28105
 
@@ -28102,34 +28158,34 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
28102
28158
  var sentenceText = text; // Capture for timeout callback
28103
28159
  this._emptySentenceTimeout = setTimeout(function () {
28104
28160
  // Check if this sentence still has no chunks after timeout
28105
- if (_this10.pendingSentenceText === sentenceText && _this10.scheduledBuffers === 0 && _this10.preparedBuffer.length === 0 && _this10.pcmChunkQueue.length === 0 && !_this10._isStopped) {
28161
+ if (_this11.pendingSentenceText === sentenceText && _this11.scheduledBuffers === 0 && _this11.preparedBuffer.length === 0 && _this11.pcmChunkQueue.length === 0 && !_this11._isStopped) {
28106
28162
  console.warn("\u26A0\uFE0F AudioPlayer: Empty sentence detected after 5s timeout - no chunks received for: \"".concat(sentenceText.substring(0, 40), "...\""));
28107
28163
  // If this empty sentence carried a segment id, report it done (nothing was heard) and
28108
28164
  // adopt it as current so the coarse stop below matches the backend's last-sent id.
28109
- if (_this10.pendingSegmentId != null) {
28110
- var emptySegId = _this10.pendingSegmentId;
28111
- _this10.currentSegmentId = emptySegId;
28112
- _this10.pendingSegmentId = null;
28113
- _this10.emit('segmentDone', {
28165
+ if (_this11.pendingSegmentId != null) {
28166
+ var emptySegId = _this11.pendingSegmentId;
28167
+ _this11.currentSegmentId = emptySegId;
28168
+ _this11.pendingSegmentId = null;
28169
+ _this11.emit('segmentDone', {
28114
28170
  segmentId: emptySegId,
28115
28171
  status: 'finished',
28116
28172
  playedMs: 0
28117
28173
  });
28118
28174
  }
28119
28175
  // Clear pending sentence to unblock next sentence
28120
- if (_this10.pendingSentenceText === sentenceText) {
28121
- _this10.pendingSentenceText = null;
28176
+ if (_this11.pendingSentenceText === sentenceText) {
28177
+ _this11.pendingSentenceText = null;
28122
28178
  }
28123
28179
  // Emit playbackStopped to allow next sentence to start
28124
28180
  // Only if we're not currently playing (to avoid interrupting real playback)
28125
- if (!_this10.isPlaying && _this10.scheduledSources.size === 0) {
28181
+ if (!_this11.isPlaying && _this11.scheduledSources.size === 0) {
28126
28182
  console.log('🛑 AudioPlayer: Emitting playbackStopped for empty sentence timeout');
28127
- _this10.emit('playbackStopped', {
28128
- segmentId: _this10.currentSegmentId
28183
+ _this11.emit('playbackStopped', {
28184
+ segmentId: _this11.currentSegmentId
28129
28185
  });
28130
28186
  }
28131
28187
  }
28132
- _this10._emptySentenceTimeout = null;
28188
+ _this11._emptySentenceTimeout = null;
28133
28189
  }, 5000); // 5 second timeout - adjust based on expected chunk arrival rate
28134
28190
  }
28135
28191
 
@@ -28139,14 +28195,14 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
28139
28195
  }, {
28140
28196
  key: "startTranscriptChecker",
28141
28197
  value: function startTranscriptChecker() {
28142
- var _this11 = this;
28198
+ var _this12 = this;
28143
28199
  if (this.isCheckingTranscripts) return;
28144
28200
  this.isCheckingTranscripts = true;
28145
28201
  console.log('📝 AudioPlayer: Transcript checker started');
28146
28202
  var _checkLoop = function checkLoop() {
28147
- if (!_this11.isCheckingTranscripts || !_this11.audioContext) return;
28148
- var currentTime = _this11.audioContext.currentTime;
28149
- var _iterator2 = _createForOfIteratorHelper(_this11.sentenceTimings),
28203
+ if (!_this12.isCheckingTranscripts || !_this12.audioContext) return;
28204
+ var currentTime = _this12.audioContext.currentTime;
28205
+ var _iterator2 = _createForOfIteratorHelper(_this12.sentenceTimings),
28150
28206
  _step2;
28151
28207
  try {
28152
28208
  for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
@@ -28160,13 +28216,13 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
28160
28216
  eventData.synced = _timing.synced;
28161
28217
  }
28162
28218
  console.log("\uD83D\uDCDD AudioPlayer: Display transcript at ".concat(currentTime.toFixed(3), "s: \"").concat(_timing.text.substring(0, 40), "...\" (synced: ").concat(_timing.synced ? _timing.synced.length : 0, ")"));
28163
- _this11.emit('transcriptDisplay', eventData);
28219
+ _this12.emit('transcriptDisplay', eventData);
28164
28220
  }
28165
28221
  // Per-segment natural finish: this segment's audio window has fully elapsed.
28166
28222
  if (!_timing.doneReported && _timing.endTime != null && currentTime >= _timing.endTime) {
28167
28223
  _timing.doneReported = true;
28168
28224
  var _playedMs = Math.round((_timing.endTime - _timing.startTime) * 1000);
28169
- _this11.emit('segmentDone', {
28225
+ _this12.emit('segmentDone', {
28170
28226
  segmentId: _timing.segmentId,
28171
28227
  status: 'finished',
28172
28228
  playedMs: _playedMs
@@ -28178,12 +28234,12 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
28178
28234
  } finally {
28179
28235
  _iterator2.f();
28180
28236
  }
28181
- if (_this11.isPlaying || _this11.scheduledBuffers > 0) {
28237
+ if (_this12.isPlaying || _this12.scheduledBuffers > 0) {
28182
28238
  requestAnimationFrame(_checkLoop);
28183
28239
  } else {
28184
28240
  // Playback drained naturally — flush any segment whose finish tick we may have missed
28185
28241
  // (the last buffer's onended can flip isPlaying=false before this loop's next tick).
28186
- var _iterator3 = _createForOfIteratorHelper(_this11.sentenceTimings),
28242
+ var _iterator3 = _createForOfIteratorHelper(_this12.sentenceTimings),
28187
28243
  _step3;
28188
28244
  try {
28189
28245
  for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
@@ -28192,7 +28248,7 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
28192
28248
  timing.doneReported = true;
28193
28249
  var end = timing.endTime != null ? timing.endTime : timing.startTime;
28194
28250
  var playedMs = Math.round((end - timing.startTime) * 1000);
28195
- _this11.emit('segmentDone', {
28251
+ _this12.emit('segmentDone', {
28196
28252
  segmentId: timing.segmentId,
28197
28253
  status: 'finished',
28198
28254
  playedMs: playedMs
@@ -28204,7 +28260,7 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
28204
28260
  } finally {
28205
28261
  _iterator3.f();
28206
28262
  }
28207
- _this11.isCheckingTranscripts = false;
28263
+ _this12.isCheckingTranscripts = false;
28208
28264
  console.log('📝 AudioPlayer: Transcript checker stopped');
28209
28265
  }
28210
28266
  };
@@ -28368,8 +28424,9 @@ __webpack_require__.r(__webpack_exports__);
28368
28424
  /* harmony import */ var _core_ClientToolsRegistry_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../core/ClientToolsRegistry.js */ "./src/core/ClientToolsRegistry.js");
28369
28425
  /* harmony import */ var _core_ClientScriptManager_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../core/ClientScriptManager.js */ "./src/core/ClientScriptManager.js");
28370
28426
  /* harmony import */ var _core_helloFlavor_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ../core/helloFlavor.js */ "./src/core/helloFlavor.js");
28371
- /* harmony import */ var _utils_screenshot_js__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ../utils/screenshot.js */ "./src/utils/screenshot.js");
28372
- /* harmony import */ var _utils_visual_tools_js__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ../utils/visual-tools.js */ "./src/utils/visual-tools.js");
28427
+ /* harmony import */ var _hopPlatform_js__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./hopPlatform.js */ "./src/v2/hopPlatform.js");
28428
+ /* harmony import */ var _utils_screenshot_js__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ../utils/screenshot.js */ "./src/utils/screenshot.js");
28429
+ /* harmony import */ var _utils_visual_tools_js__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ../utils/visual-tools.js */ "./src/utils/visual-tools.js");
28373
28430
  function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
28374
28431
  function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
28375
28432
  function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
@@ -28405,6 +28462,7 @@ function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e
28405
28462
 
28406
28463
 
28407
28464
 
28465
+
28408
28466
  /**
28409
28467
 
28410
28468
  * VoiceSDK v2 - Multi-codec speech-to-speech SDK
@@ -28502,23 +28560,20 @@ var VoiceSDK_v2 = /*#__PURE__*/function (_EventEmitter) {
28502
28560
  // Audio constraints for getUserMedia (optional)
28503
28561
  // If not provided, defaults will be used: echoCancellation: true, noiseSuppression: true, autoGainControl: true
28504
28562
  audioConstraints: config.audioConstraints || null,
28505
- // Last-hop TTS playback via hidden <audio> (AEC far-end). Default on.
28506
- // Set false to fall back to AudioContext.destination (old path).
28507
- // QA/debug override wins over config: ?ttp_hop=0 / localStorage ttp_hop=0
28508
- // forces AudioContext.destination; =1 forces the HTML hop.
28563
+ // Last-hop TTS playback via hidden <audio> (AEC far-end). Platform default
28564
+ // (hop on mobile/macOS only) applies when unset; set explicitly to force.
28565
+ // QA/debug override wins over config: ?ttp_hop=0|1 mirrored to
28566
+ // sessionStorage 'ttp_hop_pref' so it survives same-origin full page loads
28567
+ // (Borusan navigate_to_form) — then localStorage 'ttp_hop'.
28509
28568
  htmlAudioPlayback: function () {
28510
28569
  try {
28511
- var _URLSearchParams$get;
28512
- var flag = (_URLSearchParams$get = new URLSearchParams(window.location.search).get('ttp_hop')) !== null && _URLSearchParams$get !== void 0 ? _URLSearchParams$get : window.localStorage.getItem('ttp_hop');
28513
- if (flag === '0' || flag === '1') {
28514
- console.log("\uD83D\uDD27 VoiceSDK v2: ttp_hop override=".concat(flag, " \u2192 htmlAudioPlayback=").concat(flag === '1'));
28515
- return flag === '1';
28516
- }
28517
- if (flag !== null && flag !== undefined) {
28518
- console.log("\uD83D\uDD27 VoiceSDK v2: ttp_hop ignored (value '".concat(flag, "', expected '0' or '1')"));
28570
+ var override = (0,_hopPlatform_js__WEBPACK_IMPORTED_MODULE_7__.readTtpHopOverride)(typeof window !== 'undefined' ? window : null);
28571
+ if (override !== null) {
28572
+ console.log("\uD83D\uDD27 VoiceSDK v2: ttp_hop override \u2192 htmlAudioPlayback=".concat(override));
28573
+ return override;
28519
28574
  }
28520
28575
  } catch (e) {
28521
- console.log('🔧 VoiceSDK v2: ttp_hop check threw (storage blocked?):', e && e.message);
28576
+ console.log('🔧 VoiceSDK v2: ttp_hop check threw:', e && e.message);
28522
28577
  }
28523
28578
  // Pass through undefined so AudioPlayer applies the platform default
28524
28579
  return config.htmlAudioPlayback;
@@ -28665,7 +28720,7 @@ var VoiceSDK_v2 = /*#__PURE__*/function (_EventEmitter) {
28665
28720
  key: "_registerBuiltInTools",
28666
28721
  value: function _registerBuiltInTools() {
28667
28722
  try {
28668
- (0,_utils_visual_tools_js__WEBPACK_IMPORTED_MODULE_8__.registerVisualTools)(this.clientToolsRegistry);
28723
+ (0,_utils_visual_tools_js__WEBPACK_IMPORTED_MODULE_9__.registerVisualTools)(this.clientToolsRegistry);
28669
28724
  } catch (error) {
28670
28725
  console.error('❌ VoiceSDK: Error registering built-in tools:', error);
28671
28726
  console.error(' Error details:', error.message, error.stack);
@@ -28991,6 +29046,12 @@ var VoiceSDK_v2 = /*#__PURE__*/function (_EventEmitter) {
28991
29046
  key: "setupAudioPlayerEvents",
28992
29047
  value: function setupAudioPlayerEvents() {
28993
29048
  var _this3 = this;
29049
+ // The initial client_audio_info snapshot (at recordingStarted) predates
29050
+ // HTML-hop establishment, so its htmlAudioRouteActive/htmlAudioPlayOk are
29051
+ // always false. Re-report once the route outcome is known.
29052
+ this.audioPlayer.on('htmlAudioRoute', function (info) {
29053
+ _this3._sendClientAudioInfo(info);
29054
+ });
28994
29055
  this.audioPlayer.on('playbackStarted', function (info) {
28995
29056
  _this3.isPlaying = true;
28996
29057
 
@@ -29417,7 +29478,7 @@ var VoiceSDK_v2 = /*#__PURE__*/function (_EventEmitter) {
29417
29478
  // iOS in-app webviews lack "Safari" in the UA (real Safari always has it).
29418
29479
  var webview = isAndroid && (/\bwv\b/.test(ua) || /Version\/[\d.]+.*Chrome/.test(ua)) || isIos && !/Safari/i.test(ua) || false;
29419
29480
  var env = {
29420
- sdkVersion: true ? "2.48.16" : 0,
29481
+ sdkVersion: true ? "2.48.18" : 0,
29421
29482
  ua: ua,
29422
29483
  platform: (uaData === null || uaData === void 0 ? void 0 : uaData.platform) || navigator.platform || '',
29423
29484
  mobile: (_uaData$mobile = uaData === null || uaData === void 0 ? void 0 : uaData.mobile) !== null && _uaData$mobile !== void 0 ? _uaData$mobile : isAndroid || isIos,
@@ -29432,7 +29493,7 @@ var VoiceSDK_v2 = /*#__PURE__*/function (_EventEmitter) {
29432
29493
  } catch (e) {
29433
29494
  console.warn('⚠️ VoiceSDK v2: Failed to build client env:', e);
29434
29495
  return {
29435
- sdkVersion: true ? "2.48.16" : 0
29496
+ sdkVersion: true ? "2.48.18" : 0
29436
29497
  };
29437
29498
  }
29438
29499
  }
@@ -29446,12 +29507,14 @@ var VoiceSDK_v2 = /*#__PURE__*/function (_EventEmitter) {
29446
29507
  */
29447
29508
  }, {
29448
29509
  key: "_sendClientAudioInfo",
29449
- value: function _sendClientAudioInfo() {
29510
+ value: function _sendClientAudioInfo(routeUpdate) {
29450
29511
  try {
29451
29512
  var _this$audioRecorder, _this$audioRecorder$g, _s$echoCancellation, _s$noiseSuppression, _s$autoGainControl, _s$voiceIsolation, _s$sampleRate, _s$channelCount, _this$audioRecorder2, _this$audioPlayer, _this$audioRecorder3, _this$audioPlayer2, _this$audioPlayer2$ge;
29452
29513
  var track = (_this$audioRecorder = this.audioRecorder) === null || _this$audioRecorder === void 0 || (_this$audioRecorder = _this$audioRecorder.mediaStream) === null || _this$audioRecorder === void 0 || (_this$audioRecorder$g = _this$audioRecorder.getAudioTracks) === null || _this$audioRecorder$g === void 0 ? void 0 : _this$audioRecorder$g.call(_this$audioRecorder)[0];
29453
- if (!track) return;
29454
- var s = track.getSettings ? track.getSettings() : {};
29514
+ // Route updates must go out even before the mic track exists — the hop
29515
+ // state is the whole point of the follow-up.
29516
+ if (!track && !routeUpdate) return;
29517
+ var s = track && track.getSettings ? track.getSettings() : {};
29455
29518
  var audio = _objectSpread({
29456
29519
  aec: (_s$echoCancellation = s.echoCancellation) !== null && _s$echoCancellation !== void 0 ? _s$echoCancellation : null,
29457
29520
  ns: (_s$noiseSuppression = s.noiseSuppression) !== null && _s$noiseSuppression !== void 0 ? _s$noiseSuppression : null,
@@ -29459,11 +29522,15 @@ var VoiceSDK_v2 = /*#__PURE__*/function (_EventEmitter) {
29459
29522
  voiceIsolation: (_s$voiceIsolation = s.voiceIsolation) !== null && _s$voiceIsolation !== void 0 ? _s$voiceIsolation : null,
29460
29523
  trackRate: (_s$sampleRate = s.sampleRate) !== null && _s$sampleRate !== void 0 ? _s$sampleRate : null,
29461
29524
  channels: (_s$channelCount = s.channelCount) !== null && _s$channelCount !== void 0 ? _s$channelCount : null,
29462
- mic: track.label || null,
29525
+ mic: track ? track.label || null : null,
29463
29526
  captureCtxRate: ((_this$audioRecorder2 = this.audioRecorder) === null || _this$audioRecorder2 === void 0 || (_this$audioRecorder2 = _this$audioRecorder2.audioContext) === null || _this$audioRecorder2 === void 0 ? void 0 : _this$audioRecorder2.sampleRate) || null,
29464
29527
  playbackCtxRate: ((_this$audioPlayer = this.audioPlayer) === null || _this$audioPlayer === void 0 || (_this$audioPlayer = _this$audioPlayer.audioContext) === null || _this$audioPlayer === void 0 ? void 0 : _this$audioPlayer.sampleRate) || null,
29465
29528
  constraintsFallback: ((_this$audioRecorder3 = this.audioRecorder) === null || _this$audioRecorder3 === void 0 || (_this$audioRecorder3 = _this$audioRecorder3.mediaStream) === null || _this$audioRecorder3 === void 0 ? void 0 : _this$audioRecorder3._ttpConstraintsFallback) === true
29466
29529
  }, ((_this$audioPlayer2 = this.audioPlayer) === null || _this$audioPlayer2 === void 0 || (_this$audioPlayer2$ge = _this$audioPlayer2.getHtmlAudioPlaybackInfo) === null || _this$audioPlayer2$ge === void 0 ? void 0 : _this$audioPlayer2$ge.call(_this$audioPlayer2)) || {});
29530
+ if (routeUpdate && routeUpdate.state) {
29531
+ audio.routeUpdate = routeUpdate.state; // 'established' | 'fallback' | 'play_pending'
29532
+ if (routeUpdate.reason) audio.routeFallbackReason = routeUpdate.reason;
29533
+ }
29467
29534
  this.sendMessage({
29468
29535
  t: 'client_audio_info',
29469
29536
  audio: audio
@@ -29574,7 +29641,7 @@ var VoiceSDK_v2 = /*#__PURE__*/function (_EventEmitter) {
29574
29641
 
29575
29642
  // Include SDK build time for debugging
29576
29643
  if (true) {
29577
- helloMessage.lastBuildTime = "2026-08-21T10:43:17.433Z";
29644
+ helloMessage.lastBuildTime = "2026-08-27T12:25:20.756Z";
29578
29645
  }
29579
29646
 
29580
29647
  // Client environment (device/browser/webview) for backend logs + Langfuse metadata
@@ -29818,7 +29885,7 @@ var VoiceSDK_v2 = /*#__PURE__*/function (_EventEmitter) {
29818
29885
  case 'get_page_context':
29819
29886
  // Handle get_page_context request from backend
29820
29887
  console.log('📖 [VISUAL ASSISTANT] Backend requested page context');
29821
- (0,_utils_visual_tools_js__WEBPACK_IMPORTED_MODULE_8__.extractPageContext)().then(function (pageContext) {
29888
+ (0,_utils_visual_tools_js__WEBPACK_IMPORTED_MODULE_9__.extractPageContext)().then(function (pageContext) {
29822
29889
  // Use existing DOM scanner
29823
29890
  _this6.sendMessage({
29824
29891
  t: 'page_context',
@@ -29839,7 +29906,7 @@ var VoiceSDK_v2 = /*#__PURE__*/function (_EventEmitter) {
29839
29906
  console.log('📸 [VISUAL ASSISTANT] Backend requested screenshot');
29840
29907
  try {
29841
29908
  // Use existing screenshot capture function
29842
- (0,_utils_screenshot_js__WEBPACK_IMPORTED_MODULE_7__.captureScreenshot)().then(function (screenshot) {
29909
+ (0,_utils_screenshot_js__WEBPACK_IMPORTED_MODULE_8__.captureScreenshot)().then(function (screenshot) {
29843
29910
  _this6.sendMessage({
29844
29911
  t: 'screenshot',
29845
29912
  screenshot: {
@@ -32199,6 +32266,99 @@ var PCMUCodec = /*#__PURE__*/function () {
32199
32266
 
32200
32267
  /***/ }),
32201
32268
 
32269
+ /***/ "./src/v2/hopPlatform.js":
32270
+ /*!*******************************!*\
32271
+ !*** ./src/v2/hopPlatform.js ***!
32272
+ \*******************************/
32273
+ /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
32274
+
32275
+ "use strict";
32276
+ __webpack_require__.r(__webpack_exports__);
32277
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
32278
+ /* harmony export */ TTP_HOP_SESSION_KEY: () => (/* binding */ TTP_HOP_SESSION_KEY),
32279
+ /* harmony export */ isMacDesktopUa: () => (/* binding */ isMacDesktopUa),
32280
+ /* harmony export */ isMobileUa: () => (/* binding */ isMobileUa),
32281
+ /* harmony export */ needsHtmlAudioHopForAec: () => (/* binding */ needsHtmlAudioHopForAec),
32282
+ /* harmony export */ readTtpHopOverride: () => (/* binding */ readTtpHopOverride)
32283
+ /* harmony export */ });
32284
+ /**
32285
+ * HTML-audio-hop platform policy + the ttp_hop QA override.
32286
+ *
32287
+ * The "hop" is AudioContext → MediaStreamDestination → hidden <audio>, used so
32288
+ * the browser's echo canceller sees agent TTS as a far-end reference. Chrome
32289
+ * renders that live stream with adaptive jitter-buffer speed control, which
32290
+ * audibly wobbles on Windows/Linux desktop — so the hop defaults ON only where
32291
+ * Web Audio → destination genuinely lacks an AEC reference (mobile + macOS).
32292
+ *
32293
+ * Kept DOM-free (all inputs passed in) so the policy is unit-testable without
32294
+ * a browser: see test/hop-platform.test.mjs.
32295
+ */
32296
+
32297
+ /** sessionStorage key mirroring an explicit ?ttp_hop= URL value (see readTtpHopOverride). */
32298
+ var TTP_HOP_SESSION_KEY = 'ttp_hop_pref';
32299
+ function isMobileUa(ua) {
32300
+ return /Android|iPhone|iPad|iPod/i.test(ua || '');
32301
+ }
32302
+ function isMacDesktopUa(ua, platform) {
32303
+ if (/iPhone|iPad|iPod/i.test(ua || '')) return false;
32304
+ return /mac/i.test(platform || '') || /Mac OS X/i.test(ua || '');
32305
+ }
32306
+
32307
+ /**
32308
+ * Hop default: on only where Web Audio destination lacks an AEC far-end
32309
+ * reference (mobile + macOS). Windows/Linux/ChromeOS desktop: off — Chrome-wide
32310
+ * AEC already covers speaker echo there, and the hop only adds speed wobble.
32311
+ * Unknown/empty environment: off (fail toward the stable direct path).
32312
+ */
32313
+ function needsHtmlAudioHopForAec(ua, platform) {
32314
+ return isMobileUa(ua) || isMacDesktopUa(ua, platform);
32315
+ }
32316
+
32317
+ /**
32318
+ * QA/debug override for the hop route. Returns true (force hop on), false
32319
+ * (force off), or null (no override — apply config/platform default).
32320
+ *
32321
+ * Precedence: URL ?ttp_hop=0|1 → sessionStorage mirror → localStorage 'ttp_hop'.
32322
+ *
32323
+ * An explicit URL value is mirrored into sessionStorage (TTP_HOP_SESSION_KEY)
32324
+ * so it survives same-origin full page loads that drop the query string —
32325
+ * Borusan's navigate_to_form does window.location.href = '/path' (call
32326
+ * 84b5af7d), which used to silently re-apply the platform default mid-call.
32327
+ * The mirror dies with the tab; localStorage remains the sticky manual knob
32328
+ * and ranks below the mirror because the URL value is the more recent intent.
32329
+ *
32330
+ * Every storage touch is individually guarded: browsers can block one storage
32331
+ * area but not another, and a blocked sessionStorage must not cost us the
32332
+ * localStorage fallback.
32333
+ */
32334
+ function readTtpHopOverride(win) {
32335
+ var w = win || (typeof window !== 'undefined' ? window : null);
32336
+ if (!w) return null;
32337
+ var flag = null;
32338
+ try {
32339
+ flag = new URLSearchParams(w.location.search).get('ttp_hop');
32340
+ } catch (e) {/* no location in this env */}
32341
+ if (flag === '0' || flag === '1') {
32342
+ try {
32343
+ w.sessionStorage.setItem(TTP_HOP_SESSION_KEY, flag);
32344
+ } catch (e) {/* storage blocked */}
32345
+ return flag === '1';
32346
+ }
32347
+ var mirrored = null;
32348
+ try {
32349
+ mirrored = w.sessionStorage.getItem(TTP_HOP_SESSION_KEY);
32350
+ } catch (e) {/* storage blocked */}
32351
+ if (mirrored === '0' || mirrored === '1') return mirrored === '1';
32352
+ var stored = null;
32353
+ try {
32354
+ stored = w.localStorage.getItem('ttp_hop');
32355
+ } catch (e) {/* storage blocked */}
32356
+ if (stored === '0' || stored === '1') return stored === '1';
32357
+ return null;
32358
+ }
32359
+
32360
+ /***/ }),
32361
+
32202
32362
  /***/ "./src/v2/utils/AudioFormatConverter.js":
32203
32363
  /*!**********************************************!*\
32204
32364
  !*** ./src/v2/utils/AudioFormatConverter.js ***!
@@ -35803,7 +35963,7 @@ var TTPChatWidget = /*#__PURE__*/function () {
35803
35963
  return;
35804
35964
  }
35805
35965
  this._ensureAboutStyles();
35806
- var version = true ? "2.48.16" : 0;
35966
+ var version = true ? "2.48.18" : 0;
35807
35967
  var convId = this._getLastConversationId();
35808
35968
  var t = function t(k, fb) {
35809
35969
  try {