@absolutejs/voice 0.0.22-beta.631 → 0.0.22-beta.633

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.
@@ -522,6 +522,17 @@ export type VoiceSessionHandle<TContext = unknown, TSession extends VoiceSession
522
522
  transferMode?: "cold" | "warm";
523
523
  }) => Promise<void>;
524
524
  close: (reason?: string) => Promise<void>;
525
+ /**
526
+ * Caller-driven in-call pause: the session stays LIVE (socket, state,
527
+ * transcript) but every idle/silence watchdog is suspended and turn
528
+ * processing is skipped, so a paused caller is never nudged or hung up on.
529
+ * Past `pause.maxMs` (default 10 min) the session closes gracefully with
530
+ * reason "pause-timeout" — completed turns stay durably persisted, so the
531
+ * host's normal draft/resume path takes over.
532
+ */
533
+ pause: () => Promise<void>;
534
+ /** Lift a caller-driven pause: watchdogs re-arm and turns process again. */
535
+ resume: () => Promise<void>;
525
536
  snapshot: () => Promise<TSession>;
526
537
  /**
527
538
  * Mutate the live turn-detection config for this session — useful when a
@@ -1016,6 +1027,12 @@ export type CreateVoiceSessionOptions<TContext = unknown, TSession extends Voice
1016
1027
  }) => string | Promise<string>);
1017
1028
  maxReprompts?: number;
1018
1029
  };
1030
+ /** Caller-driven in-call pause (VoiceSessionHandle.pause/resume). `maxMs`
1031
+ * bounds how long a session may sit paused before it closes gracefully
1032
+ * with reason "pause-timeout" (default 10 minutes). */
1033
+ pause?: {
1034
+ maxMs?: number;
1035
+ };
1019
1036
  stt?: STTAdapter;
1020
1037
  realtime?: RealtimeAdapter;
1021
1038
  realtimeInputFormat?: AudioFormat;
@@ -1161,7 +1178,7 @@ export type VoiceClientCloseMessage = {
1161
1178
  };
1162
1179
  export type VoiceClientCallControlMessage = {
1163
1180
  type: "call_control";
1164
- action: "complete" | "escalate" | "no-answer" | "transfer" | "voicemail";
1181
+ action: "complete" | "escalate" | "no-answer" | "pause" | "resume" | "transfer" | "voicemail";
1165
1182
  metadata?: Record<string, unknown>;
1166
1183
  reason?: string;
1167
1184
  target?: string;
package/dist/index.js CHANGED
@@ -4368,6 +4368,9 @@ var createVoiceSession = (options) => {
4368
4368
  if (callSilenceTimeoutMs === undefined || callSilenceFired) {
4369
4369
  return;
4370
4370
  }
4371
+ if (callerPaused) {
4372
+ return;
4373
+ }
4371
4374
  clearCallSilenceWatchdog();
4372
4375
  callSilenceWatchdog = setTimeout(fireCallSilenceTimeout, callSilenceTimeoutMs);
4373
4376
  };
@@ -4413,6 +4416,9 @@ var createVoiceSession = (options) => {
4413
4416
  if (stuckCloseAfterMs === undefined || stuckCloseFired) {
4414
4417
  return;
4415
4418
  }
4419
+ if (callerPaused) {
4420
+ return;
4421
+ }
4416
4422
  clearStuckCloseWatchdog();
4417
4423
  stuckCloseWatchdog = setTimeout(fireStuckClose, stuckCloseAfterMs);
4418
4424
  };
@@ -4435,6 +4441,9 @@ var createVoiceSession = (options) => {
4435
4441
  idleRepromptsUsed = 0;
4436
4442
  }
4437
4443
  clearIdleRepromptWatchdog();
4444
+ if (callerPaused) {
4445
+ return;
4446
+ }
4438
4447
  if (idleRepromptsUsed >= idleRepromptMax) {
4439
4448
  return;
4440
4449
  }
@@ -4455,6 +4464,60 @@ var createVoiceSession = (options) => {
4455
4464
  });
4456
4465
  }, idleRepromptAfterMs);
4457
4466
  };
4467
+ const PAUSE_MAX_MS_DEFAULT = 600000;
4468
+ const pauseMaxMs = options.pause?.maxMs && options.pause.maxMs > 0 ? options.pause.maxMs : PAUSE_MAX_MS_DEFAULT;
4469
+ let callerPaused = false;
4470
+ let pauseTimeout = null;
4471
+ const clearPauseTimeout = () => {
4472
+ if (pauseTimeout) {
4473
+ clearTimeout(pauseTimeout);
4474
+ pauseTimeout = null;
4475
+ }
4476
+ };
4477
+ const pauseInternal = async () => {
4478
+ if (callerPaused) {
4479
+ return;
4480
+ }
4481
+ const snapshot = await readSession();
4482
+ if (snapshot.status === "completed" || snapshot.status === "failed" || snapshot.call?.endedAt) {
4483
+ return;
4484
+ }
4485
+ callerPaused = true;
4486
+ clearCallSilenceWatchdog();
4487
+ clearStuckCloseWatchdog();
4488
+ clearIdleRepromptWatchdog();
4489
+ clearPauseTimeout();
4490
+ pauseTimeout = setTimeout(() => {
4491
+ pauseTimeout = null;
4492
+ api.close("pause-timeout");
4493
+ }, pauseMaxMs);
4494
+ await appendTrace({
4495
+ payload: {
4496
+ action: "call.paused",
4497
+ maxMs: pauseMaxMs
4498
+ },
4499
+ session: snapshot,
4500
+ type: "call.lifecycle"
4501
+ });
4502
+ };
4503
+ const resumeInternal = async () => {
4504
+ if (!callerPaused) {
4505
+ return;
4506
+ }
4507
+ callerPaused = false;
4508
+ clearPauseTimeout();
4509
+ kickCallSilenceWatchdog();
4510
+ kickStuckCloseWatchdog();
4511
+ kickIdleRepromptWatchdog(true);
4512
+ const snapshot = await readSession();
4513
+ await appendTrace({
4514
+ payload: {
4515
+ action: "call.resumed"
4516
+ },
4517
+ session: snapshot,
4518
+ type: "call.lifecycle"
4519
+ });
4520
+ };
4458
4521
  const recordingConfig = options.recording;
4459
4522
  const recordingChannels = new Set(recordingConfig?.channels ?? ["assistant", "user"]);
4460
4523
  const recordingMaxBytes = recordingConfig?.maxBytesPerChannel ?? 50 * 1024 * 1024;
@@ -4962,6 +5025,7 @@ var createVoiceSession = (options) => {
4962
5025
  clearCallSilenceWatchdog();
4963
5026
  clearStuckCloseWatchdog();
4964
5027
  clearIdleRepromptWatchdog();
5028
+ clearPauseTimeout();
4965
5029
  clearAmdEvaluationTimer();
4966
5030
  await closeTTSSession("failed");
4967
5031
  await closeAdapter("failed");
@@ -5073,6 +5137,7 @@ var createVoiceSession = (options) => {
5073
5137
  clearCallSilenceWatchdog();
5074
5138
  clearStuckCloseWatchdog();
5075
5139
  clearIdleRepromptWatchdog();
5140
+ clearPauseTimeout();
5076
5141
  clearAmdEvaluationTimer();
5077
5142
  await closeTTSSession("complete");
5078
5143
  await closeAdapter("complete");
@@ -6033,6 +6098,19 @@ var createVoiceSession = (options) => {
6033
6098
  }
6034
6099
  kickStuckCloseWatchdog();
6035
6100
  kickIdleRepromptWatchdog(true);
6101
+ if (callerPaused) {
6102
+ await appendTrace({
6103
+ payload: {
6104
+ action: "turn.skipped",
6105
+ reason: "caller-paused",
6106
+ status: "skipped"
6107
+ },
6108
+ session,
6109
+ turnId: turn.id,
6110
+ type: "call.lifecycle"
6111
+ });
6112
+ return;
6113
+ }
6036
6114
  const liveOpsControl = await options.liveOps?.getControl(options.id);
6037
6115
  if (liveOpsControl?.assistantPaused || liveOpsControl?.operatorTakeover) {
6038
6116
  await appendTrace({
@@ -6883,6 +6961,7 @@ var createVoiceSession = (options) => {
6883
6961
  clearCallSilenceWatchdog();
6884
6962
  clearStuckCloseWatchdog();
6885
6963
  clearIdleRepromptWatchdog();
6964
+ clearPauseTimeout();
6886
6965
  clearAmdEvaluationTimer();
6887
6966
  if (options.noiseSuppressor?.close) {
6888
6967
  try {
@@ -6948,9 +7027,15 @@ var createVoiceSession = (options) => {
6948
7027
  markVoicemail: async (input) => runSerial("api.markVoicemail", async () => {
6949
7028
  await markVoicemailInternal(input);
6950
7029
  }),
7030
+ pause: async () => runSerial("api.pause", async () => {
7031
+ await pauseInternal();
7032
+ }),
6951
7033
  receiveAudio: async (audio) => runSerial("api.receiveAudio", async () => {
6952
7034
  await receiveAudioInternal(audio);
6953
7035
  }),
7036
+ resume: async () => runSerial("api.resume", async () => {
7037
+ await resumeInternal();
7038
+ }),
6954
7039
  snapshot: async () => runSerial("api.snapshot", async () => readSession()),
6955
7040
  transfer: async (input) => runSerial("api.transfer", async () => {
6956
7041
  await transferInternal(input);
@@ -39724,7 +39809,7 @@ var isVoiceClientMessage = (value) => {
39724
39809
  if (!("action" in value)) {
39725
39810
  return false;
39726
39811
  }
39727
- if (value.action !== "complete" && value.action !== "escalate" && value.action !== "no-answer" && value.action !== "transfer" && value.action !== "voicemail") {
39812
+ if (value.action !== "complete" && value.action !== "escalate" && value.action !== "no-answer" && value.action !== "pause" && value.action !== "resume" && value.action !== "transfer" && value.action !== "voicemail") {
39728
39813
  return false;
39729
39814
  }
39730
39815
  return (!("metadata" in value) || value.metadata === undefined || value.metadata !== null && typeof value.metadata === "object") && (!("reason" in value) || value.reason === undefined || typeof value.reason === "string") && (!("target" in value) || value.target === undefined || typeof value.target === "string");
@@ -40313,6 +40398,12 @@ var voice = (config) => {
40313
40398
  if (message.action === "complete") {
40314
40399
  await current.complete();
40315
40400
  }
40401
+ if (message.action === "pause") {
40402
+ await current.pause();
40403
+ }
40404
+ if (message.action === "resume") {
40405
+ await current.resume();
40406
+ }
40316
40407
  }
40317
40408
  if (message.type === "start" && message.sessionId && message.sessionId !== sessionState.sessionId) {
40318
40409
  const currentSession = runtime.activeSessions.get(sessionState.sessionId);
@@ -44373,7 +44464,9 @@ var createContractApi = (session) => ({
44373
44464
  fail: async () => {},
44374
44465
  markNoAnswer: async () => {},
44375
44466
  markVoicemail: async () => {},
44467
+ pause: async () => {},
44376
44468
  receiveAudio: async () => {},
44469
+ resume: async () => {},
44377
44470
  setTurnDetection: async () => ({
44378
44471
  silenceMs: 0,
44379
44472
  speechThreshold: 0,
@@ -0,0 +1,2 @@
1
+ import type { VoicePluginConfig, VoiceSessionStore } from "./core/types";
2
+ export declare const manifest: import("@absolutejs/manifest").PackageManifest<VoicePluginConfig, VoiceSessionStore>;