@openclaw/voice-call 2026.5.7 → 2026.5.10-beta.1
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/README.md +2 -0
- package/dist/{config-7w04YpHh.js → config-D1yqfwMq.js} +73 -0
- package/dist/{config-compat-B0me39_4.js → config-compat-DphZfbFl.js} +1 -1
- package/dist/{guarded-json-api-Btx5EE4w.js → guarded-json-api-C2JLyCeV.js} +2 -7
- package/dist/index.js +51 -9
- package/dist/{plivo-B-a7KFoT.js → plivo-3Lcw84OQ.js} +1 -1
- package/dist/realtime-handler-B_aqJXZj.js +1054 -0
- package/dist/{response-generator-BrcmwDZU.js → response-generator-WvI4YRlo.js} +1 -1
- package/dist/{runtime-entry-DFzuGKLG.js → runtime-entry-CVTYOHsM.js} +306 -129
- package/dist/runtime-entry.js +1 -1
- package/dist/setup-api.js +1 -1
- package/dist/{telnyx-jjBE8boz.js → telnyx-C-Byj00a.js} +1 -1
- package/dist/{twilio-1OqbcXLL.js → twilio-COJM-9Sh.js} +1 -1
- package/openclaw.plugin.json +80 -0
- package/package.json +5 -5
- package/dist/realtime-handler-C-SaPrny.js +0 -490
package/README.md
CHANGED
|
@@ -106,6 +106,8 @@ Notes:
|
|
|
106
106
|
- advanced webhook, streaming, and tunnel notes: `https://docs.openclaw.ai/plugins/voice-call`
|
|
107
107
|
- `responseModel` is optional. When unset, voice responses use the runtime default model.
|
|
108
108
|
- `sessionScope` defaults to `per-phone`, preserving caller memory across calls. Use `per-call` for reception, booking, IVR, and bridge flows where each carrier call should start fresh.
|
|
109
|
+
- `realtime.consultThinkingLevel` is optional. When set, it overrides the thinking level used by the model behind realtime `openclaw_agent_consult` calls.
|
|
110
|
+
- `realtime.consultFastMode` is optional. When set, it toggles fast mode for realtime `openclaw_agent_consult` calls.
|
|
109
111
|
|
|
110
112
|
## Stale call reaper
|
|
111
113
|
|
|
@@ -189,6 +189,11 @@ const RealtimeToolSchema = z.object({
|
|
|
189
189
|
}).strict();
|
|
190
190
|
const VoiceCallRealtimeProvidersConfigSchema = z.record(z.string(), z.record(z.string(), z.unknown())).default({});
|
|
191
191
|
const VoiceCallRealtimeToolPolicySchema = z.enum(REALTIME_VOICE_AGENT_CONSULT_TOOL_POLICIES);
|
|
192
|
+
const VoiceCallRealtimeConsultPolicySchema = z.enum([
|
|
193
|
+
"auto",
|
|
194
|
+
"substantive",
|
|
195
|
+
"always"
|
|
196
|
+
]);
|
|
192
197
|
const VoiceCallRealtimeFastContextSourceSchema = z.enum(["memory", "sessions"]);
|
|
193
198
|
const VoiceCallRealtimeFastContextConfigSchema = z.object({
|
|
194
199
|
/** Enable bounded memory/session lookup before the full consult agent. */
|
|
@@ -208,6 +213,45 @@ const VoiceCallRealtimeFastContextConfigSchema = z.object({
|
|
|
208
213
|
sources: ["memory", "sessions"],
|
|
209
214
|
fallbackToConsult: false
|
|
210
215
|
});
|
|
216
|
+
const VoiceCallRealtimeAgentContextConfigSchema = z.object({
|
|
217
|
+
/** Inject a compact agent persona/context capsule into realtime voice instructions. */
|
|
218
|
+
enabled: z.boolean().default(false),
|
|
219
|
+
/** Maximum number of characters from the generated capsule to append. */
|
|
220
|
+
maxChars: z.number().int().positive().default(6e3),
|
|
221
|
+
/** Include configured agent identity fields. */
|
|
222
|
+
includeIdentity: z.boolean().default(true),
|
|
223
|
+
/** Include agents.defaults/list systemPromptOverride when configured. */
|
|
224
|
+
includeSystemPrompt: z.boolean().default(true),
|
|
225
|
+
/** Include selected workspace files such as SOUL.md and IDENTITY.md. */
|
|
226
|
+
includeWorkspaceFiles: z.boolean().default(true),
|
|
227
|
+
/** Workspace-relative files to include, bounded by maxChars. */
|
|
228
|
+
files: z.array(z.string().min(1)).default([
|
|
229
|
+
"SOUL.md",
|
|
230
|
+
"IDENTITY.md",
|
|
231
|
+
"USER.md"
|
|
232
|
+
])
|
|
233
|
+
}).strict().default({
|
|
234
|
+
enabled: false,
|
|
235
|
+
maxChars: 6e3,
|
|
236
|
+
includeIdentity: true,
|
|
237
|
+
includeSystemPrompt: true,
|
|
238
|
+
includeWorkspaceFiles: true,
|
|
239
|
+
files: [
|
|
240
|
+
"SOUL.md",
|
|
241
|
+
"IDENTITY.md",
|
|
242
|
+
"USER.md"
|
|
243
|
+
]
|
|
244
|
+
});
|
|
245
|
+
const VoiceCallRealtimeConsultThinkingLevelSchema = z.enum([
|
|
246
|
+
"off",
|
|
247
|
+
"minimal",
|
|
248
|
+
"low",
|
|
249
|
+
"medium",
|
|
250
|
+
"high",
|
|
251
|
+
"xhigh",
|
|
252
|
+
"adaptive",
|
|
253
|
+
"max"
|
|
254
|
+
]);
|
|
211
255
|
const VoiceCallStreamingProvidersConfigSchema = z.record(z.string(), z.record(z.string(), z.unknown())).default({});
|
|
212
256
|
const VoiceCallRealtimeConfigSchema = z.object({
|
|
213
257
|
/** Enable realtime voice-to-voice mode. */
|
|
@@ -220,16 +264,25 @@ const VoiceCallRealtimeConfigSchema = z.object({
|
|
|
220
264
|
instructions: z.string().default(DEFAULT_VOICE_CALL_REALTIME_INSTRUCTIONS),
|
|
221
265
|
/** Tool policy for the shared OpenClaw agent consult tool. */
|
|
222
266
|
toolPolicy: VoiceCallRealtimeToolPolicySchema.default("safe-read-only"),
|
|
267
|
+
/** Guidance for when the realtime model should call the OpenClaw agent consult tool. */
|
|
268
|
+
consultPolicy: VoiceCallRealtimeConsultPolicySchema.default("auto"),
|
|
269
|
+
/** Optional thinking level override for the regular agent behind realtime consults. */
|
|
270
|
+
consultThinkingLevel: VoiceCallRealtimeConsultThinkingLevelSchema.optional(),
|
|
271
|
+
/** Optional fast mode override for the regular agent behind realtime consults. */
|
|
272
|
+
consultFastMode: z.boolean().optional(),
|
|
223
273
|
/** Tool definitions exposed to the realtime provider. */
|
|
224
274
|
tools: z.array(RealtimeToolSchema).default([]),
|
|
225
275
|
/** Low-latency memory/session context for the consult tool. */
|
|
226
276
|
fastContext: VoiceCallRealtimeFastContextConfigSchema,
|
|
277
|
+
/** Bounded agent persona/context injection for the fast realtime voice path. */
|
|
278
|
+
agentContext: VoiceCallRealtimeAgentContextConfigSchema,
|
|
227
279
|
/** Provider-owned raw config blobs keyed by provider id. */
|
|
228
280
|
providers: VoiceCallRealtimeProvidersConfigSchema
|
|
229
281
|
}).strict().default({
|
|
230
282
|
enabled: false,
|
|
231
283
|
instructions: DEFAULT_VOICE_CALL_REALTIME_INSTRUCTIONS,
|
|
232
284
|
toolPolicy: "safe-read-only",
|
|
285
|
+
consultPolicy: "auto",
|
|
233
286
|
tools: [],
|
|
234
287
|
fastContext: {
|
|
235
288
|
enabled: false,
|
|
@@ -238,6 +291,18 @@ const VoiceCallRealtimeConfigSchema = z.object({
|
|
|
238
291
|
sources: ["memory", "sessions"],
|
|
239
292
|
fallbackToConsult: false
|
|
240
293
|
},
|
|
294
|
+
agentContext: {
|
|
295
|
+
enabled: false,
|
|
296
|
+
maxChars: 6e3,
|
|
297
|
+
includeIdentity: true,
|
|
298
|
+
includeSystemPrompt: true,
|
|
299
|
+
includeWorkspaceFiles: true,
|
|
300
|
+
files: [
|
|
301
|
+
"SOUL.md",
|
|
302
|
+
"IDENTITY.md",
|
|
303
|
+
"USER.md"
|
|
304
|
+
]
|
|
305
|
+
},
|
|
241
306
|
providers: {}
|
|
242
307
|
});
|
|
243
308
|
const VoiceCallStreamingConfigSchema = z.object({
|
|
@@ -423,6 +488,11 @@ function normalizeVoiceCallConfig(config) {
|
|
|
423
488
|
...config.realtime?.fastContext,
|
|
424
489
|
sources: config.realtime?.fastContext?.sources ?? defaults.realtime.fastContext.sources
|
|
425
490
|
};
|
|
491
|
+
const realtimeAgentContext = {
|
|
492
|
+
...defaults.realtime.agentContext,
|
|
493
|
+
...config.realtime?.agentContext,
|
|
494
|
+
files: config.realtime?.agentContext?.files ?? defaults.realtime.agentContext.files
|
|
495
|
+
};
|
|
426
496
|
return {
|
|
427
497
|
...defaults,
|
|
428
498
|
...config,
|
|
@@ -459,7 +529,10 @@ function normalizeVoiceCallConfig(config) {
|
|
|
459
529
|
provider: realtimeProvider,
|
|
460
530
|
streamPath: config.realtime?.streamPath ?? defaultRealtimeStreamPathForServePath(serve.path ?? defaults.serve.path),
|
|
461
531
|
tools: config.realtime?.tools ?? defaults.realtime.tools,
|
|
532
|
+
consultThinkingLevel: VoiceCallRealtimeConsultThinkingLevelSchema.optional().parse(config.realtime?.consultThinkingLevel ?? defaults.realtime.consultThinkingLevel),
|
|
533
|
+
consultFastMode: config.realtime?.consultFastMode ?? defaults.realtime.consultFastMode,
|
|
462
534
|
fastContext: realtimeFastContext,
|
|
535
|
+
agentContext: realtimeAgentContext,
|
|
463
536
|
providers: realtimeProviders
|
|
464
537
|
},
|
|
465
538
|
tts: normalizeVoiceCallTtsConfig(defaults.tts, config.tts)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as VoiceCallConfigSchema } from "./config-
|
|
1
|
+
import { t as VoiceCallConfigSchema } from "./config-D1yqfwMq.js";
|
|
2
2
|
import { asOptionalRecord, readStringField } from "openclaw/plugin-sdk/text-runtime";
|
|
3
3
|
//#region extensions/voice-call/src/config-compat.ts
|
|
4
4
|
const VOICE_CALL_LEGACY_CONFIG_REMOVAL_VERSION = "2026.6.0";
|
|
@@ -2,6 +2,7 @@ import { fetchWithSsrFGuard } from "./runtime-api.js";
|
|
|
2
2
|
import "./api.js";
|
|
3
3
|
import { t as getHeader } from "./http-headers-BrnxBasF.js";
|
|
4
4
|
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
5
|
+
import { isLoopbackHost } from "openclaw/plugin-sdk/gateway-runtime";
|
|
5
6
|
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
|
|
6
7
|
import crypto from "node:crypto";
|
|
7
8
|
import { safeEqualSecret } from "openclaw/plugin-sdk/security-runtime";
|
|
@@ -194,12 +195,6 @@ function buildTwilioVerificationUrl(ctx, publicUrl, urlOptions) {
|
|
|
194
195
|
return publicUrl;
|
|
195
196
|
}
|
|
196
197
|
}
|
|
197
|
-
function isLoopbackAddress(address) {
|
|
198
|
-
if (!address) return false;
|
|
199
|
-
if (address === "127.0.0.1" || address === "::1") return true;
|
|
200
|
-
if (address.startsWith("::ffff:127.")) return true;
|
|
201
|
-
return false;
|
|
202
|
-
}
|
|
203
198
|
function stripPortFromUrl(url) {
|
|
204
199
|
try {
|
|
205
200
|
const parsed = new URL(url);
|
|
@@ -336,7 +331,7 @@ function verifyTwilioWebhook(ctx, authToken, options) {
|
|
|
336
331
|
ok: false,
|
|
337
332
|
reason: "Missing X-Twilio-Signature header"
|
|
338
333
|
};
|
|
339
|
-
const isLoopback =
|
|
334
|
+
const isLoopback = isLoopbackHost(options?.remoteIP ?? ctx.remoteAddress ?? "");
|
|
340
335
|
const allowLoopbackForwarding = options?.allowNgrokFreeTierLoopbackBypass && isLoopback;
|
|
341
336
|
const verificationUrl = buildTwilioVerificationUrl(ctx, options?.publicUrl, {
|
|
342
337
|
allowedHosts: options?.allowedHosts,
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { definePluginEntry, sleep } from "./runtime-api.js";
|
|
2
2
|
import "./api.js";
|
|
3
|
-
import { i as resolveVoiceCallConfig, s as validateProviderConfig } from "./config-
|
|
4
|
-
import { a as setupTailscaleExposureRoute, i as getTailscaleSelfInfo, n as resolveWebhookExposureStatus, r as cleanupTailscaleExposureRoute, s as resolveUserPath, t as createVoiceCallRuntime } from "./runtime-entry-
|
|
5
|
-
import { i as parseVoiceCallPluginConfig, r as normalizeVoiceCallLegacyConfigInput, t as formatVoiceCallLegacyConfigWarnings } from "./config-compat-
|
|
3
|
+
import { i as resolveVoiceCallConfig, s as validateProviderConfig } from "./config-D1yqfwMq.js";
|
|
4
|
+
import { a as setupTailscaleExposureRoute, i as getTailscaleSelfInfo, n as resolveWebhookExposureStatus, r as cleanupTailscaleExposureRoute, s as resolveUserPath, t as createVoiceCallRuntime } from "./runtime-entry-CVTYOHsM.js";
|
|
5
|
+
import { i as parseVoiceCallPluginConfig, r as normalizeVoiceCallLegacyConfigInput, t as formatVoiceCallLegacyConfigWarnings } from "./config-compat-DphZfbFl.js";
|
|
6
6
|
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
7
7
|
import { ErrorCodes, callGatewayFromCli, errorShape } from "openclaw/plugin-sdk/gateway-runtime";
|
|
8
8
|
import { normalizeOptionalLowercaseString, normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
|
|
@@ -732,6 +732,11 @@ const voiceCallConfigSchema = {
|
|
|
732
732
|
help: "Controls the shared openclaw_agent_consult tool.",
|
|
733
733
|
advanced: true
|
|
734
734
|
},
|
|
735
|
+
"realtime.consultPolicy": {
|
|
736
|
+
label: "Realtime Consult Policy",
|
|
737
|
+
help: "Guides when the realtime voice model should call openclaw_agent_consult.",
|
|
738
|
+
advanced: true
|
|
739
|
+
},
|
|
735
740
|
"realtime.fastContext.enabled": {
|
|
736
741
|
label: "Enable Fast Realtime Context",
|
|
737
742
|
help: "Searches memory/session context before the full consult agent.",
|
|
@@ -753,6 +758,31 @@ const voiceCallConfigSchema = {
|
|
|
753
758
|
label: "Fallback To Full Consult",
|
|
754
759
|
advanced: true
|
|
755
760
|
},
|
|
761
|
+
"realtime.agentContext.enabled": {
|
|
762
|
+
label: "Enable Agent Voice Context",
|
|
763
|
+
help: "Injects a compact agent identity, system prompt, and workspace context capsule into realtime voice instructions.",
|
|
764
|
+
advanced: true
|
|
765
|
+
},
|
|
766
|
+
"realtime.agentContext.maxChars": {
|
|
767
|
+
label: "Agent Voice Context Limit",
|
|
768
|
+
advanced: true
|
|
769
|
+
},
|
|
770
|
+
"realtime.agentContext.includeIdentity": {
|
|
771
|
+
label: "Include Agent Identity",
|
|
772
|
+
advanced: true
|
|
773
|
+
},
|
|
774
|
+
"realtime.agentContext.includeSystemPrompt": {
|
|
775
|
+
label: "Include Agent System Prompt",
|
|
776
|
+
advanced: true
|
|
777
|
+
},
|
|
778
|
+
"realtime.agentContext.includeWorkspaceFiles": {
|
|
779
|
+
label: "Include Agent Workspace Files",
|
|
780
|
+
advanced: true
|
|
781
|
+
},
|
|
782
|
+
"realtime.agentContext.files": {
|
|
783
|
+
label: "Agent Voice Context Files",
|
|
784
|
+
advanced: true
|
|
785
|
+
},
|
|
756
786
|
"realtime.providers": {
|
|
757
787
|
label: "Realtime Provider Config",
|
|
758
788
|
advanced: true
|
|
@@ -804,6 +834,8 @@ const VoiceCallToolSchema = Type.Union([
|
|
|
804
834
|
to: Type.Optional(Type.String({ description: "Call target" })),
|
|
805
835
|
message: Type.String({ description: "Intro message" }),
|
|
806
836
|
mode: Type.Optional(Type.Union([Type.Literal("notify"), Type.Literal("conversation")])),
|
|
837
|
+
sessionKey: Type.Optional(Type.String({ description: "OpenClaw session key for the call" })),
|
|
838
|
+
requesterSessionKey: Type.Optional(Type.String({ description: "OpenClaw session key that initiated the call" })),
|
|
807
839
|
dtmfSequence: Type.Optional(Type.String({ description: "DTMF digits to play before connect" }))
|
|
808
840
|
}),
|
|
809
841
|
Type.Object({
|
|
@@ -834,6 +866,8 @@ const VoiceCallToolSchema = Type.Union([
|
|
|
834
866
|
to: Type.Optional(Type.String({ description: "Call target" })),
|
|
835
867
|
sid: Type.Optional(Type.String({ description: "Call SID" })),
|
|
836
868
|
message: Type.Optional(Type.String({ description: "Optional intro message" })),
|
|
869
|
+
sessionKey: Type.Optional(Type.String({ description: "OpenClaw session key for the call" })),
|
|
870
|
+
requesterSessionKey: Type.Optional(Type.String({ description: "OpenClaw session key that initiated the call" })),
|
|
837
871
|
dtmfSequence: Type.Optional(Type.String({ description: "DTMF digits to play before connect" }))
|
|
838
872
|
})
|
|
839
873
|
]);
|
|
@@ -937,10 +971,11 @@ var voice_call_default = definePluginEntry({
|
|
|
937
971
|
return { error: await describeHistoricalCall(rt, callId) ?? "Call not found" };
|
|
938
972
|
};
|
|
939
973
|
const initiateCallAndRespond = async (params) => {
|
|
940
|
-
const result = await params.rt.manager.initiateCall(params.to,
|
|
974
|
+
const result = await params.rt.manager.initiateCall(params.to, params.sessionKey, {
|
|
941
975
|
message: params.message,
|
|
942
976
|
mode: params.mode,
|
|
943
|
-
dtmfSequence: params.dtmfSequence
|
|
977
|
+
dtmfSequence: params.dtmfSequence,
|
|
978
|
+
...params.requesterSessionKey ? { requesterSessionKey: params.requesterSessionKey } : {}
|
|
944
979
|
});
|
|
945
980
|
if (!result.success) {
|
|
946
981
|
respondError(params.respond, result.error || "initiate failed");
|
|
@@ -985,7 +1020,9 @@ var voice_call_default = definePluginEntry({
|
|
|
985
1020
|
respond,
|
|
986
1021
|
to,
|
|
987
1022
|
message,
|
|
988
|
-
mode: params?.mode === "notify" || params?.mode === "conversation" ? params.mode : void 0
|
|
1023
|
+
mode: params?.mode === "notify" || params?.mode === "conversation" ? params.mode : void 0,
|
|
1024
|
+
sessionKey: normalizeOptionalString(params?.sessionKey),
|
|
1025
|
+
requesterSessionKey: normalizeOptionalString(params?.requesterSessionKey)
|
|
989
1026
|
});
|
|
990
1027
|
} catch (err) {
|
|
991
1028
|
sendError(respond, err);
|
|
@@ -1128,6 +1165,8 @@ var voice_call_default = definePluginEntry({
|
|
|
1128
1165
|
const to = normalizeOptionalString(params?.to) ?? "";
|
|
1129
1166
|
const message = normalizeOptionalString(params?.message) ?? "";
|
|
1130
1167
|
const dtmfSequence = normalizeOptionalString(params?.dtmfSequence);
|
|
1168
|
+
const sessionKey = normalizeOptionalString(params?.sessionKey);
|
|
1169
|
+
const requesterSessionKey = normalizeOptionalString(params?.requesterSessionKey);
|
|
1131
1170
|
if (!to) {
|
|
1132
1171
|
respondError(respond, "to required", ErrorCodes.INVALID_REQUEST);
|
|
1133
1172
|
return;
|
|
@@ -1139,7 +1178,9 @@ var voice_call_default = definePluginEntry({
|
|
|
1139
1178
|
to,
|
|
1140
1179
|
message: message || void 0,
|
|
1141
1180
|
mode,
|
|
1142
|
-
dtmfSequence
|
|
1181
|
+
dtmfSequence,
|
|
1182
|
+
sessionKey,
|
|
1183
|
+
...requesterSessionKey ? { requesterSessionKey } : {}
|
|
1143
1184
|
});
|
|
1144
1185
|
} catch (err) {
|
|
1145
1186
|
sendError(respond, err);
|
|
@@ -1233,9 +1274,10 @@ var voice_call_default = definePluginEntry({
|
|
|
1233
1274
|
}
|
|
1234
1275
|
const to = normalizeOptionalString(rawParams.to) ?? rt.config.toNumber;
|
|
1235
1276
|
if (!to) throw new Error("to required for call");
|
|
1236
|
-
const result = await rt.manager.initiateCall(to,
|
|
1277
|
+
const result = await rt.manager.initiateCall(to, normalizeOptionalString(rawParams.sessionKey), {
|
|
1237
1278
|
dtmfSequence: normalizeOptionalString(rawParams.dtmfSequence),
|
|
1238
|
-
message: normalizeOptionalString(rawParams.message)
|
|
1279
|
+
message: normalizeOptionalString(rawParams.message),
|
|
1280
|
+
...normalizeOptionalString(rawParams.requesterSessionKey) ? { requesterSessionKey: normalizeOptionalString(rawParams.requesterSessionKey) } : {}
|
|
1239
1281
|
});
|
|
1240
1282
|
if (!result.success) throw new Error(result.error || "initiate failed");
|
|
1241
1283
|
return json({
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as escapeXml } from "./voice-mapping-BYDGdWGx.js";
|
|
2
2
|
import { t as getHeader } from "./http-headers-BrnxBasF.js";
|
|
3
|
-
import { n as reconstructWebhookUrl, r as verifyPlivoWebhook, t as guardedJsonApiRequest } from "./guarded-json-api-
|
|
3
|
+
import { n as reconstructWebhookUrl, r as verifyPlivoWebhook, t as guardedJsonApiRequest } from "./guarded-json-api-C2JLyCeV.js";
|
|
4
4
|
import { normalizeLowercaseStringOrEmpty, normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
|
|
5
5
|
import crypto from "node:crypto";
|
|
6
6
|
//#region extensions/voice-call/src/providers/plivo.ts
|