@openclaw/voice-call 2026.7.2-beta.1 → 2026.7.2-beta.3
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/doctor-contract-api.js +13 -7
- package/dist/{guarded-json-api-DoLAoIRF.js → guarded-json-api-Dm4NvtGo.js} +1 -1
- package/dist/index.js +5 -15
- package/dist/{plivo-BICQA_yK.js → plivo-rISw-5A8.js} +2 -2
- package/dist/{realtime-handler-DJk-eYyf.js → realtime-handler-HSxcgwY_.js} +91 -165
- package/dist/{response-generator-hmAdGp5h.js → response-generator-r9wHPOEl.js} +1 -1
- package/dist/{runtime-entry-DhTLZ2uB.js → runtime-entry-D8Mkwy8G.js} +86 -167
- package/dist/runtime-entry.js +1 -1
- package/dist/{store-C1pl3avP.js → store-path-nYL-yM0S.js} +10 -3
- package/dist/{telnyx-B9tF5jeJ.js → telnyx-DTD9jTqC.js} +1 -1
- package/dist/{twilio-U_myvowz.js → twilio-GzbIb5fv.js} +9 -5
- package/npm-shrinkwrap.json +3 -3
- package/package.json +4 -4
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { a as
|
|
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";
|
|
2
2
|
import { normalizeAgentId } from "openclaw/plugin-sdk/routing";
|
|
3
|
-
import os from "node:os";
|
|
4
3
|
import path from "node:path";
|
|
4
|
+
import os from "node:os";
|
|
5
5
|
import fs from "node:fs/promises";
|
|
6
6
|
import { archiveLegacyStateSource, detectOpenClawStateDatabaseSchemaMigrations, repairOpenClawStateDatabaseSchema } from "openclaw/plugin-sdk/runtime-doctor";
|
|
7
7
|
//#region extensions/voice-call/doctor-contract-api.ts
|
|
@@ -50,7 +50,7 @@ function resolveSessionStoreAgentIds(params) {
|
|
|
50
50
|
function resolveVoiceCallStorePath(params) {
|
|
51
51
|
const configuredStore = getVoiceCallConfigStore(params.config);
|
|
52
52
|
if (configuredStore) return resolveUserPath(configuredStore, params.env);
|
|
53
|
-
return
|
|
53
|
+
return resolveDefaultVoiceCallStoreDir(params.env);
|
|
54
54
|
}
|
|
55
55
|
function resolveVoiceCallStateDatabaseEnv(params) {
|
|
56
56
|
return {
|
|
@@ -59,7 +59,14 @@ function resolveVoiceCallStateDatabaseEnv(params) {
|
|
|
59
59
|
};
|
|
60
60
|
}
|
|
61
61
|
function describeVoiceCallSchemaMigration(migration) {
|
|
62
|
-
|
|
62
|
+
switch (migration.kind) {
|
|
63
|
+
case "agent-databases-composite-primary-key": return "agent database registry primary key -> agent_id,path";
|
|
64
|
+
case "audit-events-v2": return "audit event ledger -> versioned message lifecycle schema";
|
|
65
|
+
case "operator-approvals-system-agent": return "operator approvals -> OpenClaw system changes";
|
|
66
|
+
case "session-watch-cursor-provenance-v4": return "session watch cursors -> provenance column";
|
|
67
|
+
case "strict-tables-v3": return "tables -> SQLite STRICT typing";
|
|
68
|
+
}
|
|
69
|
+
return migration.kind;
|
|
63
70
|
}
|
|
64
71
|
/** Return true when a path exists and is a file. */
|
|
65
72
|
/** Build the plugin state key for one migrated event chunk. */
|
|
@@ -190,15 +197,14 @@ const stateMigrations = [{
|
|
|
190
197
|
const { entries, warnings: readWarnings } = await readLegacyCallRecords(filePath);
|
|
191
198
|
warnings.push(...readWarnings);
|
|
192
199
|
const stateDatabaseEnv = resolveVoiceCallStateDatabaseEnv(params);
|
|
193
|
-
|
|
194
|
-
if (schemaMigrations.length > 0) {
|
|
200
|
+
if (detectOpenClawStateDatabaseSchemaMigrations({ env: stateDatabaseEnv }).length > 0) {
|
|
195
201
|
const repaired = repairOpenClawStateDatabaseSchema({ env: stateDatabaseEnv });
|
|
196
202
|
warnings.push(...repaired.warnings);
|
|
197
203
|
if (repaired.warnings.length > 0) return {
|
|
198
204
|
changes,
|
|
199
205
|
warnings
|
|
200
206
|
};
|
|
201
|
-
changes.push(...
|
|
207
|
+
changes.push(...repaired.changes.map((change) => change.replace(/^Migrated shared state /, "Migrated Voice Call SQLite ").replaceAll("→", "->")));
|
|
202
208
|
}
|
|
203
209
|
if (entries.length === 0) return {
|
|
204
210
|
changes,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { fetchWithSsrFGuard } from "./runtime-api.js";
|
|
2
2
|
import "./api.js";
|
|
3
|
-
import { n as normalizeProxyIp, o as getHeader } from "./runtime-entry-
|
|
3
|
+
import { n as normalizeProxyIp, o as getHeader } from "./runtime-entry-D8Mkwy8G.js";
|
|
4
4
|
import { n as markWebhookReplay, t as createWebhookReplayCache } from "./webhook-replay-aaP79dI8.js";
|
|
5
5
|
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
6
6
|
import { isLoopbackHost } from "openclaw/plugin-sdk/gateway-runtime";
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { definePluginEntry, sleep } from "./runtime-api.js";
|
|
2
2
|
import "./api.js";
|
|
3
|
-
import { c as cleanupTailscaleExposureRoute, h as resolveUserPath, l as getTailscaleSelfInfo, s as resolveWebhookExposureStatus, t as createVoiceCallRuntime, u as setupTailscaleExposureRoute, v as VoiceCallConfigSchema, x as validateProviderConfig, y as resolveVoiceCallConfig } from "./runtime-entry-
|
|
4
|
-
import {
|
|
3
|
+
import { c as cleanupTailscaleExposureRoute, h as resolveUserPath, l as getTailscaleSelfInfo, s as resolveWebhookExposureStatus, t as createVoiceCallRuntime, u as setupTailscaleExposureRoute, v as VoiceCallConfigSchema, x as validateProviderConfig, y as resolveVoiceCallConfig } from "./runtime-entry-D8Mkwy8G.js";
|
|
4
|
+
import { g as setVoiceCallStateRuntime, t as resolveDefaultVoiceCallStoreDir, u as getCallHistoryFromStore } from "./store-path-nYL-yM0S.js";
|
|
5
5
|
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
6
6
|
import { ErrorCodes, callGatewayFromCli, errorShape } from "openclaw/plugin-sdk/gateway-runtime";
|
|
7
7
|
import { MAX_TCP_PORT, MAX_TIMER_TIMEOUT_MS, addTimerTimeoutGraceMs, clampTimerTimeoutMs, parseStrictNonNegativeInteger, resolveTimerTimeoutMs, timestampMsToIsoString } from "openclaw/plugin-sdk/number-runtime";
|
|
@@ -10,16 +10,14 @@ import { asOptionalRecord, isRecord, normalizeOptionalLowercaseString, normalize
|
|
|
10
10
|
import { jsonResult } from "openclaw/plugin-sdk/tool-results";
|
|
11
11
|
import { Type } from "typebox";
|
|
12
12
|
import fs from "node:fs";
|
|
13
|
-
import os from "node:os";
|
|
14
|
-
import path from "node:path";
|
|
15
13
|
import { randomUUID } from "node:crypto";
|
|
14
|
+
import path from "node:path";
|
|
16
15
|
import { format } from "node:util";
|
|
17
16
|
//#region extensions/voice-call/src/cli.ts
|
|
18
17
|
const VOICE_CALL_GATEWAY_DEFAULT_TIMEOUT_MS = 5e3;
|
|
19
18
|
const VOICE_CALL_GATEWAY_OPERATION_TIMEOUT_MS = 3e4;
|
|
20
19
|
const VOICE_CALL_GATEWAY_TRANSCRIPT_BUFFER_MS = 1e4;
|
|
21
20
|
const VOICE_CALL_GATEWAY_POLL_INTERVAL_MS = 1e3;
|
|
22
|
-
const voiceCallCliDeps = { callGatewayFromCli };
|
|
23
21
|
function writeStdoutLine(...values) {
|
|
24
22
|
process.stdout.write(`${format(...values)}\n`);
|
|
25
23
|
}
|
|
@@ -41,7 +39,7 @@ async function callVoiceCallGateway(method, params, opts) {
|
|
|
41
39
|
const timeoutMs = typeof opts?.timeoutMs === "number" && Number.isFinite(opts.timeoutMs) ? Math.max(1, Math.ceil(opts.timeoutMs)) : VOICE_CALL_GATEWAY_DEFAULT_TIMEOUT_MS;
|
|
42
40
|
return {
|
|
43
41
|
ok: true,
|
|
44
|
-
payload: await
|
|
42
|
+
payload: await callGatewayFromCli(method, {
|
|
45
43
|
json: true,
|
|
46
44
|
timeout: String(timeoutMs)
|
|
47
45
|
}, params, { progress: false })
|
|
@@ -105,15 +103,7 @@ function resolveMode(input) {
|
|
|
105
103
|
return "funnel";
|
|
106
104
|
}
|
|
107
105
|
function resolveDefaultStorePath(config) {
|
|
108
|
-
const
|
|
109
|
-
const existing = [resolvedPreferred].find((dir) => {
|
|
110
|
-
try {
|
|
111
|
-
return fs.existsSync(path.join(dir, "calls.jsonl")) || fs.existsSync(dir);
|
|
112
|
-
} catch {
|
|
113
|
-
return false;
|
|
114
|
-
}
|
|
115
|
-
}) ?? resolvedPreferred;
|
|
116
|
-
const base = config.store?.trim() ? resolveUserPath(config.store) : existing;
|
|
106
|
+
const base = config.store?.trim() ? resolveUserPath(config.store) : resolveDefaultVoiceCallStoreDir();
|
|
117
107
|
return path.join(base, "calls.jsonl");
|
|
118
108
|
}
|
|
119
109
|
function percentile(values, p) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { g as escapeXml, o as getHeader } from "./runtime-entry-
|
|
2
|
-
import { a as reconstructWebhookUrl, o as verifyPlivoWebhook, t as guardedJsonApiRequest } from "./guarded-json-api-
|
|
1
|
+
import { g as escapeXml, o as getHeader } from "./runtime-entry-D8Mkwy8G.js";
|
|
2
|
+
import { a as reconstructWebhookUrl, o as verifyPlivoWebhook, t as guardedJsonApiRequest } from "./guarded-json-api-Dm4NvtGo.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,7 +1,7 @@
|
|
|
1
1
|
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
2
2
|
import { isFutureDateTimestampMs, resolveExpiresAtMsFromDurationMs } from "openclaw/plugin-sdk/number-runtime";
|
|
3
3
|
import { createSubsystemLogger } from "openclaw/plugin-sdk/runtime-env";
|
|
4
|
-
import { REALTIME_VOICE_AGENT_CONSULT_TOOL_NAME, buildRealtimeVoiceAgentConsultWorkingResponse,
|
|
4
|
+
import { REALTIME_VOICE_AGENT_CONSULT_TOOL_NAME, buildRealtimeVoiceAgentConsultWorkingResponse, calculateMulawRms, createRealtimeVoiceSessionHarness, createSpeechThresholdGate, readRealtimeVoiceConsultQuestion, readSpeakableRealtimeVoiceToolResult } from "openclaw/plugin-sdk/realtime-voice";
|
|
5
5
|
import { normalizeWebhookPath } from "openclaw/plugin-sdk/webhook-ingress";
|
|
6
6
|
import { randomUUID } from "node:crypto";
|
|
7
7
|
import { sliceUtf16Safe, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
@@ -11,13 +11,7 @@ import WebSocket$1, { WebSocketServer } from "ws";
|
|
|
11
11
|
const TELEPHONY_SAMPLE_RATE = 8e3;
|
|
12
12
|
const TELEPHONY_CHUNK_BYTES = 160;
|
|
13
13
|
const TELEPHONY_CHUNK_MS = 20;
|
|
14
|
-
const DEFAULT_SPEECH_RMS_THRESHOLD = .035;
|
|
15
|
-
const DEFAULT_REQUIRED_LOUD_CHUNKS = 4;
|
|
16
|
-
const DEFAULT_REQUIRED_QUIET_CHUNKS = 12;
|
|
17
14
|
const DEFAULT_MAX_QUEUED_AUDIO_BYTES = TELEPHONY_SAMPLE_RATE * 120;
|
|
18
|
-
const PCM16_MAX_AMPLITUDE = 32768;
|
|
19
|
-
const MULAW_LINEAR_SAMPLES = /* @__PURE__ */ new Int16Array(256);
|
|
20
|
-
for (let i = 0; i < MULAW_LINEAR_SAMPLES.length; i += 1) MULAW_LINEAR_SAMPLES[i] = decodeMulawSample(i);
|
|
21
15
|
/** Paces outgoing mulaw audio frames at telephony cadence. */
|
|
22
16
|
var RealtimeAudioPacer = class {
|
|
23
17
|
constructor(params) {
|
|
@@ -112,52 +106,6 @@ var RealtimeAudioPacer = class {
|
|
|
112
106
|
if (this.queue.length > 0) this.timer = setTimeout(() => this.pump(), delayMs);
|
|
113
107
|
}
|
|
114
108
|
};
|
|
115
|
-
/** Calculate normalized RMS from mulaw bytes. */
|
|
116
|
-
function calculateMulawRms(muLaw) {
|
|
117
|
-
if (muLaw.length === 0) return 0;
|
|
118
|
-
let sum = 0;
|
|
119
|
-
for (const sample of muLaw) {
|
|
120
|
-
const normalized = (MULAW_LINEAR_SAMPLES[sample] ?? 0) / PCM16_MAX_AMPLITUDE;
|
|
121
|
-
sum += normalized * normalized;
|
|
122
|
-
}
|
|
123
|
-
return Math.sqrt(sum / muLaw.length);
|
|
124
|
-
}
|
|
125
|
-
/** Detect likely speech start from consecutive loud mulaw chunks. */
|
|
126
|
-
var RealtimeMulawSpeechStartDetector = class {
|
|
127
|
-
constructor(params = {}) {
|
|
128
|
-
this.params = params;
|
|
129
|
-
this.loudChunks = 0;
|
|
130
|
-
this.quietChunks = DEFAULT_REQUIRED_QUIET_CHUNKS;
|
|
131
|
-
this.speaking = false;
|
|
132
|
-
}
|
|
133
|
-
/** Accept one mulaw chunk and return true only on transition into speaking. */
|
|
134
|
-
accept(muLaw) {
|
|
135
|
-
if (calculateMulawRms(muLaw) >= (this.params.rmsThreshold ?? DEFAULT_SPEECH_RMS_THRESHOLD)) {
|
|
136
|
-
this.quietChunks = 0;
|
|
137
|
-
this.loudChunks += 1;
|
|
138
|
-
const requiredLoudChunks = this.params.requiredLoudChunks ?? DEFAULT_REQUIRED_LOUD_CHUNKS;
|
|
139
|
-
if (!this.speaking && this.loudChunks >= requiredLoudChunks) {
|
|
140
|
-
this.speaking = true;
|
|
141
|
-
return true;
|
|
142
|
-
}
|
|
143
|
-
return false;
|
|
144
|
-
}
|
|
145
|
-
this.loudChunks = 0;
|
|
146
|
-
this.quietChunks += 1;
|
|
147
|
-
const requiredQuietChunks = this.params.requiredQuietChunks ?? DEFAULT_REQUIRED_QUIET_CHUNKS;
|
|
148
|
-
if (this.quietChunks >= requiredQuietChunks) this.speaking = false;
|
|
149
|
-
return false;
|
|
150
|
-
}
|
|
151
|
-
};
|
|
152
|
-
/** Decode one G.711 mulaw byte to a linear PCM sample. */
|
|
153
|
-
function decodeMulawSample(value) {
|
|
154
|
-
const muLaw = ~value & 255;
|
|
155
|
-
const sign = muLaw & 128;
|
|
156
|
-
const exponent = muLaw >> 4 & 7;
|
|
157
|
-
let sample = ((muLaw & 15) << 3) + 132 << exponent;
|
|
158
|
-
sample -= 132;
|
|
159
|
-
return sign ? -sample : sample;
|
|
160
|
-
}
|
|
161
109
|
//#endregion
|
|
162
110
|
//#region extensions/voice-call/src/webhook/stream-frame-adapter.ts
|
|
163
111
|
/** Parse numeric timestamps sent as numbers or integer strings. */
|
|
@@ -475,7 +423,6 @@ var RealtimeCallHandler = class {
|
|
|
475
423
|
this.partialUserTranscriptUpdatedAtByCallId = /* @__PURE__ */ new Map();
|
|
476
424
|
this.recentFinalUserTranscriptsByCallId = /* @__PURE__ */ new Map();
|
|
477
425
|
this.recentFinalUserTranscriptTimersByCallId = /* @__PURE__ */ new Map();
|
|
478
|
-
this.forcedConsultCoordinatorsByCallId = /* @__PURE__ */ new Map();
|
|
479
426
|
this.forcedConsultsByCallId = /* @__PURE__ */ new Map();
|
|
480
427
|
this.nativeConsultsInFlightByCallId = /* @__PURE__ */ new Map();
|
|
481
428
|
this.publicOrigin = null;
|
|
@@ -661,56 +608,51 @@ var RealtimeCallHandler = class {
|
|
|
661
608
|
}
|
|
662
609
|
const { callId, instructions, initialGreetingInstructions } = registration;
|
|
663
610
|
const callRecord = this.manager.getCallByProviderCallId(callSid);
|
|
664
|
-
const
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
callId,
|
|
697
|
-
providerCallId: callSid,
|
|
698
|
-
reason
|
|
699
|
-
} }));
|
|
700
|
-
};
|
|
611
|
+
const harness = createRealtimeVoiceSessionHarness({
|
|
612
|
+
talk: {
|
|
613
|
+
sessionId: `voice-call:${callId}:realtime`,
|
|
614
|
+
mode: "realtime",
|
|
615
|
+
transport: "gateway-relay",
|
|
616
|
+
brain: "agent-consult",
|
|
617
|
+
provider: this.realtimeProvider.id
|
|
618
|
+
},
|
|
619
|
+
talkPayloads: {
|
|
620
|
+
turnStarted: () => ({
|
|
621
|
+
callId,
|
|
622
|
+
providerCallId: callSid
|
|
623
|
+
}),
|
|
624
|
+
turnEnded: (reason) => ({
|
|
625
|
+
callId,
|
|
626
|
+
providerCallId: callSid,
|
|
627
|
+
reason
|
|
628
|
+
}),
|
|
629
|
+
inputAudioDelta: (audio) => ({ byteLength: audio.byteLength }),
|
|
630
|
+
outputAudioStarted: () => ({
|
|
631
|
+
callId,
|
|
632
|
+
providerCallId: callSid
|
|
633
|
+
}),
|
|
634
|
+
outputAudioDelta: (audio) => ({ byteLength: audio.byteLength }),
|
|
635
|
+
outputAudioDone: (reason) => ({
|
|
636
|
+
callId,
|
|
637
|
+
providerCallId: callSid,
|
|
638
|
+
reason
|
|
639
|
+
})
|
|
640
|
+
},
|
|
641
|
+
onTalkEvent: (event) => appendRecentTalkEventMetadata(callRecord, event)
|
|
642
|
+
});
|
|
701
643
|
const providerHandlesInputAudioBargeIn = this.realtimeProvider.capabilities?.handlesInputAudioBargeIn === true;
|
|
702
644
|
const cancelOutputAudioForBargeIn = (source, interruptProvider, clearedAudioBytes = 0) => {
|
|
703
|
-
const outputAudioActive = talk.outputAudioActive;
|
|
645
|
+
const outputAudioActive = harness.talk.outputAudioActive;
|
|
704
646
|
const pendingTelephonyAudio = audioPacer.hasPendingAudio();
|
|
705
647
|
if (source === "provider" && !outputAudioActive && !pendingTelephonyAudio && clearedAudioBytes === 0) return;
|
|
706
|
-
const interruptedTurnId = talk.activeTurnId;
|
|
648
|
+
const interruptedTurnId = harness.talk.activeTurnId;
|
|
707
649
|
interruptProvider?.(outputAudioActive || pendingTelephonyAudio);
|
|
708
650
|
const clearedBytes = clearedAudioBytes + (source === "local" || pendingTelephonyAudio ? audioPacer.clearAudio() : 0);
|
|
709
651
|
console.log(`[voice-call] realtime outbound audio cleared by ${source} barge-in callId=${callId} providerCallId=${callSid} queuedBytes=${clearedBytes}`);
|
|
710
652
|
if (!outputAudioActive || !interruptedTurnId) return;
|
|
711
653
|
const reason = `${source}-barge-in`;
|
|
712
|
-
finishOutputAudio(reason);
|
|
713
|
-
|
|
654
|
+
harness.finishOutputAudio(reason);
|
|
655
|
+
harness.talk.cancelTurn({
|
|
714
656
|
turnId: interruptedTurnId,
|
|
715
657
|
payload: {
|
|
716
658
|
callId,
|
|
@@ -718,9 +660,8 @@ var RealtimeCallHandler = class {
|
|
|
718
660
|
reason
|
|
719
661
|
}
|
|
720
662
|
});
|
|
721
|
-
if (cancelled.ok) rememberTalkEvent(cancelled.event);
|
|
722
663
|
};
|
|
723
|
-
|
|
664
|
+
harness.emit({
|
|
724
665
|
type: "session.started",
|
|
725
666
|
payload: {
|
|
726
667
|
callId,
|
|
@@ -762,9 +703,13 @@ var RealtimeCallHandler = class {
|
|
|
762
703
|
if (ws.readyState === WebSocket$1.OPEN) ws.close(1013, "Backpressure: paced audio queue exceeded");
|
|
763
704
|
}
|
|
764
705
|
});
|
|
765
|
-
const speechDetector =
|
|
706
|
+
const speechDetector = createSpeechThresholdGate({
|
|
707
|
+
rmsThreshold: .035,
|
|
708
|
+
speechFrames: BARGE_IN_REQUIRED_LOUD_CHUNKS,
|
|
709
|
+
silenceFrames: 12
|
|
710
|
+
});
|
|
766
711
|
const interruptResponseOnInputAudio = typeof this.providerConfig.interruptResponseOnInputAudio === "boolean" ? this.providerConfig.interruptResponseOnInputAudio : void 0;
|
|
767
|
-
const session =
|
|
712
|
+
const session = harness.createBridge({
|
|
768
713
|
provider: this.realtimeProvider,
|
|
769
714
|
cfg: this.coreConfig,
|
|
770
715
|
providerConfig: this.providerConfig,
|
|
@@ -776,46 +721,38 @@ var RealtimeCallHandler = class {
|
|
|
776
721
|
audioSink: {
|
|
777
722
|
isOpen: () => ws.readyState === WebSocket$1.OPEN,
|
|
778
723
|
sendAudio: (muLaw) => {
|
|
779
|
-
|
|
780
|
-
rememberTalkEvent(talk.startOutputAudio({
|
|
781
|
-
turnId,
|
|
782
|
-
payload: {
|
|
783
|
-
callId,
|
|
784
|
-
providerCallId: callSid
|
|
785
|
-
}
|
|
786
|
-
}).event);
|
|
787
|
-
emitTalkEvent({
|
|
788
|
-
type: "output.audio.delta",
|
|
789
|
-
turnId,
|
|
790
|
-
payload: { byteLength: muLaw.length }
|
|
791
|
-
});
|
|
724
|
+
harness.recordOutputAudio(muLaw);
|
|
792
725
|
audioPacer.sendAudio(muLaw);
|
|
793
726
|
},
|
|
794
727
|
clearAudio: (reason) => {
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
728
|
+
harness.flushOutput(() => {
|
|
729
|
+
const clearedBytes = audioPacer.clearAudio();
|
|
730
|
+
if (reason === "barge-in") {
|
|
731
|
+
cancelOutputAudioForBargeIn("provider", void 0, clearedBytes);
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
console.log(`[voice-call] realtime outbound audio clear requested callId=${callId} providerCallId=${callSid} queuedBytes=${clearedBytes}`);
|
|
735
|
+
harness.finishOutputAudio("clear");
|
|
736
|
+
});
|
|
802
737
|
},
|
|
803
738
|
sendMark: (markName) => {
|
|
804
739
|
audioPacer.sendMark(markName);
|
|
805
740
|
}
|
|
806
741
|
},
|
|
807
742
|
onTranscript: (role, text, isFinal) => {
|
|
808
|
-
const turnId =
|
|
809
|
-
|
|
810
|
-
|
|
743
|
+
const turnId = harness.ensureTurn();
|
|
744
|
+
const eventType = role === "assistant" ? isFinal ? "output.text.done" : "output.text.delta" : isFinal ? "transcript.done" : "transcript.delta";
|
|
745
|
+
const payload = role === "assistant" ? { text } : {
|
|
746
|
+
role,
|
|
747
|
+
text
|
|
748
|
+
};
|
|
749
|
+
harness.emit({
|
|
750
|
+
type: eventType,
|
|
811
751
|
turnId,
|
|
812
|
-
payload
|
|
813
|
-
role,
|
|
814
|
-
text
|
|
815
|
-
},
|
|
752
|
+
payload,
|
|
816
753
|
final: isFinal
|
|
817
754
|
});
|
|
818
|
-
if (role === "user" && isFinal)
|
|
755
|
+
if (role === "user" && isFinal) harness.emit({
|
|
819
756
|
type: "input.audio.committed",
|
|
820
757
|
turnId,
|
|
821
758
|
payload: {
|
|
@@ -851,6 +788,7 @@ var RealtimeCallHandler = class {
|
|
|
851
788
|
};
|
|
852
789
|
this.manager.processEvent(event);
|
|
853
790
|
this.scheduleForcedAgentConsult({
|
|
791
|
+
harness,
|
|
854
792
|
session,
|
|
855
793
|
callId,
|
|
856
794
|
callSid,
|
|
@@ -872,8 +810,8 @@ var RealtimeCallHandler = class {
|
|
|
872
810
|
});
|
|
873
811
|
},
|
|
874
812
|
onToolCall: (toolEvent, sessionLocal) => {
|
|
875
|
-
const turnId =
|
|
876
|
-
|
|
813
|
+
const turnId = harness.ensureTurn();
|
|
814
|
+
harness.emit({
|
|
877
815
|
type: "tool.call",
|
|
878
816
|
turnId,
|
|
879
817
|
itemId: toolEvent.itemId,
|
|
@@ -884,17 +822,17 @@ var RealtimeCallHandler = class {
|
|
|
884
822
|
}
|
|
885
823
|
});
|
|
886
824
|
console.log(`[voice-call] realtime tool call received callId=${callId} providerCallId=${callSid} tool=${toolEvent.name}`);
|
|
887
|
-
return this.executeToolCall(sessionLocal, callId, toolEvent.callId || toolEvent.itemId, toolEvent.name, toolEvent.args, turnId,
|
|
825
|
+
return this.executeToolCall(sessionLocal, callId, toolEvent.callId || toolEvent.itemId, toolEvent.name, toolEvent.args, turnId, harness);
|
|
888
826
|
},
|
|
889
827
|
onEvent: (event) => {
|
|
890
828
|
if (event.type === "input_audio_buffer.speech_started") {
|
|
891
|
-
|
|
829
|
+
harness.ensureTurn();
|
|
892
830
|
return;
|
|
893
831
|
}
|
|
894
832
|
if (event.type === "input_audio_buffer.speech_stopped") {
|
|
895
|
-
const turnId = talk.activeTurnId;
|
|
833
|
+
const turnId = harness.talk.activeTurnId;
|
|
896
834
|
if (!turnId) return;
|
|
897
|
-
|
|
835
|
+
harness.emit({
|
|
898
836
|
type: "input.audio.committed",
|
|
899
837
|
turnId,
|
|
900
838
|
payload: {
|
|
@@ -907,18 +845,18 @@ var RealtimeCallHandler = class {
|
|
|
907
845
|
return;
|
|
908
846
|
}
|
|
909
847
|
if (event.type === "response.done") {
|
|
910
|
-
finishOutputAudio("response.done");
|
|
911
|
-
|
|
848
|
+
harness.finishOutputAudio("response.done");
|
|
849
|
+
harness.endTurn("response.done");
|
|
912
850
|
return;
|
|
913
851
|
}
|
|
914
|
-
if (event.type === "error")
|
|
852
|
+
if (event.type === "error") harness.emit({
|
|
915
853
|
type: "session.error",
|
|
916
854
|
payload: { message: event.detail ?? "Realtime provider error" },
|
|
917
855
|
final: true
|
|
918
856
|
});
|
|
919
857
|
},
|
|
920
858
|
onReady: () => {
|
|
921
|
-
|
|
859
|
+
harness.emit({
|
|
922
860
|
type: "session.ready",
|
|
923
861
|
payload: {
|
|
924
862
|
callId,
|
|
@@ -928,7 +866,7 @@ var RealtimeCallHandler = class {
|
|
|
928
866
|
},
|
|
929
867
|
onError: (error) => {
|
|
930
868
|
console.error("[voice-call] realtime voice error:", error.message);
|
|
931
|
-
|
|
869
|
+
harness.emit({
|
|
932
870
|
type: "session.error",
|
|
933
871
|
payload: { message: error.message },
|
|
934
872
|
final: true
|
|
@@ -940,8 +878,8 @@ var RealtimeCallHandler = class {
|
|
|
940
878
|
this.activeTelephonyClosersByCallId.delete(callId);
|
|
941
879
|
this.activeTelephonyClosersByCallId.delete(callSid);
|
|
942
880
|
this.clearUserTranscriptState(callId);
|
|
943
|
-
finishOutputAudio(reason);
|
|
944
|
-
|
|
881
|
+
harness.finishOutputAudio(reason);
|
|
882
|
+
harness.emit({
|
|
945
883
|
type: "session.closed",
|
|
946
884
|
payload: { reason },
|
|
947
885
|
final: true
|
|
@@ -971,17 +909,16 @@ var RealtimeCallHandler = class {
|
|
|
971
909
|
this.activeTelephonyClosersByCallId.set(callSid, closeTelephony);
|
|
972
910
|
const sendAudioToSession = session.sendAudio.bind(session);
|
|
973
911
|
session.sendAudio = (audio) => {
|
|
974
|
-
if (speechDetector.accept(
|
|
912
|
+
if (speechDetector.accept({
|
|
913
|
+
rms: calculateMulawRms(audio),
|
|
914
|
+
peak: 0
|
|
915
|
+
})) {
|
|
975
916
|
console.log(`[voice-call] realtime local speech detected callId=${callId} providerCallId=${callSid}`);
|
|
976
917
|
if (!providerHandlesInputAudioBargeIn) cancelOutputAudioForBargeIn("local", (audioPlaybackActive) => {
|
|
977
918
|
session.handleBargeIn({ audioPlaybackActive });
|
|
978
919
|
});
|
|
979
920
|
}
|
|
980
|
-
|
|
981
|
-
type: "input.audio.delta",
|
|
982
|
-
turnId: ensureTalkTurn(),
|
|
983
|
-
payload: { byteLength: audio.length }
|
|
984
|
-
});
|
|
921
|
+
harness.recordInputAudio(audio);
|
|
985
922
|
sendAudioToSession(audio);
|
|
986
923
|
};
|
|
987
924
|
const closeSession = session.close.bind(session);
|
|
@@ -997,7 +934,8 @@ var RealtimeCallHandler = class {
|
|
|
997
934
|
this.activeTelephonyClosersByCallId.delete(callId);
|
|
998
935
|
this.activeTelephonyClosersByCallId.delete(callSid);
|
|
999
936
|
this.clearUserTranscriptState(callId);
|
|
1000
|
-
this.
|
|
937
|
+
this.forcedConsultsByCallId.delete(callId);
|
|
938
|
+
harness.close();
|
|
1001
939
|
audioPacer.close();
|
|
1002
940
|
}
|
|
1003
941
|
};
|
|
@@ -1080,18 +1018,6 @@ var RealtimeCallHandler = class {
|
|
|
1080
1018
|
});
|
|
1081
1019
|
}
|
|
1082
1020
|
}
|
|
1083
|
-
clearForcedConsultState(callId) {
|
|
1084
|
-
this.forcedConsultCoordinatorsByCallId.get(callId)?.clear();
|
|
1085
|
-
this.forcedConsultCoordinatorsByCallId.delete(callId);
|
|
1086
|
-
this.forcedConsultsByCallId.delete(callId);
|
|
1087
|
-
}
|
|
1088
|
-
forcedConsultCoordinator(callId) {
|
|
1089
|
-
const existing = this.forcedConsultCoordinatorsByCallId.get(callId);
|
|
1090
|
-
if (existing) return existing;
|
|
1091
|
-
const created = createRealtimeVoiceForcedConsultCoordinator();
|
|
1092
|
-
this.forcedConsultCoordinatorsByCallId.set(callId, created);
|
|
1093
|
-
return created;
|
|
1094
|
-
}
|
|
1095
1021
|
closeTelephonyBridge(callIdOrSid, bridge, reason) {
|
|
1096
1022
|
const closer = this.activeTelephonyClosersByCallId.get(callIdOrSid);
|
|
1097
1023
|
if (closer) {
|
|
@@ -1108,7 +1034,7 @@ var RealtimeCallHandler = class {
|
|
|
1108
1034
|
if (!handler) return;
|
|
1109
1035
|
const existingForcedConsult = this.forcedConsultsByCallId.get(params.callId);
|
|
1110
1036
|
if (existingForcedConsult && !existingForcedConsult.completedAt) return;
|
|
1111
|
-
const coordinator =
|
|
1037
|
+
const coordinator = params.harness.forcedConsults;
|
|
1112
1038
|
if (coordinator.hasRecentNativeConsult(question, { allowUnknownQuestion: true })) return;
|
|
1113
1039
|
coordinator.clearPending();
|
|
1114
1040
|
const pending = coordinator.prepare(question);
|
|
@@ -1124,7 +1050,7 @@ var RealtimeCallHandler = class {
|
|
|
1124
1050
|
});
|
|
1125
1051
|
}
|
|
1126
1052
|
async runForcedAgentConsult(params) {
|
|
1127
|
-
const coordinator =
|
|
1053
|
+
const coordinator = params.harness.forcedConsults;
|
|
1128
1054
|
coordinator.markStarted(params.handle);
|
|
1129
1055
|
const startedAt = Date.now();
|
|
1130
1056
|
logger.debug(`[voice-call] realtime forced agent consult reason=${FORCED_CONSULT_REASON} consultPolicy=always callId=${params.callId} providerCallId=${params.callSid} chars=${params.handle.question.length}`);
|
|
@@ -1216,14 +1142,14 @@ var RealtimeCallHandler = class {
|
|
|
1216
1142
|
reason
|
|
1217
1143
|
});
|
|
1218
1144
|
}
|
|
1219
|
-
async executeToolCall(bridge, callId, bridgeCallId, name, args, turnId,
|
|
1145
|
+
async executeToolCall(bridge, callId, bridgeCallId, name, args, turnId, harness) {
|
|
1220
1146
|
const handler = this.toolHandlers.get(name);
|
|
1221
1147
|
const startedAt = Date.now();
|
|
1222
1148
|
const hasResultError = (result) => {
|
|
1223
1149
|
return Boolean(result && typeof result === "object" && !Array.isArray(result) && "error" in result);
|
|
1224
1150
|
};
|
|
1225
1151
|
const emitFinalToolEvent = (result) => {
|
|
1226
|
-
|
|
1152
|
+
harness.emit({
|
|
1227
1153
|
type: hasResultError(result) ? "tool.error" : "tool.result",
|
|
1228
1154
|
turnId,
|
|
1229
1155
|
callId: bridgeCallId,
|
|
@@ -1241,7 +1167,7 @@ var RealtimeCallHandler = class {
|
|
|
1241
1167
|
const submitWorkingResponse = async () => {
|
|
1242
1168
|
if (handler && name === REALTIME_VOICE_AGENT_CONSULT_TOOL_NAME && bridge.bridge.supportsToolResultContinuation && !this.config.fastContext.enabled) {
|
|
1243
1169
|
await bridge.submitToolResult(bridgeCallId, buildRealtimeVoiceAgentConsultWorkingResponse("caller"), { willContinue: true });
|
|
1244
|
-
|
|
1170
|
+
harness.emit({
|
|
1245
1171
|
type: "tool.progress",
|
|
1246
1172
|
turnId,
|
|
1247
1173
|
callId: bridgeCallId,
|
|
@@ -1253,7 +1179,7 @@ var RealtimeCallHandler = class {
|
|
|
1253
1179
|
}
|
|
1254
1180
|
};
|
|
1255
1181
|
if (name === REALTIME_VOICE_AGENT_CONSULT_TOOL_NAME) {
|
|
1256
|
-
const coordinator =
|
|
1182
|
+
const coordinator = harness.forcedConsults;
|
|
1257
1183
|
const forcedMatch = coordinator.recordNativeConsult(args, bridgeCallId);
|
|
1258
1184
|
if (forcedMatch.kind === "none") {
|
|
1259
1185
|
const pending = coordinator.consumePending();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { b as resolveVoiceCallSessionKey, m as resolveCallAgentId, p as resolveVoiceResponseModel } from "./runtime-entry-
|
|
1
|
+
import { b as resolveVoiceCallSessionKey, m as resolveCallAgentId, p as resolveVoiceResponseModel } from "./runtime-entry-D8Mkwy8G.js";
|
|
2
2
|
import { isRecord, normalizeLowercaseStringOrEmpty, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
3
3
|
import crypto from "node:crypto";
|
|
4
4
|
import { ModelSelectionLockedError, applyModelOverrideToSessionEntry, resolvePersistedSessionRuntimeId } from "openclaw/plugin-sdk/model-session-runtime";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { TtsConfigSchema, isBlockedHostnameOrIp, isRequestBodyLimitError, readRequestBodyWithLimit, requestBodyErrorToText } from "./runtime-api.js";
|
|
2
2
|
import "./api.js";
|
|
3
3
|
import { t as TWILIO_REGIONS } from "./twilio-region-4fkgz3UG.js";
|
|
4
|
-
import {
|
|
4
|
+
import { _ as TerminalStates, d as loadActiveCallsFromStore, g as setVoiceCallStateRuntime, l as findCallMatchesInStore, p as persistCallRecord, t as resolveDefaultVoiceCallStoreDir, u as getCallHistoryFromStore } from "./store-path-nYL-yM0S.js";
|
|
5
5
|
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
6
6
|
import { isLoopbackHost } from "openclaw/plugin-sdk/gateway-runtime";
|
|
7
7
|
import { MAX_TIMER_TIMEOUT_MS, asDateTimestampMs, resolveExpiresAtMsFromDurationMs, resolveTimerTimeoutMs } from "openclaw/plugin-sdk/number-runtime";
|
|
@@ -13,16 +13,16 @@ import { REALTIME_VOICE_AGENT_CONSULT_TOOL_NAME, REALTIME_VOICE_AGENT_CONSULT_TO
|
|
|
13
13
|
import { mergeDeep } from "openclaw/plugin-sdk/plugin-config-runtime";
|
|
14
14
|
import { buildSecretInputSchema, hasConfiguredSecretInput, normalizeResolvedSecretInputString } from "openclaw/plugin-sdk/secret-input";
|
|
15
15
|
import { canonicalizeMainSessionAlias } from "openclaw/plugin-sdk/session-store-runtime";
|
|
16
|
+
import { resolveSpeechProviderApiKey } from "openclaw/plugin-sdk/speech-core";
|
|
16
17
|
import { WEBHOOK_BODY_READ_DEFAULTS, createWebhookInFlightLimiter, normalizeWebhookPath } from "openclaw/plugin-sdk/webhook-ingress";
|
|
17
18
|
import { z } from "zod";
|
|
18
19
|
import fs from "node:fs";
|
|
19
|
-
import os from "node:os";
|
|
20
|
-
import path from "node:path";
|
|
21
20
|
import crypto from "node:crypto";
|
|
22
21
|
import { redactIdentifier } from "openclaw/plugin-sdk/logging-core";
|
|
22
|
+
import path from "node:path";
|
|
23
|
+
import os from "node:os";
|
|
23
24
|
import { root } from "openclaw/plugin-sdk/security-runtime";
|
|
24
25
|
import { sliceUtf16Safe, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
25
|
-
import { parseTtsDirectives } from "openclaw/plugin-sdk/speech";
|
|
26
26
|
import { spawn } from "node:child_process";
|
|
27
27
|
import { runCommandWithTimeout } from "openclaw/plugin-sdk/process-runtime";
|
|
28
28
|
import http from "node:http";
|
|
@@ -586,28 +586,28 @@ function resolveVoiceCallConfig(config) {
|
|
|
586
586
|
const resolved = normalizeVoiceCallConfig(config);
|
|
587
587
|
if (resolved.provider === "telnyx") {
|
|
588
588
|
resolved.telnyx = resolved.telnyx ?? {};
|
|
589
|
-
resolved.telnyx.apiKey = resolved.telnyx.apiKey ?? process.env.TELNYX_API_KEY;
|
|
590
|
-
resolved.telnyx.connectionId = resolved.telnyx.connectionId ?? process.env.TELNYX_CONNECTION_ID;
|
|
591
|
-
resolved.telnyx.publicKey = resolved.telnyx.publicKey ?? process.env.TELNYX_PUBLIC_KEY;
|
|
589
|
+
resolved.telnyx.apiKey = resolved.telnyx.apiKey ?? resolveSpeechProviderApiKey(process.env.TELNYX_API_KEY);
|
|
590
|
+
resolved.telnyx.connectionId = resolved.telnyx.connectionId ?? resolveSpeechProviderApiKey(process.env.TELNYX_CONNECTION_ID);
|
|
591
|
+
resolved.telnyx.publicKey = resolved.telnyx.publicKey ?? resolveSpeechProviderApiKey(process.env.TELNYX_PUBLIC_KEY);
|
|
592
592
|
}
|
|
593
593
|
if (resolved.provider === "twilio") {
|
|
594
|
-
resolved.fromNumber = resolved.fromNumber ?? process.env.TWILIO_FROM_NUMBER;
|
|
594
|
+
resolved.fromNumber = resolved.fromNumber ?? resolveSpeechProviderApiKey(process.env.TWILIO_FROM_NUMBER);
|
|
595
595
|
resolved.twilio = resolved.twilio ?? {};
|
|
596
|
-
resolved.twilio.accountSid = resolved.twilio.accountSid ?? process.env.TWILIO_ACCOUNT_SID;
|
|
597
|
-
resolved.twilio.authToken = resolved.twilio.authToken ?? process.env.TWILIO_AUTH_TOKEN;
|
|
596
|
+
resolved.twilio.accountSid = resolved.twilio.accountSid ?? resolveSpeechProviderApiKey(process.env.TWILIO_ACCOUNT_SID);
|
|
597
|
+
resolved.twilio.authToken = resolved.twilio.authToken ?? resolveSpeechProviderApiKey(process.env.TWILIO_AUTH_TOKEN);
|
|
598
598
|
}
|
|
599
599
|
if (resolved.provider === "plivo") {
|
|
600
600
|
resolved.plivo = resolved.plivo ?? {};
|
|
601
|
-
resolved.plivo.authId = resolved.plivo.authId ?? process.env.PLIVO_AUTH_ID;
|
|
602
|
-
resolved.plivo.authToken = resolved.plivo.authToken ?? process.env.PLIVO_AUTH_TOKEN;
|
|
601
|
+
resolved.plivo.authId = resolved.plivo.authId ?? resolveSpeechProviderApiKey(process.env.PLIVO_AUTH_ID);
|
|
602
|
+
resolved.plivo.authToken = resolved.plivo.authToken ?? resolveSpeechProviderApiKey(process.env.PLIVO_AUTH_TOKEN);
|
|
603
603
|
}
|
|
604
604
|
resolved.tunnel = resolved.tunnel ?? {
|
|
605
605
|
provider: "none",
|
|
606
606
|
allowNgrokFreeTierLoopbackBypass: false
|
|
607
607
|
};
|
|
608
608
|
resolved.tunnel.allowNgrokFreeTierLoopbackBypass = resolved.tunnel.allowNgrokFreeTierLoopbackBypass ?? false;
|
|
609
|
-
resolved.tunnel.ngrokAuthToken = resolved.tunnel.ngrokAuthToken ?? process.env.NGROK_AUTHTOKEN;
|
|
610
|
-
resolved.tunnel.ngrokDomain = resolved.tunnel.ngrokDomain ?? process.env.NGROK_DOMAIN;
|
|
609
|
+
resolved.tunnel.ngrokAuthToken = resolved.tunnel.ngrokAuthToken ?? resolveSpeechProviderApiKey(process.env.NGROK_AUTHTOKEN);
|
|
610
|
+
resolved.tunnel.ngrokDomain = resolved.tunnel.ngrokDomain ?? resolveSpeechProviderApiKey(process.env.NGROK_DOMAIN);
|
|
611
611
|
resolved.webhookSecurity = resolved.webhookSecurity ?? {
|
|
612
612
|
allowedHosts: [],
|
|
613
613
|
trustForwardingHeaders: false,
|
|
@@ -1552,14 +1552,7 @@ function resolveRestoredMaxDurationAnchor(call) {
|
|
|
1552
1552
|
function resolveDefaultStoreBase(config, storePath) {
|
|
1553
1553
|
const rawOverride = storePath?.trim() || config.store?.trim();
|
|
1554
1554
|
if (rawOverride) return resolveUserPath(rawOverride);
|
|
1555
|
-
|
|
1556
|
-
return [preferred].map((dir) => resolveUserPath(dir)).find((dir) => {
|
|
1557
|
-
try {
|
|
1558
|
-
return fs.existsSync(path.join(dir, "calls.jsonl")) || fs.existsSync(dir);
|
|
1559
|
-
} catch {
|
|
1560
|
-
return false;
|
|
1561
|
-
}
|
|
1562
|
-
}) ?? resolveUserPath(preferred);
|
|
1555
|
+
return resolveDefaultVoiceCallStoreDir();
|
|
1563
1556
|
}
|
|
1564
1557
|
/**
|
|
1565
1558
|
* Manages voice calls: state ownership and delegation to manager helper modules.
|
|
@@ -1919,26 +1912,26 @@ function chunkAudio(audio, chunkSize = 160) {
|
|
|
1919
1912
|
/** Default timeout for one telephony synthesis request. */
|
|
1920
1913
|
const TELEPHONY_DEFAULT_TTS_TIMEOUT_MS = 8e3;
|
|
1921
1914
|
/** Create a TTS provider that honors voice-call overrides and converts PCM to mulaw. */
|
|
1922
|
-
function createTelephonyTtsProvider(params) {
|
|
1915
|
+
async function createTelephonyTtsProvider(params) {
|
|
1923
1916
|
const { coreConfig, ttsOverride, runtime, logger } = params;
|
|
1924
|
-
const
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1917
|
+
const preparedConfig = await runtime.prepareTtsRequest({
|
|
1918
|
+
cfg: coreConfig,
|
|
1919
|
+
override: ttsOverride,
|
|
1920
|
+
text: ""
|
|
1921
|
+
});
|
|
1929
1922
|
return {
|
|
1930
|
-
synthesisTimeoutMs: resolveTimerTimeoutMs(
|
|
1923
|
+
synthesisTimeoutMs: resolveTimerTimeoutMs(preparedConfig.cfg.messages?.tts?.timeoutMs, TELEPHONY_DEFAULT_TTS_TIMEOUT_MS),
|
|
1931
1924
|
synthesizeForTelephony: async (text) => {
|
|
1932
|
-
const
|
|
1933
|
-
cfg:
|
|
1934
|
-
|
|
1935
|
-
preferredProviderId: activeProvider
|
|
1925
|
+
const prepared = await runtime.prepareTtsRequest({
|
|
1926
|
+
cfg: preparedConfig.cfg,
|
|
1927
|
+
text
|
|
1936
1928
|
});
|
|
1929
|
+
const directives = prepared.directives;
|
|
1937
1930
|
if (directives.warnings.length > 0) logger?.warn?.(`[voice-call] Ignored telephony TTS directive overrides (${directives.warnings.join("; ")})`);
|
|
1938
1931
|
const cleanText = directives.hasDirective ? directives.ttsText?.trim() || directives.cleanedText.trim() : text;
|
|
1939
1932
|
const result = await runtime.textToSpeechTelephony({
|
|
1940
1933
|
text: cleanText,
|
|
1941
|
-
cfg:
|
|
1934
|
+
cfg: prepared.cfg,
|
|
1942
1935
|
overrides: directives.overrides
|
|
1943
1936
|
});
|
|
1944
1937
|
if (!result.success || !result.audioBuffer || !result.sampleRate) throw new Error(result.error ?? "TTS conversion failed");
|
|
@@ -1950,94 +1943,6 @@ function createTelephonyTtsProvider(params) {
|
|
|
1950
1943
|
}
|
|
1951
1944
|
};
|
|
1952
1945
|
}
|
|
1953
|
-
/** Apply voice-call TTS overrides to core config without mutating the original object. */
|
|
1954
|
-
function applyTtsOverride(coreConfig, override) {
|
|
1955
|
-
if (!override) return coreConfig;
|
|
1956
|
-
const base = coreConfig.messages?.tts;
|
|
1957
|
-
const merged = mergeTtsConfig(base, override);
|
|
1958
|
-
if (!merged) return coreConfig;
|
|
1959
|
-
return {
|
|
1960
|
-
...coreConfig,
|
|
1961
|
-
messages: {
|
|
1962
|
-
...coreConfig.messages,
|
|
1963
|
-
tts: merged
|
|
1964
|
-
}
|
|
1965
|
-
};
|
|
1966
|
-
}
|
|
1967
|
-
/** Merge core and voice-call TTS config, keeping undefined override fields out. */
|
|
1968
|
-
function mergeTtsConfig(base, override) {
|
|
1969
|
-
if (!base && !override) return;
|
|
1970
|
-
if (!override) return base;
|
|
1971
|
-
if (!base) return override;
|
|
1972
|
-
return mergeDeep(base, override);
|
|
1973
|
-
}
|
|
1974
|
-
/** Resolve directive override policy for telephony synthesis. */
|
|
1975
|
-
function resolveTelephonyModelOverridePolicy(overrides) {
|
|
1976
|
-
if (!(overrides?.enabled ?? true)) return {
|
|
1977
|
-
enabled: false,
|
|
1978
|
-
allowText: false,
|
|
1979
|
-
allowProvider: false,
|
|
1980
|
-
allowVoice: false,
|
|
1981
|
-
allowModelId: false,
|
|
1982
|
-
allowVoiceSettings: false,
|
|
1983
|
-
allowNormalization: false,
|
|
1984
|
-
allowSeed: false
|
|
1985
|
-
};
|
|
1986
|
-
const allow = (value, defaultValue = true) => value ?? defaultValue;
|
|
1987
|
-
return {
|
|
1988
|
-
enabled: true,
|
|
1989
|
-
allowText: allow(overrides?.allowText),
|
|
1990
|
-
allowProvider: allow(overrides?.allowProvider, false),
|
|
1991
|
-
allowVoice: allow(overrides?.allowVoice),
|
|
1992
|
-
allowModelId: allow(overrides?.allowModelId),
|
|
1993
|
-
allowVoiceSettings: allow(overrides?.allowVoiceSettings),
|
|
1994
|
-
allowNormalization: allow(overrides?.allowNormalization),
|
|
1995
|
-
allowSeed: allow(overrides?.allowSeed)
|
|
1996
|
-
};
|
|
1997
|
-
}
|
|
1998
|
-
/** Read model override policy from TTS config when present. */
|
|
1999
|
-
function readTelephonyModelOverrides(ttsConfig) {
|
|
2000
|
-
const value = ttsConfig?.modelOverrides;
|
|
2001
|
-
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
2002
|
-
}
|
|
2003
|
-
/** Normalize provider ids for config lookup. */
|
|
2004
|
-
function normalizeProviderId(value) {
|
|
2005
|
-
return typeof value === "string" ? value.trim().toLowerCase() || void 0 : void 0;
|
|
2006
|
-
}
|
|
2007
|
-
/** Coerce provider config objects while rejecting arrays and primitives. */
|
|
2008
|
-
function asProviderConfig(value) {
|
|
2009
|
-
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
2010
|
-
}
|
|
2011
|
-
/** Collect named provider configs from canonical and legacy TTS config shapes. */
|
|
2012
|
-
function collectTelephonyProviderConfigs(ttsConfig) {
|
|
2013
|
-
if (!ttsConfig) return {};
|
|
2014
|
-
const entries = {};
|
|
2015
|
-
const rawProviders = ttsConfig.providers && typeof ttsConfig.providers === "object" && !Array.isArray(ttsConfig.providers) ? ttsConfig.providers : {};
|
|
2016
|
-
for (const [providerId, value] of Object.entries(rawProviders)) {
|
|
2017
|
-
const normalized = normalizeProviderId(providerId) ?? providerId;
|
|
2018
|
-
entries[normalized] = asProviderConfig(value);
|
|
2019
|
-
}
|
|
2020
|
-
const reservedKeys = /* @__PURE__ */ new Set([
|
|
2021
|
-
"auto",
|
|
2022
|
-
"enabled",
|
|
2023
|
-
"maxTextLength",
|
|
2024
|
-
"mode",
|
|
2025
|
-
"modelOverrides",
|
|
2026
|
-
"persona",
|
|
2027
|
-
"personas",
|
|
2028
|
-
"prefsPath",
|
|
2029
|
-
"provider",
|
|
2030
|
-
"providers",
|
|
2031
|
-
"summaryModel",
|
|
2032
|
-
"timeoutMs"
|
|
2033
|
-
]);
|
|
2034
|
-
for (const [key, value] of Object.entries(ttsConfig)) {
|
|
2035
|
-
if (reservedKeys.has(key) || typeof value !== "object" || value === null || Array.isArray(value)) continue;
|
|
2036
|
-
const normalized = normalizeProviderId(key) ?? key;
|
|
2037
|
-
entries[normalized] ??= asProviderConfig(value);
|
|
2038
|
-
}
|
|
2039
|
-
return entries;
|
|
2040
|
-
}
|
|
2041
1946
|
//#endregion
|
|
2042
1947
|
//#region extensions/voice-call/src/bounded-child-output.ts
|
|
2043
1948
|
const DEFAULT_MAX_OUTPUT_CHARS = 16384;
|
|
@@ -2165,6 +2070,30 @@ async function cleanupTailscaleExposure(config) {
|
|
|
2165
2070
|
const NGROK_LOG_BUFFER_MAX_CHARS = 16384;
|
|
2166
2071
|
const NGROK_ERROR_MARKER = "ERR_NGROK";
|
|
2167
2072
|
const TUNNEL_COMMAND_OUTPUT_MAX_BYTES = 16384;
|
|
2073
|
+
const NGROK_STOP_GRACE_MS = 2e3;
|
|
2074
|
+
const NGROK_FORCE_KILL_WAIT_MS = 1e3;
|
|
2075
|
+
async function terminateNgrokProcess(proc, isClosed) {
|
|
2076
|
+
if (isClosed()) return;
|
|
2077
|
+
await new Promise((resolve) => {
|
|
2078
|
+
let finished = false;
|
|
2079
|
+
let forceKillWaitTimer;
|
|
2080
|
+
const finish = () => {
|
|
2081
|
+
if (finished) return;
|
|
2082
|
+
finished = true;
|
|
2083
|
+
if (forceKillTimer) clearTimeout(forceKillTimer);
|
|
2084
|
+
if (forceKillWaitTimer) clearTimeout(forceKillWaitTimer);
|
|
2085
|
+
proc.off("close", finish);
|
|
2086
|
+
resolve();
|
|
2087
|
+
};
|
|
2088
|
+
proc.once("close", finish);
|
|
2089
|
+
const forceKillTimer = setTimeout(() => {
|
|
2090
|
+
forceKillWaitTimer = setTimeout(finish, NGROK_FORCE_KILL_WAIT_MS);
|
|
2091
|
+
if (!isClosed()) proc.kill("SIGKILL");
|
|
2092
|
+
}, NGROK_STOP_GRACE_MS);
|
|
2093
|
+
proc.kill("SIGTERM");
|
|
2094
|
+
if (isClosed()) finish();
|
|
2095
|
+
});
|
|
2096
|
+
}
|
|
2168
2097
|
function listenForChildStreamErrors(proc, onError) {
|
|
2169
2098
|
proc.stdout.on("error", (error) => onError("stdout", error));
|
|
2170
2099
|
proc.stderr.on("error", (error) => onError("stderr", error));
|
|
@@ -2200,23 +2129,25 @@ async function startNgrokTunnel(config) {
|
|
|
2200
2129
|
"pipe",
|
|
2201
2130
|
"pipe"
|
|
2202
2131
|
] });
|
|
2203
|
-
let
|
|
2204
|
-
let
|
|
2132
|
+
let startupSettled = false;
|
|
2133
|
+
let childClosed = false;
|
|
2205
2134
|
let publicUrl = null;
|
|
2206
2135
|
let outputBuffer = "";
|
|
2207
2136
|
let stderrTail = "";
|
|
2208
2137
|
const timeout = setTimeout(() => {
|
|
2209
|
-
if (!
|
|
2210
|
-
|
|
2211
|
-
proc.
|
|
2212
|
-
|
|
2138
|
+
if (!startupSettled) {
|
|
2139
|
+
startupSettled = true;
|
|
2140
|
+
terminateNgrokProcess(proc, () => childClosed).then(() => {
|
|
2141
|
+
reject(/* @__PURE__ */ new Error("ngrok startup timed out (30s)"));
|
|
2142
|
+
});
|
|
2213
2143
|
}
|
|
2214
2144
|
}, 3e4);
|
|
2145
|
+
timeout.unref();
|
|
2215
2146
|
const rejectIfPending = (message, kill = false) => {
|
|
2216
|
-
if (!
|
|
2217
|
-
|
|
2147
|
+
if (!startupSettled) {
|
|
2148
|
+
startupSettled = true;
|
|
2218
2149
|
clearTimeout(timeout);
|
|
2219
|
-
if (kill && !
|
|
2150
|
+
if (kill && !childClosed) proc.kill("SIGKILL");
|
|
2220
2151
|
reject(new Error(message));
|
|
2221
2152
|
}
|
|
2222
2153
|
};
|
|
@@ -2225,8 +2156,8 @@ async function startNgrokTunnel(config) {
|
|
|
2225
2156
|
const log = JSON.parse(line);
|
|
2226
2157
|
if (log.msg === "started tunnel" && log.url) publicUrl = log.url;
|
|
2227
2158
|
if (log.addr && log.url && !publicUrl) publicUrl = log.url;
|
|
2228
|
-
if (publicUrl && !
|
|
2229
|
-
|
|
2159
|
+
if (publicUrl && !startupSettled) {
|
|
2160
|
+
startupSettled = true;
|
|
2230
2161
|
clearTimeout(timeout);
|
|
2231
2162
|
const fullUrl = publicUrl + config.path;
|
|
2232
2163
|
console.log(`[voice-call] ngrok tunnel active: ${fullUrl}`);
|
|
@@ -2234,38 +2165,22 @@ async function startNgrokTunnel(config) {
|
|
|
2234
2165
|
publicUrl: fullUrl,
|
|
2235
2166
|
provider: "ngrok",
|
|
2236
2167
|
stop: async () => {
|
|
2237
|
-
|
|
2238
|
-
await new Promise((res) => {
|
|
2239
|
-
let finished = false;
|
|
2240
|
-
const finish = () => {
|
|
2241
|
-
if (finished) return;
|
|
2242
|
-
finished = true;
|
|
2243
|
-
clearTimeout(fallback);
|
|
2244
|
-
proc.off("close", finish);
|
|
2245
|
-
res();
|
|
2246
|
-
};
|
|
2247
|
-
if (closed) {
|
|
2248
|
-
res();
|
|
2249
|
-
return;
|
|
2250
|
-
}
|
|
2251
|
-
proc.once("close", finish);
|
|
2252
|
-
const fallback = setTimeout(finish, 2e3);
|
|
2253
|
-
proc.kill("SIGTERM");
|
|
2254
|
-
if (closed) finish();
|
|
2255
|
-
});
|
|
2168
|
+
await terminateNgrokProcess(proc, () => childClosed);
|
|
2256
2169
|
}
|
|
2257
2170
|
});
|
|
2258
2171
|
}
|
|
2259
2172
|
} catch {}
|
|
2260
2173
|
};
|
|
2261
|
-
proc.stdout.
|
|
2262
|
-
|
|
2174
|
+
proc.stdout.setEncoding("utf8");
|
|
2175
|
+
proc.stderr.setEncoding("utf8");
|
|
2176
|
+
proc.stdout.on("data", (chunk) => {
|
|
2177
|
+
const lines = (outputBuffer + chunk).split("\n");
|
|
2263
2178
|
outputBuffer = lines.pop() || "";
|
|
2264
2179
|
if (outputBuffer.length > NGROK_LOG_BUFFER_MAX_CHARS) outputBuffer = sliceUtf16Safe(outputBuffer, -16384);
|
|
2265
2180
|
for (const line of lines) if (line.trim()) processLine(line);
|
|
2266
2181
|
});
|
|
2267
|
-
proc.stderr.on("data", (
|
|
2268
|
-
const combined = stderrTail +
|
|
2182
|
+
proc.stderr.on("data", (chunk) => {
|
|
2183
|
+
const combined = stderrTail + chunk;
|
|
2269
2184
|
if (combined.includes(NGROK_ERROR_MARKER)) rejectIfPending(`ngrok error: ${formatBoundedChildOutput(appendBoundedChildOutput(emptyBoundedChildOutput(), combined))}`, true);
|
|
2270
2185
|
stderrTail = sliceUtf16Safe(combined, -8);
|
|
2271
2186
|
});
|
|
@@ -2276,9 +2191,9 @@ async function startNgrokTunnel(config) {
|
|
|
2276
2191
|
rejectIfPending(`Failed to start ngrok: ${err.message}`);
|
|
2277
2192
|
});
|
|
2278
2193
|
proc.on("close", (code) => {
|
|
2279
|
-
|
|
2280
|
-
if (!
|
|
2281
|
-
|
|
2194
|
+
childClosed = true;
|
|
2195
|
+
if (!startupSettled) {
|
|
2196
|
+
startupSettled = true;
|
|
2282
2197
|
clearTimeout(timeout);
|
|
2283
2198
|
reject(/* @__PURE__ */ new Error(`ngrok exited unexpectedly with code ${code}`));
|
|
2284
2199
|
}
|
|
@@ -3119,15 +3034,19 @@ function startStaleCallReaper(params) {
|
|
|
3119
3034
|
const maxAgeSeconds = params.staleCallReaperSeconds;
|
|
3120
3035
|
if (!maxAgeSeconds || maxAgeSeconds <= 0) return null;
|
|
3121
3036
|
const maxAgeMs = maxAgeSeconds * 1e3;
|
|
3037
|
+
const callsBeingReaped = /* @__PURE__ */ new Set();
|
|
3122
3038
|
const interval = setInterval(() => {
|
|
3123
3039
|
const now = Date.now();
|
|
3124
3040
|
for (const call of params.manager.getActiveCalls()) {
|
|
3125
3041
|
if (call.answeredAt || TerminalStates.has(call.state) || LiveConversationStates.has(call.state)) continue;
|
|
3126
3042
|
const age = now - call.startedAt;
|
|
3127
|
-
if (age > maxAgeMs) {
|
|
3043
|
+
if (age > maxAgeMs && !callsBeingReaped.has(call.callId)) {
|
|
3044
|
+
callsBeingReaped.add(call.callId);
|
|
3128
3045
|
console.log(`[voice-call] Reaping stale call ${call.callId} (age: ${Math.round(age / 1e3)}s, state: ${call.state})`);
|
|
3129
3046
|
params.manager.endCall(call.callId).catch((err) => {
|
|
3130
3047
|
console.warn(`[voice-call] Reaper failed to end call ${call.callId}:`, err);
|
|
3048
|
+
}).finally(() => {
|
|
3049
|
+
callsBeingReaped.delete(call.callId);
|
|
3131
3050
|
});
|
|
3132
3051
|
}
|
|
3133
3052
|
}
|
|
@@ -3143,7 +3062,7 @@ const WEBHOOK_BODY_TIMEOUT_MS = WEBHOOK_BODY_READ_DEFAULTS.preAuth.timeoutMs;
|
|
|
3143
3062
|
const MISSING_REMOTE_ADDRESS_IN_FLIGHT_KEY = "__voice_call_no_remote__";
|
|
3144
3063
|
const STREAM_DISCONNECT_HANGUP_GRACE_MS = 2e3;
|
|
3145
3064
|
const loadRealtimeTranscriptionRuntime = createLazyRuntimeModule(() => import("./realtime-transcription.runtime-CbJAs5t_.js"));
|
|
3146
|
-
const loadResponseGeneratorModule = createLazyRuntimeModule(() => import("./response-generator-
|
|
3065
|
+
const loadResponseGeneratorModule = createLazyRuntimeModule(() => import("./response-generator-r9wHPOEl.js"));
|
|
3147
3066
|
function appendRecentTalkEventMetadata(call, event) {
|
|
3148
3067
|
const metadata = call.metadata ?? {};
|
|
3149
3068
|
const recent = Array.isArray(metadata.recentTalkEvents) ? metadata.recentTalkEvents.filter((entry) => Boolean(entry) && typeof entry === "object" && !Array.isArray(entry)) : [];
|
|
@@ -3818,12 +3737,12 @@ const REALTIME_VOICE_CONSULT_SYSTEM_PROMPT = [
|
|
|
3818
3737
|
"Do not print secret values or dump environment variables; only check whether required configuration is present.",
|
|
3819
3738
|
"Be accurate, brief, and speakable."
|
|
3820
3739
|
].join(" ");
|
|
3821
|
-
const loadTelnyxProvider = createLazyRuntimeModule(() => import("./telnyx-
|
|
3822
|
-
const loadTwilioProvider = createLazyRuntimeModule(() => import("./twilio-
|
|
3823
|
-
const loadPlivoProvider = createLazyRuntimeModule(() => import("./plivo-
|
|
3740
|
+
const loadTelnyxProvider = createLazyRuntimeModule(() => import("./telnyx-DTD9jTqC.js"));
|
|
3741
|
+
const loadTwilioProvider = createLazyRuntimeModule(() => import("./twilio-GzbIb5fv.js"));
|
|
3742
|
+
const loadPlivoProvider = createLazyRuntimeModule(() => import("./plivo-rISw-5A8.js"));
|
|
3824
3743
|
const loadMockProvider = createLazyRuntimeModule(() => import("./mock-YsvBTX-7.js"));
|
|
3825
3744
|
const loadRealtimeVoiceRuntime = createLazyRuntimeModule(() => import("./realtime-voice.runtime-vtdCOWg-.js"));
|
|
3826
|
-
const loadRealtimeHandler = createLazyRuntimeModule(() => import("./realtime-handler-
|
|
3745
|
+
const loadRealtimeHandler = createLazyRuntimeModule(() => import("./realtime-handler-HSxcgwY_.js"));
|
|
3827
3746
|
function resolveVoiceCallConsultSessionKey(call) {
|
|
3828
3747
|
return resolveVoiceCallSessionKey({
|
|
3829
3748
|
config: call.config,
|
|
@@ -4103,8 +4022,8 @@ async function createVoiceCallRuntime(params) {
|
|
|
4103
4022
|
if (provider.name === "twilio" && config.streaming?.enabled) {
|
|
4104
4023
|
const twilioProvider = provider;
|
|
4105
4024
|
if (ttsRuntime?.textToSpeechTelephony) try {
|
|
4106
|
-
const ttsProvider = createTelephonyTtsProvider({
|
|
4107
|
-
coreConfig,
|
|
4025
|
+
const ttsProvider = await createTelephonyTtsProvider({
|
|
4026
|
+
coreConfig: cfg,
|
|
4108
4027
|
ttsOverride: config.tts,
|
|
4109
4028
|
runtime: ttsRuntime,
|
|
4110
4029
|
logger: log
|
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-D8Mkwy8G.js";
|
|
2
2
|
export { createVoiceCallRuntime };
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import path from "node:path";
|
|
3
2
|
import { createHash, randomUUID } from "node:crypto";
|
|
3
|
+
import path from "node:path";
|
|
4
4
|
import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store";
|
|
5
|
+
import { resolveStateDir } from "openclaw/plugin-sdk/state-paths";
|
|
5
6
|
//#region extensions/voice-call/src/types.ts
|
|
6
7
|
const ProviderNameSchema = z.enum([
|
|
7
8
|
"telnyx",
|
|
@@ -124,7 +125,7 @@ const CallRecordSchema = z.object({
|
|
|
124
125
|
});
|
|
125
126
|
//#endregion
|
|
126
127
|
//#region extensions/voice-call/src/runtime-state.ts
|
|
127
|
-
const { setRuntime: setVoiceCallStateRuntime,
|
|
128
|
+
const { setRuntime: setVoiceCallStateRuntime, tryGetRuntime: getOptionalVoiceCallStateRuntime } = createPluginRuntimeStore({
|
|
128
129
|
pluginId: "voice-call-state",
|
|
129
130
|
errorMessage: "Voice Call state runtime not initialized"
|
|
130
131
|
});
|
|
@@ -400,4 +401,10 @@ async function getCallHistoryFromStore(storePath, limit = 50) {
|
|
|
400
401
|
return readCallHistoryFromStore(storePath).slice(-limit);
|
|
401
402
|
}
|
|
402
403
|
//#endregion
|
|
403
|
-
|
|
404
|
+
//#region extensions/voice-call/src/store-path.ts
|
|
405
|
+
/** Resolve the plugin-owned store below OpenClaw's canonical state directory. */
|
|
406
|
+
function resolveDefaultVoiceCallStoreDir(env = process.env) {
|
|
407
|
+
return path.join(resolveStateDir(env), "voice-calls");
|
|
408
|
+
}
|
|
409
|
+
//#endregion
|
|
410
|
+
export { TerminalStates as _, CALL_RECORD_EVENT_META_MAX_ENTRIES as a, buildVoiceCallLegacyJsonlEventKey as c, loadActiveCallsFromStore as d, parseVoiceCallRecordLine as f, setVoiceCallStateRuntime as g, resolveVoiceCallLegacyCallLogPath as h, CALL_RECORD_EVENT_CHUNKS_NAMESPACE as i, findCallMatchesInStore as l, prepareVoiceCallRecordForStorage as m, CALL_RECORD_CHUNK_MAX_ENTRIES as n, MAX_CALL_RECORD_EVENTS as o, persistCallRecord as p, CALL_RECORD_EVENTS_NAMESPACE as r, RAW_CALL_RECORD_CHUNK_BYTES as s, resolveDefaultVoiceCallStoreDir as t, getCallHistoryFromStore as u };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { s as verifyTelnyxWebhook, t as guardedJsonApiRequest } from "./guarded-json-api-
|
|
1
|
+
import { s as verifyTelnyxWebhook, t as guardedJsonApiRequest } from "./guarded-json-api-Dm4NvtGo.js";
|
|
2
2
|
import crypto from "node:crypto";
|
|
3
3
|
//#region extensions/voice-call/src/providers/telnyx.ts
|
|
4
4
|
function normalizeTelnyxDirection(direction) {
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { fetchWithSsrFGuard } from "./runtime-api.js";
|
|
2
2
|
import "./api.js";
|
|
3
3
|
import { n as requireSupportedTwilioApiHostname, r as resolveTwilioApiBaseUrl } from "./twilio-region-4fkgz3UG.js";
|
|
4
|
-
import { _ as mapVoiceToPolly, a as normalizeProviderStatus, f as chunkAudio, g as escapeXml, i as mapProviderStatusToEndReason, o as getHeader, r as isProviderStatusTerminal } from "./runtime-entry-
|
|
5
|
-
import { c as verifyTwilioWebhook, i as readProviderJsonResponseText, n as cancelProviderResponseBody, r as readProviderErrorResponseSnippet, t as guardedJsonApiRequest } from "./guarded-json-api-
|
|
4
|
+
import { _ as mapVoiceToPolly, a as normalizeProviderStatus, f as chunkAudio, g as escapeXml, i as mapProviderStatusToEndReason, o as getHeader, r as isProviderStatusTerminal } from "./runtime-entry-D8Mkwy8G.js";
|
|
5
|
+
import { c as verifyTwilioWebhook, i as readProviderJsonResponseText, n as cancelProviderResponseBody, r as readProviderErrorResponseSnippet, t as guardedJsonApiRequest } from "./guarded-json-api-Dm4NvtGo.js";
|
|
6
6
|
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
7
|
+
import { sleepWithAbort } from "openclaw/plugin-sdk/runtime-env";
|
|
7
8
|
import crypto from "node:crypto";
|
|
8
9
|
import { safeEqualSecret } from "openclaw/plugin-sdk/security-runtime";
|
|
9
10
|
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
@@ -628,9 +629,12 @@ var TwilioProvider = class TwilioProvider {
|
|
|
628
629
|
chunkAttempts += 1;
|
|
629
630
|
if (sendAudioChunk(chunk).sent) chunkDelivered += 1;
|
|
630
631
|
const waitMs = nextChunkDueAt - Date.now();
|
|
631
|
-
if (waitMs > 0)
|
|
632
|
-
|
|
633
|
-
})
|
|
632
|
+
if (waitMs > 0) try {
|
|
633
|
+
await sleepWithAbort(Math.ceil(waitMs), signal);
|
|
634
|
+
} catch (error) {
|
|
635
|
+
if (!signal.aborted) throw error;
|
|
636
|
+
break;
|
|
637
|
+
}
|
|
634
638
|
nextChunkDueAt += CHUNK_DELAY_MS;
|
|
635
639
|
if (signal.aborted) break;
|
|
636
640
|
}
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/voice-call",
|
|
3
|
-
"version": "2026.7.2-beta.
|
|
3
|
+
"version": "2026.7.2-beta.3",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@openclaw/voice-call",
|
|
9
|
-
"version": "2026.7.2-beta.
|
|
9
|
+
"version": "2026.7.2-beta.3",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"typebox": "1.3.3",
|
|
12
12
|
"ws": "8.21.0",
|
|
13
13
|
"zod": "4.4.3"
|
|
14
14
|
},
|
|
15
15
|
"peerDependencies": {
|
|
16
|
-
"openclaw": ">=2026.7.2-beta.
|
|
16
|
+
"openclaw": ">=2026.7.2-beta.3"
|
|
17
17
|
},
|
|
18
18
|
"peerDependenciesMeta": {
|
|
19
19
|
"openclaw": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/voice-call",
|
|
3
|
-
"version": "2026.7.2-beta.
|
|
3
|
+
"version": "2026.7.2-beta.3",
|
|
4
4
|
"description": "OpenClaw voice-call plugin for Twilio, Telnyx, and Plivo phone calls.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"zod": "4.4.3"
|
|
14
14
|
},
|
|
15
15
|
"peerDependencies": {
|
|
16
|
-
"openclaw": ">=2026.7.2-beta.
|
|
16
|
+
"openclaw": ">=2026.7.2-beta.3"
|
|
17
17
|
},
|
|
18
18
|
"peerDependenciesMeta": {
|
|
19
19
|
"openclaw": {
|
|
@@ -30,10 +30,10 @@
|
|
|
30
30
|
"minHostVersion": ">=2026.4.10"
|
|
31
31
|
},
|
|
32
32
|
"compat": {
|
|
33
|
-
"pluginApi": ">=2026.7.2-beta.
|
|
33
|
+
"pluginApi": ">=2026.7.2-beta.3"
|
|
34
34
|
},
|
|
35
35
|
"build": {
|
|
36
|
-
"openclawVersion": "2026.7.2-beta.
|
|
36
|
+
"openclawVersion": "2026.7.2-beta.3"
|
|
37
37
|
},
|
|
38
38
|
"release": {
|
|
39
39
|
"publishToClawHub": true,
|