@openclaw/voice-call 2026.9.6 → 2026.9.7

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.
Files changed (62) hide show
  1. package/assets/icon.png +0 -0
  2. package/dist/.setup/{cli-D6j7ppuN.mjs → cli-D6uwMXyS.mjs} +6 -10
  3. package/dist/.setup/{config-72FnMWHb.mjs → config-Ds7xoZbI.mjs} +27 -30
  4. package/dist/.setup/{guarded-json-api-exU4Hutw.mjs → guarded-json-api-Bqc6QS8A.mjs} +23 -17
  5. package/dist/.setup/{mock-CcYEAUhV.mjs → mock-ClWW6Ufu.mjs} +34 -53
  6. package/dist/.setup/{plivo-BGJda1Uz.mjs → plivo-69ObrQPk.mjs} +17 -42
  7. package/dist/.setup/realtime-agent-context.legacy-DAEWY27o.mjs +53 -0
  8. package/dist/.setup/{realtime-handler-DlFmdSPn.mjs → realtime-handler-BgDPcPZt.mjs} +34 -53
  9. package/dist/.setup/{response-generator-hZyGIjO4.mjs → response-generator-BzKrJMEb.mjs} +2 -2
  10. package/dist/.setup/{runtime-entry-dDRm6RgA.mjs → runtime-entry-BNOQRXii.mjs} +82 -297
  11. package/dist/.setup/{telnyx-BV56PXr-.mjs → telnyx-DjFGCfy1.mjs} +19 -55
  12. package/dist/.setup/{twilio-BMb-7cu-.mjs → twilio-Bp3gfmUB.mjs} +29 -78
  13. package/dist/.setup/{store-path-B1j1up4_.mjs → utils-B5pnM_Ij.mjs} +14 -6
  14. package/dist/api.js +5 -1
  15. package/dist/doctor-contract-api.js +186 -15
  16. package/dist/index.js +6 -42
  17. package/dist/runtime-entry.js +1 -1
  18. package/node_modules/typebox/build/compile/code.mjs +1 -2
  19. package/node_modules/typebox/build/compile/validator.mjs +5 -1
  20. package/node_modules/typebox/build/format/iri.mjs +16 -9
  21. package/node_modules/typebox/build/guard/emit.mjs +2 -2
  22. package/node_modules/typebox/build/guard/guard.d.mts +7 -5
  23. package/node_modules/typebox/build/guard/guard.mjs +14 -9
  24. package/node_modules/typebox/build/guard/index.mjs +6 -0
  25. package/node_modules/typebox/build/guard/unicode/unicode.d.mts +6 -0
  26. package/node_modules/typebox/build/guard/unicode/unicode.mjs +35 -0
  27. package/node_modules/typebox/build/guard/{string.mjs → unicode/unicode_segment.mjs} +16 -19
  28. package/node_modules/typebox/build/schema/build.mjs +2 -3
  29. package/node_modules/typebox/build/schema/engine/_pathing.d.mts +1 -0
  30. package/node_modules/typebox/build/schema/engine/_pathing.mjs +11 -0
  31. package/node_modules/typebox/build/schema/engine/_refine.mjs +13 -5
  32. package/node_modules/typebox/build/schema/engine/additionalProperties.mjs +2 -1
  33. package/node_modules/typebox/build/schema/engine/dependencies.mjs +2 -1
  34. package/node_modules/typebox/build/schema/engine/dependentSchemas.mjs +2 -1
  35. package/node_modules/typebox/build/schema/engine/dynamicRef.d.mts +1 -1
  36. package/node_modules/typebox/build/schema/engine/dynamicRef.mjs +2 -2
  37. package/node_modules/typebox/build/schema/engine/format.mjs +9 -2
  38. package/node_modules/typebox/build/schema/engine/patternProperties.mjs +3 -2
  39. package/node_modules/typebox/build/schema/engine/properties.mjs +3 -2
  40. package/node_modules/typebox/build/schema/engine/propertyNames.mjs +2 -1
  41. package/node_modules/typebox/build/schema/engine/recursiveRef.d.mts +1 -1
  42. package/node_modules/typebox/build/schema/engine/recursiveRef.mjs +2 -2
  43. package/node_modules/typebox/build/schema/engine/ref.d.mts +1 -1
  44. package/node_modules/typebox/build/schema/engine/ref.mjs +2 -2
  45. package/node_modules/typebox/build/schema/engine/schema.mjs +24 -24
  46. package/node_modules/typebox/build/schema/parse.mjs +3 -0
  47. package/node_modules/typebox/build/schema/pointer/pointer.d.mts +1 -6
  48. package/node_modules/typebox/build/schema/pointer/pointer.mjs +47 -64
  49. package/node_modules/typebox/build/system/settings/internal.d.mts +2 -0
  50. package/node_modules/typebox/build/system/settings/internal.mjs +20 -0
  51. package/node_modules/typebox/build/system/settings/settings.d.mts +11 -0
  52. package/node_modules/typebox/build/system/settings/settings.mjs +14 -0
  53. package/node_modules/typebox/build/type/extends/inference.d.mts +1 -1
  54. package/node_modules/typebox/build/type/extends/inference.mjs +15 -2
  55. package/node_modules/typebox/build/type/extends/tuple.d.mts +2 -1
  56. package/node_modules/typebox/build/type/extends/tuple.mjs +21 -1
  57. package/node_modules/typebox/build/value/parse/parse.mjs +10 -4
  58. package/node_modules/typebox/package.json +1 -1
  59. package/node_modules/typebox/readme.md +3 -1
  60. package/package.json +5 -5
  61. package/dist/runtime-api.js +0 -6
  62. /package/node_modules/typebox/build/guard/{string.d.mts → unicode/unicode_segment.d.mts} +0 -0
package/assets/icon.png CHANGED
Binary file
@@ -1,8 +1,7 @@
1
- import { sleep } from "../runtime-api.js";
2
- import "../api.js";
3
- import { l as resolveVoiceCallStreamExposurePaths, u as validateProviderConfig } from "./config-72FnMWHb.mjs";
4
- import { d as cleanupTailscaleExposureRoute, f as getTailscaleSelfInfo, p as setupTailscaleExposureRoutes, u as resolveWebhookExposureStatus, v as resolveVoiceCallAgentId, y as resolveUserPath } from "./runtime-entry-dDRm6RgA.mjs";
5
- import { b as setVoiceCallStateRuntime, d as getCallHistoryFromStore, f as loadActiveCallsFromStore, o as MAX_CALL_RECORD_EVENTS, t as resolveDefaultVoiceCallStoreDir, u as findCallInStore } from "./store-path-B1j1up4_.mjs";
1
+ import { sleep } from "../api.js";
2
+ import { l as resolveVoiceCallStreamExposurePaths, u as validateProviderConfig } from "./config-Ds7xoZbI.mjs";
3
+ import { d as findCallInStore, f as getCallHistoryFromStore, n as resolveDefaultVoiceCallStoreDir, p as loadActiveCallsFromStore, s as MAX_CALL_RECORD_EVENTS, t as resolveUserPath, x as setVoiceCallStateRuntime } from "./utils-B5pnM_Ij.mjs";
4
+ import { d as cleanupTailscaleExposureRoute, f as getTailscaleSelfInfo, p as setupTailscaleExposureRoutes, u as resolveWebhookExposureStatus, v as resolveVoiceCallAgentId } from "./runtime-entry-BNOQRXii.mjs";
6
5
  import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
7
6
  import { callGatewayFromCli, isGatewayClientRequestError, isGatewayTransportError, redactSensitiveUrlLikeString } from "openclaw/plugin-sdk/gateway-runtime";
8
7
  import { isRecord, normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/string-coerce-runtime";
@@ -240,10 +239,6 @@ function resolveContinueTimeout(config) {
240
239
  function resolveVoiceCallDeadlineMs(timeoutMs, nowMs = Date.now()) {
241
240
  return nowMs + (clampTimerTimeoutMs(timeoutMs) ?? MAX_TIMER_TIMEOUT_MS);
242
241
  }
243
- function readGatewayOperationId(payload) {
244
- if (isRecord(payload) && typeof payload.operationId === "string" && payload.operationId) return payload.operationId;
245
- throw new Error("voicecall gateway response missing operationId");
246
- }
247
242
  function readGatewayPollTimeoutMs(payload, fallbackTimeoutMs) {
248
243
  if (isRecord(payload) && typeof payload.pollTimeoutMs === "number") return clampTimerTimeoutMs(payload.pollTimeoutMs) ?? fallbackTimeoutMs;
249
244
  return fallbackTimeoutMs;
@@ -263,8 +258,9 @@ function readCompletedContinueResult(payload) {
263
258
  }
264
259
  async function pollContinueGateway(payload, fallbackTimeoutMs) {
265
260
  if (!isRecord(payload) || typeof payload.operationId !== "string") return payload;
261
+ if (!payload.operationId) throw new Error("voicecall gateway response missing operationId");
266
262
  const params = {
267
- operationId: readGatewayOperationId(payload),
263
+ operationId: payload.operationId,
268
264
  timeoutMs: readGatewayPollTimeoutMs(payload, fallbackTimeoutMs)
269
265
  };
270
266
  const deadlineMs = resolveVoiceCallDeadlineMs(params.timeoutMs);
@@ -1,5 +1,4 @@
1
- import { TtsConfigSchema } from "../runtime-api.js";
2
- import "../api.js";
1
+ import { TtsConfigSchema } from "../api.js";
3
2
  import { t as TWILIO_REGIONS } from "./twilio-region-4fkgz3UG.mjs";
4
3
  import { normalizeAgentId, parseAgentSessionKey } from "openclaw/plugin-sdk/routing";
5
4
  import { REALTIME_VOICE_AGENT_CONSULT_TOOL_NAME, REALTIME_VOICE_AGENT_CONSULT_TOOL_POLICIES, resolveRealtimeVoiceAgentConsultTools } from "openclaw/plugin-sdk/realtime-voice";
@@ -10,6 +9,21 @@ import { resolveSpeechProviderApiKey } from "openclaw/plugin-sdk/speech-core";
10
9
  import { normalizeWebhookPath } from "openclaw/plugin-sdk/webhook-ingress";
11
10
  import { z } from "zod";
12
11
  import { MAX_TCP_PORT } from "openclaw/plugin-sdk/number-runtime";
12
+ //#region extensions/voice-call/src/allowlist.ts
13
+ /** Normalize a phone number to digits only. */
14
+ function normalizePhoneNumber(input) {
15
+ if (!input) return "";
16
+ return input.replace(/\D/g, "");
17
+ }
18
+ /** Return true when the normalized caller exactly matches an allowlist entry. */
19
+ function isAllowlistedCaller(normalizedFrom, allowFrom) {
20
+ if (!normalizedFrom) return false;
21
+ return (allowFrom ?? []).some((num) => {
22
+ const normalizedAllow = normalizePhoneNumber(num);
23
+ return normalizedAllow !== "" && normalizedAllow === normalizedFrom;
24
+ });
25
+ }
26
+ //#endregion
13
27
  //#region extensions/voice-call/src/realtime-call-control.ts
14
28
  /** Stable provider-facing tool name for ending the current phone call. */
15
29
  const REALTIME_VOICE_END_CALL_TOOL_NAME = "openclaw_end_call";
@@ -205,7 +219,7 @@ const RealtimeToolSchema = z.object({
205
219
  required: z.array(z.string()).optional()
206
220
  })
207
221
  }).strict();
208
- const VoiceCallRealtimeProvidersConfigSchema = z.record(z.string(), z.record(z.string(), z.unknown())).default({});
222
+ const VoiceCallProvidersConfigSchema = z.record(z.string(), z.record(z.string(), z.unknown())).default({});
209
223
  const VoiceCallRealtimeToolPolicySchema = z.enum(REALTIME_VOICE_AGENT_CONSULT_TOOL_POLICIES);
210
224
  const VoiceCallRealtimeConsultPolicySchema = z.enum([
211
225
  "auto",
@@ -232,9 +246,9 @@ const VoiceCallRealtimeFastContextConfigSchema = z.object({
232
246
  fallbackToConsult: false
233
247
  });
234
248
  const VoiceCallRealtimeAgentContextConfigSchema = z.object({
235
- /** Inject a compact agent persona/context capsule into realtime voice instructions. */
249
+ /** Include configured identity and selected profile files alongside the always-on agent context. */
236
250
  enabled: z.boolean().default(false),
237
- /** Maximum number of characters from the generated capsule to append. */
251
+ /** Maximum number of characters in the generated profile-file block. */
238
252
  maxChars: z.number().int().positive().default(6e3),
239
253
  /** Include configured agent identity fields. */
240
254
  includeIdentity: z.boolean().default(true),
@@ -268,7 +282,6 @@ const VoiceCallRealtimeConsultThinkingLevelSchema = z.enum([
268
282
  "max",
269
283
  "ultra"
270
284
  ]);
271
- const VoiceCallStreamingProvidersConfigSchema = z.record(z.string(), z.record(z.string(), z.unknown())).default({});
272
285
  const VoiceCallRealtimeConfigSchema = z.object({
273
286
  /** Enable realtime voice-to-voice mode. */
274
287
  enabled: z.boolean().default(false),
@@ -293,7 +306,7 @@ const VoiceCallRealtimeConfigSchema = z.object({
293
306
  /** Bounded agent persona/context injection for the fast realtime voice path. */
294
307
  agentContext: VoiceCallRealtimeAgentContextConfigSchema,
295
308
  /** Provider-owned raw config blobs keyed by provider id. */
296
- providers: VoiceCallRealtimeProvidersConfigSchema
309
+ providers: VoiceCallProvidersConfigSchema
297
310
  }).strict().default({
298
311
  enabled: false,
299
312
  instructions: DEFAULT_VOICE_CALL_REALTIME_INSTRUCTIONS,
@@ -328,7 +341,7 @@ const VoiceCallStreamingConfigSchema = z.object({
328
341
  /** WebSocket path for media stream connections */
329
342
  streamPath: z.string().min(1).default("/voice/stream"),
330
343
  /** Provider-owned raw config blobs keyed by provider id. */
331
- providers: VoiceCallStreamingProvidersConfigSchema,
344
+ providers: VoiceCallProvidersConfigSchema,
332
345
  /**
333
346
  * Close unauthenticated media stream sockets if no valid `start` frame arrives in time.
334
347
  * Protects against pre-auth idle connection hold attacks.
@@ -431,9 +444,6 @@ const VoiceCallConfigSchema = z.object({
431
444
  });
432
445
  const TWILIO_AUTH_TOKEN_PATH = "plugins.entries.voice-call.config.twilio.authToken";
433
446
  const DEFAULT_VOICE_CALL_CONFIG = VoiceCallConfigSchema.parse({});
434
- function cloneDefaultVoiceCallConfig() {
435
- return structuredClone(DEFAULT_VOICE_CALL_CONFIG);
436
- }
437
447
  function defaultRealtimeStreamPathForServePath(servePath) {
438
448
  const normalized = normalizeWebhookPath(servePath);
439
449
  if (normalized.endsWith("/webhook")) return `${normalized.slice(0, -8)}/stream/realtime`;
@@ -469,16 +479,13 @@ function normalizeVoiceCallTtsConfig(defaults, overrides) {
469
479
  if (!defaults && !overrides) return;
470
480
  return TtsConfigSchema.parse(mergeDeep(defaults ?? {}, overrides ?? {}));
471
481
  }
472
- function normalizePhoneRouteKey(phone) {
473
- return phone?.replace(/\D/g, "") ?? "";
474
- }
475
482
  function resolveVoiceCallNumberRouteKey(config, phone) {
476
483
  const routes = config.numbers;
477
484
  if (!routes) return;
478
485
  if (phone && Object.hasOwn(routes, phone)) return phone;
479
- const normalizedPhone = normalizePhoneRouteKey(phone);
486
+ const normalizedPhone = normalizePhoneNumber(phone);
480
487
  if (!normalizedPhone) return;
481
- return Object.keys(routes).find((routeKey) => normalizePhoneRouteKey(routeKey) === normalizedPhone);
488
+ return Object.keys(routes).find((routeKey) => normalizePhoneNumber(routeKey) === normalizedPhone);
482
489
  }
483
490
  /** Resolve inbound-only number routing from a persisted call record. */
484
491
  function resolveVoiceCallNumberRouteKeyForCall(call) {
@@ -517,7 +524,7 @@ function resolveTwilioAuthToken(config) {
517
524
  });
518
525
  }
519
526
  function normalizeVoiceCallConfig(config) {
520
- const defaults = cloneDefaultVoiceCallConfig();
527
+ const defaults = structuredClone(DEFAULT_VOICE_CALL_CONFIG);
521
528
  const serve = {
522
529
  ...defaults.serve,
523
530
  ...config.serve
@@ -595,7 +602,7 @@ function resolveVoiceCallSessionKey(params) {
595
602
  });
596
603
  const prefix = `agent:${normalizeAgentId(params.config.agentId)}:voice`;
597
604
  if (params.config.sessionScope === "per-call") return `${prefix}:call:${params.callId}`.toLowerCase();
598
- const normalizedPhone = params.phone?.replace(/\D/g, "");
605
+ const normalizedPhone = normalizePhoneNumber(params.phone);
599
606
  return (normalizedPhone ? `${prefix}:${normalizedPhone}` : `${prefix}:${params.callId}`).toLowerCase();
600
607
  }
601
608
  /** Resolve persisted or integration-provided keys into the configured agent namespace. */
@@ -613,12 +620,11 @@ function resolveVoiceCallAgentSessionKey(params) {
613
620
  if (!wrappedInput) throw new Error("Voice Call session key could not be normalized");
614
621
  normalizedScopedKey = `agent:${agentId}:${wrappedInput.rest}`;
615
622
  }
616
- const canonicalMain = canonicalizeMainSessionAlias({
623
+ return canonicalizeMainSessionAlias({
617
624
  cfg: { session: params.coreSession },
618
625
  agentId,
619
626
  sessionKey: normalizedScopedKey
620
627
  });
621
- return canonicalMain === normalizedScopedKey ? normalizedScopedKey : canonicalMain;
622
628
  }
623
629
  /**
624
630
  * Resolves the configuration by merging environment variables into missing fields.
@@ -643,18 +649,9 @@ function resolveVoiceCallConfig(config) {
643
649
  resolved.plivo.authId = resolved.plivo.authId ?? resolveSpeechProviderApiKey(process.env.PLIVO_AUTH_ID);
644
650
  resolved.plivo.authToken = resolved.plivo.authToken ?? resolveSpeechProviderApiKey(process.env.PLIVO_AUTH_TOKEN);
645
651
  }
646
- resolved.tunnel = resolved.tunnel ?? {
647
- provider: "none",
648
- allowNgrokFreeTierLoopbackBypass: false
649
- };
650
652
  resolved.tunnel.allowNgrokFreeTierLoopbackBypass = resolved.tunnel.allowNgrokFreeTierLoopbackBypass ?? false;
651
653
  resolved.tunnel.ngrokAuthToken = resolved.tunnel.ngrokAuthToken ?? resolveSpeechProviderApiKey(process.env.NGROK_AUTHTOKEN);
652
654
  resolved.tunnel.ngrokDomain = resolved.tunnel.ngrokDomain ?? resolveSpeechProviderApiKey(process.env.NGROK_DOMAIN);
653
- resolved.webhookSecurity = resolved.webhookSecurity ?? {
654
- allowedHosts: [],
655
- trustForwardingHeaders: false,
656
- trustedProxyIPs: []
657
- };
658
655
  resolved.webhookSecurity.allowedHosts = resolved.webhookSecurity.allowedHosts ?? [];
659
656
  resolved.webhookSecurity.trustForwardingHeaders = resolved.webhookSecurity.trustForwardingHeaders ?? false;
660
657
  resolved.webhookSecurity.trustedProxyIPs = resolved.webhookSecurity.trustedProxyIPs ?? [];
@@ -694,4 +691,4 @@ function validateProviderConfig(config) {
694
691
  };
695
692
  }
696
693
  //#endregion
697
- export { resolveVoiceCallEffectiveConfig as a, resolveVoiceCallSessionKey as c, REALTIME_VOICE_END_CALL_TOOL_NAME as d, resolveVoiceCallRealtimeTools as f, resolveVoiceCallConfig as i, resolveVoiceCallStreamExposurePaths as l, normalizeVoiceCallConfig as n, resolveVoiceCallNumberRouteKeyForCall as o, resolveTwilioAuthToken as r, resolveVoiceCallPublicPathPrefix as s, VoiceCallConfigSchema as t, validateProviderConfig as u };
694
+ export { resolveVoiceCallEffectiveConfig as a, resolveVoiceCallSessionKey as c, REALTIME_VOICE_END_CALL_TOOL_NAME as d, resolveVoiceCallRealtimeTools as f, resolveVoiceCallConfig as i, resolveVoiceCallStreamExposurePaths as l, normalizePhoneNumber as m, normalizeVoiceCallConfig as n, resolveVoiceCallNumberRouteKeyForCall as o, isAllowlistedCaller as p, resolveTwilioAuthToken as r, resolveVoiceCallPublicPathPrefix as s, VoiceCallConfigSchema as t, validateProviderConfig as u };
@@ -1,13 +1,12 @@
1
- import { fetchWithSsrFGuard } from "../runtime-api.js";
2
- import "../api.js";
3
- import { l as getHeader, n as normalizeProxyIp } from "./runtime-entry-dDRm6RgA.mjs";
1
+ import { fetchWithSsrFGuard } from "../api.js";
2
+ import { l as getHeader, n as normalizeProxyIp } from "./runtime-entry-BNOQRXii.mjs";
4
3
  import { n as reserveWebhookReplay, t as createWebhookReplayCache } from "./webhook-replay-CCQSoBNF.mjs";
5
4
  import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
6
5
  import { isLoopbackHost } from "openclaw/plugin-sdk/gateway-runtime";
7
6
  import { normalizeLowercaseStringOrEmpty, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
8
7
  import crypto from "node:crypto";
9
- import { redactSensitiveText, safeEqualSecret } from "openclaw/plugin-sdk/security-runtime";
10
8
  import { isIP } from "node:net";
9
+ import { redactSensitiveText, safeEqualSecret } from "openclaw/plugin-sdk/security-runtime";
11
10
  import { readResponseTextPrefix, readResponseWithLimit } from "openclaw/plugin-sdk/response-limit-runtime";
12
11
  //#region extensions/voice-call/src/webhook-security.ts
13
12
  const twilioReplayCache = createWebhookReplayCache();
@@ -217,24 +216,15 @@ function createTwilioReplayKey(params) {
217
216
  const canonicalParams = buildCanonicalTwilioParamString(params.requestParams);
218
217
  return `twilio:req:${sha256Hex(`${params.verificationUrl}\n${canonicalParams}\n${params.signature}`)}`;
219
218
  }
220
- function decodeBase64OrBase64Url(input) {
221
- const normalized = input.replace(/-/g, "+").replace(/_/g, "/");
222
- const padLen = (4 - normalized.length % 4) % 4;
223
- const padded = normalized + "=".repeat(padLen);
224
- return Buffer.from(padded, "base64");
225
- }
226
- function base64UrlEncode(buf) {
227
- return buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
228
- }
229
219
  function importEd25519PublicKey(publicKey) {
230
220
  const trimmed = publicKey.trim();
231
221
  if (trimmed.startsWith("-----BEGIN")) return trimmed;
232
- const decoded = decodeBase64OrBase64Url(trimmed);
222
+ const decoded = Buffer.from(trimmed, "base64");
233
223
  if (decoded.length === 32) return crypto.createPublicKey({
234
224
  key: {
235
225
  kty: "OKP",
236
226
  crv: "Ed25519",
237
- x: base64UrlEncode(decoded)
227
+ x: decoded.toString("base64url")
238
228
  },
239
229
  format: "jwk"
240
230
  });
@@ -277,7 +267,7 @@ function verifyTelnyxWebhook(ctx, publicKey, options) {
277
267
  };
278
268
  try {
279
269
  const signedPayload = `${timestamp}|${ctx.rawBody}`;
280
- const signatureBuffer = decodeBase64OrBase64Url(signature);
270
+ const signatureBuffer = Buffer.from(signature, "base64");
281
271
  const canonicalSignature = signatureBuffer.toString("base64");
282
272
  const key = importEd25519PublicKey(publicKey);
283
273
  if (!crypto.verify(null, Buffer.from(signedPayload), key, signatureBuffer)) return {
@@ -571,5 +561,21 @@ async function guardedJsonApiRequest(params) {
571
561
  await release();
572
562
  }
573
563
  }
564
+ /** Failed carrier probes keep calls alive; an empty or missing response is terminal. */
565
+ async function readProviderCallStatus(request, describe) {
566
+ try {
567
+ const data = await request();
568
+ return data ? describe(data) : {
569
+ status: "not-found",
570
+ isTerminal: true
571
+ };
572
+ } catch {
573
+ return {
574
+ status: "error",
575
+ isTerminal: false,
576
+ isUnknown: true
577
+ };
578
+ }
579
+ }
574
580
  //#endregion
575
- export { reconstructWebhookUrl as a, verifyTwilioWebhook as c, readVoiceCallProviderJsonResponse as i, cancelProviderResponseBody as n, verifyPlivoWebhook as o, readProviderErrorResponseSnippet as r, verifyTelnyxWebhook as s, guardedJsonApiRequest as t };
581
+ export { readVoiceCallProviderJsonResponse as a, verifyTelnyxWebhook as c, readProviderErrorResponseSnippet as i, verifyTwilioWebhook as l, readProviderCallStatus as n, reconstructWebhookUrl as o, cancelProviderResponseBody as r, verifyPlivoWebhook as s, guardedJsonApiRequest as t };
@@ -61,67 +61,48 @@ var MockProvider = class {
61
61
  ...base,
62
62
  type: evt.type
63
63
  };
64
- case "call.speaking": {
65
- const payload = evt;
66
- return {
67
- ...base,
68
- type: evt.type,
69
- text: payload.text ?? ""
70
- };
71
- }
72
- case "call.assistant-speech": {
73
- const payload = evt;
74
- return {
75
- ...base,
76
- type: evt.type,
77
- transcript: payload.transcript ?? ""
78
- };
79
- }
64
+ case "call.speaking": return {
65
+ ...base,
66
+ type: evt.type,
67
+ text: evt.text ?? ""
68
+ };
69
+ case "call.assistant-speech": return {
70
+ ...base,
71
+ type: evt.type,
72
+ transcript: evt.transcript ?? ""
73
+ };
80
74
  case "call.speech": {
81
- const payload = evt;
82
- const transcript = payload.transcript ?? "";
75
+ const transcript = evt.transcript ?? "";
83
76
  if (!transcript.trim()) return null;
84
77
  return {
85
78
  ...base,
86
79
  type: evt.type,
87
80
  transcript,
88
- isFinal: payload.isFinal ?? true,
89
- confidence: payload.confidence
90
- };
91
- }
92
- case "call.silence": {
93
- const payload = evt;
94
- return {
95
- ...base,
96
- type: evt.type,
97
- durationMs: payload.durationMs ?? 0
98
- };
99
- }
100
- case "call.dtmf": {
101
- const payload = evt;
102
- return {
103
- ...base,
104
- type: evt.type,
105
- digits: payload.digits ?? ""
106
- };
107
- }
108
- case "call.ended": {
109
- const payload = evt;
110
- return {
111
- ...base,
112
- type: evt.type,
113
- reason: payload.reason ?? "completed"
114
- };
115
- }
116
- case "call.error": {
117
- const payload = evt;
118
- return {
119
- ...base,
120
- type: evt.type,
121
- error: payload.error ?? "unknown error",
122
- retryable: payload.retryable
81
+ isFinal: evt.isFinal ?? true,
82
+ confidence: evt.confidence
123
83
  };
124
84
  }
85
+ case "call.silence": return {
86
+ ...base,
87
+ type: evt.type,
88
+ durationMs: evt.durationMs ?? 0
89
+ };
90
+ case "call.dtmf": return {
91
+ ...base,
92
+ type: evt.type,
93
+ digits: evt.digits ?? ""
94
+ };
95
+ case "call.ended": return {
96
+ ...base,
97
+ type: evt.type,
98
+ reason: evt.reason ?? "completed"
99
+ };
100
+ case "call.error": return {
101
+ ...base,
102
+ type: evt.type,
103
+ error: evt.error ?? "unknown error",
104
+ retryable: evt.retryable
105
+ };
125
106
  default: return null;
126
107
  }
127
108
  }
@@ -1,5 +1,5 @@
1
- import { b as escapeXml, l as getHeader } from "./runtime-entry-dDRm6RgA.mjs";
2
- import { a as reconstructWebhookUrl, o as verifyPlivoWebhook, t as guardedJsonApiRequest } from "./guarded-json-api-exU4Hutw.mjs";
1
+ import { l as getHeader, y as escapeXml } from "./runtime-entry-BNOQRXii.mjs";
2
+ import { n as readProviderCallStatus, o as reconstructWebhookUrl, s as verifyPlivoWebhook, t as guardedJsonApiRequest } from "./guarded-json-api-Bqc6QS8A.mjs";
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
@@ -81,18 +81,14 @@ var PlivoProvider = class PlivoProvider {
81
81
  }
82
82
  parseWebhookEvent(ctx, options) {
83
83
  const flow = normalizeOptionalString(ctx.query?.flow) ?? "";
84
- const parsed = this.parseBody(ctx.rawBody);
85
- if (!parsed) return {
86
- events: [],
87
- statusCode: 400
88
- };
84
+ const parsed = new URLSearchParams(ctx.rawBody);
89
85
  const callUuid = parsed.get("CallUUID") || void 0;
90
86
  if (callUuid) {
91
87
  const webhookBase = this.baseWebhookUrlFromCtx(ctx);
92
88
  if (webhookBase) this.callUuidToWebhookUrl.set(callUuid, webhookBase);
93
89
  }
94
90
  if (flow === "xml-speak") {
95
- const callId = this.getCallIdFromQuery(ctx);
91
+ const callId = normalizeOptionalString(ctx.query?.callId);
96
92
  const pending = callId ? this.pendingSpeakByCallId.get(callId) : void 0;
97
93
  if (callId) this.pendingSpeakByCallId.delete(callId);
98
94
  const actionUrl = pending?.listenAfterPlayback && callId ? this.buildActionUrl(ctx, {
@@ -111,7 +107,7 @@ var PlivoProvider = class PlivoProvider {
111
107
  };
112
108
  }
113
109
  if (flow === "xml-listen") {
114
- const callId = this.getCallIdFromQuery(ctx);
110
+ const callId = normalizeOptionalString(ctx.query?.callId);
115
111
  const pending = callId ? this.pendingListenByCallId.get(callId) : void 0;
116
112
  if (callId) this.pendingListenByCallId.delete(callId);
117
113
  const actionUrl = this.buildActionUrl(ctx, {
@@ -128,7 +124,7 @@ var PlivoProvider = class PlivoProvider {
128
124
  statusCode: 200
129
125
  };
130
126
  }
131
- const callIdFromQuery = this.getCallIdFromQuery(ctx);
127
+ const callIdFromQuery = normalizeOptionalString(ctx.query?.callId);
132
128
  const dedupeKey = options?.verifiedRequestKey ?? createPlivoRequestDedupeKey(ctx);
133
129
  const event = this.normalizeEvent(parsed, callIdFromQuery, dedupeKey);
134
130
  return {
@@ -181,7 +177,7 @@ var PlivoProvider = class PlivoProvider {
181
177
  const event = {
182
178
  ...baseEvent,
183
179
  type: "call.ended",
184
- reason: callStatus === "completed" ? "completed" : callStatus === "busy" ? "busy" : callStatus === "no-answer" ? "no-answer" : "failed"
180
+ reason: callStatus
185
181
  };
186
182
  this.releaseCallState({
187
183
  callId: baseEvent.callId || void 0,
@@ -325,32 +321,21 @@ var PlivoProvider = class PlivoProvider {
325
321
  "machine",
326
322
  "hangup"
327
323
  ]);
328
- try {
329
- const data = await guardedJsonApiRequest({
330
- url: `${this.baseUrl}/Call/${input.providerCallId}/`,
331
- method: "GET",
332
- headers: { Authorization: `Basic ${Buffer.from(`${this.authId}:${this.authToken}`).toString("base64")}` },
333
- allowNotFound: true,
334
- allowedHostnames: [this.apiHost],
335
- auditContext: "plivo-get-call-status",
336
- errorPrefix: "Plivo get call status error"
337
- });
338
- if (!data) return {
339
- status: "not-found",
340
- isTerminal: true
341
- };
324
+ return readProviderCallStatus(() => guardedJsonApiRequest({
325
+ url: `${this.baseUrl}/Call/${input.providerCallId}/`,
326
+ method: "GET",
327
+ headers: { Authorization: `Basic ${Buffer.from(`${this.authId}:${this.authToken}`).toString("base64")}` },
328
+ allowNotFound: true,
329
+ allowedHostnames: [this.apiHost],
330
+ auditContext: "plivo-get-call-status",
331
+ errorPrefix: "Plivo get call status error"
332
+ }), (data) => {
342
333
  const status = data.call_status ?? "unknown";
343
334
  return {
344
335
  status,
345
336
  isTerminal: terminalStatuses.has(status)
346
337
  };
347
- } catch {
348
- return {
349
- status: "error",
350
- isTerminal: false,
351
- isUnknown: true
352
- };
353
- }
338
+ });
354
339
  }
355
340
  static normalizeNumber(numberOrSip) {
356
341
  const trimmed = numberOrSip.trim();
@@ -392,9 +377,6 @@ var PlivoProvider = class PlivoProvider {
392
377
  <Wait length="300" />
393
378
  </Response>`;
394
379
  }
395
- getCallIdFromQuery(ctx) {
396
- return normalizeOptionalString(ctx.query?.callId) || void 0;
397
- }
398
380
  buildActionUrl(ctx, opts) {
399
381
  const base = this.baseWebhookUrlFromCtx(ctx);
400
382
  if (!base) return null;
@@ -421,13 +403,6 @@ var PlivoProvider = class PlivoProvider {
421
403
  return null;
422
404
  }
423
405
  }
424
- parseBody(rawBody) {
425
- try {
426
- return new URLSearchParams(rawBody);
427
- } catch {
428
- return null;
429
- }
430
- }
431
406
  static extractTranscript(params) {
432
407
  for (const key of [
433
408
  "Speech",
@@ -0,0 +1,53 @@
1
+ import { asOptionalRecord, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
2
+ import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
3
+ import { root } from "openclaw/plugin-sdk/security-runtime";
4
+ import { resolveAgentIdentity, resolveAgentWorkspaceDir } from "openclaw/plugin-sdk/agent-runtime";
5
+ //#region extensions/voice-call/src/realtime-agent-context.legacy.ts
6
+ function limitLegacyContextText(text, maxChars) {
7
+ if (text.length <= maxChars) return text;
8
+ const truncated = `${truncateUtf16Safe(text, Math.max(0, maxChars - 32)).trimEnd()}\n[truncated]`;
9
+ return truncateUtf16Safe(truncated, maxChars);
10
+ }
11
+ async function buildLegacyRealtimeVoiceAgentContext(params) {
12
+ const { config, coreConfig, agentId } = params;
13
+ if (!config.enabled) return;
14
+ const capsule = [
15
+ "OpenClaw agent voice context:",
16
+ `- Agent id: ${agentId}`,
17
+ "- Use this context to match the OpenClaw agent's personality and standing preferences on fast voice turns.",
18
+ "- Treat this as compact context only; call openclaw_agent_consult when the caller needs the full agent brain, tools, memory, or workspace state."
19
+ ];
20
+ if (config.includeIdentity) {
21
+ const identity = asOptionalRecord(resolveAgentIdentity(coreConfig, agentId));
22
+ const lines = [
23
+ ["Name", identity?.name],
24
+ ["Emoji", identity?.emoji],
25
+ ["Vibe", identity?.vibe],
26
+ ["Theme", identity?.theme],
27
+ ["Creature/persona", identity?.creature]
28
+ ].flatMap(([label, value]) => {
29
+ const text = normalizeOptionalString(value);
30
+ return text ? [`- ${label}: ${text}`] : [];
31
+ });
32
+ if (lines.length > 0) capsule.push(`Configured identity:\n${lines.join("\n")}`);
33
+ }
34
+ if (config.includeWorkspaceFiles) {
35
+ const workspace = await root(resolveAgentWorkspaceDir(coreConfig, agentId)).catch(() => null);
36
+ if (workspace) {
37
+ const sections = [];
38
+ let remaining = config.maxChars;
39
+ for (const file of config.files) {
40
+ if (remaining <= 0) continue;
41
+ const trimmed = (await workspace.readText(file).catch(() => void 0))?.trim();
42
+ if (!trimmed) continue;
43
+ const section = `### ${file}\n${limitLegacyContextText(trimmed, Math.max(0, remaining - file.length - 16))}`;
44
+ sections.push(section);
45
+ remaining -= section.length;
46
+ }
47
+ if (sections.length > 0) capsule.push(`Workspace voice context:\n${sections.join("\n\n")}`);
48
+ }
49
+ }
50
+ return limitLegacyContextText(capsule.join("\n\n"), config.maxChars);
51
+ }
52
+ //#endregion
53
+ export { buildLegacyRealtimeVoiceAgentContext };