@onereach/step-voice 4.0.48-missconv.9 → 5.0.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.
@@ -2,6 +2,7 @@ import { ITypedEvent } from '@onereach/flow-sdk/dst/types';
2
2
  import VoiceStep, { IVoiceCall, TODO, VoiceEvent } from './voice';
3
3
  interface INPUT {
4
4
  asr: TODO;
5
+ autoPause: boolean;
5
6
  processHangUp: 'user' | 'bot' | 'both' | 'none' | string;
6
7
  recordAfterTransfer: boolean;
7
8
  recordCall: boolean;
@@ -27,11 +28,13 @@ interface EVENT extends VoiceEvent {
27
28
  }
28
29
  export default class GlobalCommand extends VoiceStep<Partial<INPUT>, OUTPUT, EVENT> {
29
30
  get isGlobal(): boolean;
31
+ get useQueue(): boolean;
30
32
  runStep(): Promise<void>;
31
- initThread(): Promise<void>;
33
+ initGrammar(): Promise<void>;
32
34
  globThread(): Promise<void>;
33
35
  hangup(call: IVoiceCall): Promise<unknown>;
34
36
  exitThread(event: ITypedEvent<EVENT>, type: string, stepExit: string): Promise<void>;
37
+ onAwake(): Promise<void>;
35
38
  exitToThread(): void;
36
39
  buildGrammar(call: IVoiceCall, choices: TODO[]): Promise<any>;
37
40
  exitFlow(): unknown;
@@ -7,23 +7,27 @@ const nanoid_1 = require("nanoid");
7
7
  const voice_1 = tslib_1.__importDefault(require("./voice"));
8
8
  class GlobalCommand extends voice_1.default {
9
9
  get isGlobal() {
10
- return this.thread.id === `G:${this.dataThreadId}`;
10
+ return this.thread.id === `G:${this.conversationName}`;
11
+ }
12
+ get useQueue() {
13
+ return this.isGlobal;
11
14
  }
12
15
  async runStep() {
13
- const gcThreadId = `G:${this.dataThreadId}`;
14
- this._clearCache();
16
+ const gcThreadId = `G:${this.conversationName}`;
15
17
  await this.process.runThread({
16
18
  id: gcThreadId,
17
19
  state: {
18
- name: 'initThread',
20
+ name: 'globThread',
19
21
  step: this.step.id,
20
22
  thread: this.dataThreadId
21
23
  }
22
24
  });
23
- await this.fetchData();
25
+ // refresh cache after global thread was started
26
+ this._clearCache();
27
+ await this.initGrammar();
24
28
  this.exitStep('no commands');
25
29
  }
26
- async initThread() {
30
+ async initGrammar() {
27
31
  const call = await this.fetchData();
28
32
  const choices = this.buildChoices({ choices: this.data.choices });
29
33
  const grammar = await this.buildGrammar(call, choices);
@@ -33,6 +37,7 @@ class GlobalCommand extends voice_1.default {
33
37
  const recordSession = this.data.recordCall && !call.recordCall;
34
38
  if (recordSession) {
35
39
  call.recordCall = true;
40
+ await this.updateData();
36
41
  }
37
42
  // set grammar on voicer
38
43
  await this.sendCommands(call, [
@@ -51,16 +56,28 @@ class GlobalCommand extends voice_1.default {
51
56
  }
52
57
  }
53
58
  ]);
54
- await this.updateData();
55
- this.gotoState({ ...this.state, name: 'globThread' });
56
59
  }
57
60
  async globThread() {
58
61
  const call = await this.fetchData();
62
+ if (call.vv >= 1 && lodash_1.default.isFunction(this.triggers.hook)) {
63
+ this.triggers.hook({ name: 'ending', thread: 'main' }, async () => {
64
+ this.thread.background = call._conv.glb.length > 1;
65
+ this.log.debug('gc set background', { bg: this.thread.background, len: call._conv.glb.length });
66
+ });
67
+ this.triggers.hook({ name: 'end', thread: 'main' }, async () => {
68
+ this.log.debug('gc terminate', { bg: this.thread.background });
69
+ if (await this.popConvStep()) {
70
+ await this.sendEventToStep({ toGlobal: this.isGlobal, action: 'onAwake' });
71
+ }
72
+ this.end();
73
+ });
74
+ }
59
75
  this.triggers.once(`in/voice/${call.id}/event`, async (event) => {
60
76
  event.processed = undefined;
61
77
  switch (event.params.type) {
62
78
  case 'hangup': {
63
79
  await this.hangup(call);
80
+ await this.popConvStep();
64
81
  await this.notifyConvEnd();
65
82
  return this.end();
66
83
  }
@@ -110,8 +127,14 @@ class GlobalCommand extends voice_1.default {
110
127
  }
111
128
  return {};
112
129
  }
113
- case 'error':
114
- return this.throwError(event.params.error);
130
+ case 'error': {
131
+ const localHandler = await this.sendEventToStep({ toGlobal: false, event: { ...this.event, processed: undefined, action: 'local' } });
132
+ if (!localHandler) {
133
+ this.throwError(event.params.error);
134
+ }
135
+ this.event.processed = true;
136
+ break;
137
+ }
115
138
  default:
116
139
  return {};
117
140
  }
@@ -165,6 +188,10 @@ class GlobalCommand extends voice_1.default {
165
188
  if (!lodash_1.default.isEmpty(params.callRecording)) {
166
189
  result.callRecording = params.callRecording;
167
190
  }
191
+ if (!this.data.autoPause) {
192
+ await this.notifyConvEnd({ onlyLocal: true });
193
+ await this._refreshCache();
194
+ }
168
195
  const exitLabel = lodash_1.default.replace(this.getExitStepLabel(stepExit) ?? stepExit, /\W+/g, '');
169
196
  await this.process.runThread({
170
197
  id: `${exitLabel}_${(0, nanoid_1.nanoid)(8)}`,
@@ -184,6 +211,12 @@ class GlobalCommand extends voice_1.default {
184
211
  // this is required to mark event as handled
185
212
  // this.gotoState(this.state, { name: 'exiting' })
186
213
  }
214
+ async onAwake() {
215
+ await super.onAwake();
216
+ await this.initGrammar();
217
+ await this.globThread();
218
+ this.triggers.refreshAll();
219
+ }
187
220
  exitToThread() {
188
221
  this.thread.exitStep(this.state.exitStep, this.state.result);
189
222
  }
@@ -31,8 +31,8 @@ class InitiateCall extends voice_1.default {
31
31
  }
32
32
  });
33
33
  }
34
- const convData = { id: callId, type: 'voicer' };
35
- await this.startConversation(convData, { events: {} });
34
+ const convData = { id: callId, type: 'voicer', vv: 0 };
35
+ await this.startConversation(convData);
36
36
  await this.pushConvStep();
37
37
  this.gotoState('waitForCall');
38
38
  }
@@ -76,9 +76,7 @@ class InitiateCall extends voice_1.default {
76
76
  };
77
77
  delete call.from;
78
78
  delete call.to;
79
- await this.startConversation(call, {
80
- events: { [`in/voice/${call.id}/event`]: {} }
81
- });
79
+ await this.startConversation(call);
82
80
  await this.transcript(call, {
83
81
  action: 'Call Start',
84
82
  reportingSettingsKey: 'transcript',
@@ -22,13 +22,10 @@ interface INPUT {
22
22
  recognitionModel?: string;
23
23
  }
24
24
  interface EVENT extends VoiceEvent {
25
- callRecording?: TODO;
26
25
  exitId?: string;
27
- digit?: string;
28
26
  digits?: string;
29
27
  phrases?: TODO[];
30
28
  tags?: string[];
31
- out?: string;
32
29
  }
33
30
  export default class KeypadInput extends VoiceStep<INPUT, {}, EVENT> {
34
31
  runStep(): Promise<void>;
@@ -10,7 +10,6 @@ interface INPUT {
10
10
  muteUser: boolean;
11
11
  muteBot: boolean;
12
12
  };
13
- interSpeechTimeout: number;
14
13
  }
15
14
  export default class SayMessage extends VoiceStep<Partial<INPUT>> {
16
15
  runStep(): Promise<void>;
@@ -6,7 +6,7 @@ const lodash_1 = tslib_1.__importDefault(require("lodash"));
6
6
  const voice_1 = tslib_1.__importDefault(require("./voice"));
7
7
  class SayMessage extends voice_1.default {
8
8
  async runStep() {
9
- const { audio, textType, tts, sensitiveData, interSpeechTimeout = 0 } = this.data;
9
+ const { audio, textType, tts, sensitiveData } = this.data;
10
10
  const call = await this.fetchData();
11
11
  this.triggers.once(`in/voice/${call.id}/event`, async (event) => {
12
12
  if (call.recordCall && sensitiveData?.muteStep) {
@@ -39,8 +39,6 @@ class SayMessage extends voice_1.default {
39
39
  const command = {
40
40
  name: 'speak',
41
41
  params: {
42
- dictation: true,
43
- interSpeechTimeout: interSpeechTimeout * 1000,
44
42
  sections: lodash_1.default.map(audio, (section) => ({
45
43
  text: section.voiceTextMsg,
46
44
  url: section.audioUrl,
@@ -44,9 +44,7 @@ class WaitForCall extends voice_1.default {
44
44
  sessionType: 'Phone'
45
45
  }
46
46
  });
47
- await this.startConversation(call, {
48
- events: { [`in/voice/${call.id}/event`]: {} }
49
- });
47
+ await this.startConversation(call);
50
48
  await this.transcript(call, {
51
49
  action: 'Call Start',
52
50
  reportingSettingsKey: 'transcript',
package/dst/step.d.ts CHANGED
@@ -1,21 +1,22 @@
1
1
  import Step from '@onereach/flow-sdk/dst/step';
2
- import { IThreadId, IEvent, IStepResult, IMergeField, IMergeFieldKey } from '@onereach/flow-sdk/dst/types';
2
+ import { IThreadId, IEvent, IStepResult, IMergeField } from '@onereach/flow-sdk/dst/types';
3
3
  import { IConversationStep } from '@onereach/step-conversation/dst/types';
4
4
  export interface ConvStepDataIn {
5
- conversation?: IMergeFieldKey | IMergeField;
5
+ conversation?: string | IMergeField;
6
6
  conversationThread?: string;
7
7
  }
8
8
  interface IConversation {
9
9
  glb: IConversationStep[];
10
10
  lcl: IConversationStep[];
11
- trg: Record<string, {}>;
12
11
  }
13
12
  interface IConversationData {
14
13
  _conv: IConversation;
15
14
  }
16
15
  export default class ConvStep<TData = unknown, TIn = unknown, TOut = unknown, TParams = any> extends Step<TIn & ConvStepDataIn, TOut, TParams> {
17
- convDataCache?: TData & IConversationData;
18
- get conversation(): IMergeFieldKey | IMergeField;
16
+ private convDataCache?;
17
+ get cache(): TData & IConversationData | undefined;
18
+ get conversation(): string | IMergeField;
19
+ get conversationName(): string;
19
20
  /** id of the thread where conversation data is stored */
20
21
  get dataThreadId(): IThreadId;
21
22
  get isGlobal(): boolean;
@@ -29,20 +30,22 @@ export default class ConvStep<TData = unknown, TIn = unknown, TOut = unknown, TP
29
30
  hasActiveGlobal(): Promise<boolean>;
30
31
  setLocalTriggers(): Promise<void>;
31
32
  waitForConversation(): Promise<void>;
32
- onSkipEvent(): Promise<void>;
33
+ protected onSkipEvent(): Promise<void>;
33
34
  cancel(): Promise<void>;
34
- onCancel(): Promise<void>;
35
+ protected onCancel(): Promise<void>;
35
36
  onSleep(): Promise<void>;
36
37
  onAwake(): Promise<void>;
37
38
  onPause(): Promise<void>;
38
39
  onResume(): Promise<void>;
39
- notifyConvEnd(): Promise<void>;
40
+ notifyConvEnd({ onlyLocal }?: {
41
+ onlyLocal?: boolean;
42
+ }): Promise<void>;
40
43
  waitConvEnd(): Promise<void>;
41
44
  waitGlobEnd(): Promise<void>;
42
45
  onConvEnd(): Promise<void>;
43
46
  pause(): Promise<void>;
44
47
  resume(): Promise<void>;
45
- sendEventToStep({ toGlobal, toStep, action, event }: {
48
+ protected sendEventToStep({ toGlobal, toStep, action, event }: {
46
49
  action?: string;
47
50
  event?: IEvent;
48
51
  toStep?: IConversationStep;
@@ -50,14 +53,14 @@ export default class ConvStep<TData = unknown, TIn = unknown, TOut = unknown, TP
50
53
  }): Promise<IStepResult | undefined>;
51
54
  activeStep(isGlobal?: boolean): Promise<IConversationStep | undefined>;
52
55
  /** @returns true if current conv is active */
53
- pushConvStep(): Promise<boolean>;
56
+ protected pushConvStep(): Promise<boolean>;
54
57
  /** @returns true if current conv was active */
55
- popConvStep(popStep?: IConversationStep): Promise<boolean>;
56
- startConversation(data: TData, { events }: {
57
- events: Record<string, {}>;
58
+ protected popConvStep(popStep?: IConversationStep): Promise<boolean>;
59
+ startConversation(data: TData, { thread }?: {
58
60
  thread?: IThreadId;
59
61
  }): Promise<void>;
60
- _fetchData(): Promise<(TData & IConversationData) | undefined>;
61
- _clearCache(): void;
62
+ protected _fetchCache(): Promise<(TData & IConversationData) | undefined>;
63
+ protected _clearCache(): void;
64
+ protected _refreshCache(): Promise<void>;
62
65
  }
63
66
  export {};
package/dst/step.js CHANGED
@@ -4,11 +4,27 @@ Object.defineProperty(exports, "__esModule", { value: true });
4
4
  const tslib_1 = require("tslib");
5
5
  const step_1 = tslib_1.__importDefault(require("@onereach/flow-sdk/dst/step"));
6
6
  class ConvStep extends step_1.default {
7
+ get cache() {
8
+ return this.convDataCache;
9
+ }
7
10
  get conversation() {
8
11
  if (this.data.conversation == null)
9
12
  throw new Error('missing data.conversation');
10
13
  return this.data.conversation;
11
14
  }
15
+ get conversationName() {
16
+ const convOrName = this.conversation;
17
+ const conv = (typeof convOrName === 'string')
18
+ ? this.config.mergeFields[convOrName] ?? convOrName
19
+ : convOrName;
20
+ if (conv.type === 'thread')
21
+ return this.dataThreadId;
22
+ if (typeof conv.name === 'string')
23
+ return conv.name;
24
+ if (typeof convOrName === 'string')
25
+ return convOrName;
26
+ throw new Error(`invalid conversation ${JSON.stringify(conv)}`);
27
+ }
12
28
  /** id of the thread where conversation data is stored */
13
29
  get dataThreadId() {
14
30
  const dataThreadId = this.state.thread ?? this.data.conversationThread;
@@ -21,7 +37,7 @@ class ConvStep extends step_1.default {
21
37
  return true;
22
38
  }
23
39
  async fetchData() {
24
- const data = await this._fetchData();
40
+ const data = await this._fetchCache();
25
41
  if (data?._conv == null)
26
42
  throw new Error(`missing conversation data in state ${this.state.name}`);
27
43
  return data;
@@ -37,7 +53,7 @@ class ConvStep extends step_1.default {
37
53
  await convDataThread.set(this.conversation, this.convDataCache);
38
54
  }
39
55
  async hasConversation() {
40
- return (await this._fetchData())?._conv != null;
56
+ return (await this._fetchCache())?._conv != null;
41
57
  }
42
58
  async runBefore() {
43
59
  await super.runBefore();
@@ -60,12 +76,12 @@ class ConvStep extends step_1.default {
60
76
  if (this.useQueue && !await this.pushConvStep()) {
61
77
  return await this.waitForConversation();
62
78
  }
63
- if (this.event.action == null && !this.event.processed && this.convDataCache?._conv?.trg?.[this.event.name] != null) {
79
+ if (this.event.action == null && !this.event.processed) {
64
80
  if (this.isGlobal)
65
81
  return;
66
82
  const hasGlobal = await this.sendEventToStep({ event: { ...this.event, action: 'global' }, toGlobal: true }); // redirect to global (1)
67
- this.log.debug('conv.sendEventToStep toGlobal', { hasGlobal });
68
83
  if (hasGlobal != null) {
84
+ this.log.debug('conv.sendEventToStep toGlobal', { hasGlobal });
69
85
  this.state.prevSkipName = this.state.name;
70
86
  this.state.name = 'onSkipEvent';
71
87
  return;
@@ -77,6 +93,8 @@ class ConvStep extends step_1.default {
77
93
  await this.setLocalTriggers();
78
94
  }
79
95
  async runAfter() {
96
+ if (this.thread.ending)
97
+ return;
80
98
  if (!await this.hasConversation())
81
99
  return await super.runAfter();
82
100
  if (this.thread.hasControlAction()) {
@@ -142,6 +160,11 @@ class ConvStep extends step_1.default {
142
160
  async onAwake() {
143
161
  this.state.name = this.state.resumeState;
144
162
  this.state.resumeState = undefined;
163
+ const conv = await this.getConversation();
164
+ if (this.isGlobal && conv.glb.length === 1) {
165
+ this.log.debug('make global thread foreground');
166
+ this.thread.background = false;
167
+ }
145
168
  }
146
169
  async onPause() {
147
170
  this.state.resumeState = this.state.prevName;
@@ -151,11 +174,10 @@ class ConvStep extends step_1.default {
151
174
  this.state.name = this.state.resumeState;
152
175
  this.state.resumeState = undefined;
153
176
  }
154
- async notifyConvEnd() {
155
- await this.popConvStep();
177
+ async notifyConvEnd({ onlyLocal } = {}) {
156
178
  const conv = await this.getConversation();
157
179
  const lcl = conv.lcl.splice(0);
158
- const glb = conv.glb.splice(0);
180
+ const glb = onlyLocal ? [] : conv.glb.splice(0);
159
181
  if (lcl.length > 0 || glb.length > 0) {
160
182
  await this.updateData();
161
183
  for (const step of lcl) {
@@ -264,20 +286,18 @@ class ConvStep extends step_1.default {
264
286
  // if (updated) await this.updateData()
265
287
  return false;
266
288
  }
267
- async startConversation(data, { events }) {
289
+ async startConversation(data, { thread: _thread } = {}) {
268
290
  this.convDataCache = {
269
291
  ...data,
270
292
  _conv: {
271
- trg: events,
272
293
  glb: [],
273
- // stk: [],
274
294
  lcl: []
275
295
  }
276
296
  };
277
297
  await this.updateData();
278
298
  // this.state.thread = this.dataThreadId
279
299
  }
280
- async _fetchData() {
300
+ async _fetchCache() {
281
301
  if (this.convDataCache != null)
282
302
  return this.convDataCache;
283
303
  const convDataThread = this.process.getSafeThread(this.dataThreadId);
@@ -288,5 +308,9 @@ class ConvStep extends step_1.default {
288
308
  _clearCache() {
289
309
  this.convDataCache = undefined;
290
310
  }
311
+ async _refreshCache() {
312
+ this._clearCache();
313
+ await this._fetchCache();
314
+ }
291
315
  }
292
316
  exports.default = ConvStep;
package/dst/voice.d.ts CHANGED
@@ -36,7 +36,7 @@ export default class VoiceStep<TIn = unknown, TOut = unknown, TParams = VoiceEve
36
36
  }, TOut, TParams> {
37
37
  runBefore(): Promise<void>;
38
38
  sendCommands({ id, type, callback }: IVoiceCall, commands: TODO[]): Promise<unknown>;
39
- handleHeartbeat(call: IVoiceCall): void;
39
+ handleHeartbeat(call: IVoiceCall): Promise<void>;
40
40
  extractSectionMessages(sections: IPromtpSection[]): string;
41
41
  extractSectionFiles(sections: IPromtpSection[]): Array<{
42
42
  fileUrl: string;
package/dst/voice.js CHANGED
@@ -15,8 +15,8 @@ class VoiceStep extends step_1.default {
15
15
  // static Error = VoiceStepError
16
16
  async runBefore() {
17
17
  await super.runBefore();
18
- if (this.convDataCache != null)
19
- this.handleHeartbeat(this.convDataCache);
18
+ if (this.cache != null)
19
+ await this.handleHeartbeat(this.cache);
20
20
  }
21
21
  async sendCommands({ id, type, callback }, commands) {
22
22
  if (lodash_1.default.isEmpty(commands))
@@ -27,7 +27,8 @@ class VoiceStep extends step_1.default {
27
27
  params: {
28
28
  id,
29
29
  cancel: this.isGlobal ? undefined : true,
30
- hbs: 60,
30
+ async: this.isGlobal ? true : undefined,
31
+ hbs: 99,
31
32
  commands
32
33
  },
33
34
  reporting: this.session.getSessionRef()
@@ -37,17 +38,18 @@ class VoiceStep extends step_1.default {
37
38
  timeout: 5000
38
39
  });
39
40
  }
40
- handleHeartbeat(call) {
41
+ async handleHeartbeat(call) {
41
42
  if (call.vv >= 1) {
42
- const expectHearbeatBefore = Date.now() + 120000;
43
+ const expectHearbeatBefore = Date.now() + 290000;
43
44
  this.triggers.deadline(expectHearbeatBefore, () => {
44
45
  this.end();
45
46
  this.throwError(new Error('missing heartbeat, call is probably already hangup'));
46
47
  });
47
48
  }
48
- else {
49
- // refresh timeout?
50
- this.log.warn('voicer is older than expected, please consider voicer upgrade');
49
+ else if (call.vv !== 0) {
50
+ this.log.debug('voicer is older than expected, please consider voicer upgrade', { version: call.vv });
51
+ call.vv = 0;
52
+ await this.updateData();
51
53
  }
52
54
  }
53
55
  extractSectionMessages(sections) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onereach/step-voice",
3
- "version": "4.0.48-missconv.9",
3
+ "version": "5.0.0",
4
4
  "author": "Roman Zolotarov <roman.zolotarov@onereach.com>",
5
5
  "contributors": [
6
6
  "Roman Zolotarov",
@@ -19,22 +19,22 @@
19
19
  "uuid": "^9.0.0"
20
20
  },
21
21
  "devDependencies": {
22
- "@onereach/flow-sdk": "^3.1.83",
23
- "@swc/cli": "^0.1.59",
24
- "@swc/core": "^1.3.28",
22
+ "@onereach/flow-sdk": "^3.1.101",
23
+ "@swc/cli": "^0.1.60",
24
+ "@swc/core": "^1.3.32",
25
25
  "@swc/jest": "^0.2.24",
26
26
  "@types/jest": "^29.4.0",
27
27
  "@types/lodash": "^4.14.191",
28
28
  "@types/timestring": "^6.0.2",
29
29
  "@types/uuid": "^9.0.0",
30
- "aws-sdk": "^2.1301.0",
30
+ "aws-sdk": "^2.1306.0",
31
31
  "babel-runtime": "^6.26.0",
32
32
  "docdash": "^2.0.1",
33
33
  "husky": "^8.0.3",
34
- "jest": "^29.4.0",
34
+ "jest": "^29.4.1",
35
35
  "pinst": "^3.0.0",
36
36
  "ts-standard": "^12.0.2",
37
- "typescript": "^4.9.4"
37
+ "typescript": "^4.9.5"
38
38
  },
39
39
  "files": [
40
40
  "/dst"
@@ -78,4 +78,4 @@
78
78
  "node_modules"
79
79
  ]
80
80
  }
81
- }
81
+ }