@openclaw/voice-call 2026.7.2-beta.4 → 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,3 +1,4 @@
1
+ import { VOICE_CALL_CLI_DESCRIPTOR } from "./cli-output-mode.js";
1
2
  import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
2
3
  //#region extensions/voice-call/cli-metadata.ts
3
4
  var cli_metadata_default = definePluginEntry({
@@ -5,7 +6,10 @@ var cli_metadata_default = definePluginEntry({
5
6
  name: "Voice Call",
6
7
  description: "Voice call channel plugin",
7
8
  register(api) {
8
- api.registerCli(() => {}, { commands: ["voicecall"] });
9
+ api.registerCli(() => {}, {
10
+ commands: ["voicecall"],
11
+ descriptors: [VOICE_CALL_CLI_DESCRIPTOR]
12
+ });
9
13
  }
10
14
  });
11
15
  //#endregion
@@ -0,0 +1,27 @@
1
+ import { getRootOptionAwareCommandPath } from "openclaw/plugin-sdk/cli-argv";
2
+ //#region extensions/voice-call/cli-output-mode.ts
3
+ const MACHINE_OUTPUT_COMMANDS = /* @__PURE__ */ new Set([
4
+ "call",
5
+ "continue",
6
+ "dtmf",
7
+ "end",
8
+ "expose",
9
+ "latency",
10
+ "speak",
11
+ "start",
12
+ "status",
13
+ "tail"
14
+ ]);
15
+ /** Voice-call result actions emit JSON, while tail reserves stdout for JSONL. */
16
+ function isVoiceCallMachineOutput(params) {
17
+ const [, command] = getRootOptionAwareCommandPath(params.argv, 2);
18
+ return MACHINE_OUTPUT_COMMANDS.has(command ?? "");
19
+ }
20
+ const VOICE_CALL_CLI_DESCRIPTOR = {
21
+ name: "voicecall",
22
+ description: "Voice call utilities",
23
+ hasSubcommands: true,
24
+ machineOutput: isVoiceCallMachineOutput
25
+ };
26
+ //#endregion
27
+ export { VOICE_CALL_CLI_DESCRIPTOR };
@@ -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,6 +1,7 @@
1
1
  import { definePluginEntry, sleep } from "./runtime-api.js";
2
2
  import "./api.js";
3
- 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";
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-D0UJiLfi.js";
4
5
  import { g as setVoiceCallStateRuntime, t as resolveDefaultVoiceCallStoreDir, u as getCallHistoryFromStore } from "./store-path-nYL-yM0S.js";
5
6
  import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
6
7
  import { ErrorCodes, callGatewayFromCli, errorShape } from "openclaw/plugin-sdk/gateway-runtime";
@@ -12,6 +13,7 @@ import { Type } from "typebox";
12
13
  import fs from "node:fs";
13
14
  import { randomUUID } from "node:crypto";
14
15
  import path from "node:path";
16
+ import { StringDecoder } from "node:string_decoder";
15
17
  import { format } from "node:util";
16
18
  //#region extensions/voice-call/src/cli.ts
17
19
  const VOICE_CALL_GATEWAY_DEFAULT_TIMEOUT_MS = 5e3;
@@ -428,22 +430,33 @@ function registerVoiceCallCli(params) {
428
430
  }
429
431
  };
430
432
  if (fs.existsSync(file) && path.basename(file) !== "calls.jsonl") {
431
- const initial = fs.readFileSync(file, "utf8");
432
- 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);
433
438
  for (const line of lines.slice(Math.max(0, lines.length - since))) writeStdoutLine(line);
434
- let offset = Buffer.byteLength(initial, "utf8");
439
+ let offset = initial.length;
440
+ let lastObservedSize = initial.length;
435
441
  for (;;) {
436
442
  try {
437
443
  const stat = fs.statSync(file);
438
- 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;
439
450
  if (stat.size > offset) {
440
451
  const fd = fs.openSync(file, "r");
441
452
  try {
442
453
  const buf = Buffer.alloc(stat.size - offset);
443
- fs.readSync(fd, buf, 0, buf.length, offset);
444
- offset = stat.size;
445
- const text = buf.toString("utf8");
446
- 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);
447
460
  } finally {
448
461
  fs.closeSync(fd);
449
462
  }
@@ -1349,7 +1362,10 @@ var voice_call_default = definePluginEntry({
1349
1362
  ensureRuntime,
1350
1363
  stateRuntime: api.runtime.state,
1351
1364
  logger: api.logger
1352
- }), { commands: ["voicecall"] });
1365
+ }), {
1366
+ commands: ["voicecall"],
1367
+ descriptors: [VOICE_CALL_CLI_DESCRIPTOR]
1368
+ });
1353
1369
  api.registerService({
1354
1370
  id: "voicecall",
1355
1371
  start: () => {
@@ -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.4",
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.4"
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.4"
33
+ "pluginApi": ">=2026.7.2-beta.7"
34
34
  },
35
35
  "build": {
36
- "openclawVersion": "2026.7.2-beta.4"
36
+ "openclawVersion": "2026.7.2-beta.7"
37
37
  },
38
38
  "release": {
39
39
  "publishToClawHub": true,
@@ -46,7 +46,6 @@
46
46
  "files": [
47
47
  "dist/**",
48
48
  "openclaw.plugin.json",
49
- "npm-shrinkwrap.json",
50
49
  "README.md",
51
50
  "skills/**"
52
51
  ],
@@ -1,61 +0,0 @@
1
- {
2
- "name": "@openclaw/voice-call",
3
- "version": "2026.7.2-beta.4",
4
- "lockfileVersion": 3,
5
- "requires": true,
6
- "packages": {
7
- "": {
8
- "name": "@openclaw/voice-call",
9
- "version": "2026.7.2-beta.4",
10
- "dependencies": {
11
- "typebox": "1.3.6",
12
- "ws": "8.21.1",
13
- "zod": "4.4.3"
14
- },
15
- "peerDependencies": {
16
- "openclaw": ">=2026.7.2-beta.4"
17
- },
18
- "peerDependenciesMeta": {
19
- "openclaw": {
20
- "optional": true
21
- }
22
- }
23
- },
24
- "node_modules/typebox": {
25
- "version": "1.3.6",
26
- "resolved": "https://registry.npmjs.org/typebox/-/typebox-1.3.6.tgz",
27
- "integrity": "sha512-Sc8RA0NCMEFmApHNU9ZMzqcpQj46She44J8ffpLM/bdhLNUZKq7DJumcLcsFx1gRmDfQPgCgOmFFJ7rcnfWNyA==",
28
- "license": "MIT"
29
- },
30
- "node_modules/ws": {
31
- "version": "8.21.1",
32
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.1.tgz",
33
- "integrity": "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==",
34
- "license": "MIT",
35
- "engines": {
36
- "node": ">=10.0.0"
37
- },
38
- "peerDependencies": {
39
- "bufferutil": "^4.0.1",
40
- "utf-8-validate": ">=5.0.2"
41
- },
42
- "peerDependenciesMeta": {
43
- "bufferutil": {
44
- "optional": true
45
- },
46
- "utf-8-validate": {
47
- "optional": true
48
- }
49
- }
50
- },
51
- "node_modules/zod": {
52
- "version": "4.4.3",
53
- "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz",
54
- "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==",
55
- "license": "MIT",
56
- "funding": {
57
- "url": "https://github.com/sponsors/colinhacks"
58
- }
59
- }
60
- }
61
- }