@absolutejs/voice 0.0.22-beta.622 → 0.0.22-beta.623
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.
- package/dist/core/types.d.ts +21 -0
- package/dist/index.js +54 -0
- package/dist/testing/index.js +53 -0
- package/package.json +1 -1
package/dist/core/types.d.ts
CHANGED
|
@@ -803,6 +803,13 @@ export type VoicePluginConfig<TContext = unknown, TSession extends VoiceSessionR
|
|
|
803
803
|
sttRecoveryLine?: string | ((input: {
|
|
804
804
|
session: TSession;
|
|
805
805
|
}) => string | Promise<string>);
|
|
806
|
+
stuckCallClose?: {
|
|
807
|
+
afterMs: number;
|
|
808
|
+
line?: string | ((input: {
|
|
809
|
+
session: TSession;
|
|
810
|
+
}) => string | Promise<string>);
|
|
811
|
+
reason?: string;
|
|
812
|
+
};
|
|
806
813
|
languageStrategy?: VoiceLanguageStrategy;
|
|
807
814
|
lexicon?: VoiceLexiconEntry[] | VoiceLexiconResolver<TContext>;
|
|
808
815
|
phraseHints?: VoicePhraseHint[] | VoicePhraseHintResolver<TContext>;
|
|
@@ -958,6 +965,20 @@ export type CreateVoiceSessionOptions<TContext = unknown, TSession extends Voice
|
|
|
958
965
|
sttRecoveryLine?: string | ((input: {
|
|
959
966
|
session: TSession;
|
|
960
967
|
}) => string | Promise<string>);
|
|
968
|
+
/** Last-resort GRACEFUL terminal close for a wedged call. If no caller-side
|
|
969
|
+
* progress (committed turn / user partial) lands for `afterMs` on a live call
|
|
970
|
+
* — STT permanently deaf, or the caller left — the assistant speaks `line` and
|
|
971
|
+
* the session COMPLETES (disposition "completed") so onComplete still saves and
|
|
972
|
+
* the call ends with a real goodbye instead of dead air + "abandoned". Reset by
|
|
973
|
+
* real progress (committed turn / user partial / (re)connect), NOT by the
|
|
974
|
+
* assistant's own speech, so STT recovery re-prompts can't defer it forever. */
|
|
975
|
+
stuckCallClose?: {
|
|
976
|
+
afterMs: number;
|
|
977
|
+
line?: string | ((input: {
|
|
978
|
+
session: TSession;
|
|
979
|
+
}) => string | Promise<string>);
|
|
980
|
+
reason?: string;
|
|
981
|
+
};
|
|
961
982
|
stt?: STTAdapter;
|
|
962
983
|
realtime?: RealtimeAdapter;
|
|
963
984
|
realtimeInputFormat?: AudioFormat;
|
package/dist/index.js
CHANGED
|
@@ -4186,6 +4186,51 @@ var createVoiceSession = (options) => {
|
|
|
4186
4186
|
clearCallSilenceWatchdog();
|
|
4187
4187
|
callSilenceWatchdog = setTimeout(fireCallSilenceTimeout, callSilenceTimeoutMs);
|
|
4188
4188
|
};
|
|
4189
|
+
const stuckCloseConfig = options.stuckCallClose;
|
|
4190
|
+
const stuckCloseAfterMs = stuckCloseConfig && stuckCloseConfig.afterMs > 0 ? stuckCloseConfig.afterMs : undefined;
|
|
4191
|
+
let stuckCloseWatchdog = null;
|
|
4192
|
+
let stuckCloseFired = false;
|
|
4193
|
+
const clearStuckCloseWatchdog = () => {
|
|
4194
|
+
if (stuckCloseWatchdog) {
|
|
4195
|
+
clearTimeout(stuckCloseWatchdog);
|
|
4196
|
+
stuckCloseWatchdog = null;
|
|
4197
|
+
}
|
|
4198
|
+
};
|
|
4199
|
+
const fireStuckClose = () => {
|
|
4200
|
+
stuckCloseWatchdog = null;
|
|
4201
|
+
if (stuckCloseFired) {
|
|
4202
|
+
return;
|
|
4203
|
+
}
|
|
4204
|
+
stuckCloseFired = true;
|
|
4205
|
+
runSerial("stuck-call-close", async () => {
|
|
4206
|
+
const snapshot = await readSession();
|
|
4207
|
+
if (snapshot.status === "completed" || snapshot.status === "failed" || snapshot.call?.endedAt) {
|
|
4208
|
+
return;
|
|
4209
|
+
}
|
|
4210
|
+
await appendTrace({
|
|
4211
|
+
payload: {
|
|
4212
|
+
action: "stuck-call-close",
|
|
4213
|
+
reason: `no caller progress for ${stuckCloseAfterMs}ms`
|
|
4214
|
+
},
|
|
4215
|
+
session: snapshot,
|
|
4216
|
+
type: "session.error"
|
|
4217
|
+
});
|
|
4218
|
+
if (stuckCloseConfig?.line) {
|
|
4219
|
+
await speakResolvedLine(stuckCloseConfig.line, snapshot);
|
|
4220
|
+
}
|
|
4221
|
+
await completeInternal(undefined, {
|
|
4222
|
+
disposition: "completed",
|
|
4223
|
+
reason: stuckCloseConfig?.reason ?? "stuck-call-close"
|
|
4224
|
+
});
|
|
4225
|
+
});
|
|
4226
|
+
};
|
|
4227
|
+
const kickStuckCloseWatchdog = () => {
|
|
4228
|
+
if (stuckCloseAfterMs === undefined || stuckCloseFired) {
|
|
4229
|
+
return;
|
|
4230
|
+
}
|
|
4231
|
+
clearStuckCloseWatchdog();
|
|
4232
|
+
stuckCloseWatchdog = setTimeout(fireStuckClose, stuckCloseAfterMs);
|
|
4233
|
+
};
|
|
4189
4234
|
const recordingConfig = options.recording;
|
|
4190
4235
|
const recordingChannels = new Set(recordingConfig?.channels ?? ["assistant", "user"]);
|
|
4191
4236
|
const recordingMaxBytes = recordingConfig?.maxBytesPerChannel ?? 50 * 1024 * 1024;
|
|
@@ -4682,6 +4727,7 @@ var createVoiceSession = (options) => {
|
|
|
4682
4727
|
type: "error"
|
|
4683
4728
|
});
|
|
4684
4729
|
clearCallSilenceWatchdog();
|
|
4730
|
+
clearStuckCloseWatchdog();
|
|
4685
4731
|
clearAmdEvaluationTimer();
|
|
4686
4732
|
await closeTTSSession("failed");
|
|
4687
4733
|
await closeAdapter("failed");
|
|
@@ -4791,6 +4837,7 @@ var createVoiceSession = (options) => {
|
|
|
4791
4837
|
type: "complete"
|
|
4792
4838
|
});
|
|
4793
4839
|
clearCallSilenceWatchdog();
|
|
4840
|
+
clearStuckCloseWatchdog();
|
|
4794
4841
|
clearAmdEvaluationTimer();
|
|
4795
4842
|
await closeTTSSession("complete");
|
|
4796
4843
|
await closeAdapter("complete");
|
|
@@ -5268,6 +5315,9 @@ var createVoiceSession = (options) => {
|
|
|
5268
5315
|
};
|
|
5269
5316
|
};
|
|
5270
5317
|
const handlePartial = async (transcript) => {
|
|
5318
|
+
if (transcript.text.trim()) {
|
|
5319
|
+
kickStuckCloseWatchdog();
|
|
5320
|
+
}
|
|
5271
5321
|
if (activeTTSTurnId !== undefined) {
|
|
5272
5322
|
const triggeringText = transcript.text.trim();
|
|
5273
5323
|
if (triggeringText) {
|
|
@@ -5742,6 +5792,7 @@ var createVoiceSession = (options) => {
|
|
|
5742
5792
|
};
|
|
5743
5793
|
const completeTurn = async (session, turn) => {
|
|
5744
5794
|
console.error(`[voice] completeTurn ENTER session=${options.id} turn=${turn.id} textLen=${turn.text?.length ?? 0}`);
|
|
5795
|
+
kickStuckCloseWatchdog();
|
|
5745
5796
|
const liveOpsControl = await options.liveOps?.getControl(options.id);
|
|
5746
5797
|
if (liveOpsControl?.assistantPaused || liveOpsControl?.operatorTakeover) {
|
|
5747
5798
|
await appendTrace({
|
|
@@ -6443,6 +6494,7 @@ var createVoiceSession = (options) => {
|
|
|
6443
6494
|
await ensureAdapter();
|
|
6444
6495
|
warmTTSSession();
|
|
6445
6496
|
kickCallSilenceWatchdog();
|
|
6497
|
+
kickStuckCloseWatchdog();
|
|
6446
6498
|
startAmdEvaluationTimer();
|
|
6447
6499
|
if (options.greeting && session.turns.length === 0) {
|
|
6448
6500
|
await speakResolvedLine(options.greeting, session);
|
|
@@ -6588,6 +6640,7 @@ var createVoiceSession = (options) => {
|
|
|
6588
6640
|
});
|
|
6589
6641
|
clearSilenceTimer();
|
|
6590
6642
|
clearCallSilenceWatchdog();
|
|
6643
|
+
clearStuckCloseWatchdog();
|
|
6591
6644
|
clearAmdEvaluationTimer();
|
|
6592
6645
|
if (options.noiseSuppressor?.close) {
|
|
6593
6646
|
try {
|
|
@@ -39705,6 +39758,7 @@ var voice = (config) => {
|
|
|
39705
39758
|
greeting: config.greeting,
|
|
39706
39759
|
resumeGreeting: config.resumeGreeting,
|
|
39707
39760
|
sttRecoveryLine: config.sttRecoveryLine,
|
|
39761
|
+
stuckCallClose: config.stuckCallClose,
|
|
39708
39762
|
handoff: config.handoff,
|
|
39709
39763
|
languageStrategy: config.languageStrategy,
|
|
39710
39764
|
lexicon,
|
package/dist/testing/index.js
CHANGED
|
@@ -6506,6 +6506,51 @@ var createVoiceSession = (options) => {
|
|
|
6506
6506
|
clearCallSilenceWatchdog();
|
|
6507
6507
|
callSilenceWatchdog = setTimeout(fireCallSilenceTimeout, callSilenceTimeoutMs);
|
|
6508
6508
|
};
|
|
6509
|
+
const stuckCloseConfig = options.stuckCallClose;
|
|
6510
|
+
const stuckCloseAfterMs = stuckCloseConfig && stuckCloseConfig.afterMs > 0 ? stuckCloseConfig.afterMs : undefined;
|
|
6511
|
+
let stuckCloseWatchdog = null;
|
|
6512
|
+
let stuckCloseFired = false;
|
|
6513
|
+
const clearStuckCloseWatchdog = () => {
|
|
6514
|
+
if (stuckCloseWatchdog) {
|
|
6515
|
+
clearTimeout(stuckCloseWatchdog);
|
|
6516
|
+
stuckCloseWatchdog = null;
|
|
6517
|
+
}
|
|
6518
|
+
};
|
|
6519
|
+
const fireStuckClose = () => {
|
|
6520
|
+
stuckCloseWatchdog = null;
|
|
6521
|
+
if (stuckCloseFired) {
|
|
6522
|
+
return;
|
|
6523
|
+
}
|
|
6524
|
+
stuckCloseFired = true;
|
|
6525
|
+
runSerial("stuck-call-close", async () => {
|
|
6526
|
+
const snapshot = await readSession();
|
|
6527
|
+
if (snapshot.status === "completed" || snapshot.status === "failed" || snapshot.call?.endedAt) {
|
|
6528
|
+
return;
|
|
6529
|
+
}
|
|
6530
|
+
await appendTrace({
|
|
6531
|
+
payload: {
|
|
6532
|
+
action: "stuck-call-close",
|
|
6533
|
+
reason: `no caller progress for ${stuckCloseAfterMs}ms`
|
|
6534
|
+
},
|
|
6535
|
+
session: snapshot,
|
|
6536
|
+
type: "session.error"
|
|
6537
|
+
});
|
|
6538
|
+
if (stuckCloseConfig?.line) {
|
|
6539
|
+
await speakResolvedLine(stuckCloseConfig.line, snapshot);
|
|
6540
|
+
}
|
|
6541
|
+
await completeInternal(undefined, {
|
|
6542
|
+
disposition: "completed",
|
|
6543
|
+
reason: stuckCloseConfig?.reason ?? "stuck-call-close"
|
|
6544
|
+
});
|
|
6545
|
+
});
|
|
6546
|
+
};
|
|
6547
|
+
const kickStuckCloseWatchdog = () => {
|
|
6548
|
+
if (stuckCloseAfterMs === undefined || stuckCloseFired) {
|
|
6549
|
+
return;
|
|
6550
|
+
}
|
|
6551
|
+
clearStuckCloseWatchdog();
|
|
6552
|
+
stuckCloseWatchdog = setTimeout(fireStuckClose, stuckCloseAfterMs);
|
|
6553
|
+
};
|
|
6509
6554
|
const recordingConfig = options.recording;
|
|
6510
6555
|
const recordingChannels = new Set(recordingConfig?.channels ?? ["assistant", "user"]);
|
|
6511
6556
|
const recordingMaxBytes = recordingConfig?.maxBytesPerChannel ?? 50 * 1024 * 1024;
|
|
@@ -7002,6 +7047,7 @@ var createVoiceSession = (options) => {
|
|
|
7002
7047
|
type: "error"
|
|
7003
7048
|
});
|
|
7004
7049
|
clearCallSilenceWatchdog();
|
|
7050
|
+
clearStuckCloseWatchdog();
|
|
7005
7051
|
clearAmdEvaluationTimer();
|
|
7006
7052
|
await closeTTSSession("failed");
|
|
7007
7053
|
await closeAdapter("failed");
|
|
@@ -7111,6 +7157,7 @@ var createVoiceSession = (options) => {
|
|
|
7111
7157
|
type: "complete"
|
|
7112
7158
|
});
|
|
7113
7159
|
clearCallSilenceWatchdog();
|
|
7160
|
+
clearStuckCloseWatchdog();
|
|
7114
7161
|
clearAmdEvaluationTimer();
|
|
7115
7162
|
await closeTTSSession("complete");
|
|
7116
7163
|
await closeAdapter("complete");
|
|
@@ -7588,6 +7635,9 @@ var createVoiceSession = (options) => {
|
|
|
7588
7635
|
};
|
|
7589
7636
|
};
|
|
7590
7637
|
const handlePartial = async (transcript) => {
|
|
7638
|
+
if (transcript.text.trim()) {
|
|
7639
|
+
kickStuckCloseWatchdog();
|
|
7640
|
+
}
|
|
7591
7641
|
if (activeTTSTurnId !== undefined) {
|
|
7592
7642
|
const triggeringText = transcript.text.trim();
|
|
7593
7643
|
if (triggeringText) {
|
|
@@ -8062,6 +8112,7 @@ var createVoiceSession = (options) => {
|
|
|
8062
8112
|
};
|
|
8063
8113
|
const completeTurn = async (session, turn) => {
|
|
8064
8114
|
console.error(`[voice] completeTurn ENTER session=${options.id} turn=${turn.id} textLen=${turn.text?.length ?? 0}`);
|
|
8115
|
+
kickStuckCloseWatchdog();
|
|
8065
8116
|
const liveOpsControl = await options.liveOps?.getControl(options.id);
|
|
8066
8117
|
if (liveOpsControl?.assistantPaused || liveOpsControl?.operatorTakeover) {
|
|
8067
8118
|
await appendTrace({
|
|
@@ -8763,6 +8814,7 @@ var createVoiceSession = (options) => {
|
|
|
8763
8814
|
await ensureAdapter();
|
|
8764
8815
|
warmTTSSession();
|
|
8765
8816
|
kickCallSilenceWatchdog();
|
|
8817
|
+
kickStuckCloseWatchdog();
|
|
8766
8818
|
startAmdEvaluationTimer();
|
|
8767
8819
|
if (options.greeting && session.turns.length === 0) {
|
|
8768
8820
|
await speakResolvedLine(options.greeting, session);
|
|
@@ -8908,6 +8960,7 @@ var createVoiceSession = (options) => {
|
|
|
8908
8960
|
});
|
|
8909
8961
|
clearSilenceTimer();
|
|
8910
8962
|
clearCallSilenceWatchdog();
|
|
8963
|
+
clearStuckCloseWatchdog();
|
|
8911
8964
|
clearAmdEvaluationTimer();
|
|
8912
8965
|
if (options.noiseSuppressor?.close) {
|
|
8913
8966
|
try {
|