@fonoster/autopilot 0.12.8 → 0.12.10

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.
@@ -25,6 +25,4 @@ export { increaseIdleTimeoutCount } from "./increaseIdleTimeoutCount";
25
25
  export { cleanSpeech } from "./cleanSpeech";
26
26
  export { appendSpeech } from "./appendSpeech";
27
27
  export { resetIdleTimeoutCount } from "./resetIdleTimeoutCount";
28
- export { setSpeaking } from "./setSpeaking";
29
- export { setSpeakingDone } from "./setSpeakingDone";
30
28
  export { resetState } from "./resetState";
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.resetState = exports.setSpeakingDone = exports.setSpeaking = exports.resetIdleTimeoutCount = exports.appendSpeech = exports.cleanSpeech = exports.increaseIdleTimeoutCount = exports.announceIdleTimeout = exports.interruptPlayback = exports.announceSystemError = exports.goodbye = exports.greetUser = void 0;
3
+ exports.resetState = exports.resetIdleTimeoutCount = exports.appendSpeech = exports.cleanSpeech = exports.increaseIdleTimeoutCount = exports.announceIdleTimeout = exports.interruptPlayback = exports.announceSystemError = exports.goodbye = exports.greetUser = void 0;
4
4
  /**
5
5
  * Copyright (C) 2025 by Fonoster Inc (https://fonoster.com)
6
6
  * http://github.com/fonoster/fonoster
@@ -37,9 +37,5 @@ var appendSpeech_1 = require("./appendSpeech");
37
37
  Object.defineProperty(exports, "appendSpeech", { enumerable: true, get: function () { return appendSpeech_1.appendSpeech; } });
38
38
  var resetIdleTimeoutCount_1 = require("./resetIdleTimeoutCount");
39
39
  Object.defineProperty(exports, "resetIdleTimeoutCount", { enumerable: true, get: function () { return resetIdleTimeoutCount_1.resetIdleTimeoutCount; } });
40
- var setSpeaking_1 = require("./setSpeaking");
41
- Object.defineProperty(exports, "setSpeaking", { enumerable: true, get: function () { return setSpeaking_1.setSpeaking; } });
42
- var setSpeakingDone_1 = require("./setSpeakingDone");
43
- Object.defineProperty(exports, "setSpeakingDone", { enumerable: true, get: function () { return setSpeakingDone_1.setSpeakingDone; } });
44
40
  var resetState_1 = require("./resetState");
45
41
  Object.defineProperty(exports, "resetState", { enumerable: true, get: function () { return resetState_1.resetState; } });
@@ -27,7 +27,6 @@ exports.resetState = (0, xstate_1.assign)(({ context }) => {
27
27
  return {
28
28
  ...context,
29
29
  speechBuffer: "",
30
- idleTimeoutCount: 0,
31
- isSpeaking: false
30
+ idleTimeoutCount: 0
32
31
  };
33
32
  });
@@ -25,13 +25,14 @@ const logger = (0, logger_1.getLogger)({ service: "autopilot", filePath: __filen
25
25
  exports.doProcessUserRequest = (0, xstate_1.fromPromise)(async ({ input }) => {
26
26
  const { context } = input;
27
27
  logger.verbose("called processUserRequest actor", {
28
- speechBuffer: context.speechBuffer
28
+ speechBuffer: context.speechBuffer,
29
+ isReentry: context.isReentry
29
30
  });
30
31
  // Stop any speech that might be playing
31
32
  await context.voice.stopSpeech();
32
33
  const languageModel = context.languageModel;
33
34
  const speech = context.speechBuffer.trim();
34
- const response = await languageModel.invoke(speech);
35
+ const response = await languageModel.invoke(speech, context.isReentry);
35
36
  try {
36
37
  if (response.type === "say" && !response.content) {
37
38
  logger.warn("ignoring say response with no content");
@@ -42,9 +42,10 @@ declare const context: ({ input }: {
42
42
  idleTimeoutCount: number;
43
43
  maxSpeechWaitTimeout: number;
44
44
  allowUserBargeIn: boolean;
45
- isSpeaking: boolean;
46
45
  sessionStartTime: number;
47
46
  maxSessionDuration: number;
48
47
  initialDtmf: string;
48
+ previousState: any;
49
+ isReentry: boolean;
49
50
  };
50
51
  export { context };
@@ -18,9 +18,10 @@ const context = ({ input }) => ({
18
18
  idleTimeoutCount: 0,
19
19
  maxSpeechWaitTimeout: input.conversationSettings.maxSpeechWaitTimeout,
20
20
  allowUserBargeIn: input.conversationSettings.allowUserBargeIn,
21
- isSpeaking: false,
22
21
  sessionStartTime: Date.now(),
23
22
  maxSessionDuration: input.conversationSettings.maxSessionDuration,
24
- initialDtmf: input.conversationSettings.initialDtmf
23
+ initialDtmf: input.conversationSettings.initialDtmf,
24
+ previousState: null,
25
+ isReentry: false
25
26
  });
26
27
  exports.context = context;
@@ -18,4 +18,3 @@
18
18
  */
19
19
  export { idleTimeoutCountExceedsMax } from "./idleTimeoutCountExceedsMax";
20
20
  export { hasSpeechResult } from "./hasSpeechResult";
21
- export { isSpeaking } from "./isSpeaking";
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.isSpeaking = exports.hasSpeechResult = exports.idleTimeoutCountExceedsMax = void 0;
3
+ exports.hasSpeechResult = exports.idleTimeoutCountExceedsMax = void 0;
4
4
  /**
5
5
  * Copyright (C) 2025 by Fonoster Inc (https://fonoster.com)
6
6
  * http://github.com/fonoster/fonoster
@@ -23,5 +23,3 @@ var idleTimeoutCountExceedsMax_1 = require("./idleTimeoutCountExceedsMax");
23
23
  Object.defineProperty(exports, "idleTimeoutCountExceedsMax", { enumerable: true, get: function () { return idleTimeoutCountExceedsMax_1.idleTimeoutCountExceedsMax; } });
24
24
  var hasSpeechResult_1 = require("./hasSpeechResult");
25
25
  Object.defineProperty(exports, "hasSpeechResult", { enumerable: true, get: function () { return hasSpeechResult_1.hasSpeechResult; } });
26
- var isSpeaking_1 = require("./isSpeaking");
27
- Object.defineProperty(exports, "isSpeaking", { enumerable: true, get: function () { return isSpeaking_1.isSpeaking; } });
@@ -29,14 +29,6 @@ declare const machine: import("xstate").StateMachine<any, import("./types").Auto
29
29
  type: "resetState";
30
30
  params: unknown;
31
31
  };
32
- setSpeaking: {
33
- type: "setSpeaking";
34
- params: unknown;
35
- };
36
- setSpeakingDone: {
37
- type: "setSpeakingDone";
38
- params: unknown;
39
- };
40
32
  greetUser: {
41
33
  type: "greetUser";
42
34
  params: unknown;
@@ -58,10 +50,6 @@ declare const machine: import("xstate").StateMachine<any, import("./types").Auto
58
50
  params: unknown;
59
51
  };
60
52
  }>, import("xstate").Values<{
61
- isSpeaking: {
62
- type: "isSpeaking";
63
- params: unknown;
64
- };
65
53
  idleTimeoutCountExceedsMax: {
66
54
  type: "idleTimeoutCountExceedsMax";
67
55
  params: unknown;
@@ -70,12 +58,12 @@ declare const machine: import("xstate").StateMachine<any, import("./types").Auto
70
58
  type: "hasSpeechResult";
71
59
  params: unknown;
72
60
  };
73
- }>, "IDLE_TIMEOUT" | "MAX_SPEECH_WAIT_TIMEOUT" | "MAX_SESSION_DURATION", "hangup" | "greeting" | "idle" | "listeningToUser" | "transitioningToIdle" | "waitingForSpeechTimeout" | "processingUserRequest", string, {
61
+ }>, "IDLE_TIMEOUT" | "MAX_SPEECH_WAIT_TIMEOUT" | "MAX_SESSION_DURATION", "hangup" | "greeting" | "idle" | "listeningToUser" | "waitingForSpeechTimeout" | "processingUserRequest", string, {
74
62
  conversationSettings: import("..").ConversationSettings;
75
63
  languageModel: import("..").LanguageModel;
76
64
  voice: import("..").Voice;
77
65
  }, {}, import("xstate").EventObject, import("xstate").MetaObject, {
78
- /** @xstate-layout N4IgpgJg5mDOIC5QDMB2BBAkgYgB6wBcBDAsAOiOVICcAKAWXQA0B9AZQFE23MB5AORYARAKoAldABU+-AJTY0WANoAGALqJQABwD2sAJYF9O1JpC5EAWgDMAJgCcZAGwOAjAA4V9pyusB2e3cAGhAAT0RbAFYAFjIHJwDrJ2jXWxU06IBfTJDFTDIoajAwI1QobFUNJBBdAyMTMwsED1cyf28-O08nJ3dXJxDwhHtrSLJ7Px6o+1TbUb9s3Ix8-QgAGzBsNgAFDg4AYQAJdkl0MUlKs1rDY1NqpvsVPzJ3SLm-W3dozoS-QcQ-O5bOMVE5-H4VB4AtFbIsQHkyKsNnhCCRyJQaLRMEIADIcFjSegcXgiSTyBFIsCXarXep3UBNGIqcaRPqBeyPXxOSL-ZrWVytVIzSL2WzRFQqaKRaxwinrTb4YikChUMB0bF4gmYIkkskKZaI+VKVxVbR6G4Ne6IayOXoTQEpdIqSJg3kC9y2qW2b3eIHRKWyg0EahEVB1W76MqSHSYeUVdRXc10xrWr7jMG2blfdzWaLWSVu1xeMgQwGigUBAKBrBkNb6QhgVCRqDRkSwNVbXYHY5iLgiHEXBM0pO3FMIZLuF5g+zRafeCbRXlJZkcvwxBwSlLimU5eEGusNptRnRtjuKtEqzEa-GE4mk8n7+ukI8tk-t6jUs3hy0MxACjwlp0rJio8URrm6KhAiWRYir4kSuP4czVvkADuRA3GUABiOjUGwWjFAAxgAFpI+gALZgDoACuBCdnsRwnGcg6mjUI4-uYf4Qm0kquGWop2AEAxhIgIpjO4fgwukQJ+BJyFkGhGFQNhuH4WAxGkRR1G0Ts9E9n2A6fqx370hxCCWFK4zWF8Mk+MkkKuDywkIO8ZCShJrLRCMkR+LxkRyQppRKTheGESR5GUTRKJKuiqp0IwrA6d2LAAOpYJIWo6ve+o1gFzbKSFalhZpNGGbSo5Wgg0Rpkk-pgp4ML+ryUStG81hJPytgeK8gJyVo1A6ARcAGGUp7UGIYAAI5UXAtEQCY5CRgAbjoADW5AIn1A1Dc2o3jVNM0IEtA0kLclSlWxJlNKkoJtJ8LrSkWXi2LyIqtDJ9jAaCtgyf4vX9YNsDDVAu2TdNhB0UlbCnOc53GWOCFJGQCPuGCDmdJK9i8mWZDwcks6QpCTgzn9W2Azt757WD2ldgxvZsP2zGJnDFW8WmMmgh64p2cETm5o4vFzP47SeJz2S7qgOgQHAZh5EzFqXVY4LOG4njeL4AQ80MrieSWYrwR94rXVku4IoUxSBXLyYVZYCRxETHy+KKkE9JriCik4ZDfAEGTq9KfkmwalKW+Vv4IN5wJOAKLjeNr3y+Ly7ue+9Pv+H7cnBqGxnNtGsYbMH7EPMyIo+OKUqzmKfSFpBcRPCjIrZvBsIBzWB7Ptnb5qvnCsIOJzJsvmD1gpEMQvZHLyeJBSQjK87j+ehgX5ap6nhVpXfw7bkco58PiZrOi68xyuspJmIw+aCa5yURoZQFRWhr9baRjC4-IzOkEkfG6bWtKM4dVfdRPGyWDWTaAMgYg32oQe+ocHLSjaJHVm3J0htReuJceEovhJGlKKBYYsgA */
66
+ /** @xstate-layout N4IgpgJg5mDOIC5QDMB2BBAkgYgFQG0AGAXUVAAcB7WASwBcbLUyQAPRAWgFYBmARgB0ADi58A7CMIA2HgBYuATj4AaEAE9EPAEwDZYsQqljZQnoT18pUgL7XVaLNlaw6AQzpgBr5B4BOACgBZdAANAH0AZQBRCIjMAHkAOTCAEQBVACV0ABUExIBKbAdMIlIkECpaBiYWdgQOHiExAWktRVNRY1kVdUQ22QEtBWMxKUNLSy4uW3sMTAEoXzAwBlQobFKWSvpGZnK6viFBHgMjbSFpKSOpVQ0EBR4uAWGrNqUtLUexGZBigRoIAAbMDYCIABSiUQAwgAJSLZdAZbKbcrbap7UB1BSEZoiT5iLRCPQ8IxiW6ICQ6bEyfSEQ4GWRaH5-AHApwudyebx+fyYFIAGSiYVygSi8TS2UKLKBYBRFGoOxq+0QXFkhGeXCOCiECmxhBJXHJCD4-EEfCGfEUWjV5l4zLm-xl7LcHi8PjAAT5guFmFF4slRQdrNlfDK8qqu1qmhMAnEolERhMhK0Rr45p4AmMFy4xlEJPk9qwAkBNBcYFQNDW2UoaVgHtBEOhcIyMTS-ORJC2CvRUYQUhjQhkClkQ8Mw1kRpJ6t1YlVQ0I5j4ap4hfmJbLFarNbrvmdnLdPK9QpFYolUod648m6g1drHrlFW7keVxrTQkzYkeQmt2Las9ThDfpmfCEIo+p5gSK52L8DoAO6uDsawAGKUL4ETkMsADGAAW2Q0AAtmAlAAK50A2kKwvCiIdmGj4RkqmKIOI6pmN0Ei6p8WinEaihPE0jKEMm+iyKuAjwYhUAoWhGFgDheGESRZHghRzatu2D5os+jH1PIzyNHoozSGqaaGr0CD4i0ejyESDw5nGoniaskmoehWG4QRRGkXurrch6QShJEjaUQA6lg2Q+n6Z6BkWjmVs50lufJnl0BpT4MWwTGHO+kFiJYDxGN+KZmdaVJCDqIhWFwYxHEIonkL4lCYXAtBrHevgZGAACOxFwGREBMJ4lYAG6UAA1p4fz1Y1zVxW1HXdb1CDDY17i7KUqX0RiGXGm0OgklqZiKI0PR3GBuiCU0n4nGxInQZNDVNbALVQHNXU9S45FNlRSIbYqW0HDwJKxoDg78DmrEKEa7ECAmsgjnSdJjLdsxFlNj3Pa9C0fcpX0thEbY0V2m29uIRIftIOpqv2gGTrICixpBJwnNqgHDrY0GoJQEBwCwxRE39vYcLIjwtFIbwdOIcMnUxdMw1ZVxDDqXD6qJizLE5-M9i+HBGIMYwEvqChaIBVhCEaRtSLo+hG+Y+qzo8onBprWnbTmOhSGmYvjHDYj6ubWiWwZNvLvb0x3RepZXnFt47s76V1E06oHaB-AyFME5mVVghlQuphjI0moOQhTlSa5snuQppFx-9TG6x7g6ElIgn9v2k66pm1rmvnuXSLOonYa4azEeQ1eC8bTxi-wSiCSMRV3Ca-ACI8bsmFVVVs+HqMPTNrU7vN710KPL6WrwS8e6TUjK58PA8U0wgXIBwuXzwRvfOzQA */
79
67
  readonly context: ({ input }: {
80
68
  input: {
81
69
  voice: import("..").Voice;
@@ -99,10 +87,11 @@ declare const machine: import("xstate").StateMachine<any, import("./types").Auto
99
87
  idleTimeoutCount: number;
100
88
  maxSpeechWaitTimeout: number;
101
89
  allowUserBargeIn: boolean;
102
- isSpeaking: boolean;
103
90
  sessionStartTime: number;
104
91
  maxSessionDuration: number;
105
92
  initialDtmf: string;
93
+ previousState: any;
94
+ isReentry: boolean;
106
95
  };
107
96
  readonly id: "fnAI";
108
97
  readonly initial: "greeting";
@@ -119,8 +108,6 @@ declare const machine: import("xstate").StateMachine<any, import("./types").Auto
119
108
  readonly idle: {
120
109
  readonly entry: readonly [{
121
110
  readonly type: "cleanSpeech";
122
- }, {
123
- readonly type: "setSpeakingDone";
124
111
  }];
125
112
  readonly on: {
126
113
  readonly SPEECH_START: {
@@ -139,27 +126,21 @@ declare const machine: import("xstate").StateMachine<any, import("./types").Auto
139
126
  params: undefined;
140
127
  }>;
141
128
  }, {
142
- readonly target: "transitioningToIdle";
129
+ readonly target: "idle";
143
130
  readonly actions: readonly [{
144
131
  readonly type: "increaseIdleTimeoutCount";
145
132
  }, {
146
133
  readonly type: "announceIdleTimeout";
147
134
  }];
135
+ readonly reenter: true;
148
136
  }];
149
137
  };
150
138
  };
151
- readonly transitioningToIdle: {
152
- readonly always: {
153
- readonly target: "idle";
154
- };
155
- };
156
139
  readonly listeningToUser: {
157
140
  readonly entry: readonly [{
158
141
  readonly type: "interruptPlayback";
159
142
  }, {
160
143
  readonly type: "resetIdleTimeoutCount";
161
- }, {
162
- readonly type: "setSpeaking";
163
144
  }];
164
145
  readonly on: {
165
146
  readonly SPEECH_RESULT: {
@@ -173,7 +154,7 @@ declare const machine: import("xstate").StateMachine<any, import("./types").Auto
173
154
  };
174
155
  readonly after: {
175
156
  readonly IDLE_TIMEOUT: readonly [{
176
- readonly target: "transitioningToIdle";
157
+ readonly target: "idle";
177
158
  readonly actions: readonly [{
178
159
  readonly type: "increaseIdleTimeoutCount";
179
160
  }, {
@@ -218,6 +199,9 @@ declare const machine: import("xstate").StateMachine<any, import("./types").Auto
218
199
  readonly guard: ({ context }: import("xstate/dist/declarations/src/guards").GuardArgs<any, {
219
200
  type: "SPEECH_START";
220
201
  }>) => any;
202
+ readonly actions: readonly [{
203
+ readonly type: "cleanSpeech";
204
+ }];
221
205
  };
222
206
  readonly SPEECH_RESULT: {
223
207
  readonly target: "processingUserRequest";
@@ -227,6 +211,7 @@ declare const machine: import("xstate").StateMachine<any, import("./types").Auto
227
211
  }, {
228
212
  readonly type: "appendSpeech";
229
213
  }];
214
+ readonly reenter: true;
230
215
  };
231
216
  };
232
217
  readonly invoke: {
@@ -257,5 +242,16 @@ declare const machine: import("xstate").StateMachine<any, import("./types").Auto
257
242
  };
258
243
  };
259
244
  };
245
+ readonly on: {
246
+ readonly "*": {
247
+ readonly actions: import("xstate").ActionFunction<any, import("./types").AutopilotEvents, import("./types").AutopilotEvents, undefined, {
248
+ src: "doProcessUserRequest";
249
+ logic: import("xstate").PromiseActorLogic<void, {
250
+ context: import("./types").AutopilotContext;
251
+ }, import("xstate").EventObject>;
252
+ id: string;
253
+ }, never, never, never, never>;
254
+ };
255
+ };
260
256
  }>;
261
257
  export { machine };
@@ -20,10 +20,11 @@ exports.machine = void 0;
20
20
  * limitations under the License.
21
21
  */
22
22
  const xstate_1 = require("xstate");
23
+ const xstate_2 = require("xstate");
23
24
  const context_1 = require("./context");
24
25
  const setup_1 = require("./setup");
25
26
  const machine = setup_1.machineSetup.createMachine({
26
- /** @xstate-layout N4IgpgJg5mDOIC5QDMB2BBAkgYgB6wBcBDAsAOiOVICcAKAWXQA0B9AZQFE23MB5AORYARAKoAldABU+-AJTY0WANoAGALqJQABwD2sAJYF9O1JpC5EAWgDMAJgCcZAGwOAjAA4V9pyusB2e3cAGhAAT0RbAFYAFjIHJwDrJ2jXWxU06IBfTJDFTDIoajAwI1QobFUNJBBdAyMTMwsED1cyf28-O08nJ3dXJxDwhHtrSLJ7Px6o+1TbUb9s3Ix8-QgAGzBsNgAFDg4AYQAJdkl0MUlKs1rDY1NqpvsVPzJ3SLm-W3dozoS-QcQ-O5bOMVE5-H4VB4AtFbIsQHkyKsNnhCCRyJQaLRMEIADIcFjSegcXgiSTyBFIsCXarXep3UBNGIqcaRPqBeyPXxOSL-ZrWVytVIzSL2WzRFQqaKRaxwinrTb4YikChUMB0bF4gmYIkkskKZaI+VKVxVbR6G4Ne6IayOXoTQEpdIqSJg3kC9y2qW2b3eIHRKWyg0EahEVB1W76MqSHSYeUVdRXc10xrWr7jMG2blfdzWaLWSVu1xeMgQwGigUBAKBrBkNb6QhgVCRqDRkSwNVbXYHY5iLgiHEXBM0pO3FMIZLuF5g+zRafeCbRXlJZkcvwxBwSlLimU5eEGusNptRnRtjuKtEqzEa-GE4mk8n7+ukI8tk-t6jUs3hy0MxACjwlp0rJio8URrm6KhAiWRYir4kSuP4czVvkADuRA3GUABiOjUGwWjFAAxgAFpI+gALZgDoACuBCdnsRwnGcg6mjUI4-uYf4Qm0kquGWop2AEAxhIgIpjO4fgwukQJ+BJyFkGhGFQNhuH4WAxGkRR1G0Ts9E9n2A6fqx370hxCCWFK4zWF8Mk+MkkKuDywkIO8ZCShJrLRCMkR+LxkRyQppRKTheGESR5GUTRKJKuiqp0IwrA6d2LAAOpYJIWo6ve+o1gFzbKSFalhZpNGGbSo5Wgg0Rpkk-pgp4ML+ryUStG81hJPytgeK8gJyVo1A6ARcAGGUp7UGIYAAI5UXAtEQCY5CRgAbjoADW5AIn1A1Dc2o3jVNM0IEtA0kLclSlWxJlNKkoJtJ8LrSkWXi2LyIqtDJ9jAaCtgyf4vX9YNsDDVAu2TdNhB0UlbCnOc53GWOCFJGQCPuGCDmdJK9i8mWZDwcks6QpCTgzn9W2Azt757WD2ldgxvZsP2zGJnDFW8WmMmgh64p2cETm5o4vFzP47SeJz2S7qgOgQHAZh5EzFqXVY4LOG4njeL4AQ80MrieSWYrwR94rXVku4IoUxSBXLyYVZYCRxETHy+KKkE9JriCik4ZDfAEGTq9KfkmwalKW+Vv4IN5wJOAKLjeNr3y+Ly7ue+9Pv+H7cnBqGxnNtGsYbMH7EPMyIo+OKUqzmKfSFpBcRPCjIrZvBsIBzWB7Ptnb5qvnCsIOJzJsvmD1gpEMQvZHLyeJBSQjK87j+ehgX5ap6nhVpXfw7bkco58PiZrOi68xyuspJmIw+aCa5yURoZQFRWhr9baRjC4-IzOkEkfG6bWtKM4dVfdRPGyWDWTaAMgYg32oQe+ocHLSjaJHVm3J0htReuJceEovhJGlKKBYYsgA */
27
+ /** @xstate-layout N4IgpgJg5mDOIC5QDMB2BBAkgYgFQG0AGAXUVAAcB7WASwBcbLUyQAPRAWgFYBmARgB0ADi58A7CMIA2HgBYuATj4AaEAE9EPAEwDZYsQqljZQnoT18pUgL7XVaLNlaw6AQzpgBr5B4BOACgBZdAANAH0AZQBRCIjMAHkAOTCAEQBVACV0ABUExIBKbAdMIlIkECpaBiYWdgQOHiExAWktRVNRY1kVdUQ22QEtBWMxKUNLSy4uW3sMTAEoXzAwBlQobFKWSvpGZnK6viFBHgMjbSFpKSOpVQ0EBR4uAWGrNqUtLUexGZBigRoIAAbMDYCIABSiUQAwgAJSLZdAZbKbcrbap7UB1BSEZoiT5iLRCPQ8IxiW6ICQ6bEyfSEQ4GWRaH5-AHApwudyebx+fyYFIAGSiYVygSi8TS2UKLKBYBRFGoOxq+0QXFkhGeXCOCiECmxhBJXHJCD4-EEfCGfEUWjV5l4zLm-xl7LcHi8PjAAT5guFmFF4slRQdrNlfDK8qqu1qmhMAnEolERhMhK0Rr45p4AmMFy4xlEJPk9qwAkBNBcYFQNDW2UoaVgHtBEOhcIyMTS-ORJC2CvRUYQUhjQhkClkQ8Mw1kRpJ6t1YlVQ0I5j4ap4hfmJbLFarNbrvmdnLdPK9QpFYolUod648m6g1drHrlFW7keVxrTQkzYkeQmt2Las9ThDfpmfCEIo+p5gSK52L8DoAO6uDsawAGKUL4ETkMsADGAAW2Q0AAtmAlAAK50A2kKwvCiIdmGj4RkqmKIOI6pmN0Ei6p8WinEaihPE0jKEMm+iyKuAjwYhUAoWhGFgDheGESRZHghRzatu2D5os+jH1PIzyNHoozSGqaaGr0CD4i0ejyESDw5nGoniaskmoehWG4QRRGkXurrch6QShJEjaUQA6lg2Q+n6Z6BkWjmVs50lufJnl0BpT4MWwTGHO+kFiJYDxGN+KZmdaVJCDqIhWFwYxHEIonkL4lCYXAtBrHevgZGAACOxFwGREBMJ4lYAG6UAA1p4fz1Y1zVxW1HXdb1CDDY17i7KUqX0RiGXGm0OgklqZiKI0PR3GBuiCU0n4nGxInQZNDVNbALVQHNXU9S45FNlRSIbYqW0HDwJKxoDg78DmrEKEa7ECAmsgjnSdJjLdsxFlNj3Pa9C0fcpX0thEbY0V2m29uIRIftIOpqv2gGTrICixpBJwnNqgHDrY0GoJQEBwCwxRE39vYcLIjwtFIbwdOIcMnUxdMw1ZVxDDqXD6qJizLE5-M9i+HBGIMYwEvqChaIBVhCEaRtSLo+hG+Y+qzo8onBprWnbTmOhSGmYvjHDYj6ubWiWwZNvLvb0x3RepZXnFt47s76V1E06oHaB-AyFME5mVVghlQuphjI0moOQhTlSa5snuQppFx-9TG6x7g6ElIgn9v2k66pm1rmvnuXSLOonYa4azEeQ1eC8bTxi-wSiCSMRV3Ca-ACI8bsmFVVVs+HqMPTNrU7vN710KPL6WrwS8e6TUjK58PA8U0wgXIBwuXzwRvfOzQA */
27
28
  context: context_1.context,
28
29
  id: "fnAI",
29
30
  initial: "greeting",
@@ -38,7 +39,7 @@ const machine = setup_1.machineSetup.createMachine({
38
39
  }
39
40
  },
40
41
  idle: {
41
- entry: [{ type: "cleanSpeech" }, { type: "setSpeakingDone" }],
42
+ entry: [{ type: "cleanSpeech" }],
42
43
  on: {
43
44
  SPEECH_START: {
44
45
  target: "listeningToUser",
@@ -53,29 +54,18 @@ const machine = setup_1.machineSetup.createMachine({
53
54
  guard: (0, xstate_1.and)(["idleTimeoutCountExceedsMax"])
54
55
  },
55
56
  {
56
- target: "transitioningToIdle",
57
+ target: "idle",
57
58
  actions: [
58
59
  { type: "increaseIdleTimeoutCount" },
59
60
  { type: "announceIdleTimeout" }
60
- ]
61
+ ],
62
+ reenter: true
61
63
  }
62
64
  ]
63
65
  }
64
66
  },
65
- transitioningToIdle: {
66
- // This intermediate state is necessary to ensure the IDLE_TIMEOUT
67
- // event is properly reset and retriggered when returning to idle.
68
- // Without it, the timer would not restart correctly.
69
- always: {
70
- target: "idle"
71
- }
72
- },
73
67
  listeningToUser: {
74
- entry: [
75
- { type: "interruptPlayback" },
76
- { type: "resetIdleTimeoutCount" },
77
- { type: "setSpeaking" }
78
- ],
68
+ entry: [{ type: "interruptPlayback" }, { type: "resetIdleTimeoutCount" }],
79
69
  on: {
80
70
  SPEECH_RESULT: {
81
71
  target: "waitingForSpeechTimeout",
@@ -89,7 +79,7 @@ const machine = setup_1.machineSetup.createMachine({
89
79
  after: {
90
80
  IDLE_TIMEOUT: [
91
81
  {
92
- target: "transitioningToIdle",
82
+ target: "idle",
93
83
  actions: [
94
84
  { type: "increaseIdleTimeoutCount" },
95
85
  { type: "announceIdleTimeout" }
@@ -127,12 +117,17 @@ const machine = setup_1.machineSetup.createMachine({
127
117
  SPEECH_START: {
128
118
  target: "listeningToUser",
129
119
  description: "Event from VAD or similar system.",
130
- guard: ({ context }) => context.allowUserBargeIn
120
+ guard: ({ context }) => context.allowUserBargeIn,
121
+ // We assume that the user wants to steer the conversation
122
+ // back to the agent so we clean the speech buffer
123
+ actions: [{ type: "cleanSpeech" }]
131
124
  },
132
125
  SPEECH_RESULT: {
126
+ // This makes sure result that
133
127
  target: "processingUserRequest",
134
128
  description: "Append speech and go back to listening.",
135
- actions: [{ type: "interruptPlayback" }, { type: "appendSpeech" }]
129
+ actions: [{ type: "interruptPlayback" }, { type: "appendSpeech" }],
130
+ reenter: true
136
131
  }
137
132
  },
138
133
  invoke: {
@@ -152,6 +147,17 @@ const machine = setup_1.machineSetup.createMachine({
152
147
  target: ".hangup",
153
148
  actions: { type: "goodbye" }
154
149
  }
150
+ },
151
+ on: {
152
+ "*": {
153
+ actions: (0, xstate_2.assign)(({ context, self }) => {
154
+ const isReentry = self.getSnapshot().value === context.previousState;
155
+ return {
156
+ previousState: self.getSnapshot().value,
157
+ isReentry
158
+ };
159
+ })
160
+ }
155
161
  }
156
162
  });
157
163
  exports.machine = machine;
@@ -30,14 +30,6 @@ declare const machineSetup: {
30
30
  type: "resetState";
31
31
  params: unknown;
32
32
  };
33
- setSpeaking: {
34
- type: "setSpeaking";
35
- params: unknown;
36
- };
37
- setSpeakingDone: {
38
- type: "setSpeakingDone";
39
- params: unknown;
40
- };
41
33
  greetUser: {
42
34
  type: "greetUser";
43
35
  params: unknown;
@@ -59,10 +51,6 @@ declare const machineSetup: {
59
51
  params: unknown;
60
52
  };
61
53
  }>, import("xstate").Values<{
62
- isSpeaking: {
63
- type: "isSpeaking";
64
- params: unknown;
65
- };
66
54
  idleTimeoutCountExceedsMax: {
67
55
  type: "idleTimeoutCountExceedsMax";
68
56
  params: unknown;
@@ -106,14 +94,6 @@ declare const machineSetup: {
106
94
  type: "resetState";
107
95
  params: unknown;
108
96
  };
109
- setSpeaking: {
110
- type: "setSpeaking";
111
- params: unknown;
112
- };
113
- setSpeakingDone: {
114
- type: "setSpeakingDone";
115
- params: unknown;
116
- };
117
97
  greetUser: {
118
98
  type: "greetUser";
119
99
  params: unknown;
@@ -135,10 +115,6 @@ declare const machineSetup: {
135
115
  params: unknown;
136
116
  };
137
117
  }>, import("xstate").Values<{
138
- isSpeaking: {
139
- type: "isSpeaking";
140
- params: unknown;
141
- };
142
118
  idleTimeoutCountExceedsMax: {
143
119
  type: "idleTimeoutCountExceedsMax";
144
120
  params: unknown;
@@ -105,21 +105,8 @@ const machineSetup = (0, xstate_1.setup)({
105
105
  return {
106
106
  ...context,
107
107
  speechBuffer: "",
108
- idleTimeoutCount: 0,
109
- isSpeaking: false
108
+ idleTimeoutCount: 0
110
109
  };
111
- }),
112
- setSpeaking: (0, xstate_1.assign)(({ context }) => {
113
- logger.verbose("called the setSpeaking action", { isSpeaking: true });
114
- context.isSpeaking = true;
115
- return context;
116
- }),
117
- setSpeakingDone: (0, xstate_1.assign)(({ context }) => {
118
- logger.verbose("called the setSpeakingDone action", {
119
- isSpeaking: false
120
- });
121
- context.isSpeaking = false;
122
- return context;
123
110
  })
124
111
  },
125
112
  guards,
@@ -35,9 +35,10 @@ type AutopilotContext = {
35
35
  maxSpeechWaitTimeout: number;
36
36
  speechBuffer: string;
37
37
  speechResponseTime: number;
38
- isSpeaking: boolean;
39
38
  knowledgeBaseSourceUrl?: string;
40
39
  initialDtmf?: string;
40
+ previousState: string | null;
41
+ isReentry: boolean;
41
42
  };
42
43
  type AutopilotEvents = {
43
44
  type: "SPEECH_START";
@@ -9,7 +9,7 @@ declare abstract class AbstractLanguageModel implements LanguageModel {
9
9
  private readonly firstMessage;
10
10
  private readonly transferOptions;
11
11
  constructor(params: LanguageModelParams, voice: Voice, telephonyContext: TelephonyContext);
12
- invoke(text: string): Promise<InvocationResult>;
12
+ invoke(text: string, isReentry: boolean): Promise<InvocationResult>;
13
13
  getChatHistoryMessages(): Promise<import("@langchain/core/messages").BaseMessage[]>;
14
14
  }
15
15
  export { AbstractLanguageModel };
@@ -41,7 +41,7 @@ class AbstractLanguageModel {
41
41
  });
42
42
  this.chain = (0, createChain_1.createChain)(model, knowledgeBase, promptTemplate, this.chatHistory);
43
43
  }
44
- async invoke(text) {
44
+ async invoke(text, isReentry) {
45
45
  const { chain, chatHistory, toolsCatalog } = this;
46
46
  const response = (await chain.invoke({ text }));
47
47
  let isFirstTool = true;
@@ -51,6 +51,13 @@ class AbstractLanguageModel {
51
51
  hasTools: (response.tool_calls?.length ?? 0) > 0,
52
52
  tools: response.tool_calls?.map((tool) => tool.name)
53
53
  });
54
+ // This handles late speech recognition
55
+ if (isReentry) {
56
+ logger.verbose("xxx reentry detected, discarding last conversation turn");
57
+ const messages = await chatHistory.getMessages();
58
+ messages.pop(); // Last AI message
59
+ messages.pop(); // Last user message
60
+ }
54
61
  // Begin the conversation with the first message
55
62
  if ((await chatHistory.getMessages()).length === 0 && this.firstMessage) {
56
63
  await chatHistory.addAIMessage(this.firstMessage);
@@ -22,7 +22,7 @@ import { ToolCall } from "@langchain/core/messages/tool";
22
22
  import { KnowledgeBase } from "../knowledge";
23
23
  import { Tool } from "../tools/types";
24
24
  type LanguageModel = {
25
- invoke: (text: string) => Promise<InvocationResult>;
25
+ invoke: (text: string, isReentry?: boolean) => Promise<InvocationResult>;
26
26
  };
27
27
  type BaseModelParams = {
28
28
  firstMessage?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fonoster/autopilot",
3
- "version": "0.12.8",
3
+ "version": "0.12.10",
4
4
  "description": "Voice AI for the Fonoster platform",
5
5
  "author": "Pedro Sanders <psanders@fonoster.com>",
6
6
  "homepage": "https://github.com/fonoster/fonoster#readme",
@@ -59,5 +59,5 @@
59
59
  "xstate": "^5.17.3",
60
60
  "zod": "^3.23.8"
61
61
  },
62
- "gitHead": "9f8a2105d2b9d7b98bb32d9439e3314b747e1083"
62
+ "gitHead": "c716ab1782c9e0c4034585a162664b026035821b"
63
63
  }
@@ -1 +0,0 @@
1
- export declare const setSpeaking: import("xstate").ActionFunction<import("xstate").MachineContext, import("xstate").AnyEventObject, import("xstate").EventObject, {}, import("xstate").ProvidedActor, never, never, never, never>;
@@ -1,29 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.setSpeaking = void 0;
4
- /**
5
- * Copyright (C) 2025 by Fonoster Inc (https://fonoster.com)
6
- * http://github.com/fonoster/fonoster
7
- *
8
- * This file is part of Fonoster
9
- *
10
- * Licensed under the MIT License (the "License");
11
- * you may not use this file except in compliance with
12
- * the License. You may obtain a copy of the License at
13
- *
14
- * https://opensource.org/licenses/MIT
15
- *
16
- * Unless required by applicable law or agreed to in writing, software
17
- * distributed under the License is distributed on an "AS IS" BASIS,
18
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19
- * See the License for the specific language governing permissions and
20
- * limitations under the License.
21
- */
22
- const logger_1 = require("@fonoster/logger");
23
- const xstate_1 = require("xstate");
24
- const logger = (0, logger_1.getLogger)({ service: "autopilot", filePath: __filename });
25
- exports.setSpeaking = (0, xstate_1.assign)(({ context }) => {
26
- logger.verbose("called the setSpeaking action", { isSpeaking: true });
27
- context.isSpeaking = true;
28
- return context;
29
- });
@@ -1 +0,0 @@
1
- export declare const setSpeakingDone: import("xstate").ActionFunction<import("xstate").MachineContext, import("xstate").AnyEventObject, import("xstate").EventObject, {}, import("xstate").ProvidedActor, never, never, never, never>;
@@ -1,29 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.setSpeakingDone = void 0;
4
- /**
5
- * Copyright (C) 2025 by Fonoster Inc (https://fonoster.com)
6
- * http://github.com/fonoster/fonoster
7
- *
8
- * This file is part of Fonoster
9
- *
10
- * Licensed under the MIT License (the "License");
11
- * you may not use this file except in compliance with
12
- * the License. You may obtain a copy of the License at
13
- *
14
- * https://opensource.org/licenses/MIT
15
- *
16
- * Unless required by applicable law or agreed to in writing, software
17
- * distributed under the License is distributed on an "AS IS" BASIS,
18
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19
- * See the License for the specific language governing permissions and
20
- * limitations under the License.
21
- */
22
- const logger_1 = require("@fonoster/logger");
23
- const xstate_1 = require("xstate");
24
- const logger = (0, logger_1.getLogger)({ service: "autopilot", filePath: __filename });
25
- exports.setSpeakingDone = (0, xstate_1.assign)(({ context }) => {
26
- logger.verbose("called the setSpeakingDone action", { isSpeaking: false });
27
- context.isSpeaking = false;
28
- return context;
29
- });
@@ -1,4 +0,0 @@
1
- import { AutopilotContext } from "../types";
2
- export declare const isSpeaking: ({ context }: {
3
- context: AutopilotContext;
4
- }) => boolean;
@@ -1,30 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.isSpeaking = void 0;
4
- /**
5
- * Copyright (C) 2025 by Fonoster Inc (https://fonoster.com)
6
- * http://github.com/fonoster/fonoster
7
- *
8
- * This file is part of Fonoster
9
- *
10
- * Licensed under the MIT License (the "License");
11
- * you may not use this file except in compliance with
12
- * the License. You may obtain a copy of the License at
13
- *
14
- * https://opensource.org/licenses/MIT
15
- *
16
- * Unless required by applicable law or agreed to in writing, software
17
- * distributed under the License is distributed on an "AS IS" BASIS,
18
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19
- * See the License for the specific language governing permissions and
20
- * limitations under the License.
21
- */
22
- const logger_1 = require("@fonoster/logger");
23
- const logger = (0, logger_1.getLogger)({ service: "autopilot", filePath: __filename });
24
- const isSpeaking = ({ context }) => {
25
- logger.verbose("called the isSpeaking guard", {
26
- isSpeaking: context.isSpeaking
27
- });
28
- return context.isSpeaking;
29
- };
30
- exports.isSpeaking = isSpeaking;