@openclaw/voice-call 2026.6.11 → 2026.6.33

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,6 +1,6 @@
1
1
  import { fetchWithSsrFGuard } from "./runtime-api.js";
2
2
  import "./api.js";
3
- import { a as getHeader } from "./runtime-entry-DIAae7sB.js";
3
+ import { a as getHeader } from "./runtime-entry-CZx1atFe.js";
4
4
  import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
5
5
  import { isLoopbackHost } from "openclaw/plugin-sdk/gateway-runtime";
6
6
  import { isFutureDateTimestampMs, resolveExpiresAtMsFromDurationMs } from "openclaw/plugin-sdk/number-runtime";
@@ -197,6 +197,18 @@ function buildTwilioVerificationUrl(ctx, publicUrl, urlOptions) {
197
197
  return publicUrl;
198
198
  }
199
199
  }
200
+ function redactTwilioVerificationUrlForDiagnostics(url) {
201
+ try {
202
+ const parsed = new URL(url);
203
+ parsed.username = parsed.username ? "***" : "";
204
+ parsed.password = parsed.password ? "***" : "";
205
+ parsed.hash = parsed.hash ? "#***" : "";
206
+ for (const key of Array.from(parsed.searchParams.keys())) parsed.searchParams.set(key, "***");
207
+ return parsed.toString();
208
+ } catch {
209
+ return "<invalid verification URL>";
210
+ }
211
+ }
200
212
  function stripPortFromUrl(url) {
201
213
  try {
202
214
  const parsed = new URL(url);
@@ -382,8 +394,7 @@ function verifyTwilioWebhook(ctx, authToken, options) {
382
394
  const isNgrokFreeTier = verificationUrl.includes(".ngrok-free.app") || verificationUrl.includes(".ngrok.io");
383
395
  return {
384
396
  ok: false,
385
- reason: `Invalid signature for URL: ${verificationUrl}`,
386
- verificationUrl,
397
+ reason: `Invalid signature for URL: ${redactTwilioVerificationUrlForDiagnostics(verificationUrl)}`,
387
398
  isNgrokFreeTier
388
399
  };
389
400
  }
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 { i as resolveVoiceCallConfig, s as validateProviderConfig } from "./config-BKDAAd8j.js";
4
- import { c as getTailscaleSelfInfo, l as setupTailscaleExposureRoute, o as resolveWebhookExposureStatus, p as resolveUserPath, s as cleanupTailscaleExposureRoute, t as createVoiceCallRuntime } from "./runtime-entry-DIAae7sB.js";
4
+ import { c as getTailscaleSelfInfo, l as setupTailscaleExposureRoute, o as resolveWebhookExposureStatus, p as resolveUserPath, s as cleanupTailscaleExposureRoute, t as createVoiceCallRuntime } from "./runtime-entry-CZx1atFe.js";
5
5
  import { c as getCallHistoryFromStore, m as setVoiceCallStateRuntime } from "./store-8M2m4Isq.js";
6
6
  import { i as parseVoiceCallPluginConfig, r as normalizeVoiceCallLegacyConfigInput, t as formatVoiceCallLegacyConfigWarnings } from "./config-compat-qDCcKTUt.js";
7
7
  import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
@@ -915,6 +915,19 @@ function asParamRecord(params) {
915
915
  function isCliOnlyProcess() {
916
916
  return process.env.OPENCLAW_CLI === "1" && !process.argv.slice(2).includes("gateway");
917
917
  }
918
+ function toVoiceCallStatus(call) {
919
+ return {
920
+ callId: call.callId,
921
+ ...call.providerCallId !== void 0 ? { providerCallId: call.providerCallId } : {},
922
+ provider: call.provider,
923
+ direction: call.direction,
924
+ state: call.state,
925
+ startedAt: call.startedAt,
926
+ ...call.answeredAt !== void 0 ? { answeredAt: call.answeredAt } : {},
927
+ ...call.endedAt !== void 0 ? { endedAt: call.endedAt } : {},
928
+ ...call.endReason !== void 0 ? { endReason: call.endReason } : {}
929
+ };
930
+ }
918
931
  const VOICE_CALL_RUNTIME_KEY = Symbol.for("openclaw.voice-call.runtime");
919
932
  const VOICE_CALL_RUNTIME_PROMISE_KEY = Symbol.for("openclaw.voice-call.runtimePromise");
920
933
  const VOICE_CALL_RUNTIME_STOP_PROMISE_KEY = Symbol.for("openclaw.voice-call.runtimeStopPromise");
@@ -1183,7 +1196,7 @@ var voice_call_default = definePluginEntry({
1183
1196
  if (!raw) {
1184
1197
  respond(true, {
1185
1198
  found: true,
1186
- calls: rt.manager.getActiveCalls()
1199
+ calls: rt.manager.getActiveCalls().map(toVoiceCallStatus)
1187
1200
  });
1188
1201
  return;
1189
1202
  }
@@ -1194,7 +1207,7 @@ var voice_call_default = definePluginEntry({
1194
1207
  }
1195
1208
  respond(true, {
1196
1209
  found: true,
1197
- call
1210
+ call: toVoiceCallStatus(call)
1198
1211
  });
1199
1212
  } catch (err) {
1200
1213
  sendError(respond, err);
@@ -1299,7 +1312,7 @@ var voice_call_default = definePluginEntry({
1299
1312
  const call = rt.manager.getCall(callId) || rt.manager.getCallByProviderCallId(callId);
1300
1313
  return json(call ? {
1301
1314
  found: true,
1302
- call
1315
+ call: toVoiceCallStatus(call)
1303
1316
  } : { found: false });
1304
1317
  }
1305
1318
  }
@@ -1309,7 +1322,7 @@ var voice_call_default = definePluginEntry({
1309
1322
  const call = rt.manager.getCall(sid) || rt.manager.getCallByProviderCallId(sid);
1310
1323
  return json(call ? {
1311
1324
  found: true,
1312
- call
1325
+ call: toVoiceCallStatus(call)
1313
1326
  } : { found: false });
1314
1327
  }
1315
1328
  const to = normalizeOptionalString(rawParams.to) ?? rt.config.toNumber;
@@ -1,5 +1,5 @@
1
- import { a as getHeader, m as escapeXml } from "./runtime-entry-DIAae7sB.js";
2
- import { a as reconstructWebhookUrl, o as verifyPlivoWebhook, t as guardedJsonApiRequest } from "./guarded-json-api-BXeBxB7w.js";
1
+ import { a as getHeader, m as escapeXml } from "./runtime-entry-CZx1atFe.js";
2
+ import { a as reconstructWebhookUrl, o as verifyPlivoWebhook, t as guardedJsonApiRequest } from "./guarded-json-api-CmMKa9uZ.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 { o as resolveVoiceCallSessionKey } from "./config-BKDAAd8j.js";
2
- import { f as resolveVoiceResponseModel } from "./runtime-entry-DIAae7sB.js";
2
+ import { f as resolveVoiceResponseModel } from "./runtime-entry-CZx1atFe.js";
3
3
  import { isRecord, normalizeLowercaseStringOrEmpty, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
4
4
  import crypto from "node:crypto";
5
5
  import { applyModelOverrideToSessionEntry } from "openclaw/plugin-sdk/model-session-runtime";
@@ -1431,6 +1431,13 @@ function runTailscaleCommand(args, timeoutMs = 2500) {
1431
1431
  clearTimeout(timer);
1432
1432
  resolve(result);
1433
1433
  };
1434
+ const timer = setTimeout(() => {
1435
+ proc.kill("SIGKILL");
1436
+ finish({
1437
+ code: -1,
1438
+ stdout: ""
1439
+ });
1440
+ }, timeoutMs);
1434
1441
  proc.stdout.on("data", (data) => {
1435
1442
  stdout = appendTailscaleCommandStdout(stdout, data);
1436
1443
  if (stdout.exceeded) {
@@ -1441,13 +1448,13 @@ function runTailscaleCommand(args, timeoutMs = 2500) {
1441
1448
  });
1442
1449
  }
1443
1450
  });
1444
- const timer = setTimeout(() => {
1451
+ proc.stdout.on("error", () => {
1445
1452
  proc.kill("SIGKILL");
1446
1453
  finish({
1447
1454
  code: -1,
1448
1455
  stdout: ""
1449
1456
  });
1450
- }, timeoutMs);
1457
+ });
1451
1458
  proc.on("error", () => {
1452
1459
  finish({
1453
1460
  code: -1,
@@ -1531,6 +1538,10 @@ async function cleanupTailscaleExposure(config) {
1531
1538
  //#endregion
1532
1539
  //#region extensions/voice-call/src/tunnel.ts
1533
1540
  const NGROK_LOG_BUFFER_MAX_CHARS = 16384;
1541
+ function listenForChildStreamErrors(proc, onError) {
1542
+ proc.stdout.on("error", (error) => onError("stdout", error));
1543
+ proc.stderr.on("error", (error) => onError("stderr", error));
1544
+ }
1534
1545
  /**
1535
1546
  * Start an ngrok tunnel to expose the local webhook server.
1536
1547
  *
@@ -1563,6 +1574,7 @@ async function startNgrokTunnel(config) {
1563
1574
  "pipe"
1564
1575
  ] });
1565
1576
  let resolved = false;
1577
+ let closed = false;
1566
1578
  let publicUrl = null;
1567
1579
  let outputBuffer = "";
1568
1580
  const timeout = setTimeout(() => {
@@ -1572,6 +1584,14 @@ async function startNgrokTunnel(config) {
1572
1584
  reject(/* @__PURE__ */ new Error("ngrok startup timed out (30s)"));
1573
1585
  }
1574
1586
  }, 3e4);
1587
+ const rejectIfPending = (message, kill = false) => {
1588
+ if (!resolved) {
1589
+ resolved = true;
1590
+ clearTimeout(timeout);
1591
+ if (kill && !closed) proc.kill("SIGKILL");
1592
+ reject(new Error(message));
1593
+ }
1594
+ };
1575
1595
  const processLine = (line) => {
1576
1596
  try {
1577
1597
  const log = JSON.parse(line);
@@ -1586,10 +1606,24 @@ async function startNgrokTunnel(config) {
1586
1606
  publicUrl: fullUrl,
1587
1607
  provider: "ngrok",
1588
1608
  stop: async () => {
1589
- proc.kill("SIGTERM");
1609
+ if (closed) return;
1590
1610
  await new Promise((res) => {
1591
- proc.on("close", () => res());
1592
- setTimeout(res, 2e3);
1611
+ let finished = false;
1612
+ const finish = () => {
1613
+ if (finished) return;
1614
+ finished = true;
1615
+ clearTimeout(fallback);
1616
+ proc.off("close", finish);
1617
+ res();
1618
+ };
1619
+ if (closed) {
1620
+ res();
1621
+ return;
1622
+ }
1623
+ proc.once("close", finish);
1624
+ const fallback = setTimeout(finish, 2e3);
1625
+ proc.kill("SIGTERM");
1626
+ if (closed) finish();
1593
1627
  });
1594
1628
  }
1595
1629
  });
@@ -1604,23 +1638,16 @@ async function startNgrokTunnel(config) {
1604
1638
  });
1605
1639
  proc.stderr.on("data", (data) => {
1606
1640
  const msg = data.toString();
1607
- if (msg.includes("ERR_NGROK")) {
1608
- if (!resolved) {
1609
- resolved = true;
1610
- clearTimeout(timeout);
1611
- const output = appendBoundedChildOutput(emptyBoundedChildOutput(), msg);
1612
- reject(/* @__PURE__ */ new Error(`ngrok error: ${formatBoundedChildOutput(output)}`));
1613
- }
1614
- }
1641
+ if (msg.includes("ERR_NGROK")) rejectIfPending(`ngrok error: ${formatBoundedChildOutput(appendBoundedChildOutput(emptyBoundedChildOutput(), msg))}`, true);
1642
+ });
1643
+ listenForChildStreamErrors(proc, (stream, error) => {
1644
+ rejectIfPending(`ngrok ${stream} error: ${error.message}`, true);
1615
1645
  });
1616
1646
  proc.on("error", (err) => {
1617
- if (!resolved) {
1618
- resolved = true;
1619
- clearTimeout(timeout);
1620
- reject(/* @__PURE__ */ new Error(`Failed to start ngrok: ${err.message}`));
1621
- }
1647
+ rejectIfPending(`Failed to start ngrok: ${err.message}`);
1622
1648
  });
1623
1649
  proc.on("close", (code) => {
1650
+ closed = true;
1624
1651
  if (!resolved) {
1625
1652
  resolved = true;
1626
1653
  clearTimeout(timeout);
@@ -1641,20 +1668,32 @@ async function runNgrokCommand(args) {
1641
1668
  ] });
1642
1669
  let stdout = emptyBoundedChildOutput();
1643
1670
  let stderr = emptyBoundedChildOutput();
1671
+ let settled = false;
1672
+ const rejectIfPending = (error, kill = false) => {
1673
+ if (settled) return;
1674
+ settled = true;
1675
+ if (kill) proc.kill("SIGKILL");
1676
+ reject(error);
1677
+ };
1644
1678
  proc.stdout.on("data", (data) => {
1645
1679
  stdout = appendBoundedChildOutput(stdout, data.toString());
1646
1680
  });
1647
1681
  proc.stderr.on("data", (data) => {
1648
1682
  stderr = appendBoundedChildOutput(stderr, data.toString());
1649
1683
  });
1684
+ listenForChildStreamErrors(proc, (stream, error) => {
1685
+ rejectIfPending(/* @__PURE__ */ new Error(`ngrok command ${stream} error: ${error.message}`), true);
1686
+ });
1650
1687
  proc.on("close", (code) => {
1688
+ if (settled) return;
1689
+ settled = true;
1651
1690
  if (code === 0) resolve(stdout.text);
1652
1691
  else {
1653
1692
  const output = stderr.text ? stderr : stdout;
1654
1693
  reject(/* @__PURE__ */ new Error(`ngrok command failed: ${formatBoundedChildOutput(output)}`));
1655
1694
  }
1656
1695
  });
1657
- proc.on("error", reject);
1696
+ proc.on("error", (error) => rejectIfPending(error));
1658
1697
  });
1659
1698
  }
1660
1699
  /**
@@ -1678,11 +1717,18 @@ async function startTailscaleTunnel(config) {
1678
1717
  "pipe",
1679
1718
  "pipe"
1680
1719
  ] });
1720
+ let resolved = false;
1681
1721
  let stdout = emptyBoundedChildOutput();
1682
1722
  let stderr = emptyBoundedChildOutput();
1723
+ const rejectIfPending = (error, kill = false) => {
1724
+ if (resolved) return;
1725
+ resolved = true;
1726
+ clearTimeout(timeout);
1727
+ if (kill) proc.kill("SIGKILL");
1728
+ reject(error);
1729
+ };
1683
1730
  const timeout = setTimeout(() => {
1684
- proc.kill("SIGKILL");
1685
- reject(/* @__PURE__ */ new Error(`Tailscale ${config.mode} timed out`));
1731
+ rejectIfPending(/* @__PURE__ */ new Error(`Tailscale ${config.mode} timed out`), true);
1686
1732
  }, 1e4);
1687
1733
  proc.stdout.on("data", (data) => {
1688
1734
  stdout = appendBoundedChildOutput(stdout, data.toString());
@@ -1690,8 +1736,13 @@ async function startTailscaleTunnel(config) {
1690
1736
  proc.stderr.on("data", (data) => {
1691
1737
  stderr = appendBoundedChildOutput(stderr, data.toString());
1692
1738
  });
1739
+ listenForChildStreamErrors(proc, (stream, error) => {
1740
+ rejectIfPending(/* @__PURE__ */ new Error(`Tailscale ${config.mode} ${stream} error: ${error.message}`), true);
1741
+ });
1693
1742
  proc.on("close", (code) => {
1694
1743
  clearTimeout(timeout);
1744
+ if (resolved) return;
1745
+ resolved = true;
1695
1746
  if (code === 0) {
1696
1747
  const publicUrl = `https://${dnsName}${path}`;
1697
1748
  console.log(`[voice-call] Tailscale ${config.mode} active: ${publicUrl}`);
@@ -1709,8 +1760,7 @@ async function startTailscaleTunnel(config) {
1709
1760
  }
1710
1761
  });
1711
1762
  proc.on("error", (err) => {
1712
- clearTimeout(timeout);
1713
- reject(err);
1763
+ rejectIfPending(err);
1714
1764
  });
1715
1765
  });
1716
1766
  }
@@ -1732,6 +1782,10 @@ async function stopTailscaleTunnel(mode, path) {
1732
1782
  clearTimeout(timeout);
1733
1783
  resolve();
1734
1784
  });
1785
+ proc.on("error", () => {
1786
+ clearTimeout(timeout);
1787
+ resolve();
1788
+ });
1735
1789
  });
1736
1790
  }
1737
1791
  /**
@@ -2510,7 +2564,7 @@ function loadRealtimeTranscriptionRuntime() {
2510
2564
  return realtimeTranscriptionRuntimePromise;
2511
2565
  }
2512
2566
  function loadResponseGeneratorModule() {
2513
- responseGeneratorModulePromise ??= import("./response-generator-DBzGujH_.js");
2567
+ responseGeneratorModulePromise ??= import("./response-generator-UQneIJrB.js");
2514
2568
  return responseGeneratorModulePromise;
2515
2569
  }
2516
2570
  function sanitizeTranscriptForLog(value) {
@@ -3198,15 +3252,15 @@ let mockProviderPromise;
3198
3252
  let realtimeVoiceRuntimePromise;
3199
3253
  let realtimeHandlerPromise;
3200
3254
  function loadTelnyxProvider() {
3201
- telnyxProviderPromise ??= import("./telnyx-CYS_IihO.js");
3255
+ telnyxProviderPromise ??= import("./telnyx-iEUfESPw.js");
3202
3256
  return telnyxProviderPromise;
3203
3257
  }
3204
3258
  function loadTwilioProvider() {
3205
- twilioProviderPromise ??= import("./twilio-Bn1ta3tQ.js");
3259
+ twilioProviderPromise ??= import("./twilio-iv98cUsF.js");
3206
3260
  return twilioProviderPromise;
3207
3261
  }
3208
3262
  function loadPlivoProvider() {
3209
- plivoProviderPromise ??= import("./plivo-CPxvHw8k.js");
3263
+ plivoProviderPromise ??= import("./plivo-5ZPBAFQL.js");
3210
3264
  return plivoProviderPromise;
3211
3265
  }
3212
3266
  function loadMockProvider() {
@@ -1,2 +1,2 @@
1
- import { t as createVoiceCallRuntime } from "./runtime-entry-DIAae7sB.js";
1
+ import { t as createVoiceCallRuntime } from "./runtime-entry-CZx1atFe.js";
2
2
  export { createVoiceCallRuntime };
@@ -1,4 +1,4 @@
1
- import { s as verifyTelnyxWebhook, t as guardedJsonApiRequest } from "./guarded-json-api-BXeBxB7w.js";
1
+ import { s as verifyTelnyxWebhook, t as guardedJsonApiRequest } from "./guarded-json-api-CmMKa9uZ.js";
2
2
  import crypto from "node:crypto";
3
3
  //#region extensions/voice-call/src/providers/telnyx.ts
4
4
  function normalizeTelnyxDirection(direction) {
@@ -1,7 +1,7 @@
1
1
  import { fetchWithSsrFGuard } from "./runtime-api.js";
2
2
  import "./api.js";
3
- import { a as getHeader, d as chunkAudio, h as mapVoiceToPolly, i as normalizeProviderStatus, m as escapeXml, n as isProviderStatusTerminal, r as mapProviderStatusToEndReason } from "./runtime-entry-DIAae7sB.js";
4
- import { c as verifyTwilioWebhook, i as readProviderJsonResponseText, n as cancelProviderResponseBody, r as readProviderErrorResponseSnippet, t as guardedJsonApiRequest } from "./guarded-json-api-BXeBxB7w.js";
3
+ import { a as getHeader, d as chunkAudio, h as mapVoiceToPolly, i as normalizeProviderStatus, m as escapeXml, n as isProviderStatusTerminal, r as mapProviderStatusToEndReason } from "./runtime-entry-CZx1atFe.js";
4
+ import { c as verifyTwilioWebhook, i as readProviderJsonResponseText, n as cancelProviderResponseBody, r as readProviderErrorResponseSnippet, t as guardedJsonApiRequest } from "./guarded-json-api-CmMKa9uZ.js";
5
5
  import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
6
6
  import crypto from "node:crypto";
7
7
  import { safeEqualSecret } from "openclaw/plugin-sdk/security-runtime";
@@ -129,10 +129,7 @@ function verifyTwilioProviderWebhook(params) {
129
129
  trustedProxyIPs: params.options.webhookSecurity?.trustedProxyIPs,
130
130
  remoteIP: params.ctx.remoteAddress
131
131
  });
132
- if (!result.ok) {
133
- console.warn(`[twilio] Webhook verification failed: ${result.reason}`);
134
- if (result.verificationUrl) console.warn(`[twilio] Verification URL: ${result.verificationUrl}`);
135
- }
132
+ if (!result.ok) console.warn(`[twilio] Webhook verification failed: ${result.reason}`);
136
133
  return {
137
134
  ok: result.ok,
138
135
  reason: result.reason,
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@openclaw/voice-call",
3
- "version": "2026.6.11",
3
+ "version": "2026.6.33",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@openclaw/voice-call",
9
- "version": "2026.6.11",
9
+ "version": "2026.6.33",
10
10
  "dependencies": {
11
11
  "commander": "14.0.3",
12
12
  "typebox": "1.1.39",
@@ -14,7 +14,7 @@
14
14
  "zod": "4.4.3"
15
15
  },
16
16
  "peerDependencies": {
17
- "openclaw": ">=2026.6.11"
17
+ "openclaw": ">=2026.6.33"
18
18
  },
19
19
  "peerDependenciesMeta": {
20
20
  "openclaw": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/voice-call",
3
- "version": "2026.6.11",
3
+ "version": "2026.6.33",
4
4
  "description": "OpenClaw voice-call plugin for Twilio, Telnyx, and Plivo phone calls.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -14,7 +14,7 @@
14
14
  "zod": "4.4.3"
15
15
  },
16
16
  "peerDependencies": {
17
- "openclaw": ">=2026.6.11"
17
+ "openclaw": ">=2026.6.33"
18
18
  },
19
19
  "peerDependenciesMeta": {
20
20
  "openclaw": {
@@ -31,10 +31,10 @@
31
31
  "minHostVersion": ">=2026.4.10"
32
32
  },
33
33
  "compat": {
34
- "pluginApi": ">=2026.6.11"
34
+ "pluginApi": ">=2026.6.33"
35
35
  },
36
36
  "build": {
37
- "openclawVersion": "2026.6.11"
37
+ "openclawVersion": "2026.6.33"
38
38
  },
39
39
  "release": {
40
40
  "publishToClawHub": true,