@openclaw/voice-call 2026.7.2-beta.2 → 2026.7.2-beta.3

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.
@@ -63,6 +63,7 @@ function describeVoiceCallSchemaMigration(migration) {
63
63
  case "agent-databases-composite-primary-key": return "agent database registry primary key -> agent_id,path";
64
64
  case "audit-events-v2": return "audit event ledger -> versioned message lifecycle schema";
65
65
  case "operator-approvals-system-agent": return "operator approvals -> OpenClaw system changes";
66
+ case "session-watch-cursor-provenance-v4": return "session watch cursors -> provenance column";
66
67
  case "strict-tables-v3": return "tables -> SQLite STRICT typing";
67
68
  }
68
69
  return migration.kind;
@@ -1,6 +1,6 @@
1
1
  import { fetchWithSsrFGuard } from "./runtime-api.js";
2
2
  import "./api.js";
3
- import { n as normalizeProxyIp, o as getHeader } from "./runtime-entry-DSgyqIwE.js";
3
+ import { n as normalizeProxyIp, o as getHeader } from "./runtime-entry-D8Mkwy8G.js";
4
4
  import { n as markWebhookReplay, t as createWebhookReplayCache } from "./webhook-replay-aaP79dI8.js";
5
5
  import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
6
6
  import { isLoopbackHost } from "openclaw/plugin-sdk/gateway-runtime";
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { definePluginEntry, sleep } from "./runtime-api.js";
2
2
  import "./api.js";
3
- import { c as cleanupTailscaleExposureRoute, h as resolveUserPath, l as getTailscaleSelfInfo, s as resolveWebhookExposureStatus, t as createVoiceCallRuntime, u as setupTailscaleExposureRoute, v as VoiceCallConfigSchema, x as validateProviderConfig, y as resolveVoiceCallConfig } from "./runtime-entry-DSgyqIwE.js";
3
+ import { c as cleanupTailscaleExposureRoute, h as resolveUserPath, l as getTailscaleSelfInfo, s as resolveWebhookExposureStatus, t as createVoiceCallRuntime, u as setupTailscaleExposureRoute, v as VoiceCallConfigSchema, x as validateProviderConfig, y as resolveVoiceCallConfig } from "./runtime-entry-D8Mkwy8G.js";
4
4
  import { g as setVoiceCallStateRuntime, t as resolveDefaultVoiceCallStoreDir, u as getCallHistoryFromStore } from "./store-path-nYL-yM0S.js";
5
5
  import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
6
6
  import { ErrorCodes, callGatewayFromCli, errorShape } from "openclaw/plugin-sdk/gateway-runtime";
@@ -1,5 +1,5 @@
1
- import { g as escapeXml, o as getHeader } from "./runtime-entry-DSgyqIwE.js";
2
- import { a as reconstructWebhookUrl, o as verifyPlivoWebhook, t as guardedJsonApiRequest } from "./guarded-json-api-BKOgUU1q.js";
1
+ import { g as escapeXml, o as getHeader } from "./runtime-entry-D8Mkwy8G.js";
2
+ import { a as reconstructWebhookUrl, o as verifyPlivoWebhook, t as guardedJsonApiRequest } from "./guarded-json-api-Dm4NvtGo.js";
3
3
  import { normalizeLowercaseStringOrEmpty, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
4
4
  import crypto from "node:crypto";
5
5
  //#region extensions/voice-call/src/providers/plivo.ts
@@ -1,7 +1,7 @@
1
1
  import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
2
2
  import { isFutureDateTimestampMs, resolveExpiresAtMsFromDurationMs } from "openclaw/plugin-sdk/number-runtime";
3
3
  import { createSubsystemLogger } from "openclaw/plugin-sdk/runtime-env";
4
- import { REALTIME_VOICE_AGENT_CONSULT_TOOL_NAME, buildRealtimeVoiceAgentConsultWorkingResponse, createRealtimeVoiceBridgeSession, createRealtimeVoiceForcedConsultCoordinator, createTalkSessionController, readRealtimeVoiceConsultQuestion, readSpeakableRealtimeVoiceToolResult, recordTalkObservabilityEvent } from "openclaw/plugin-sdk/realtime-voice";
4
+ import { REALTIME_VOICE_AGENT_CONSULT_TOOL_NAME, buildRealtimeVoiceAgentConsultWorkingResponse, calculateMulawRms, createRealtimeVoiceSessionHarness, createSpeechThresholdGate, readRealtimeVoiceConsultQuestion, readSpeakableRealtimeVoiceToolResult } from "openclaw/plugin-sdk/realtime-voice";
5
5
  import { normalizeWebhookPath } from "openclaw/plugin-sdk/webhook-ingress";
6
6
  import { randomUUID } from "node:crypto";
7
7
  import { sliceUtf16Safe, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
@@ -11,13 +11,7 @@ import WebSocket$1, { WebSocketServer } from "ws";
11
11
  const TELEPHONY_SAMPLE_RATE = 8e3;
12
12
  const TELEPHONY_CHUNK_BYTES = 160;
13
13
  const TELEPHONY_CHUNK_MS = 20;
14
- const DEFAULT_SPEECH_RMS_THRESHOLD = .035;
15
- const DEFAULT_REQUIRED_LOUD_CHUNKS = 4;
16
- const DEFAULT_REQUIRED_QUIET_CHUNKS = 12;
17
14
  const DEFAULT_MAX_QUEUED_AUDIO_BYTES = TELEPHONY_SAMPLE_RATE * 120;
18
- const PCM16_MAX_AMPLITUDE = 32768;
19
- const MULAW_LINEAR_SAMPLES = /* @__PURE__ */ new Int16Array(256);
20
- for (let i = 0; i < MULAW_LINEAR_SAMPLES.length; i += 1) MULAW_LINEAR_SAMPLES[i] = decodeMulawSample(i);
21
15
  /** Paces outgoing mulaw audio frames at telephony cadence. */
22
16
  var RealtimeAudioPacer = class {
23
17
  constructor(params) {
@@ -112,52 +106,6 @@ var RealtimeAudioPacer = class {
112
106
  if (this.queue.length > 0) this.timer = setTimeout(() => this.pump(), delayMs);
113
107
  }
114
108
  };
115
- /** Calculate normalized RMS from mulaw bytes. */
116
- function calculateMulawRms(muLaw) {
117
- if (muLaw.length === 0) return 0;
118
- let sum = 0;
119
- for (const sample of muLaw) {
120
- const normalized = (MULAW_LINEAR_SAMPLES[sample] ?? 0) / PCM16_MAX_AMPLITUDE;
121
- sum += normalized * normalized;
122
- }
123
- return Math.sqrt(sum / muLaw.length);
124
- }
125
- /** Detect likely speech start from consecutive loud mulaw chunks. */
126
- var RealtimeMulawSpeechStartDetector = class {
127
- constructor(params = {}) {
128
- this.params = params;
129
- this.loudChunks = 0;
130
- this.quietChunks = DEFAULT_REQUIRED_QUIET_CHUNKS;
131
- this.speaking = false;
132
- }
133
- /** Accept one mulaw chunk and return true only on transition into speaking. */
134
- accept(muLaw) {
135
- if (calculateMulawRms(muLaw) >= (this.params.rmsThreshold ?? DEFAULT_SPEECH_RMS_THRESHOLD)) {
136
- this.quietChunks = 0;
137
- this.loudChunks += 1;
138
- const requiredLoudChunks = this.params.requiredLoudChunks ?? DEFAULT_REQUIRED_LOUD_CHUNKS;
139
- if (!this.speaking && this.loudChunks >= requiredLoudChunks) {
140
- this.speaking = true;
141
- return true;
142
- }
143
- return false;
144
- }
145
- this.loudChunks = 0;
146
- this.quietChunks += 1;
147
- const requiredQuietChunks = this.params.requiredQuietChunks ?? DEFAULT_REQUIRED_QUIET_CHUNKS;
148
- if (this.quietChunks >= requiredQuietChunks) this.speaking = false;
149
- return false;
150
- }
151
- };
152
- /** Decode one G.711 mulaw byte to a linear PCM sample. */
153
- function decodeMulawSample(value) {
154
- const muLaw = ~value & 255;
155
- const sign = muLaw & 128;
156
- const exponent = muLaw >> 4 & 7;
157
- let sample = ((muLaw & 15) << 3) + 132 << exponent;
158
- sample -= 132;
159
- return sign ? -sample : sample;
160
- }
161
109
  //#endregion
162
110
  //#region extensions/voice-call/src/webhook/stream-frame-adapter.ts
163
111
  /** Parse numeric timestamps sent as numbers or integer strings. */
@@ -475,7 +423,6 @@ var RealtimeCallHandler = class {
475
423
  this.partialUserTranscriptUpdatedAtByCallId = /* @__PURE__ */ new Map();
476
424
  this.recentFinalUserTranscriptsByCallId = /* @__PURE__ */ new Map();
477
425
  this.recentFinalUserTranscriptTimersByCallId = /* @__PURE__ */ new Map();
478
- this.forcedConsultCoordinatorsByCallId = /* @__PURE__ */ new Map();
479
426
  this.forcedConsultsByCallId = /* @__PURE__ */ new Map();
480
427
  this.nativeConsultsInFlightByCallId = /* @__PURE__ */ new Map();
481
428
  this.publicOrigin = null;
@@ -661,56 +608,51 @@ var RealtimeCallHandler = class {
661
608
  }
662
609
  const { callId, instructions, initialGreetingInstructions } = registration;
663
610
  const callRecord = this.manager.getCallByProviderCallId(callSid);
664
- const talk = createTalkSessionController({
665
- sessionId: `voice-call:${callId}:realtime`,
666
- mode: "realtime",
667
- transport: "gateway-relay",
668
- brain: "agent-consult",
669
- provider: this.realtimeProvider.id
670
- }, { onEvent: recordTalkObservabilityEvent });
671
- const rememberTalkEvent = (event) => {
672
- if (event) appendRecentTalkEventMetadata(callRecord, event);
673
- return event;
674
- };
675
- const emitTalkEvent = (input) => {
676
- return rememberTalkEvent(talk.emit(input));
677
- };
678
- const ensureTalkTurn = () => {
679
- const turn = talk.ensureTurn({ payload: {
680
- callId,
681
- providerCallId: callSid
682
- } });
683
- rememberTalkEvent(turn.event);
684
- return turn.turnId;
685
- };
686
- const endTalkTurn = (reason = "completed") => {
687
- const ended = talk.endTurn({ payload: {
688
- callId,
689
- providerCallId: callSid,
690
- reason
691
- } });
692
- if (ended.ok) rememberTalkEvent(ended.event);
693
- };
694
- const finishOutputAudio = (reason) => {
695
- rememberTalkEvent(talk.finishOutputAudio({ payload: {
696
- callId,
697
- providerCallId: callSid,
698
- reason
699
- } }));
700
- };
611
+ const harness = createRealtimeVoiceSessionHarness({
612
+ talk: {
613
+ sessionId: `voice-call:${callId}:realtime`,
614
+ mode: "realtime",
615
+ transport: "gateway-relay",
616
+ brain: "agent-consult",
617
+ provider: this.realtimeProvider.id
618
+ },
619
+ talkPayloads: {
620
+ turnStarted: () => ({
621
+ callId,
622
+ providerCallId: callSid
623
+ }),
624
+ turnEnded: (reason) => ({
625
+ callId,
626
+ providerCallId: callSid,
627
+ reason
628
+ }),
629
+ inputAudioDelta: (audio) => ({ byteLength: audio.byteLength }),
630
+ outputAudioStarted: () => ({
631
+ callId,
632
+ providerCallId: callSid
633
+ }),
634
+ outputAudioDelta: (audio) => ({ byteLength: audio.byteLength }),
635
+ outputAudioDone: (reason) => ({
636
+ callId,
637
+ providerCallId: callSid,
638
+ reason
639
+ })
640
+ },
641
+ onTalkEvent: (event) => appendRecentTalkEventMetadata(callRecord, event)
642
+ });
701
643
  const providerHandlesInputAudioBargeIn = this.realtimeProvider.capabilities?.handlesInputAudioBargeIn === true;
702
644
  const cancelOutputAudioForBargeIn = (source, interruptProvider, clearedAudioBytes = 0) => {
703
- const outputAudioActive = talk.outputAudioActive;
645
+ const outputAudioActive = harness.talk.outputAudioActive;
704
646
  const pendingTelephonyAudio = audioPacer.hasPendingAudio();
705
647
  if (source === "provider" && !outputAudioActive && !pendingTelephonyAudio && clearedAudioBytes === 0) return;
706
- const interruptedTurnId = talk.activeTurnId;
648
+ const interruptedTurnId = harness.talk.activeTurnId;
707
649
  interruptProvider?.(outputAudioActive || pendingTelephonyAudio);
708
650
  const clearedBytes = clearedAudioBytes + (source === "local" || pendingTelephonyAudio ? audioPacer.clearAudio() : 0);
709
651
  console.log(`[voice-call] realtime outbound audio cleared by ${source} barge-in callId=${callId} providerCallId=${callSid} queuedBytes=${clearedBytes}`);
710
652
  if (!outputAudioActive || !interruptedTurnId) return;
711
653
  const reason = `${source}-barge-in`;
712
- finishOutputAudio(reason);
713
- const cancelled = talk.cancelTurn({
654
+ harness.finishOutputAudio(reason);
655
+ harness.talk.cancelTurn({
714
656
  turnId: interruptedTurnId,
715
657
  payload: {
716
658
  callId,
@@ -718,9 +660,8 @@ var RealtimeCallHandler = class {
718
660
  reason
719
661
  }
720
662
  });
721
- if (cancelled.ok) rememberTalkEvent(cancelled.event);
722
663
  };
723
- emitTalkEvent({
664
+ harness.emit({
724
665
  type: "session.started",
725
666
  payload: {
726
667
  callId,
@@ -762,9 +703,13 @@ var RealtimeCallHandler = class {
762
703
  if (ws.readyState === WebSocket$1.OPEN) ws.close(1013, "Backpressure: paced audio queue exceeded");
763
704
  }
764
705
  });
765
- const speechDetector = new RealtimeMulawSpeechStartDetector({ requiredLoudChunks: BARGE_IN_REQUIRED_LOUD_CHUNKS });
706
+ const speechDetector = createSpeechThresholdGate({
707
+ rmsThreshold: .035,
708
+ speechFrames: BARGE_IN_REQUIRED_LOUD_CHUNKS,
709
+ silenceFrames: 12
710
+ });
766
711
  const interruptResponseOnInputAudio = typeof this.providerConfig.interruptResponseOnInputAudio === "boolean" ? this.providerConfig.interruptResponseOnInputAudio : void 0;
767
- const session = createRealtimeVoiceBridgeSession({
712
+ const session = harness.createBridge({
768
713
  provider: this.realtimeProvider,
769
714
  cfg: this.coreConfig,
770
715
  providerConfig: this.providerConfig,
@@ -776,46 +721,38 @@ var RealtimeCallHandler = class {
776
721
  audioSink: {
777
722
  isOpen: () => ws.readyState === WebSocket$1.OPEN,
778
723
  sendAudio: (muLaw) => {
779
- const turnId = ensureTalkTurn();
780
- rememberTalkEvent(talk.startOutputAudio({
781
- turnId,
782
- payload: {
783
- callId,
784
- providerCallId: callSid
785
- }
786
- }).event);
787
- emitTalkEvent({
788
- type: "output.audio.delta",
789
- turnId,
790
- payload: { byteLength: muLaw.length }
791
- });
724
+ harness.recordOutputAudio(muLaw);
792
725
  audioPacer.sendAudio(muLaw);
793
726
  },
794
727
  clearAudio: (reason) => {
795
- const clearedBytes = audioPacer.clearAudio();
796
- if (reason === "barge-in") {
797
- cancelOutputAudioForBargeIn("provider", void 0, clearedBytes);
798
- return;
799
- }
800
- console.log(`[voice-call] realtime outbound audio clear requested callId=${callId} providerCallId=${callSid} queuedBytes=${clearedBytes}`);
801
- finishOutputAudio("clear");
728
+ harness.flushOutput(() => {
729
+ const clearedBytes = audioPacer.clearAudio();
730
+ if (reason === "barge-in") {
731
+ cancelOutputAudioForBargeIn("provider", void 0, clearedBytes);
732
+ return;
733
+ }
734
+ console.log(`[voice-call] realtime outbound audio clear requested callId=${callId} providerCallId=${callSid} queuedBytes=${clearedBytes}`);
735
+ harness.finishOutputAudio("clear");
736
+ });
802
737
  },
803
738
  sendMark: (markName) => {
804
739
  audioPacer.sendMark(markName);
805
740
  }
806
741
  },
807
742
  onTranscript: (role, text, isFinal) => {
808
- const turnId = ensureTalkTurn();
809
- emitTalkEvent({
810
- type: role === "assistant" ? isFinal ? "output.text.done" : "output.text.delta" : isFinal ? "transcript.done" : "transcript.delta",
743
+ const turnId = harness.ensureTurn();
744
+ const eventType = role === "assistant" ? isFinal ? "output.text.done" : "output.text.delta" : isFinal ? "transcript.done" : "transcript.delta";
745
+ const payload = role === "assistant" ? { text } : {
746
+ role,
747
+ text
748
+ };
749
+ harness.emit({
750
+ type: eventType,
811
751
  turnId,
812
- payload: role === "assistant" ? { text } : {
813
- role,
814
- text
815
- },
752
+ payload,
816
753
  final: isFinal
817
754
  });
818
- if (role === "user" && isFinal) emitTalkEvent({
755
+ if (role === "user" && isFinal) harness.emit({
819
756
  type: "input.audio.committed",
820
757
  turnId,
821
758
  payload: {
@@ -851,6 +788,7 @@ var RealtimeCallHandler = class {
851
788
  };
852
789
  this.manager.processEvent(event);
853
790
  this.scheduleForcedAgentConsult({
791
+ harness,
854
792
  session,
855
793
  callId,
856
794
  callSid,
@@ -872,8 +810,8 @@ var RealtimeCallHandler = class {
872
810
  });
873
811
  },
874
812
  onToolCall: (toolEvent, sessionLocal) => {
875
- const turnId = ensureTalkTurn();
876
- emitTalkEvent({
813
+ const turnId = harness.ensureTurn();
814
+ harness.emit({
877
815
  type: "tool.call",
878
816
  turnId,
879
817
  itemId: toolEvent.itemId,
@@ -884,17 +822,17 @@ var RealtimeCallHandler = class {
884
822
  }
885
823
  });
886
824
  console.log(`[voice-call] realtime tool call received callId=${callId} providerCallId=${callSid} tool=${toolEvent.name}`);
887
- return this.executeToolCall(sessionLocal, callId, toolEvent.callId || toolEvent.itemId, toolEvent.name, toolEvent.args, turnId, emitTalkEvent);
825
+ return this.executeToolCall(sessionLocal, callId, toolEvent.callId || toolEvent.itemId, toolEvent.name, toolEvent.args, turnId, harness);
888
826
  },
889
827
  onEvent: (event) => {
890
828
  if (event.type === "input_audio_buffer.speech_started") {
891
- ensureTalkTurn();
829
+ harness.ensureTurn();
892
830
  return;
893
831
  }
894
832
  if (event.type === "input_audio_buffer.speech_stopped") {
895
- const turnId = talk.activeTurnId;
833
+ const turnId = harness.talk.activeTurnId;
896
834
  if (!turnId) return;
897
- emitTalkEvent({
835
+ harness.emit({
898
836
  type: "input.audio.committed",
899
837
  turnId,
900
838
  payload: {
@@ -907,18 +845,18 @@ var RealtimeCallHandler = class {
907
845
  return;
908
846
  }
909
847
  if (event.type === "response.done") {
910
- finishOutputAudio("response.done");
911
- endTalkTurn("response.done");
848
+ harness.finishOutputAudio("response.done");
849
+ harness.endTurn("response.done");
912
850
  return;
913
851
  }
914
- if (event.type === "error") emitTalkEvent({
852
+ if (event.type === "error") harness.emit({
915
853
  type: "session.error",
916
854
  payload: { message: event.detail ?? "Realtime provider error" },
917
855
  final: true
918
856
  });
919
857
  },
920
858
  onReady: () => {
921
- emitTalkEvent({
859
+ harness.emit({
922
860
  type: "session.ready",
923
861
  payload: {
924
862
  callId,
@@ -928,7 +866,7 @@ var RealtimeCallHandler = class {
928
866
  },
929
867
  onError: (error) => {
930
868
  console.error("[voice-call] realtime voice error:", error.message);
931
- emitTalkEvent({
869
+ harness.emit({
932
870
  type: "session.error",
933
871
  payload: { message: error.message },
934
872
  final: true
@@ -940,8 +878,8 @@ var RealtimeCallHandler = class {
940
878
  this.activeTelephonyClosersByCallId.delete(callId);
941
879
  this.activeTelephonyClosersByCallId.delete(callSid);
942
880
  this.clearUserTranscriptState(callId);
943
- finishOutputAudio(reason);
944
- emitTalkEvent({
881
+ harness.finishOutputAudio(reason);
882
+ harness.emit({
945
883
  type: "session.closed",
946
884
  payload: { reason },
947
885
  final: true
@@ -971,17 +909,16 @@ var RealtimeCallHandler = class {
971
909
  this.activeTelephonyClosersByCallId.set(callSid, closeTelephony);
972
910
  const sendAudioToSession = session.sendAudio.bind(session);
973
911
  session.sendAudio = (audio) => {
974
- if (speechDetector.accept(audio)) {
912
+ if (speechDetector.accept({
913
+ rms: calculateMulawRms(audio),
914
+ peak: 0
915
+ })) {
975
916
  console.log(`[voice-call] realtime local speech detected callId=${callId} providerCallId=${callSid}`);
976
917
  if (!providerHandlesInputAudioBargeIn) cancelOutputAudioForBargeIn("local", (audioPlaybackActive) => {
977
918
  session.handleBargeIn({ audioPlaybackActive });
978
919
  });
979
920
  }
980
- emitTalkEvent({
981
- type: "input.audio.delta",
982
- turnId: ensureTalkTurn(),
983
- payload: { byteLength: audio.length }
984
- });
921
+ harness.recordInputAudio(audio);
985
922
  sendAudioToSession(audio);
986
923
  };
987
924
  const closeSession = session.close.bind(session);
@@ -997,7 +934,8 @@ var RealtimeCallHandler = class {
997
934
  this.activeTelephonyClosersByCallId.delete(callId);
998
935
  this.activeTelephonyClosersByCallId.delete(callSid);
999
936
  this.clearUserTranscriptState(callId);
1000
- this.clearForcedConsultState(callId);
937
+ this.forcedConsultsByCallId.delete(callId);
938
+ harness.close();
1001
939
  audioPacer.close();
1002
940
  }
1003
941
  };
@@ -1080,18 +1018,6 @@ var RealtimeCallHandler = class {
1080
1018
  });
1081
1019
  }
1082
1020
  }
1083
- clearForcedConsultState(callId) {
1084
- this.forcedConsultCoordinatorsByCallId.get(callId)?.clear();
1085
- this.forcedConsultCoordinatorsByCallId.delete(callId);
1086
- this.forcedConsultsByCallId.delete(callId);
1087
- }
1088
- forcedConsultCoordinator(callId) {
1089
- const existing = this.forcedConsultCoordinatorsByCallId.get(callId);
1090
- if (existing) return existing;
1091
- const created = createRealtimeVoiceForcedConsultCoordinator();
1092
- this.forcedConsultCoordinatorsByCallId.set(callId, created);
1093
- return created;
1094
- }
1095
1021
  closeTelephonyBridge(callIdOrSid, bridge, reason) {
1096
1022
  const closer = this.activeTelephonyClosersByCallId.get(callIdOrSid);
1097
1023
  if (closer) {
@@ -1108,7 +1034,7 @@ var RealtimeCallHandler = class {
1108
1034
  if (!handler) return;
1109
1035
  const existingForcedConsult = this.forcedConsultsByCallId.get(params.callId);
1110
1036
  if (existingForcedConsult && !existingForcedConsult.completedAt) return;
1111
- const coordinator = this.forcedConsultCoordinator(params.callId);
1037
+ const coordinator = params.harness.forcedConsults;
1112
1038
  if (coordinator.hasRecentNativeConsult(question, { allowUnknownQuestion: true })) return;
1113
1039
  coordinator.clearPending();
1114
1040
  const pending = coordinator.prepare(question);
@@ -1124,7 +1050,7 @@ var RealtimeCallHandler = class {
1124
1050
  });
1125
1051
  }
1126
1052
  async runForcedAgentConsult(params) {
1127
- const coordinator = this.forcedConsultCoordinator(params.callId);
1053
+ const coordinator = params.harness.forcedConsults;
1128
1054
  coordinator.markStarted(params.handle);
1129
1055
  const startedAt = Date.now();
1130
1056
  logger.debug(`[voice-call] realtime forced agent consult reason=${FORCED_CONSULT_REASON} consultPolicy=always callId=${params.callId} providerCallId=${params.callSid} chars=${params.handle.question.length}`);
@@ -1216,14 +1142,14 @@ var RealtimeCallHandler = class {
1216
1142
  reason
1217
1143
  });
1218
1144
  }
1219
- async executeToolCall(bridge, callId, bridgeCallId, name, args, turnId, emitTalkEvent) {
1145
+ async executeToolCall(bridge, callId, bridgeCallId, name, args, turnId, harness) {
1220
1146
  const handler = this.toolHandlers.get(name);
1221
1147
  const startedAt = Date.now();
1222
1148
  const hasResultError = (result) => {
1223
1149
  return Boolean(result && typeof result === "object" && !Array.isArray(result) && "error" in result);
1224
1150
  };
1225
1151
  const emitFinalToolEvent = (result) => {
1226
- emitTalkEvent?.({
1152
+ harness.emit({
1227
1153
  type: hasResultError(result) ? "tool.error" : "tool.result",
1228
1154
  turnId,
1229
1155
  callId: bridgeCallId,
@@ -1241,7 +1167,7 @@ var RealtimeCallHandler = class {
1241
1167
  const submitWorkingResponse = async () => {
1242
1168
  if (handler && name === REALTIME_VOICE_AGENT_CONSULT_TOOL_NAME && bridge.bridge.supportsToolResultContinuation && !this.config.fastContext.enabled) {
1243
1169
  await bridge.submitToolResult(bridgeCallId, buildRealtimeVoiceAgentConsultWorkingResponse("caller"), { willContinue: true });
1244
- emitTalkEvent?.({
1170
+ harness.emit({
1245
1171
  type: "tool.progress",
1246
1172
  turnId,
1247
1173
  callId: bridgeCallId,
@@ -1253,7 +1179,7 @@ var RealtimeCallHandler = class {
1253
1179
  }
1254
1180
  };
1255
1181
  if (name === REALTIME_VOICE_AGENT_CONSULT_TOOL_NAME) {
1256
- const coordinator = this.forcedConsultCoordinator(callId);
1182
+ const coordinator = harness.forcedConsults;
1257
1183
  const forcedMatch = coordinator.recordNativeConsult(args, bridgeCallId);
1258
1184
  if (forcedMatch.kind === "none") {
1259
1185
  const pending = coordinator.consumePending();
@@ -1,4 +1,4 @@
1
- import { b as resolveVoiceCallSessionKey, m as resolveCallAgentId, p as resolveVoiceResponseModel } from "./runtime-entry-DSgyqIwE.js";
1
+ import { b as resolveVoiceCallSessionKey, m as resolveCallAgentId, p as resolveVoiceResponseModel } from "./runtime-entry-D8Mkwy8G.js";
2
2
  import { isRecord, normalizeLowercaseStringOrEmpty, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
3
3
  import crypto from "node:crypto";
4
4
  import { ModelSelectionLockedError, applyModelOverrideToSessionEntry, resolvePersistedSessionRuntimeId } from "openclaw/plugin-sdk/model-session-runtime";
@@ -23,7 +23,6 @@ import path from "node:path";
23
23
  import os from "node:os";
24
24
  import { root } from "openclaw/plugin-sdk/security-runtime";
25
25
  import { sliceUtf16Safe, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
26
- import { parseTtsDirectives } from "openclaw/plugin-sdk/speech";
27
26
  import { spawn } from "node:child_process";
28
27
  import { runCommandWithTimeout } from "openclaw/plugin-sdk/process-runtime";
29
28
  import http from "node:http";
@@ -1913,26 +1912,26 @@ function chunkAudio(audio, chunkSize = 160) {
1913
1912
  /** Default timeout for one telephony synthesis request. */
1914
1913
  const TELEPHONY_DEFAULT_TTS_TIMEOUT_MS = 8e3;
1915
1914
  /** Create a TTS provider that honors voice-call overrides and converts PCM to mulaw. */
1916
- function createTelephonyTtsProvider(params) {
1915
+ async function createTelephonyTtsProvider(params) {
1917
1916
  const { coreConfig, ttsOverride, runtime, logger } = params;
1918
- const mergedConfig = applyTtsOverride(coreConfig, ttsOverride);
1919
- const ttsConfig = mergedConfig.messages?.tts;
1920
- const modelOverrides = resolveTelephonyModelOverridePolicy(readTelephonyModelOverrides(ttsConfig));
1921
- const providerConfigs = collectTelephonyProviderConfigs(ttsConfig);
1922
- const activeProvider = normalizeProviderId(ttsConfig?.provider);
1917
+ const preparedConfig = await runtime.prepareTtsRequest({
1918
+ cfg: coreConfig,
1919
+ override: ttsOverride,
1920
+ text: ""
1921
+ });
1923
1922
  return {
1924
- synthesisTimeoutMs: resolveTimerTimeoutMs(mergedConfig.messages?.tts?.timeoutMs, TELEPHONY_DEFAULT_TTS_TIMEOUT_MS),
1923
+ synthesisTimeoutMs: resolveTimerTimeoutMs(preparedConfig.cfg.messages?.tts?.timeoutMs, TELEPHONY_DEFAULT_TTS_TIMEOUT_MS),
1925
1924
  synthesizeForTelephony: async (text) => {
1926
- const directives = parseTtsDirectives(text, modelOverrides, {
1927
- cfg: mergedConfig,
1928
- providerConfigs,
1929
- preferredProviderId: activeProvider
1925
+ const prepared = await runtime.prepareTtsRequest({
1926
+ cfg: preparedConfig.cfg,
1927
+ text
1930
1928
  });
1929
+ const directives = prepared.directives;
1931
1930
  if (directives.warnings.length > 0) logger?.warn?.(`[voice-call] Ignored telephony TTS directive overrides (${directives.warnings.join("; ")})`);
1932
1931
  const cleanText = directives.hasDirective ? directives.ttsText?.trim() || directives.cleanedText.trim() : text;
1933
1932
  const result = await runtime.textToSpeechTelephony({
1934
1933
  text: cleanText,
1935
- cfg: mergedConfig,
1934
+ cfg: prepared.cfg,
1936
1935
  overrides: directives.overrides
1937
1936
  });
1938
1937
  if (!result.success || !result.audioBuffer || !result.sampleRate) throw new Error(result.error ?? "TTS conversion failed");
@@ -1944,94 +1943,6 @@ function createTelephonyTtsProvider(params) {
1944
1943
  }
1945
1944
  };
1946
1945
  }
1947
- /** Apply voice-call TTS overrides to core config without mutating the original object. */
1948
- function applyTtsOverride(coreConfig, override) {
1949
- if (!override) return coreConfig;
1950
- const base = coreConfig.messages?.tts;
1951
- const merged = mergeTtsConfig(base, override);
1952
- if (!merged) return coreConfig;
1953
- return {
1954
- ...coreConfig,
1955
- messages: {
1956
- ...coreConfig.messages,
1957
- tts: merged
1958
- }
1959
- };
1960
- }
1961
- /** Merge core and voice-call TTS config, keeping undefined override fields out. */
1962
- function mergeTtsConfig(base, override) {
1963
- if (!base && !override) return;
1964
- if (!override) return base;
1965
- if (!base) return override;
1966
- return mergeDeep(base, override);
1967
- }
1968
- /** Resolve directive override policy for telephony synthesis. */
1969
- function resolveTelephonyModelOverridePolicy(overrides) {
1970
- if (!(overrides?.enabled ?? true)) return {
1971
- enabled: false,
1972
- allowText: false,
1973
- allowProvider: false,
1974
- allowVoice: false,
1975
- allowModelId: false,
1976
- allowVoiceSettings: false,
1977
- allowNormalization: false,
1978
- allowSeed: false
1979
- };
1980
- const allow = (value, defaultValue = true) => value ?? defaultValue;
1981
- return {
1982
- enabled: true,
1983
- allowText: allow(overrides?.allowText),
1984
- allowProvider: allow(overrides?.allowProvider, false),
1985
- allowVoice: allow(overrides?.allowVoice),
1986
- allowModelId: allow(overrides?.allowModelId),
1987
- allowVoiceSettings: allow(overrides?.allowVoiceSettings),
1988
- allowNormalization: allow(overrides?.allowNormalization),
1989
- allowSeed: allow(overrides?.allowSeed)
1990
- };
1991
- }
1992
- /** Read model override policy from TTS config when present. */
1993
- function readTelephonyModelOverrides(ttsConfig) {
1994
- const value = ttsConfig?.modelOverrides;
1995
- return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
1996
- }
1997
- /** Normalize provider ids for config lookup. */
1998
- function normalizeProviderId(value) {
1999
- return typeof value === "string" ? value.trim().toLowerCase() || void 0 : void 0;
2000
- }
2001
- /** Coerce provider config objects while rejecting arrays and primitives. */
2002
- function asProviderConfig(value) {
2003
- return value && typeof value === "object" && !Array.isArray(value) ? value : {};
2004
- }
2005
- /** Collect named provider configs from canonical and legacy TTS config shapes. */
2006
- function collectTelephonyProviderConfigs(ttsConfig) {
2007
- if (!ttsConfig) return {};
2008
- const entries = {};
2009
- const rawProviders = ttsConfig.providers && typeof ttsConfig.providers === "object" && !Array.isArray(ttsConfig.providers) ? ttsConfig.providers : {};
2010
- for (const [providerId, value] of Object.entries(rawProviders)) {
2011
- const normalized = normalizeProviderId(providerId) ?? providerId;
2012
- entries[normalized] = asProviderConfig(value);
2013
- }
2014
- const reservedKeys = /* @__PURE__ */ new Set([
2015
- "auto",
2016
- "enabled",
2017
- "maxTextLength",
2018
- "mode",
2019
- "modelOverrides",
2020
- "persona",
2021
- "personas",
2022
- "prefsPath",
2023
- "provider",
2024
- "providers",
2025
- "summaryModel",
2026
- "timeoutMs"
2027
- ]);
2028
- for (const [key, value] of Object.entries(ttsConfig)) {
2029
- if (reservedKeys.has(key) || typeof value !== "object" || value === null || Array.isArray(value)) continue;
2030
- const normalized = normalizeProviderId(key) ?? key;
2031
- entries[normalized] ??= asProviderConfig(value);
2032
- }
2033
- return entries;
2034
- }
2035
1946
  //#endregion
2036
1947
  //#region extensions/voice-call/src/bounded-child-output.ts
2037
1948
  const DEFAULT_MAX_OUTPUT_CHARS = 16384;
@@ -2159,6 +2070,30 @@ async function cleanupTailscaleExposure(config) {
2159
2070
  const NGROK_LOG_BUFFER_MAX_CHARS = 16384;
2160
2071
  const NGROK_ERROR_MARKER = "ERR_NGROK";
2161
2072
  const TUNNEL_COMMAND_OUTPUT_MAX_BYTES = 16384;
2073
+ const NGROK_STOP_GRACE_MS = 2e3;
2074
+ const NGROK_FORCE_KILL_WAIT_MS = 1e3;
2075
+ async function terminateNgrokProcess(proc, isClosed) {
2076
+ if (isClosed()) return;
2077
+ await new Promise((resolve) => {
2078
+ let finished = false;
2079
+ let forceKillWaitTimer;
2080
+ const finish = () => {
2081
+ if (finished) return;
2082
+ finished = true;
2083
+ if (forceKillTimer) clearTimeout(forceKillTimer);
2084
+ if (forceKillWaitTimer) clearTimeout(forceKillWaitTimer);
2085
+ proc.off("close", finish);
2086
+ resolve();
2087
+ };
2088
+ proc.once("close", finish);
2089
+ const forceKillTimer = setTimeout(() => {
2090
+ forceKillWaitTimer = setTimeout(finish, NGROK_FORCE_KILL_WAIT_MS);
2091
+ if (!isClosed()) proc.kill("SIGKILL");
2092
+ }, NGROK_STOP_GRACE_MS);
2093
+ proc.kill("SIGTERM");
2094
+ if (isClosed()) finish();
2095
+ });
2096
+ }
2162
2097
  function listenForChildStreamErrors(proc, onError) {
2163
2098
  proc.stdout.on("error", (error) => onError("stdout", error));
2164
2099
  proc.stderr.on("error", (error) => onError("stderr", error));
@@ -2194,23 +2129,25 @@ async function startNgrokTunnel(config) {
2194
2129
  "pipe",
2195
2130
  "pipe"
2196
2131
  ] });
2197
- let resolved = false;
2198
- let closed = false;
2132
+ let startupSettled = false;
2133
+ let childClosed = false;
2199
2134
  let publicUrl = null;
2200
2135
  let outputBuffer = "";
2201
2136
  let stderrTail = "";
2202
2137
  const timeout = setTimeout(() => {
2203
- if (!resolved) {
2204
- resolved = true;
2205
- proc.kill("SIGTERM");
2206
- reject(/* @__PURE__ */ new Error("ngrok startup timed out (30s)"));
2138
+ if (!startupSettled) {
2139
+ startupSettled = true;
2140
+ terminateNgrokProcess(proc, () => childClosed).then(() => {
2141
+ reject(/* @__PURE__ */ new Error("ngrok startup timed out (30s)"));
2142
+ });
2207
2143
  }
2208
2144
  }, 3e4);
2145
+ timeout.unref();
2209
2146
  const rejectIfPending = (message, kill = false) => {
2210
- if (!resolved) {
2211
- resolved = true;
2147
+ if (!startupSettled) {
2148
+ startupSettled = true;
2212
2149
  clearTimeout(timeout);
2213
- if (kill && !closed) proc.kill("SIGKILL");
2150
+ if (kill && !childClosed) proc.kill("SIGKILL");
2214
2151
  reject(new Error(message));
2215
2152
  }
2216
2153
  };
@@ -2219,8 +2156,8 @@ async function startNgrokTunnel(config) {
2219
2156
  const log = JSON.parse(line);
2220
2157
  if (log.msg === "started tunnel" && log.url) publicUrl = log.url;
2221
2158
  if (log.addr && log.url && !publicUrl) publicUrl = log.url;
2222
- if (publicUrl && !resolved) {
2223
- resolved = true;
2159
+ if (publicUrl && !startupSettled) {
2160
+ startupSettled = true;
2224
2161
  clearTimeout(timeout);
2225
2162
  const fullUrl = publicUrl + config.path;
2226
2163
  console.log(`[voice-call] ngrok tunnel active: ${fullUrl}`);
@@ -2228,38 +2165,22 @@ async function startNgrokTunnel(config) {
2228
2165
  publicUrl: fullUrl,
2229
2166
  provider: "ngrok",
2230
2167
  stop: async () => {
2231
- if (closed) return;
2232
- await new Promise((res) => {
2233
- let finished = false;
2234
- const finish = () => {
2235
- if (finished) return;
2236
- finished = true;
2237
- clearTimeout(fallback);
2238
- proc.off("close", finish);
2239
- res();
2240
- };
2241
- if (closed) {
2242
- res();
2243
- return;
2244
- }
2245
- proc.once("close", finish);
2246
- const fallback = setTimeout(finish, 2e3);
2247
- proc.kill("SIGTERM");
2248
- if (closed) finish();
2249
- });
2168
+ await terminateNgrokProcess(proc, () => childClosed);
2250
2169
  }
2251
2170
  });
2252
2171
  }
2253
2172
  } catch {}
2254
2173
  };
2255
- proc.stdout.on("data", (data) => {
2256
- const lines = (outputBuffer + data.toString()).split("\n");
2174
+ proc.stdout.setEncoding("utf8");
2175
+ proc.stderr.setEncoding("utf8");
2176
+ proc.stdout.on("data", (chunk) => {
2177
+ const lines = (outputBuffer + chunk).split("\n");
2257
2178
  outputBuffer = lines.pop() || "";
2258
2179
  if (outputBuffer.length > NGROK_LOG_BUFFER_MAX_CHARS) outputBuffer = sliceUtf16Safe(outputBuffer, -16384);
2259
2180
  for (const line of lines) if (line.trim()) processLine(line);
2260
2181
  });
2261
- proc.stderr.on("data", (data) => {
2262
- const combined = stderrTail + data.toString();
2182
+ proc.stderr.on("data", (chunk) => {
2183
+ const combined = stderrTail + chunk;
2263
2184
  if (combined.includes(NGROK_ERROR_MARKER)) rejectIfPending(`ngrok error: ${formatBoundedChildOutput(appendBoundedChildOutput(emptyBoundedChildOutput(), combined))}`, true);
2264
2185
  stderrTail = sliceUtf16Safe(combined, -8);
2265
2186
  });
@@ -2270,9 +2191,9 @@ async function startNgrokTunnel(config) {
2270
2191
  rejectIfPending(`Failed to start ngrok: ${err.message}`);
2271
2192
  });
2272
2193
  proc.on("close", (code) => {
2273
- closed = true;
2274
- if (!resolved) {
2275
- resolved = true;
2194
+ childClosed = true;
2195
+ if (!startupSettled) {
2196
+ startupSettled = true;
2276
2197
  clearTimeout(timeout);
2277
2198
  reject(/* @__PURE__ */ new Error(`ngrok exited unexpectedly with code ${code}`));
2278
2199
  }
@@ -3141,7 +3062,7 @@ const WEBHOOK_BODY_TIMEOUT_MS = WEBHOOK_BODY_READ_DEFAULTS.preAuth.timeoutMs;
3141
3062
  const MISSING_REMOTE_ADDRESS_IN_FLIGHT_KEY = "__voice_call_no_remote__";
3142
3063
  const STREAM_DISCONNECT_HANGUP_GRACE_MS = 2e3;
3143
3064
  const loadRealtimeTranscriptionRuntime = createLazyRuntimeModule(() => import("./realtime-transcription.runtime-CbJAs5t_.js"));
3144
- const loadResponseGeneratorModule = createLazyRuntimeModule(() => import("./response-generator-Cdj8EuLg.js"));
3065
+ const loadResponseGeneratorModule = createLazyRuntimeModule(() => import("./response-generator-r9wHPOEl.js"));
3145
3066
  function appendRecentTalkEventMetadata(call, event) {
3146
3067
  const metadata = call.metadata ?? {};
3147
3068
  const recent = Array.isArray(metadata.recentTalkEvents) ? metadata.recentTalkEvents.filter((entry) => Boolean(entry) && typeof entry === "object" && !Array.isArray(entry)) : [];
@@ -3816,12 +3737,12 @@ const REALTIME_VOICE_CONSULT_SYSTEM_PROMPT = [
3816
3737
  "Do not print secret values or dump environment variables; only check whether required configuration is present.",
3817
3738
  "Be accurate, brief, and speakable."
3818
3739
  ].join(" ");
3819
- const loadTelnyxProvider = createLazyRuntimeModule(() => import("./telnyx-DMbLYwj4.js"));
3820
- const loadTwilioProvider = createLazyRuntimeModule(() => import("./twilio-BTKKlkLT.js"));
3821
- const loadPlivoProvider = createLazyRuntimeModule(() => import("./plivo-ByAIoPFp.js"));
3740
+ const loadTelnyxProvider = createLazyRuntimeModule(() => import("./telnyx-DTD9jTqC.js"));
3741
+ const loadTwilioProvider = createLazyRuntimeModule(() => import("./twilio-GzbIb5fv.js"));
3742
+ const loadPlivoProvider = createLazyRuntimeModule(() => import("./plivo-rISw-5A8.js"));
3822
3743
  const loadMockProvider = createLazyRuntimeModule(() => import("./mock-YsvBTX-7.js"));
3823
3744
  const loadRealtimeVoiceRuntime = createLazyRuntimeModule(() => import("./realtime-voice.runtime-vtdCOWg-.js"));
3824
- const loadRealtimeHandler = createLazyRuntimeModule(() => import("./realtime-handler-DJk-eYyf.js"));
3745
+ const loadRealtimeHandler = createLazyRuntimeModule(() => import("./realtime-handler-HSxcgwY_.js"));
3825
3746
  function resolveVoiceCallConsultSessionKey(call) {
3826
3747
  return resolveVoiceCallSessionKey({
3827
3748
  config: call.config,
@@ -4101,8 +4022,8 @@ async function createVoiceCallRuntime(params) {
4101
4022
  if (provider.name === "twilio" && config.streaming?.enabled) {
4102
4023
  const twilioProvider = provider;
4103
4024
  if (ttsRuntime?.textToSpeechTelephony) try {
4104
- const ttsProvider = createTelephonyTtsProvider({
4105
- coreConfig,
4025
+ const ttsProvider = await createTelephonyTtsProvider({
4026
+ coreConfig: cfg,
4106
4027
  ttsOverride: config.tts,
4107
4028
  runtime: ttsRuntime,
4108
4029
  logger: log
@@ -1,2 +1,2 @@
1
- import { t as createVoiceCallRuntime } from "./runtime-entry-DSgyqIwE.js";
1
+ import { t as createVoiceCallRuntime } from "./runtime-entry-D8Mkwy8G.js";
2
2
  export { createVoiceCallRuntime };
@@ -1,4 +1,4 @@
1
- import { s as verifyTelnyxWebhook, t as guardedJsonApiRequest } from "./guarded-json-api-BKOgUU1q.js";
1
+ import { s as verifyTelnyxWebhook, t as guardedJsonApiRequest } from "./guarded-json-api-Dm4NvtGo.js";
2
2
  import crypto from "node:crypto";
3
3
  //#region extensions/voice-call/src/providers/telnyx.ts
4
4
  function normalizeTelnyxDirection(direction) {
@@ -1,9 +1,10 @@
1
1
  import { fetchWithSsrFGuard } from "./runtime-api.js";
2
2
  import "./api.js";
3
3
  import { n as requireSupportedTwilioApiHostname, r as resolveTwilioApiBaseUrl } from "./twilio-region-4fkgz3UG.js";
4
- import { _ as mapVoiceToPolly, a as normalizeProviderStatus, f as chunkAudio, g as escapeXml, i as mapProviderStatusToEndReason, o as getHeader, r as isProviderStatusTerminal } from "./runtime-entry-DSgyqIwE.js";
5
- import { c as verifyTwilioWebhook, i as readProviderJsonResponseText, n as cancelProviderResponseBody, r as readProviderErrorResponseSnippet, t as guardedJsonApiRequest } from "./guarded-json-api-BKOgUU1q.js";
4
+ import { _ as mapVoiceToPolly, a as normalizeProviderStatus, f as chunkAudio, g as escapeXml, i as mapProviderStatusToEndReason, o as getHeader, r as isProviderStatusTerminal } from "./runtime-entry-D8Mkwy8G.js";
5
+ import { c as verifyTwilioWebhook, i as readProviderJsonResponseText, n as cancelProviderResponseBody, r as readProviderErrorResponseSnippet, t as guardedJsonApiRequest } from "./guarded-json-api-Dm4NvtGo.js";
6
6
  import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
7
+ import { sleepWithAbort } from "openclaw/plugin-sdk/runtime-env";
7
8
  import crypto from "node:crypto";
8
9
  import { safeEqualSecret } from "openclaw/plugin-sdk/security-runtime";
9
10
  import { setTimeout as setTimeout$1 } from "node:timers/promises";
@@ -628,9 +629,12 @@ var TwilioProvider = class TwilioProvider {
628
629
  chunkAttempts += 1;
629
630
  if (sendAudioChunk(chunk).sent) chunkDelivered += 1;
630
631
  const waitMs = nextChunkDueAt - Date.now();
631
- if (waitMs > 0) await new Promise((resolve) => {
632
- setTimeout(resolve, Math.ceil(waitMs));
633
- });
632
+ if (waitMs > 0) try {
633
+ await sleepWithAbort(Math.ceil(waitMs), signal);
634
+ } catch (error) {
635
+ if (!signal.aborted) throw error;
636
+ break;
637
+ }
634
638
  nextChunkDueAt += CHUNK_DELAY_MS;
635
639
  if (signal.aborted) break;
636
640
  }
@@ -1,19 +1,19 @@
1
1
  {
2
2
  "name": "@openclaw/voice-call",
3
- "version": "2026.7.2-beta.2",
3
+ "version": "2026.7.2-beta.3",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@openclaw/voice-call",
9
- "version": "2026.7.2-beta.2",
9
+ "version": "2026.7.2-beta.3",
10
10
  "dependencies": {
11
11
  "typebox": "1.3.3",
12
12
  "ws": "8.21.0",
13
13
  "zod": "4.4.3"
14
14
  },
15
15
  "peerDependencies": {
16
- "openclaw": ">=2026.7.2-beta.2"
16
+ "openclaw": ">=2026.7.2-beta.3"
17
17
  },
18
18
  "peerDependenciesMeta": {
19
19
  "openclaw": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/voice-call",
3
- "version": "2026.7.2-beta.2",
3
+ "version": "2026.7.2-beta.3",
4
4
  "description": "OpenClaw voice-call plugin for Twilio, Telnyx, and Plivo phone calls.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -13,7 +13,7 @@
13
13
  "zod": "4.4.3"
14
14
  },
15
15
  "peerDependencies": {
16
- "openclaw": ">=2026.7.2-beta.2"
16
+ "openclaw": ">=2026.7.2-beta.3"
17
17
  },
18
18
  "peerDependenciesMeta": {
19
19
  "openclaw": {
@@ -30,10 +30,10 @@
30
30
  "minHostVersion": ">=2026.4.10"
31
31
  },
32
32
  "compat": {
33
- "pluginApi": ">=2026.7.2-beta.2"
33
+ "pluginApi": ">=2026.7.2-beta.3"
34
34
  },
35
35
  "build": {
36
- "openclawVersion": "2026.7.2-beta.2"
36
+ "openclawVersion": "2026.7.2-beta.3"
37
37
  },
38
38
  "release": {
39
39
  "publishToClawHub": true,