@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.
- package/dist/.setup/{carrier-api-Bt9KxU9b.mjs → carrier-api-CZQtPnYQ.mjs} +18 -34
- package/dist/.setup/{cli-cSqX5Cvm.mjs → cli-BUVmNuk8.mjs} +117 -69
- package/dist/.setup/{config-BdbTQCSh.mjs → config-D_w9HzxP.mjs} +63 -126
- package/dist/.setup/{mock-rySvj-G6.mjs → mock-fux1a_i_.mjs} +4 -6
- package/dist/.setup/{plivo-C3NbDLe-.mjs → plivo-Cpxxx8BT.mjs} +8 -24
- package/dist/.setup/{realtime-handler-dG-YrnQ5.mjs → realtime-handler-BkoZ-ZYd.mjs} +196 -278
- package/dist/.setup/{response-generator-DEU_bfHI.mjs → response-generator-COluXT2F.mjs} +12 -17
- package/dist/.setup/{runtime-entry-BqhtjzE-.mjs → runtime-entry-Br7ttAwq.mjs} +414 -593
- package/dist/.setup/{state-retention-api-ONgqE-_V.mjs → state-retention-api-BDJR8dnG.mjs} +7 -13
- package/dist/.setup/{store-path-Cfwff6j7.mjs → store-path-C0yJCDE8.mjs} +4 -6
- package/dist/.setup/{telnyx-BuC-Ttbj.mjs → telnyx-DdFzdUEf.mjs} +5 -7
- package/dist/.setup/{twilio-BVBIIqx-.mjs → twilio-BqUbz9jp.mjs} +11 -21
- package/dist/.setup/{twilio-region-wBOx-8sz.mjs → twilio-region-DVg9p6JJ.mjs} +1 -2
- package/dist/.setup/{voice-mapping-1lzFmzOF.mjs → voice-mapping-CvuJ1B50.mjs} +1 -2
- package/dist/.setup/webhook-replay-DnlMxw4z.mjs +30 -0
- package/dist/.source-module-urls/8b7cb5a3dc0f034090cbbda318ba348c0d48853d04f0e3b500529cf7666abe03.json +1 -1
- package/dist/cli-metadata.js +1 -2
- package/dist/cli-output-mode.js +1 -2
- package/dist/doctor-contract-api.js +22 -4
- package/dist/index.js +109 -203
- package/dist/runtime-entry.js +1 -1
- package/dist/setup-api.js +3 -6
- package/dist/state-retention-api.js +1 -1
- package/node_modules/typebox/build/format/duration.d.mts +2 -2
- package/node_modules/typebox/build/format/duration.mjs +2 -2
- package/node_modules/typebox/build/format/idn_email.d.mts +1 -0
- package/node_modules/typebox/build/format/idn_email.mjs +1 -0
- package/node_modules/typebox/build/format/idn_hostname.d.mts +1 -1
- package/node_modules/typebox/build/format/idn_hostname.mjs +1 -1
- package/node_modules/typebox/build/format/ipv4.d.mts +2 -2
- package/node_modules/typebox/build/format/ipv4.mjs +2 -2
- package/node_modules/typebox/build/format/ipv6.d.mts +1 -1
- package/node_modules/typebox/build/format/ipv6.mjs +1 -1
- package/node_modules/typebox/build/format/relative_json_pointer.d.mts +2 -2
- package/node_modules/typebox/build/format/relative_json_pointer.mjs +2 -2
- package/node_modules/typebox/build/schema/build.mjs +1 -0
- package/node_modules/typebox/build/schema/engine/_unique.d.mts +4 -2
- package/node_modules/typebox/build/schema/engine/_unique.mjs +7 -3
- package/node_modules/typebox/build/schema/engine/additionalItems.mjs +3 -3
- package/node_modules/typebox/build/schema/engine/additionalProperties.d.mts +0 -3
- package/node_modules/typebox/build/schema/engine/additionalProperties.mjs +15 -17
- package/node_modules/typebox/build/schema/engine/contains.mjs +3 -3
- package/node_modules/typebox/build/schema/engine/index.d.mts +1 -0
- package/node_modules/typebox/build/schema/engine/index.mjs +1 -0
- package/node_modules/typebox/build/schema/engine/items.mjs +7 -4
- package/node_modules/typebox/build/schema/engine/maxContains.mjs +2 -2
- package/node_modules/typebox/build/schema/engine/minContains.mjs +3 -3
- package/node_modules/typebox/build/schema/engine/oneOf.mjs +2 -2
- package/node_modules/typebox/build/schema/engine/patternProperties.mjs +2 -2
- package/node_modules/typebox/build/schema/engine/propertyNames.mjs +2 -2
- package/node_modules/typebox/build/schema/engine/unevaluatedItems.mjs +2 -2
- package/node_modules/typebox/build/schema/engine/unevaluatedProperties.mjs +2 -2
- package/node_modules/typebox/build/system/hashing/hash.mjs +63 -34
- package/node_modules/typebox/build/system/locale/en_US.mjs +2 -1
- package/node_modules/typebox/build/system/memory/assign.d.mts +2 -8
- package/node_modules/typebox/build/system/memory/assign.mjs +13 -6
- package/node_modules/typebox/build/system/memory/clone.d.mts +4 -5
- package/node_modules/typebox/build/system/memory/clone.mjs +8 -96
- package/node_modules/typebox/build/system/memory/create.d.mts +1 -5
- package/node_modules/typebox/build/system/memory/create.mjs +18 -13
- package/node_modules/typebox/build/system/memory/deep.d.mts +2 -0
- package/node_modules/typebox/build/system/memory/deep.mjs +108 -0
- package/node_modules/typebox/build/system/memory/discard.d.mts +1 -1
- package/node_modules/typebox/build/system/memory/discard.mjs +9 -8
- package/node_modules/typebox/build/system/memory/finalize.d.mts +4 -0
- package/node_modules/typebox/build/system/memory/finalize.mjs +30 -0
- package/node_modules/typebox/build/system/memory/keys.d.mts +2 -0
- package/node_modules/typebox/build/system/memory/keys.mjs +8 -0
- package/node_modules/typebox/build/system/memory/update.d.mts +1 -4
- package/node_modules/typebox/build/system/memory/update.mjs +43 -16
- package/node_modules/typebox/build/type/engine/call/index.d.mts +1 -0
- package/node_modules/typebox/build/type/engine/call/index.mjs +1 -0
- package/node_modules/typebox/build/type/engine/evaluate/composite.d.mts +2 -24
- package/node_modules/typebox/build/type/engine/evaluate/composite.mjs +2 -29
- package/node_modules/typebox/build/type/engine/evaluate/index.d.mts +1 -0
- package/node_modules/typebox/build/type/engine/evaluate/index.mjs +1 -0
- package/node_modules/typebox/build/type/engine/evaluate/narrow.d.mts +5 -1
- package/node_modules/typebox/build/type/engine/evaluate/narrow.mjs +6 -1
- package/node_modules/typebox/build/type/engine/evaluate/property.d.mts +30 -0
- package/node_modules/typebox/build/type/engine/evaluate/property.mjs +29 -0
- package/node_modules/typebox/build/type/engine/immutable/instantiate_add.d.mts +1 -3
- package/node_modules/typebox/build/type/engine/immutable/instantiate_add.mjs +1 -4
- package/node_modules/typebox/build/type/engine/object/from_intersect.d.mts +2 -2
- package/node_modules/typebox/build/type/engine/object/from_intersect.mjs +2 -2
- package/node_modules/typebox/build/type/engine/object/from_union.d.mts +2 -2
- package/node_modules/typebox/build/type/engine/object/from_union.mjs +2 -2
- package/node_modules/typebox/build/type/engine/optional/instantiate_add.d.mts +1 -3
- package/node_modules/typebox/build/type/engine/optional/instantiate_add.mjs +1 -4
- package/node_modules/typebox/build/type/engine/readonly/instantiate_add.d.mts +1 -3
- package/node_modules/typebox/build/type/engine/readonly/instantiate_add.mjs +1 -4
- package/node_modules/typebox/build/type/engine/this/index.d.mts +1 -0
- package/node_modules/typebox/build/type/engine/this/index.mjs +1 -0
- package/node_modules/typebox/build/value/clone/clone.d.mts +4 -3
- package/node_modules/typebox/build/value/clone/clone.mjs +5 -7
- package/node_modules/typebox/build/value/repair/from_cyclic.d.mts +2 -0
- package/node_modules/typebox/build/value/repair/from_cyclic.mjs +6 -0
- package/node_modules/typebox/build/value/shared/union_priority_sort.mjs +25 -14
- package/node_modules/typebox/package.json +1 -1
- package/node_modules/typebox/readme.md +1 -1
- package/node_modules/zod/package.json +1 -1
- package/node_modules/zod/src/v4/classic/tests/array.test.ts +27 -0
- package/node_modules/zod/src/v4/classic/tests/map.test.ts +7 -0
- package/node_modules/zod/src/v4/classic/tests/set.test.ts +6 -0
- package/node_modules/zod/src/v4/core/compile.ts +9 -0
- package/node_modules/zod/src/v4/core/schemas.ts +35 -1
- package/node_modules/zod/src/v4/core/util.ts +13 -0
- package/node_modules/zod/src/v4/core/versions.ts +1 -1
- package/node_modules/zod/v4/core/compile.cjs +8 -0
- package/node_modules/zod/v4/core/compile.js +8 -0
- package/node_modules/zod/v4/core/schemas.cjs +30 -0
- package/node_modules/zod/v4/core/schemas.d.cts +3 -1
- package/node_modules/zod/v4/core/schemas.d.ts +3 -1
- package/node_modules/zod/v4/core/schemas.js +30 -0
- package/node_modules/zod/v4/core/util.cjs +15 -0
- package/node_modules/zod/v4/core/util.d.cts +2 -0
- package/node_modules/zod/v4/core/util.d.ts +2 -0
- package/node_modules/zod/v4/core/util.js +14 -0
- package/node_modules/zod/v4/core/versions.cjs +1 -1
- package/node_modules/zod/v4/core/versions.js +1 -1
- package/openclaw.plugin.json +9 -2
- package/package.json +6 -6
- package/dist/.setup/webhook-replay-B7RJNWNG.mjs +0 -43
- package/node_modules/typebox/build/system/memory/freeze.d.mts +0 -4
- package/node_modules/typebox/build/system/memory/freeze.mjs +0 -6
|
@@ -1,20 +1,18 @@
|
|
|
1
1
|
import { fetchWithSsrFGuard } from "../api.js";
|
|
2
|
-
import {
|
|
3
|
-
import { n as reserveWebhookReplay, t as createWebhookReplayCache } from "./webhook-replay-
|
|
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
|
-
|
|
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) >
|
|
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
|
-
|
|
356
|
-
|
|
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
|
-
|
|
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
|
-
|
|
437
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
2
|
-
import { g as
|
|
3
|
-
import {
|
|
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 {
|
|
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 {
|
|
16
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
|
|
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, {
|
|
238
|
+
}, params, {
|
|
239
|
+
progress: false,
|
|
240
|
+
...opts?.signal ? { signal: opts.signal } : {}
|
|
241
|
+
})
|
|
224
242
|
};
|
|
225
243
|
} catch (err) {
|
|
226
|
-
if (
|
|
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 }, {
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
310
|
-
|
|
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
|
-
|
|
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
|
-
|
|
396
|
-
process.on("SIGTERM", onSigterm);
|
|
444
|
+
if (!interruptGateway) listen();
|
|
397
445
|
runtime = createRuntime();
|
|
398
446
|
}
|
|
399
447
|
const active = await runtime;
|
|
400
|
-
if (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
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
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
|
|
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
|
|
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 };
|