@openclaw/voice-call 2026.6.8-beta.2 → 2026.6.9-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/{guarded-json-api-tNxwxQt0.js → guarded-json-api-CwIsZeyV.js} +1 -1
- package/dist/index.js +1 -1
- package/dist/{plivo-K3Nv0D8o.js → plivo-BK14Vyzi.js} +2 -2
- package/dist/{response-generator-39zOYri_.js → response-generator-DRjFfMHx.js} +1 -1
- package/dist/{runtime-entry-DeDXZz2k.js → runtime-entry-BJNPmzDP.js} +76 -23
- package/dist/runtime-entry.js +1 -1
- package/dist/{telnyx-YbJp3QiY.js → telnyx-Bkl9KKSK.js} +1 -1
- package/dist/{twilio-DBcVB36L.js → twilio-CDM_5OfE.js} +2 -2
- package/npm-shrinkwrap.json +3 -3
- package/package.json +4 -4
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { fetchWithSsrFGuard } from "./runtime-api.js";
|
|
2
2
|
import "./api.js";
|
|
3
|
-
import { a as getHeader } from "./runtime-entry-
|
|
3
|
+
import { a as getHeader } from "./runtime-entry-BJNPmzDP.js";
|
|
4
4
|
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
5
5
|
import { isLoopbackHost } from "openclaw/plugin-sdk/gateway-runtime";
|
|
6
6
|
import { isFutureDateTimestampMs, resolveExpiresAtMsFromDurationMs } from "openclaw/plugin-sdk/number-runtime";
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { definePluginEntry, sleep } from "./runtime-api.js";
|
|
2
2
|
import "./api.js";
|
|
3
3
|
import { i as resolveVoiceCallConfig, s as validateProviderConfig } from "./config-jaA6vOZ2.js";
|
|
4
|
-
import { c as getTailscaleSelfInfo, l as setupTailscaleExposureRoute, o as resolveWebhookExposureStatus, p as resolveUserPath, s as cleanupTailscaleExposureRoute, t as createVoiceCallRuntime } from "./runtime-entry-
|
|
4
|
+
import { c as getTailscaleSelfInfo, l as setupTailscaleExposureRoute, o as resolveWebhookExposureStatus, p as resolveUserPath, s as cleanupTailscaleExposureRoute, t as createVoiceCallRuntime } from "./runtime-entry-BJNPmzDP.js";
|
|
5
5
|
import { c as getCallHistoryFromStore, m as setVoiceCallStateRuntime } from "./store-8M2m4Isq.js";
|
|
6
6
|
import { i as parseVoiceCallPluginConfig, r as normalizeVoiceCallLegacyConfigInput, t as formatVoiceCallLegacyConfigWarnings } from "./config-compat-BPPFhsJ7.js";
|
|
7
7
|
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { a as getHeader, m as escapeXml } from "./runtime-entry-
|
|
2
|
-
import { n as reconstructWebhookUrl, r as verifyPlivoWebhook, t as guardedJsonApiRequest } from "./guarded-json-api-
|
|
1
|
+
import { a as getHeader, m as escapeXml } from "./runtime-entry-BJNPmzDP.js";
|
|
2
|
+
import { n as reconstructWebhookUrl, r as verifyPlivoWebhook, t as guardedJsonApiRequest } from "./guarded-json-api-CwIsZeyV.js";
|
|
3
3
|
import { normalizeLowercaseStringOrEmpty, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
4
4
|
import crypto from "node:crypto";
|
|
5
5
|
//#region extensions/voice-call/src/providers/plivo.ts
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { o as resolveVoiceCallSessionKey } from "./config-jaA6vOZ2.js";
|
|
2
|
-
import { f as resolveVoiceResponseModel } from "./runtime-entry-
|
|
2
|
+
import { f as resolveVoiceResponseModel } from "./runtime-entry-BJNPmzDP.js";
|
|
3
3
|
import { isRecord, normalizeLowercaseStringOrEmpty, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
4
4
|
import crypto from "node:crypto";
|
|
5
5
|
import { applyModelOverrideToSessionEntry } from "openclaw/plugin-sdk/model-session-runtime";
|
|
@@ -107,6 +107,16 @@ function startMaxDurationTimer(params) {
|
|
|
107
107
|
}, maxDurationMs);
|
|
108
108
|
params.ctx.maxDurationTimers.set(params.callId, timer);
|
|
109
109
|
}
|
|
110
|
+
/** Backfill max-duration enforcement from the first live conversation signal. */
|
|
111
|
+
function ensureMaxDurationTimerForLiveCall(params) {
|
|
112
|
+
if (params.call.answeredAt) return;
|
|
113
|
+
params.call.answeredAt = params.liveAt;
|
|
114
|
+
startMaxDurationTimer({
|
|
115
|
+
ctx: params.ctx,
|
|
116
|
+
callId: params.call.callId,
|
|
117
|
+
onTimeout: params.onTimeout
|
|
118
|
+
});
|
|
119
|
+
}
|
|
110
120
|
/** Clear and forget a pending final-transcript waiter. */
|
|
111
121
|
function clearTranscriptWaiter(ctx, callId) {
|
|
112
122
|
const waiter = ctx.transcriptWaiters.get(callId);
|
|
@@ -418,6 +428,14 @@ async function speak(ctx, callId, text) {
|
|
|
418
428
|
};
|
|
419
429
|
const { call, providerCallId, provider } = connected;
|
|
420
430
|
try {
|
|
431
|
+
ensureMaxDurationTimerForLiveCall({
|
|
432
|
+
ctx,
|
|
433
|
+
call,
|
|
434
|
+
liveAt: Date.now(),
|
|
435
|
+
onTimeout: async (id) => {
|
|
436
|
+
await endCall(ctx, id, { reason: "timeout" });
|
|
437
|
+
}
|
|
438
|
+
});
|
|
421
439
|
transitionState(call, "speaking");
|
|
422
440
|
persistCallRecord(ctx.storePath, call);
|
|
423
441
|
const numberRouteKey = typeof call.metadata?.numberRouteKey === "string" ? call.metadata.numberRouteKey : call.to;
|
|
@@ -795,6 +813,14 @@ function processEvent(ctx, event) {
|
|
|
795
813
|
transitionState(call, "active");
|
|
796
814
|
break;
|
|
797
815
|
case "call.speaking":
|
|
816
|
+
ensureMaxDurationTimerForLiveCall({
|
|
817
|
+
ctx,
|
|
818
|
+
call,
|
|
819
|
+
liveAt: event.timestamp,
|
|
820
|
+
onTimeout: async (callId) => {
|
|
821
|
+
await endCall(ctx, callId, { reason: "timeout" });
|
|
822
|
+
}
|
|
823
|
+
});
|
|
798
824
|
transitionState(call, "speaking");
|
|
799
825
|
break;
|
|
800
826
|
case "call.speech":
|
|
@@ -807,6 +833,14 @@ function processEvent(ctx, event) {
|
|
|
807
833
|
}
|
|
808
834
|
addTranscriptEntry(call, "user", event.transcript);
|
|
809
835
|
}
|
|
836
|
+
ensureMaxDurationTimerForLiveCall({
|
|
837
|
+
ctx,
|
|
838
|
+
call,
|
|
839
|
+
liveAt: event.timestamp,
|
|
840
|
+
onTimeout: async (callId) => {
|
|
841
|
+
await endCall(ctx, callId, { reason: "timeout" });
|
|
842
|
+
}
|
|
843
|
+
});
|
|
810
844
|
transitionState(call, "listening");
|
|
811
845
|
break;
|
|
812
846
|
case "call.silence":
|
|
@@ -857,6 +891,9 @@ function incrementRestoreStatusCount(counts, status) {
|
|
|
857
891
|
const key = normalizeOptionalString(status) ?? "terminal";
|
|
858
892
|
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
859
893
|
}
|
|
894
|
+
function resolveRestoredMaxDurationAnchor(call) {
|
|
895
|
+
return call.answeredAt ?? (call.state === "speaking" || call.state === "listening" ? call.startedAt : void 0);
|
|
896
|
+
}
|
|
860
897
|
function resolveDefaultStoreBase(config, storePath) {
|
|
861
898
|
const rawOverride = storePath?.trim() || config.store?.trim();
|
|
862
899
|
if (rawOverride) return resolveUserPath(rawOverride);
|
|
@@ -903,24 +940,31 @@ var CallManager = class {
|
|
|
903
940
|
this.providerCallIdMap = /* @__PURE__ */ new Map();
|
|
904
941
|
for (const [callId, call] of verified) if (call.providerCallId) this.providerCallIdMap.set(call.providerCallId, callId);
|
|
905
942
|
let skippedAlreadyElapsedTimers = 0;
|
|
906
|
-
for (const [callId, call] of verified)
|
|
907
|
-
const
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
if (
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
ctx: this.getContext(),
|
|
917
|
-
callId,
|
|
918
|
-
timeoutMs: maxDurationMs - elapsed,
|
|
919
|
-
onTimeout: async (id) => {
|
|
920
|
-
await endCall(this.getContext(), id, { reason: "timeout" });
|
|
943
|
+
for (const [callId, call] of verified) {
|
|
944
|
+
const maxDurationAnchor = resolveRestoredMaxDurationAnchor(call);
|
|
945
|
+
if (maxDurationAnchor !== void 0 && !TerminalStates.has(call.state)) {
|
|
946
|
+
const elapsed = Date.now() - maxDurationAnchor;
|
|
947
|
+
const maxDurationMs = resolveVoiceCallSecondsTimerDelayMs(this.config.maxDurationSeconds);
|
|
948
|
+
if (elapsed >= maxDurationMs) {
|
|
949
|
+
verified.delete(callId);
|
|
950
|
+
if (call.providerCallId) this.providerCallIdMap.delete(call.providerCallId);
|
|
951
|
+
skippedAlreadyElapsedTimers += 1;
|
|
952
|
+
continue;
|
|
921
953
|
}
|
|
922
|
-
|
|
923
|
-
|
|
954
|
+
if (call.answeredAt === void 0) {
|
|
955
|
+
call.answeredAt = maxDurationAnchor;
|
|
956
|
+
persistCallRecord(this.storePath, call);
|
|
957
|
+
}
|
|
958
|
+
startMaxDurationTimer({
|
|
959
|
+
ctx: this.getContext(),
|
|
960
|
+
callId,
|
|
961
|
+
timeoutMs: maxDurationMs - elapsed,
|
|
962
|
+
onTimeout: async (id) => {
|
|
963
|
+
await endCall(this.getContext(), id, { reason: "timeout" });
|
|
964
|
+
}
|
|
965
|
+
});
|
|
966
|
+
console.log(`[voice-call] Restarted max-duration timer for restored call ${callId}`);
|
|
967
|
+
}
|
|
924
968
|
}
|
|
925
969
|
if (skippedAlreadyElapsedTimers > 0) console.log(`[voice-call] Skipped ${skippedAlreadyElapsedTimers} restored call(s) whose max-duration timer already elapsed`);
|
|
926
970
|
if (verified.size > 0) console.log(`[voice-call] Restored ${verified.size} active call(s) from store`);
|
|
@@ -2442,6 +2486,11 @@ function isProviderStatusTerminal(status) {
|
|
|
2442
2486
|
//#endregion
|
|
2443
2487
|
//#region extensions/voice-call/src/webhook/stale-call-reaper.ts
|
|
2444
2488
|
const CHECK_INTERVAL_MS = 3e4;
|
|
2489
|
+
/** States that indicate a live conversation with speech/transcription.
|
|
2490
|
+
* Inbound Twilio calls may never fire a call.answered event, so answeredAt
|
|
2491
|
+
* can be absent even while the call is actively transcribing. These states
|
|
2492
|
+
* prove the call is live and should not be reaped. */
|
|
2493
|
+
const LiveConversationStates = new Set(["speaking", "listening"]);
|
|
2445
2494
|
/** Start a stale-call reaper and return its cleanup callback. */
|
|
2446
2495
|
function startStaleCallReaper(params) {
|
|
2447
2496
|
const maxAgeSeconds = params.staleCallReaperSeconds;
|
|
@@ -2450,7 +2499,7 @@ function startStaleCallReaper(params) {
|
|
|
2450
2499
|
const interval = setInterval(() => {
|
|
2451
2500
|
const now = Date.now();
|
|
2452
2501
|
for (const call of params.manager.getActiveCalls()) {
|
|
2453
|
-
if (call.answeredAt || TerminalStates.has(call.state)) continue;
|
|
2502
|
+
if (call.answeredAt || TerminalStates.has(call.state) || LiveConversationStates.has(call.state)) continue;
|
|
2454
2503
|
const age = now - call.startedAt;
|
|
2455
2504
|
if (age > maxAgeMs) {
|
|
2456
2505
|
console.log(`[voice-call] Reaping stale call ${call.callId} (age: ${Math.round(age / 1e3)}s, state: ${call.state})`);
|
|
@@ -2478,7 +2527,7 @@ function loadRealtimeTranscriptionRuntime() {
|
|
|
2478
2527
|
return realtimeTranscriptionRuntimePromise;
|
|
2479
2528
|
}
|
|
2480
2529
|
function loadResponseGeneratorModule() {
|
|
2481
|
-
responseGeneratorModulePromise ??= import("./response-generator-
|
|
2530
|
+
responseGeneratorModulePromise ??= import("./response-generator-DRjFfMHx.js");
|
|
2482
2531
|
return responseGeneratorModulePromise;
|
|
2483
2532
|
}
|
|
2484
2533
|
function sanitizeTranscriptForLog(value) {
|
|
@@ -3055,7 +3104,11 @@ var VoiceCallWebhookServer = class {
|
|
|
3055
3104
|
try {
|
|
3056
3105
|
const pathname = buildRequestUrl(req.url).pathname;
|
|
3057
3106
|
const pattern = this.realtimeHandler?.getStreamPathPattern();
|
|
3058
|
-
|
|
3107
|
+
if (!pattern) return false;
|
|
3108
|
+
const normalizedPattern = this.normalizeWebhookPathForMatch(pattern);
|
|
3109
|
+
const normalizedPathname = this.normalizeWebhookPathForMatch(pathname);
|
|
3110
|
+
if (normalizedPattern === "/") return true;
|
|
3111
|
+
return normalizedPathname === normalizedPattern || normalizedPathname.startsWith(`${normalizedPattern}/`);
|
|
3059
3112
|
} catch {
|
|
3060
3113
|
return false;
|
|
3061
3114
|
}
|
|
@@ -3162,15 +3215,15 @@ let mockProviderPromise;
|
|
|
3162
3215
|
let realtimeVoiceRuntimePromise;
|
|
3163
3216
|
let realtimeHandlerPromise;
|
|
3164
3217
|
function loadTelnyxProvider() {
|
|
3165
|
-
telnyxProviderPromise ??= import("./telnyx-
|
|
3218
|
+
telnyxProviderPromise ??= import("./telnyx-Bkl9KKSK.js");
|
|
3166
3219
|
return telnyxProviderPromise;
|
|
3167
3220
|
}
|
|
3168
3221
|
function loadTwilioProvider() {
|
|
3169
|
-
twilioProviderPromise ??= import("./twilio-
|
|
3222
|
+
twilioProviderPromise ??= import("./twilio-CDM_5OfE.js");
|
|
3170
3223
|
return twilioProviderPromise;
|
|
3171
3224
|
}
|
|
3172
3225
|
function loadPlivoProvider() {
|
|
3173
|
-
plivoProviderPromise ??= import("./plivo-
|
|
3226
|
+
plivoProviderPromise ??= import("./plivo-BK14Vyzi.js");
|
|
3174
3227
|
return plivoProviderPromise;
|
|
3175
3228
|
}
|
|
3176
3229
|
function loadMockProvider() {
|
package/dist/runtime-entry.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as createVoiceCallRuntime } from "./runtime-entry-
|
|
1
|
+
import { t as createVoiceCallRuntime } from "./runtime-entry-BJNPmzDP.js";
|
|
2
2
|
export { createVoiceCallRuntime };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as verifyTelnyxWebhook, t as guardedJsonApiRequest } from "./guarded-json-api-
|
|
1
|
+
import { i as verifyTelnyxWebhook, t as guardedJsonApiRequest } from "./guarded-json-api-CwIsZeyV.js";
|
|
2
2
|
import crypto from "node:crypto";
|
|
3
3
|
//#region extensions/voice-call/src/providers/telnyx.ts
|
|
4
4
|
function normalizeTelnyxDirection(direction) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { fetchWithSsrFGuard } from "./runtime-api.js";
|
|
2
2
|
import "./api.js";
|
|
3
|
-
import { a as getHeader, d as chunkAudio, h as mapVoiceToPolly, i as normalizeProviderStatus, m as escapeXml, n as isProviderStatusTerminal, r as mapProviderStatusToEndReason } from "./runtime-entry-
|
|
4
|
-
import { a as verifyTwilioWebhook, t as guardedJsonApiRequest } from "./guarded-json-api-
|
|
3
|
+
import { a as getHeader, d as chunkAudio, h as mapVoiceToPolly, i as normalizeProviderStatus, m as escapeXml, n as isProviderStatusTerminal, r as mapProviderStatusToEndReason } from "./runtime-entry-BJNPmzDP.js";
|
|
4
|
+
import { a as verifyTwilioWebhook, t as guardedJsonApiRequest } from "./guarded-json-api-CwIsZeyV.js";
|
|
5
5
|
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
6
6
|
import crypto from "node:crypto";
|
|
7
7
|
import { safeEqualSecret } from "openclaw/plugin-sdk/security-runtime";
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/voice-call",
|
|
3
|
-
"version": "2026.6.
|
|
3
|
+
"version": "2026.6.9-beta.1",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@openclaw/voice-call",
|
|
9
|
-
"version": "2026.6.
|
|
9
|
+
"version": "2026.6.9-beta.1",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"commander": "14.0.3",
|
|
12
12
|
"typebox": "1.1.39",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"zod": "4.4.3"
|
|
15
15
|
},
|
|
16
16
|
"peerDependencies": {
|
|
17
|
-
"openclaw": ">=2026.6.
|
|
17
|
+
"openclaw": ">=2026.6.9-beta.1"
|
|
18
18
|
},
|
|
19
19
|
"peerDependenciesMeta": {
|
|
20
20
|
"openclaw": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/voice-call",
|
|
3
|
-
"version": "2026.6.
|
|
3
|
+
"version": "2026.6.9-beta.1",
|
|
4
4
|
"description": "OpenClaw voice-call plugin for Twilio, Telnyx, and Plivo phone calls.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"zod": "4.4.3"
|
|
15
15
|
},
|
|
16
16
|
"peerDependencies": {
|
|
17
|
-
"openclaw": ">=2026.6.
|
|
17
|
+
"openclaw": ">=2026.6.9-beta.1"
|
|
18
18
|
},
|
|
19
19
|
"peerDependenciesMeta": {
|
|
20
20
|
"openclaw": {
|
|
@@ -31,10 +31,10 @@
|
|
|
31
31
|
"minHostVersion": ">=2026.4.10"
|
|
32
32
|
},
|
|
33
33
|
"compat": {
|
|
34
|
-
"pluginApi": ">=2026.6.
|
|
34
|
+
"pluginApi": ">=2026.6.9-beta.1"
|
|
35
35
|
},
|
|
36
36
|
"build": {
|
|
37
|
-
"openclawVersion": "2026.6.
|
|
37
|
+
"openclawVersion": "2026.6.9-beta.1"
|
|
38
38
|
},
|
|
39
39
|
"release": {
|
|
40
40
|
"publishToClawHub": true,
|