ttp-agent-sdk 2.34.7 → 2.34.8

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.
@@ -13660,7 +13660,7 @@ var TTPChatWidget = /*#__PURE__*/function () {
13660
13660
  this.landingScreen.setupEventHandlers({
13661
13661
  onSelectVoice: function () {
13662
13662
  var _onSelectVoice = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee() {
13663
- var _this6$voiceInterface4, _this6$voiceInterface5, _this6$voiceInterface6, _this6$voiceInterface8, _this6$config$behavio, idleState, isDomainError, statusTitle, _this6$voiceInterface7, titleText, domainErrorTitle, _widgetMode, _this6$voiceInterface0, _this6$config$behavio2, _this6$voiceInterface9, _widgetMode2, _t;
13663
+ var _this6$voiceInterface4, _this6$voiceInterface5, _this6$voiceInterface6, _this6$voiceInterface7, _this6$shadowRoot, floatingButton, _this6$voiceInterface8, _this6$voiceInterface0, _this6$config$behavio, idleState, isDomainError, statusTitle, _this6$voiceInterface9, titleText, domainErrorTitle, _widgetMode, _this6$voiceInterface10, _this6$voiceInterface11, _this6$config$behavio2, _this6$voiceInterface1, _this6$voiceInterface12, _this6$shadowRoot2, existingBar, _floatingButton, _widgetMode2, _t;
13664
13664
  return _regenerator().w(function (_context) {
13665
13665
  while (1) switch (_context.p = _context.n) {
13666
13666
  case 0:
@@ -13694,30 +13694,52 @@ var TTPChatWidget = /*#__PURE__*/function () {
13694
13694
  _this6.voiceInterface.showConnectingState();
13695
13695
  }
13696
13696
 
13697
- // Start the call
13697
+ // CRITICAL: On mobile, show minimized bar immediately BEFORE blocking operations
13698
+ // This ensures UI is visible while permission/connection happens
13699
+ if (!((_this6$voiceInterface5 = _this6.voiceInterface) !== null && _this6$voiceInterface5 !== void 0 && _this6$voiceInterface5.isMobile && (_this6$voiceInterface6 = _this6.voiceInterface) !== null && _this6$voiceInterface6 !== void 0 && _this6$voiceInterface6.createMobileMinimizedBar)) {
13700
+ _context.n = 3;
13701
+ break;
13702
+ }
13703
+ // Hide floating button immediately so minimized bar is visible
13704
+ floatingButton = ((_this6$shadowRoot = _this6.shadowRoot) === null || _this6$shadowRoot === void 0 ? void 0 : _this6$shadowRoot.getElementById('text-chat-button')) || document.getElementById('text-chat-button');
13705
+ if (floatingButton) {
13706
+ floatingButton.style.display = 'none';
13707
+ }
13708
+ // Create minimized bar with connecting state
13709
+ _this6.voiceInterface.createMobileMinimizedBar();
13710
+ // Show connecting status in minimized bar
13711
+ _this6.voiceInterface.updateMobileStatus('connecting');
13712
+ // Ensure UI is painted before starting heavy operations
13698
13713
  _context.n = 3;
13699
- return _this6.startVoiceCall();
13714
+ return new Promise(function (resolve) {
13715
+ return requestAnimationFrame(function () {
13716
+ return requestAnimationFrame(resolve);
13717
+ });
13718
+ });
13700
13719
  case 3:
13701
- if (!((_this6$voiceInterface5 = _this6.voiceInterface) !== null && _this6$voiceInterface5 !== void 0 && _this6$voiceInterface5.isActive)) {
13702
- _context.n = 4;
13720
+ _context.n = 4;
13721
+ return _this6.startVoiceCall();
13722
+ case 4:
13723
+ if (!((_this6$voiceInterface7 = _this6.voiceInterface) !== null && _this6$voiceInterface7 !== void 0 && _this6$voiceInterface7.isActive)) {
13724
+ _context.n = 5;
13703
13725
  break;
13704
13726
  }
13705
13727
  console.log('✅ Voice call started successfully');
13706
- _context.n = 6;
13728
+ _context.n = 7;
13707
13729
  break;
13708
- case 4:
13730
+ case 5:
13709
13731
  // This shouldn't happen if startVoiceCall() throws properly, but just in case
13710
13732
  console.warn('⚠️ startVoiceCall() returned but call is not active');
13711
13733
 
13712
13734
  // CRITICAL: Check if we're showing a domain error - if so, stay on voice interface
13713
13735
  // We check by looking at the status title text content
13714
- idleState = (_this6$voiceInterface6 = _this6.voiceInterface) === null || _this6$voiceInterface6 === void 0 || (_this6$voiceInterface6 = _this6$voiceInterface6.shadowRoot) === null || _this6$voiceInterface6 === void 0 ? void 0 : _this6$voiceInterface6.getElementById('voiceIdleState');
13736
+ idleState = (_this6$voiceInterface8 = _this6.voiceInterface) === null || _this6$voiceInterface8 === void 0 || (_this6$voiceInterface8 = _this6$voiceInterface8.shadowRoot) === null || _this6$voiceInterface8 === void 0 ? void 0 : _this6$voiceInterface8.getElementById('voiceIdleState');
13715
13737
  isDomainError = false;
13716
13738
  if (idleState && idleState.style.display !== 'none') {
13717
13739
  statusTitle = idleState.querySelector('.voice-status-title');
13718
13740
  if (statusTitle) {
13719
13741
  titleText = statusTitle.textContent.trim();
13720
- domainErrorTitle = (_this6$voiceInterface7 = _this6.voiceInterface) === null || _this6$voiceInterface7 === void 0 ? void 0 : _this6$voiceInterface7.t('domainNotValidated');
13742
+ domainErrorTitle = (_this6$voiceInterface9 = _this6.voiceInterface) === null || _this6$voiceInterface9 === void 0 ? void 0 : _this6$voiceInterface9.t('domainNotValidated');
13721
13743
  console.log('🔍 Checking for domain error - titleText:', titleText, 'domainErrorTitle:', domainErrorTitle);
13722
13744
  if (titleText === domainErrorTitle || titleText.includes('Domain not') || titleText.includes('not whitelisted')) {
13723
13745
  isDomainError = true;
@@ -13726,7 +13748,7 @@ var TTPChatWidget = /*#__PURE__*/function () {
13726
13748
  }
13727
13749
  }
13728
13750
  if (!isDomainError) {
13729
- _context.n = 5;
13751
+ _context.n = 6;
13730
13752
  break;
13731
13753
  }
13732
13754
  // Set currentView to voice so we don't return to landing
@@ -13736,42 +13758,55 @@ var TTPChatWidget = /*#__PURE__*/function () {
13736
13758
  // Don't reset connecting state or return to landing - stay on voice interface showing error
13737
13759
  console.log('✅ Staying on voice interface with domain error displayed');
13738
13760
  return _context.a(2);
13739
- case 5:
13761
+ case 6:
13740
13762
  // Not a domain error - reset connecting state and return to landing
13741
13763
  console.log('⚠️ Not a domain error - returning to landing screen');
13742
- if ((_this6$voiceInterface8 = _this6.voiceInterface) !== null && _this6$voiceInterface8 !== void 0 && _this6$voiceInterface8.resetConnectingState) {
13764
+ if ((_this6$voiceInterface0 = _this6.voiceInterface) !== null && _this6$voiceInterface0 !== void 0 && _this6$voiceInterface0.resetConnectingState) {
13743
13765
  _this6.voiceInterface.resetConnectingState();
13744
13766
  }
13745
13767
  _widgetMode = ((_this6$config$behavio = _this6.config.behavior) === null || _this6$config$behavio === void 0 ? void 0 : _this6$config$behavio.mode) || 'unified';
13746
13768
  if (_widgetMode === 'unified') {
13747
13769
  _this6.showLanding();
13748
13770
  }
13749
- case 6:
13750
- _context.n = 9;
13751
- break;
13752
13771
  case 7:
13753
- _context.p = 7;
13772
+ _context.n = 10;
13773
+ break;
13774
+ case 8:
13775
+ _context.p = 8;
13754
13776
  _t = _context.v;
13755
13777
  if (!(_t.isServerRejection || _t.name === 'ServerRejected')) {
13756
- _context.n = 8;
13778
+ _context.n = 9;
13757
13779
  break;
13758
13780
  }
13759
13781
  // Server rejection is expected - the error handler will show domain error message
13760
13782
  // Reset connecting state but don't log as error
13761
- if ((_this6$voiceInterface9 = _this6.voiceInterface) !== null && _this6$voiceInterface9 !== void 0 && _this6$voiceInterface9.resetConnectingState) {
13783
+ if ((_this6$voiceInterface1 = _this6.voiceInterface) !== null && _this6$voiceInterface1 !== void 0 && _this6$voiceInterface1.resetConnectingState) {
13762
13784
  _this6.voiceInterface.resetConnectingState();
13763
13785
  }
13764
13786
  // Don't show toast for server rejection - domain error handler will show message
13765
13787
  return _context.a(2);
13766
- case 8:
13788
+ case 9:
13767
13789
  // Log other errors (permission denied, etc.)
13768
13790
  console.error('❌ Failed to start voice call:', _t);
13769
13791
 
13770
13792
  // Reset connecting state on error
13771
- if ((_this6$voiceInterface0 = _this6.voiceInterface) !== null && _this6$voiceInterface0 !== void 0 && _this6$voiceInterface0.resetConnectingState) {
13793
+ if ((_this6$voiceInterface10 = _this6.voiceInterface) !== null && _this6$voiceInterface10 !== void 0 && _this6$voiceInterface10.resetConnectingState) {
13772
13794
  _this6.voiceInterface.resetConnectingState();
13773
13795
  }
13774
13796
 
13797
+ // Clean up mobile bar if it was created early
13798
+ if ((_this6$voiceInterface11 = _this6.voiceInterface) !== null && _this6$voiceInterface11 !== void 0 && _this6$voiceInterface11.isMobile) {
13799
+ existingBar = document.getElementById('mobile-voice-call-bar-container');
13800
+ if (existingBar && (_this6$voiceInterface12 = _this6.voiceInterface) !== null && _this6$voiceInterface12 !== void 0 && _this6$voiceInterface12.removeMobileMinimizedBar) {
13801
+ _this6.voiceInterface.removeMobileMinimizedBar();
13802
+ }
13803
+ // Restore floating button if it was hidden
13804
+ _floatingButton = ((_this6$shadowRoot2 = _this6.shadowRoot) === null || _this6$shadowRoot2 === void 0 ? void 0 : _this6$shadowRoot2.getElementById('text-chat-button')) || document.getElementById('text-chat-button');
13805
+ if (_floatingButton) {
13806
+ _floatingButton.style.display = '';
13807
+ }
13808
+ }
13809
+
13775
13810
  // Show user-friendly error message (unless it's a user cancellation)
13776
13811
  if (_t.message !== 'Call cancelled by user') {
13777
13812
  _this6.showErrorToast(_t.message || 'Failed to start voice call. Please try again.', 'error');
@@ -13782,15 +13817,15 @@ var TTPChatWidget = /*#__PURE__*/function () {
13782
13817
  if (_widgetMode2 === 'unified') {
13783
13818
  _this6.showLanding();
13784
13819
  }
13785
- case 9:
13786
- _context.p = 9;
13820
+ case 10:
13821
+ _context.p = 10;
13787
13822
  // ✅ Always reset flag, even if showVoice() or startVoiceCall() throws
13788
13823
  _this6.isStartingCall = false;
13789
- return _context.f(9);
13790
- case 10:
13824
+ return _context.f(10);
13825
+ case 11:
13791
13826
  return _context.a(2);
13792
13827
  }
13793
- }, _callee, null, [[2, 7, 9, 10]]);
13828
+ }, _callee, null, [[2, 8, 10, 11]]);
13794
13829
  }));
13795
13830
  function onSelectVoice() {
13796
13831
  return _onSelectVoice.apply(this, arguments);
@@ -16132,7 +16167,7 @@ var VoiceInterface = /*#__PURE__*/function () {
16132
16167
  value: (function () {
16133
16168
  var _proceedWithVoiceCall = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee5() {
16134
16169
  var _this3 = this;
16135
- var isResumeCall, panel, header, toggleText, _voiceInterface, originalSection, compactSection, idleState, _panel, fallbackPanel, sampleRate, mediaStream, floatingButton, fallbackButton, _idleState, _panel2, _fallbackPanel, deviceInfo, _idleState2, activeState, voiceInterface, signedUrl, connected, serverRejected, originalOnError, originalOnDisconnected, attempts, _this$sdk$voiceSDK, error, _idleState3, _panel3, _fallbackPanel2, _floatingButton, _fallbackButton, updateTimer, _idleState4, _panel4, _fallbackPanel3, _floatingButton2, _fallbackButton2, _updateTimer, _deviceInfo, _t2, _t3, _t4, _t5, _t6, _t7;
16170
+ var isResumeCall, panel, header, toggleText, _voiceInterface, originalSection, compactSection, idleState, _panel, fallbackPanel, sampleRate, mediaStream, floatingButton, fallbackButton, existingBar, _floatingButton, _fallbackButton, _idleState, _panel2, _fallbackPanel, deviceInfo, _idleState2, activeState, voiceInterface, signedUrl, connected, serverRejected, originalOnError, originalOnDisconnected, attempts, _this$sdk$voiceSDK, error, _existingBar, _existingBar2, _idleState3, _panel3, _fallbackPanel2, _floatingButton2, _fallbackButton2, updateTimer, _existingBar3, _existingBar4, _idleState4, _panel4, _fallbackPanel3, _floatingButton3, _fallbackButton3, _updateTimer, _deviceInfo, _t2, _t3, _t4, _t5, _t6, _t7;
16136
16171
  return _regenerator().w(function (_context5) {
16137
16172
  while (1) switch (_context5.p = _context5.n) {
16138
16173
  case 0:
@@ -16247,6 +16282,23 @@ var VoiceInterface = /*#__PURE__*/function () {
16247
16282
  console.error('❌ Mobile: Permission denied before connecting:', _t2);
16248
16283
  // Permission was denied - reset connecting state and restore UI
16249
16284
  this.resetConnectingState();
16285
+
16286
+ // Remove mobile bar if it was created early
16287
+ existingBar = document.getElementById('mobile-voice-call-bar-container');
16288
+ if (existingBar) {
16289
+ this.removeMobileMinimizedBar();
16290
+ }
16291
+
16292
+ // Restore floating button visibility
16293
+ _floatingButton = this.shadowRoot.getElementById('text-chat-button');
16294
+ if (_floatingButton) {
16295
+ _floatingButton.style.display = '';
16296
+ } else {
16297
+ _fallbackButton = document.getElementById('text-chat-button');
16298
+ if (_fallbackButton) {
16299
+ _fallbackButton.style.display = '';
16300
+ }
16301
+ }
16250
16302
  _idleState = this.shadowRoot.getElementById('voiceIdleState');
16251
16303
  if (_idleState) {
16252
16304
  _idleState.style.display = 'flex';
@@ -16380,6 +16432,11 @@ var VoiceInterface = /*#__PURE__*/function () {
16380
16432
  _context5.n = 15;
16381
16433
  break;
16382
16434
  }
16435
+ // ✅ Connection is ready (hello_ack received) - update status BEFORE audio can start
16436
+ // This ensures status is "listening" when agent starts speaking
16437
+ if (this.isMobile) {
16438
+ this.updateMobileStatus('listening');
16439
+ }
16383
16440
  return _context5.a(3, 17);
16384
16441
  case 15:
16385
16442
  _context5.n = 16;
@@ -16419,6 +16476,17 @@ var VoiceInterface = /*#__PURE__*/function () {
16419
16476
  break;
16420
16477
  }
16421
16478
  console.log('✅ WebSocket ready, starting listening...');
16479
+
16480
+ // ✅ CRITICAL: Update status to "listening" IMMEDIATELY after hello_ack
16481
+ // This ensures status is correct BEFORE agent starts sending audio
16482
+ // Agent can start sending audio immediately after hello_ack, so we need
16483
+ // to update status now, not after startListening() completes
16484
+ if (this.isMobile) {
16485
+ // Status was already updated to "listening" in the hello_ack wait loop above
16486
+ // But ensure it's set here too in case we didn't go through that path
16487
+ this.updateMobileStatus('listening');
16488
+ }
16489
+
16422
16490
  // CRITICAL: startListening() will request microphone permission
16423
16491
  // This must be called directly from user interaction (button click)
16424
16492
  // to work on mobile browsers
@@ -16452,13 +16520,17 @@ var VoiceInterface = /*#__PURE__*/function () {
16452
16520
  console.log('🎤 Started listening - permission granted');
16453
16521
  this.isActive = true;
16454
16522
 
16455
- // ONLY NOW that permission is granted and call is active, create mobile bar (mobile only)
16523
+ // Update mobile bar if it already exists (created early), otherwise create it
16456
16524
  if (this.isMobile) {
16457
- console.log('📱 Mobile device - permission granted, creating minimized bar');
16458
-
16459
- // Panel and floating button are already hidden from permission request step
16460
- // Just create the mobile minimized bar
16461
- this.createMobileMinimizedBar();
16525
+ _existingBar = document.getElementById('mobile-voice-call-bar-container');
16526
+ if (!_existingBar) {
16527
+ console.log('📱 Mobile device - permission granted, creating minimized bar');
16528
+ // Panel and floating button are already hidden from permission request step
16529
+ // Just create the mobile minimized bar
16530
+ this.createMobileMinimizedBar();
16531
+ } else {
16532
+ console.log('📱 Mobile device - bar already exists, updating status');
16533
+ }
16462
16534
 
16463
16535
  // Initialize messages with welcome message
16464
16536
  if (this.messages.length === 0) {
@@ -16469,7 +16541,7 @@ var VoiceInterface = /*#__PURE__*/function () {
16469
16541
  }
16470
16542
  // Start waveform animation
16471
16543
  this.startWaveformAnimation();
16472
- // Update status
16544
+ // Update status to listening (was connecting before)
16473
16545
  this.updateMobileStatus('listening');
16474
16546
  } else {
16475
16547
  // Desktop: initialize UI
@@ -16498,6 +16570,11 @@ var VoiceInterface = /*#__PURE__*/function () {
16498
16570
  // Permission was denied or error occurred - reset connecting state and restore UI
16499
16571
  this.resetConnectingState();
16500
16572
  if (this.isMobile) {
16573
+ // Remove mobile bar if it was created early
16574
+ _existingBar2 = document.getElementById('mobile-voice-call-bar-container');
16575
+ if (_existingBar2) {
16576
+ this.removeMobileMinimizedBar();
16577
+ }
16501
16578
  // Restore idle state so user can try again
16502
16579
  _idleState3 = this.shadowRoot.getElementById('voiceIdleState');
16503
16580
  if (_idleState3) {
@@ -16514,13 +16591,13 @@ var VoiceInterface = /*#__PURE__*/function () {
16514
16591
  }
16515
16592
  }
16516
16593
  // Ensure floating button is visible
16517
- _floatingButton = this.shadowRoot.getElementById('text-chat-button');
16518
- if (_floatingButton) {
16519
- _floatingButton.style.display = '';
16594
+ _floatingButton2 = this.shadowRoot.getElementById('text-chat-button');
16595
+ if (_floatingButton2) {
16596
+ _floatingButton2.style.display = '';
16520
16597
  } else {
16521
- _fallbackButton = document.getElementById('text-chat-button');
16522
- if (_fallbackButton) {
16523
- _fallbackButton.style.display = '';
16598
+ _fallbackButton2 = document.getElementById('text-chat-button');
16599
+ if (_fallbackButton2) {
16600
+ _fallbackButton2.style.display = '';
16524
16601
  }
16525
16602
  }
16526
16603
  }
@@ -16575,13 +16652,17 @@ var VoiceInterface = /*#__PURE__*/function () {
16575
16652
  console.log('🎤 Started listening - permission granted');
16576
16653
  this.isActive = true;
16577
16654
 
16578
- // ONLY NOW that permission is granted and call is active, create mobile bar (mobile only)
16655
+ // Update mobile bar if it already exists (created early), otherwise create it
16579
16656
  if (this.isMobile) {
16580
- console.log('📱 Mobile device - permission granted, creating minimized bar');
16581
-
16582
- // Panel and floating button are already hidden from permission request step
16583
- // Just create the mobile minimized bar
16584
- this.createMobileMinimizedBar();
16657
+ _existingBar3 = document.getElementById('mobile-voice-call-bar-container');
16658
+ if (!_existingBar3) {
16659
+ console.log('📱 Mobile device - permission granted, creating minimized bar');
16660
+ // Panel and floating button are already hidden from permission request step
16661
+ // Just create the mobile minimized bar
16662
+ this.createMobileMinimizedBar();
16663
+ } else {
16664
+ console.log('📱 Mobile device - bar already exists, updating status');
16665
+ }
16585
16666
 
16586
16667
  // Initialize messages with welcome message
16587
16668
  if (this.messages.length === 0) {
@@ -16592,7 +16673,7 @@ var VoiceInterface = /*#__PURE__*/function () {
16592
16673
  }
16593
16674
  // Start waveform animation
16594
16675
  this.startWaveformAnimation();
16595
- // Update status
16676
+ // Update status to listening (was connecting before)
16596
16677
  this.updateMobileStatus('listening');
16597
16678
  } else {
16598
16679
  // Desktop: initialize UI
@@ -16621,6 +16702,11 @@ var VoiceInterface = /*#__PURE__*/function () {
16621
16702
  // Permission was denied or error occurred - reset connecting state and restore UI
16622
16703
  this.resetConnectingState();
16623
16704
  if (this.isMobile) {
16705
+ // Remove mobile bar if it was created early
16706
+ _existingBar4 = document.getElementById('mobile-voice-call-bar-container');
16707
+ if (_existingBar4) {
16708
+ this.removeMobileMinimizedBar();
16709
+ }
16624
16710
  // Restore idle state so user can try again
16625
16711
  _idleState4 = this.shadowRoot.getElementById('voiceIdleState');
16626
16712
  if (_idleState4) {
@@ -16637,13 +16723,13 @@ var VoiceInterface = /*#__PURE__*/function () {
16637
16723
  }
16638
16724
  }
16639
16725
  // Ensure floating button is visible
16640
- _floatingButton2 = this.shadowRoot.getElementById('text-chat-button');
16641
- if (_floatingButton2) {
16642
- _floatingButton2.style.display = '';
16726
+ _floatingButton3 = this.shadowRoot.getElementById('text-chat-button');
16727
+ if (_floatingButton3) {
16728
+ _floatingButton3.style.display = '';
16643
16729
  } else {
16644
- _fallbackButton2 = document.getElementById('text-chat-button');
16645
- if (_fallbackButton2) {
16646
- _fallbackButton2.style.display = '';
16730
+ _fallbackButton3 = document.getElementById('text-chat-button');
16731
+ if (_fallbackButton3) {
16732
+ _fallbackButton3.style.display = '';
16647
16733
  }
16648
16734
  }
16649
16735
  }
@@ -18595,7 +18681,10 @@ var Mobile = /*#__PURE__*/function () {
18595
18681
  dotColor = this.config.statusDotColor || '#10b981';
18596
18682
  } else {
18597
18683
  // Use dynamic status based on state
18598
- if (status === 'listening' || status === 'recording') {
18684
+ if (status === 'connecting') {
18685
+ displayText = this.t('connecting') || 'Connecting...';
18686
+ dotColor = '#3b82f6'; // Blue for connecting
18687
+ } else if (status === 'listening' || status === 'recording') {
18599
18688
  displayText = this.t('listening') || 'Listening...';
18600
18689
  dotColor = '#22c55e';
18601
18690
  } else if (status === 'speaking') {
@@ -23062,9 +23151,16 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
23062
23151
  var config = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
23063
23152
  _classCallCheck(this, AudioPlayer);
23064
23153
  _this = _callSuper(this, AudioPlayer);
23154
+
23155
+ // Version identification log
23156
+ console.log('%c🔊 AUDIO PLAYER - SILENT BUFFER PRIMING FIX ENABLED', 'background: #10b981; color: white; font-size: 14px; font-weight: bold; padding: 4px 8px; border-radius: 4px;');
23157
+ console.log('%cAndroid Greeting Cut-Off Fix: Silent buffer priming (100ms) + dynamic startup buffer', 'background: #d1fae5; color: #065f46; font-size: 12px; padding: 2px 6px; border-radius: 3px;');
23158
+ console.log('%cBuild: 2026-01-30-SILENT-BUFFER-PRIMING | Look for [scheduleWAV] and priming logs', 'background: #e0e7ff; color: #1e40af; font-size: 11px; padding: 2px 6px; border-radius: 3px;');
23159
+ console.log('');
23065
23160
  _this.config = config;
23066
23161
  _this.audioContext = null;
23067
23162
  _this.gainNode = null; // GainNode for volume/mute control
23163
+ _this._audioContextPrimed = false; // Track if AudioContext has been primed with silent buffer
23068
23164
 
23069
23165
  // Track temporary listener in waitForAudioContextReady() for cleanup
23070
23166
  _this._waitForReadyStateHandler = null;
@@ -23522,13 +23618,19 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
23522
23618
  currentTime = _this4.audioContext.currentTime; // Ensure we don't schedule in the past, but NEVER decrease nextStartTime
23523
23619
  // This prevents overlapping audio by maintaining sequential ordering
23524
23620
  if (_this4.scheduledBuffers === 0) {
23525
- // First chunk: Add startup buffer on mobile to account for AudioContext initialization latency
23526
- // On mobile, when AudioContext transitions from suspended to running, there's startup delay
23527
- // Scheduling at time 0 causes the beginning to be skipped due to this latency
23621
+ // First chunk: Use dynamic startup buffer based on audio priming status
23528
23622
  isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
23529
- startupBuffer = isMobile ? 0.1 : 0; // 100ms buffer on mobile
23623
+ startupBuffer = 0.03; // 30ms default for responses
23624
+ if (isMobile) {
23625
+ if (_this4._audioContextPrimed) {
23626
+ startupBuffer = 0; // Primed - no buffer needed
23627
+ } else {
23628
+ startupBuffer = 2.0; // Not primed yet - 2000ms safety fallback
23629
+ console.warn('⚠️ AudioPlayer: Audio not primed yet, using 2000ms fallback buffer');
23630
+ }
23631
+ }
23530
23632
  _this4.nextStartTime = currentTime + startupBuffer;
23531
- console.log("\uD83C\uDFB5 [scheduleWAV] FIRST FRAME - state: ".concat(_this4.audioContext.state, ", currentTime: ").concat(currentTime, ", nextStartTime: ").concat(_this4.nextStartTime), isMobile ? '(mobile)' : '(desktop)');
23633
+ console.log("\uD83C\uDFB5 [scheduleWAV] FIRST FRAME - primed: ".concat(_this4._audioContextPrimed, ", buffer: ").concat(startupBuffer * 1000, "ms"));
23532
23634
  } else {
23533
23635
  // Subsequent chunks: ensure nextStartTime is not in the past
23534
23636
  // If nextStartTime is already in the future, use it (seamless playback)
@@ -24478,6 +24580,9 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
24478
24580
  this.gainNode.gain.value = 1.0; // Default to full volume
24479
24581
  this.gainNode.connect(this.audioContext.destination);
24480
24582
  console.log('✅ AudioPlayer: GainNode created for volume control');
24583
+
24584
+ // Prime audio hardware for Android
24585
+ this._primeAudioContext();
24481
24586
  } catch (error) {
24482
24587
  // Fallback to default if browser doesn't support custom sample rate
24483
24588
  console.error("\u274C Failed to create AudioContext:", error);
@@ -24492,13 +24597,65 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
24492
24597
  this.gainNode.gain.value = 1.0; // Default to full volume
24493
24598
  this.gainNode.connect(this.audioContext.destination);
24494
24599
  console.log('✅ AudioPlayer: GainNode created for volume control (fallback)');
24600
+
24601
+ // Prime audio hardware for Android
24602
+ this._primeAudioContext();
24495
24603
  }
24496
24604
  }
24497
24605
 
24606
+ /**
24607
+ * Prime AudioContext with silent buffer to initialize Android hardware
24608
+ * This prevents the first audio frame from being cut off on Android devices
24609
+ */
24610
+ }, {
24611
+ key: "_primeAudioContext",
24612
+ value: (function () {
24613
+ var _primeAudioContext2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee5() {
24614
+ var sampleRate, silentBuffer, source, _t3;
24615
+ return _regenerator().w(function (_context6) {
24616
+ while (1) switch (_context6.p = _context6.n) {
24617
+ case 0:
24618
+ _context6.p = 0;
24619
+ if (!(!this.audioContext || this._audioContextPrimed)) {
24620
+ _context6.n = 1;
24621
+ break;
24622
+ }
24623
+ return _context6.a(2);
24624
+ case 1:
24625
+ sampleRate = this.audioContext.sampleRate;
24626
+ silentBuffer = this.audioContext.createBuffer(1, sampleRate * 0.1, sampleRate);
24627
+ source = this.audioContext.createBufferSource();
24628
+ source.buffer = silentBuffer;
24629
+ source.connect(this.audioContext.destination);
24630
+ source.start();
24631
+ _context6.n = 2;
24632
+ return new Promise(function (resolve) {
24633
+ return setTimeout(resolve, 100);
24634
+ });
24635
+ case 2:
24636
+ this._audioContextPrimed = true;
24637
+ console.log('✅ AudioPlayer: Audio hardware primed with silent buffer');
24638
+ _context6.n = 4;
24639
+ break;
24640
+ case 3:
24641
+ _context6.p = 3;
24642
+ _t3 = _context6.v;
24643
+ console.error('❌ AudioPlayer: Audio priming failed:', _t3);
24644
+ case 4:
24645
+ return _context6.a(2);
24646
+ }
24647
+ }, _callee5, this, [[0, 3]]);
24648
+ }));
24649
+ function _primeAudioContext() {
24650
+ return _primeAudioContext2.apply(this, arguments);
24651
+ }
24652
+ return _primeAudioContext;
24653
+ }()
24498
24654
  /**
24499
24655
  * Clean up AudioContext and its event listeners
24500
24656
  * Ensures listeners are removed before context is closed/nullified to prevent race conditions
24501
24657
  */
24658
+ )
24502
24659
  }, {
24503
24660
  key: "_cleanupAudioContext",
24504
24661
  value: function _cleanupAudioContext() {
@@ -24533,6 +24690,7 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
24533
24690
 
24534
24691
  // Set to null after cleanup
24535
24692
  this.audioContext = null;
24693
+ this._audioContextPrimed = false;
24536
24694
  }
24537
24695
 
24538
24696
  /**
@@ -24601,35 +24759,35 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
24601
24759
  }, {
24602
24760
  key: "processQueue",
24603
24761
  value: (function () {
24604
- var _processQueue = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee5() {
24762
+ var _processQueue = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee6() {
24605
24763
  var _this8 = this;
24606
- var audioBlob, wasFirstPlay, audioContext, arrayBuffer, audioBuffer, shouldEmitStart, source, _t3;
24607
- return _regenerator().w(function (_context6) {
24608
- while (1) switch (_context6.p = _context6.n) {
24764
+ var audioBlob, wasFirstPlay, audioContext, arrayBuffer, audioBuffer, shouldEmitStart, source, _t4;
24765
+ return _regenerator().w(function (_context7) {
24766
+ while (1) switch (_context7.p = _context7.n) {
24609
24767
  case 0:
24610
24768
  if (!this._isStopped) {
24611
- _context6.n = 1;
24769
+ _context7.n = 1;
24612
24770
  break;
24613
24771
  }
24614
24772
  console.log('🛑 AudioPlayer: Not processing queue - playback was stopped (barge-in)');
24615
- return _context6.a(2);
24773
+ return _context7.a(2);
24616
24774
  case 1:
24617
24775
  if (!(this.isProcessingQueue || this.audioQueue.length === 0)) {
24618
- _context6.n = 2;
24776
+ _context7.n = 2;
24619
24777
  break;
24620
24778
  }
24621
- return _context6.a(2);
24779
+ return _context7.a(2);
24622
24780
  case 2:
24623
24781
  this.isProcessingQueue = true;
24624
24782
  audioBlob = this.audioQueue.shift();
24625
24783
  if (audioBlob) {
24626
- _context6.n = 3;
24784
+ _context7.n = 3;
24627
24785
  break;
24628
24786
  }
24629
24787
  this.isProcessingQueue = false;
24630
- return _context6.a(2);
24788
+ return _context7.a(2);
24631
24789
  case 3:
24632
- _context6.p = 3;
24790
+ _context7.p = 3;
24633
24791
  wasFirstPlay = !this.isPlaying && this.currentSource === null; // Initialize audio context if needed
24634
24792
  if (!this.audioContext) {
24635
24793
  this.initializeAudioContext();
@@ -24638,18 +24796,18 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
24638
24796
  // ✅ NEW: Wait for AudioContext to be in 'running' state before proceeding
24639
24797
  // This is critical for mobile devices where AudioContext initialization takes time
24640
24798
  // Replaces fixed timeouts with event-driven waiting
24641
- _context6.n = 4;
24799
+ _context7.n = 4;
24642
24800
  return this.waitForAudioContextReady();
24643
24801
  case 4:
24644
24802
  audioContext = this.audioContext; // Decode audio
24645
- _context6.n = 5;
24803
+ _context7.n = 5;
24646
24804
  return audioBlob.arrayBuffer();
24647
24805
  case 5:
24648
- arrayBuffer = _context6.v;
24649
- _context6.n = 6;
24806
+ arrayBuffer = _context7.v;
24807
+ _context7.n = 6;
24650
24808
  return audioContext.decodeAudioData(arrayBuffer);
24651
24809
  case 6:
24652
- audioBuffer = _context6.v;
24810
+ audioBuffer = _context7.v;
24653
24811
  shouldEmitStart = wasFirstPlay && !this.isPlaying && this.currentSource === null; // Create source
24654
24812
  source = audioContext.createBufferSource();
24655
24813
  source.buffer = audioBuffer;
@@ -24692,14 +24850,14 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
24692
24850
  // Start playback - AudioContext should now be fully ready
24693
24851
 
24694
24852
  source.start();
24695
- _context6.n = 8;
24853
+ _context7.n = 8;
24696
24854
  break;
24697
24855
  case 7:
24698
- _context6.p = 7;
24699
- _t3 = _context6.v;
24700
- console.error('❌ AudioPlayer v2: Error processing queue:', _t3);
24856
+ _context7.p = 7;
24857
+ _t4 = _context7.v;
24858
+ console.error('❌ AudioPlayer v2: Error processing queue:', _t4);
24701
24859
  this.currentSource = null;
24702
- this.emit('playbackError', _t3);
24860
+ this.emit('playbackError', _t4);
24703
24861
 
24704
24862
  // Try next chunk
24705
24863
 
@@ -24714,9 +24872,9 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
24714
24872
  this.emit('playbackStopped');
24715
24873
  }
24716
24874
  case 8:
24717
- return _context6.a(2);
24875
+ return _context7.a(2);
24718
24876
  }
24719
- }, _callee5, this, [[3, 7]]);
24877
+ }, _callee6, this, [[3, 7]]);
24720
24878
  }));
24721
24879
  function processQueue() {
24722
24880
  return _processQueue.apply(this, arguments);
@@ -24990,39 +25148,39 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
24990
25148
  }, {
24991
25149
  key: "resumeAudioContext",
24992
25150
  value: (function () {
24993
- var _resumeAudioContext = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee6() {
24994
- var _t4;
24995
- return _regenerator().w(function (_context7) {
24996
- while (1) switch (_context7.p = _context7.n) {
25151
+ var _resumeAudioContext = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee7() {
25152
+ var _t5;
25153
+ return _regenerator().w(function (_context8) {
25154
+ while (1) switch (_context8.p = _context8.n) {
24997
25155
  case 0:
24998
25156
  if (!(!this.audioContext || this.audioContext.state === 'closed')) {
24999
- _context7.n = 2;
25157
+ _context8.n = 2;
25000
25158
  break;
25001
25159
  }
25002
- _context7.n = 1;
25160
+ _context8.n = 1;
25003
25161
  return this.initializeAudioContext();
25004
25162
  case 1:
25005
- return _context7.a(2);
25163
+ return _context8.a(2);
25006
25164
  case 2:
25007
25165
  if (!(this.audioContext.state === 'suspended')) {
25008
- _context7.n = 6;
25166
+ _context8.n = 6;
25009
25167
  break;
25010
25168
  }
25011
- _context7.p = 3;
25012
- _context7.n = 4;
25169
+ _context8.p = 3;
25170
+ _context8.n = 4;
25013
25171
  return this.audioContext.resume();
25014
25172
  case 4:
25015
25173
  console.log('✅ AudioPlayer v2: AudioContext resumed after mic permission');
25016
- _context7.n = 6;
25174
+ _context8.n = 6;
25017
25175
  break;
25018
25176
  case 5:
25019
- _context7.p = 5;
25020
- _t4 = _context7.v;
25021
- console.warn('⚠️ AudioPlayer v2: Failed to resume AudioContext:', _t4);
25177
+ _context8.p = 5;
25178
+ _t5 = _context8.v;
25179
+ console.warn('⚠️ AudioPlayer v2: Failed to resume AudioContext:', _t5);
25022
25180
  case 6:
25023
- return _context7.a(2);
25181
+ return _context8.a(2);
25024
25182
  }
25025
- }, _callee6, this, [[3, 5]]);
25183
+ }, _callee7, this, [[3, 5]]);
25026
25184
  }));
25027
25185
  function resumeAudioContext() {
25028
25186
  return _resumeAudioContext.apply(this, arguments);
@@ -25261,6 +25419,7 @@ var VoiceSDK_v2 = /*#__PURE__*/function (_EventEmitter) {
25261
25419
  _this.transcriptQueue = []; // Queue of transcripts for history
25262
25420
  _this.currentPlayingSentenceId = null; // Sentence ID currently being played
25263
25421
  _this.audioFrameCount = 0; // Track number of audio frames received
25422
+ _this.lastAudioStartTime = 0; // Timestamp of last audio_start message (to prevent premature stop_playing)
25264
25423
 
25265
25424
  // Screen wake lock - keeps screen on during voice calls (mobile)
25266
25425
  _this.wakeLock = null;
@@ -26334,6 +26493,16 @@ var VoiceSDK_v2 = /*#__PURE__*/function (_EventEmitter) {
26334
26493
  this.stopAudioPlayback();
26335
26494
  break;
26336
26495
  case 'stop_playing':
26496
+ // CRITICAL: Ignore stop_playing messages that arrive too soon after audio_start
26497
+ // Backend sometimes sends stop_playing immediately after audio_start, which cuts sentences prematurely
26498
+ // Only honor stop_playing if it's been at least 200ms since the last audio_start
26499
+ var timeSinceAudioStart = Date.now() - this.lastAudioStartTime;
26500
+ var MIN_STOP_PLAYING_DELAY_MS = 200; // 200ms grace period after audio_start
26501
+
26502
+ if (timeSinceAudioStart < MIN_STOP_PLAYING_DELAY_MS) {
26503
+ console.warn("\u26A0\uFE0F VoiceSDK v2: Ignoring premature stop_playing (".concat(timeSinceAudioStart, "ms after audio_start, minimum ").concat(MIN_STOP_PLAYING_DELAY_MS, "ms required)"));
26504
+ break;
26505
+ }
26337
26506
  this.emit('stopPlaying', message);
26338
26507
  this.stopAudioPlayback();
26339
26508
  break;
@@ -26342,6 +26511,9 @@ var VoiceSDK_v2 = /*#__PURE__*/function (_EventEmitter) {
26342
26511
  // Store the text in AudioPlayer for synced display when audio actually starts playing
26343
26512
  console.log('📝 VoiceSDK v2: Received audio_start with text:', message.text);
26344
26513
 
26514
+ // CRITICAL: Record timestamp to prevent premature stop_playing messages from cutting sentences
26515
+ this.lastAudioStartTime = Date.now();
26516
+
26345
26517
  // NOTE: We do NOT stop current audio here - that only happens on user barge-in (stop_playing)
26346
26518
  // If audio is already playing, the new sentence will queue and wait for current one to finish
26347
26519
  // This allows sentences to play sequentially without interruption
@@ -26475,7 +26647,149 @@ var VoiceSDK_v2 = /*#__PURE__*/function (_EventEmitter) {
26475
26647
  this.emit('message', message);
26476
26648
  }
26477
26649
  } catch (error) {
26650
+ var _error$message, _error$message2;
26651
+ // Enhanced error logging to help debug JSON parsing issues
26652
+ var rawData = typeof event.data === 'string' ? event.data : String(event.data);
26653
+ var errorPosition = ((_error$message = error.message) === null || _error$message === void 0 || (_error$message = _error$message.match(/position (\d+)/)) === null || _error$message === void 0 ? void 0 : _error$message[1]) || 'unknown';
26654
+ var position = parseInt(errorPosition, 10);
26478
26655
  console.error('VoiceSDK v2: Error parsing message:', error);
26656
+ console.error('VoiceSDK v2: Raw message length:', rawData.length);
26657
+ console.error('VoiceSDK v2: Error at position:', position);
26658
+
26659
+ // Show context around the error position
26660
+ if (position !== 'unknown' && position > 0) {
26661
+ var start = Math.max(0, position - 50);
26662
+ var end = Math.min(rawData.length, position + 50);
26663
+ var context = rawData.substring(start, end);
26664
+ var relativePos = position - start;
26665
+ var marker = ' '.repeat(relativePos) + '^';
26666
+ console.error('VoiceSDK v2: Context around error:');
26667
+ console.error(context);
26668
+ console.error(marker);
26669
+
26670
+ // Show the problematic character(s)
26671
+ var problematicChar = rawData.charAt(position);
26672
+ var charCode = problematicChar.charCodeAt(0);
26673
+ console.error("VoiceSDK v2: Problematic character: \"".concat(problematicChar, "\" (charCode: ").concat(charCode, ")"));
26674
+
26675
+ // Check for common control characters
26676
+ if (charCode < 32 && charCode !== 9 && charCode !== 10 && charCode !== 13) {
26677
+ console.error('VoiceSDK v2: This is an unescaped control character. The backend should escape it in JSON strings.');
26678
+ }
26679
+ }
26680
+
26681
+ // Try to detect if this was a client_tool_call message
26682
+ var looksLikeToolCall = rawData.includes('client_tool_call') || rawData.includes('"t":"client_tool_call"');
26683
+ if (looksLikeToolCall) {
26684
+ console.error('⚠️ VoiceSDK v2: CRITICAL - Failed to parse client_tool_call message!');
26685
+ console.error('⚠️ VoiceSDK v2: This means a tool execution (like form filling) was lost due to JSON parsing error.');
26686
+ console.error('⚠️ VoiceSDK v2: The backend sent malformed JSON with unescaped control characters.');
26687
+
26688
+ // Try to extract tool name from raw string (basic regex attempt)
26689
+ var toolNameMatch = rawData.match(/"toolName"\s*:\s*"([^"]+)"/);
26690
+ if (toolNameMatch) {
26691
+ console.error("\u26A0\uFE0F VoiceSDK v2: Attempted tool: \"".concat(toolNameMatch[1], "\""));
26692
+ }
26693
+
26694
+ // Try to extract toolCallId
26695
+ var toolCallIdMatch = rawData.match(/"toolCallId"\s*:\s*"([^"]+)"/);
26696
+ if (toolCallIdMatch) {
26697
+ console.error("\u26A0\uFE0F VoiceSDK v2: Tool call ID: \"".concat(toolCallIdMatch[1], "\""));
26698
+ }
26699
+ }
26700
+
26701
+ // Attempt recovery: sanitize control characters and try parsing again
26702
+ if ((_error$message2 = error.message) !== null && _error$message2 !== void 0 && _error$message2.includes('Bad control character')) {
26703
+ console.warn('VoiceSDK v2: Attempting to recover by sanitizing control characters...');
26704
+ try {
26705
+ // Replace unescaped control characters (except \t, \n, \r which are sometimes valid)
26706
+ // This is a best-effort recovery - may not work for all cases
26707
+ var sanitized = rawData;
26708
+ // Replace control characters that aren't properly escaped
26709
+ // Look for control chars that aren't part of \n, \t, \r, \\ sequences
26710
+ sanitized = sanitized.replace(/([^\\])([\x00-\x08\x0B\x0C\x0E-\x1F])/g, function (match, prev, ctrl) {
26711
+ var code = ctrl.charCodeAt(0);
26712
+ // Map common control chars to escape sequences
26713
+ var escapes = {
26714
+ 0: "\\u0000",
26715
+ 1: "\\u0001",
26716
+ 2: "\\u0002",
26717
+ 3: "\\u0003",
26718
+ 4: "\\u0004",
26719
+ 5: "\\u0005",
26720
+ 6: "\\u0006",
26721
+ 7: "\\u0007",
26722
+ 8: '\\b',
26723
+ 11: "\\u000B",
26724
+ 12: '\\f',
26725
+ 14: "\\u000E",
26726
+ 15: "\\u000F",
26727
+ 16: "\\u0010",
26728
+ 17: "\\u0011",
26729
+ 18: "\\u0012",
26730
+ 19: "\\u0013",
26731
+ 20: "\\u0014",
26732
+ 21: "\\u0015",
26733
+ 22: "\\u0016",
26734
+ 23: "\\u0017",
26735
+ 24: "\\u0018",
26736
+ 25: "\\u0019",
26737
+ 26: "\\u001A",
26738
+ 27: "\\u001B",
26739
+ 28: "\\u001C",
26740
+ 29: "\\u001D",
26741
+ 30: "\\u001E",
26742
+ 31: "\\u001F"
26743
+ };
26744
+ return prev + (escapes[code] || "\\u".concat(code.toString(16).padStart(4, '0')));
26745
+ });
26746
+ var recoveredMessage = JSON.parse(sanitized);
26747
+ console.warn('✅ VoiceSDK v2: Successfully recovered message after sanitization!');
26748
+ console.warn('✅ VoiceSDK v2: Processing recovered message...');
26749
+
26750
+ // Process the recovered message
26751
+ if (recoveredMessage.t === 'client_tool_call' && this.clientToolsRegistry) {
26752
+ console.warn('✅ VoiceSDK v2: Recovered client_tool_call - executing tool');
26753
+ this.clientToolsRegistry.handleToolCall(recoveredMessage);
26754
+ } else {
26755
+ // Re-process through normal flow
26756
+ var _message = recoveredMessage;
26757
+ if (_message.t !== 'audio') {
26758
+ console.log('📥 VoiceSDK v2: Received message type:', _message.t, 'Full message:', JSON.stringify(_message).substring(0, 200));
26759
+ }
26760
+ if (!this.conversationId && _message.conversationId) {
26761
+ this.conversationId = _message.conversationId;
26762
+ console.log('🔍 VoiceSDK v2: Captured conversationId from message:', this.conversationId);
26763
+ this.emit('conversationIdChanged', this.conversationId);
26764
+ }
26765
+ if (_message.t !== 'audio') {
26766
+ console.log('📨 VoiceSDK v2: Received message type:', _message.t, _message);
26767
+ }
26768
+ // Re-enter switch statement logic (simplified - just handle client_tool_call)
26769
+ if (_message.t === 'client_tool_call') {
26770
+ this.clientToolsRegistry.handleToolCall(_message);
26771
+ } else {
26772
+ this.emit('message', _message);
26773
+ }
26774
+ }
26775
+ return; // Successfully recovered, exit error handler
26776
+ } catch (recoveryError) {
26777
+ console.error('❌ VoiceSDK v2: Recovery attempt failed:', recoveryError);
26778
+ }
26779
+ }
26780
+
26781
+ // Show first 500 chars of raw message for debugging
26782
+ console.error('VoiceSDK v2: First 500 chars of raw message:', rawData.substring(0, 500));
26783
+
26784
+ // Emit error event so listeners can handle it
26785
+ this.emit('error', {
26786
+ type: 'json_parse_error',
26787
+ error: error,
26788
+ rawMessage: rawData.substring(0, 1000),
26789
+ // Include first 1000 chars for debugging
26790
+ position: position,
26791
+ wasToolCall: looksLikeToolCall
26792
+ });
26479
26793
  }
26480
26794
  }
26481
26795