@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 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-7w04YpHh.js";
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 = isLoopbackAddress(options?.remoteIP ?? ctx.remoteAddress);
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-7w04YpHh.js";
4
- import { a as setupTailscaleExposureRoute, i as getTailscaleSelfInfo, n as resolveWebhookExposureStatus, r as cleanupTailscaleExposureRoute, s as resolveUserPath, t as createVoiceCallRuntime } from "./runtime-entry-DFzuGKLG.js";
5
- import { i as parseVoiceCallPluginConfig, r as normalizeVoiceCallLegacyConfigInput, t as formatVoiceCallLegacyConfigWarnings } from "./config-compat-B0me39_4.js";
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, void 0, {
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, void 0, {
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-Btx5EE4w.js";
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