@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.
- package/dist/{cli-D7qQRwUE.js → cli-BGdihqyA.js} +1 -1
- package/dist/doctor-contract-api.js +1 -0
- package/dist/{guarded-json-api-Z9SuGiD_.js → guarded-json-api-3Jy2k-eX.js} +1 -1
- package/dist/index.js +2 -2
- package/dist/{plivo-qSdVTUcE.js → plivo-BsdFT1gS.js} +2 -2
- package/dist/{realtime-handler-BSW48gqM.js → realtime-handler-C7N4qzvf.js} +128 -10
- package/dist/{response-generator-IX26g0hi.js → response-generator-DsdC88Xg.js} +1 -1
- package/dist/{runtime-entry-B8SNb2qD.js → runtime-entry-Ln4SWaYK.js} +20 -16
- package/dist/runtime-entry.js +1 -1
- package/dist/{telnyx-B8shXuBM.js → telnyx-eCxGOUfA.js} +1 -1
- package/dist/{twilio-DhsTO8-E.js → twilio-DQKhnXXE.js} +2 -2
- package/openclaw.plugin.json +1 -0
- package/package.json +4 -4
|
@@ -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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
2
|
-
import { a as reconstructWebhookUrl, o as verifyPlivoWebhook, t as guardedJsonApiRequest } from "./guarded-json-api-
|
|
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-
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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-
|
|
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
|
|
239
|
-
echo
|
|
240
|
-
fable
|
|
241
|
-
onyx
|
|
242
|
-
nova
|
|
243
|
-
shimmer
|
|
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
|
|
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-
|
|
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
|
|
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-
|
|
3421
|
-
const loadTwilioProvider = createLazyRuntimeModule(() => import("./twilio-
|
|
3422
|
-
const loadPlivoProvider = createLazyRuntimeModule(() => import("./plivo-
|
|
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-
|
|
3429
|
+
const loadRealtimeHandler = createLazyRuntimeModule(() => import("./realtime-handler-C7N4qzvf.js"));
|
|
3426
3430
|
function resolveVoiceCallConsultSessionKey(call) {
|
|
3427
3431
|
return resolveVoiceCallSessionKey({
|
|
3428
3432
|
config: call.config,
|
package/dist/runtime-entry.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as createVoiceCallRuntime } from "./runtime-entry-
|
|
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-
|
|
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-
|
|
5
|
-
import { c as verifyTwilioWebhook, i as readVoiceCallProviderJsonResponse, n as cancelProviderResponseBody, r as readProviderErrorResponseSnippet, t as guardedJsonApiRequest } from "./guarded-json-api-
|
|
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";
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/voice-call",
|
|
3
|
-
"version": "2026.9.
|
|
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.
|
|
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.
|
|
34
|
+
"pluginApi": ">=2026.9.4"
|
|
35
35
|
},
|
|
36
36
|
"build": {
|
|
37
|
-
"openclawVersion": "2026.9.
|
|
37
|
+
"openclawVersion": "2026.9.4"
|
|
38
38
|
},
|
|
39
39
|
"release": {
|
|
40
40
|
"publishToClawHub": true,
|