@openclaw/voice-call 2026.9.3 → 2026.9.4

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.
@@ -1,7 +1,7 @@
1
1
  import { sleep } from "./runtime-api.js";
2
2
  import "./api.js";
3
3
  import { l as resolveVoiceCallStreamExposurePaths, u as validateProviderConfig } from "./config-DQwvsxeV.js";
4
- import { d as cleanupTailscaleExposureRoute, f as getTailscaleSelfInfo, p as setupTailscaleExposureRoutes, u as resolveWebhookExposureStatus, v as resolveVoiceCallAgentId, y as resolveUserPath } from "./runtime-entry-B8SNb2qD.js";
4
+ import { d as cleanupTailscaleExposureRoute, f as getTailscaleSelfInfo, p as setupTailscaleExposureRoutes, u as resolveWebhookExposureStatus, v as resolveVoiceCallAgentId, y as resolveUserPath } from "./runtime-entry-Ln4SWaYK.js";
5
5
  import { b as setVoiceCallStateRuntime, d as loadActiveCallsFromStore, l as findCallInStore, o as MAX_CALL_RECORD_EVENTS, t as resolveDefaultVoiceCallStoreDir, u as getCallHistoryFromStore } from "./store-path-M0GXxUfv.js";
6
6
  import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
7
7
  import { callGatewayFromCli, isGatewayClientRequestError, isGatewayTransportError, redactSensitiveUrlLikeString } from "openclaw/plugin-sdk/gateway-runtime";
@@ -69,6 +69,7 @@ function describeVoiceCallSchemaMigration(migration) {
69
69
  case "creator-namespace-v14": return "cron creators -> explicit principal namespaces";
70
70
  case "conversation-binding-targets-v15": return "conversation bindings -> exact target keys without agent/session projections";
71
71
  case "skill-workshop-directory-ownership-v16": return "Skill Workshop proposals -> per-agent Workshop directory ownership";
72
+ case "prepared-worker-ownership-v17": return "prepared workers -> one-use capacity and fixed workspace ownership";
72
73
  case "worker-placement-execution-mode-v8": return "cloud worker placements -> execution-mode claims";
73
74
  case "operator-approvals-system-agent": return "operator approvals -> OpenClaw system changes";
74
75
  case "session-watch-cursor-provenance-v4": return "session watch cursors -> provenance column";
@@ -1,6 +1,6 @@
1
1
  import { fetchWithSsrFGuard } from "./runtime-api.js";
2
2
  import "./api.js";
3
- import { l as getHeader, n as normalizeProxyIp } from "./runtime-entry-B8SNb2qD.js";
3
+ import { l as getHeader, n as normalizeProxyIp } from "./runtime-entry-Ln4SWaYK.js";
4
4
  import { n as reserveWebhookReplay, t as createWebhookReplayCache } from "./webhook-replay-CCQSoBNF.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
@@ -2,7 +2,7 @@ import { definePluginEntry } from "./runtime-api.js";
2
2
  import "./api.js";
3
3
  import { VOICE_CALL_CLI_DESCRIPTOR } from "./cli-output-mode.js";
4
4
  import { i as resolveVoiceCallConfig, t as VoiceCallConfigSchema, u as validateProviderConfig } from "./config-DQwvsxeV.js";
5
- import { t as createVoiceCallRuntime } from "./runtime-entry-B8SNb2qD.js";
5
+ import { t as createVoiceCallRuntime } from "./runtime-entry-Ln4SWaYK.js";
6
6
  import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
7
7
  import { ErrorCodes, errorShape } from "openclaw/plugin-sdk/gateway-runtime";
8
8
  import { resolveGlobalSingleton } from "openclaw/plugin-sdk/global-singleton";
@@ -514,7 +514,7 @@ var voice_call_default = definePluginEntry({
514
514
  }
515
515
  }));
516
516
  api.registerCli(async ({ program }) => {
517
- const { registerVoiceCallCli } = await import("./cli-D7qQRwUE.js");
517
+ const { registerVoiceCallCli } = await import("./cli-BGdihqyA.js");
518
518
  registerVoiceCallCli({
519
519
  program,
520
520
  config,
@@ -1,5 +1,5 @@
1
- import { b as escapeXml, l as getHeader } from "./runtime-entry-B8SNb2qD.js";
2
- import { a as reconstructWebhookUrl, o as verifyPlivoWebhook, t as guardedJsonApiRequest } from "./guarded-json-api-Z9SuGiD_.js";
1
+ import { b as escapeXml, l as getHeader } from "./runtime-entry-Ln4SWaYK.js";
2
+ import { a as reconstructWebhookUrl, o as verifyPlivoWebhook, t as guardedJsonApiRequest } from "./guarded-json-api-3Jy2k-eX.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,5 +1,5 @@
1
1
  import { d as REALTIME_VOICE_END_CALL_TOOL_NAME, s as resolveVoiceCallPublicPathPrefix } from "./config-DQwvsxeV.js";
2
- import { c as canonicalizeVoiceCallMediaBase64, o as WebSocket, s as WebSocketServer } from "./runtime-entry-B8SNb2qD.js";
2
+ import { c as canonicalizeVoiceCallMediaBase64, o as WebSocket, s as WebSocketServer } from "./runtime-entry-Ln4SWaYK.js";
3
3
  import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
4
4
  import { asNullableRecord, asOptionalObjectRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
5
5
  import { createSubsystemLogger } from "openclaw/plugin-sdk/runtime-env";
@@ -15,6 +15,8 @@ const TELEPHONY_CHUNK_BYTES = 160;
15
15
  const LEAD_MS = 160;
16
16
  const DEFAULT_MAX_QUEUED_AUDIO_BYTES = TELEPHONY_SAMPLE_RATE * 120;
17
17
  const QUEUE_COMPACT_HEAD_THRESHOLD = 256;
18
+ const MAX_PLAYBACK_SEGMENTS = 128;
19
+ const MAX_PENDING_MARK_BOUNDARIES = 64;
18
20
  /** Paces outgoing mulaw audio frames at telephony cadence. */
19
21
  var RealtimeAudioPacer = class {
20
22
  constructor(params) {
@@ -25,9 +27,18 @@ var RealtimeAudioPacer = class {
25
27
  this.queuedAudioBytes = 0;
26
28
  this.closed = false;
27
29
  this.streamClockMs = null;
30
+ this.playbackSegments = [];
31
+ this.sentAudioMs = 0;
32
+ this.retiredAudioMs = 0;
33
+ this.confirmedPlayedMs = 0;
34
+ this.playbackMarkPrefix = `openclaw-playout-${randomUUID()}`;
35
+ this.playbackMarkSequence = 0;
36
+ this.lastPlaybackMarkMs = 0;
37
+ this.markBoundaries = [];
38
+ this.retiredItemOffsets = /* @__PURE__ */ new Map();
28
39
  }
29
40
  /** Queue mulaw audio and split it into 20ms-ish telephony chunks. */
30
- sendAudio(muLaw) {
41
+ sendAudio(muLaw, metadata) {
31
42
  if (this.closed || muLaw.length === 0) return;
32
43
  const maxQueuedAudioBytes = this.params.maxQueuedAudioBytes ?? DEFAULT_MAX_QUEUED_AUDIO_BYTES;
33
44
  for (let offset = 0; offset < muLaw.length; offset += TELEPHONY_CHUNK_BYTES) {
@@ -36,15 +47,42 @@ var RealtimeAudioPacer = class {
36
47
  this.failBackpressure();
37
48
  return;
38
49
  }
50
+ const durationMs = chunk.length / 8;
51
+ const segment = this.extendPlaybackSegment(metadata?.itemId, durationMs);
52
+ if (!segment) {
53
+ this.failBackpressure();
54
+ return;
55
+ }
39
56
  this.queue.push({
40
57
  type: "audio",
41
58
  chunk,
42
- durationMs: chunk.length / 8
59
+ durationMs,
60
+ segment
43
61
  });
44
62
  this.queuedAudioBytes += chunk.length;
45
63
  }
46
64
  this.ensurePump();
47
65
  }
66
+ /**
67
+ * Retained provider items in playback order with consumed playout duration.
68
+ * Queued audio has not reached the telephony line, so unplayed items report zero.
69
+ */
70
+ getPlaybackState() {
71
+ let remaining = Math.max(0, this.confirmedPlayedMs - this.retiredAudioMs);
72
+ const playedByItem = /* @__PURE__ */ new Map();
73
+ for (const segment of this.playbackSegments) {
74
+ const consumed = Math.min(remaining, segment.sentMs);
75
+ remaining -= consumed;
76
+ if (segment.itemId !== void 0) {
77
+ const previousMs = playedByItem.get(segment.itemId) ?? this.retiredItemOffsets.get(segment.itemId) ?? 0;
78
+ playedByItem.set(segment.itemId, previousMs + consumed);
79
+ }
80
+ }
81
+ return Array.from(playedByItem, ([itemId, audioEndMs]) => ({
82
+ itemId,
83
+ audioEndMs: Math.floor(audioEndMs)
84
+ }));
85
+ }
48
86
  /** Queue a provider mark frame after prior audio frames. */
49
87
  sendMark(name) {
50
88
  if (this.closed || !name) return;
@@ -54,14 +92,32 @@ var RealtimeAudioPacer = class {
54
92
  });
55
93
  this.ensurePump();
56
94
  }
95
+ /** Retire the playback prefix once the carrier confirms playout reached the mark. */
96
+ acknowledgeMark(name) {
97
+ if (this.closed || !name) return;
98
+ const boundaryIndex = this.markBoundaries.findIndex((boundary) => boundary.name === name);
99
+ if (boundaryIndex < 0) return;
100
+ const boundary = this.markBoundaries[boundaryIndex];
101
+ this.markBoundaries.splice(0, boundaryIndex + 1);
102
+ if (!boundary) return;
103
+ while (this.playbackSegments.length > 0) {
104
+ const head = this.playbackSegments[0];
105
+ if (!head || head.sentMs < head.totalMs || head.lastSentEndMs === void 0 || head.lastSentEndMs > boundary.sentMs) break;
106
+ const retired = this.playbackSegments.shift();
107
+ if (retired) {
108
+ this.retiredAudioMs += retired.sentMs;
109
+ if (retired.itemId !== void 0) this.retiredItemOffsets.set(retired.itemId, (this.retiredItemOffsets.get(retired.itemId) ?? 0) + retired.sentMs);
110
+ }
111
+ }
112
+ this.confirmedPlayedMs = Math.max(this.confirmedPlayedMs, Math.min(boundary.sentMs, this.sentAudioMs));
113
+ }
57
114
  /** Clear queued audio and notify the provider stream. */
58
115
  clearAudio() {
59
116
  if (this.closed) return 0;
60
117
  const clearedAudioBytes = this.queuedAudioBytes;
61
118
  this.clearTimer();
62
119
  this.resetQueue();
63
- this.queuedAudioBytes = 0;
64
- this.streamClockMs = null;
120
+ this.resetPlaybackState();
65
121
  this.params.send(this.params.serializer.clear());
66
122
  return clearedAudioBytes;
67
123
  }
@@ -74,8 +130,33 @@ var RealtimeAudioPacer = class {
74
130
  this.closed = true;
75
131
  this.clearTimer();
76
132
  this.resetQueue();
133
+ this.resetPlaybackState();
134
+ }
135
+ extendPlaybackSegment(itemId, durationMs) {
136
+ let segment = this.playbackSegments.at(-1);
137
+ if (!segment || segment.itemId !== itemId) {
138
+ if (this.playbackSegments.length >= MAX_PLAYBACK_SEGMENTS) return null;
139
+ segment = {
140
+ itemId,
141
+ totalMs: 0,
142
+ sentMs: 0
143
+ };
144
+ this.playbackSegments.push(segment);
145
+ }
146
+ segment.totalMs += durationMs;
147
+ return segment;
148
+ }
149
+ resetPlaybackState() {
150
+ this.playbackSegments = [];
77
151
  this.queuedAudioBytes = 0;
152
+ this.sentAudioMs = 0;
153
+ this.retiredAudioMs = 0;
154
+ this.confirmedPlayedMs = 0;
155
+ this.lastPlaybackMarkMs = 0;
156
+ this.markBoundaries = [];
157
+ this.retiredItemOffsets.clear();
78
158
  this.streamClockMs = null;
159
+ this.params.onPlaybackReset?.();
79
160
  }
80
161
  /** Clear the scheduled pump timer. */
81
162
  clearTimer() {
@@ -123,7 +204,7 @@ var RealtimeAudioPacer = class {
123
204
  while (this.pendingQueueSize > 0 && this.streamClockMs < now + LEAD_MS) {
124
205
  const item = this.takeNextItem();
125
206
  if (!item) break;
126
- if (!(item.type === "audio" ? this.sendAudioItem(item) : this.params.send(this.params.serializer.mark(item.name)))) {
207
+ if (!(item.type === "audio" ? this.sendAudioItem(item) : this.sendMarkItem(item))) {
127
208
  this.resetQueue();
128
209
  this.queuedAudioBytes = 0;
129
210
  this.streamClockMs = null;
@@ -140,7 +221,30 @@ var RealtimeAudioPacer = class {
140
221
  sendAudioItem(item) {
141
222
  this.queuedAudioBytes = Math.max(0, this.queuedAudioBytes - item.chunk.length);
142
223
  const sent = this.params.send(this.params.serializer.media(item.chunk.toString("base64")));
224
+ if (sent) {
225
+ item.segment.sentMs += item.durationMs;
226
+ this.sentAudioMs += item.durationMs;
227
+ item.segment.lastSentEndMs = this.sentAudioMs;
228
+ }
143
229
  this.streamClockMs = (this.streamClockMs ?? performance.now()) + item.durationMs;
230
+ if (!sent || item.segment.itemId === void 0 || this.sentAudioMs - this.lastPlaybackMarkMs < LEAD_MS) return sent;
231
+ this.lastPlaybackMarkMs = this.sentAudioMs;
232
+ this.playbackMarkSequence += 1;
233
+ return this.sendMarkItem({
234
+ type: "mark",
235
+ name: `${this.playbackMarkPrefix}-${this.playbackMarkSequence}`
236
+ });
237
+ }
238
+ /** Send a queued mark frame and bind it to the playback prefix before it. */
239
+ sendMarkItem(item) {
240
+ const sent = this.params.send(this.params.serializer.mark(item.name));
241
+ if (sent) {
242
+ this.markBoundaries.push({
243
+ name: item.name,
244
+ sentMs: this.sentAudioMs
245
+ });
246
+ if (this.markBoundaries.length > MAX_PENDING_MARK_BOUNDARIES) this.markBoundaries.shift();
247
+ }
144
248
  return sent;
145
249
  }
146
250
  };
@@ -572,7 +676,8 @@ var RealtimeCallHandler = class {
572
676
  return;
573
677
  }
574
678
  if (frame.kind === "mark") {
575
- telephonyBinding.bridge.acknowledgeMark();
679
+ telephonyBinding.acknowledgeCarrierMark(frame.name);
680
+ telephonyBinding.bridge.acknowledgeMark(frame.name);
576
681
  return;
577
682
  }
578
683
  if (frame.kind === "error") {
@@ -811,7 +916,9 @@ var RealtimeCallHandler = class {
811
916
  }
812
917
  return true;
813
918
  };
919
+ const pendingMarkAcks = /* @__PURE__ */ new Map();
814
920
  const audioPacer = new RealtimeAudioPacer({
921
+ onPlaybackReset: () => pendingMarkAcks.clear(),
815
922
  send: sendString,
816
923
  serializer: {
817
924
  media: (payload) => adapter.serializeMedia(payload),
@@ -847,10 +954,11 @@ var RealtimeCallHandler = class {
847
954
  triggerGreetingOnReady: Boolean(initialGreetingInstructions),
848
955
  audioSink: {
849
956
  isOpen: () => ws.readyState === WebSocket.OPEN,
850
- sendAudio: (muLaw) => {
957
+ sendAudio: (muLaw, metadata) => {
851
958
  harness.recordOutputAudio(muLaw);
852
- audioPacer.sendAudio(muLaw);
959
+ audioPacer.sendAudio(muLaw, metadata);
853
960
  },
961
+ getPlaybackState: () => audioPacer.getPlaybackState(),
854
962
  clearAudio: (reason) => {
855
963
  harness.flushOutput(() => {
856
964
  const clearedBytes = audioPacer.clearAudio();
@@ -862,8 +970,9 @@ var RealtimeCallHandler = class {
862
970
  harness.finishOutputAudio("clear");
863
971
  });
864
972
  },
865
- sendMark: (markName) => {
973
+ sendMark: (markName, acknowledge) => {
866
974
  audioPacer.sendMark(markName);
975
+ if (markName && acknowledge) pendingMarkAcks.set(markName, acknowledge);
867
976
  }
868
977
  },
869
978
  onTranscript: (role, text, isFinal) => {
@@ -1132,6 +1241,15 @@ var RealtimeCallHandler = class {
1132
1241
  };
1133
1242
  const telephonyBinding = {
1134
1243
  bridge: session,
1244
+ acknowledgeCarrierMark: (markName) => {
1245
+ audioPacer.acknowledgeMark(markName);
1246
+ if (!markName) return;
1247
+ const acknowledge = pendingMarkAcks.get(markName);
1248
+ if (acknowledge) {
1249
+ pendingMarkAcks.delete(markName);
1250
+ acknowledge();
1251
+ }
1252
+ },
1135
1253
  close: (cause) => closeBinding(telephonyBinding, cause),
1136
1254
  endCall: () => {
1137
1255
  closeBinding(telephonyBinding);
@@ -1,5 +1,5 @@
1
1
  import { c as resolveVoiceCallSessionKey } from "./config-DQwvsxeV.js";
2
- import { _ as resolveCallAgentId, g as resolveVoiceResponseModel } from "./runtime-entry-B8SNb2qD.js";
2
+ import { _ as resolveCallAgentId, g as resolveVoiceResponseModel } from "./runtime-entry-Ln4SWaYK.js";
3
3
  import { filterStringEntries, isRecord, normalizeLowercaseStringOrEmpty, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
4
4
  import { resolveAgentConfig } from "openclaw/plugin-sdk/agent-scope-runtime";
5
5
  import { isValidAgentHarnessSessionStoreEntry } from "openclaw/plugin-sdk/session-store-runtime";
@@ -25,6 +25,7 @@ import { runCommandWithTimeout } from "openclaw/plugin-sdk/process-runtime";
25
25
  import http from "node:http";
26
26
  import { URL as URL$1 } from "node:url";
27
27
  import { resolveConfiguredCapabilityProvider } from "openclaw/plugin-sdk/provider-selection-runtime";
28
+ import { rejectWebSocketUpgrade } from "openclaw/plugin-sdk/websocket-runtime";
28
29
  import { canonicalizeBase64 } from "openclaw/plugin-sdk/media-runtime";
29
30
  //#region extensions/voice-call/src/allowlist.ts
30
31
  /** Normalize a phone number to digits only. */
@@ -234,14 +235,14 @@ function escapeXml(text) {
234
235
  /**
235
236
  * Map of OpenAI voice names to similar Twilio Polly voices.
236
237
  */
237
- const OPENAI_TO_POLLY_MAP = {
238
- alloy: "Polly.Joanna",
239
- echo: "Polly.Matthew",
240
- fable: "Polly.Amy",
241
- onyx: "Polly.Brian",
242
- nova: "Polly.Salli",
243
- shimmer: "Polly.Kimberly"
244
- };
238
+ const OPENAI_TO_POLLY_MAP = /* @__PURE__ */ new Map([
239
+ ["alloy", "Polly.Joanna"],
240
+ ["echo", "Polly.Matthew"],
241
+ ["fable", "Polly.Amy"],
242
+ ["onyx", "Polly.Brian"],
243
+ ["nova", "Polly.Salli"],
244
+ ["shimmer", "Polly.Kimberly"]
245
+ ]);
245
246
  /**
246
247
  * Default Polly voice when no mapping is found.
247
248
  */
@@ -256,7 +257,7 @@ const DEFAULT_POLLY_VOICE = "Polly.Joanna";
256
257
  function mapVoiceToPolly(voice) {
257
258
  if (!voice) return DEFAULT_POLLY_VOICE;
258
259
  if (voice.startsWith("Polly.") || voice.startsWith("Google.")) return voice;
259
- return OPENAI_TO_POLLY_MAP[normalizeLowercaseStringOrEmpty(voice)] || DEFAULT_POLLY_VOICE;
260
+ return OPENAI_TO_POLLY_MAP.get(normalizeLowercaseStringOrEmpty(voice)) ?? DEFAULT_POLLY_VOICE;
260
261
  }
261
262
  //#endregion
262
263
  //#region extensions/voice-call/src/manager/twiml.ts
@@ -2592,7 +2593,7 @@ function normalizeProviderStatus(status) {
2592
2593
  /** Map terminal provider status strings to OpenClaw end reasons. */
2593
2594
  function mapProviderStatusToEndReason(status) {
2594
2595
  const normalized = normalizeProviderStatus(status);
2595
- return TERMINAL_PROVIDER_STATUS_TO_END_REASON[normalized] ?? null;
2596
+ return Object.hasOwn(TERMINAL_PROVIDER_STATUS_TO_END_REASON, normalized) ? TERMINAL_PROVIDER_STATUS_TO_END_REASON[normalized] ?? null : null;
2596
2597
  }
2597
2598
  /** Return true when a provider status is terminal. */
2598
2599
  function isProviderStatusTerminal(status) {
@@ -2691,7 +2692,7 @@ const MAX_WEBHOOK_BODY_BYTES = WEBHOOK_BODY_READ_DEFAULTS.preAuth.maxBytes;
2691
2692
  const WEBHOOK_BODY_TIMEOUT_MS = WEBHOOK_BODY_READ_DEFAULTS.preAuth.timeoutMs;
2692
2693
  const MISSING_REMOTE_ADDRESS_IN_FLIGHT_KEY = "__voice_call_no_remote__";
2693
2694
  const loadRealtimeTranscriptionRuntime = createLazyRuntimeModule(() => import("./realtime-transcription.runtime-CbJAs5t_.js"));
2694
- const loadResponseGeneratorModule = createLazyRuntimeModule(() => import("./response-generator-IX26g0hi.js"));
2695
+ const loadResponseGeneratorModule = createLazyRuntimeModule(() => import("./response-generator-DsdC88Xg.js"));
2695
2696
  function appendRecentTalkEventMetadata(call, event) {
2696
2697
  const metadata = call.metadata ?? {};
2697
2698
  const recent = Array.isArray(metadata.recentTalkEvents) ? metadata.recentTalkEvents.filter((entry) => Boolean(entry) && typeof entry === "object" && !Array.isArray(entry)) : [];
@@ -3001,7 +3002,10 @@ var VoiceCallWebhookServer = class {
3001
3002
  return;
3002
3003
  }
3003
3004
  if (this.getUpgradePathname(request) === streamPath && this.mediaStreamHandler) this.mediaStreamHandler?.handleUpgrade(request, socket, head);
3004
- else socket.destroy();
3005
+ else {
3006
+ socket.once("error", () => {});
3007
+ rejectWebSocketUpgrade(socket, { status: 404 });
3008
+ }
3005
3009
  });
3006
3010
  this.server.on("error", (err) => {
3007
3011
  this.server = null;
@@ -3417,12 +3421,12 @@ const REALTIME_VOICE_CONSULT_SYSTEM_PROMPT = [
3417
3421
  "Do not print secret values or dump environment variables; only check whether required configuration is present.",
3418
3422
  "Be accurate, brief, and speakable."
3419
3423
  ].join(" ");
3420
- const loadTelnyxProvider = createLazyRuntimeModule(() => import("./telnyx-B8shXuBM.js"));
3421
- const loadTwilioProvider = createLazyRuntimeModule(() => import("./twilio-DhsTO8-E.js"));
3422
- const loadPlivoProvider = createLazyRuntimeModule(() => import("./plivo-qSdVTUcE.js"));
3424
+ const loadTelnyxProvider = createLazyRuntimeModule(() => import("./telnyx-eCxGOUfA.js"));
3425
+ const loadTwilioProvider = createLazyRuntimeModule(() => import("./twilio-DQKhnXXE.js"));
3426
+ const loadPlivoProvider = createLazyRuntimeModule(() => import("./plivo-BsdFT1gS.js"));
3423
3427
  const loadMockProvider = createLazyRuntimeModule(() => import("./mock-C57qj18W.js"));
3424
3428
  const loadRealtimeVoiceRuntime = createLazyRuntimeModule(() => import("./realtime-voice.runtime-vtdCOWg-.js"));
3425
- const loadRealtimeHandler = createLazyRuntimeModule(() => import("./realtime-handler-BSW48gqM.js"));
3429
+ const loadRealtimeHandler = createLazyRuntimeModule(() => import("./realtime-handler-C7N4qzvf.js"));
3426
3430
  function resolveVoiceCallConsultSessionKey(call) {
3427
3431
  return resolveVoiceCallSessionKey({
3428
3432
  config: call.config,
@@ -1,2 +1,2 @@
1
- import { t as createVoiceCallRuntime } from "./runtime-entry-B8SNb2qD.js";
1
+ import { t as createVoiceCallRuntime } from "./runtime-entry-Ln4SWaYK.js";
2
2
  export { createVoiceCallRuntime };
@@ -1,4 +1,4 @@
1
- import { s as verifyTelnyxWebhook, t as guardedJsonApiRequest } from "./guarded-json-api-Z9SuGiD_.js";
1
+ import { s as verifyTelnyxWebhook, t as guardedJsonApiRequest } from "./guarded-json-api-3Jy2k-eX.js";
2
2
  import crypto from "node:crypto";
3
3
  //#region extensions/voice-call/src/providers/telnyx.ts
4
4
  function normalizeTelnyxDirection(direction) {
@@ -1,8 +1,8 @@
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 { a as normalizeProviderStatus, b as escapeXml, h as chunkAudio, i as mapProviderStatusToEndReason, l as getHeader, r as isProviderStatusTerminal, x as mapVoiceToPolly } from "./runtime-entry-B8SNb2qD.js";
5
- import { c as verifyTwilioWebhook, i as readVoiceCallProviderJsonResponse, n as cancelProviderResponseBody, r as readProviderErrorResponseSnippet, t as guardedJsonApiRequest } from "./guarded-json-api-Z9SuGiD_.js";
4
+ import { a as normalizeProviderStatus, b as escapeXml, h as chunkAudio, i as mapProviderStatusToEndReason, l as getHeader, r as isProviderStatusTerminal, x as mapVoiceToPolly } from "./runtime-entry-Ln4SWaYK.js";
5
+ import { c as verifyTwilioWebhook, i as readVoiceCallProviderJsonResponse, n as cancelProviderResponseBody, r as readProviderErrorResponseSnippet, t as guardedJsonApiRequest } from "./guarded-json-api-3Jy2k-eX.js";
6
6
  import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
7
7
  import { sleepWithAbort } from "openclaw/plugin-sdk/runtime-env";
8
8
  import crypto from "node:crypto";
@@ -1,5 +1,6 @@
1
1
  {
2
2
  "id": "voice-call",
3
+ "categories": ["voice", "tools"],
3
4
  "doctorContract": {
4
5
  "resolveSessionStoreAgentIds": true,
5
6
  "stateMigrations": [{ "id": "voice-call-calls-jsonl-to-plugin-state" }]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/voice-call",
3
- "version": "2026.9.3",
3
+ "version": "2026.9.4",
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.9.3"
16
+ "openclaw": ">=2026.9.4"
17
17
  },
18
18
  "peerDependenciesMeta": {
19
19
  "openclaw": {
@@ -31,10 +31,10 @@
31
31
  "minHostVersion": ">=2026.4.10"
32
32
  },
33
33
  "compat": {
34
- "pluginApi": ">=2026.9.3"
34
+ "pluginApi": ">=2026.9.4"
35
35
  },
36
36
  "build": {
37
- "openclawVersion": "2026.9.3"
37
+ "openclawVersion": "2026.9.4"
38
38
  },
39
39
  "release": {
40
40
  "publishToClawHub": true,