@openclaw/voice-call 2026.7.2-beta.5 → 2026.8.1-beta.2

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,9 +1,10 @@
1
- import { a as CALL_RECORD_EVENT_META_MAX_ENTRIES, c as buildVoiceCallLegacyJsonlEventKey, f as parseVoiceCallRecordLine, h as resolveVoiceCallLegacyCallLogPath, i as CALL_RECORD_EVENT_CHUNKS_NAMESPACE, m as prepareVoiceCallRecordForStorage, n as CALL_RECORD_CHUNK_MAX_ENTRIES, o as MAX_CALL_RECORD_EVENTS, r as CALL_RECORD_EVENTS_NAMESPACE, s as RAW_CALL_RECORD_CHUNK_BYTES, t as resolveDefaultVoiceCallStoreDir } from "./store-path-nYL-yM0S.js";
1
+ import { a as CALL_RECORD_EVENT_META_MAX_ENTRIES, c as buildVoiceCallLegacyJsonlEventKey, f as parseVoiceCallRecordLine, h as resolveVoiceCallLegacyCallLogPath, i as CALL_RECORD_EVENT_CHUNKS_NAMESPACE, m as prepareVoiceCallRecordForStorage, n as CALL_RECORD_CHUNK_MAX_ENTRIES, o as MAX_CALL_RECORD_EVENTS, r as CALL_RECORD_EVENTS_NAMESPACE, s as RAW_CALL_RECORD_CHUNK_BYTES, t as resolveDefaultVoiceCallStoreDir } from "./store-path-51_oC_xn.js";
2
2
  import { normalizeAgentId } from "openclaw/plugin-sdk/routing";
3
+ import { asOptionalRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
3
4
  import path from "node:path";
4
5
  import os from "node:os";
5
6
  import fs from "node:fs/promises";
6
- import { archiveLegacyStateSource, detectOpenClawStateDatabaseSchemaMigrations, repairOpenClawStateDatabaseSchema } from "openclaw/plugin-sdk/runtime-doctor";
7
+ import { archiveLegacyStateSource } from "openclaw/plugin-sdk/runtime-doctor-migrations";
7
8
  //#region extensions/voice-call/doctor-contract-api.ts
8
9
  /** Resolve home from doctor env with OS fallback. */
9
10
  function resolveHome(env) {
@@ -26,21 +27,18 @@ function getVoiceCallConfigStore(config) {
26
27
  }
27
28
  return "";
28
29
  }
29
- function asRecord(value) {
30
- return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
31
- }
32
30
  /** Return Voice Call agents whose templated core session stores need migration. */
33
31
  function resolveSessionStoreAgentIds(params) {
34
32
  const agentIds = /* @__PURE__ */ new Set();
35
33
  for (const pluginId of ["voice-call", "@openclaw/voice-call"]) {
36
34
  const entry = params.cfg.plugins?.entries?.[pluginId];
37
35
  if (!entry) continue;
38
- const config = entry.config === void 0 ? {} : asRecord(entry.config);
36
+ const config = entry.config === void 0 ? {} : asOptionalRecord(entry.config);
39
37
  if (!config) continue;
40
38
  agentIds.add(normalizeAgentId(typeof config.agentId === "string" ? config.agentId : void 0));
41
- const numbers = asRecord(config.numbers);
39
+ const numbers = asOptionalRecord(config.numbers);
42
40
  for (const route of Object.values(numbers ?? {})) {
43
- const agentId = asRecord(route)?.agentId;
41
+ const agentId = asOptionalRecord(route)?.agentId;
44
42
  if (typeof agentId === "string") agentIds.add(normalizeAgentId(agentId));
45
43
  }
46
44
  }
@@ -62,6 +60,8 @@ function describeVoiceCallSchemaMigration(migration) {
62
60
  switch (migration.kind) {
63
61
  case "agent-databases-composite-primary-key": return "agent database registry primary key -> agent_id,path";
64
62
  case "audit-events-v2": return "audit event ledger -> versioned message lifecycle schema";
63
+ case "commitments-retirement-v7": return "retired commitments storage -> removed table and indexes";
64
+ case "worker-placement-execution-mode-v8": return "cloud worker placements -> execution-mode claims";
65
65
  case "operator-approvals-system-agent": return "operator approvals -> OpenClaw system changes";
66
66
  case "session-watch-cursor-provenance-v4": return "session watch cursors -> provenance column";
67
67
  case "strict-tables-v3": return "tables -> SQLite STRICT typing";
@@ -185,12 +185,14 @@ const stateMigrations = [{
185
185
  id: "voice-call-calls-jsonl-to-plugin-state",
186
186
  label: "Voice Call call log",
187
187
  async detectLegacyState(params) {
188
+ const { detectOpenClawStateDatabaseSchemaMigrations } = await import("openclaw/plugin-sdk/doctor-repair-runtime");
188
189
  const { entries } = await readLegacyCallRecords(resolveVoiceCallLegacyCallLogPath(resolveVoiceCallStorePath(params)));
189
190
  const schemaMigrations = detectOpenClawStateDatabaseSchemaMigrations({ env: resolveVoiceCallStateDatabaseEnv(params) });
190
191
  if (entries.length === 0 && schemaMigrations.length === 0) return null;
191
192
  return { preview: [...schemaMigrations.map((migration) => `- Voice Call SQLite schema: ${describeVoiceCallSchemaMigration(migration)}`), ...entries.length > 0 ? [`- Voice Call call log: ${entries.length} ${entries.length === 1 ? "record" : "records"} -> plugin state (${CALL_RECORD_EVENTS_NAMESPACE})`] : []] };
192
193
  },
193
194
  async migrateLegacyState(params) {
195
+ const { detectOpenClawStateDatabaseSchemaMigrations, repairOpenClawStateDatabaseSchema } = await import("openclaw/plugin-sdk/doctor-repair-runtime");
194
196
  const changes = [];
195
197
  const warnings = [];
196
198
  const filePath = resolveVoiceCallLegacyCallLogPath(resolveVoiceCallStorePath(params));
@@ -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";
4
- import { n as markWebhookReplay, t as createWebhookReplayCache } from "./webhook-replay-aaP79dI8.js";
3
+ import { n as normalizeProxyIp, s as getHeader } from "./runtime-entry-F13eJ4pp.js";
4
+ import { n as reserveWebhookReplay, t as createWebhookReplayCache } from "./webhook-replay-FQrEGVSM.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
- import { safeEqualSecret } from "openclaw/plugin-sdk/security-runtime";
9
+ import { redactSensitiveText, 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;
@@ -244,8 +256,7 @@ function verifyTelnyxWebhook(ctx, publicKey, options) {
244
256
  return {
245
257
  ok: true,
246
258
  reason: "verification skipped (dev mode)",
247
- isReplay: markWebhookReplay(telnyxReplayCache, replayKey),
248
- verifiedRequestKey: replayKey
259
+ ...reserveWebhookReplay(telnyxReplayCache, replayKey)
249
260
  };
250
261
  }
251
262
  if (!publicKey) return {
@@ -258,8 +269,8 @@ function verifyTelnyxWebhook(ctx, publicKey, options) {
258
269
  ok: false,
259
270
  reason: "Missing signature or timestamp header"
260
271
  };
261
- const eventTimeSec = Number.parseInt(timestamp, 10);
262
- if (!Number.isFinite(eventTimeSec)) return {
272
+ const eventTimeSec = /^(?:0|[1-9]\d*)$/.test(timestamp) ? Number(timestamp) : void 0;
273
+ if (eventTimeSec === void 0 || !Number.isSafeInteger(eventTimeSec)) return {
263
274
  ok: false,
264
275
  reason: "Invalid timestamp header"
265
276
  };
@@ -281,8 +292,7 @@ function verifyTelnyxWebhook(ctx, publicKey, options) {
281
292
  const replayKey = `telnyx:${sha256Hex(`${timestamp}\n${canonicalSignature}\n${ctx.rawBody}`)}`;
282
293
  return {
283
294
  ok: true,
284
- isReplay: markWebhookReplay(telnyxReplayCache, replayKey),
285
- verifiedRequestKey: replayKey
295
+ ...reserveWebhookReplay(telnyxReplayCache, replayKey)
286
296
  };
287
297
  } catch (err) {
288
298
  return {
@@ -300,8 +310,7 @@ function verifyTwilioWebhook(ctx, authToken, options) {
300
310
  return {
301
311
  ok: true,
302
312
  reason: "verification skipped (dev mode)",
303
- isReplay: markWebhookReplay(twilioReplayCache, replayKey),
304
- verifiedRequestKey: replayKey
313
+ ...reserveWebhookReplay(twilioReplayCache, replayKey)
305
314
  };
306
315
  }
307
316
  const signature = getHeader(ctx.headers, "x-twilio-signature");
@@ -327,8 +336,7 @@ function verifyTwilioWebhook(ctx, authToken, options) {
327
336
  return {
328
337
  ok: true,
329
338
  verificationUrl,
330
- isReplay: markWebhookReplay(twilioReplayCache, replayKey),
331
- verifiedRequestKey: replayKey
339
+ ...reserveWebhookReplay(twilioReplayCache, replayKey)
332
340
  };
333
341
  }
334
342
  const variants = /* @__PURE__ */ new Set();
@@ -351,8 +359,7 @@ function verifyTwilioWebhook(ctx, authToken, options) {
351
359
  return {
352
360
  ok: true,
353
361
  verificationUrl: candidateUrl,
354
- isReplay: markWebhookReplay(twilioReplayCache, replayKey),
355
- verifiedRequestKey: replayKey
362
+ ...reserveWebhookReplay(twilioReplayCache, replayKey)
356
363
  };
357
364
  }
358
365
  const isNgrokFreeTier = verificationUrl.includes(".ngrok-free.app") || verificationUrl.includes(".ngrok.io");
@@ -444,8 +451,7 @@ function verifyPlivoWebhook(ctx, authToken, options) {
444
451
  return {
445
452
  ok: true,
446
453
  reason: "verification skipped (dev mode)",
447
- isReplay: markWebhookReplay(plivoReplayCache, replayKey),
448
- verifiedRequestKey: replayKey
454
+ ...reserveWebhookReplay(plivoReplayCache, replayKey)
449
455
  };
450
456
  }
451
457
  const signatureV3 = getHeader(ctx.headers, "x-plivo-signature-v3");
@@ -495,13 +501,11 @@ function verifyPlivoWebhook(ctx, authToken, options) {
495
501
  postParams,
496
502
  nonce: nonceV3
497
503
  });
498
- const isReplay = markWebhookReplay(plivoReplayCache, replayKey);
499
504
  return {
500
505
  ok: true,
501
506
  version: "v3",
502
507
  verificationUrl,
503
- isReplay,
504
- verifiedRequestKey: replayKey
508
+ ...reserveWebhookReplay(plivoReplayCache, replayKey)
505
509
  };
506
510
  }
507
511
  if (signatureV2 && nonceV2) {
@@ -517,13 +521,11 @@ function verifyPlivoWebhook(ctx, authToken, options) {
517
521
  reason: "Invalid Plivo V2 signature"
518
522
  };
519
523
  const replayKey = createPlivoV2ReplayKey(verificationUrl, nonceV2);
520
- const isReplay = markWebhookReplay(plivoReplayCache, replayKey);
521
524
  return {
522
525
  ok: true,
523
526
  version: "v2",
524
527
  verificationUrl,
525
- isReplay,
526
- verifiedRequestKey: replayKey
528
+ ...reserveWebhookReplay(plivoReplayCache, replayKey)
527
529
  };
528
530
  }
529
531
  return {
@@ -543,26 +545,20 @@ async function cancelProviderResponseBody(response) {
543
545
  function appendTruncatedSuffix(text) {
544
546
  return `${text.trimEnd()}${TRUNCATED_SUFFIX}`;
545
547
  }
546
- async function readProviderResponseTextWithLimit(params) {
547
- if (params.truncateOnLimit) {
548
- const prefix = await readResponseTextPrefix(params.response, params.maxBytes);
549
- return prefix.truncated ? appendTruncatedSuffix(prefix.text) : prefix.text;
548
+ async function readVoiceCallProviderJsonResponse(response, malformedJsonMessage) {
549
+ const body = await readResponseWithLimit(response, PROVIDER_JSON_RESPONSE_MAX_BYTES, { onOverflow: ({ size, maxBytes }) => /* @__PURE__ */ new Error(`provider response body too large: ${size} bytes (limit: ${maxBytes} bytes)`) });
550
+ if (body.byteLength === 0) return;
551
+ try {
552
+ const text = new TextDecoder("utf-8", { fatal: true }).decode(body);
553
+ return JSON.parse(text);
554
+ } catch (cause) {
555
+ throw new Error(malformedJsonMessage, { cause });
550
556
  }
551
- const body = await readResponseWithLimit(params.response, params.maxBytes, { onOverflow: ({ size, maxBytes }) => /* @__PURE__ */ new Error(`provider response body too large: ${size} bytes (limit: ${maxBytes} bytes)`) });
552
- return new TextDecoder().decode(body);
553
- }
554
- async function readProviderJsonResponseText(response) {
555
- return await readProviderResponseTextWithLimit({
556
- response,
557
- maxBytes: PROVIDER_JSON_RESPONSE_MAX_BYTES
558
- });
559
557
  }
560
558
  async function readProviderErrorResponseSnippet(response) {
561
- return await readProviderResponseTextWithLimit({
562
- response,
563
- maxBytes: PROVIDER_ERROR_RESPONSE_MAX_BYTES,
564
- truncateOnLimit: true
565
- });
559
+ const prefix = await readResponseTextPrefix(response, PROVIDER_ERROR_RESPONSE_MAX_BYTES);
560
+ const text = redactSensitiveText(prefix.text, { mode: "tools" });
561
+ return prefix.truncated ? appendTruncatedSuffix(text) : text;
566
562
  }
567
563
  //#endregion
568
564
  //#region extensions/voice-call/src/providers/shared/guarded-json-api.ts
@@ -589,16 +585,10 @@ async function guardedJsonApiRequest(params) {
589
585
  const errorText = await readProviderErrorResponseSnippet(response);
590
586
  throw new Error(`${params.errorPrefix}: ${response.status} ${errorText}`);
591
587
  }
592
- const text = await readProviderJsonResponseText(response);
593
- if (!text) return;
594
- try {
595
- return JSON.parse(text);
596
- } catch {
597
- throw new Error(`${params.errorPrefix}: malformed JSON response`);
598
- }
588
+ return await readVoiceCallProviderJsonResponse(response, `${params.errorPrefix}: malformed JSON response`);
599
589
  } finally {
600
590
  await release();
601
591
  }
602
592
  }
603
593
  //#endregion
604
- export { reconstructWebhookUrl as a, verifyTwilioWebhook as c, readProviderJsonResponseText as i, cancelProviderResponseBody as n, verifyPlivoWebhook as o, readProviderErrorResponseSnippet as r, verifyTelnyxWebhook as s, guardedJsonApiRequest as t };
594
+ 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 };