@exotel-npm-dev/exotel-ai-assist 1.2.8 → 1.3.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.
@@ -343,7 +343,7 @@ class BroadcastChannelTransport {
343
343
  this.pendingUrl = null;
344
344
  this.pendingAuthToken = null;
345
345
  this.lockReleaser = null;
346
- this.ackReceived = false;
346
+ this.ackPayload = null;
347
347
  this._destroyed = false;
348
348
  const channelName = `exotel-ai-assist:${sessionHash}`;
349
349
  const lockName = `exotel-ai-assist-leader:${sessionHash}`;
@@ -352,15 +352,14 @@ class BroadcastChannelTransport {
352
352
  var _a;
353
353
  const data = ev.data;
354
354
  if (this.isLeader) {
355
- if (data.type === "JOIN" && this.socketConnected) {
355
+ if (data.type === "JOIN" && this.socketConnected && this.ackPayload) {
356
356
  this.channel.postMessage({
357
357
  type: "CONNECTED"
358
358
  });
359
- if (this.ackReceived) {
360
- this.channel.postMessage({
361
- type: "ACKNOWLEDGED"
362
- });
363
- }
359
+ this.channel.postMessage({
360
+ type: "MESSAGE",
361
+ payload: this.ackPayload
362
+ });
364
363
  }
365
364
  return;
366
365
  }
@@ -415,7 +414,7 @@ class BroadcastChannelTransport {
415
414
  var _a;
416
415
  if (this._destroyed) return;
417
416
  this.socketConnected = true;
418
- this.ackReceived = false;
417
+ this.ackPayload = null;
419
418
  this.socket.send(JSON.stringify({
420
419
  type: "auth",
421
420
  access_token: authToken
@@ -450,7 +449,7 @@ class BroadcastChannelTransport {
450
449
  var _a;
451
450
  if (this._destroyed) return;
452
451
  this.socketConnected = false;
453
- this.ackReceived = false;
452
+ this.ackPayload = null;
454
453
  const msg = {
455
454
  type: "DISCONNECTED",
456
455
  code: ev.code
@@ -483,11 +482,17 @@ class BroadcastChannelTransport {
483
482
  onMessage(handler) {
484
483
  this.handler = handler;
485
484
  }
486
- markAcknowledged() {
487
- this.ackReceived = true;
485
+ markAcknowledged(rawAckPayload) {
486
+ this.ackPayload = rawAckPayload;
488
487
  }
489
488
  destroy() {
490
489
  var _a;
490
+ if (this.isLeader) {
491
+ this.channel.postMessage({
492
+ type: "DISCONNECTED",
493
+ code: 1000
494
+ });
495
+ }
491
496
  this._destroyed = true;
492
497
  this.disconnect();
493
498
  this.channel.close();
@@ -1706,12 +1711,13 @@ class ExotelAIAssistController extends EventEmitter {
1706
1711
  if (this.connectionEstablished) {
1707
1712
  const wasThrottled = this._streamState === "throttled";
1708
1713
  if (this.readyFired && !wasThrottled) {
1709
- this.readyFired = false;
1710
1714
  this.emit("onReady", false);
1711
1715
  }
1716
+ this.connectionEstablished = false;
1717
+ this.readyFired = false;
1718
+ this._streamState = null;
1712
1719
  this._setStatus("disconnected");
1713
1720
  this.emit("onCallEnd");
1714
- this.destroy();
1715
1721
  } else {
1716
1722
  this._scheduleReconnect();
1717
1723
  }
@@ -1775,7 +1781,7 @@ class ExotelAIAssistController extends EventEmitter {
1775
1781
  return;
1776
1782
  }
1777
1783
  if (msgType === "ack") {
1778
- (_b = this.transport) === null || _b === void 0 ? void 0 : _b.markAcknowledged();
1784
+ (_b = this.transport) === null || _b === void 0 ? void 0 : _b.markAcknowledged(raw);
1779
1785
  const state = parsed.stream_state;
1780
1786
  if (state) this._handleStreamStatus(state);
1781
1787
  }
package/dist/index.js CHANGED
@@ -19785,7 +19785,7 @@ class BroadcastChannelTransport {
19785
19785
  this.pendingUrl = null;
19786
19786
  this.pendingAuthToken = null;
19787
19787
  this.lockReleaser = null;
19788
- this.ackReceived = false;
19788
+ this.ackPayload = null;
19789
19789
  this._destroyed = false;
19790
19790
  const channelName = `exotel-ai-assist:${sessionHash}`;
19791
19791
  const lockName = `exotel-ai-assist-leader:${sessionHash}`;
@@ -19794,15 +19794,14 @@ class BroadcastChannelTransport {
19794
19794
  var _a;
19795
19795
  const data = ev.data;
19796
19796
  if (this.isLeader) {
19797
- if (data.type === "JOIN" && this.socketConnected) {
19797
+ if (data.type === "JOIN" && this.socketConnected && this.ackPayload) {
19798
19798
  this.channel.postMessage({
19799
19799
  type: "CONNECTED"
19800
19800
  });
19801
- if (this.ackReceived) {
19802
- this.channel.postMessage({
19803
- type: "ACKNOWLEDGED"
19804
- });
19805
- }
19801
+ this.channel.postMessage({
19802
+ type: "MESSAGE",
19803
+ payload: this.ackPayload
19804
+ });
19806
19805
  }
19807
19806
  return;
19808
19807
  }
@@ -19857,7 +19856,7 @@ class BroadcastChannelTransport {
19857
19856
  var _a;
19858
19857
  if (this._destroyed) return;
19859
19858
  this.socketConnected = true;
19860
- this.ackReceived = false;
19859
+ this.ackPayload = null;
19861
19860
  this.socket.send(JSON.stringify({
19862
19861
  type: "auth",
19863
19862
  access_token: authToken
@@ -19892,7 +19891,7 @@ class BroadcastChannelTransport {
19892
19891
  var _a;
19893
19892
  if (this._destroyed) return;
19894
19893
  this.socketConnected = false;
19895
- this.ackReceived = false;
19894
+ this.ackPayload = null;
19896
19895
  const msg = {
19897
19896
  type: "DISCONNECTED",
19898
19897
  code: ev.code
@@ -19925,11 +19924,17 @@ class BroadcastChannelTransport {
19925
19924
  onMessage(handler) {
19926
19925
  this.handler = handler;
19927
19926
  }
19928
- markAcknowledged() {
19929
- this.ackReceived = true;
19927
+ markAcknowledged(rawAckPayload) {
19928
+ this.ackPayload = rawAckPayload;
19930
19929
  }
19931
19930
  destroy() {
19932
19931
  var _a;
19932
+ if (this.isLeader) {
19933
+ this.channel.postMessage({
19934
+ type: "DISCONNECTED",
19935
+ code: 1000
19936
+ });
19937
+ }
19933
19938
  this._destroyed = true;
19934
19939
  this.disconnect();
19935
19940
  this.channel.close();
@@ -21148,12 +21153,13 @@ class ExotelAIAssistController extends EventEmitter {
21148
21153
  if (this.connectionEstablished) {
21149
21154
  const wasThrottled = this._streamState === "throttled";
21150
21155
  if (this.readyFired && !wasThrottled) {
21151
- this.readyFired = false;
21152
21156
  this.emit("onReady", false);
21153
21157
  }
21158
+ this.connectionEstablished = false;
21159
+ this.readyFired = false;
21160
+ this._streamState = null;
21154
21161
  this._setStatus("disconnected");
21155
21162
  this.emit("onCallEnd");
21156
- this.destroy();
21157
21163
  } else {
21158
21164
  this._scheduleReconnect();
21159
21165
  }
@@ -21217,7 +21223,7 @@ class ExotelAIAssistController extends EventEmitter {
21217
21223
  return;
21218
21224
  }
21219
21225
  if (msgType === "ack") {
21220
- (_b = this.transport) === null || _b === void 0 ? void 0 : _b.markAcknowledged();
21226
+ (_b = this.transport) === null || _b === void 0 ? void 0 : _b.markAcknowledged(raw);
21221
21227
  const state = parsed.stream_state;
21222
21228
  if (state) this._handleStreamStatus(state);
21223
21229
  }
@@ -21309,7 +21315,14 @@ function useExotelAIAssist(params) {
21309
21315
  setLastError(null);
21310
21316
  const ctrl = new ExotelAIAssistController(params);
21311
21317
  controllerRef.current = ctrl;
21312
- ctrl.on("onReady", setIsReady);
21318
+ ctrl.on("onReady", ready => {
21319
+ setIsReady(ready);
21320
+ if (!ready) {
21321
+ setSuggestions([]);
21322
+ setTranscripts([]);
21323
+ setSentiment(null);
21324
+ }
21325
+ });
21313
21326
  ctrl.on("streamState", setStreamState);
21314
21327
  ctrl.on("botConfig", setBotConfig);
21315
21328
  ctrl.on("suggestion", s => setSuggestions(prev => [...prev, s].slice(-50)));
@@ -5071,7 +5071,7 @@ class BroadcastChannelTransport {
5071
5071
  this.pendingUrl = null;
5072
5072
  this.pendingAuthToken = null;
5073
5073
  this.lockReleaser = null;
5074
- this.ackReceived = false;
5074
+ this.ackPayload = null;
5075
5075
  this._destroyed = false;
5076
5076
  const channelName = `exotel-ai-assist:${sessionHash}`;
5077
5077
  const lockName = `exotel-ai-assist-leader:${sessionHash}`;
@@ -5080,15 +5080,14 @@ class BroadcastChannelTransport {
5080
5080
  var _a;
5081
5081
  const data = ev.data;
5082
5082
  if (this.isLeader) {
5083
- if (data.type === "JOIN" && this.socketConnected) {
5083
+ if (data.type === "JOIN" && this.socketConnected && this.ackPayload) {
5084
5084
  this.channel.postMessage({
5085
5085
  type: "CONNECTED"
5086
5086
  });
5087
- if (this.ackReceived) {
5088
- this.channel.postMessage({
5089
- type: "ACKNOWLEDGED"
5090
- });
5091
- }
5087
+ this.channel.postMessage({
5088
+ type: "MESSAGE",
5089
+ payload: this.ackPayload
5090
+ });
5092
5091
  }
5093
5092
  return;
5094
5093
  }
@@ -5143,7 +5142,7 @@ class BroadcastChannelTransport {
5143
5142
  var _a;
5144
5143
  if (this._destroyed) return;
5145
5144
  this.socketConnected = true;
5146
- this.ackReceived = false;
5145
+ this.ackPayload = null;
5147
5146
  this.socket.send(JSON.stringify({
5148
5147
  type: "auth",
5149
5148
  access_token: authToken
@@ -5178,7 +5177,7 @@ class BroadcastChannelTransport {
5178
5177
  var _a;
5179
5178
  if (this._destroyed) return;
5180
5179
  this.socketConnected = false;
5181
- this.ackReceived = false;
5180
+ this.ackPayload = null;
5182
5181
  const msg = {
5183
5182
  type: "DISCONNECTED",
5184
5183
  code: ev.code
@@ -5211,11 +5210,17 @@ class BroadcastChannelTransport {
5211
5210
  onMessage(handler) {
5212
5211
  this.handler = handler;
5213
5212
  }
5214
- markAcknowledged() {
5215
- this.ackReceived = true;
5213
+ markAcknowledged(rawAckPayload) {
5214
+ this.ackPayload = rawAckPayload;
5216
5215
  }
5217
5216
  destroy() {
5218
5217
  var _a;
5218
+ if (this.isLeader) {
5219
+ this.channel.postMessage({
5220
+ type: "DISCONNECTED",
5221
+ code: 1000
5222
+ });
5223
+ }
5219
5224
  this._destroyed = true;
5220
5225
  this.disconnect();
5221
5226
  this.channel.close();
@@ -6434,12 +6439,13 @@ class ExotelAIAssistController extends EventEmitter {
6434
6439
  if (this.connectionEstablished) {
6435
6440
  const wasThrottled = this._streamState === "throttled";
6436
6441
  if (this.readyFired && !wasThrottled) {
6437
- this.readyFired = false;
6438
6442
  this.emit("onReady", false);
6439
6443
  }
6444
+ this.connectionEstablished = false;
6445
+ this.readyFired = false;
6446
+ this._streamState = null;
6440
6447
  this._setStatus("disconnected");
6441
6448
  this.emit("onCallEnd");
6442
- this.destroy();
6443
6449
  } else {
6444
6450
  this._scheduleReconnect();
6445
6451
  }
@@ -6503,7 +6509,7 @@ class ExotelAIAssistController extends EventEmitter {
6503
6509
  return;
6504
6510
  }
6505
6511
  if (msgType === "ack") {
6506
- (_b = this.transport) === null || _b === void 0 ? void 0 : _b.markAcknowledged();
6512
+ (_b = this.transport) === null || _b === void 0 ? void 0 : _b.markAcknowledged(raw);
6507
6513
  const state = parsed.stream_state;
6508
6514
  if (state) this._handleStreamStatus(state);
6509
6515
  }
@@ -6595,7 +6601,14 @@ function useExotelAIAssist(params) {
6595
6601
  setLastError(null);
6596
6602
  const ctrl = new ExotelAIAssistController(params);
6597
6603
  controllerRef.current = ctrl;
6598
- ctrl.on("onReady", setIsReady);
6604
+ ctrl.on("onReady", ready => {
6605
+ setIsReady(ready);
6606
+ if (!ready) {
6607
+ setSuggestions([]);
6608
+ setTranscripts([]);
6609
+ setSentiment(null);
6610
+ }
6611
+ });
6599
6612
  ctrl.on("streamState", setStreamState);
6600
6613
  ctrl.on("botConfig", setBotConfig);
6601
6614
  ctrl.on("suggestion", s => setSuggestions(prev => [...prev, s].slice(-50)));
@@ -5,9 +5,9 @@ export interface ITransport {
5
5
  disconnect(): void;
6
6
  send(payload: string): void;
7
7
  onMessage(handler: TransportMessageHandler): void;
8
- /** Signal that the server ack has been received. The leader stores this so
9
- * follower tabs joining later get an immediate ACKNOWLEDGED broadcast. */
10
- markAcknowledged(): void;
8
+ /** Store the raw ack payload so follower tabs joining later receive it as a
9
+ * MESSAGE replay instead of a bare ACKNOWLEDGED signal. */
10
+ markAcknowledged(rawAckPayload: string): void;
11
11
  destroy(): void;
12
12
  }
13
13
  export declare class BroadcastChannelTransport implements ITransport {
@@ -19,7 +19,7 @@ export declare class BroadcastChannelTransport implements ITransport {
19
19
  private pendingUrl;
20
20
  private pendingAuthToken;
21
21
  private lockReleaser;
22
- private ackReceived;
22
+ private ackPayload;
23
23
  private _destroyed;
24
24
  /**
25
25
  * @param sessionHash - A hash of all connection-relevant params (see
@@ -34,7 +34,7 @@ export declare class BroadcastChannelTransport implements ITransport {
34
34
  disconnect(): void;
35
35
  send(payload: string): void;
36
36
  onMessage(handler: TransportMessageHandler): void;
37
- markAcknowledged(): void;
37
+ markAcknowledged(rawAckPayload: string): void;
38
38
  destroy(): void;
39
39
  }
40
40
  export declare function createTransport(sessionHash: string): ITransport;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exotel-npm-dev/exotel-ai-assist",
3
- "version": "1.2.8",
3
+ "version": "1.3.0",
4
4
  "description": "AI-powered real-time suggestions, transcript, and sentiment for agent calls",
5
5
  "license": "Apache-2.0",
6
6
  "main": "dist/index.js",
@@ -145,12 +145,13 @@ export class ExotelAIAssistController extends EventEmitter<ControllerEvents> {
145
145
  if (this.connectionEstablished) {
146
146
  const wasThrottled = this._streamState === "throttled";
147
147
  if (this.readyFired && !wasThrottled) {
148
- this.readyFired = false;
149
148
  this.emit("onReady", false);
150
149
  }
150
+ this.connectionEstablished = false;
151
+ this.readyFired = false;
152
+ this._streamState = null;
151
153
  this._setStatus("disconnected");
152
154
  this.emit("onCallEnd");
153
- this.destroy();
154
155
  } else {
155
156
  this._scheduleReconnect();
156
157
  }
@@ -224,7 +225,7 @@ export class ExotelAIAssistController extends EventEmitter<ControllerEvents> {
224
225
  }
225
226
 
226
227
  if (msgType === "ack") {
227
- this.transport?.markAcknowledged();
228
+ this.transport?.markAcknowledged(raw);
228
229
  const state = (parsed as WssResponse).stream_state;
229
230
  if (state) this._handleStreamStatus(state);
230
231
  }
@@ -63,7 +63,14 @@ export function useExotelAIAssist(params: ExotelAIAssistParams): UseExotelAIAssi
63
63
  const ctrl = new ExotelAIAssistController(params);
64
64
  controllerRef.current = ctrl;
65
65
 
66
- ctrl.on("onReady", setIsReady);
66
+ ctrl.on("onReady", (ready) => {
67
+ setIsReady(ready);
68
+ if (!ready) {
69
+ setSuggestions([]);
70
+ setTranscripts([]);
71
+ setSentiment(null);
72
+ }
73
+ });
67
74
  ctrl.on("streamState", setStreamState);
68
75
  ctrl.on("botConfig", setBotConfig);
69
76
  ctrl.on("suggestion", (s) => setSuggestions((prev) => [...prev, s].slice(-MAX_SUGGESTIONS)));
@@ -11,9 +11,9 @@ export interface ITransport {
11
11
  disconnect(): void;
12
12
  send(payload: string): void;
13
13
  onMessage(handler: TransportMessageHandler): void;
14
- /** Signal that the server ack has been received. The leader stores this so
15
- * follower tabs joining later get an immediate ACKNOWLEDGED broadcast. */
16
- markAcknowledged(): void;
14
+ /** Store the raw ack payload so follower tabs joining later receive it as a
15
+ * MESSAGE replay instead of a bare ACKNOWLEDGED signal. */
16
+ markAcknowledged(rawAckPayload: string): void;
17
17
  destroy(): void;
18
18
  }
19
19
 
@@ -26,7 +26,7 @@ export class BroadcastChannelTransport implements ITransport {
26
26
  private pendingUrl: string | null = null;
27
27
  private pendingAuthToken: string | null = null;
28
28
  private lockReleaser: (() => void) | null = null;
29
- private ackReceived = false;
29
+ private ackPayload: string | null = null;
30
30
  private _destroyed = false;
31
31
 
32
32
  /**
@@ -44,11 +44,9 @@ export class BroadcastChannelTransport implements ITransport {
44
44
  const data = ev.data;
45
45
 
46
46
  if (this.isLeader) {
47
- if (data.type === "JOIN" && this.socketConnected) {
47
+ if (data.type === "JOIN" && this.socketConnected && this.ackPayload) {
48
48
  this.channel.postMessage({ type: "CONNECTED" } satisfies WorkerInboundMessage);
49
- if (this.ackReceived) {
50
- this.channel.postMessage({ type: "ACKNOWLEDGED" } satisfies WorkerInboundMessage);
51
- }
49
+ this.channel.postMessage({ type: "MESSAGE", payload: this.ackPayload } satisfies WorkerInboundMessage);
52
50
  }
53
51
  return;
54
52
  }
@@ -107,7 +105,7 @@ export class BroadcastChannelTransport implements ITransport {
107
105
  this.socket.onopen = () => {
108
106
  if (this._destroyed) return;
109
107
  this.socketConnected = true;
110
- this.ackReceived = false;
108
+ this.ackPayload = null;
111
109
  this.socket!.send(JSON.stringify({ type: "auth", access_token: authToken }));
112
110
  const msg: WorkerInboundMessage = { type: "CONNECTED" };
113
111
  this.channel.postMessage(msg);
@@ -131,7 +129,7 @@ export class BroadcastChannelTransport implements ITransport {
131
129
  this.socket.onclose = (ev) => {
132
130
  if (this._destroyed) return;
133
131
  this.socketConnected = false;
134
- this.ackReceived = false;
132
+ this.ackPayload = null;
135
133
  const msg: WorkerInboundMessage = { type: "DISCONNECTED", code: ev.code };
136
134
  this.channel.postMessage(msg);
137
135
  this.handler?.(msg);
@@ -165,11 +163,14 @@ export class BroadcastChannelTransport implements ITransport {
165
163
  this.handler = handler;
166
164
  }
167
165
 
168
- markAcknowledged(): void {
169
- this.ackReceived = true;
166
+ markAcknowledged(rawAckPayload: string): void {
167
+ this.ackPayload = rawAckPayload;
170
168
  }
171
169
 
172
170
  destroy(): void {
171
+ if (this.isLeader) {
172
+ this.channel.postMessage({ type: "DISCONNECTED", code: 1000 } satisfies WorkerInboundMessage);
173
+ }
173
174
  this._destroyed = true;
174
175
  this.disconnect();
175
176
  this.channel.close();