@openclaw/voice-call 2026.7.2-beta.2 → 2026.7.2-beta.4

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.
Files changed (29) hide show
  1. package/README.md +1 -1
  2. package/dist/doctor-contract-api.js +1 -0
  3. package/dist/{guarded-json-api-BKOgUU1q.js → guarded-json-api-Dxhe25Zi.js} +1 -2
  4. package/dist/index.js +10 -6
  5. package/dist/{plivo-ByAIoPFp.js → plivo-B-KvPVji.js} +2 -3
  6. package/dist/{realtime-handler-DJk-eYyf.js → realtime-handler-hIVPEZxN.js} +95 -175
  7. package/dist/{response-generator-Cdj8EuLg.js → response-generator-z6M9229t.js} +1 -1
  8. package/dist/{runtime-entry-DSgyqIwE.js → runtime-entry-CQtitvRd.js} +89 -152
  9. package/dist/runtime-entry.js +1 -1
  10. package/dist/{telnyx-DMbLYwj4.js → telnyx-BG56lL7z.js} +1 -1
  11. package/dist/{twilio-BTKKlkLT.js → twilio-B3FQ_P9x.js} +9 -5
  12. package/node_modules/typebox/build/compile/validator.d.mts +1 -4
  13. package/node_modules/typebox/build/guard/guard.d.mts +1 -1
  14. package/node_modules/typebox/build/guard/guard.mjs +5 -2
  15. package/node_modules/typebox/build/guard/string.mjs +16 -4
  16. package/node_modules/typebox/build/schema/engine/_functions.mjs +34 -15
  17. package/node_modules/typebox/build/type/action/readonly_object.d.mts +5 -0
  18. package/node_modules/typebox/build/type/action/readonly_object.mjs +8 -0
  19. package/node_modules/typebox/build/typebox.d.mts +1 -1
  20. package/node_modules/typebox/build/typebox.mjs +1 -1
  21. package/node_modules/typebox/package.json +1 -1
  22. package/node_modules/typebox/readme.md +29 -32
  23. package/node_modules/ws/lib/receiver.js +15 -32
  24. package/node_modules/ws/lib/websocket-server.js +4 -4
  25. package/node_modules/ws/lib/websocket.js +4 -4
  26. package/node_modules/ws/package.json +5 -1
  27. package/npm-shrinkwrap.json +11 -11
  28. package/openclaw.plugin.json +1 -15
  29. package/package.json +6 -6
@@ -8,6 +8,7 @@ import { MAX_TIMER_TIMEOUT_MS, asDateTimestampMs, resolveExpiresAtMsFromDuration
8
8
  import { normalizeAgentId, parseAgentSessionKey } from "openclaw/plugin-sdk/routing";
9
9
  import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
10
10
  import { createSubsystemLogger } from "openclaw/plugin-sdk/runtime-env";
11
+ import { resolveDefaultAgentId } from "openclaw/plugin-sdk/agent-runtime";
11
12
  import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
12
13
  import { REALTIME_VOICE_AGENT_CONSULT_TOOL_NAME, REALTIME_VOICE_AGENT_CONSULT_TOOL_POLICIES, assertRealtimeVoiceAgentConsultModelSelectionUnlocked, buildRealtimeVoiceAgentConsultPolicyInstructions, consultRealtimeVoiceAgent, convertPcmToMulaw8k, createTalkSessionController, recordTalkObservabilityEvent, resolveRealtimeVoiceAgentConsultTools, resolveRealtimeVoiceAgentConsultToolsAllow, resolveRealtimeVoiceFastContextConsult } from "openclaw/plugin-sdk/realtime-voice";
13
14
  import { mergeDeep } from "openclaw/plugin-sdk/plugin-config-runtime";
@@ -23,13 +24,13 @@ import path from "node:path";
23
24
  import os from "node:os";
24
25
  import { root } from "openclaw/plugin-sdk/security-runtime";
25
26
  import { sliceUtf16Safe, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
26
- import { parseTtsDirectives } from "openclaw/plugin-sdk/speech";
27
27
  import { spawn } from "node:child_process";
28
28
  import { runCommandWithTimeout } from "openclaw/plugin-sdk/process-runtime";
29
29
  import http from "node:http";
30
30
  import { URL as URL$1 } from "node:url";
31
31
  import { resolveConfiguredCapabilityProvider } from "openclaw/plugin-sdk/provider-selection-runtime";
32
32
  import { WebSocket, WebSocketServer } from "ws";
33
+ import { canonicalizeBase64 } from "openclaw/plugin-sdk/media-runtime";
33
34
  //#region extensions/voice-call/src/realtime-defaults.ts
34
35
  /** Baseline instructions that keep realtime calls brief and route deep work to agent consult. */
35
36
  const DEFAULT_VOICE_CALL_REALTIME_INSTRUCTIONS = `You are OpenClaw's phone-call realtime voice interface. Keep spoken replies brief and natural. When a question needs deeper reasoning, current information, or tools, call ${REALTIME_VOICE_AGENT_CONSULT_TOOL_NAME} before answering.`;
@@ -405,7 +406,7 @@ const VoiceCallConfigSchema = z.object({
405
406
  publicUrl: z.string().url().optional(),
406
407
  /** Skip webhook signature verification (development only, NOT for production) */
407
408
  skipSignatureVerification: z.boolean().default(false),
408
- /** TTS override (deep-merges with core messages.tts) */
409
+ /** TTS override (deep-merges with core tts) */
409
410
  tts: TtsConfigSchema,
410
411
  /** Store path for call logs */
411
412
  store: z.string().optional(),
@@ -1839,7 +1840,7 @@ async function buildRealtimeVoiceInstructions(params) {
1839
1840
  if (consultGuidance) sections.push(consultGuidance);
1840
1841
  const contextConfig = config.realtime.agentContext;
1841
1842
  if (!contextConfig.enabled) return sections.filter(Boolean).join("\n\n");
1842
- const agentId = config.agentId ?? "main";
1843
+ const { agentId } = params;
1843
1844
  const capsule = [
1844
1845
  "OpenClaw agent voice context:",
1845
1846
  `- Agent id: ${agentId}`,
@@ -1913,26 +1914,26 @@ function chunkAudio(audio, chunkSize = 160) {
1913
1914
  /** Default timeout for one telephony synthesis request. */
1914
1915
  const TELEPHONY_DEFAULT_TTS_TIMEOUT_MS = 8e3;
1915
1916
  /** Create a TTS provider that honors voice-call overrides and converts PCM to mulaw. */
1916
- function createTelephonyTtsProvider(params) {
1917
+ async function createTelephonyTtsProvider(params) {
1917
1918
  const { coreConfig, ttsOverride, runtime, logger } = params;
1918
- const mergedConfig = applyTtsOverride(coreConfig, ttsOverride);
1919
- const ttsConfig = mergedConfig.messages?.tts;
1920
- const modelOverrides = resolveTelephonyModelOverridePolicy(readTelephonyModelOverrides(ttsConfig));
1921
- const providerConfigs = collectTelephonyProviderConfigs(ttsConfig);
1922
- const activeProvider = normalizeProviderId(ttsConfig?.provider);
1919
+ const preparedConfig = await runtime.prepareTtsRequest({
1920
+ cfg: coreConfig,
1921
+ override: ttsOverride,
1922
+ text: ""
1923
+ });
1923
1924
  return {
1924
- synthesisTimeoutMs: resolveTimerTimeoutMs(mergedConfig.messages?.tts?.timeoutMs, TELEPHONY_DEFAULT_TTS_TIMEOUT_MS),
1925
+ synthesisTimeoutMs: resolveTimerTimeoutMs(preparedConfig.cfg.tts?.timeoutMs, TELEPHONY_DEFAULT_TTS_TIMEOUT_MS),
1925
1926
  synthesizeForTelephony: async (text) => {
1926
- const directives = parseTtsDirectives(text, modelOverrides, {
1927
- cfg: mergedConfig,
1928
- providerConfigs,
1929
- preferredProviderId: activeProvider
1927
+ const prepared = await runtime.prepareTtsRequest({
1928
+ cfg: preparedConfig.cfg,
1929
+ text
1930
1930
  });
1931
+ const directives = prepared.directives;
1931
1932
  if (directives.warnings.length > 0) logger?.warn?.(`[voice-call] Ignored telephony TTS directive overrides (${directives.warnings.join("; ")})`);
1932
1933
  const cleanText = directives.hasDirective ? directives.ttsText?.trim() || directives.cleanedText.trim() : text;
1933
1934
  const result = await runtime.textToSpeechTelephony({
1934
1935
  text: cleanText,
1935
- cfg: mergedConfig,
1936
+ cfg: prepared.cfg,
1936
1937
  overrides: directives.overrides
1937
1938
  });
1938
1939
  if (!result.success || !result.audioBuffer || !result.sampleRate) throw new Error(result.error ?? "TTS conversion failed");
@@ -1944,94 +1945,6 @@ function createTelephonyTtsProvider(params) {
1944
1945
  }
1945
1946
  };
1946
1947
  }
1947
- /** Apply voice-call TTS overrides to core config without mutating the original object. */
1948
- function applyTtsOverride(coreConfig, override) {
1949
- if (!override) return coreConfig;
1950
- const base = coreConfig.messages?.tts;
1951
- const merged = mergeTtsConfig(base, override);
1952
- if (!merged) return coreConfig;
1953
- return {
1954
- ...coreConfig,
1955
- messages: {
1956
- ...coreConfig.messages,
1957
- tts: merged
1958
- }
1959
- };
1960
- }
1961
- /** Merge core and voice-call TTS config, keeping undefined override fields out. */
1962
- function mergeTtsConfig(base, override) {
1963
- if (!base && !override) return;
1964
- if (!override) return base;
1965
- if (!base) return override;
1966
- return mergeDeep(base, override);
1967
- }
1968
- /** Resolve directive override policy for telephony synthesis. */
1969
- function resolveTelephonyModelOverridePolicy(overrides) {
1970
- if (!(overrides?.enabled ?? true)) return {
1971
- enabled: false,
1972
- allowText: false,
1973
- allowProvider: false,
1974
- allowVoice: false,
1975
- allowModelId: false,
1976
- allowVoiceSettings: false,
1977
- allowNormalization: false,
1978
- allowSeed: false
1979
- };
1980
- const allow = (value, defaultValue = true) => value ?? defaultValue;
1981
- return {
1982
- enabled: true,
1983
- allowText: allow(overrides?.allowText),
1984
- allowProvider: allow(overrides?.allowProvider, false),
1985
- allowVoice: allow(overrides?.allowVoice),
1986
- allowModelId: allow(overrides?.allowModelId),
1987
- allowVoiceSettings: allow(overrides?.allowVoiceSettings),
1988
- allowNormalization: allow(overrides?.allowNormalization),
1989
- allowSeed: allow(overrides?.allowSeed)
1990
- };
1991
- }
1992
- /** Read model override policy from TTS config when present. */
1993
- function readTelephonyModelOverrides(ttsConfig) {
1994
- const value = ttsConfig?.modelOverrides;
1995
- return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
1996
- }
1997
- /** Normalize provider ids for config lookup. */
1998
- function normalizeProviderId(value) {
1999
- return typeof value === "string" ? value.trim().toLowerCase() || void 0 : void 0;
2000
- }
2001
- /** Coerce provider config objects while rejecting arrays and primitives. */
2002
- function asProviderConfig(value) {
2003
- return value && typeof value === "object" && !Array.isArray(value) ? value : {};
2004
- }
2005
- /** Collect named provider configs from canonical and legacy TTS config shapes. */
2006
- function collectTelephonyProviderConfigs(ttsConfig) {
2007
- if (!ttsConfig) return {};
2008
- const entries = {};
2009
- const rawProviders = ttsConfig.providers && typeof ttsConfig.providers === "object" && !Array.isArray(ttsConfig.providers) ? ttsConfig.providers : {};
2010
- for (const [providerId, value] of Object.entries(rawProviders)) {
2011
- const normalized = normalizeProviderId(providerId) ?? providerId;
2012
- entries[normalized] = asProviderConfig(value);
2013
- }
2014
- const reservedKeys = /* @__PURE__ */ new Set([
2015
- "auto",
2016
- "enabled",
2017
- "maxTextLength",
2018
- "mode",
2019
- "modelOverrides",
2020
- "persona",
2021
- "personas",
2022
- "prefsPath",
2023
- "provider",
2024
- "providers",
2025
- "summaryModel",
2026
- "timeoutMs"
2027
- ]);
2028
- for (const [key, value] of Object.entries(ttsConfig)) {
2029
- if (reservedKeys.has(key) || typeof value !== "object" || value === null || Array.isArray(value)) continue;
2030
- const normalized = normalizeProviderId(key) ?? key;
2031
- entries[normalized] ??= asProviderConfig(value);
2032
- }
2033
- return entries;
2034
- }
2035
1948
  //#endregion
2036
1949
  //#region extensions/voice-call/src/bounded-child-output.ts
2037
1950
  const DEFAULT_MAX_OUTPUT_CHARS = 16384;
@@ -2159,6 +2072,30 @@ async function cleanupTailscaleExposure(config) {
2159
2072
  const NGROK_LOG_BUFFER_MAX_CHARS = 16384;
2160
2073
  const NGROK_ERROR_MARKER = "ERR_NGROK";
2161
2074
  const TUNNEL_COMMAND_OUTPUT_MAX_BYTES = 16384;
2075
+ const NGROK_STOP_GRACE_MS = 2e3;
2076
+ const NGROK_FORCE_KILL_WAIT_MS = 1e3;
2077
+ async function terminateNgrokProcess(proc, isClosed) {
2078
+ if (isClosed()) return;
2079
+ await new Promise((resolve) => {
2080
+ let finished = false;
2081
+ let forceKillWaitTimer;
2082
+ const finish = () => {
2083
+ if (finished) return;
2084
+ finished = true;
2085
+ if (forceKillTimer) clearTimeout(forceKillTimer);
2086
+ if (forceKillWaitTimer) clearTimeout(forceKillWaitTimer);
2087
+ proc.off("close", finish);
2088
+ resolve();
2089
+ };
2090
+ proc.once("close", finish);
2091
+ const forceKillTimer = setTimeout(() => {
2092
+ forceKillWaitTimer = setTimeout(finish, NGROK_FORCE_KILL_WAIT_MS);
2093
+ if (!isClosed()) proc.kill("SIGKILL");
2094
+ }, NGROK_STOP_GRACE_MS);
2095
+ proc.kill("SIGTERM");
2096
+ if (isClosed()) finish();
2097
+ });
2098
+ }
2162
2099
  function listenForChildStreamErrors(proc, onError) {
2163
2100
  proc.stdout.on("error", (error) => onError("stdout", error));
2164
2101
  proc.stderr.on("error", (error) => onError("stderr", error));
@@ -2194,23 +2131,25 @@ async function startNgrokTunnel(config) {
2194
2131
  "pipe",
2195
2132
  "pipe"
2196
2133
  ] });
2197
- let resolved = false;
2198
- let closed = false;
2134
+ let startupSettled = false;
2135
+ let childClosed = false;
2199
2136
  let publicUrl = null;
2200
2137
  let outputBuffer = "";
2201
2138
  let stderrTail = "";
2202
2139
  const timeout = setTimeout(() => {
2203
- if (!resolved) {
2204
- resolved = true;
2205
- proc.kill("SIGTERM");
2206
- reject(/* @__PURE__ */ new Error("ngrok startup timed out (30s)"));
2140
+ if (!startupSettled) {
2141
+ startupSettled = true;
2142
+ terminateNgrokProcess(proc, () => childClosed).then(() => {
2143
+ reject(/* @__PURE__ */ new Error("ngrok startup timed out (30s)"));
2144
+ });
2207
2145
  }
2208
2146
  }, 3e4);
2147
+ timeout.unref();
2209
2148
  const rejectIfPending = (message, kill = false) => {
2210
- if (!resolved) {
2211
- resolved = true;
2149
+ if (!startupSettled) {
2150
+ startupSettled = true;
2212
2151
  clearTimeout(timeout);
2213
- if (kill && !closed) proc.kill("SIGKILL");
2152
+ if (kill && !childClosed) proc.kill("SIGKILL");
2214
2153
  reject(new Error(message));
2215
2154
  }
2216
2155
  };
@@ -2219,8 +2158,8 @@ async function startNgrokTunnel(config) {
2219
2158
  const log = JSON.parse(line);
2220
2159
  if (log.msg === "started tunnel" && log.url) publicUrl = log.url;
2221
2160
  if (log.addr && log.url && !publicUrl) publicUrl = log.url;
2222
- if (publicUrl && !resolved) {
2223
- resolved = true;
2161
+ if (publicUrl && !startupSettled) {
2162
+ startupSettled = true;
2224
2163
  clearTimeout(timeout);
2225
2164
  const fullUrl = publicUrl + config.path;
2226
2165
  console.log(`[voice-call] ngrok tunnel active: ${fullUrl}`);
@@ -2228,38 +2167,22 @@ async function startNgrokTunnel(config) {
2228
2167
  publicUrl: fullUrl,
2229
2168
  provider: "ngrok",
2230
2169
  stop: async () => {
2231
- if (closed) return;
2232
- await new Promise((res) => {
2233
- let finished = false;
2234
- const finish = () => {
2235
- if (finished) return;
2236
- finished = true;
2237
- clearTimeout(fallback);
2238
- proc.off("close", finish);
2239
- res();
2240
- };
2241
- if (closed) {
2242
- res();
2243
- return;
2244
- }
2245
- proc.once("close", finish);
2246
- const fallback = setTimeout(finish, 2e3);
2247
- proc.kill("SIGTERM");
2248
- if (closed) finish();
2249
- });
2170
+ await terminateNgrokProcess(proc, () => childClosed);
2250
2171
  }
2251
2172
  });
2252
2173
  }
2253
2174
  } catch {}
2254
2175
  };
2255
- proc.stdout.on("data", (data) => {
2256
- const lines = (outputBuffer + data.toString()).split("\n");
2176
+ proc.stdout.setEncoding("utf8");
2177
+ proc.stderr.setEncoding("utf8");
2178
+ proc.stdout.on("data", (chunk) => {
2179
+ const lines = (outputBuffer + chunk).split("\n");
2257
2180
  outputBuffer = lines.pop() || "";
2258
2181
  if (outputBuffer.length > NGROK_LOG_BUFFER_MAX_CHARS) outputBuffer = sliceUtf16Safe(outputBuffer, -16384);
2259
2182
  for (const line of lines) if (line.trim()) processLine(line);
2260
2183
  });
2261
- proc.stderr.on("data", (data) => {
2262
- const combined = stderrTail + data.toString();
2184
+ proc.stderr.on("data", (chunk) => {
2185
+ const combined = stderrTail + chunk;
2263
2186
  if (combined.includes(NGROK_ERROR_MARKER)) rejectIfPending(`ngrok error: ${formatBoundedChildOutput(appendBoundedChildOutput(emptyBoundedChildOutput(), combined))}`, true);
2264
2187
  stderrTail = sliceUtf16Safe(combined, -8);
2265
2188
  });
@@ -2270,9 +2193,9 @@ async function startNgrokTunnel(config) {
2270
2193
  rejectIfPending(`Failed to start ngrok: ${err.message}`);
2271
2194
  });
2272
2195
  proc.on("close", (code) => {
2273
- closed = true;
2274
- if (!resolved) {
2275
- resolved = true;
2196
+ childClosed = true;
2197
+ if (!startupSettled) {
2198
+ startupSettled = true;
2276
2199
  clearTimeout(timeout);
2277
2200
  reject(/* @__PURE__ */ new Error(`ngrok exited unexpectedly with code ${code}`));
2278
2201
  }
@@ -2446,6 +2369,11 @@ function getHeader(headers, name) {
2446
2369
  return value;
2447
2370
  }
2448
2371
  //#endregion
2372
+ //#region extensions/voice-call/src/media-base64.ts
2373
+ function canonicalizeVoiceCallMediaBase64(payloadBase64) {
2374
+ return canonicalizeBase64(payloadBase64.replaceAll("-", "+").replaceAll("_", "/"));
2375
+ }
2376
+ //#endregion
2449
2377
  //#region extensions/voice-call/src/media-stream.ts
2450
2378
  const DEFAULT_PRE_START_TIMEOUT_MS = 5e3;
2451
2379
  const DEFAULT_MAX_PENDING_CONNECTIONS = 32;
@@ -2560,7 +2488,9 @@ var MediaStreamHandler = class {
2560
2488
  break;
2561
2489
  case "media":
2562
2490
  if (session && message.media?.payload) {
2563
- const audioBuffer = Buffer.from(message.media.payload, "base64");
2491
+ const canonicalPayload = canonicalizeVoiceCallMediaBase64(message.media.payload);
2492
+ if (!canonicalPayload) break;
2493
+ const audioBuffer = Buffer.from(canonicalPayload, "base64");
2564
2494
  const turnId = this.ensureActiveTurn(session);
2565
2495
  this.emitTalkEvent(session, {
2566
2496
  type: "input.audio.delta",
@@ -3141,7 +3071,7 @@ const WEBHOOK_BODY_TIMEOUT_MS = WEBHOOK_BODY_READ_DEFAULTS.preAuth.timeoutMs;
3141
3071
  const MISSING_REMOTE_ADDRESS_IN_FLIGHT_KEY = "__voice_call_no_remote__";
3142
3072
  const STREAM_DISCONNECT_HANGUP_GRACE_MS = 2e3;
3143
3073
  const loadRealtimeTranscriptionRuntime = createLazyRuntimeModule(() => import("./realtime-transcription.runtime-CbJAs5t_.js"));
3144
- const loadResponseGeneratorModule = createLazyRuntimeModule(() => import("./response-generator-Cdj8EuLg.js"));
3074
+ const loadResponseGeneratorModule = createLazyRuntimeModule(() => import("./response-generator-z6M9229t.js"));
3145
3075
  function appendRecentTalkEventMetadata(call, event) {
3146
3076
  const metadata = call.metadata ?? {};
3147
3077
  const recent = Array.isArray(metadata.recentTalkEvents) ? metadata.recentTalkEvents.filter((entry) => Boolean(entry) && typeof entry === "object" && !Array.isArray(entry)) : [];
@@ -3816,12 +3746,12 @@ const REALTIME_VOICE_CONSULT_SYSTEM_PROMPT = [
3816
3746
  "Do not print secret values or dump environment variables; only check whether required configuration is present.",
3817
3747
  "Be accurate, brief, and speakable."
3818
3748
  ].join(" ");
3819
- const loadTelnyxProvider = createLazyRuntimeModule(() => import("./telnyx-DMbLYwj4.js"));
3820
- const loadTwilioProvider = createLazyRuntimeModule(() => import("./twilio-BTKKlkLT.js"));
3821
- const loadPlivoProvider = createLazyRuntimeModule(() => import("./plivo-ByAIoPFp.js"));
3749
+ const loadTelnyxProvider = createLazyRuntimeModule(() => import("./telnyx-BG56lL7z.js"));
3750
+ const loadTwilioProvider = createLazyRuntimeModule(() => import("./twilio-B3FQ_P9x.js"));
3751
+ const loadPlivoProvider = createLazyRuntimeModule(() => import("./plivo-B-KvPVji.js"));
3822
3752
  const loadMockProvider = createLazyRuntimeModule(() => import("./mock-YsvBTX-7.js"));
3823
3753
  const loadRealtimeVoiceRuntime = createLazyRuntimeModule(() => import("./realtime-voice.runtime-vtdCOWg-.js"));
3824
- const loadRealtimeHandler = createLazyRuntimeModule(() => import("./realtime-handler-DJk-eYyf.js"));
3754
+ const loadRealtimeHandler = createLazyRuntimeModule(() => import("./realtime-handler-hIVPEZxN.js"));
3825
3755
  function resolveVoiceCallConsultSessionKey(call) {
3826
3756
  return resolveVoiceCallSessionKey({
3827
3757
  config: call.config,
@@ -3946,7 +3876,8 @@ async function createRealtimeInstructionsResolver(params) {
3946
3876
  baseInstructions: params.config.realtime.instructions,
3947
3877
  config: genericConfig,
3948
3878
  coreConfig: params.coreConfig,
3949
- agentRuntime: params.agentRuntime
3879
+ agentRuntime: params.agentRuntime,
3880
+ agentId: params.config.agentId
3950
3881
  });
3951
3882
  const entries = await Promise.all(listRealtimeAgentIds(params.config, params.coreConfig).map(async (agentId) => {
3952
3883
  return [agentId, await buildRealtimeVoiceInstructions({
@@ -3956,7 +3887,8 @@ async function createRealtimeInstructionsResolver(params) {
3956
3887
  agentId
3957
3888
  },
3958
3889
  coreConfig: params.coreConfig,
3959
- agentRuntime: params.agentRuntime
3890
+ agentRuntime: params.agentRuntime,
3891
+ agentId
3960
3892
  })];
3961
3893
  }));
3962
3894
  const instructionsByAgentId = new Map(entries);
@@ -3974,8 +3906,13 @@ async function createVoiceCallRuntime(params) {
3974
3906
  error: console.error,
3975
3907
  debug: console.debug
3976
3908
  };
3977
- const config = resolveVoiceCallConfig(rawConfig);
3978
3909
  const cfg = fullConfig ?? coreConfig;
3910
+ const unresolvedConfig = resolveVoiceCallConfig(rawConfig);
3911
+ const configuredAgentId = unresolvedConfig.agentId ? normalizeAgentId(unresolvedConfig.agentId) : resolveDefaultAgentId(cfg);
3912
+ const config = {
3913
+ ...unresolvedConfig,
3914
+ agentId: configuredAgentId
3915
+ };
3979
3916
  if (!config.enabled) throw new Error("Voice call disabled. Enable the plugin entry in config.");
3980
3917
  if (config.skipSignatureVerification) log.warn("[voice-call] SECURITY WARNING: skipSignatureVerification=true disables webhook signature verification (development only). Do not use in production.");
3981
3918
  const validation = validateProviderConfig(config);
@@ -4101,8 +4038,8 @@ async function createVoiceCallRuntime(params) {
4101
4038
  if (provider.name === "twilio" && config.streaming?.enabled) {
4102
4039
  const twilioProvider = provider;
4103
4040
  if (ttsRuntime?.textToSpeechTelephony) try {
4104
- const ttsProvider = createTelephonyTtsProvider({
4105
- coreConfig,
4041
+ const ttsProvider = await createTelephonyTtsProvider({
4042
+ coreConfig: cfg,
4106
4043
  ttsOverride: config.tts,
4107
4044
  runtime: ttsRuntime,
4108
4045
  logger: log
@@ -4140,4 +4077,4 @@ async function createVoiceCallRuntime(params) {
4140
4077
  }
4141
4078
  }
4142
4079
  //#endregion
4143
- export { mapVoiceToPolly as _, normalizeProviderStatus as a, resolveVoiceCallSessionKey as b, cleanupTailscaleExposureRoute as c, TELEPHONY_DEFAULT_TTS_TIMEOUT_MS as d, chunkAudio as f, escapeXml as g, resolveUserPath as h, mapProviderStatusToEndReason as i, getTailscaleSelfInfo as l, resolveCallAgentId as m, normalizeProxyIp as n, getHeader as o, resolveVoiceResponseModel as p, isProviderStatusTerminal as r, resolveWebhookExposureStatus as s, createVoiceCallRuntime as t, setupTailscaleExposureRoute as u, VoiceCallConfigSchema as v, validateProviderConfig as x, resolveVoiceCallConfig as y };
4080
+ export { validateProviderConfig as S, escapeXml as _, normalizeProviderStatus as a, resolveVoiceCallConfig as b, resolveWebhookExposureStatus as c, setupTailscaleExposureRoute as d, TELEPHONY_DEFAULT_TTS_TIMEOUT_MS as f, resolveUserPath as g, resolveCallAgentId as h, mapProviderStatusToEndReason as i, cleanupTailscaleExposureRoute as l, resolveVoiceResponseModel as m, normalizeProxyIp as n, canonicalizeVoiceCallMediaBase64 as o, chunkAudio as p, isProviderStatusTerminal as r, getHeader as s, createVoiceCallRuntime as t, getTailscaleSelfInfo as u, mapVoiceToPolly as v, resolveVoiceCallSessionKey as x, VoiceCallConfigSchema as y };
@@ -1,2 +1,2 @@
1
- import { t as createVoiceCallRuntime } from "./runtime-entry-DSgyqIwE.js";
1
+ import { t as createVoiceCallRuntime } from "./runtime-entry-CQtitvRd.js";
2
2
  export { createVoiceCallRuntime };
@@ -1,4 +1,4 @@
1
- import { s as verifyTelnyxWebhook, t as guardedJsonApiRequest } from "./guarded-json-api-BKOgUU1q.js";
1
+ import { s as verifyTelnyxWebhook, t as guardedJsonApiRequest } from "./guarded-json-api-Dxhe25Zi.js";
2
2
  import crypto from "node:crypto";
3
3
  //#region extensions/voice-call/src/providers/telnyx.ts
4
4
  function normalizeTelnyxDirection(direction) {
@@ -1,9 +1,10 @@
1
1
  import { fetchWithSsrFGuard } from "./runtime-api.js";
2
2
  import "./api.js";
3
3
  import { n as requireSupportedTwilioApiHostname, r as resolveTwilioApiBaseUrl } from "./twilio-region-4fkgz3UG.js";
4
- import { _ as mapVoiceToPolly, a as normalizeProviderStatus, f as chunkAudio, g as escapeXml, i as mapProviderStatusToEndReason, o as getHeader, r as isProviderStatusTerminal } from "./runtime-entry-DSgyqIwE.js";
5
- import { c as verifyTwilioWebhook, i as readProviderJsonResponseText, n as cancelProviderResponseBody, r as readProviderErrorResponseSnippet, t as guardedJsonApiRequest } from "./guarded-json-api-BKOgUU1q.js";
4
+ import { _ as escapeXml, a as normalizeProviderStatus, i as mapProviderStatusToEndReason, p as chunkAudio, r as isProviderStatusTerminal, s as getHeader, v as mapVoiceToPolly } from "./runtime-entry-CQtitvRd.js";
5
+ import { c as verifyTwilioWebhook, i as readProviderJsonResponseText, n as cancelProviderResponseBody, r as readProviderErrorResponseSnippet, t as guardedJsonApiRequest } from "./guarded-json-api-Dxhe25Zi.js";
6
6
  import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
7
+ import { sleepWithAbort } from "openclaw/plugin-sdk/runtime-env";
7
8
  import crypto from "node:crypto";
8
9
  import { safeEqualSecret } from "openclaw/plugin-sdk/security-runtime";
9
10
  import { setTimeout as setTimeout$1 } from "node:timers/promises";
@@ -628,9 +629,12 @@ var TwilioProvider = class TwilioProvider {
628
629
  chunkAttempts += 1;
629
630
  if (sendAudioChunk(chunk).sent) chunkDelivered += 1;
630
631
  const waitMs = nextChunkDueAt - Date.now();
631
- if (waitMs > 0) await new Promise((resolve) => {
632
- setTimeout(resolve, Math.ceil(waitMs));
633
- });
632
+ if (waitMs > 0) try {
633
+ await sleepWithAbort(Math.ceil(waitMs), signal);
634
+ } catch (error) {
635
+ if (!signal.aborted) throw error;
636
+ break;
637
+ }
634
638
  nextChunkDueAt += CHUNK_DELAY_MS;
635
639
  if (signal.aborted) break;
636
640
  }
@@ -1,14 +1,11 @@
1
1
  import { type TLocalizedValidationError } from '../error/index.mjs';
2
2
  import { type StaticDecode, type StaticEncode, type TProperties, type TSchema } from '../type/index.mjs';
3
- import { BuildResult, EvaluateResult } from '../schema/index.mjs';
4
3
  export declare class Validator<Context extends TProperties = TProperties, Type extends TSchema = TSchema, Encode extends unknown = StaticEncode<Type, Context>, Decode extends unknown = StaticDecode<Type, Context>> {
5
4
  private readonly hasCodec;
6
5
  private readonly buildResult;
7
6
  private readonly evaluateResult;
8
- /** Constructs a Validator with the given Context and Type. */
7
+ /** Constructs a Validator. */
9
8
  constructor(context: Context, type: Type);
10
- /** Constructs a Validator with the given arguments. */
11
- constructor(hasCodec: boolean, buildResult: BuildResult, evaluateResult: EvaluateResult);
12
9
  /** Returns true if this Validator is using JIT acceleration. */
13
10
  IsAccelerated(): boolean;
14
11
  /** Returns the Context for this validator. */
@@ -61,5 +61,5 @@ export declare function Keys(value: Record<PropertyKey, unknown>): string[];
61
61
  export declare function Symbols(value: Record<PropertyKey, unknown>): symbol[];
62
62
  /** Returns the property values for the given object via `Object.values()` */
63
63
  export declare function Values(value: Record<PropertyKey, unknown>): unknown[];
64
- /** Tests values for deep equality */
64
+ /** Returns true if left and right values are structurally equal */
65
65
  export declare function IsDeepEqual(left: unknown, right: unknown): boolean;
@@ -1,3 +1,4 @@
1
+ // deno-fmt-ignore-file
1
2
  import * as String from './string.mjs';
2
3
  // --------------------------------------------------------------------------
3
4
  // Guards
@@ -203,7 +204,9 @@ function DeepEqualArray(left, right) {
203
204
  return IsArray(right) && IsEqual(left.length, right.length) &&
204
205
  left.every((_, index) => IsDeepEqual(left[index], right[index]));
205
206
  }
206
- /** Tests values for deep equality */
207
+ /** Returns true if left and right values are structurally equal */
207
208
  export function IsDeepEqual(left, right) {
208
- return (IsArray(left) ? DeepEqualArray(left, right) : IsObject(left) ? DeepEqualObject(left, right) : IsEqual(left, right));
209
+ return (IsArray(left) ? DeepEqualArray(left, right) :
210
+ IsObject(left) ? DeepEqualObject(left, right) :
211
+ IsEqual(left, right));
209
212
  }
@@ -5,6 +5,18 @@ function IsBetween(value, min, max) {
5
5
  return value >= min && value <= max;
6
6
  }
7
7
  // --------------------------------------------------------------------------
8
+ // IsZeroWidthJoiner
9
+ // --------------------------------------------------------------------------
10
+ function IsZeroWidthJoiner(value) {
11
+ return (value === 0x200D);
12
+ }
13
+ // --------------------------------------------------------------------------
14
+ // IsHighSurrogate
15
+ // --------------------------------------------------------------------------
16
+ function IsHighSurrogate(value) {
17
+ return IsBetween(value, 0xD800, 0xDBFF);
18
+ }
19
+ // --------------------------------------------------------------------------
8
20
  // IsRegionalIndicator
9
21
  // --------------------------------------------------------------------------
10
22
  function IsRegionalIndicator(value) {
@@ -72,10 +84,10 @@ function NextGraphemeClusterIndex(value, clusterStart) {
72
84
  // IsGraphemeCodePoint
73
85
  // --------------------------------------------------------------------------
74
86
  function IsGraphemeCodePoint(value) {
75
- return (IsBetween(value, 0xD800, 0xDBFF) || // High surrogate
76
- IsBetween(value, 0x0300, 0x036F) || // Combining diacritical marks
77
- (value === 0x200D) // Zero-width joiner
78
- );
87
+ return (IsHighSurrogate(value) ||
88
+ IsCombiningMark(value) ||
89
+ IsVariationSelector(value) ||
90
+ IsZeroWidthJoiner(value));
79
91
  }
80
92
  // --------------------------------------------------------------------------
81
93
  // GraphemeCount
@@ -1,47 +1,66 @@
1
1
  // deno-fmt-ignore-file
2
- import * as Schema from '../types/index.mjs';
3
2
  import { Hashing } from '../../system/hashing/index.mjs';
4
3
  import { EmitGuard as E } from '../../guard/index.mjs';
5
4
  import { BuildSchema } from './schema.mjs';
6
- const functions = new Map();
5
+ const index = [0];
6
+ const names = new Map;
7
+ const funcs = new Map();
8
+ // ------------------------------------------------------------------
9
+ // CreateName
10
+ // ------------------------------------------------------------------
11
+ function NextName() {
12
+ return Hashing.Hash(index[0]++);
13
+ }
14
+ function CreateName(schema, href) {
15
+ if (!names.has(schema))
16
+ names.set(schema, new Map());
17
+ const hrefs = names.get(schema);
18
+ if (hrefs.has(href))
19
+ return hrefs.get(href);
20
+ const name = NextName();
21
+ hrefs.set(href, name);
22
+ return name;
23
+ }
7
24
  // ------------------------------------------------------------------
8
25
  // CreateCallExpression
9
26
  // ------------------------------------------------------------------
10
- function CreateCallExpression(context, _schema, hash, value) {
27
+ function CreateCallExpression(context, _schema, name, value) {
11
28
  return context.UseUnevaluated()
12
- ? E.Call(`check_${hash}`, ['context', value])
13
- : E.Call(`check_${hash}`, [value]);
29
+ ? E.Call(`check_${name}`, ['context', value])
30
+ : E.Call(`check_${name}`, [value]);
14
31
  }
15
32
  // ------------------------------------------------------------------
16
33
  // CreateFunctionExpression
17
34
  // ------------------------------------------------------------------
18
- function CreateFunctionExpression(stack, context, schema, hash) {
35
+ function CreateFunctionExpression(stack, context, schema, name) {
19
36
  const expression = BuildSchema(stack, context, schema, 'value');
20
37
  return context.UseUnevaluated()
21
- ? E.ConstDeclaration(`check_${hash}`, E.ArrowFunction(['context', 'value'], expression))
22
- : E.ConstDeclaration(`check_${hash}`, E.ArrowFunction(['value'], expression));
38
+ ? E.ConstDeclaration(`check_${name}`, E.ArrowFunction(['context', 'value'], expression))
39
+ : E.ConstDeclaration(`check_${name}`, E.ArrowFunction(['value'], expression));
23
40
  }
24
41
  // ------------------------------------------------------------------
25
42
  // ResetFunctions
26
43
  // ------------------------------------------------------------------
27
44
  export function ResetFunctions() {
28
- functions.clear();
45
+ index[0] = 0;
46
+ names.clear();
47
+ funcs.clear();
29
48
  }
30
49
  // ------------------------------------------------------------------
31
50
  // GetFunctions
32
51
  // ------------------------------------------------------------------
33
52
  export function GetFunctions() {
34
- return [...functions.values()];
53
+ return [...funcs.values()];
35
54
  }
36
55
  // ------------------------------------------------------------------
37
56
  // CreateFunction
38
57
  // ------------------------------------------------------------------
39
58
  export function CreateFunction(stack, context, schema, value) {
40
- const hash = Schema.IsSchemaObject(schema) ? Hashing.Hash({ __baseURL: stack.BaseURL().href, ...schema }) : Hashing.Hash(schema);
41
- const call = CreateCallExpression(context, schema, hash, value);
42
- if (functions.has(hash))
59
+ const name = CreateName(schema, stack.BaseURL().href);
60
+ const call = CreateCallExpression(context, schema, name, value);
61
+ if (funcs.has(name))
43
62
  return call;
44
- functions.set(hash, '');
45
- functions.set(hash, CreateFunctionExpression(stack, context, schema, hash));
63
+ funcs.set(name, '');
64
+ funcs.set(name, CreateFunctionExpression(stack, context, schema, name));
46
65
  return call;
47
66
  }
@@ -9,3 +9,8 @@ export declare function ReadonlyObjectDeferred<Type extends TSchema>(type: Type,
9
9
  export type TReadonlyObject<Type extends TSchema> = (TReadonlyObjectAction<Type>);
10
10
  /** This type is an alias for TypeScript's `Readonly<T>` utility type. It will make all properties of a TObject readonly or marks an TArray or TTuple as immutable `readonly T[]`. */
11
11
  export declare function ReadonlyObject<Type extends TSchema>(type: Type, options?: TSchemaOptions): TReadonlyObject<Type>;
12
+ /**
13
+ * This type has been renamed to ReadonlyObject.
14
+ * @deprecated
15
+ */
16
+ export declare const ReadonlyType: typeof ReadonlyObject;
@@ -9,3 +9,11 @@ export function ReadonlyObjectDeferred(type, options = {}) {
9
9
  export function ReadonlyObject(type, options = {}) {
10
10
  return ReadonlyObjectAction(type, options);
11
11
  }
12
+ // ------------------------------------------------------------------
13
+ // Alias
14
+ // ------------------------------------------------------------------
15
+ /**
16
+ * This type has been renamed to ReadonlyObject.
17
+ * @deprecated
18
+ */
19
+ export const ReadonlyType = ReadonlyObject;
@@ -19,7 +19,7 @@ export { Omit, type TOmit, type TOmitDeferred } from './type/action/omit.mjs';
19
19
  export { Parameters, type TParameters, type TParametersDeferred } from './type/action/parameters.mjs';
20
20
  export { Partial, type TPartial, type TPartialDeferred } from './type/action/partial.mjs';
21
21
  export { Pick, type TPick, type TPickDeferred } from './type/action/pick.mjs';
22
- export { ReadonlyObject, type TReadonlyObject, type TReadonlyObjectDeferred } from './type/action/readonly_object.mjs';
22
+ export { ReadonlyObject, ReadonlyType, type TReadonlyObject, type TReadonlyObjectDeferred } from './type/action/readonly_object.mjs';
23
23
  export { Required, type TRequired, type TRequiredDeferred } from './type/action/required.mjs';
24
24
  export { ReturnType, type TReturnType, type TReturnTypeDeferred } from './type/action/return_type.mjs';
25
25
  export { type TUncapitalize, type TUncapitalizeDeferred, Uncapitalize } from './type/action/uncapitalize.mjs';