@openclaw/voice-call 2026.10.10-beta.5 → 2026.11.5-beta.1

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 (124) hide show
  1. package/dist/.setup/{carrier-api-Bt9KxU9b.mjs → carrier-api-CZQtPnYQ.mjs} +18 -34
  2. package/dist/.setup/{cli-cSqX5Cvm.mjs → cli-BUVmNuk8.mjs} +117 -69
  3. package/dist/.setup/{config-BdbTQCSh.mjs → config-D_w9HzxP.mjs} +63 -126
  4. package/dist/.setup/{mock-rySvj-G6.mjs → mock-fux1a_i_.mjs} +4 -6
  5. package/dist/.setup/{plivo-C3NbDLe-.mjs → plivo-Cpxxx8BT.mjs} +8 -24
  6. package/dist/.setup/{realtime-handler-dG-YrnQ5.mjs → realtime-handler-BkoZ-ZYd.mjs} +196 -278
  7. package/dist/.setup/{response-generator-DEU_bfHI.mjs → response-generator-COluXT2F.mjs} +12 -17
  8. package/dist/.setup/{runtime-entry-BqhtjzE-.mjs → runtime-entry-Br7ttAwq.mjs} +414 -593
  9. package/dist/.setup/{state-retention-api-ONgqE-_V.mjs → state-retention-api-BDJR8dnG.mjs} +7 -13
  10. package/dist/.setup/{store-path-Cfwff6j7.mjs → store-path-C0yJCDE8.mjs} +4 -6
  11. package/dist/.setup/{telnyx-BuC-Ttbj.mjs → telnyx-DdFzdUEf.mjs} +5 -7
  12. package/dist/.setup/{twilio-BVBIIqx-.mjs → twilio-BqUbz9jp.mjs} +11 -21
  13. package/dist/.setup/{twilio-region-wBOx-8sz.mjs → twilio-region-DVg9p6JJ.mjs} +1 -2
  14. package/dist/.setup/{voice-mapping-1lzFmzOF.mjs → voice-mapping-CvuJ1B50.mjs} +1 -2
  15. package/dist/.setup/webhook-replay-DnlMxw4z.mjs +30 -0
  16. package/dist/.source-module-urls/8b7cb5a3dc0f034090cbbda318ba348c0d48853d04f0e3b500529cf7666abe03.json +1 -1
  17. package/dist/cli-metadata.js +1 -2
  18. package/dist/cli-output-mode.js +1 -2
  19. package/dist/doctor-contract-api.js +22 -4
  20. package/dist/index.js +109 -203
  21. package/dist/runtime-entry.js +1 -1
  22. package/dist/setup-api.js +3 -6
  23. package/dist/state-retention-api.js +1 -1
  24. package/node_modules/typebox/build/format/duration.d.mts +2 -2
  25. package/node_modules/typebox/build/format/duration.mjs +2 -2
  26. package/node_modules/typebox/build/format/idn_email.d.mts +1 -0
  27. package/node_modules/typebox/build/format/idn_email.mjs +1 -0
  28. package/node_modules/typebox/build/format/idn_hostname.d.mts +1 -1
  29. package/node_modules/typebox/build/format/idn_hostname.mjs +1 -1
  30. package/node_modules/typebox/build/format/ipv4.d.mts +2 -2
  31. package/node_modules/typebox/build/format/ipv4.mjs +2 -2
  32. package/node_modules/typebox/build/format/ipv6.d.mts +1 -1
  33. package/node_modules/typebox/build/format/ipv6.mjs +1 -1
  34. package/node_modules/typebox/build/format/relative_json_pointer.d.mts +2 -2
  35. package/node_modules/typebox/build/format/relative_json_pointer.mjs +2 -2
  36. package/node_modules/typebox/build/schema/build.mjs +1 -0
  37. package/node_modules/typebox/build/schema/engine/_unique.d.mts +4 -2
  38. package/node_modules/typebox/build/schema/engine/_unique.mjs +7 -3
  39. package/node_modules/typebox/build/schema/engine/additionalItems.mjs +3 -3
  40. package/node_modules/typebox/build/schema/engine/additionalProperties.d.mts +0 -3
  41. package/node_modules/typebox/build/schema/engine/additionalProperties.mjs +15 -17
  42. package/node_modules/typebox/build/schema/engine/contains.mjs +3 -3
  43. package/node_modules/typebox/build/schema/engine/index.d.mts +1 -0
  44. package/node_modules/typebox/build/schema/engine/index.mjs +1 -0
  45. package/node_modules/typebox/build/schema/engine/items.mjs +7 -4
  46. package/node_modules/typebox/build/schema/engine/maxContains.mjs +2 -2
  47. package/node_modules/typebox/build/schema/engine/minContains.mjs +3 -3
  48. package/node_modules/typebox/build/schema/engine/oneOf.mjs +2 -2
  49. package/node_modules/typebox/build/schema/engine/patternProperties.mjs +2 -2
  50. package/node_modules/typebox/build/schema/engine/propertyNames.mjs +2 -2
  51. package/node_modules/typebox/build/schema/engine/unevaluatedItems.mjs +2 -2
  52. package/node_modules/typebox/build/schema/engine/unevaluatedProperties.mjs +2 -2
  53. package/node_modules/typebox/build/system/hashing/hash.mjs +63 -34
  54. package/node_modules/typebox/build/system/locale/en_US.mjs +2 -1
  55. package/node_modules/typebox/build/system/memory/assign.d.mts +2 -8
  56. package/node_modules/typebox/build/system/memory/assign.mjs +13 -6
  57. package/node_modules/typebox/build/system/memory/clone.d.mts +4 -5
  58. package/node_modules/typebox/build/system/memory/clone.mjs +8 -96
  59. package/node_modules/typebox/build/system/memory/create.d.mts +1 -5
  60. package/node_modules/typebox/build/system/memory/create.mjs +18 -13
  61. package/node_modules/typebox/build/system/memory/deep.d.mts +2 -0
  62. package/node_modules/typebox/build/system/memory/deep.mjs +108 -0
  63. package/node_modules/typebox/build/system/memory/discard.d.mts +1 -1
  64. package/node_modules/typebox/build/system/memory/discard.mjs +9 -8
  65. package/node_modules/typebox/build/system/memory/finalize.d.mts +4 -0
  66. package/node_modules/typebox/build/system/memory/finalize.mjs +30 -0
  67. package/node_modules/typebox/build/system/memory/keys.d.mts +2 -0
  68. package/node_modules/typebox/build/system/memory/keys.mjs +8 -0
  69. package/node_modules/typebox/build/system/memory/update.d.mts +1 -4
  70. package/node_modules/typebox/build/system/memory/update.mjs +43 -16
  71. package/node_modules/typebox/build/type/engine/call/index.d.mts +1 -0
  72. package/node_modules/typebox/build/type/engine/call/index.mjs +1 -0
  73. package/node_modules/typebox/build/type/engine/evaluate/composite.d.mts +2 -24
  74. package/node_modules/typebox/build/type/engine/evaluate/composite.mjs +2 -29
  75. package/node_modules/typebox/build/type/engine/evaluate/index.d.mts +1 -0
  76. package/node_modules/typebox/build/type/engine/evaluate/index.mjs +1 -0
  77. package/node_modules/typebox/build/type/engine/evaluate/narrow.d.mts +5 -1
  78. package/node_modules/typebox/build/type/engine/evaluate/narrow.mjs +6 -1
  79. package/node_modules/typebox/build/type/engine/evaluate/property.d.mts +30 -0
  80. package/node_modules/typebox/build/type/engine/evaluate/property.mjs +29 -0
  81. package/node_modules/typebox/build/type/engine/immutable/instantiate_add.d.mts +1 -3
  82. package/node_modules/typebox/build/type/engine/immutable/instantiate_add.mjs +1 -4
  83. package/node_modules/typebox/build/type/engine/object/from_intersect.d.mts +2 -2
  84. package/node_modules/typebox/build/type/engine/object/from_intersect.mjs +2 -2
  85. package/node_modules/typebox/build/type/engine/object/from_union.d.mts +2 -2
  86. package/node_modules/typebox/build/type/engine/object/from_union.mjs +2 -2
  87. package/node_modules/typebox/build/type/engine/optional/instantiate_add.d.mts +1 -3
  88. package/node_modules/typebox/build/type/engine/optional/instantiate_add.mjs +1 -4
  89. package/node_modules/typebox/build/type/engine/readonly/instantiate_add.d.mts +1 -3
  90. package/node_modules/typebox/build/type/engine/readonly/instantiate_add.mjs +1 -4
  91. package/node_modules/typebox/build/type/engine/this/index.d.mts +1 -0
  92. package/node_modules/typebox/build/type/engine/this/index.mjs +1 -0
  93. package/node_modules/typebox/build/value/clone/clone.d.mts +4 -3
  94. package/node_modules/typebox/build/value/clone/clone.mjs +5 -7
  95. package/node_modules/typebox/build/value/repair/from_cyclic.d.mts +2 -0
  96. package/node_modules/typebox/build/value/repair/from_cyclic.mjs +6 -0
  97. package/node_modules/typebox/build/value/shared/union_priority_sort.mjs +25 -14
  98. package/node_modules/typebox/package.json +1 -1
  99. package/node_modules/typebox/readme.md +1 -1
  100. package/node_modules/zod/package.json +1 -1
  101. package/node_modules/zod/src/v4/classic/tests/array.test.ts +27 -0
  102. package/node_modules/zod/src/v4/classic/tests/map.test.ts +7 -0
  103. package/node_modules/zod/src/v4/classic/tests/set.test.ts +6 -0
  104. package/node_modules/zod/src/v4/core/compile.ts +9 -0
  105. package/node_modules/zod/src/v4/core/schemas.ts +35 -1
  106. package/node_modules/zod/src/v4/core/util.ts +13 -0
  107. package/node_modules/zod/src/v4/core/versions.ts +1 -1
  108. package/node_modules/zod/v4/core/compile.cjs +8 -0
  109. package/node_modules/zod/v4/core/compile.js +8 -0
  110. package/node_modules/zod/v4/core/schemas.cjs +30 -0
  111. package/node_modules/zod/v4/core/schemas.d.cts +3 -1
  112. package/node_modules/zod/v4/core/schemas.d.ts +3 -1
  113. package/node_modules/zod/v4/core/schemas.js +30 -0
  114. package/node_modules/zod/v4/core/util.cjs +15 -0
  115. package/node_modules/zod/v4/core/util.d.cts +2 -0
  116. package/node_modules/zod/v4/core/util.d.ts +2 -0
  117. package/node_modules/zod/v4/core/util.js +14 -0
  118. package/node_modules/zod/v4/core/versions.cjs +1 -1
  119. package/node_modules/zod/v4/core/versions.js +1 -1
  120. package/openclaw.plugin.json +9 -2
  121. package/package.json +6 -6
  122. package/dist/.setup/webhook-replay-B7RJNWNG.mjs +0 -43
  123. package/node_modules/typebox/build/system/memory/freeze.d.mts +0 -4
  124. package/node_modules/typebox/build/system/memory/freeze.mjs +0 -6
@@ -1,20 +1,18 @@
1
1
  import { fetchWithSsrFGuard } from "../api.js";
2
- import { c as getHeader, r as normalizeProxyIp } from "./runtime-entry-BqhtjzE-.mjs";
3
- import { n as reserveWebhookReplay, t as createWebhookReplayCache } from "./webhook-replay-B7RJNWNG.mjs";
2
+ import { n as normalizeProxyIp, s as getHeader } from "./runtime-entry-Br7ttAwq.mjs";
3
+ import { n as reserveWebhookReplay, t as createWebhookReplayCache } from "./webhook-replay-DnlMxw4z.mjs";
4
+ import crypto from "node:crypto";
4
5
  import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
5
6
  import { isLoopbackHost } from "openclaw/plugin-sdk/gateway-runtime";
6
7
  import { normalizeLowercaseStringOrEmpty, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
7
- import crypto from "node:crypto";
8
8
  import { isIP } from "node:net";
9
+ import { sha256Hex } from "openclaw/plugin-sdk/node-crypto-runtime";
9
10
  import { redactSensitiveText, safeEqualSecret } from "openclaw/plugin-sdk/security-runtime";
10
11
  import { readResponseTextPrefix, readResponseWithLimit } from "openclaw/plugin-sdk/response-limit-runtime";
11
- //#region extensions/voice-call/src/webhook-security.ts
12
+ // extensions/voice-call/src/webhook-security.ts
12
13
  const twilioReplayCache = createWebhookReplayCache();
13
14
  const plivoReplayCache = createWebhookReplayCache();
14
15
  const telnyxReplayCache = createWebhookReplayCache();
15
- function sha256Hex(input) {
16
- return crypto.createHash("sha256").update(input).digest("hex");
17
- }
18
16
  function skipWebhookVerification(provider, ctx, cache) {
19
17
  const replayKey = `${provider}:skip:${sha256Hex(`${ctx.method}\n${ctx.url}\n${ctx.rawBody}`)}`;
20
18
  return {
@@ -254,9 +252,8 @@ function verifyTelnyxWebhook(ctx, publicKey, options) {
254
252
  ok: false,
255
253
  reason: "Invalid signature"
256
254
  };
257
- const maxSkewMs = options?.maxSkewMs ?? 3e5;
258
255
  const eventTimeMs = eventTimeSec * 1e3;
259
- if (Math.abs(Date.now() - eventTimeMs) > maxSkewMs) return {
256
+ if (Math.abs(Date.now() - eventTimeMs) > 3e5) return {
260
257
  ok: false,
261
258
  reason: "Timestamp too old"
262
259
  };
@@ -297,7 +294,6 @@ function verifyTwilioWebhook(ctx, authToken, options) {
297
294
  });
298
295
  return {
299
296
  ok: true,
300
- verificationUrl: candidateUrl,
301
297
  ...reserveWebhookReplay(twilioReplayCache, replayKey)
302
298
  };
303
299
  };
@@ -317,11 +313,9 @@ function verifyTwilioWebhook(ctx, authToken, options) {
317
313
  const result = verifyUrl(candidateUrl);
318
314
  if (result) return result;
319
315
  }
320
- const isNgrokFreeTier = verificationUrl.includes(".ngrok-free.app") || verificationUrl.includes(".ngrok.io");
321
316
  return {
322
317
  ok: false,
323
- reason: `Invalid signature for URL: ${redactTwilioVerificationUrlForDiagnostics(verificationUrl)}`,
324
- isNgrokFreeTier
318
+ reason: `Invalid signature for URL: ${redactTwilioVerificationUrlForDiagnostics(verificationUrl)}`
325
319
  };
326
320
  }
327
321
  function normalizeSignatureBase64(input) {
@@ -352,19 +346,13 @@ function constructPlivoV3BaseUrl(params) {
352
346
  function verifyPlivoSignature(params) {
353
347
  const hmacBase = `${params.baseUrl}${params.version === "v3" ? "." : ""}${params.nonce}`;
354
348
  const expected = normalizeSignatureBase64(crypto.createHmac("sha256", params.authToken).update(hmacBase).digest("base64"));
355
- const ok = params.signatures.map(normalizeSignatureBase64).some((signature) => safeEqualSecret(expected, signature));
356
- const result = {
357
- ok,
358
- version: params.version,
359
- verificationUrl: params.verificationUrl
360
- };
361
- if (!ok) return {
362
- ...result,
349
+ if (!params.signatures.map(normalizeSignatureBase64).some((signature) => safeEqualSecret(expected, signature))) return {
350
+ ok: false,
363
351
  reason: `Invalid Plivo ${params.version.toUpperCase()} signature`
364
352
  };
365
353
  const replayKey = `plivo:${params.version}:${sha256Hex(`${params.baseUrl}\n${params.nonce}`)}`;
366
354
  return {
367
- ...result,
355
+ ok: true,
368
356
  ...reserveWebhookReplay(plivoReplayCache, replayKey)
369
357
  };
370
358
  }
@@ -400,8 +388,6 @@ function verifyPlivoWebhook(ctx, authToken, options) {
400
388
  const method = ctx.method === "GET" || ctx.method === "POST" ? ctx.method : null;
401
389
  if (!method) return {
402
390
  ok: false,
403
- version: "v3",
404
- verificationUrl,
405
391
  reason: `Unsupported HTTP method for Plivo V3 signature: ${ctx.method}`
406
392
  };
407
393
  const postParams = new URLSearchParams(ctx.rawBody);
@@ -413,7 +399,6 @@ function verifyPlivoWebhook(ctx, authToken, options) {
413
399
  return verifyPlivoSignature({
414
400
  authToken,
415
401
  version: "v3",
416
- verificationUrl,
417
402
  signatures: normalizeStringEntries(signatureV3.split(",")),
418
403
  nonce: nonceV3,
419
404
  baseUrl
@@ -422,19 +407,20 @@ function verifyPlivoWebhook(ctx, authToken, options) {
422
407
  if (signatureV2 && nonceV2) return verifyPlivoSignature({
423
408
  authToken,
424
409
  version: "v2",
425
- verificationUrl,
426
410
  signatures: [signatureV2],
427
411
  nonce: nonceV2,
428
412
  baseUrl: getBaseUrlNoQuery(verificationUrl)
429
413
  });
430
414
  return {
431
415
  ok: false,
432
- reason: "Missing Plivo signature headers (V3 or V2)",
433
- verificationUrl
416
+ reason: "Missing Plivo signature headers (V3 or V2)"
434
417
  };
435
418
  }
436
- //#endregion
437
- //#region extensions/voice-call/src/providers/shared/guarded-json-api.ts
419
+ // extensions/voice-call/src/providers/base.ts
420
+ function assertVoiceCallProviderSetting(value, message) {
421
+ if (!value) throw new Error(message);
422
+ }
423
+ // extensions/voice-call/src/providers/shared/guarded-json-api.ts
438
424
  const VOICE_CALL_PROVIDER_API_TIMEOUT_MS = 3e4;
439
425
  const PROVIDER_JSON_RESPONSE_MAX_BYTES = 1048576;
440
426
  const PROVIDER_ERROR_RESPONSE_MAX_BYTES = 8192;
@@ -474,8 +460,7 @@ async function guardedJsonApiRequest(params) {
474
460
  await release();
475
461
  }
476
462
  }
477
- //#endregion
478
- //#region extensions/voice-call/src/providers/shared/carrier-api.ts
463
+ // extensions/voice-call/src/providers/shared/carrier-api.ts
479
464
  /** Keep call controls and recovery probes on the same carrier endpoint and credentials. */
480
465
  function createCarrierApi(name, baseUrl, authorization, options = {}) {
481
466
  const provider = name.toLowerCase();
@@ -526,5 +511,4 @@ function createCarrierApi(name, baseUrl, authorization, options = {}) {
526
511
  }
527
512
  };
528
513
  }
529
- //#endregion
530
- export { verifyTwilioWebhook as a, verifyTelnyxWebhook as i, reconstructWebhookUrl as n, verifyPlivoWebhook as r, createCarrierApi as t };
514
+ export { verifyTelnyxWebhook as a, verifyPlivoWebhook as i, assertVoiceCallProviderSetting as n, verifyTwilioWebhook as o, reconstructWebhookUrl as r, createCarrierApi as t };
@@ -1,19 +1,20 @@
1
- import { sleep } from "../api.js";
2
- import { g as CallBriefSchema, l as resolveVoiceCallStreamExposurePaths, u as validateProviderConfig } from "./config-BdbTQCSh.mjs";
3
- import { S as resolveVoiceCallAgentId, _ as findCallInStore, b as setVoiceCallStateRuntime, d as getTailscaleSelfInfo, f as setupTailscaleExposureRoutes, g as MAX_CALL_RECORD_EVENTS, l as resolveWebhookExposureStatus, u as cleanupTailscaleExposureRoute, v as getCallHistoryFromStore, y as loadActiveCallsFromStore } from "./runtime-entry-BqhtjzE-.mjs";
4
- import { r as resolveUserPath, t as resolveDefaultVoiceCallStoreDir } from "./store-path-Cfwff6j7.mjs";
1
+ import { c as resolveVoiceCallStreamExposurePaths, h as CallBriefSchema, l as validateProviderConfig } from "./config-D_w9HzxP.mjs";
2
+ import { _ as getCallHistoryFromStore, c as resolveWebhookExposureStatus, d as setupTailscaleExposureRoutes, g as findCallInStore, h as MAX_CALL_RECORD_EVENTS, l as cleanupTailscaleExposureRoute, u as getTailscaleSelfInfo, v as loadActiveCallsFromStore, x as resolveVoiceCallAgentId, y as setVoiceCallStateRuntime } from "./runtime-entry-Br7ttAwq.mjs";
3
+ import { r as resolveUserPath, t as resolveDefaultVoiceCallStoreDir } from "./store-path-C0yJCDE8.mjs";
5
4
  import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
6
5
  import { callGatewayFromCli, isGatewayClientRequestError, isGatewayTransportError, redactSensitiveUrlLikeString } from "openclaw/plugin-sdk/gateway-runtime";
7
- import { isRecord, normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/string-coerce-runtime";
8
6
  import { MAX_TCP_PORT, MAX_TIMER_TIMEOUT_MS, addTimerTimeoutGraceMs, clampTimerTimeoutMs, parseStrictNonNegativeInteger } from "openclaw/plugin-sdk/number-runtime";
7
+ import { isRecord, normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/string-coerce-runtime";
8
+ import { sleepWithAbort } from "openclaw/plugin-sdk/runtime-env";
9
9
  import fs from "node:fs";
10
10
  import path from "node:path";
11
- import { realpath } from "node:fs/promises";
11
+ import { format } from "node:util";
12
12
  import { createDeferred } from "openclaw/plugin-sdk/concurrency-runtime";
13
+ import { realpath } from "node:fs/promises";
13
14
  import { readRegularFile } from "openclaw/plugin-sdk/file-access-runtime";
14
15
  import { once } from "node:events";
15
- import { format } from "node:util";
16
- //#region extensions/voice-call/src/cli-command-io.ts
16
+ import { setTimeout } from "node:timers/promises";
17
+ // extensions/voice-call/src/cli-command-io.ts
17
18
  function writeCliLine(...values) {
18
19
  process.stdout.write(`${format(...values)}\n`);
19
20
  }
@@ -26,11 +27,11 @@ function parseCliInteger(raw, optionName, opts) {
26
27
  if (parsed === void 0 || parsed < min || opts?.max !== void 0 && parsed > opts.max) throw new Error(`Invalid numeric value for ${optionName}: ${raw ?? ""}`);
27
28
  return parsed;
28
29
  }
29
- //#endregion
30
- //#region extensions/voice-call/src/cli-call-log.ts
30
+ // extensions/voice-call/src/cli-call-log.ts
31
31
  const READ_BYTES = 65536;
32
- async function writeLogChunk(chunk) {
33
- if (!process.stdout.write(chunk)) await once(process.stdout, "drain");
32
+ async function writeLogChunk(chunk, signal) {
33
+ signal.throwIfAborted();
34
+ if (!process.stdout.write(chunk)) await once(process.stdout, "drain", { signal });
34
35
  }
35
36
  function* readLogLine({ fd, start, end }) {
36
37
  for (let offset = start; offset < end;) {
@@ -41,20 +42,21 @@ function* readLogLine({ fd, start, end }) {
41
42
  yield chunk.subarray(0, bytesRead);
42
43
  }
43
44
  }
44
- async function* readLogLines(file, last, pollMs) {
45
+ async function* readLogLines(file, last, pollMs, signal) {
45
46
  let initial = true;
46
47
  let offset = 0;
47
48
  let lineStart = 0;
48
49
  let previous;
49
50
  const buffer = Buffer.alloc(READ_BYTES);
50
51
  for (;;) {
52
+ signal?.throwIfAborted();
51
53
  let fd;
52
54
  try {
53
55
  const flags = process.platform === "win32" ? "r" : fs.constants.O_RDONLY | fs.constants.O_NONBLOCK;
54
56
  fd = fs.openSync(file, flags);
55
57
  } catch (error) {
56
58
  if (initial || pollMs === void 0 || !isRecord(error) || error.code !== "ENOENT") throw error;
57
- await sleep(pollMs);
59
+ await setTimeout(pollMs, void 0, { signal });
58
60
  continue;
59
61
  }
60
62
  try {
@@ -96,7 +98,7 @@ async function* readLogLines(file, last, pollMs) {
96
98
  }
97
99
  initial = false;
98
100
  if (pollMs === void 0) return;
99
- await sleep(pollMs);
101
+ await setTimeout(pollMs, void 0, { signal });
100
102
  }
101
103
  }
102
104
  function summarizeSeries(values) {
@@ -143,24 +145,43 @@ function registerVoiceCallLogs(params) {
143
145
  const file = options.file;
144
146
  const since = parseCliInteger(options.since, "--since", { min: 0 });
145
147
  const pollMs = parseCliInteger(options.poll, "--poll", { min: 50 });
146
- if (fs.existsSync(file) && path.basename(file) !== "calls.jsonl") for await (const line of readLogLines(file, since, pollMs)) {
147
- for (const chunk of readLogLine(line)) await writeLogChunk(chunk);
148
- await writeLogChunk("\n");
149
- }
150
- else {
151
- params.ensureHistoryStateRuntime();
152
- let initial = true;
153
- let seen = /* @__PURE__ */ new Set();
154
- for (;;) {
155
- const lines = (await getCallHistoryFromStore(path.dirname(file), MAX_CALL_RECORD_EVENTS)).map((call) => JSON.stringify(call));
156
- for (const line of initial ? lines.slice(Math.max(0, lines.length - since)) : lines) if (!seen.has(line)) {
157
- await writeLogChunk(`${line}\n`);
158
- seen.add(line);
148
+ const cancellation = new AbortController();
149
+ const { signal } = cancellation;
150
+ const interrupt = (exitCode) => {
151
+ if (!signal.aborted) {
152
+ process.exitCode = exitCode;
153
+ cancellation.abort();
154
+ }
155
+ };
156
+ const onSigint = () => interrupt(130);
157
+ const onSigterm = () => interrupt(143);
158
+ process.on("SIGINT", onSigint);
159
+ process.on("SIGTERM", onSigterm);
160
+ try {
161
+ if (fs.existsSync(file) && path.basename(file) !== "calls.jsonl") for await (const line of readLogLines(file, since, pollMs, signal)) {
162
+ for (const chunk of readLogLine(line)) await writeLogChunk(chunk, signal);
163
+ await writeLogChunk("\n", signal);
164
+ }
165
+ else {
166
+ params.ensureHistoryStateRuntime();
167
+ let initial = true;
168
+ let seen = /* @__PURE__ */ new Set();
169
+ while (!signal.aborted) {
170
+ const lines = (await getCallHistoryFromStore(path.dirname(file), MAX_CALL_RECORD_EVENTS)).map((call) => JSON.stringify(call));
171
+ for (const line of initial ? lines.slice(Math.max(0, lines.length - since)) : lines) if (!seen.has(line)) {
172
+ await writeLogChunk(`${line}\n`, signal);
173
+ seen.add(line);
174
+ }
175
+ seen = new Set(lines);
176
+ initial = false;
177
+ await setTimeout(pollMs, void 0, { signal });
159
178
  }
160
- seen = new Set(lines);
161
- initial = false;
162
- await sleep(pollMs);
163
179
  }
180
+ } catch (error) {
181
+ if (!signal.aborted || !(error instanceof Error) || error.name !== "AbortError") throw error;
182
+ } finally {
183
+ process.off("SIGINT", onSigint);
184
+ process.off("SIGTERM", onSigterm);
164
185
  }
165
186
  });
166
187
  params.root.command("latency").description("Summarize turn latency metrics from voice-call history or a custom JSONL log").option("--file <path>", "Path to calls.jsonl", params.defaultFile).option("--last <n>", "Analyze last N records", "200").action(async (options) => {
@@ -183,15 +204,11 @@ function registerVoiceCallLogs(params) {
183
204
  }
184
205
  });
185
206
  }
186
- //#endregion
187
- //#region extensions/voice-call/src/cli-gateway-call.ts
207
+ // extensions/voice-call/src/cli-gateway-call.ts
188
208
  const VOICE_CALL_GATEWAY_DEFAULT_TIMEOUT_MS = 5e3;
189
209
  const VOICE_CALL_GATEWAY_OPERATION_TIMEOUT_MS = 3e4;
190
210
  const VOICE_CALL_GATEWAY_TRANSCRIPT_BUFFER_MS = 1e4;
191
211
  const VOICE_CALL_GATEWAY_POLL_INTERVAL_MS = 1e3;
192
- function isGatewayUnavailableForLocalFallback(err) {
193
- return isGatewayTransportError(err) && err.kind === "closed" && (err.code === void 0 || err.code === 1006);
194
- }
195
212
  function isGatewayCredentialFailure(err) {
196
213
  return err instanceof Error && (err.name === "GatewayCredentialsRequiredError" || err.name === "GatewayExplicitAuthRequiredError" || err.name === "GatewaySecretRefUnavailableError");
197
214
  }
@@ -209,21 +226,23 @@ function gatewayOperationalError(err) {
209
226
  })();
210
227
  return new Error(redactSensitiveUrlLikeString(detail));
211
228
  }
212
- function isUnknownMethod(err, method) {
213
- return formatErrorMessage(err).includes(`unknown method: ${method}`);
214
- }
215
229
  async function callVoiceCallGateway(method, params, opts) {
216
230
  try {
231
+ opts?.signal?.throwIfAborted();
217
232
  const timeoutMs = typeof opts?.timeoutMs === "number" && Number.isFinite(opts.timeoutMs) ? Math.max(1, Math.ceil(opts.timeoutMs)) : VOICE_CALL_GATEWAY_DEFAULT_TIMEOUT_MS;
218
233
  return {
219
234
  ok: true,
220
235
  payload: await callGatewayFromCli(method, {
221
236
  json: true,
222
237
  timeout: String(timeoutMs)
223
- }, params, { progress: false })
238
+ }, params, {
239
+ progress: false,
240
+ ...opts?.signal ? { signal: opts.signal } : {}
241
+ })
224
242
  };
225
243
  } catch (err) {
226
- if (isGatewayUnavailableForLocalFallback(err)) return {
244
+ if (opts?.signal?.aborted && err instanceof Error && err.name === "AbortError") throw err;
245
+ if (isGatewayTransportError(err) && err.kind === "closed" && (err.code === void 0 || err.code === 1006)) return {
227
246
  ok: false,
228
247
  error: err
229
248
  };
@@ -236,25 +255,31 @@ function resolveOperationTimeout(config) {
236
255
  function resolveContinueTimeout(config) {
237
256
  return clampTimerTimeoutMs(config.transcriptTimeoutMs + VOICE_CALL_GATEWAY_OPERATION_TIMEOUT_MS + VOICE_CALL_GATEWAY_TRANSCRIPT_BUFFER_MS) ?? 1;
238
257
  }
239
- async function pollContinueGateway(payload, fallbackTimeoutMs) {
258
+ async function pollContinueGateway(payload, fallbackTimeoutMs, signal) {
259
+ signal?.throwIfAborted();
240
260
  if (!isRecord(payload) || typeof payload.operationId !== "string") return payload;
241
261
  if (!payload.operationId) throw new Error("voicecall gateway response missing operationId");
242
262
  const operationId = payload.operationId;
243
263
  const timeoutMs = typeof payload.pollTimeoutMs === "number" ? clampTimerTimeoutMs(payload.pollTimeoutMs) ?? fallbackTimeoutMs : fallbackTimeoutMs;
244
264
  const deadlineMs = performance.now() + (clampTimerTimeoutMs(timeoutMs) ?? MAX_TIMER_TIMEOUT_MS);
245
265
  for (;;) {
266
+ signal?.throwIfAborted();
246
267
  const remainingMs = deadlineMs - performance.now();
247
268
  if (remainingMs <= 0) break;
248
- const gateway = await callVoiceCallGateway("voicecall.continue.result", { operationId }, { timeoutMs: Math.min(VOICE_CALL_GATEWAY_DEFAULT_TIMEOUT_MS, remainingMs) });
269
+ const gateway = await callVoiceCallGateway("voicecall.continue.result", { operationId }, {
270
+ timeoutMs: Math.min(VOICE_CALL_GATEWAY_DEFAULT_TIMEOUT_MS, remainingMs),
271
+ signal
272
+ });
249
273
  if (!gateway.ok) throw new Error(`gateway unavailable while waiting for voicecall continue result: ${formatErrorMessage(gateway.error)}`);
250
274
  const result = gateway.payload;
251
275
  if (!isRecord(result)) throw new Error("voicecall gateway response missing operation status");
252
- if (result.status === "completed") return result.result;
253
276
  if (result.status === "failed") throw new Error(typeof result.error === "string" ? result.error : "continue failed");
254
- if (result.status !== "pending") throw new Error("voicecall gateway response has unknown operation status");
277
+ if (result.status !== "pending" && result.status !== "completed") throw new Error("voicecall gateway response has unknown operation status");
278
+ signal?.throwIfAborted();
279
+ if (result.status === "completed") return result.result;
255
280
  const sleepMs = Math.min(VOICE_CALL_GATEWAY_POLL_INTERVAL_MS, deadlineMs - performance.now());
256
281
  if (sleepMs <= 0) break;
257
- await sleep(sleepMs);
282
+ await sleepWithAbort(sleepMs, signal);
258
283
  }
259
284
  throw new Error("voicecall continue timed out waiting for gateway operation");
260
285
  }
@@ -267,14 +292,20 @@ async function ensureStandaloneRuntime(params) {
267
292
  }
268
293
  }
269
294
  async function runGatewayManagerCommand(params) {
295
+ params.signal?.throwIfAborted();
270
296
  const gateway = await params.gatewayCall();
297
+ params.signal?.throwIfAborted();
271
298
  if (gateway.ok) {
272
- writeCliJson(params.resolveGatewayPayload ? await params.resolveGatewayPayload(gateway.payload) : gateway.payload);
299
+ const payload = params.resolveGatewayPayload ? await params.resolveGatewayPayload(gateway.payload) : gateway.payload;
300
+ params.signal?.throwIfAborted();
301
+ writeCliJson(payload);
273
302
  return;
274
303
  }
275
304
  const runtime = await ensureStandaloneRuntime(params);
305
+ params.signal?.throwIfAborted();
276
306
  const result = await params.managerFallback(runtime.manager);
277
307
  if (!result.success) throw new Error(result.error || `${params.failureLabel} failed`);
308
+ params.signal?.throwIfAborted();
278
309
  writeCliJson(result);
279
310
  }
280
311
  function readGatewayCallId(payload, invalidCallIdMessage) {
@@ -306,11 +337,22 @@ async function initiateVoiceCall(params) {
306
337
  if (params.failureMessage && !result.callId) throw new Error(params.failureMessage);
307
338
  return result.callId;
308
339
  }
309
- //#endregion
310
- //#region extensions/voice-call/src/cli.ts
340
+ // extensions/voice-call/src/cli.ts
341
+ const briefFileDecoder = new TextDecoder("utf-8", {
342
+ fatal: true,
343
+ ignoreBOM: true
344
+ });
311
345
  async function readCliBrief(options) {
312
346
  if (options.brief && options.briefFile) throw new Error("Use either --brief or --brief-file");
313
- const raw = options.briefFile ? (await readRegularFile({ filePath: await realpath(resolveUserPath(options.briefFile)) })).buffer.toString("utf8") : options.brief;
347
+ let raw = options.brief;
348
+ if (options.briefFile) {
349
+ const { buffer } = await readRegularFile({ filePath: await realpath(resolveUserPath(options.briefFile)) });
350
+ try {
351
+ raw = briefFileDecoder.decode(buffer);
352
+ } catch (error) {
353
+ throw new Error(`Brief file must be valid UTF-8: ${options.briefFile}. Re-save it as UTF-8 and retry.`, { cause: error });
354
+ }
355
+ }
314
356
  if (raw === void 0) return;
315
357
  if (raw.length > 16e3) throw new Error("Brief input is too large");
316
358
  const value = options.briefFile || raw.trimStart().startsWith("{") || raw.trimStart().startsWith("[") ? JSON.parse(raw) : { task: raw };
@@ -378,29 +420,40 @@ function writeSetupStatus(status) {
378
420
  writeCliLine("Voice Call setup: %s", status.ok ? "OK" : "needs attention");
379
421
  for (const check of status.checks) writeCliLine("%s %s: %s", check.ok ? "OK" : "FAIL", check.id, check.message);
380
422
  }
381
- async function runWithStandaloneRuntime(createRuntime, run) {
423
+ async function runWithStandaloneRuntime(createRuntime, run, interruptGateway = false) {
382
424
  let runtime;
383
425
  const interrupted = createDeferred();
426
+ const cancellation = new AbortController();
384
427
  let exitSignal;
385
428
  const interrupt = (signal) => {
386
429
  exitSignal ??= signal;
387
430
  process.exitCode = exitSignal === "SIGINT" ? 130 : 143;
431
+ cancellation.abort();
388
432
  interrupted.resolve();
389
433
  };
390
434
  const onSigint = () => interrupt("SIGINT");
391
435
  const onSigterm = () => interrupt("SIGTERM");
436
+ const listen = () => {
437
+ process.on("SIGINT", onSigint);
438
+ process.on("SIGTERM", onSigterm);
439
+ };
392
440
  try {
441
+ if (interruptGateway) listen();
393
442
  await run(async () => {
394
443
  if (!runtime) {
395
- process.on("SIGINT", onSigint);
396
- process.on("SIGTERM", onSigterm);
444
+ if (!interruptGateway) listen();
397
445
  runtime = createRuntime();
398
446
  }
399
447
  const active = await runtime;
400
- if (exitSignal) throw new Error(`Voice call command interrupted by ${exitSignal}`);
448
+ if (exitSignal) {
449
+ if (interruptGateway) cancellation.signal.throwIfAborted();
450
+ throw new Error(`Voice call command interrupted by ${exitSignal}`);
451
+ }
401
452
  return active;
402
- });
453
+ }, interruptGateway ? cancellation.signal : void 0);
403
454
  if (runtime) await interrupted.promise;
455
+ } catch (error) {
456
+ if (!interruptGateway || !cancellation.signal.aborted || !(error instanceof Error) || error.name !== "AbortError") throw error;
404
457
  } finally {
405
458
  try {
406
459
  await (await runtime?.catch(() => void 0))?.stop();
@@ -499,19 +552,15 @@ function registerVoiceCallCli(params) {
499
552
  message: options.message
500
553
  };
501
554
  const continueTimeoutMs = resolveContinueTimeout(config);
502
- return managerAction({
503
- gatewayCall: async () => {
504
- try {
505
- return await callVoiceCallGateway("voicecall.continue.start", gatewayParams, { timeoutMs: resolveOperationTimeout(config) });
506
- } catch (err) {
507
- if (!isUnknownMethod(err, "voicecall.continue.start")) throw err;
508
- return callVoiceCallGateway("voicecall.continue", gatewayParams, { timeoutMs: continueTimeoutMs });
509
- }
510
- },
511
- resolveGatewayPayload: (payload) => pollContinueGateway(payload, continueTimeoutMs),
555
+ return runWithStandaloneRuntime(createRuntime, (ensureRuntime, signal) => runGatewayManagerCommand({
556
+ config,
557
+ ensureRuntime,
558
+ signal,
559
+ gatewayCall: () => callVoiceCallGateway("voicecall.continue.start", gatewayParams, { timeoutMs: resolveOperationTimeout(config) }),
560
+ resolveGatewayPayload: (payload) => pollContinueGateway(payload, continueTimeoutMs, signal),
512
561
  managerFallback: (manager) => manager.continueCall(options.callId, options.message),
513
562
  failureLabel: "continue"
514
- });
563
+ }), true);
515
564
  });
516
565
  root.command("speak").description("Speak a message without waiting for response").requiredOption("--call-id <id>", "Call ID").requiredOption("--message <text>", "Message to speak").action((options) => managerAction({
517
566
  gatewayCall: () => callVoiceCallGateway("voicecall.speak", {
@@ -578,11 +627,11 @@ function registerVoiceCallCli(params) {
578
627
  });
579
628
  root.command("expose").description("Enable/disable Tailscale serve/funnel for the webhook").option("--mode <mode>", "off | serve (tailnet) | funnel (public)", "funnel").option("--path <path>", "Tailscale path to expose (recommend matching serve.path)").option("--port <port>", "Local webhook port").option("--serve-path <path>", "Local webhook path").action(async (options) => {
580
629
  const mode = resolveMode(options.mode ?? "funnel");
581
- const servePort = parseCliInteger(options.port ?? String(config.serve.port ?? 3334), "--port", {
630
+ const servePort = parseCliInteger(options.port ?? String(config.serve.port), "--port", {
582
631
  min: 1,
583
632
  max: MAX_TCP_PORT
584
633
  });
585
- const servePath = options.servePath ?? config.serve.path ?? "/voice/webhook";
634
+ const servePath = options.servePath ?? config.serve.path;
586
635
  const tsPath = options.path ?? config.tailscale?.path ?? servePath;
587
636
  const streamExposurePaths = resolveVoiceCallStreamExposurePaths(config, {
588
637
  publicWebhookPath: tsPath,
@@ -631,5 +680,4 @@ function registerVoiceCallCli(params) {
631
680
  });
632
681
  });
633
682
  }
634
- //#endregion
635
683
  export { registerVoiceCallCli };