@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,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { c as verifyTelnyxWebhook, n as readProviderCallStatus, t as guardedJsonApiRequest } from "./guarded-json-api-Bqc6QS8A.mjs";
|
|
2
2
|
import crypto from "node:crypto";
|
|
3
3
|
//#region extensions/voice-call/src/providers/telnyx.ts
|
|
4
4
|
function normalizeTelnyxDirection(direction) {
|
|
@@ -30,9 +30,6 @@ var TelnyxProvider = class {
|
|
|
30
30
|
this.publicKey = config.publicKey;
|
|
31
31
|
this.options = options;
|
|
32
32
|
}
|
|
33
|
-
/**
|
|
34
|
-
* Make an authenticated request to the Telnyx API.
|
|
35
|
-
*/
|
|
36
33
|
async apiRequest(endpoint, body, options) {
|
|
37
34
|
return await guardedJsonApiRequest({
|
|
38
35
|
url: `${this.baseUrl}${endpoint}`,
|
|
@@ -48,9 +45,6 @@ var TelnyxProvider = class {
|
|
|
48
45
|
errorPrefix: "Telnyx API error"
|
|
49
46
|
});
|
|
50
47
|
}
|
|
51
|
-
/**
|
|
52
|
-
* Verify Telnyx webhook signature using Ed25519.
|
|
53
|
-
*/
|
|
54
48
|
verifyWebhook(ctx) {
|
|
55
49
|
const result = verifyTelnyxWebhook(ctx, this.publicKey, { skipVerification: this.options.skipVerification });
|
|
56
50
|
return {
|
|
@@ -61,9 +55,6 @@ var TelnyxProvider = class {
|
|
|
61
55
|
releaseReplay: result.releaseReplay
|
|
62
56
|
};
|
|
63
57
|
}
|
|
64
|
-
/**
|
|
65
|
-
* Parse Telnyx webhook event into normalized format.
|
|
66
|
-
*/
|
|
67
58
|
parseWebhookEvent(ctx, options) {
|
|
68
59
|
try {
|
|
69
60
|
const data = JSON.parse(ctx.rawBody).data;
|
|
@@ -83,9 +74,6 @@ var TelnyxProvider = class {
|
|
|
83
74
|
};
|
|
84
75
|
}
|
|
85
76
|
}
|
|
86
|
-
/**
|
|
87
|
-
* Convert Telnyx event to normalized event format.
|
|
88
|
-
*/
|
|
89
77
|
normalizeEvent(data, dedupeKey) {
|
|
90
78
|
let callId = "";
|
|
91
79
|
if (data.payload?.client_state) callId = decodeClientStateBase64(data.payload.client_state) ?? data.payload.client_state;
|
|
@@ -190,9 +178,6 @@ var TelnyxProvider = class {
|
|
|
190
178
|
status: "initiated"
|
|
191
179
|
};
|
|
192
180
|
}
|
|
193
|
-
/**
|
|
194
|
-
* Hang up a call via Telnyx API.
|
|
195
|
-
*/
|
|
196
181
|
async hangupCall(input) {
|
|
197
182
|
await this.apiRequest(`/calls/${input.providerCallId}/actions/hangup`, { command_id: crypto.randomUUID() }, { allowNotFound: true });
|
|
198
183
|
}
|
|
@@ -203,9 +188,6 @@ var TelnyxProvider = class {
|
|
|
203
188
|
};
|
|
204
189
|
await this.apiRequest(`/calls/${input.providerCallId}/actions/answer`, body);
|
|
205
190
|
}
|
|
206
|
-
/**
|
|
207
|
-
* Play TTS audio via Telnyx speak action.
|
|
208
|
-
*/
|
|
209
191
|
async playTts(input) {
|
|
210
192
|
await this.apiRequest(`/calls/${input.providerCallId}/actions/speak`, {
|
|
211
193
|
command_id: crypto.randomUUID(),
|
|
@@ -214,57 +196,39 @@ var TelnyxProvider = class {
|
|
|
214
196
|
language: input.locale || "en-US"
|
|
215
197
|
});
|
|
216
198
|
}
|
|
217
|
-
/**
|
|
218
|
-
* Start transcription (STT) via Telnyx.
|
|
219
|
-
*/
|
|
220
199
|
async startListening(input) {
|
|
221
200
|
await this.apiRequest(`/calls/${input.providerCallId}/actions/transcription_start`, {
|
|
222
201
|
command_id: crypto.randomUUID(),
|
|
223
202
|
language: input.language || "en"
|
|
224
203
|
});
|
|
225
204
|
}
|
|
226
|
-
/**
|
|
227
|
-
* Stop transcription via Telnyx.
|
|
228
|
-
*/
|
|
229
205
|
async stopListening(input) {
|
|
230
206
|
await this.apiRequest(`/calls/${input.providerCallId}/actions/transcription_stop`, { command_id: crypto.randomUUID() }, { allowNotFound: true });
|
|
231
207
|
}
|
|
232
208
|
async getCallStatus(input) {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
if (!data) return {
|
|
247
|
-
status: "not-found",
|
|
248
|
-
isTerminal: true
|
|
249
|
-
};
|
|
250
|
-
const state = data.data?.state ?? "unknown";
|
|
209
|
+
return readProviderCallStatus(() => guardedJsonApiRequest({
|
|
210
|
+
url: `${this.baseUrl}/calls/${input.providerCallId}`,
|
|
211
|
+
method: "GET",
|
|
212
|
+
headers: {
|
|
213
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
214
|
+
"Content-Type": "application/json"
|
|
215
|
+
},
|
|
216
|
+
allowNotFound: true,
|
|
217
|
+
allowedHostnames: [this.apiHost],
|
|
218
|
+
auditContext: "telnyx-get-call-status",
|
|
219
|
+
errorPrefix: "Telnyx get call status error"
|
|
220
|
+
}), (data) => {
|
|
221
|
+
const status = data.data?.state ?? "unknown";
|
|
251
222
|
const isAlive = data.data?.is_alive;
|
|
252
|
-
|
|
253
|
-
status
|
|
223
|
+
return isAlive === void 0 ? {
|
|
224
|
+
status,
|
|
254
225
|
isTerminal: false,
|
|
255
226
|
isUnknown: true
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
status: state,
|
|
227
|
+
} : {
|
|
228
|
+
status,
|
|
259
229
|
isTerminal: !isAlive
|
|
260
230
|
};
|
|
261
|
-
}
|
|
262
|
-
return {
|
|
263
|
-
status: "error",
|
|
264
|
-
isTerminal: false,
|
|
265
|
-
isUnknown: true
|
|
266
|
-
};
|
|
267
|
-
}
|
|
231
|
+
});
|
|
268
232
|
}
|
|
269
233
|
};
|
|
270
234
|
function buildTelnyxStreamingFields(streamUrl, streamAuthToken) {
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { fetchWithSsrFGuard } from "../
|
|
2
|
-
import "../api.js";
|
|
1
|
+
import { fetchWithSsrFGuard } from "../api.js";
|
|
3
2
|
import { n as requireSupportedTwilioApiHostname, r as resolveTwilioApiBaseUrl } from "./twilio-region-4fkgz3UG.mjs";
|
|
4
|
-
import { a as normalizeProviderStatus, b as
|
|
5
|
-
import {
|
|
3
|
+
import { a as normalizeProviderStatus, b as mapVoiceToPolly, h as chunkAudio, i as mapProviderStatusToEndReason, l as getHeader, r as isProviderStatusTerminal, y as escapeXml } from "./runtime-entry-BNOQRXii.mjs";
|
|
4
|
+
import { a as readVoiceCallProviderJsonResponse, i as readProviderErrorResponseSnippet, l as verifyTwilioWebhook, n as readProviderCallStatus, r as cancelProviderResponseBody, t as guardedJsonApiRequest } from "./guarded-json-api-Bqc6QS8A.mjs";
|
|
6
5
|
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
7
6
|
import { sleepWithAbort } from "openclaw/plugin-sdk/runtime-env";
|
|
8
7
|
import crypto from "node:crypto";
|
|
@@ -75,10 +74,6 @@ async function twilioApiRequest(params) {
|
|
|
75
74
|
}
|
|
76
75
|
//#endregion
|
|
77
76
|
//#region extensions/voice-call/src/providers/twilio/twiml-policy.ts
|
|
78
|
-
/** Return true for Twilio outbound call directions. */
|
|
79
|
-
function isOutboundDirection(direction) {
|
|
80
|
-
return direction?.startsWith("outbound") ?? false;
|
|
81
|
-
}
|
|
82
77
|
/** Read the Twilio request fields needed by TwiML decision logic. */
|
|
83
78
|
function readTwimlRequestView(ctx) {
|
|
84
79
|
const params = new URLSearchParams(ctx.rawBody);
|
|
@@ -95,23 +90,17 @@ function readTwimlRequestView(ctx) {
|
|
|
95
90
|
/** Decide the TwiML response kind for a Twilio webhook request. */
|
|
96
91
|
function decideTwimlResponse(input) {
|
|
97
92
|
if (input.callIdFromQuery && !input.isStatusCallback) {
|
|
98
|
-
if (input.hasStoredTwiml) return
|
|
99
|
-
|
|
100
|
-
consumeStoredTwimlCallId: input.callIdFromQuery
|
|
101
|
-
};
|
|
102
|
-
if (isOutboundDirection(input.direction)) return input.canStream ? { kind: "stream" } : { kind: "pause" };
|
|
93
|
+
if (input.hasStoredTwiml) return "stored";
|
|
94
|
+
if (input.direction?.startsWith("outbound")) return input.canStream ? "stream" : "pause";
|
|
103
95
|
}
|
|
104
|
-
if (input.isStatusCallback) return
|
|
96
|
+
if (input.isStatusCallback) return "empty";
|
|
105
97
|
if (input.direction === "inbound") {
|
|
106
|
-
if (input.hasActiveStreams) return
|
|
107
|
-
if (input.canStream && input.callSid) return
|
|
108
|
-
|
|
109
|
-
activateStreamCallSid: input.callSid
|
|
110
|
-
};
|
|
111
|
-
return { kind: "pause" };
|
|
98
|
+
if (input.hasActiveStreams) return "queue";
|
|
99
|
+
if (input.canStream && input.callSid) return "stream";
|
|
100
|
+
return "pause";
|
|
112
101
|
}
|
|
113
|
-
if (input.callStatus !== "in-progress") return
|
|
114
|
-
return input.canStream ?
|
|
102
|
+
if (input.callStatus !== "in-progress") return "empty";
|
|
103
|
+
return input.canStream ? "stream" : "pause";
|
|
115
104
|
}
|
|
116
105
|
//#endregion
|
|
117
106
|
//#region extensions/voice-call/src/providers/twilio/webhook.ts
|
|
@@ -155,15 +144,6 @@ function createTwilioRequestDedupeKey(ctx, verifiedRequestKey) {
|
|
|
155
144
|
return `twilio:fallback:${crypto.createHash("sha256").update(`${signature}\n${callSid}\n${callStatus}\n${direction}\n${callId}\n${flow}\n${turnToken}\n${ctx.rawBody}`).digest("hex")}`;
|
|
156
145
|
}
|
|
157
146
|
var TwilioProvider = class TwilioProvider {
|
|
158
|
-
/**
|
|
159
|
-
* Delete stored TwiML for a given `callId`.
|
|
160
|
-
*
|
|
161
|
-
* We keep TwiML in-memory only long enough to satisfy the initial Twilio
|
|
162
|
-
* webhook request before streaming. Subsequent webhooks should not reuse it.
|
|
163
|
-
*/
|
|
164
|
-
deleteStoredTwiml(callId) {
|
|
165
|
-
this.twimlStorage.delete(callId);
|
|
166
|
-
}
|
|
167
147
|
/**
|
|
168
148
|
* Release all process-local metadata owned by one Twilio call.
|
|
169
149
|
* Terminal webhooks can be replayed, so this must stay idempotent.
|
|
@@ -174,7 +154,7 @@ var TwilioProvider = class TwilioProvider {
|
|
|
174
154
|
if (!resolvedCallId && webhookUrl) try {
|
|
175
155
|
resolvedCallId = new URL(webhookUrl).searchParams.get("callId") || void 0;
|
|
176
156
|
} catch {}
|
|
177
|
-
if (resolvedCallId) this.
|
|
157
|
+
if (resolvedCallId) this.twimlStorage.delete(resolvedCallId);
|
|
178
158
|
this.callWebhookUrls.delete(providerCallId);
|
|
179
159
|
this.callStreamMap.delete(providerCallId);
|
|
180
160
|
this.streamAuthTokens.delete(providerCallId);
|
|
@@ -246,9 +226,6 @@ var TwilioProvider = class TwilioProvider {
|
|
|
246
226
|
if (!streamSid || !this.mediaStreamHandler) return;
|
|
247
227
|
this.mediaStreamHandler.clearTtsQueue(streamSid, reason);
|
|
248
228
|
}
|
|
249
|
-
/**
|
|
250
|
-
* Make an authenticated request to the Twilio API.
|
|
251
|
-
*/
|
|
252
229
|
async apiRequest(endpoint, params, options) {
|
|
253
230
|
return await twilioApiRequest({
|
|
254
231
|
baseUrl: this.baseUrl,
|
|
@@ -287,9 +264,6 @@ var TwilioProvider = class TwilioProvider {
|
|
|
287
264
|
options: this.options
|
|
288
265
|
});
|
|
289
266
|
}
|
|
290
|
-
/**
|
|
291
|
-
* Parse Twilio webhook event into normalized format.
|
|
292
|
-
*/
|
|
293
267
|
parseWebhookEvent(ctx, options) {
|
|
294
268
|
try {
|
|
295
269
|
const params = new URLSearchParams(ctx.rawBody);
|
|
@@ -316,9 +290,6 @@ var TwilioProvider = class TwilioProvider {
|
|
|
316
290
|
};
|
|
317
291
|
}
|
|
318
292
|
}
|
|
319
|
-
/**
|
|
320
|
-
* Parse Twilio direction to normalized format.
|
|
321
|
-
*/
|
|
322
293
|
static parseDirection(direction) {
|
|
323
294
|
if (direction === "inbound") return "inbound";
|
|
324
295
|
if (direction === "outbound-api" || direction === "outbound-dial") return "outbound";
|
|
@@ -328,9 +299,6 @@ var TwilioProvider = class TwilioProvider {
|
|
|
328
299
|
if (!trimmed || !/^\d+(?:\.\d+)?$/.test(trimmed)) return .9;
|
|
329
300
|
return Number(trimmed);
|
|
330
301
|
}
|
|
331
|
-
/**
|
|
332
|
-
* Convert Twilio webhook params to normalized event format.
|
|
333
|
-
*/
|
|
334
302
|
normalizeEvent(params, options) {
|
|
335
303
|
const callSid = params.get("CallSid") || "";
|
|
336
304
|
const callIdOverride = options?.callIdOverride;
|
|
@@ -408,15 +376,15 @@ var TwilioProvider = class TwilioProvider {
|
|
|
408
376
|
if (!ctx) return TwilioProvider.EMPTY_TWIML;
|
|
409
377
|
const view = readTwimlRequestView(ctx);
|
|
410
378
|
const storedTwiml = view.callIdFromQuery ? this.twimlStorage.get(view.callIdFromQuery) : void 0;
|
|
411
|
-
|
|
379
|
+
switch (decideTwimlResponse({
|
|
412
380
|
...view,
|
|
413
381
|
hasStoredTwiml: Boolean(storedTwiml),
|
|
414
382
|
hasActiveStreams: this.activeStreamCalls.size > 0,
|
|
415
383
|
canStream: Boolean(view.callSid && this.getStreamUrl())
|
|
416
|
-
})
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
384
|
+
})) {
|
|
385
|
+
case "stored":
|
|
386
|
+
if (view.callIdFromQuery) this.twimlStorage.delete(view.callIdFromQuery);
|
|
387
|
+
return storedTwiml ?? TwilioProvider.EMPTY_TWIML;
|
|
420
388
|
case "queue": return TwilioProvider.QUEUE_TWIML;
|
|
421
389
|
case "pause": return TwilioProvider.PAUSE_TWIML;
|
|
422
390
|
case "stream": {
|
|
@@ -431,7 +399,7 @@ var TwilioProvider = class TwilioProvider {
|
|
|
431
399
|
if (!view.callIdFromQuery || view.isStatusCallback) return null;
|
|
432
400
|
const storedTwiml = this.twimlStorage.get(view.callIdFromQuery);
|
|
433
401
|
if (!storedTwiml) return null;
|
|
434
|
-
this.
|
|
402
|
+
this.twimlStorage.delete(view.callIdFromQuery);
|
|
435
403
|
console.log(`[voice-call] Twilio initial TwiML consumed for call ${view.callIdFromQuery} (kind=pre-connect, callSid=${view.callSid ?? "unknown"})`);
|
|
436
404
|
return storedTwiml;
|
|
437
405
|
}
|
|
@@ -516,9 +484,6 @@ var TwilioProvider = class TwilioProvider {
|
|
|
516
484
|
status: result.status === "queued" ? "queued" : "initiated"
|
|
517
485
|
};
|
|
518
486
|
}
|
|
519
|
-
/**
|
|
520
|
-
* Hang up a call via Twilio API.
|
|
521
|
-
*/
|
|
522
487
|
async hangupCall(input) {
|
|
523
488
|
await this.apiRequest(`/Calls/${input.providerCallId}.json`, { Status: "completed" }, { allowNotFound: true });
|
|
524
489
|
this.releaseCallState(input.providerCallId, input.callId);
|
|
@@ -621,7 +586,7 @@ var TwilioProvider = class TwilioProvider {
|
|
|
621
586
|
for (const chunk of chunkAudio(muLawAudio, CHUNK_SIZE)) {
|
|
622
587
|
if (signal.aborted) break;
|
|
623
588
|
chunkAttempts += 1;
|
|
624
|
-
if (!handler.sendAudio(streamSid, chunk)
|
|
589
|
+
if (!handler.sendAudio(streamSid, chunk)) {
|
|
625
590
|
handler.clearAudio(streamSid);
|
|
626
591
|
throw new Error(`Telephony stream playback failed: audio chunk ${chunkAttempts} not delivered`);
|
|
627
592
|
}
|
|
@@ -642,9 +607,6 @@ var TwilioProvider = class TwilioProvider {
|
|
|
642
607
|
await handler.sendMarkAndWait(streamSid, markName, muLawAudio.length / 8, signal);
|
|
643
608
|
});
|
|
644
609
|
}
|
|
645
|
-
/**
|
|
646
|
-
* Start listening for speech via Twilio <Gather>.
|
|
647
|
-
*/
|
|
648
610
|
async startListening(input) {
|
|
649
611
|
const webhookUrl = this.callWebhookUrls.get(input.providerCallId);
|
|
650
612
|
if (!webhookUrl) throw new Error("Missing webhook URL for this call (provider state not initialized)");
|
|
@@ -662,32 +624,21 @@ var TwilioProvider = class TwilioProvider {
|
|
|
662
624
|
*/
|
|
663
625
|
async stopListening(_input) {}
|
|
664
626
|
async getCallStatus(input) {
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
});
|
|
675
|
-
if (!data) return {
|
|
676
|
-
status: "not-found",
|
|
677
|
-
isTerminal: true
|
|
678
|
-
};
|
|
627
|
+
return readProviderCallStatus(() => guardedJsonApiRequest({
|
|
628
|
+
url: `${this.baseUrl}/Calls/${input.providerCallId}.json`,
|
|
629
|
+
method: "GET",
|
|
630
|
+
headers: { Authorization: `Basic ${Buffer.from(`${this.accountSid}:${this.authToken}`).toString("base64")}` },
|
|
631
|
+
allowNotFound: true,
|
|
632
|
+
allowedHostnames: [new URL(this.baseUrl).hostname],
|
|
633
|
+
auditContext: "twilio-get-call-status",
|
|
634
|
+
errorPrefix: "Twilio get call status error"
|
|
635
|
+
}), (data) => {
|
|
679
636
|
const status = normalizeProviderStatus(data.status);
|
|
680
637
|
return {
|
|
681
638
|
status,
|
|
682
639
|
isTerminal: isProviderStatusTerminal(status)
|
|
683
640
|
};
|
|
684
|
-
}
|
|
685
|
-
return {
|
|
686
|
-
status: "error",
|
|
687
|
-
isTerminal: false,
|
|
688
|
-
isUnknown: true
|
|
689
|
-
};
|
|
690
|
-
}
|
|
641
|
+
});
|
|
691
642
|
}
|
|
692
643
|
};
|
|
693
644
|
//#endregion
|
|
@@ -3,6 +3,7 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store";
|
|
5
5
|
import { resolveStateDir } from "openclaw/plugin-sdk/state-paths";
|
|
6
|
+
import os from "node:os";
|
|
6
7
|
//#region extensions/voice-call/src/types.ts
|
|
7
8
|
const ProviderNameSchema = z.enum([
|
|
8
9
|
"telnyx",
|
|
@@ -373,11 +374,6 @@ async function loadActiveCallsFromStore(storePath, stateRuntime) {
|
|
|
373
374
|
} catch (err) {
|
|
374
375
|
console.error("[voice-call] Failed to read SQLite call records:", err);
|
|
375
376
|
}
|
|
376
|
-
if (calls.length === 0) return {
|
|
377
|
-
activeCalls: /* @__PURE__ */ new Map(),
|
|
378
|
-
providerCallIdMap: /* @__PURE__ */ new Map(),
|
|
379
|
-
processedEventIds: /* @__PURE__ */ new Set()
|
|
380
|
-
};
|
|
381
377
|
const callMap = /* @__PURE__ */ new Map();
|
|
382
378
|
for (const call of calls) {
|
|
383
379
|
callMap.delete(call.callId);
|
|
@@ -426,4 +422,16 @@ function resolveDefaultVoiceCallStoreDir(env = process.env) {
|
|
|
426
422
|
return path.join(resolveStateDir(env), "voice-calls");
|
|
427
423
|
}
|
|
428
424
|
//#endregion
|
|
429
|
-
|
|
425
|
+
//#region extensions/voice-call/src/utils.ts
|
|
426
|
+
/** Runtime uses the OS home; Doctor supplies the home from its migration environment. */
|
|
427
|
+
function resolveUserPath(input, homedir = os.homedir) {
|
|
428
|
+
const trimmed = input.trim();
|
|
429
|
+
if (!trimmed) return trimmed;
|
|
430
|
+
if (trimmed.startsWith("~")) {
|
|
431
|
+
const expanded = trimmed.replace(/^~(?=$|[\\/])/, homedir);
|
|
432
|
+
return path.resolve(expanded);
|
|
433
|
+
}
|
|
434
|
+
return path.resolve(trimmed);
|
|
435
|
+
}
|
|
436
|
+
//#endregion
|
|
437
|
+
export { TerminalStates as S, appendCallReplayKey as _, CALL_RECORD_EVENT_CHUNKS_NAMESPACE as a, reserveRejectedProviderCall as b, buildChunkKey as c, findCallInStore as d, getCallHistoryFromStore as f, resolveVoiceCallLegacyCallLogPath as g, persistCallRecord as h, CALL_RECORD_EVENTS_NAMESPACE as i, buildVoiceCallLegacyJsonlEventKey as l, parseVoiceCallRecordLine as m, resolveDefaultVoiceCallStoreDir as n, CALL_RECORD_EVENT_META_MAX_ENTRIES as o, loadActiveCallsFromStore as p, CALL_RECORD_CHUNK_MAX_ENTRIES as r, MAX_CALL_RECORD_EVENTS as s, resolveUserPath as t, encodeCallRecordEvent as u, releaseRejectedProviderCall as v, setVoiceCallStateRuntime as x, rememberManagerReplayKey as y };
|
package/dist/api.js
CHANGED
|
@@ -1,2 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
|
2
|
+
import { isRequestBodyLimitError, readRequestBodyWithLimit, requestBodyErrorToText, sendHttpRequestRejection } from "openclaw/plugin-sdk/webhook-request-guards";
|
|
3
|
+
import { fetchWithSsrFGuard, isBlockedHostnameOrIp } from "openclaw/plugin-sdk/ssrf-runtime";
|
|
4
|
+
import { TtsAutoSchema, TtsConfigSchema, TtsModeSchema, TtsProviderSchema } from "openclaw/plugin-sdk/tts-runtime";
|
|
5
|
+
import { sleep } from "openclaw/plugin-sdk/runtime-env";
|
|
2
6
|
export { TtsAutoSchema, TtsConfigSchema, TtsModeSchema, TtsProviderSchema, definePluginEntry, fetchWithSsrFGuard, isBlockedHostnameOrIp, isRequestBodyLimitError, readRequestBodyWithLimit, requestBodyErrorToText, sendHttpRequestRejection, sleep };
|
|
@@ -1,23 +1,194 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as CALL_RECORD_EVENT_CHUNKS_NAMESPACE, c as buildChunkKey, g as resolveVoiceCallLegacyCallLogPath, i as CALL_RECORD_EVENTS_NAMESPACE, l as buildVoiceCallLegacyJsonlEventKey, m as parseVoiceCallRecordLine, n as resolveDefaultVoiceCallStoreDir, o as CALL_RECORD_EVENT_META_MAX_ENTRIES, r as CALL_RECORD_CHUNK_MAX_ENTRIES, s as MAX_CALL_RECORD_EVENTS, t as resolveUserPath, u as encodeCallRecordEvent } from "./.setup/utils-B5pnM_Ij.mjs";
|
|
2
|
+
import "node:module";
|
|
2
3
|
import { normalizeAgentId } from "openclaw/plugin-sdk/routing";
|
|
3
4
|
import { asOptionalRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
4
5
|
import { existsSync } from "node:fs";
|
|
5
|
-
import path from "node:path";
|
|
6
6
|
import os from "node:os";
|
|
7
7
|
import fs$1 from "node:fs/promises";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
//#region \0rolldown/runtime.js
|
|
9
|
+
var __defProp = Object.defineProperty;
|
|
10
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
11
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
12
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
13
|
+
var __exportAll = (all, no_symbols) => {
|
|
14
|
+
let target = {};
|
|
15
|
+
for (var name in all) __defProp(target, name, {
|
|
16
|
+
get: all[name],
|
|
17
|
+
enumerable: true
|
|
18
|
+
});
|
|
19
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
20
|
+
return target;
|
|
21
|
+
};
|
|
22
|
+
var __copyProps = (to, from, except, desc) => {
|
|
23
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
24
|
+
key = keys[i];
|
|
25
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
26
|
+
get: ((k) => from[k]).bind(null, key),
|
|
27
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
return to;
|
|
31
|
+
};
|
|
32
|
+
var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
|
|
33
|
+
//#endregion
|
|
34
|
+
//#region packages/normalization-core/src/record-coerce.ts
|
|
35
|
+
/** Type guard for non-array object records at browser-safe boundaries. */
|
|
36
|
+
function isRecord$1(value) {
|
|
37
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
13
38
|
}
|
|
14
|
-
/**
|
|
15
|
-
function
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
39
|
+
/** Returns a non-array record or null. */
|
|
40
|
+
function asNullableRecord$1(value) {
|
|
41
|
+
return isRecord$1(value) ? value : null;
|
|
42
|
+
}
|
|
43
|
+
//#endregion
|
|
44
|
+
//#region src/config/channel-config-normalization.ts
|
|
45
|
+
/** Applies one channel-specific doctor migration to every object-shaped account. */
|
|
46
|
+
function normalizeChannelAccounts(params) {
|
|
47
|
+
const rawAccounts = asNullableRecord$1(params.entry.accounts);
|
|
48
|
+
if (!rawAccounts) return {
|
|
49
|
+
entry: params.entry,
|
|
50
|
+
changed: false
|
|
51
|
+
};
|
|
52
|
+
let changed = false;
|
|
53
|
+
const accounts = { ...rawAccounts };
|
|
54
|
+
for (const [accountId, value] of Object.entries(rawAccounts)) {
|
|
55
|
+
const account = asNullableRecord$1(value);
|
|
56
|
+
if (!account) continue;
|
|
57
|
+
const normalized = params.normalizeAccount({
|
|
58
|
+
account,
|
|
59
|
+
accountId,
|
|
60
|
+
pathPrefix: `${params.pathPrefix}.accounts.${accountId}`,
|
|
61
|
+
changes: params.changes
|
|
62
|
+
});
|
|
63
|
+
if (normalized.changed) {
|
|
64
|
+
accounts[accountId] = normalized.entry;
|
|
65
|
+
changed = true;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return changed ? {
|
|
69
|
+
entry: {
|
|
70
|
+
...params.entry,
|
|
71
|
+
accounts
|
|
72
|
+
},
|
|
73
|
+
changed: true
|
|
74
|
+
} : {
|
|
75
|
+
entry: params.entry,
|
|
76
|
+
changed: false
|
|
77
|
+
};
|
|
20
78
|
}
|
|
79
|
+
/** Applies the same channel-specific doctor migration at root and account scope. */
|
|
80
|
+
function normalizeChannelConfigEntries(params) {
|
|
81
|
+
const changes = params.changes ?? [];
|
|
82
|
+
const channels = params.cfg.channels;
|
|
83
|
+
const entry = asNullableRecord$1(channels?.[params.channelId]);
|
|
84
|
+
if (!entry) return {
|
|
85
|
+
config: params.cfg,
|
|
86
|
+
changes
|
|
87
|
+
};
|
|
88
|
+
const channelPath = `channels.${params.channelId}`;
|
|
89
|
+
const root = params.normalizeEntry({
|
|
90
|
+
entry,
|
|
91
|
+
pathPrefix: channelPath,
|
|
92
|
+
changes
|
|
93
|
+
});
|
|
94
|
+
const accounts = normalizeChannelAccounts({
|
|
95
|
+
entry: root.entry,
|
|
96
|
+
pathPrefix: channelPath,
|
|
97
|
+
changes,
|
|
98
|
+
normalizeAccount: (accountParams) => params.normalizeEntry({
|
|
99
|
+
entry: accountParams.account,
|
|
100
|
+
accountId: accountParams.accountId,
|
|
101
|
+
pathPrefix: accountParams.pathPrefix,
|
|
102
|
+
changes: accountParams.changes
|
|
103
|
+
})
|
|
104
|
+
});
|
|
105
|
+
if (!root.changed && !accounts.changed) return {
|
|
106
|
+
config: params.cfg,
|
|
107
|
+
changes
|
|
108
|
+
};
|
|
109
|
+
return {
|
|
110
|
+
config: {
|
|
111
|
+
...params.cfg,
|
|
112
|
+
channels: {
|
|
113
|
+
...channels,
|
|
114
|
+
[params.channelId]: accounts.entry
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
changes
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
//#endregion
|
|
121
|
+
//#region src/plugin-sdk/legacy-webhook-listener-migration.ts
|
|
122
|
+
/** Preserve explicitly configured webhook listeners while moving ingress onto Gateway routes. */
|
|
123
|
+
function createLegacyWebhookListenerDoctorContract(params) {
|
|
124
|
+
const portKey = params.portKey ?? "webhookPort";
|
|
125
|
+
const hostKey = params.hostKey === void 0 ? "webhookHost" : params.hostKey;
|
|
126
|
+
const source = (entry) => params.webhookKey ? asNullableRecord$1(entry[params.webhookKey]) : entry;
|
|
127
|
+
const hasLegacy = (value) => {
|
|
128
|
+
const entry = asNullableRecord$1(value);
|
|
129
|
+
const listener = entry && source(entry);
|
|
130
|
+
return Boolean(listener && (Object.hasOwn(listener, portKey) || hostKey && Object.hasOwn(listener, hostKey)));
|
|
131
|
+
};
|
|
132
|
+
const prefix = `channels.${params.channelKey}`;
|
|
133
|
+
return {
|
|
134
|
+
legacyConfigRules: [{
|
|
135
|
+
path: ["channels", params.channelKey],
|
|
136
|
+
message: `${prefix} webhook listeners moved to Gateway routes. Run "openclaw doctor --fix" to preserve explicitly configured listener settings as legacyWebhook.`,
|
|
137
|
+
match: (value) => {
|
|
138
|
+
const accounts = asNullableRecord$1(asNullableRecord$1(value)?.accounts);
|
|
139
|
+
return hasLegacy(value) || Object.values(accounts ?? {}).some(hasLegacy);
|
|
140
|
+
}
|
|
141
|
+
}],
|
|
142
|
+
normalizeCompatibilityConfig: ({ cfg }) => {
|
|
143
|
+
const root = asNullableRecord$1(asNullableRecord$1(cfg.channels)?.[params.channelKey]);
|
|
144
|
+
const inherited = root && source(root);
|
|
145
|
+
const canonicalRoot = asNullableRecord$1(root?.legacyWebhook);
|
|
146
|
+
return normalizeChannelConfigEntries({
|
|
147
|
+
cfg,
|
|
148
|
+
channelId: params.channelKey,
|
|
149
|
+
normalizeEntry: ({ entry, accountId, pathPrefix, changes }) => {
|
|
150
|
+
const listener = source(entry);
|
|
151
|
+
if (!listener || !hasLegacy(entry)) return {
|
|
152
|
+
entry,
|
|
153
|
+
changed: false
|
|
154
|
+
};
|
|
155
|
+
const next = { ...entry };
|
|
156
|
+
const port = Object.hasOwn(listener, portKey) ? listener[portKey] : (accountId ? canonicalRoot?.port ?? inherited?.[portKey] : void 0) ?? params.defaultPort;
|
|
157
|
+
const inheritedHost = accountId ? canonicalRoot ? canonicalRoot.host : (hostKey ? inherited?.[hostKey] : void 0) ?? params.defaultHost : params.defaultHost;
|
|
158
|
+
const host = hostKey ? listener[hostKey] ?? inheritedHost : inheritedHost;
|
|
159
|
+
const legacyPath = [pathPrefix, params.webhookKey].filter(Boolean).join(".");
|
|
160
|
+
if (Object.hasOwn(entry, "legacyWebhook")) changes.push(`Removed ${legacyPath} legacy listener keys; ${pathPrefix}.legacyWebhook is already configured.`);
|
|
161
|
+
else if (accountId && root?.legacyWebhook === false) changes.push(`Removed ${legacyPath} legacy listener keys; ${prefix}.legacyWebhook: false keeps this account's inherited listener disabled.`);
|
|
162
|
+
else {
|
|
163
|
+
next.legacyWebhook = {
|
|
164
|
+
port,
|
|
165
|
+
...host !== void 0 ? { host } : {}
|
|
166
|
+
};
|
|
167
|
+
changes.push(`Moved ${legacyPath} listener settings to ${pathPrefix}.legacyWebhook. Point the external callback or reverse proxy at the Gateway port and webhook path, verify delivery, then set legacyWebhook: false to disable legacy forwarding.`);
|
|
168
|
+
}
|
|
169
|
+
const updated = params.webhookKey ? { ...listener } : next;
|
|
170
|
+
delete updated[portKey];
|
|
171
|
+
if (hostKey) delete updated[hostKey];
|
|
172
|
+
if (params.webhookKey) {
|
|
173
|
+
if (Object.keys(updated).length) next[params.webhookKey] = updated;
|
|
174
|
+
else delete next[params.webhookKey];
|
|
175
|
+
}
|
|
176
|
+
return {
|
|
177
|
+
entry: next,
|
|
178
|
+
changed: true
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
//#endregion
|
|
186
|
+
//#region \0openclaw:bundled-sdk:openclaw/plugin-sdk/runtime-doctor-migrations
|
|
187
|
+
var runtime_doctor_migrations_exports = /* @__PURE__ */ __exportAll({ createLegacyWebhookListenerDoctorContract: () => createLegacyWebhookListenerDoctorContract });
|
|
188
|
+
import * as import_openclaw_plugin_sdk_runtime_doctor_migrations from "openclaw/plugin-sdk/runtime-doctor-migrations";
|
|
189
|
+
__reExport(runtime_doctor_migrations_exports, import_openclaw_plugin_sdk_runtime_doctor_migrations);
|
|
190
|
+
//#endregion
|
|
191
|
+
//#region extensions/voice-call/doctor-contract-api.ts
|
|
21
192
|
/** Read the configured voice-call store path from either package id. */
|
|
22
193
|
function getVoiceCallConfigStore(config) {
|
|
23
194
|
for (const pluginId of ["voice-call", "@openclaw/voice-call"]) {
|
|
@@ -48,7 +219,7 @@ function resolveSessionStoreAgentIds(params) {
|
|
|
48
219
|
/** Resolve the voice-call store path used by legacy and plugin-state call records. */
|
|
49
220
|
function resolveVoiceCallStorePath(params) {
|
|
50
221
|
const configuredStore = getVoiceCallConfigStore(params.config);
|
|
51
|
-
if (configuredStore) return resolveUserPath(configuredStore, params.env);
|
|
222
|
+
if (configuredStore) return resolveUserPath(configuredStore, () => params.env.HOME?.trim() || os.homedir());
|
|
52
223
|
return resolveDefaultVoiceCallStoreDir(params.env);
|
|
53
224
|
}
|
|
54
225
|
function resolveVoiceCallStateDatabaseEnv(params) {
|
|
@@ -226,7 +397,7 @@ const stateMigrations = [{
|
|
|
226
397
|
warnings
|
|
227
398
|
};
|
|
228
399
|
}
|
|
229
|
-
await archiveLegacyStateSource({
|
|
400
|
+
await (0, runtime_doctor_migrations_exports.archiveLegacyStateSource)({
|
|
230
401
|
filePath,
|
|
231
402
|
label: "Voice Call call-log",
|
|
232
403
|
changes,
|