@openclaw/voice-call 2026.9.6 → 2026.9.7
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/assets/icon.png +0 -0
- package/dist/.setup/{cli-D6j7ppuN.mjs → cli-D6uwMXyS.mjs} +6 -10
- package/dist/.setup/{config-72FnMWHb.mjs → config-Ds7xoZbI.mjs} +27 -30
- package/dist/.setup/{guarded-json-api-exU4Hutw.mjs → guarded-json-api-Bqc6QS8A.mjs} +23 -17
- package/dist/.setup/{mock-CcYEAUhV.mjs → mock-ClWW6Ufu.mjs} +34 -53
- package/dist/.setup/{plivo-BGJda1Uz.mjs → plivo-69ObrQPk.mjs} +17 -42
- package/dist/.setup/realtime-agent-context.legacy-DAEWY27o.mjs +53 -0
- package/dist/.setup/{realtime-handler-DlFmdSPn.mjs → realtime-handler-BgDPcPZt.mjs} +34 -53
- package/dist/.setup/{response-generator-hZyGIjO4.mjs → response-generator-BzKrJMEb.mjs} +2 -2
- package/dist/.setup/{runtime-entry-dDRm6RgA.mjs → runtime-entry-BNOQRXii.mjs} +82 -297
- package/dist/.setup/{telnyx-BV56PXr-.mjs → telnyx-DjFGCfy1.mjs} +19 -55
- package/dist/.setup/{twilio-BMb-7cu-.mjs → twilio-Bp3gfmUB.mjs} +29 -78
- package/dist/.setup/{store-path-B1j1up4_.mjs → utils-B5pnM_Ij.mjs} +14 -6
- package/dist/api.js +5 -1
- package/dist/doctor-contract-api.js +186 -15
- package/dist/index.js +6 -42
- package/dist/runtime-entry.js +1 -1
- package/node_modules/typebox/build/compile/code.mjs +1 -2
- package/node_modules/typebox/build/compile/validator.mjs +5 -1
- package/node_modules/typebox/build/format/iri.mjs +16 -9
- package/node_modules/typebox/build/guard/emit.mjs +2 -2
- package/node_modules/typebox/build/guard/guard.d.mts +7 -5
- package/node_modules/typebox/build/guard/guard.mjs +14 -9
- package/node_modules/typebox/build/guard/index.mjs +6 -0
- package/node_modules/typebox/build/guard/unicode/unicode.d.mts +6 -0
- package/node_modules/typebox/build/guard/unicode/unicode.mjs +35 -0
- package/node_modules/typebox/build/guard/{string.mjs → unicode/unicode_segment.mjs} +16 -19
- package/node_modules/typebox/build/schema/build.mjs +2 -3
- package/node_modules/typebox/build/schema/engine/_pathing.d.mts +1 -0
- package/node_modules/typebox/build/schema/engine/_pathing.mjs +11 -0
- package/node_modules/typebox/build/schema/engine/_refine.mjs +13 -5
- package/node_modules/typebox/build/schema/engine/additionalProperties.mjs +2 -1
- package/node_modules/typebox/build/schema/engine/dependencies.mjs +2 -1
- package/node_modules/typebox/build/schema/engine/dependentSchemas.mjs +2 -1
- package/node_modules/typebox/build/schema/engine/dynamicRef.d.mts +1 -1
- package/node_modules/typebox/build/schema/engine/dynamicRef.mjs +2 -2
- package/node_modules/typebox/build/schema/engine/format.mjs +9 -2
- package/node_modules/typebox/build/schema/engine/patternProperties.mjs +3 -2
- package/node_modules/typebox/build/schema/engine/properties.mjs +3 -2
- package/node_modules/typebox/build/schema/engine/propertyNames.mjs +2 -1
- package/node_modules/typebox/build/schema/engine/recursiveRef.d.mts +1 -1
- package/node_modules/typebox/build/schema/engine/recursiveRef.mjs +2 -2
- package/node_modules/typebox/build/schema/engine/ref.d.mts +1 -1
- package/node_modules/typebox/build/schema/engine/ref.mjs +2 -2
- package/node_modules/typebox/build/schema/engine/schema.mjs +24 -24
- package/node_modules/typebox/build/schema/parse.mjs +3 -0
- package/node_modules/typebox/build/schema/pointer/pointer.d.mts +1 -6
- package/node_modules/typebox/build/schema/pointer/pointer.mjs +47 -64
- package/node_modules/typebox/build/system/settings/internal.d.mts +2 -0
- package/node_modules/typebox/build/system/settings/internal.mjs +20 -0
- package/node_modules/typebox/build/system/settings/settings.d.mts +11 -0
- package/node_modules/typebox/build/system/settings/settings.mjs +14 -0
- package/node_modules/typebox/build/type/extends/inference.d.mts +1 -1
- package/node_modules/typebox/build/type/extends/inference.mjs +15 -2
- package/node_modules/typebox/build/type/extends/tuple.d.mts +2 -1
- package/node_modules/typebox/build/type/extends/tuple.mjs +21 -1
- package/node_modules/typebox/build/value/parse/parse.mjs +10 -4
- package/node_modules/typebox/package.json +1 -1
- package/node_modules/typebox/readme.md +3 -1
- package/package.json +5 -5
- package/dist/runtime-api.js +0 -6
- /package/node_modules/typebox/build/guard/{string.d.mts → unicode/unicode_segment.d.mts} +0 -0
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { isBlockedHostnameOrIp, isRequestBodyLimitError, readRequestBodyWithLimit, requestBodyErrorToText, sendHttpRequestRejection } from "../
|
|
2
|
-
import "
|
|
3
|
-
import {
|
|
4
|
-
import { _ as releaseRejectedProviderCall, b as setVoiceCallStateRuntime, d as getCallHistoryFromStore, f as loadActiveCallsFromStore, g as appendCallReplayKey, m as persistCallRecord, t as resolveDefaultVoiceCallStoreDir, u as findCallInStore, v as rememberManagerReplayKey, x as TerminalStates, y as reserveRejectedProviderCall } from "./store-path-B1j1up4_.mjs";
|
|
1
|
+
import { isBlockedHostnameOrIp, isRequestBodyLimitError, readRequestBodyWithLimit, requestBodyErrorToText, sendHttpRequestRejection } from "../api.js";
|
|
2
|
+
import { a as resolveVoiceCallEffectiveConfig, c as resolveVoiceCallSessionKey, f as resolveVoiceCallRealtimeTools, i as resolveVoiceCallConfig, l as resolveVoiceCallStreamExposurePaths, m as normalizePhoneNumber, n as normalizeVoiceCallConfig, o as resolveVoiceCallNumberRouteKeyForCall, p as isAllowlistedCaller, r as resolveTwilioAuthToken, u as validateProviderConfig } from "./config-Ds7xoZbI.mjs";
|
|
3
|
+
import { S as TerminalStates, _ as appendCallReplayKey, b as reserveRejectedProviderCall, d as findCallInStore, f as getCallHistoryFromStore, h as persistCallRecord, n as resolveDefaultVoiceCallStoreDir, p as loadActiveCallsFromStore, t as resolveUserPath, v as releaseRejectedProviderCall, x as setVoiceCallStateRuntime, y as rememberManagerReplayKey } from "./utils-B5pnM_Ij.mjs";
|
|
5
4
|
import { createRequire } from "node:module";
|
|
6
5
|
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
7
6
|
import { isLoopbackHost } from "openclaw/plugin-sdk/gateway-runtime";
|
|
@@ -18,31 +17,15 @@ import { KeyedAsyncQueue } from "openclaw/plugin-sdk/keyed-async-queue";
|
|
|
18
17
|
import crypto from "node:crypto";
|
|
19
18
|
import { redactIdentifier } from "openclaw/plugin-sdk/logging-core";
|
|
20
19
|
import path from "node:path";
|
|
21
|
-
import
|
|
22
|
-
import { root } from "openclaw/plugin-sdk/security-runtime";
|
|
23
|
-
import { sliceUtf16Safe, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
20
|
+
import * as realtimeBootstrapSdk from "openclaw/plugin-sdk/realtime-bootstrap-context";
|
|
24
21
|
import { spawn } from "node:child_process";
|
|
22
|
+
import { sliceUtf16Safe, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
25
23
|
import { runCommandWithTimeout } from "openclaw/plugin-sdk/process-runtime";
|
|
26
24
|
import http from "node:http";
|
|
27
25
|
import { URL as URL$1 } from "node:url";
|
|
28
26
|
import { resolveConfiguredCapabilityProvider } from "openclaw/plugin-sdk/provider-selection-runtime";
|
|
29
27
|
import { rejectWebSocketUpgrade } from "openclaw/plugin-sdk/websocket-runtime";
|
|
30
28
|
import { canonicalizeBase64 } from "openclaw/plugin-sdk/media-runtime";
|
|
31
|
-
//#region extensions/voice-call/src/allowlist.ts
|
|
32
|
-
/** Normalize a phone number to digits only. */
|
|
33
|
-
function normalizePhoneNumber(input) {
|
|
34
|
-
if (!input) return "";
|
|
35
|
-
return input.replace(/\D/g, "");
|
|
36
|
-
}
|
|
37
|
-
/** Return true when the normalized caller exactly matches an allowlist entry. */
|
|
38
|
-
function isAllowlistedCaller(normalizedFrom, allowFrom) {
|
|
39
|
-
if (!normalizedFrom) return false;
|
|
40
|
-
return (allowFrom ?? []).some((num) => {
|
|
41
|
-
const normalizedAllow = normalizePhoneNumber(num);
|
|
42
|
-
return normalizedAllow !== "" && normalizedAllow === normalizedFrom;
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
//#endregion
|
|
46
29
|
//#region extensions/voice-call/src/manager/state.ts
|
|
47
30
|
const ConversationStates = /* @__PURE__ */ new Set(["speaking", "listening"]);
|
|
48
31
|
const StateOrder = [
|
|
@@ -92,10 +75,6 @@ function resolveVoiceCallSecondsTimerDelayMs(seconds, minMs = 1) {
|
|
|
92
75
|
const timeoutMs = Math.floor(seconds * 1e3);
|
|
93
76
|
return resolveTimerTimeoutMs(Number.isFinite(timeoutMs) ? timeoutMs : MAX_TIMER_TIMEOUT_MS, minMs, minMs);
|
|
94
77
|
}
|
|
95
|
-
/** Normalize a millisecond timeout delay with fallback behavior. */
|
|
96
|
-
function resolveVoiceCallTimerDelayMs(timeoutMs, fallbackMs = 1) {
|
|
97
|
-
return resolveTimerTimeoutMs(timeoutMs, fallbackMs);
|
|
98
|
-
}
|
|
99
78
|
//#endregion
|
|
100
79
|
//#region extensions/voice-call/src/manager/timers.ts
|
|
101
80
|
/** Clear and forget the max-duration timer for a call. */
|
|
@@ -109,7 +88,7 @@ function clearMaxDurationTimer(ctx, callId) {
|
|
|
109
88
|
/** Start or replace the max-duration timer for a call. */
|
|
110
89
|
function startMaxDurationTimer(params) {
|
|
111
90
|
clearMaxDurationTimer(params.ctx, params.callId);
|
|
112
|
-
const maxDurationMs = params.timeoutMs === void 0 ? resolveVoiceCallSecondsTimerDelayMs(params.ctx.config.maxDurationSeconds) :
|
|
91
|
+
const maxDurationMs = params.timeoutMs === void 0 ? resolveVoiceCallSecondsTimerDelayMs(params.ctx.config.maxDurationSeconds) : resolveTimerTimeoutMs(params.timeoutMs, 1);
|
|
113
92
|
console.log(`[voice-call] Starting max duration timer (${Math.ceil(maxDurationMs / 1e3)}s) for call ${params.callId}`);
|
|
114
93
|
const timer = setTimeout(() => {
|
|
115
94
|
const work = (async () => {
|
|
@@ -155,7 +134,7 @@ function resolveTranscriptWaiter(ctx, callId, transcript, turnToken) {
|
|
|
155
134
|
/** Wait for the next final transcript for a call, optionally scoped to a turn token. */
|
|
156
135
|
function waitForFinalTranscript(ctx, callId, turnToken) {
|
|
157
136
|
if (ctx.transcriptWaiters.has(callId)) return Promise.reject(/* @__PURE__ */ new Error("Already waiting for transcript"));
|
|
158
|
-
const timeoutMs =
|
|
137
|
+
const timeoutMs = resolveTimerTimeoutMs(ctx.config.transcriptTimeoutMs, 1);
|
|
159
138
|
return new Promise((resolve, reject) => {
|
|
160
139
|
const timeout = setTimeout(() => {
|
|
161
140
|
ctx.transcriptWaiters.delete(callId);
|
|
@@ -324,20 +303,10 @@ function lookupConnectedCall(ctx, callId) {
|
|
|
324
303
|
}
|
|
325
304
|
function requireConnectedCall(ctx, callId) {
|
|
326
305
|
const lookup = lookupConnectedCall(ctx, callId);
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
error: lookup.error
|
|
330
|
-
};
|
|
331
|
-
if (lookup.kind === "ended") return {
|
|
332
|
-
ok: false,
|
|
306
|
+
return lookup.kind === "ended" ? {
|
|
307
|
+
kind: "error",
|
|
333
308
|
error: "Call has ended"
|
|
334
|
-
};
|
|
335
|
-
return {
|
|
336
|
-
ok: true,
|
|
337
|
-
call: lookup.call,
|
|
338
|
-
providerCallId: lookup.providerCallId,
|
|
339
|
-
provider: lookup.provider
|
|
340
|
-
};
|
|
309
|
+
} : lookup;
|
|
341
310
|
}
|
|
342
311
|
function isCurrentCall(ctx, call) {
|
|
343
312
|
return ctx.activeCalls.get(call.callId) === call && !TerminalStates.has(call.state);
|
|
@@ -483,7 +452,7 @@ async function initiateCall(ctx, to, sessionKey, options) {
|
|
|
483
452
|
}
|
|
484
453
|
async function speak(ctx, callId, text, options) {
|
|
485
454
|
const connected = requireConnectedCall(ctx, callId);
|
|
486
|
-
if (
|
|
455
|
+
if (connected.kind === "error") return {
|
|
487
456
|
success: false,
|
|
488
457
|
error: connected.error
|
|
489
458
|
};
|
|
@@ -549,7 +518,7 @@ async function sendDtmf(ctx, callId, digits) {
|
|
|
549
518
|
error: validationError
|
|
550
519
|
};
|
|
551
520
|
const connected = requireConnectedCall(ctx, callId);
|
|
552
|
-
if (
|
|
521
|
+
if (connected.kind === "error") return {
|
|
553
522
|
success: false,
|
|
554
523
|
error: connected.error
|
|
555
524
|
};
|
|
@@ -640,7 +609,7 @@ async function speakInitialMessage(ctx, providerCallId) {
|
|
|
640
609
|
}
|
|
641
610
|
async function continueCall(ctx, callId, prompt) {
|
|
642
611
|
const connected = requireConnectedCall(ctx, callId);
|
|
643
|
-
if (
|
|
612
|
+
if (connected.kind === "error") return {
|
|
644
613
|
success: false,
|
|
645
614
|
error: connected.error
|
|
646
615
|
};
|
|
@@ -1032,18 +1001,6 @@ async function processEventInQueue(ctx, event) {
|
|
|
1032
1001
|
return result;
|
|
1033
1002
|
}
|
|
1034
1003
|
//#endregion
|
|
1035
|
-
//#region extensions/voice-call/src/utils.ts
|
|
1036
|
-
/** Resolve user input paths, including "~" against the current OS home. */
|
|
1037
|
-
function resolveUserPath(input) {
|
|
1038
|
-
const trimmed = input.trim();
|
|
1039
|
-
if (!trimmed) return trimmed;
|
|
1040
|
-
if (trimmed.startsWith("~")) {
|
|
1041
|
-
const expanded = trimmed.replace(/^~(?=$|[\\/])/, () => os.homedir());
|
|
1042
|
-
return path.resolve(expanded);
|
|
1043
|
-
}
|
|
1044
|
-
return path.resolve(trimmed);
|
|
1045
|
-
}
|
|
1046
|
-
//#endregion
|
|
1047
1004
|
//#region extensions/voice-call/src/manager.ts
|
|
1048
1005
|
function markRestoredCallSkipped(call, endReason) {
|
|
1049
1006
|
call.endedAt = Date.now();
|
|
@@ -1062,9 +1019,6 @@ function resolveDefaultStoreBase(config, storePath) {
|
|
|
1062
1019
|
if (rawOverride) return resolveUserPath(rawOverride);
|
|
1063
1020
|
return resolveDefaultVoiceCallStoreDir();
|
|
1064
1021
|
}
|
|
1065
|
-
/**
|
|
1066
|
-
* Manages voice calls: state ownership and delegation to manager helper modules.
|
|
1067
|
-
*/
|
|
1068
1022
|
var CallManager = class {
|
|
1069
1023
|
runOperation(operation) {
|
|
1070
1024
|
if (this.closing) return Promise.reject(/* @__PURE__ */ new Error("Voice Call manager is stopping"));
|
|
@@ -1272,27 +1226,15 @@ var CallManager = class {
|
|
|
1272
1226
|
if (keptVerificationFailures > 0) console.log(`[voice-call] Kept ${keptVerificationFailures} restored call(s) after verification failure (relying on timer)`);
|
|
1273
1227
|
return verified;
|
|
1274
1228
|
}
|
|
1275
|
-
/**
|
|
1276
|
-
* Get the current provider.
|
|
1277
|
-
*/
|
|
1278
1229
|
getProvider() {
|
|
1279
1230
|
return this.provider;
|
|
1280
1231
|
}
|
|
1281
|
-
/**
|
|
1282
|
-
* Initiate an outbound call.
|
|
1283
|
-
*/
|
|
1284
1232
|
async initiateCall(to, sessionKey, options) {
|
|
1285
1233
|
return this.runOperation(() => initiateCall(this.getContext(), to, sessionKey, options));
|
|
1286
1234
|
}
|
|
1287
|
-
/**
|
|
1288
|
-
* Speak to user in an active call.
|
|
1289
|
-
*/
|
|
1290
1235
|
async speak(callId, text, options) {
|
|
1291
1236
|
return this.runOperation(() => speak(this.getContext(), callId, text, options));
|
|
1292
1237
|
}
|
|
1293
|
-
/**
|
|
1294
|
-
* Send DTMF digits to an active call.
|
|
1295
|
-
*/
|
|
1296
1238
|
async sendDtmf(callId, digits) {
|
|
1297
1239
|
return this.runOperation(() => sendDtmf(this.getContext(), callId, digits));
|
|
1298
1240
|
}
|
|
@@ -1308,9 +1250,6 @@ var CallManager = class {
|
|
|
1308
1250
|
async continueCall(callId, prompt) {
|
|
1309
1251
|
return this.runOperation(() => continueCall(this.getContext(), callId, prompt));
|
|
1310
1252
|
}
|
|
1311
|
-
/**
|
|
1312
|
-
* End an active call.
|
|
1313
|
-
*/
|
|
1314
1253
|
endCall(callId, options) {
|
|
1315
1254
|
return this.runOperation(() => endCall(this.getContext(), callId, options));
|
|
1316
1255
|
}
|
|
@@ -1343,9 +1282,6 @@ var CallManager = class {
|
|
|
1343
1282
|
streamSessionIssuer: this.streamSessionIssuer
|
|
1344
1283
|
};
|
|
1345
1284
|
}
|
|
1346
|
-
/**
|
|
1347
|
-
* Process a webhook event.
|
|
1348
|
-
*/
|
|
1349
1285
|
processEvent(event) {
|
|
1350
1286
|
return this.runOperation(() => processEvent(this.getContext(), event));
|
|
1351
1287
|
}
|
|
@@ -1380,9 +1316,6 @@ var CallManager = class {
|
|
|
1380
1316
|
console.warn(`[voice-call] Failed to speak initial message for call ${call.callId}: ${formatErrorMessage(err)}`);
|
|
1381
1317
|
});
|
|
1382
1318
|
}
|
|
1383
|
-
/**
|
|
1384
|
-
* Get an active call by ID.
|
|
1385
|
-
*/
|
|
1386
1319
|
getCall(callId) {
|
|
1387
1320
|
return this.activeCalls.get(callId);
|
|
1388
1321
|
}
|
|
@@ -1390,9 +1323,6 @@ var CallManager = class {
|
|
|
1390
1323
|
getCallForStream(callId) {
|
|
1391
1324
|
return this.runOperation(() => this.mutationQueue.enqueue("state", async () => this.closing ? void 0 : this.activeCalls.get(callId)));
|
|
1392
1325
|
}
|
|
1393
|
-
/**
|
|
1394
|
-
* Get an active call by provider call ID (e.g., Twilio CallSid).
|
|
1395
|
-
*/
|
|
1396
1326
|
getCallByProviderCallId(providerCallId) {
|
|
1397
1327
|
return getCallByProviderCallId({
|
|
1398
1328
|
activeCalls: this.activeCalls,
|
|
@@ -1400,9 +1330,6 @@ var CallManager = class {
|
|
|
1400
1330
|
providerCallId
|
|
1401
1331
|
});
|
|
1402
1332
|
}
|
|
1403
|
-
/**
|
|
1404
|
-
* Get all active calls.
|
|
1405
|
-
*/
|
|
1406
1333
|
getActiveCalls() {
|
|
1407
1334
|
return Array.from(this.activeCalls.values());
|
|
1408
1335
|
}
|
|
@@ -1412,86 +1339,37 @@ var CallManager = class {
|
|
|
1412
1339
|
if (active) return active;
|
|
1413
1340
|
return this.runOperation(() => findCallInStore(this.storePath, callId, this.stateRuntime));
|
|
1414
1341
|
}
|
|
1415
|
-
/**
|
|
1416
|
-
* Get call history (from persisted logs).
|
|
1417
|
-
*/
|
|
1418
1342
|
async getCallHistory(limit = 50) {
|
|
1419
1343
|
return this.runOperation(() => getCallHistoryFromStore(this.storePath, limit, this.stateRuntime));
|
|
1420
1344
|
}
|
|
1421
1345
|
};
|
|
1422
1346
|
//#endregion
|
|
1423
1347
|
//#region extensions/voice-call/src/realtime-agent-context.ts
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
if (text.length <= maxChars) return text;
|
|
1427
|
-
return `${truncateUtf16Safe(text, Math.max(0, maxChars - 32)).trimEnd()}\n[truncated]`;
|
|
1428
|
-
}
|
|
1429
|
-
/** Read configured workspace context files through the safe workspace root. */
|
|
1430
|
-
async function readWorkspaceVoiceContextFiles(params) {
|
|
1431
|
-
const sections = [];
|
|
1432
|
-
let remaining = params.maxChars;
|
|
1433
|
-
const workspaceRoot = await root(params.workspaceDir).catch(() => null);
|
|
1434
|
-
if (!workspaceRoot) return sections;
|
|
1435
|
-
for (const file of params.files) {
|
|
1436
|
-
if (remaining <= 0) continue;
|
|
1437
|
-
const trimmed = (await workspaceRoot.readText(file).catch(() => void 0))?.trim();
|
|
1438
|
-
if (!trimmed) continue;
|
|
1439
|
-
const section = `### ${file}\n${limitText(trimmed, Math.max(0, remaining - file.length - 16))}`;
|
|
1440
|
-
sections.push(section);
|
|
1441
|
-
remaining -= section.length;
|
|
1442
|
-
}
|
|
1443
|
-
return sections;
|
|
1444
|
-
}
|
|
1445
|
-
/** Build final realtime instructions from base instructions, consult policy, and fast context. */
|
|
1348
|
+
const contextSdk = realtimeBootstrapSdk;
|
|
1349
|
+
/** Build final realtime instructions from base instructions, consult policy, and agent context. */
|
|
1446
1350
|
async function buildRealtimeVoiceInstructions(params) {
|
|
1447
1351
|
const { config } = params;
|
|
1448
|
-
const sections = [params.baseInstructions];
|
|
1449
|
-
const consultGuidance = buildRealtimeVoiceAgentConsultPolicyInstructions(config.realtime);
|
|
1450
|
-
if (consultGuidance) sections.push(consultGuidance);
|
|
1451
1352
|
const contextConfig = config.realtime.agentContext;
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
const
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
].filter(Boolean);
|
|
1469
|
-
if (identityLines.length > 0) capsule.push(`Configured identity:\n${identityLines.join("\n")}`);
|
|
1470
|
-
}
|
|
1471
|
-
if (contextConfig.includeWorkspaceFiles) {
|
|
1472
|
-
const fileSections = await readWorkspaceVoiceContextFiles({
|
|
1473
|
-
workspaceDir: params.agentRuntime.resolveAgentWorkspaceDir(params.coreConfig, agentId),
|
|
1474
|
-
files: contextConfig.files,
|
|
1475
|
-
maxChars: contextConfig.maxChars
|
|
1476
|
-
});
|
|
1477
|
-
if (fileSections.length > 0) capsule.push(`Workspace voice context:\n${fileSections.join("\n\n")}`);
|
|
1353
|
+
const sections = [params.baseInstructions, buildRealtimeVoiceAgentConsultPolicyInstructions(config.realtime)];
|
|
1354
|
+
if (contextSdk.resolveRealtimeVoiceAgentContextInstructions) sections.push(await contextSdk.resolveRealtimeVoiceAgentContextInstructions({
|
|
1355
|
+
config: params.coreConfig,
|
|
1356
|
+
agentId: params.agentId,
|
|
1357
|
+
files: contextConfig.enabled && contextConfig.includeWorkspaceFiles ? contextConfig.files : [],
|
|
1358
|
+
includeIdentity: contextConfig.enabled && contextConfig.includeIdentity,
|
|
1359
|
+
maxChars: contextConfig.maxChars,
|
|
1360
|
+
warn: params.warn
|
|
1361
|
+
}));
|
|
1362
|
+
else {
|
|
1363
|
+
const { buildLegacyRealtimeVoiceAgentContext } = await import("./realtime-agent-context.legacy-DAEWY27o.mjs");
|
|
1364
|
+
sections.push(await buildLegacyRealtimeVoiceAgentContext({
|
|
1365
|
+
config: contextConfig,
|
|
1366
|
+
coreConfig: params.coreConfig,
|
|
1367
|
+
agentId: params.agentId
|
|
1368
|
+
}));
|
|
1478
1369
|
}
|
|
1479
|
-
sections.push(limitText(capsule.join("\n\n"), contextConfig.maxChars));
|
|
1480
1370
|
return sections.filter(Boolean).join("\n\n");
|
|
1481
1371
|
}
|
|
1482
1372
|
//#endregion
|
|
1483
|
-
//#region extensions/voice-call/src/realtime-fast-context.ts
|
|
1484
|
-
/** Resolve fast-context consult data using caller-oriented labels. */
|
|
1485
|
-
async function resolveRealtimeFastContextConsult(params) {
|
|
1486
|
-
return await resolveRealtimeVoiceFastContextConsult({
|
|
1487
|
-
...params,
|
|
1488
|
-
labels: {
|
|
1489
|
-
audienceLabel: "caller",
|
|
1490
|
-
contextName: "OpenClaw memory or session context"
|
|
1491
|
-
}
|
|
1492
|
-
});
|
|
1493
|
-
}
|
|
1494
|
-
//#endregion
|
|
1495
1373
|
//#region extensions/voice-call/src/resolve-call-agent-id.ts
|
|
1496
1374
|
/** Setup and startup must resolve the same owner before provisioning telephony. */
|
|
1497
1375
|
function resolveVoiceCallAgentId(config, coreConfig) {
|
|
@@ -1521,10 +1399,8 @@ function resolveVoiceResponseModel(params) {
|
|
|
1521
1399
|
/**
|
|
1522
1400
|
* Chunk audio buffer into 20ms frames for streaming (8kHz mono mu-law).
|
|
1523
1401
|
*/
|
|
1524
|
-
function chunkAudio(audio, chunkSize = 160) {
|
|
1525
|
-
|
|
1526
|
-
for (let i = 0; i < audio.length; i += chunkSize) yield audio.subarray(i, Math.min(i + chunkSize, audio.length));
|
|
1527
|
-
})();
|
|
1402
|
+
function* chunkAudio(audio, chunkSize = 160) {
|
|
1403
|
+
for (let i = 0; i < audio.length; i += chunkSize) yield audio.subarray(i, i + chunkSize);
|
|
1528
1404
|
}
|
|
1529
1405
|
//#endregion
|
|
1530
1406
|
//#region extensions/voice-call/src/telephony-tts.ts
|
|
@@ -1584,28 +1460,9 @@ async function createTelephonyTtsProvider(params) {
|
|
|
1584
1460
|
//#endregion
|
|
1585
1461
|
//#region extensions/voice-call/src/bounded-child-output.ts
|
|
1586
1462
|
const DEFAULT_MAX_OUTPUT_CHARS = 16384;
|
|
1587
|
-
/**
|
|
1588
|
-
function
|
|
1589
|
-
return {
|
|
1590
|
-
text: "",
|
|
1591
|
-
truncated: false
|
|
1592
|
-
};
|
|
1593
|
-
}
|
|
1594
|
-
/** Append output while retaining the newest maxChars and recording truncation. */
|
|
1595
|
-
function appendBoundedChildOutput(current, chunk, maxChars = DEFAULT_MAX_OUTPUT_CHARS) {
|
|
1596
|
-
const appended = current.text + chunk;
|
|
1597
|
-
if (appended.length <= maxChars) return {
|
|
1598
|
-
text: appended,
|
|
1599
|
-
truncated: current.truncated
|
|
1600
|
-
};
|
|
1601
|
-
return {
|
|
1602
|
-
text: sliceUtf16Safe(appended, -maxChars),
|
|
1603
|
-
truncated: true
|
|
1604
|
-
};
|
|
1605
|
-
}
|
|
1606
|
-
/** Format captured output with a truncation marker when older text was dropped. */
|
|
1607
|
-
function formatBoundedChildOutput(output) {
|
|
1608
|
-
return output.truncated ? `[output truncated]\n${output.text}` : output.text;
|
|
1463
|
+
/** Keep the newest diagnostic text without splitting a surrogate pair. */
|
|
1464
|
+
function formatBoundedChildOutput(text, maxChars = DEFAULT_MAX_OUTPUT_CHARS) {
|
|
1465
|
+
return text.length > maxChars ? `[output truncated]\n${sliceUtf16Safe(text, -maxChars)}` : text;
|
|
1609
1466
|
}
|
|
1610
1467
|
//#endregion
|
|
1611
1468
|
//#region extensions/voice-call/src/webhook/tailscale.ts
|
|
@@ -1672,14 +1529,11 @@ async function getTailscaleSelfInfo() {
|
|
|
1672
1529
|
return null;
|
|
1673
1530
|
}
|
|
1674
1531
|
}
|
|
1675
|
-
async function getTailscaleDnsName() {
|
|
1676
|
-
return (await getTailscaleSelfInfo())?.dnsName ?? null;
|
|
1677
|
-
}
|
|
1678
1532
|
async function cleanupTailscaleExposureRoute(opts) {
|
|
1679
1533
|
await runTailscaleCommand(buildTailscaleExposureArgs(opts));
|
|
1680
1534
|
}
|
|
1681
1535
|
async function setupTailscaleExposureRoutes(opts) {
|
|
1682
|
-
const dnsName = await
|
|
1536
|
+
const dnsName = (await getTailscaleSelfInfo())?.dnsName;
|
|
1683
1537
|
if (!dnsName) {
|
|
1684
1538
|
console.warn("[voice-call] Could not get Tailscale DNS name");
|
|
1685
1539
|
return null;
|
|
@@ -1855,7 +1709,7 @@ async function startNgrokTunnel(config) {
|
|
|
1855
1709
|
});
|
|
1856
1710
|
proc.stderr.on("data", (chunk) => {
|
|
1857
1711
|
const combined = stderrTail + chunk;
|
|
1858
|
-
if (combined.includes(NGROK_ERROR_MARKER)) rejectIfPending(`ngrok error: ${formatBoundedChildOutput(
|
|
1712
|
+
if (combined.includes(NGROK_ERROR_MARKER)) rejectIfPending(`ngrok error: ${formatBoundedChildOutput(combined)}`, true);
|
|
1859
1713
|
stderrTail = sliceUtf16Safe(combined, -8);
|
|
1860
1714
|
});
|
|
1861
1715
|
listenForChildStreamErrors(proc, (stream, error) => {
|
|
@@ -1874,9 +1728,6 @@ async function startNgrokTunnel(config) {
|
|
|
1874
1728
|
});
|
|
1875
1729
|
});
|
|
1876
1730
|
}
|
|
1877
|
-
/**
|
|
1878
|
-
* Start a Tailscale serve/funnel tunnel.
|
|
1879
|
-
*/
|
|
1880
1731
|
async function startTailscaleTunnel(config) {
|
|
1881
1732
|
const path = config.path.startsWith("/") ? config.path : `/${config.path}`;
|
|
1882
1733
|
const routes = [{
|
|
@@ -1908,9 +1759,6 @@ async function startTailscaleTunnel(config) {
|
|
|
1908
1759
|
}
|
|
1909
1760
|
};
|
|
1910
1761
|
}
|
|
1911
|
-
/**
|
|
1912
|
-
* Start a tunnel based on configuration.
|
|
1913
|
-
*/
|
|
1914
1762
|
async function startTunnel(config) {
|
|
1915
1763
|
switch (config.provider) {
|
|
1916
1764
|
case "ngrok": return startNgrokTunnel({
|
|
@@ -1919,15 +1767,9 @@ async function startTunnel(config) {
|
|
|
1919
1767
|
authToken: config.ngrokAuthToken,
|
|
1920
1768
|
domain: config.ngrokDomain
|
|
1921
1769
|
});
|
|
1922
|
-
case "tailscale-serve":
|
|
1923
|
-
mode: "serve",
|
|
1924
|
-
port: config.port,
|
|
1925
|
-
tailscalePort: config.tailscalePort ?? 443,
|
|
1926
|
-
path: config.path,
|
|
1927
|
-
streamPaths: config.streamPaths
|
|
1928
|
-
});
|
|
1770
|
+
case "tailscale-serve":
|
|
1929
1771
|
case "tailscale-funnel": return startTailscaleTunnel({
|
|
1930
|
-
mode: "funnel",
|
|
1772
|
+
mode: config.provider === "tailscale-serve" ? "serve" : "funnel",
|
|
1931
1773
|
port: config.port,
|
|
1932
1774
|
tailscalePort: config.tailscalePort ?? 443,
|
|
1933
1775
|
path: config.path,
|
|
@@ -1942,14 +1784,11 @@ async function startTunnel(config) {
|
|
|
1942
1784
|
function providerRequiresPublicWebhook(providerName) {
|
|
1943
1785
|
return providerName === "twilio" || providerName === "telnyx" || providerName === "plivo";
|
|
1944
1786
|
}
|
|
1945
|
-
/** Return true for localhost, private, or otherwise provider-unreachable hosts. */
|
|
1946
|
-
function isLocalOnlyWebhookHost(hostname) {
|
|
1947
|
-
return isBlockedHostnameOrIp(hostname);
|
|
1948
|
-
}
|
|
1949
1787
|
/** Return true when a webhook URL parses to a local/private host. */
|
|
1950
1788
|
function isProviderUnreachableWebhookUrl(webhookUrl) {
|
|
1951
1789
|
try {
|
|
1952
|
-
|
|
1790
|
+
const parsed = new URL(webhookUrl);
|
|
1791
|
+
return isBlockedHostnameOrIp(parsed.hostname);
|
|
1953
1792
|
} catch {
|
|
1954
1793
|
return false;
|
|
1955
1794
|
}
|
|
@@ -2422,54 +2261,21 @@ Content-Length: ${Buffer.byteLength(body)}\r\n\r
|
|
|
2422
2261
|
* Send a message to a stream's WebSocket if available.
|
|
2423
2262
|
*/
|
|
2424
2263
|
sendToStream(streamSid, message) {
|
|
2425
|
-
const session = this.
|
|
2426
|
-
if (!session) return
|
|
2427
|
-
|
|
2428
|
-
bufferedBeforeBytes: 0,
|
|
2429
|
-
bufferedAfterBytes: 0
|
|
2430
|
-
};
|
|
2431
|
-
const readyState = session.ws.readyState;
|
|
2432
|
-
const bufferedBeforeBytes = session.ws.bufferedAmount;
|
|
2433
|
-
if (readyState !== WebSocket.OPEN) return {
|
|
2434
|
-
sent: false,
|
|
2435
|
-
readyState,
|
|
2436
|
-
bufferedBeforeBytes,
|
|
2437
|
-
bufferedAfterBytes: session.ws.bufferedAmount
|
|
2438
|
-
};
|
|
2439
|
-
if (bufferedBeforeBytes > MAX_WS_BUFFERED_BYTES) {
|
|
2264
|
+
const session = this.getOpenSession(streamSid);
|
|
2265
|
+
if (!session) return false;
|
|
2266
|
+
if (session.ws.bufferedAmount > MAX_WS_BUFFERED_BYTES) {
|
|
2440
2267
|
session.ws.close(1013, "Backpressure: send buffer exceeded");
|
|
2441
|
-
return
|
|
2442
|
-
sent: false,
|
|
2443
|
-
readyState,
|
|
2444
|
-
bufferedBeforeBytes,
|
|
2445
|
-
bufferedAfterBytes: session.ws.bufferedAmount
|
|
2446
|
-
};
|
|
2268
|
+
return false;
|
|
2447
2269
|
}
|
|
2448
2270
|
try {
|
|
2449
2271
|
session.ws.send(JSON.stringify(message));
|
|
2450
|
-
|
|
2451
|
-
if (bufferedAfterBytes > MAX_WS_BUFFERED_BYTES) {
|
|
2272
|
+
if (session.ws.bufferedAmount > MAX_WS_BUFFERED_BYTES) {
|
|
2452
2273
|
session.ws.close(1013, "Backpressure: send buffer exceeded");
|
|
2453
|
-
return
|
|
2454
|
-
sent: false,
|
|
2455
|
-
readyState,
|
|
2456
|
-
bufferedBeforeBytes,
|
|
2457
|
-
bufferedAfterBytes
|
|
2458
|
-
};
|
|
2274
|
+
return false;
|
|
2459
2275
|
}
|
|
2460
|
-
return
|
|
2461
|
-
sent: true,
|
|
2462
|
-
readyState,
|
|
2463
|
-
bufferedBeforeBytes,
|
|
2464
|
-
bufferedAfterBytes
|
|
2465
|
-
};
|
|
2276
|
+
return true;
|
|
2466
2277
|
} catch {
|
|
2467
|
-
return
|
|
2468
|
-
sent: false,
|
|
2469
|
-
readyState,
|
|
2470
|
-
bufferedBeforeBytes,
|
|
2471
|
-
bufferedAfterBytes: session.ws.bufferedAmount
|
|
2472
|
-
};
|
|
2278
|
+
return false;
|
|
2473
2279
|
}
|
|
2474
2280
|
}
|
|
2475
2281
|
/**
|
|
@@ -2533,7 +2339,7 @@ Content-Length: ${Buffer.byteLength(body)}\r\n\r
|
|
|
2533
2339
|
marks.set(name, pending);
|
|
2534
2340
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
2535
2341
|
});
|
|
2536
|
-
if (!this.sendMark(streamSid, name)
|
|
2342
|
+
if (!this.sendMark(streamSid, name)) pending.settle(/* @__PURE__ */ new Error("Telephony stream playback failed: completion mark not delivered"));
|
|
2537
2343
|
return acknowledgement;
|
|
2538
2344
|
}
|
|
2539
2345
|
/**
|
|
@@ -2740,8 +2546,7 @@ const TERMINAL_PROVIDER_STATUS_TO_END_REASON = {
|
|
|
2740
2546
|
};
|
|
2741
2547
|
/** Normalize provider status text, falling back to "unknown". */
|
|
2742
2548
|
function normalizeProviderStatus(status) {
|
|
2743
|
-
|
|
2744
|
-
return normalized && normalized.length > 0 ? normalized : "unknown";
|
|
2549
|
+
return normalizeOptionalLowercaseString(status) ?? "unknown";
|
|
2745
2550
|
}
|
|
2746
2551
|
/** Map terminal provider status strings to OpenClaw end reasons. */
|
|
2747
2552
|
function mapProviderStatusToEndReason(status) {
|
|
@@ -2845,7 +2650,7 @@ const MAX_WEBHOOK_BODY_BYTES = WEBHOOK_BODY_READ_DEFAULTS.preAuth.maxBytes;
|
|
|
2845
2650
|
const WEBHOOK_BODY_TIMEOUT_MS = WEBHOOK_BODY_READ_DEFAULTS.preAuth.timeoutMs;
|
|
2846
2651
|
const MISSING_REMOTE_ADDRESS_IN_FLIGHT_KEY = "__voice_call_no_remote__";
|
|
2847
2652
|
const loadRealtimeTranscriptionRuntime = createLazyRuntimeModule(() => import("./realtime-transcription.runtime-CbJAs5t_.mjs"));
|
|
2848
|
-
const loadResponseGeneratorModule = createLazyRuntimeModule(() => import("./response-generator-
|
|
2653
|
+
const loadResponseGeneratorModule = createLazyRuntimeModule(() => import("./response-generator-BzKrJMEb.mjs"));
|
|
2849
2654
|
function appendRecentTalkEventMetadata(metadata, event) {
|
|
2850
2655
|
const previous = metadata ?? {};
|
|
2851
2656
|
const recent = Array.isArray(previous.recentTalkEvents) ? previous.recentTalkEvents.filter((entry) => Boolean(entry) && typeof entry === "object" && !Array.isArray(entry)) : [];
|
|
@@ -2960,9 +2765,6 @@ var VoiceCallWebhookServer = class {
|
|
|
2960
2765
|
});
|
|
2961
2766
|
});
|
|
2962
2767
|
}
|
|
2963
|
-
/**
|
|
2964
|
-
* Get the media stream handler (for wiring to provider).
|
|
2965
|
-
*/
|
|
2966
2768
|
getMediaStreamHandler() {
|
|
2967
2769
|
return this.mediaStreamHandler;
|
|
2968
2770
|
}
|
|
@@ -3023,7 +2825,7 @@ var VoiceCallWebhookServer = class {
|
|
|
3023
2825
|
*/
|
|
3024
2826
|
async initializeMediaStreaming() {
|
|
3025
2827
|
const streaming = this.config.streaming;
|
|
3026
|
-
const pluginConfig = this.fullConfig ?? this.coreConfig;
|
|
2828
|
+
const pluginConfig = this.fullConfig ?? this.coreConfig ?? void 0;
|
|
3027
2829
|
const { getRealtimeTranscriptionProvider, listRealtimeTranscriptionProviders } = await loadRealtimeTranscriptionRuntime();
|
|
3028
2830
|
const resolution = resolveConfiguredCapabilityProvider({
|
|
3029
2831
|
configuredProviderId: streaming.provider,
|
|
@@ -3056,7 +2858,7 @@ var VoiceCallWebhookServer = class {
|
|
|
3056
2858
|
const streamConfig = {
|
|
3057
2859
|
transcriptionProvider: resolution.provider,
|
|
3058
2860
|
providerConfig: resolution.providerConfig,
|
|
3059
|
-
cfg:
|
|
2861
|
+
cfg: pluginConfig,
|
|
3060
2862
|
preStartTimeoutMs: streaming.preStartTimeoutMs,
|
|
3061
2863
|
maxPendingConnections: streaming.maxPendingConnections,
|
|
3062
2864
|
maxPendingConnectionsPerIp: streaming.maxPendingConnectionsPerIp,
|
|
@@ -3162,7 +2964,7 @@ var VoiceCallWebhookServer = class {
|
|
|
3162
2964
|
this.realtimeHandler.handleWebSocketUpgrade(request, socket, head);
|
|
3163
2965
|
return;
|
|
3164
2966
|
}
|
|
3165
|
-
if (this.getUpgradePathname(request) === streamPath && this.mediaStreamHandler) this.mediaStreamHandler
|
|
2967
|
+
if (this.getUpgradePathname(request) === streamPath && this.mediaStreamHandler) this.mediaStreamHandler.handleUpgrade(request, socket, head);
|
|
3166
2968
|
else {
|
|
3167
2969
|
socket.once("error", () => {});
|
|
3168
2970
|
rejectWebSocketUpgrade(socket, { status: 404 });
|
|
@@ -3193,9 +2995,6 @@ var VoiceCallWebhookServer = class {
|
|
|
3193
2995
|
});
|
|
3194
2996
|
return this.startPromise;
|
|
3195
2997
|
}
|
|
3196
|
-
/**
|
|
3197
|
-
* Stop the webhook server.
|
|
3198
|
-
*/
|
|
3199
2998
|
stop() {
|
|
3200
2999
|
if (this.stopPromise) return this.stopPromise;
|
|
3201
3000
|
const server = this.server;
|
|
@@ -3253,9 +3052,6 @@ var VoiceCallWebhookServer = class {
|
|
|
3253
3052
|
isWebhookPathMatch(requestPath, configuredPath) {
|
|
3254
3053
|
return normalizeWebhookPath(requestPath) === normalizeWebhookPath(configuredPath);
|
|
3255
3054
|
}
|
|
3256
|
-
/**
|
|
3257
|
-
* Handle incoming HTTP request.
|
|
3258
|
-
*/
|
|
3259
3055
|
async handleRequest(req, res, webhookPath) {
|
|
3260
3056
|
const payload = await this.runWebhookPipeline(req, webhookPath, res);
|
|
3261
3057
|
if (payload) this.writeWebhookResponse(res, payload);
|
|
@@ -3300,7 +3096,11 @@ var VoiceCallWebhookServer = class {
|
|
|
3300
3096
|
try {
|
|
3301
3097
|
let body = "";
|
|
3302
3098
|
try {
|
|
3303
|
-
body = await
|
|
3099
|
+
body = await readRequestBodyWithLimit(req, {
|
|
3100
|
+
maxBytes: MAX_WEBHOOK_BODY_BYTES,
|
|
3101
|
+
timeoutMs: WEBHOOK_BODY_TIMEOUT_MS,
|
|
3102
|
+
destroyOnLimit: false
|
|
3103
|
+
});
|
|
3304
3104
|
} catch (err) {
|
|
3305
3105
|
if (isRequestBodyLimitError(err, "PAYLOAD_TOO_LARGE")) {
|
|
3306
3106
|
await sendHttpRequestRejection(req, res, 413, "Payload Too Large");
|
|
@@ -3505,16 +3305,6 @@ var VoiceCallWebhookServer = class {
|
|
|
3505
3305
|
res.end(payload.body);
|
|
3506
3306
|
}
|
|
3507
3307
|
/**
|
|
3508
|
-
* Read request body as string with timeout protection.
|
|
3509
|
-
*/
|
|
3510
|
-
readBody(req, maxBytes, timeoutMs) {
|
|
3511
|
-
return readRequestBodyWithLimit(req, {
|
|
3512
|
-
maxBytes,
|
|
3513
|
-
timeoutMs,
|
|
3514
|
-
destroyOnLimit: false
|
|
3515
|
-
});
|
|
3516
|
-
}
|
|
3517
|
-
/**
|
|
3518
3308
|
* Handle auto-response for inbound calls using the agent system.
|
|
3519
3309
|
* Supports tool calling for richer voice interactions.
|
|
3520
3310
|
*/
|
|
@@ -3585,21 +3375,12 @@ const REALTIME_VOICE_CONSULT_SYSTEM_PROMPT = [
|
|
|
3585
3375
|
"Do not print secret values or dump environment variables; only check whether required configuration is present.",
|
|
3586
3376
|
"Be accurate, brief, and speakable."
|
|
3587
3377
|
].join(" ");
|
|
3588
|
-
const loadTelnyxProvider = createLazyRuntimeModule(() => import("./telnyx-
|
|
3589
|
-
const loadTwilioProvider = createLazyRuntimeModule(() => import("./twilio-
|
|
3590
|
-
const loadPlivoProvider = createLazyRuntimeModule(() => import("./plivo-
|
|
3591
|
-
const loadMockProvider = createLazyRuntimeModule(() => import("./mock-
|
|
3378
|
+
const loadTelnyxProvider = createLazyRuntimeModule(() => import("./telnyx-DjFGCfy1.mjs"));
|
|
3379
|
+
const loadTwilioProvider = createLazyRuntimeModule(() => import("./twilio-Bp3gfmUB.mjs"));
|
|
3380
|
+
const loadPlivoProvider = createLazyRuntimeModule(() => import("./plivo-69ObrQPk.mjs"));
|
|
3381
|
+
const loadMockProvider = createLazyRuntimeModule(() => import("./mock-ClWW6Ufu.mjs"));
|
|
3592
3382
|
const loadRealtimeVoiceRuntime = createLazyRuntimeModule(() => import("./realtime-voice.runtime-vtdCOWg-.mjs"));
|
|
3593
|
-
const loadRealtimeHandler = createLazyRuntimeModule(() => import("./realtime-handler-
|
|
3594
|
-
function resolveVoiceCallConsultSessionKey(call) {
|
|
3595
|
-
return resolveVoiceCallSessionKey({
|
|
3596
|
-
config: call.config,
|
|
3597
|
-
callId: call.callId,
|
|
3598
|
-
phone: call.direction === "outbound" ? call.to : call.from,
|
|
3599
|
-
explicitSessionKey: call.sessionKey,
|
|
3600
|
-
coreSession: call.coreSession
|
|
3601
|
-
});
|
|
3602
|
-
}
|
|
3383
|
+
const loadRealtimeHandler = createLazyRuntimeModule(() => import("./realtime-handler-BgDPcPZt.mjs"));
|
|
3603
3384
|
function mapVoiceCallConsultTranscript(call, context) {
|
|
3604
3385
|
const transcript = (call.transcript ?? []).map((entry) => ({
|
|
3605
3386
|
role: entry.speaker === "bot" ? "assistant" : "user",
|
|
@@ -3707,7 +3488,7 @@ async function createRealtimeInstructionsResolver(params) {
|
|
|
3707
3488
|
baseInstructions: params.config.realtime.instructions,
|
|
3708
3489
|
config: genericConfig,
|
|
3709
3490
|
coreConfig: params.coreConfig,
|
|
3710
|
-
|
|
3491
|
+
warn: params.warn,
|
|
3711
3492
|
agentId: params.config.agentId
|
|
3712
3493
|
});
|
|
3713
3494
|
const entries = await Promise.all(listRealtimeAgentIds(params.config, params.coreConfig).map(async (agentId) => {
|
|
@@ -3718,7 +3499,7 @@ async function createRealtimeInstructionsResolver(params) {
|
|
|
3718
3499
|
agentId
|
|
3719
3500
|
},
|
|
3720
3501
|
coreConfig: params.coreConfig,
|
|
3721
|
-
|
|
3502
|
+
warn: params.warn,
|
|
3722
3503
|
agentId
|
|
3723
3504
|
})];
|
|
3724
3505
|
}));
|
|
@@ -3751,13 +3532,13 @@ async function createVoiceCallRuntime(params) {
|
|
|
3751
3532
|
if (stateRuntime) setVoiceCallStateRuntime({ state: stateRuntime });
|
|
3752
3533
|
const manager = new CallManager(config, void 0, cfg.session, stateRuntime);
|
|
3753
3534
|
const realtimeVoiceRuntime = config.realtime.enabled ? await loadRealtimeVoiceRuntime() : null;
|
|
3754
|
-
const webhookServer = new VoiceCallWebhookServer(config, manager, provider, coreConfig,
|
|
3535
|
+
const webhookServer = new VoiceCallWebhookServer(config, manager, provider, coreConfig, cfg, agentRuntime, log);
|
|
3755
3536
|
if (realtimeVoiceRuntime) {
|
|
3756
3537
|
const { RealtimeCallHandler } = await loadRealtimeHandler();
|
|
3757
3538
|
const resolveRealtimeInstructions = await createRealtimeInstructionsResolver({
|
|
3758
3539
|
config,
|
|
3759
3540
|
coreConfig: cfg,
|
|
3760
|
-
|
|
3541
|
+
warn: (message) => log.warn(`[voice-call] ${message}`)
|
|
3761
3542
|
});
|
|
3762
3543
|
const realtimeConfig = {
|
|
3763
3544
|
...config.realtime,
|
|
@@ -3791,12 +3572,14 @@ async function createVoiceCallRuntime(params) {
|
|
|
3791
3572
|
const numberRouteKey = resolveVoiceCallNumberRouteKeyForCall(call);
|
|
3792
3573
|
const effectiveConfig = resolveVoiceCallEffectiveConfig(config, numberRouteKey).config;
|
|
3793
3574
|
const agentId = resolveCallAgentId(call, effectiveConfig);
|
|
3794
|
-
const sessionKey =
|
|
3795
|
-
...call,
|
|
3575
|
+
const sessionKey = resolveVoiceCallSessionKey({
|
|
3796
3576
|
config: {
|
|
3797
3577
|
...effectiveConfig,
|
|
3798
3578
|
agentId
|
|
3799
3579
|
},
|
|
3580
|
+
callId: call.callId,
|
|
3581
|
+
phone: call.direction === "outbound" ? call.to : call.from,
|
|
3582
|
+
explicitSessionKey: call.sessionKey,
|
|
3800
3583
|
coreSession: cfg.session
|
|
3801
3584
|
});
|
|
3802
3585
|
const requesterSessionKey = typeof call.metadata?.requesterSessionKey === "string" ? call.metadata.requesterSessionKey : void 0;
|
|
@@ -3808,13 +3591,17 @@ async function createVoiceCallRuntime(params) {
|
|
|
3808
3591
|
spawnedBy: requesterSessionKey
|
|
3809
3592
|
};
|
|
3810
3593
|
assertRealtimeVoiceAgentConsultModelSelectionUnlocked(modelLockParams);
|
|
3811
|
-
const fastContext = await
|
|
3594
|
+
const fastContext = await resolveRealtimeVoiceFastContextConsult({
|
|
3812
3595
|
cfg,
|
|
3813
3596
|
agentId,
|
|
3814
3597
|
sessionKey,
|
|
3815
3598
|
config: effectiveConfig.realtime.fastContext,
|
|
3816
3599
|
args,
|
|
3817
|
-
logger: log
|
|
3600
|
+
logger: log,
|
|
3601
|
+
labels: {
|
|
3602
|
+
audienceLabel: "caller",
|
|
3603
|
+
contextName: "OpenClaw memory or session context"
|
|
3604
|
+
}
|
|
3818
3605
|
});
|
|
3819
3606
|
handlerContext.abortSignal?.throwIfAborted();
|
|
3820
3607
|
if (fastContext.handled) {
|
|
@@ -3915,10 +3702,8 @@ async function createVoiceCallRuntime(params) {
|
|
|
3915
3702
|
}
|
|
3916
3703
|
if (realtimeVoiceRuntime) log.info("[voice-call] Realtime voice enabled");
|
|
3917
3704
|
await manager.initialize(provider, webhookUrl);
|
|
3918
|
-
const stop = () => lifecycle.stop();
|
|
3919
3705
|
log.info("[voice-call] Runtime initialized");
|
|
3920
3706
|
log.info(`[voice-call] Webhook URL: ${webhookUrl}`);
|
|
3921
|
-
if (publicUrl && publicUrl !== webhookUrl) log.info(`[voice-call] Public URL: ${publicUrl}`);
|
|
3922
3707
|
return {
|
|
3923
3708
|
config,
|
|
3924
3709
|
provider,
|
|
@@ -3926,7 +3711,7 @@ async function createVoiceCallRuntime(params) {
|
|
|
3926
3711
|
webhookServer,
|
|
3927
3712
|
webhookUrl,
|
|
3928
3713
|
publicUrl,
|
|
3929
|
-
stop
|
|
3714
|
+
stop: () => lifecycle.stop()
|
|
3930
3715
|
};
|
|
3931
3716
|
} catch (err) {
|
|
3932
3717
|
await lifecycle.stop({ suppressErrors: true });
|
|
@@ -3934,4 +3719,4 @@ async function createVoiceCallRuntime(params) {
|
|
|
3934
3719
|
}
|
|
3935
3720
|
}
|
|
3936
3721
|
//#endregion
|
|
3937
|
-
export { resolveCallAgentId as _, normalizeProviderStatus as a,
|
|
3722
|
+
export { resolveCallAgentId as _, normalizeProviderStatus as a, mapVoiceToPolly as b, canonicalizeVoiceCallMediaBase64 as c, cleanupTailscaleExposureRoute as d, getTailscaleSelfInfo as f, resolveVoiceResponseModel as g, chunkAudio as h, mapProviderStatusToEndReason as i, getHeader as l, TELEPHONY_DEFAULT_TTS_TIMEOUT_MS as m, normalizeProxyIp as n, WebSocket as o, setupTailscaleExposureRoutes as p, isProviderStatusTerminal as r, WebSocketServer as s, createVoiceCallRuntime as t, resolveWebhookExposureStatus as u, resolveVoiceCallAgentId as v, escapeXml as y };
|