@openclaw/voice-call 2026.7.2-beta.5 → 2026.7.2-beta.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.
@@ -1,12 +1,13 @@
1
1
  import { fetchWithSsrFGuard } from "./runtime-api.js";
2
2
  import "./api.js";
3
- import { n as normalizeProxyIp, s as getHeader } from "./runtime-entry-CQtitvRd.js";
3
+ import { n as normalizeProxyIp, s as getHeader } from "./runtime-entry-D0UJiLfi.js";
4
4
  import { n as markWebhookReplay, t as createWebhookReplayCache } from "./webhook-replay-aaP79dI8.js";
5
5
  import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
6
6
  import { isLoopbackHost } from "openclaw/plugin-sdk/gateway-runtime";
7
7
  import { normalizeLowercaseStringOrEmpty, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
8
8
  import crypto from "node:crypto";
9
9
  import { safeEqualSecret } from "openclaw/plugin-sdk/security-runtime";
10
+ import { isIP } from "node:net";
10
11
  import { readResponseTextPrefix, readResponseWithLimit } from "openclaw/plugin-sdk/response-limit-runtime";
11
12
  //#region extensions/voice-call/src/webhook-security.ts
12
13
  const twilioReplayCache = createWebhookReplayCache();
@@ -57,7 +58,17 @@ function extractHostname(hostHeader) {
57
58
  if (hostHeader.startsWith("[")) {
58
59
  const endBracket = hostHeader.indexOf("]");
59
60
  if (endBracket === -1) return null;
60
- return normalizeLowercaseStringOrEmpty(hostHeader.slice(1, endBracket));
61
+ const suffix = hostHeader.slice(endBracket + 1);
62
+ if (suffix && !/^:\d+$/u.test(suffix)) return null;
63
+ const hostname = hostHeader.slice(1, endBracket);
64
+ if (isIP(hostname) !== 6) return null;
65
+ try {
66
+ const parsedHostname = new URL(`https://[${hostname}]/`).hostname;
67
+ if (!parsedHostname.startsWith("[") || !parsedHostname.endsWith("]")) return null;
68
+ return normalizeLowercaseStringOrEmpty(parsedHostname.slice(1, -1));
69
+ } catch {
70
+ return null;
71
+ }
61
72
  }
62
73
  if (hostHeader.includes("@")) return null;
63
74
  const hostname = hostHeader.split(":").at(0);
@@ -78,6 +89,9 @@ function normalizeAllowedHosts(allowedHosts) {
78
89
  }
79
90
  return normalized.size > 0 ? normalized : null;
80
91
  }
92
+ function formatHostnameForUrl(hostname) {
93
+ return isIP(hostname) === 6 ? `[${hostname}]` : hostname;
94
+ }
81
95
  /**
82
96
  * Reconstruct the public webhook URL from request headers.
83
97
  *
@@ -147,15 +161,13 @@ function reconstructWebhookUrl(ctx, options) {
147
161
  const parsed = new URL(ctx.url);
148
162
  path = parsed.pathname + parsed.search;
149
163
  } catch {}
150
- return `${proto}://${host}${path}`;
164
+ return `${proto}://${formatHostnameForUrl(host)}${path}`;
151
165
  }
152
166
  function buildTwilioVerificationUrl(ctx, publicUrl, urlOptions) {
153
167
  if (!publicUrl) return reconstructWebhookUrl(ctx, urlOptions);
154
168
  try {
155
169
  const base = new URL(publicUrl);
156
- const requestUrl = new URL(ctx.url);
157
- base.pathname = requestUrl.pathname;
158
- base.search = requestUrl.search;
170
+ base.search = new URL(ctx.url).search;
159
171
  return base.toString();
160
172
  } catch {
161
173
  return publicUrl;
@@ -258,8 +270,8 @@ function verifyTelnyxWebhook(ctx, publicKey, options) {
258
270
  ok: false,
259
271
  reason: "Missing signature or timestamp header"
260
272
  };
261
- const eventTimeSec = Number.parseInt(timestamp, 10);
262
- if (!Number.isFinite(eventTimeSec)) return {
273
+ const eventTimeSec = /^(?:0|[1-9]\d*)$/.test(timestamp) ? Number(timestamp) : void 0;
274
+ if (eventTimeSec === void 0 || !Number.isSafeInteger(eventTimeSec)) return {
263
275
  ok: false,
264
276
  reason: "Invalid timestamp header"
265
277
  };
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { definePluginEntry, sleep } from "./runtime-api.js";
2
2
  import "./api.js";
3
3
  import { VOICE_CALL_CLI_DESCRIPTOR } from "./cli-output-mode.js";
4
- import { S as validateProviderConfig, b as resolveVoiceCallConfig, c as resolveWebhookExposureStatus, d as setupTailscaleExposureRoute, g as resolveUserPath, l as cleanupTailscaleExposureRoute, t as createVoiceCallRuntime, u as getTailscaleSelfInfo, y as VoiceCallConfigSchema } from "./runtime-entry-CQtitvRd.js";
4
+ import { S as validateProviderConfig, b as resolveVoiceCallConfig, c as resolveWebhookExposureStatus, d as setupTailscaleExposureRoute, g as resolveUserPath, l as cleanupTailscaleExposureRoute, t as createVoiceCallRuntime, u as getTailscaleSelfInfo, y as VoiceCallConfigSchema } from "./runtime-entry-D0UJiLfi.js";
5
5
  import { g as setVoiceCallStateRuntime, t as resolveDefaultVoiceCallStoreDir, u as getCallHistoryFromStore } from "./store-path-nYL-yM0S.js";
6
6
  import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
7
7
  import { ErrorCodes, callGatewayFromCli, errorShape } from "openclaw/plugin-sdk/gateway-runtime";
@@ -13,6 +13,7 @@ import { Type } from "typebox";
13
13
  import fs from "node:fs";
14
14
  import { randomUUID } from "node:crypto";
15
15
  import path from "node:path";
16
+ import { StringDecoder } from "node:string_decoder";
16
17
  import { format } from "node:util";
17
18
  //#region extensions/voice-call/src/cli.ts
18
19
  const VOICE_CALL_GATEWAY_DEFAULT_TIMEOUT_MS = 5e3;
@@ -429,22 +430,33 @@ function registerVoiceCallCli(params) {
429
430
  }
430
431
  };
431
432
  if (fs.existsSync(file) && path.basename(file) !== "calls.jsonl") {
432
- const initial = fs.readFileSync(file, "utf8");
433
- const lines = initial.split("\n").filter(Boolean);
433
+ const initial = fs.readFileSync(file);
434
+ let decoder = new StringDecoder("utf8");
435
+ const initialLines = decoder.write(initial).split("\n");
436
+ let pendingLine = initialLines.pop() ?? "";
437
+ const lines = initialLines.filter(Boolean);
434
438
  for (const line of lines.slice(Math.max(0, lines.length - since))) writeStdoutLine(line);
435
- let offset = Buffer.byteLength(initial, "utf8");
439
+ let offset = initial.length;
440
+ let lastObservedSize = initial.length;
436
441
  for (;;) {
437
442
  try {
438
443
  const stat = fs.statSync(file);
439
- if (stat.size < offset) offset = 0;
444
+ if (stat.size < lastObservedSize) {
445
+ offset = 0;
446
+ decoder = new StringDecoder("utf8");
447
+ pendingLine = "";
448
+ }
449
+ lastObservedSize = stat.size;
440
450
  if (stat.size > offset) {
441
451
  const fd = fs.openSync(file, "r");
442
452
  try {
443
453
  const buf = Buffer.alloc(stat.size - offset);
444
- fs.readSync(fd, buf, 0, buf.length, offset);
445
- offset = stat.size;
446
- const text = buf.toString("utf8");
447
- for (const line of text.split("\n").filter(Boolean)) writeStdoutLine(line);
454
+ const bytesRead = fs.readSync(fd, buf, 0, buf.length, offset);
455
+ offset += bytesRead;
456
+ const text = decoder.write(buf.subarray(0, bytesRead));
457
+ const completeLines = `${pendingLine}${text}`.split("\n");
458
+ pendingLine = completeLines.pop() ?? "";
459
+ for (const line of completeLines.filter(Boolean)) writeStdoutLine(line);
448
460
  } finally {
449
461
  fs.closeSync(fd);
450
462
  }
@@ -1,5 +1,5 @@
1
- import { _ as escapeXml, s as getHeader } from "./runtime-entry-CQtitvRd.js";
2
- import { a as reconstructWebhookUrl, o as verifyPlivoWebhook, t as guardedJsonApiRequest } from "./guarded-json-api-Dxhe25Zi.js";
1
+ import { _ as escapeXml, s as getHeader } from "./runtime-entry-D0UJiLfi.js";
2
+ import { a as reconstructWebhookUrl, o as verifyPlivoWebhook, t as guardedJsonApiRequest } from "./guarded-json-api-DS9sZa3C.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,4 +1,4 @@
1
- import { o as canonicalizeVoiceCallMediaBase64 } from "./runtime-entry-CQtitvRd.js";
1
+ import { o as canonicalizeVoiceCallMediaBase64 } from "./runtime-entry-D0UJiLfi.js";
2
2
  import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
3
3
  import { isFutureDateTimestampMs, resolveExpiresAtMsFromDurationMs } from "openclaw/plugin-sdk/number-runtime";
4
4
  import { createSubsystemLogger } from "openclaw/plugin-sdk/runtime-env";
@@ -634,7 +634,7 @@ var RealtimeCallHandler = class {
634
634
  },
635
635
  onTalkEvent: (event) => appendRecentTalkEventMetadata(callRecord, event)
636
636
  });
637
- const providerHandlesInputAudioBargeIn = this.realtimeProvider.capabilities?.handlesInputAudioBargeIn === true;
637
+ let providerHandlesInputAudioBargeIn = this.realtimeProvider.capabilities?.handlesInputAudioBargeIn === true;
638
638
  const cancelOutputAudioForBargeIn = (source, interruptProvider, clearedAudioBytes = 0) => {
639
639
  const outputAudioActive = harness.talk.outputAudioActive;
640
640
  const pendingTelephonyAudio = audioPacer.hasPendingAudio();
@@ -890,6 +890,7 @@ var RealtimeCallHandler = class {
890
890
  });
891
891
  }
892
892
  });
893
+ providerHandlesInputAudioBargeIn = session.bridge.handlesInputAudioBargeIn ?? providerHandlesInputAudioBargeIn;
893
894
  const closeTelephony = (reason) => {
894
895
  try {
895
896
  session.close();
@@ -1,4 +1,4 @@
1
- import { h as resolveCallAgentId, m as resolveVoiceResponseModel, x as resolveVoiceCallSessionKey } from "./runtime-entry-CQtitvRd.js";
1
+ import { h as resolveCallAgentId, m as resolveVoiceResponseModel, x as resolveVoiceCallSessionKey } from "./runtime-entry-D0UJiLfi.js";
2
2
  import { isRecord, normalizeLowercaseStringOrEmpty, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
3
3
  import crypto from "node:crypto";
4
4
  import { ModelSelectionLockedError, applyModelOverrideToSessionEntry, resolvePersistedSessionRuntimeId } from "openclaw/plugin-sdk/model-session-runtime";
@@ -3071,7 +3071,7 @@ const WEBHOOK_BODY_TIMEOUT_MS = WEBHOOK_BODY_READ_DEFAULTS.preAuth.timeoutMs;
3071
3071
  const MISSING_REMOTE_ADDRESS_IN_FLIGHT_KEY = "__voice_call_no_remote__";
3072
3072
  const STREAM_DISCONNECT_HANGUP_GRACE_MS = 2e3;
3073
3073
  const loadRealtimeTranscriptionRuntime = createLazyRuntimeModule(() => import("./realtime-transcription.runtime-CbJAs5t_.js"));
3074
- const loadResponseGeneratorModule = createLazyRuntimeModule(() => import("./response-generator-z6M9229t.js"));
3074
+ const loadResponseGeneratorModule = createLazyRuntimeModule(() => import("./response-generator-D-7ApVfT.js"));
3075
3075
  function appendRecentTalkEventMetadata(call, event) {
3076
3076
  const metadata = call.metadata ?? {};
3077
3077
  const recent = Array.isArray(metadata.recentTalkEvents) ? metadata.recentTalkEvents.filter((entry) => Boolean(entry) && typeof entry === "object" && !Array.isArray(entry)) : [];
@@ -3746,12 +3746,12 @@ const REALTIME_VOICE_CONSULT_SYSTEM_PROMPT = [
3746
3746
  "Do not print secret values or dump environment variables; only check whether required configuration is present.",
3747
3747
  "Be accurate, brief, and speakable."
3748
3748
  ].join(" ");
3749
- const loadTelnyxProvider = createLazyRuntimeModule(() => import("./telnyx-BG56lL7z.js"));
3750
- const loadTwilioProvider = createLazyRuntimeModule(() => import("./twilio-B3FQ_P9x.js"));
3751
- const loadPlivoProvider = createLazyRuntimeModule(() => import("./plivo-B-KvPVji.js"));
3749
+ const loadTelnyxProvider = createLazyRuntimeModule(() => import("./telnyx-Dx6cfLvV.js"));
3750
+ const loadTwilioProvider = createLazyRuntimeModule(() => import("./twilio-D_D8KysU.js"));
3751
+ const loadPlivoProvider = createLazyRuntimeModule(() => import("./plivo-vZ--Wqxv.js"));
3752
3752
  const loadMockProvider = createLazyRuntimeModule(() => import("./mock-YsvBTX-7.js"));
3753
3753
  const loadRealtimeVoiceRuntime = createLazyRuntimeModule(() => import("./realtime-voice.runtime-vtdCOWg-.js"));
3754
- const loadRealtimeHandler = createLazyRuntimeModule(() => import("./realtime-handler-hIVPEZxN.js"));
3754
+ const loadRealtimeHandler = createLazyRuntimeModule(() => import("./realtime-handler--i_OERv6.js"));
3755
3755
  function resolveVoiceCallConsultSessionKey(call) {
3756
3756
  return resolveVoiceCallSessionKey({
3757
3757
  config: call.config,
@@ -1,2 +1,2 @@
1
- import { t as createVoiceCallRuntime } from "./runtime-entry-CQtitvRd.js";
1
+ import { t as createVoiceCallRuntime } from "./runtime-entry-D0UJiLfi.js";
2
2
  export { createVoiceCallRuntime };
@@ -1,4 +1,4 @@
1
- import { s as verifyTelnyxWebhook, t as guardedJsonApiRequest } from "./guarded-json-api-Dxhe25Zi.js";
1
+ import { s as verifyTelnyxWebhook, t as guardedJsonApiRequest } from "./guarded-json-api-DS9sZa3C.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 { _ as escapeXml, a as normalizeProviderStatus, i as mapProviderStatusToEndReason, p as chunkAudio, r as isProviderStatusTerminal, s as getHeader, v as mapVoiceToPolly } from "./runtime-entry-CQtitvRd.js";
5
- import { c as verifyTwilioWebhook, i as readProviderJsonResponseText, n as cancelProviderResponseBody, r as readProviderErrorResponseSnippet, t as guardedJsonApiRequest } from "./guarded-json-api-Dxhe25Zi.js";
4
+ import { _ as escapeXml, a as normalizeProviderStatus, i as mapProviderStatusToEndReason, p as chunkAudio, r as isProviderStatusTerminal, s as getHeader, v as mapVoiceToPolly } from "./runtime-entry-D0UJiLfi.js";
5
+ import { c as verifyTwilioWebhook, i as readProviderJsonResponseText, n as cancelProviderResponseBody, r as readProviderErrorResponseSnippet, t as guardedJsonApiRequest } from "./guarded-json-api-DS9sZa3C.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/voice-call",
3
- "version": "2026.7.2-beta.5",
3
+ "version": "2026.7.2-beta.7",
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.7.2-beta.5"
16
+ "openclaw": ">=2026.7.2-beta.7"
17
17
  },
18
18
  "peerDependenciesMeta": {
19
19
  "openclaw": {
@@ -30,10 +30,10 @@
30
30
  "minHostVersion": ">=2026.4.10"
31
31
  },
32
32
  "compat": {
33
- "pluginApi": ">=2026.7.2-beta.5"
33
+ "pluginApi": ">=2026.7.2-beta.7"
34
34
  },
35
35
  "build": {
36
- "openclawVersion": "2026.7.2-beta.5"
36
+ "openclawVersion": "2026.7.2-beta.7"
37
37
  },
38
38
  "release": {
39
39
  "publishToClawHub": true,