@openclaw/voice-call 2026.7.2-beta.7 → 2026.7.33
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/README.md +3 -2
- package/dist/.setup/config-BBpso-fA.mjs +674 -0
- package/dist/.setup/config-compat-CYRE-SSo.mjs +156 -0
- package/dist/{guarded-json-api-DS9sZa3C.js → .setup/guarded-json-api-BukKSVNW.mjs} +56 -42
- package/dist/{mock-YsvBTX-7.js → .setup/mock-DY4yTqR2.mjs} +1 -1
- package/dist/{plivo-vZ--Wqxv.js → .setup/plivo-DFHh72MH.mjs} +3 -2
- package/dist/{realtime-handler--i_OERv6.js → .setup/realtime-handler-DWLxvZuO.mjs} +198 -130
- package/dist/{response-generator-D-7ApVfT.js → .setup/response-generator-DoC6lfcm.mjs} +6 -21
- package/dist/{runtime-entry-D0UJiLfi.js → .setup/runtime-entry-CH-CV0Sf.mjs} +384 -868
- package/dist/{store-path-nYL-yM0S.js → .setup/store-1UfyY3HS.mjs} +6 -13
- package/dist/{telnyx-Dx6cfLvV.js → .setup/telnyx-Bb8X-nin.mjs} +1 -1
- package/dist/{twilio-D_D8KysU.js → .setup/twilio-Bsbf3O9A.mjs} +11 -15
- package/dist/{webhook-replay-aaP79dI8.js → .setup/webhook-replay-g3cDA5wr.mjs} +1 -1
- package/dist/cli-metadata.js +1 -5
- package/dist/doctor-contract-api.js +14 -36
- package/dist/index.js +46 -48
- package/dist/runtime-entry.js +1 -1
- package/dist/setup-api.js +2 -86
- package/node_modules/commander/LICENSE +22 -0
- package/node_modules/commander/Readme.md +1172 -0
- package/node_modules/commander/index.js +21 -0
- package/node_modules/commander/lib/argument.js +147 -0
- package/node_modules/commander/lib/command.js +2790 -0
- package/node_modules/commander/lib/error.js +36 -0
- package/node_modules/commander/lib/help.js +731 -0
- package/node_modules/commander/lib/option.js +377 -0
- package/node_modules/commander/lib/suggestSimilar.js +99 -0
- package/node_modules/commander/package-support.json +19 -0
- package/node_modules/commander/package.json +64 -0
- package/node_modules/commander/typings/index.d.ts +1113 -0
- package/node_modules/typebox/build/compile/validator.d.mts +4 -1
- package/node_modules/typebox/build/guard/guard.d.mts +1 -1
- package/node_modules/typebox/build/guard/guard.mjs +2 -5
- package/node_modules/typebox/build/guard/string.mjs +4 -16
- package/node_modules/typebox/build/schema/engine/_functions.mjs +15 -34
- package/node_modules/typebox/build/type/action/readonly_object.d.mts +0 -5
- package/node_modules/typebox/build/type/action/readonly_object.mjs +0 -8
- package/node_modules/typebox/build/typebox.d.mts +1 -1
- package/node_modules/typebox/build/typebox.mjs +1 -1
- package/node_modules/typebox/package.json +1 -1
- package/node_modules/typebox/readme.md +32 -29
- package/node_modules/ws/lib/permessage-deflate.js +3 -1
- package/node_modules/ws/package.json +1 -1
- package/openclaw.plugin.json +15 -2
- package/package.json +8 -6
- package/dist/cli-output-mode.js +0 -27
- package/dist/twilio-region-4fkgz3UG.js +0 -25
- /package/dist/{realtime-transcription.runtime-CbJAs5t_.js → .setup/realtime-transcription.runtime-CbJAs5t_.mjs} +0 -0
- /package/dist/{realtime-voice.runtime-vtdCOWg-.js → .setup/realtime-voice.runtime-vtdCOWg-.mjs} +0 -0
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { createHash, randomUUID } from "node:crypto";
|
|
3
2
|
import path from "node:path";
|
|
3
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
4
4
|
import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store";
|
|
5
|
-
import { resolveStateDir } from "openclaw/plugin-sdk/state-paths";
|
|
6
5
|
//#region extensions/voice-call/src/types.ts
|
|
7
6
|
const ProviderNameSchema = z.enum([
|
|
8
7
|
"telnyx",
|
|
@@ -125,7 +124,7 @@ const CallRecordSchema = z.object({
|
|
|
125
124
|
});
|
|
126
125
|
//#endregion
|
|
127
126
|
//#region extensions/voice-call/src/runtime-state.ts
|
|
128
|
-
const { setRuntime: setVoiceCallStateRuntime, tryGetRuntime: getOptionalVoiceCallStateRuntime } = createPluginRuntimeStore({
|
|
127
|
+
const { setRuntime: setVoiceCallStateRuntime, clearRuntime: clearVoiceCallStateRuntime, tryGetRuntime: getOptionalVoiceCallStateRuntime } = createPluginRuntimeStore({
|
|
129
128
|
pluginId: "voice-call-state",
|
|
130
129
|
errorMessage: "Voice Call state runtime not initialized"
|
|
131
130
|
});
|
|
@@ -141,7 +140,7 @@ const MAX_CALL_RECORD_EVENTS = 1e3;
|
|
|
141
140
|
const CALL_RECORD_EVENT_META_MAX_ENTRIES = 1100;
|
|
142
141
|
const CALL_RECORD_CHUNK_MAX_ENTRIES = 48048;
|
|
143
142
|
/** Raw UTF-8 bytes stored per call record chunk before base64 encoding. */
|
|
144
|
-
const RAW_CALL_RECORD_CHUNK_BYTES =
|
|
143
|
+
const RAW_CALL_RECORD_CHUNK_BYTES = 48128;
|
|
145
144
|
let callRecordEventSequence = 0;
|
|
146
145
|
/** Return the pre-SQLite JSONL call log path for migration/compat checks. */
|
|
147
146
|
function resolveVoiceCallLegacyCallLogPath(storePath) {
|
|
@@ -204,8 +203,8 @@ function buildNewEventKey(order) {
|
|
|
204
203
|
}
|
|
205
204
|
/** Recover the sequence segment from newer event keys. */
|
|
206
205
|
function parseEventKeySequence(key) {
|
|
207
|
-
const
|
|
208
|
-
return
|
|
206
|
+
const match = /^event:[^:]+:(\d+):/.exec(key);
|
|
207
|
+
return match ? Number.parseInt(match[1], 10) : 0;
|
|
209
208
|
}
|
|
210
209
|
/** Parse a stored call record line from v2 envelope or legacy raw-call JSON. */
|
|
211
210
|
function parseVoiceCallRecordLine(line, sequence = 0) {
|
|
@@ -401,10 +400,4 @@ async function getCallHistoryFromStore(storePath, limit = 50) {
|
|
|
401
400
|
return readCallHistoryFromStore(storePath).slice(-limit);
|
|
402
401
|
}
|
|
403
402
|
//#endregion
|
|
404
|
-
|
|
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 };
|
|
403
|
+
export { MAX_CALL_RECORD_EVENTS as a, findCallMatchesInStore as c, parseVoiceCallRecordLine as d, persistCallRecord as f, TerminalStates as g, setVoiceCallStateRuntime as h, CALL_RECORD_EVENT_META_MAX_ENTRIES as i, getCallHistoryFromStore as l, resolveVoiceCallLegacyCallLogPath as m, CALL_RECORD_EVENTS_NAMESPACE as n, RAW_CALL_RECORD_CHUNK_BYTES as o, prepareVoiceCallRecordForStorage as p, CALL_RECORD_EVENT_CHUNKS_NAMESPACE as r, buildVoiceCallLegacyJsonlEventKey as s, CALL_RECORD_CHUNK_MAX_ENTRIES as t, loadActiveCallsFromStore 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-BukKSVNW.mjs";
|
|
2
2
|
import crypto from "node:crypto";
|
|
3
3
|
//#region extensions/voice-call/src/providers/telnyx.ts
|
|
4
4
|
function normalizeTelnyxDirection(direction) {
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { fetchWithSsrFGuard } from "
|
|
2
|
-
import "
|
|
3
|
-
import {
|
|
4
|
-
import { _ as
|
|
5
|
-
import { c as verifyTwilioWebhook, i as readProviderJsonResponseText, n as cancelProviderResponseBody, r as readProviderErrorResponseSnippet, t as guardedJsonApiRequest } from "./guarded-json-api-
|
|
1
|
+
import { fetchWithSsrFGuard } from "../runtime-api.js";
|
|
2
|
+
import "../api.js";
|
|
3
|
+
import { l as requireSupportedTwilioApiHostname, u as resolveTwilioApiBaseUrl } from "./config-BBpso-fA.mjs";
|
|
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-CH-CV0Sf.mjs";
|
|
5
|
+
import { c as verifyTwilioWebhook, i as readProviderJsonResponseText, n as cancelProviderResponseBody, r as readProviderErrorResponseSnippet, t as guardedJsonApiRequest } from "./guarded-json-api-BukKSVNW.mjs";
|
|
6
6
|
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
7
|
-
import { sleepWithAbort } from "openclaw/plugin-sdk/runtime-env";
|
|
8
7
|
import crypto from "node:crypto";
|
|
9
8
|
import { safeEqualSecret } from "openclaw/plugin-sdk/security-runtime";
|
|
10
9
|
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
@@ -179,9 +178,9 @@ var TwilioProvider = class TwilioProvider {
|
|
|
179
178
|
deleteStoredTwimlForProviderCall(providerCallId) {
|
|
180
179
|
const webhookUrl = this.callWebhookUrls.get(providerCallId);
|
|
181
180
|
if (!webhookUrl) return;
|
|
182
|
-
const
|
|
183
|
-
if (!
|
|
184
|
-
this.deleteStoredTwiml(
|
|
181
|
+
const callIdMatch = webhookUrl.match(/callId=([^&]+)/);
|
|
182
|
+
if (!callIdMatch) return;
|
|
183
|
+
this.deleteStoredTwiml(callIdMatch[1]);
|
|
185
184
|
this.streamAuthTokens.delete(providerCallId);
|
|
186
185
|
}
|
|
187
186
|
constructor(config, options = {}) {
|
|
@@ -629,12 +628,9 @@ var TwilioProvider = class TwilioProvider {
|
|
|
629
628
|
chunkAttempts += 1;
|
|
630
629
|
if (sendAudioChunk(chunk).sent) chunkDelivered += 1;
|
|
631
630
|
const waitMs = nextChunkDueAt - Date.now();
|
|
632
|
-
if (waitMs > 0)
|
|
633
|
-
|
|
634
|
-
}
|
|
635
|
-
if (!signal.aborted) throw error;
|
|
636
|
-
break;
|
|
637
|
-
}
|
|
631
|
+
if (waitMs > 0) await new Promise((resolve) => {
|
|
632
|
+
setTimeout(resolve, Math.ceil(waitMs));
|
|
633
|
+
});
|
|
638
634
|
nextChunkDueAt += CHUNK_DELAY_MS;
|
|
639
635
|
if (signal.aborted) break;
|
|
640
636
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isFutureDateTimestampMs, resolveExpiresAtMsFromDurationMs } from "openclaw/plugin-sdk/number-runtime";
|
|
2
2
|
//#region extensions/voice-call/src/webhook-replay.ts
|
|
3
|
-
const REPLAY_WINDOW_MS =
|
|
3
|
+
const REPLAY_WINDOW_MS = 6e5;
|
|
4
4
|
const REPLAY_CACHE_MAX_ENTRIES = 1e4;
|
|
5
5
|
const REPLAY_CACHE_PRUNE_INTERVAL = 64;
|
|
6
6
|
function createWebhookReplayCache() {
|
package/dist/cli-metadata.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { VOICE_CALL_CLI_DESCRIPTOR } from "./cli-output-mode.js";
|
|
2
1
|
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
|
3
2
|
//#region extensions/voice-call/cli-metadata.ts
|
|
4
3
|
var cli_metadata_default = definePluginEntry({
|
|
@@ -6,10 +5,7 @@ var cli_metadata_default = definePluginEntry({
|
|
|
6
5
|
name: "Voice Call",
|
|
7
6
|
description: "Voice call channel plugin",
|
|
8
7
|
register(api) {
|
|
9
|
-
api.registerCli(() => {}, {
|
|
10
|
-
commands: ["voicecall"],
|
|
11
|
-
descriptors: [VOICE_CALL_CLI_DESCRIPTOR]
|
|
12
|
-
});
|
|
8
|
+
api.registerCli(() => {}, { commands: ["voicecall"] });
|
|
13
9
|
}
|
|
14
10
|
});
|
|
15
11
|
//#endregion
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as MAX_CALL_RECORD_EVENTS, d as parseVoiceCallRecordLine, i as CALL_RECORD_EVENT_META_MAX_ENTRIES, m as resolveVoiceCallLegacyCallLogPath, n as CALL_RECORD_EVENTS_NAMESPACE, o as RAW_CALL_RECORD_CHUNK_BYTES, p as prepareVoiceCallRecordForStorage, r as CALL_RECORD_EVENT_CHUNKS_NAMESPACE, s as buildVoiceCallLegacyJsonlEventKey, t as CALL_RECORD_CHUNK_MAX_ENTRIES } from "./.setup/store-1UfyY3HS.mjs";
|
|
2
2
|
import { normalizeAgentId } from "openclaw/plugin-sdk/routing";
|
|
3
|
-
import path from "node:path";
|
|
4
3
|
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
5
|
import fs from "node:fs/promises";
|
|
6
|
-
import { archiveLegacyStateSource
|
|
6
|
+
import { archiveLegacyStateSource } from "openclaw/plugin-sdk/runtime-doctor";
|
|
7
7
|
//#region extensions/voice-call/doctor-contract-api.ts
|
|
8
8
|
/** Resolve home from doctor env with OS fallback. */
|
|
9
9
|
function resolveHome(env) {
|
|
@@ -50,23 +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
|
|
54
|
-
}
|
|
55
|
-
function resolveVoiceCallStateDatabaseEnv(params) {
|
|
56
|
-
return {
|
|
57
|
-
...params.env,
|
|
58
|
-
OPENCLAW_STATE_DIR: resolveVoiceCallStorePath(params)
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
function describeVoiceCallSchemaMigration(migration) {
|
|
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;
|
|
53
|
+
return path.join(resolveHome(params.env), ".openclaw", "voice-calls");
|
|
70
54
|
}
|
|
71
55
|
/** Return true when a path exists and is a file. */
|
|
72
56
|
/** Build the plugin state key for one migrated event chunk. */
|
|
@@ -185,32 +169,26 @@ const stateMigrations = [{
|
|
|
185
169
|
id: "voice-call-calls-jsonl-to-plugin-state",
|
|
186
170
|
label: "Voice Call call log",
|
|
187
171
|
async detectLegacyState(params) {
|
|
188
|
-
const
|
|
189
|
-
const
|
|
190
|
-
if (entries.length === 0
|
|
191
|
-
return { preview: [
|
|
172
|
+
const storePath = resolveVoiceCallStorePath(params);
|
|
173
|
+
const { entries } = await readLegacyCallRecords(resolveVoiceCallLegacyCallLogPath(storePath));
|
|
174
|
+
if (entries.length === 0) return null;
|
|
175
|
+
return { preview: [`- Voice Call call log: ${entries.length} ${entries.length === 1 ? "record" : "records"} -> plugin state (${CALL_RECORD_EVENTS_NAMESPACE})`] };
|
|
192
176
|
},
|
|
193
177
|
async migrateLegacyState(params) {
|
|
194
178
|
const changes = [];
|
|
195
179
|
const warnings = [];
|
|
196
|
-
const
|
|
180
|
+
const storePath = resolveVoiceCallStorePath(params);
|
|
181
|
+
const filePath = resolveVoiceCallLegacyCallLogPath(storePath);
|
|
197
182
|
const { entries, warnings: readWarnings } = await readLegacyCallRecords(filePath);
|
|
198
183
|
warnings.push(...readWarnings);
|
|
199
|
-
const stateDatabaseEnv = resolveVoiceCallStateDatabaseEnv(params);
|
|
200
|
-
if (detectOpenClawStateDatabaseSchemaMigrations({ env: stateDatabaseEnv }).length > 0) {
|
|
201
|
-
const repaired = repairOpenClawStateDatabaseSchema({ env: stateDatabaseEnv });
|
|
202
|
-
warnings.push(...repaired.warnings);
|
|
203
|
-
if (repaired.warnings.length > 0) return {
|
|
204
|
-
changes,
|
|
205
|
-
warnings
|
|
206
|
-
};
|
|
207
|
-
changes.push(...repaired.changes.map((change) => change.replace(/^Migrated shared state /, "Migrated Voice Call SQLite ").replaceAll("→", "->")));
|
|
208
|
-
}
|
|
209
184
|
if (entries.length === 0) return {
|
|
210
185
|
changes,
|
|
211
186
|
warnings
|
|
212
187
|
};
|
|
213
|
-
const env =
|
|
188
|
+
const env = {
|
|
189
|
+
...params.env,
|
|
190
|
+
OPENCLAW_STATE_DIR: storePath
|
|
191
|
+
};
|
|
214
192
|
const imported = await importLegacyCallRecords({
|
|
215
193
|
entries,
|
|
216
194
|
eventStore: params.context.openPluginStateKeyedStore({
|
package/dist/index.js
CHANGED
|
@@ -1,25 +1,27 @@
|
|
|
1
1
|
import { definePluginEntry, sleep } from "./runtime-api.js";
|
|
2
2
|
import "./api.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
import { c as validateProviderConfig, i as resolveVoiceCallConfig } from "./.setup/config-BBpso-fA.mjs";
|
|
4
|
+
import { c as cleanupTailscaleExposureRoute, h as resolveUserPath, l as getTailscaleSelfInfo, s as resolveWebhookExposureStatus, t as createVoiceCallRuntime, u as setupTailscaleExposureRoute } from "./.setup/runtime-entry-CH-CV0Sf.mjs";
|
|
5
|
+
import { h as setVoiceCallStateRuntime, l as getCallHistoryFromStore } from "./.setup/store-1UfyY3HS.mjs";
|
|
6
|
+
import { i as parseVoiceCallPluginConfig, r as normalizeVoiceCallLegacyConfigInput, t as formatVoiceCallLegacyConfigWarnings } from "./.setup/config-compat-CYRE-SSo.mjs";
|
|
6
7
|
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
7
8
|
import { ErrorCodes, callGatewayFromCli, errorShape } from "openclaw/plugin-sdk/gateway-runtime";
|
|
8
9
|
import { MAX_TCP_PORT, MAX_TIMER_TIMEOUT_MS, addTimerTimeoutGraceMs, clampTimerTimeoutMs, parseStrictNonNegativeInteger, resolveTimerTimeoutMs, timestampMsToIsoString } from "openclaw/plugin-sdk/number-runtime";
|
|
9
10
|
import { normalizeAgentId } from "openclaw/plugin-sdk/routing";
|
|
10
|
-
import {
|
|
11
|
+
import { isRecord, normalizeOptionalLowercaseString, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
11
12
|
import { jsonResult } from "openclaw/plugin-sdk/tool-results";
|
|
12
13
|
import { Type } from "typebox";
|
|
13
14
|
import fs from "node:fs";
|
|
14
|
-
import
|
|
15
|
+
import os from "node:os";
|
|
15
16
|
import path from "node:path";
|
|
16
|
-
import {
|
|
17
|
+
import { randomUUID } from "node:crypto";
|
|
17
18
|
import { format } from "node:util";
|
|
18
19
|
//#region extensions/voice-call/src/cli.ts
|
|
19
20
|
const VOICE_CALL_GATEWAY_DEFAULT_TIMEOUT_MS = 5e3;
|
|
20
21
|
const VOICE_CALL_GATEWAY_OPERATION_TIMEOUT_MS = 3e4;
|
|
21
22
|
const VOICE_CALL_GATEWAY_TRANSCRIPT_BUFFER_MS = 1e4;
|
|
22
23
|
const VOICE_CALL_GATEWAY_POLL_INTERVAL_MS = 1e3;
|
|
24
|
+
const voiceCallCliDeps = { callGatewayFromCli };
|
|
23
25
|
function writeStdoutLine(...values) {
|
|
24
26
|
process.stdout.write(`${format(...values)}\n`);
|
|
25
27
|
}
|
|
@@ -28,7 +30,8 @@ function writeStdoutJson(value) {
|
|
|
28
30
|
}
|
|
29
31
|
function parseVoiceCallIntOption(raw, optionName, opts) {
|
|
30
32
|
const min = opts?.min ?? 0;
|
|
31
|
-
const
|
|
33
|
+
const value = raw?.trim() ?? "";
|
|
34
|
+
const parsed = parseStrictNonNegativeInteger(value);
|
|
32
35
|
if (parsed === void 0 || parsed < min || opts?.max !== void 0 && parsed > opts.max) throw new Error(`Invalid numeric value for ${optionName}: ${raw ?? ""}`);
|
|
33
36
|
return parsed;
|
|
34
37
|
}
|
|
@@ -41,7 +44,7 @@ async function callVoiceCallGateway(method, params, opts) {
|
|
|
41
44
|
const timeoutMs = typeof opts?.timeoutMs === "number" && Number.isFinite(opts.timeoutMs) ? Math.max(1, Math.ceil(opts.timeoutMs)) : VOICE_CALL_GATEWAY_DEFAULT_TIMEOUT_MS;
|
|
42
45
|
return {
|
|
43
46
|
ok: true,
|
|
44
|
-
payload: await callGatewayFromCli(method, {
|
|
47
|
+
payload: await voiceCallCliDeps.callGatewayFromCli(method, {
|
|
45
48
|
json: true,
|
|
46
49
|
timeout: String(timeoutMs)
|
|
47
50
|
}, params, { progress: false })
|
|
@@ -89,17 +92,13 @@ function readCompletedContinueResult(payload) {
|
|
|
89
92
|
}
|
|
90
93
|
async function pollVoiceCallContinueGateway(params) {
|
|
91
94
|
const deadlineMs = resolveVoiceCallDeadlineMs(params.timeoutMs);
|
|
92
|
-
|
|
93
|
-
const
|
|
94
|
-
if (remainingMs <= 0) break;
|
|
95
|
-
const gateway = await callVoiceCallGateway("voicecall.continue.result", { operationId: params.operationId }, { timeoutMs: Math.min(VOICE_CALL_GATEWAY_DEFAULT_TIMEOUT_MS, remainingMs) });
|
|
95
|
+
while (Date.now() <= deadlineMs) {
|
|
96
|
+
const gateway = await callVoiceCallGateway("voicecall.continue.result", { operationId: params.operationId }, { timeoutMs: VOICE_CALL_GATEWAY_DEFAULT_TIMEOUT_MS });
|
|
96
97
|
if (!gateway.ok) throw new Error(`gateway unavailable while waiting for voicecall continue result: ${formatErrorMessage(gateway.error)}`);
|
|
97
98
|
const result = readCompletedContinueResult(gateway.payload);
|
|
98
99
|
if (result.status === "completed") return result.result;
|
|
99
100
|
if (result.status === "failed") throw new Error(result.error);
|
|
100
|
-
|
|
101
|
-
if (sleepMs <= 0) break;
|
|
102
|
-
await sleep(sleepMs);
|
|
101
|
+
await sleep(Math.min(VOICE_CALL_GATEWAY_POLL_INTERVAL_MS, Math.max(1, deadlineMs - Date.now())));
|
|
103
102
|
}
|
|
104
103
|
throw new Error("voicecall continue timed out waiting for gateway operation");
|
|
105
104
|
}
|
|
@@ -109,7 +108,16 @@ function resolveMode(input) {
|
|
|
109
108
|
return "funnel";
|
|
110
109
|
}
|
|
111
110
|
function resolveDefaultStorePath(config) {
|
|
112
|
-
const
|
|
111
|
+
const preferred = path.join(os.homedir(), ".openclaw", "voice-calls");
|
|
112
|
+
const resolvedPreferred = resolveUserPath(preferred);
|
|
113
|
+
const existing = [resolvedPreferred].find((dir) => {
|
|
114
|
+
try {
|
|
115
|
+
return fs.existsSync(path.join(dir, "calls.jsonl")) || fs.existsSync(dir);
|
|
116
|
+
} catch {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
}) ?? resolvedPreferred;
|
|
120
|
+
const base = config.store?.trim() ? resolveUserPath(config.store) : existing;
|
|
113
121
|
return path.join(base, "calls.jsonl");
|
|
114
122
|
}
|
|
115
123
|
function percentile(values, p) {
|
|
@@ -430,33 +438,22 @@ function registerVoiceCallCli(params) {
|
|
|
430
438
|
}
|
|
431
439
|
};
|
|
432
440
|
if (fs.existsSync(file) && path.basename(file) !== "calls.jsonl") {
|
|
433
|
-
const initial = fs.readFileSync(file);
|
|
434
|
-
|
|
435
|
-
const initialLines = decoder.write(initial).split("\n");
|
|
436
|
-
let pendingLine = initialLines.pop() ?? "";
|
|
437
|
-
const lines = initialLines.filter(Boolean);
|
|
441
|
+
const initial = fs.readFileSync(file, "utf8");
|
|
442
|
+
const lines = initial.split("\n").filter(Boolean);
|
|
438
443
|
for (const line of lines.slice(Math.max(0, lines.length - since))) writeStdoutLine(line);
|
|
439
|
-
let offset = initial
|
|
440
|
-
let lastObservedSize = initial.length;
|
|
444
|
+
let offset = Buffer.byteLength(initial, "utf8");
|
|
441
445
|
for (;;) {
|
|
442
446
|
try {
|
|
443
447
|
const stat = fs.statSync(file);
|
|
444
|
-
if (stat.size <
|
|
445
|
-
offset = 0;
|
|
446
|
-
decoder = new StringDecoder("utf8");
|
|
447
|
-
pendingLine = "";
|
|
448
|
-
}
|
|
449
|
-
lastObservedSize = stat.size;
|
|
448
|
+
if (stat.size < offset) offset = 0;
|
|
450
449
|
if (stat.size > offset) {
|
|
451
450
|
const fd = fs.openSync(file, "r");
|
|
452
451
|
try {
|
|
453
452
|
const buf = Buffer.alloc(stat.size - offset);
|
|
454
|
-
|
|
455
|
-
offset
|
|
456
|
-
const text =
|
|
457
|
-
const
|
|
458
|
-
pendingLine = completeLines.pop() ?? "";
|
|
459
|
-
for (const line of completeLines.filter(Boolean)) writeStdoutLine(line);
|
|
453
|
+
fs.readSync(fd, buf, 0, buf.length, offset);
|
|
454
|
+
offset = stat.size;
|
|
455
|
+
const text = buf.toString("utf8");
|
|
456
|
+
for (const line of text.split("\n").filter(Boolean)) writeStdoutLine(line);
|
|
460
457
|
} finally {
|
|
461
458
|
fs.closeSync(fd);
|
|
462
459
|
}
|
|
@@ -545,12 +542,12 @@ function registerVoiceCallCli(params) {
|
|
|
545
542
|
//#endregion
|
|
546
543
|
//#region extensions/voice-call/src/gateway-continue-operation.ts
|
|
547
544
|
const VOICE_CALL_CONTINUE_OPERATION_BUFFER_MS = 3e4;
|
|
548
|
-
const VOICE_CALL_CONTINUE_OPERATION_CLEANUP_MS =
|
|
545
|
+
const VOICE_CALL_CONTINUE_OPERATION_CLEANUP_MS = 3e5;
|
|
549
546
|
/** Create a process-local operation store for gateway continue-call polling. */
|
|
550
547
|
function createVoiceCallContinueOperationStore(params) {
|
|
551
548
|
const operations = /* @__PURE__ */ new Map();
|
|
552
549
|
const resolvePollTimeoutMs = (rt) => {
|
|
553
|
-
const ttsTimeoutMs = rt.config.tts?.timeoutMs ?? params.config.tts?.timeoutMs ?? params.coreConfig.tts?.timeoutMs ?? 8e3;
|
|
550
|
+
const ttsTimeoutMs = rt.config.tts?.timeoutMs ?? params.config.tts?.timeoutMs ?? params.coreConfig.messages?.tts?.timeoutMs ?? 8e3;
|
|
554
551
|
return resolveTimerTimeoutMs((rt.config.transcriptTimeoutMs ?? params.config.transcriptTimeoutMs) + ttsTimeoutMs + VOICE_CALL_CONTINUE_OPERATION_BUFFER_MS, VOICE_CALL_CONTINUE_OPERATION_BUFFER_MS);
|
|
555
552
|
};
|
|
556
553
|
const scheduleCleanup = (operationId) => {
|
|
@@ -663,12 +660,12 @@ const VOICE_CALL_WRITE_METHOD_SCOPE = { scope: "operator.write" };
|
|
|
663
660
|
const VOICE_CALL_READ_METHOD_SCOPE = { scope: "operator.read" };
|
|
664
661
|
const voiceCallConfigSchema = {
|
|
665
662
|
parse(value) {
|
|
666
|
-
const
|
|
667
|
-
const enabled = typeof
|
|
668
|
-
return
|
|
669
|
-
...
|
|
663
|
+
const normalized = normalizeVoiceCallLegacyConfigInput(value);
|
|
664
|
+
const enabled = typeof normalized.enabled === "boolean" ? normalized.enabled : true;
|
|
665
|
+
return parseVoiceCallPluginConfig({
|
|
666
|
+
...normalized,
|
|
670
667
|
enabled,
|
|
671
|
-
provider:
|
|
668
|
+
provider: normalized.provider ?? (enabled ? "mock" : void 0)
|
|
672
669
|
});
|
|
673
670
|
},
|
|
674
671
|
uiHints: {
|
|
@@ -748,7 +745,6 @@ const voiceCallConfigSchema = {
|
|
|
748
745
|
},
|
|
749
746
|
"streaming.enabled": {
|
|
750
747
|
label: "Enable Streaming",
|
|
751
|
-
help: "Classic streaming transcription currently requires the Twilio call provider.",
|
|
752
748
|
advanced: true
|
|
753
749
|
},
|
|
754
750
|
"streaming.provider": {
|
|
@@ -839,7 +835,7 @@ const voiceCallConfigSchema = {
|
|
|
839
835
|
},
|
|
840
836
|
"tts.provider": {
|
|
841
837
|
label: "TTS Provider Override",
|
|
842
|
-
help: "Deep-merges with tts (Microsoft is ignored for calls).",
|
|
838
|
+
help: "Deep-merges with messages.tts (Microsoft is ignored for calls).",
|
|
843
839
|
advanced: true
|
|
844
840
|
},
|
|
845
841
|
"tts.providers": {
|
|
@@ -958,6 +954,11 @@ var voice_call_default = definePluginEntry({
|
|
|
958
954
|
register(api) {
|
|
959
955
|
const config = resolveVoiceCallConfig(voiceCallConfigSchema.parse(api.pluginConfig));
|
|
960
956
|
const validation = validateProviderConfig(config);
|
|
957
|
+
if (api.pluginConfig && typeof api.pluginConfig === "object") for (const warning of formatVoiceCallLegacyConfigWarnings({
|
|
958
|
+
value: api.pluginConfig,
|
|
959
|
+
configPathPrefix: "plugins.entries.voice-call.config",
|
|
960
|
+
doctorFixCommand: "openclaw doctor --fix"
|
|
961
|
+
})) api.logger.warn(warning);
|
|
961
962
|
const runtimeState = getVoiceCallRuntimeGlobalState();
|
|
962
963
|
const continueOperationStore = createVoiceCallContinueOperationStore({
|
|
963
964
|
config,
|
|
@@ -1362,10 +1363,7 @@ var voice_call_default = definePluginEntry({
|
|
|
1362
1363
|
ensureRuntime,
|
|
1363
1364
|
stateRuntime: api.runtime.state,
|
|
1364
1365
|
logger: api.logger
|
|
1365
|
-
}), {
|
|
1366
|
-
commands: ["voicecall"],
|
|
1367
|
-
descriptors: [VOICE_CALL_CLI_DESCRIPTOR]
|
|
1368
|
-
});
|
|
1366
|
+
}), { commands: ["voicecall"] });
|
|
1369
1367
|
api.registerService({
|
|
1370
1368
|
id: "voicecall",
|
|
1371
1369
|
start: () => {
|
package/dist/runtime-entry.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as createVoiceCallRuntime } from "
|
|
1
|
+
import { t as createVoiceCallRuntime } from "./.setup/runtime-entry-CH-CV0Sf.mjs";
|
|
2
2
|
export { createVoiceCallRuntime };
|
package/dist/setup-api.js
CHANGED
|
@@ -1,90 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { n as migrateVoiceCallLegacyConfigInput } from "./.setup/config-compat-CYRE-SSo.mjs";
|
|
2
|
+
import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
2
3
|
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
|
3
|
-
//#region extensions/voice-call/src/config-migration.ts
|
|
4
|
-
const asObject = asOptionalRecord;
|
|
5
|
-
const getString = readStringField;
|
|
6
|
-
/** Read finite numeric config values. */
|
|
7
|
-
function getNumber(obj, key) {
|
|
8
|
-
const value = obj?.[key];
|
|
9
|
-
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
10
|
-
}
|
|
11
|
-
/** Merge legacy provider-specific values into the canonical providers map. */
|
|
12
|
-
function mergeProviderConfig(providersValue, providerId, compatValues) {
|
|
13
|
-
if (Object.keys(compatValues).length === 0) return asObject(providersValue);
|
|
14
|
-
const providers = asObject(providersValue) ?? {};
|
|
15
|
-
const existing = asObject(providers[providerId]) ?? {};
|
|
16
|
-
return {
|
|
17
|
-
...providers,
|
|
18
|
-
[providerId]: {
|
|
19
|
-
...existing,
|
|
20
|
-
...compatValues
|
|
21
|
-
}
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
/** Migrate legacy voice-call config input to the current canonical shape. */
|
|
25
|
-
function migrateVoiceCallLegacyConfigInput(params) {
|
|
26
|
-
const raw = asObject(params.value) ?? {};
|
|
27
|
-
const realtime = asObject(raw.realtime);
|
|
28
|
-
const realtimeAgentContext = asObject(realtime?.agentContext);
|
|
29
|
-
const twilio = asObject(raw.twilio);
|
|
30
|
-
const streaming = asObject(raw.streaming);
|
|
31
|
-
const configPathPrefix = params.configPathPrefix ?? "plugins.entries.voice-call.config";
|
|
32
|
-
const legacyStreamingOpenAICompat = {};
|
|
33
|
-
const streamingOpenAIApiKey = getString(streaming, "openaiApiKey");
|
|
34
|
-
if (streamingOpenAIApiKey) legacyStreamingOpenAICompat.apiKey = streamingOpenAIApiKey;
|
|
35
|
-
const streamingSttModel = getString(streaming, "sttModel");
|
|
36
|
-
if (streamingSttModel) legacyStreamingOpenAICompat.model = streamingSttModel;
|
|
37
|
-
const streamingSilenceDurationMs = getNumber(streaming, "silenceDurationMs");
|
|
38
|
-
if (streamingSilenceDurationMs !== void 0) legacyStreamingOpenAICompat.silenceDurationMs = streamingSilenceDurationMs;
|
|
39
|
-
const streamingVadThreshold = getNumber(streaming, "vadThreshold");
|
|
40
|
-
if (streamingVadThreshold !== void 0) legacyStreamingOpenAICompat.vadThreshold = streamingVadThreshold;
|
|
41
|
-
const streamingProvider = getString(streaming, "provider");
|
|
42
|
-
const legacyStreamingProvider = getString(streaming, "sttProvider");
|
|
43
|
-
const normalizedStreaming = streaming ? {
|
|
44
|
-
...streaming,
|
|
45
|
-
provider: streamingProvider ?? legacyStreamingProvider,
|
|
46
|
-
providers: mergeProviderConfig(streaming.providers, "openai", legacyStreamingOpenAICompat)
|
|
47
|
-
} : void 0;
|
|
48
|
-
if (normalizedStreaming) {
|
|
49
|
-
delete normalizedStreaming.sttProvider;
|
|
50
|
-
delete normalizedStreaming.openaiApiKey;
|
|
51
|
-
delete normalizedStreaming.sttModel;
|
|
52
|
-
delete normalizedStreaming.silenceDurationMs;
|
|
53
|
-
delete normalizedStreaming.vadThreshold;
|
|
54
|
-
}
|
|
55
|
-
const normalizedTwilio = twilio ? { ...twilio } : void 0;
|
|
56
|
-
if (normalizedTwilio) delete normalizedTwilio.from;
|
|
57
|
-
const normalizedRealtimeAgentContext = realtimeAgentContext ? { ...realtimeAgentContext } : void 0;
|
|
58
|
-
if (normalizedRealtimeAgentContext) delete normalizedRealtimeAgentContext.includeSystemPrompt;
|
|
59
|
-
const normalizedRealtime = realtime ? {
|
|
60
|
-
...realtime,
|
|
61
|
-
agentContext: normalizedRealtimeAgentContext ?? realtime.agentContext
|
|
62
|
-
} : void 0;
|
|
63
|
-
const config = {
|
|
64
|
-
...raw,
|
|
65
|
-
provider: raw.provider === "log" ? "mock" : raw.provider,
|
|
66
|
-
fromNumber: raw.fromNumber ?? (typeof twilio?.from === "string" ? twilio.from : void 0),
|
|
67
|
-
twilio: normalizedTwilio,
|
|
68
|
-
streaming: normalizedStreaming,
|
|
69
|
-
realtime: normalizedRealtime
|
|
70
|
-
};
|
|
71
|
-
const changes = [];
|
|
72
|
-
if (raw.provider === "log") changes.push(`Moved ${configPathPrefix}.provider "log" → "mock".`);
|
|
73
|
-
if (typeof twilio?.from === "string" && typeof raw.fromNumber !== "string") changes.push(`Moved ${configPathPrefix}.twilio.from → ${configPathPrefix}.fromNumber.`);
|
|
74
|
-
if (typeof streaming?.sttProvider === "string") changes.push(`Moved ${configPathPrefix}.streaming.sttProvider → ${configPathPrefix}.streaming.provider.`);
|
|
75
|
-
if (typeof streaming?.openaiApiKey === "string") changes.push(`Moved ${configPathPrefix}.streaming.openaiApiKey → ${configPathPrefix}.streaming.providers.openai.apiKey.`);
|
|
76
|
-
if (typeof streaming?.sttModel === "string") changes.push(`Moved ${configPathPrefix}.streaming.sttModel → ${configPathPrefix}.streaming.providers.openai.model.`);
|
|
77
|
-
if (getNumber(streaming, "silenceDurationMs") !== void 0) changes.push(`Moved ${configPathPrefix}.streaming.silenceDurationMs → ${configPathPrefix}.streaming.providers.openai.silenceDurationMs.`);
|
|
78
|
-
else if (typeof streaming?.silenceDurationMs === "number") changes.push(`Removed invalid ${configPathPrefix}.streaming.silenceDurationMs.`);
|
|
79
|
-
if (getNumber(streaming, "vadThreshold") !== void 0) changes.push(`Moved ${configPathPrefix}.streaming.vadThreshold → ${configPathPrefix}.streaming.providers.openai.vadThreshold.`);
|
|
80
|
-
else if (typeof streaming?.vadThreshold === "number") changes.push(`Removed invalid ${configPathPrefix}.streaming.vadThreshold.`);
|
|
81
|
-
if (realtimeAgentContext && Object.hasOwn(realtimeAgentContext, "includeSystemPrompt")) changes.push(`Removed ${configPathPrefix}.realtime.agentContext.includeSystemPrompt.`);
|
|
82
|
-
return {
|
|
83
|
-
config,
|
|
84
|
-
changes
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
//#endregion
|
|
88
4
|
//#region extensions/voice-call/setup-api.ts
|
|
89
5
|
/** Migrate voice-call plugin config inside the full OpenClaw config object. */
|
|
90
6
|
function migrateVoiceCallPluginConfig(config) {
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
(The MIT License)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2011 TJ Holowaychuk <tj@vision-media.ca>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
6
|
+
a copy of this software and associated documentation files (the
|
|
7
|
+
'Software'), to deal in the Software without restriction, including
|
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
11
|
+
the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be
|
|
14
|
+
included in all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
19
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
20
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
21
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
22
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|