@openclaw/voice-call 2026.6.11 → 2026.7.1-beta.2

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.
@@ -2,7 +2,9 @@ import { TtsConfigSchema } from "./runtime-api.js";
2
2
  import "./api.js";
3
3
  import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
4
4
  import { REALTIME_VOICE_AGENT_CONSULT_TOOL_NAME, REALTIME_VOICE_AGENT_CONSULT_TOOL_POLICIES } from "openclaw/plugin-sdk/realtime-voice";
5
+ import { normalizeAgentId, parseAgentSessionKey } from "openclaw/plugin-sdk/routing";
5
6
  import { buildSecretInputSchema, hasConfiguredSecretInput, normalizeResolvedSecretInputString } from "openclaw/plugin-sdk/secret-input";
7
+ import { canonicalizeMainSessionAlias } from "openclaw/plugin-sdk/session-store-runtime";
6
8
  import { z } from "zod";
7
9
  //#region extensions/voice-call/src/deep-merge.ts
8
10
  const BLOCKED_MERGE_KEYS = new Set([
@@ -442,6 +444,13 @@ function resolveVoiceCallNumberRouteKey(config, phone) {
442
444
  if (!normalizedPhone) return;
443
445
  return Object.keys(routes).find((routeKey) => normalizePhoneRouteKey(routeKey) === normalizedPhone);
444
446
  }
447
+ /** Resolve inbound-only number routing from a persisted call record. */
448
+ function resolveVoiceCallNumberRouteKeyForCall(call) {
449
+ if (call.direction !== "inbound") return;
450
+ const storedRouteKey = call.metadata?.numberRouteKey;
451
+ if (typeof storedRouteKey === "string") return storedRouteKey;
452
+ return call.to;
453
+ }
445
454
  function resolveVoiceCallEffectiveConfig(config, phoneOrRouteKey) {
446
455
  const numberRouteKey = resolveVoiceCallNumberRouteKey(config, phoneOrRouteKey);
447
456
  if (!numberRouteKey) return { config };
@@ -538,10 +547,37 @@ function normalizeVoiceCallConfig(config) {
538
547
  }
539
548
  function resolveVoiceCallSessionKey(params) {
540
549
  const explicit = params.explicitSessionKey?.trim();
541
- if (explicit) return explicit;
542
- if (params.config.sessionScope === "per-call") return `voice:call:${params.callId}`;
550
+ if (explicit) return resolveVoiceCallAgentSessionKey({
551
+ config: params.config,
552
+ sessionKey: explicit,
553
+ coreSession: params.coreSession
554
+ });
555
+ const prefix = `agent:${normalizeAgentId(params.config.agentId)}:voice`;
556
+ if (params.config.sessionScope === "per-call") return `${prefix}:call:${params.callId}`.toLowerCase();
543
557
  const normalizedPhone = params.phone?.replace(/\D/g, "");
544
- return normalizedPhone ? `voice:${normalizedPhone}` : `voice:${params.callId}`;
558
+ return (normalizedPhone ? `${prefix}:${normalizedPhone}` : `${prefix}:${params.callId}`).toLowerCase();
559
+ }
560
+ /** Resolve persisted or integration-provided keys into the configured agent namespace. */
561
+ function resolveVoiceCallAgentSessionKey(params) {
562
+ const sessionKey = params.sessionKey.trim();
563
+ if (!sessionKey) throw new Error("Voice Call session key cannot be empty");
564
+ const lower = sessionKey.toLowerCase();
565
+ const agentId = normalizeAgentId(params.config.agentId);
566
+ if (lower === "global" || lower === "unknown") return lower;
567
+ const parsedInput = parseAgentSessionKey(sessionKey);
568
+ let normalizedScopedKey;
569
+ if (parsedInput && normalizeAgentId(parsedInput.agentId) === parsedInput.agentId && parsedInput.agentId === agentId) normalizedScopedKey = `agent:${parsedInput.agentId}:${parsedInput.rest}`;
570
+ else {
571
+ const wrappedInput = parseAgentSessionKey(`agent:${agentId}:${sessionKey}`);
572
+ if (!wrappedInput) throw new Error("Voice Call session key could not be normalized");
573
+ normalizedScopedKey = `agent:${agentId}:${wrappedInput.rest}`;
574
+ }
575
+ const canonicalMain = canonicalizeMainSessionAlias({
576
+ cfg: { session: params.coreSession },
577
+ agentId,
578
+ sessionKey: normalizedScopedKey
579
+ });
580
+ return canonicalMain === normalizedScopedKey ? normalizedScopedKey : canonicalMain;
545
581
  }
546
582
  /**
547
583
  * Resolves the configuration by merging environment variables into missing fields.
@@ -616,4 +652,4 @@ function validateProviderConfig(config) {
616
652
  };
617
653
  }
618
654
  //#endregion
619
- export { resolveVoiceCallEffectiveConfig as a, normalizePath as c, resolveVoiceCallConfig as i, deepMergeDefined as l, normalizeVoiceCallConfig as n, resolveVoiceCallSessionKey as o, resolveTwilioAuthToken as r, validateProviderConfig as s, VoiceCallConfigSchema as t };
655
+ export { resolveVoiceCallEffectiveConfig as a, validateProviderConfig as c, resolveVoiceCallConfig as i, normalizePath as l, normalizeVoiceCallConfig as n, resolveVoiceCallNumberRouteKeyForCall as o, resolveTwilioAuthToken as r, resolveVoiceCallSessionKey as s, VoiceCallConfigSchema as t, deepMergeDefined as u };
@@ -1,4 +1,4 @@
1
- import { t as VoiceCallConfigSchema } from "./config-BKDAAd8j.js";
1
+ import { t as VoiceCallConfigSchema } from "./config-DNtTB7Iw.js";
2
2
  import { asOptionalRecord, readStringField } from "openclaw/plugin-sdk/string-coerce-runtime";
3
3
  //#region extensions/voice-call/src/config-compat.ts
4
4
  /** Version where legacy voice-call config shape support is removed. */
@@ -1,4 +1,5 @@
1
1
  import { a as MAX_CALL_RECORD_EVENTS, f as prepareVoiceCallRecordForStorage, i as CALL_RECORD_EVENT_META_MAX_ENTRIES, n as CALL_RECORD_EVENTS_NAMESPACE, o as RAW_CALL_RECORD_CHUNK_BYTES, p as resolveVoiceCallLegacyCallLogPath, r as CALL_RECORD_EVENT_CHUNKS_NAMESPACE, s as buildVoiceCallLegacyJsonlEventKey, t as CALL_RECORD_CHUNK_MAX_ENTRIES, u as parseVoiceCallRecordLine } from "./store-8M2m4Isq.js";
2
+ import { normalizeAgentId } from "openclaw/plugin-sdk/routing";
2
3
  import os from "node:os";
3
4
  import path from "node:path";
4
5
  import fs from "node:fs/promises";
@@ -24,6 +25,26 @@ function getVoiceCallConfigStore(config) {
24
25
  }
25
26
  return "";
26
27
  }
28
+ function asRecord(value) {
29
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
30
+ }
31
+ /** Return Voice Call agents whose templated core session stores need migration. */
32
+ function resolveSessionStoreAgentIds(params) {
33
+ const agentIds = /* @__PURE__ */ new Set();
34
+ for (const pluginId of ["voice-call", "@openclaw/voice-call"]) {
35
+ const entry = params.cfg.plugins?.entries?.[pluginId];
36
+ if (!entry) continue;
37
+ const config = entry.config === void 0 ? {} : asRecord(entry.config);
38
+ if (!config) continue;
39
+ agentIds.add(normalizeAgentId(typeof config.agentId === "string" ? config.agentId : void 0));
40
+ const numbers = asRecord(config.numbers);
41
+ for (const route of Object.values(numbers ?? {})) {
42
+ const agentId = asRecord(route)?.agentId;
43
+ if (typeof agentId === "string") agentIds.add(normalizeAgentId(agentId));
44
+ }
45
+ }
46
+ return [...agentIds].toSorted();
47
+ }
27
48
  /** Resolve the voice-call store path used by legacy and plugin-state call records. */
28
49
  function resolveVoiceCallStorePath(params) {
29
50
  const configuredStore = getVoiceCallConfigStore(params.config);
@@ -220,4 +241,4 @@ const stateMigrations = [{
220
241
  }
221
242
  }];
222
243
  //#endregion
223
- export { stateMigrations };
244
+ export { resolveSessionStoreAgentIds, stateMigrations };
@@ -1,6 +1,6 @@
1
1
  import { fetchWithSsrFGuard } from "./runtime-api.js";
2
2
  import "./api.js";
3
- import { a as getHeader } from "./runtime-entry-DIAae7sB.js";
3
+ import { a as getHeader } from "./runtime-entry-DN7HllXj.js";
4
4
  import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
5
5
  import { isLoopbackHost } from "openclaw/plugin-sdk/gateway-runtime";
6
6
  import { isFutureDateTimestampMs, resolveExpiresAtMsFromDurationMs } from "openclaw/plugin-sdk/number-runtime";
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import { definePluginEntry, sleep } from "./runtime-api.js";
2
2
  import "./api.js";
3
- import { i as resolveVoiceCallConfig, s as validateProviderConfig } from "./config-BKDAAd8j.js";
4
- import { c as getTailscaleSelfInfo, l as setupTailscaleExposureRoute, o as resolveWebhookExposureStatus, p as resolveUserPath, s as cleanupTailscaleExposureRoute, t as createVoiceCallRuntime } from "./runtime-entry-DIAae7sB.js";
3
+ import { c as validateProviderConfig, i as resolveVoiceCallConfig } from "./config-DNtTB7Iw.js";
4
+ import { c as getTailscaleSelfInfo, l as setupTailscaleExposureRoute, o as resolveWebhookExposureStatus, p as resolveUserPath, s as cleanupTailscaleExposureRoute, t as createVoiceCallRuntime } from "./runtime-entry-DN7HllXj.js";
5
5
  import { c as getCallHistoryFromStore, m as setVoiceCallStateRuntime } from "./store-8M2m4Isq.js";
6
- import { i as parseVoiceCallPluginConfig, r as normalizeVoiceCallLegacyConfigInput, t as formatVoiceCallLegacyConfigWarnings } from "./config-compat-qDCcKTUt.js";
6
+ import { i as parseVoiceCallPluginConfig, r as normalizeVoiceCallLegacyConfigInput, t as formatVoiceCallLegacyConfigWarnings } from "./config-compat-CCEjHjAJ.js";
7
7
  import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
8
8
  import { ErrorCodes, callGatewayFromCli, errorShape } from "openclaw/plugin-sdk/gateway-runtime";
9
9
  import { MAX_TCP_PORT, MAX_TIMER_TIMEOUT_MS, addTimerTimeoutGraceMs, clampTimerTimeoutMs, parseStrictNonNegativeInteger, resolveTimerTimeoutMs, timestampMsToIsoString } from "openclaw/plugin-sdk/number-runtime";
@@ -915,6 +915,19 @@ function asParamRecord(params) {
915
915
  function isCliOnlyProcess() {
916
916
  return process.env.OPENCLAW_CLI === "1" && !process.argv.slice(2).includes("gateway");
917
917
  }
918
+ function toVoiceCallStatus(call) {
919
+ return {
920
+ callId: call.callId,
921
+ ...call.providerCallId !== void 0 ? { providerCallId: call.providerCallId } : {},
922
+ provider: call.provider,
923
+ direction: call.direction,
924
+ state: call.state,
925
+ startedAt: call.startedAt,
926
+ ...call.answeredAt !== void 0 ? { answeredAt: call.answeredAt } : {},
927
+ ...call.endedAt !== void 0 ? { endedAt: call.endedAt } : {},
928
+ ...call.endReason !== void 0 ? { endReason: call.endReason } : {}
929
+ };
930
+ }
918
931
  const VOICE_CALL_RUNTIME_KEY = Symbol.for("openclaw.voice-call.runtime");
919
932
  const VOICE_CALL_RUNTIME_PROMISE_KEY = Symbol.for("openclaw.voice-call.runtimePromise");
920
933
  const VOICE_CALL_RUNTIME_STOP_PROMISE_KEY = Symbol.for("openclaw.voice-call.runtimeStopPromise");
@@ -1183,7 +1196,7 @@ var voice_call_default = definePluginEntry({
1183
1196
  if (!raw) {
1184
1197
  respond(true, {
1185
1198
  found: true,
1186
- calls: rt.manager.getActiveCalls()
1199
+ calls: rt.manager.getActiveCalls().map(toVoiceCallStatus)
1187
1200
  });
1188
1201
  return;
1189
1202
  }
@@ -1194,7 +1207,7 @@ var voice_call_default = definePluginEntry({
1194
1207
  }
1195
1208
  respond(true, {
1196
1209
  found: true,
1197
- call
1210
+ call: toVoiceCallStatus(call)
1198
1211
  });
1199
1212
  } catch (err) {
1200
1213
  sendError(respond, err);
@@ -1299,7 +1312,7 @@ var voice_call_default = definePluginEntry({
1299
1312
  const call = rt.manager.getCall(callId) || rt.manager.getCallByProviderCallId(callId);
1300
1313
  return json(call ? {
1301
1314
  found: true,
1302
- call
1315
+ call: toVoiceCallStatus(call)
1303
1316
  } : { found: false });
1304
1317
  }
1305
1318
  }
@@ -1309,7 +1322,7 @@ var voice_call_default = definePluginEntry({
1309
1322
  const call = rt.manager.getCall(sid) || rt.manager.getCallByProviderCallId(sid);
1310
1323
  return json(call ? {
1311
1324
  found: true,
1312
- call
1325
+ call: toVoiceCallStatus(call)
1313
1326
  } : { found: false });
1314
1327
  }
1315
1328
  const to = normalizeOptionalString(rawParams.to) ?? rt.config.toNumber;
@@ -1,5 +1,5 @@
1
- import { a as getHeader, m as escapeXml } from "./runtime-entry-DIAae7sB.js";
2
- import { a as reconstructWebhookUrl, o as verifyPlivoWebhook, t as guardedJsonApiRequest } from "./guarded-json-api-BXeBxB7w.js";
1
+ import { a as getHeader, m as escapeXml } from "./runtime-entry-DN7HllXj.js";
2
+ import { a as reconstructWebhookUrl, o as verifyPlivoWebhook, t as guardedJsonApiRequest } from "./guarded-json-api-7ITxTL6G.js";
3
3
  import { normalizeLowercaseStringOrEmpty, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
4
4
  import crypto from "node:crypto";
5
5
  //#region extensions/voice-call/src/providers/plivo.ts
@@ -1,4 +1,4 @@
1
- import { c as normalizePath } from "./config-BKDAAd8j.js";
1
+ import { l as normalizePath } from "./config-DNtTB7Iw.js";
2
2
  import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
3
3
  import { isFutureDateTimestampMs, resolveExpiresAtMsFromDurationMs } from "openclaw/plugin-sdk/number-runtime";
4
4
  import { createSubsystemLogger } from "openclaw/plugin-sdk/runtime-env";
@@ -6,6 +6,7 @@ import { REALTIME_VOICE_AGENT_CONSULT_TOOL_NAME, buildRealtimeVoiceAgentConsultW
6
6
  import { randomUUID } from "node:crypto";
7
7
  import "node:http";
8
8
  import WebSocket, { WebSocketServer } from "ws";
9
+ import { sliceUtf16Safe, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
9
10
  //#region extensions/voice-call/src/webhook/realtime-audio-pacer.ts
10
11
  const TELEPHONY_SAMPLE_RATE = 8e3;
11
12
  const TELEPHONY_CHUNK_BYTES = 160;
@@ -379,7 +380,7 @@ function appendTranscriptText(base, fragment) {
379
380
  }
380
381
  function limitPartialUserTranscript(text) {
381
382
  if (text.length <= MAX_PARTIAL_USER_TRANSCRIPT_CHARS) return text;
382
- const tail = text.slice(-1200);
383
+ const tail = sliceUtf16Safe(text, -1200);
383
384
  return tail.replace(/^\S+\s+/, "").trimStart() || tail.trimStart();
384
385
  }
385
386
  function withFallbackConsultQuestion(args, fallback) {
@@ -412,7 +413,7 @@ function buildForcedConsultSpeechPrompt(result) {
412
413
  "Internal OpenClaw consult result is ready.",
413
414
  "Do not call tools for this internal result.",
414
415
  "Speak the following answer to the caller now, briefly and naturally:",
415
- trimmed.length <= FORCED_CONSULT_RESULT_MAX_CHARS ? trimmed : `${trimmed.slice(0, FORCED_CONSULT_RESULT_MAX_CHARS - 16).trimEnd()} [truncated]`
416
+ trimmed.length <= FORCED_CONSULT_RESULT_MAX_CHARS ? trimmed : `${truncateUtf16Safe(trimmed, FORCED_CONSULT_RESULT_MAX_CHARS - 16).trimEnd()} [truncated]`
416
417
  ].join("\n");
417
418
  }
418
419
  function appendRecentTalkEventMetadata(call, event) {
@@ -1,5 +1,5 @@
1
- import { o as resolveVoiceCallSessionKey } from "./config-BKDAAd8j.js";
2
- import { f as resolveVoiceResponseModel } from "./runtime-entry-DIAae7sB.js";
1
+ import { s as resolveVoiceCallSessionKey } from "./config-DNtTB7Iw.js";
2
+ import { f as resolveVoiceResponseModel } from "./runtime-entry-DN7HllXj.js";
3
3
  import { isRecord, normalizeLowercaseStringOrEmpty, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
4
4
  import crypto from "node:crypto";
5
5
  import { applyModelOverrideToSessionEntry } from "openclaw/plugin-sdk/model-session-runtime";
@@ -106,7 +106,8 @@ async function generateVoiceResponse(params) {
106
106
  config: voiceConfig,
107
107
  callId,
108
108
  phone: from,
109
- explicitSessionKey: sessionKey
109
+ explicitSessionKey: sessionKey,
110
+ coreSession: coreConfig.session
110
111
  });
111
112
  const agentId = voiceConfig.agentId ?? "main";
112
113
  const toolsAllow = resolveVoiceAgentToolsAllow(cfg, agentId);
@@ -154,7 +155,6 @@ async function generateVoiceResponse(params) {
154
155
  error: "Voice response session could not be initialized"
155
156
  };
156
157
  const sessionId = sessionEntry.sessionId;
157
- const sessionFile = agentRuntime.session.resolveSessionFilePath(sessionId, sessionEntry, { agentId });
158
158
  const thinkLevel = agentRuntime.resolveThinkingDefault({
159
159
  cfg,
160
160
  provider,
@@ -171,10 +171,15 @@ async function generateVoiceResponse(params) {
171
171
  const result = await agentRuntime.runEmbeddedAgent({
172
172
  sessionId,
173
173
  sessionKey: resolvedSessionKey,
174
+ sessionTarget: {
175
+ agentId,
176
+ sessionId,
177
+ sessionKey: resolvedSessionKey,
178
+ storePath
179
+ },
174
180
  sandboxSessionKey: resolveVoiceSandboxSessionKey(agentId, resolvedSessionKey),
175
181
  agentId,
176
182
  messageProvider: "voice",
177
- sessionFile,
178
183
  workspaceDir,
179
184
  config: cfg,
180
185
  prompt: userMessage,
@@ -1,11 +1,12 @@
1
1
  import { isBlockedHostnameOrIp, isRequestBodyLimitError, readRequestBodyWithLimit, requestBodyErrorToText } from "./runtime-api.js";
2
2
  import "./api.js";
3
- import { a as resolveVoiceCallEffectiveConfig, i as resolveVoiceCallConfig, l as deepMergeDefined, n as normalizeVoiceCallConfig, o as resolveVoiceCallSessionKey, r as resolveTwilioAuthToken, s as validateProviderConfig } from "./config-BKDAAd8j.js";
3
+ import { a as resolveVoiceCallEffectiveConfig, c as validateProviderConfig, i as resolveVoiceCallConfig, n as normalizeVoiceCallConfig, o as resolveVoiceCallNumberRouteKeyForCall, r as resolveTwilioAuthToken, s as resolveVoiceCallSessionKey, u as deepMergeDefined } from "./config-DNtTB7Iw.js";
4
4
  import { c as getCallHistoryFromStore, d as persistCallRecord, h as TerminalStates, l as loadActiveCallsFromStore, m as setVoiceCallStateRuntime } from "./store-8M2m4Isq.js";
5
5
  import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
6
6
  import { isLoopbackHost } from "openclaw/plugin-sdk/gateway-runtime";
7
7
  import { MAX_TIMER_TIMEOUT_MS, asDateTimestampMs, resolveExpiresAtMsFromDurationMs, resolveTimerTimeoutMs } from "openclaw/plugin-sdk/number-runtime";
8
8
  import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
9
+ import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
9
10
  import { REALTIME_VOICE_AGENT_CONSULT_TOOL_NAME, buildRealtimeVoiceAgentConsultPolicyInstructions, consultRealtimeVoiceAgent, convertPcmToMulaw8k, createTalkSessionController, recordTalkObservabilityEvent, resolveRealtimeVoiceAgentConsultTools, resolveRealtimeVoiceAgentConsultToolsAllow, resolveRealtimeVoiceFastContextConsult } from "openclaw/plugin-sdk/realtime-voice";
10
11
  import fs from "node:fs";
11
12
  import os from "node:os";
@@ -356,7 +357,8 @@ async function initiateCall(ctx, to, sessionKey, options) {
356
357
  config: ctx.config,
357
358
  callId,
358
359
  phone: to,
359
- explicitSessionKey: sessionKey
360
+ explicitSessionKey: sessionKey,
361
+ coreSession: ctx.coreSession
360
362
  }),
361
363
  startedAt: Date.now(),
362
364
  transcript: [],
@@ -438,7 +440,7 @@ async function speak(ctx, callId, text) {
438
440
  });
439
441
  transitionState(call, "speaking");
440
442
  persistCallRecord(ctx.storePath, call);
441
- const numberRouteKey = typeof call.metadata?.numberRouteKey === "string" ? call.metadata.numberRouteKey : call.to;
443
+ const numberRouteKey = resolveVoiceCallNumberRouteKeyForCall(call);
442
444
  const voice = resolvePreferredTtsVoice(resolveVoiceCallEffectiveConfig(ctx.config, numberRouteKey).config);
443
445
  await provider.playTts({
444
446
  callId,
@@ -910,7 +912,7 @@ function resolveDefaultStoreBase(config, storePath) {
910
912
  * Manages voice calls: state ownership and delegation to manager helper modules.
911
913
  */
912
914
  var CallManager = class {
913
- constructor(config, storePath) {
915
+ constructor(config, storePath, coreSession) {
914
916
  this.activeCalls = /* @__PURE__ */ new Map();
915
917
  this.providerCallIdMap = /* @__PURE__ */ new Map();
916
918
  this.processedEventIds = /* @__PURE__ */ new Set();
@@ -922,6 +924,7 @@ var CallManager = class {
922
924
  this.maxDurationTimers = /* @__PURE__ */ new Map();
923
925
  this.initialMessageInFlight = /* @__PURE__ */ new Set();
924
926
  this.config = config;
927
+ this.coreSession = coreSession;
925
928
  this.storePath = resolveDefaultStoreBase(config, storePath);
926
929
  }
927
930
  /**
@@ -1085,6 +1088,7 @@ var CallManager = class {
1085
1088
  rejectedProviderCallIds: this.rejectedProviderCallIds,
1086
1089
  provider: this.provider,
1087
1090
  config: this.config,
1091
+ coreSession: this.coreSession,
1088
1092
  storePath: this.storePath,
1089
1093
  webhookUrl: this.webhookUrl,
1090
1094
  activeTurnCalls: this.activeTurnCalls,
@@ -2503,16 +2507,8 @@ const WEBHOOK_BODY_TIMEOUT_MS = WEBHOOK_BODY_READ_DEFAULTS.preAuth.timeoutMs;
2503
2507
  const MISSING_REMOTE_ADDRESS_IN_FLIGHT_KEY = "__voice_call_no_remote__";
2504
2508
  const STREAM_DISCONNECT_HANGUP_GRACE_MS = 2e3;
2505
2509
  const TRANSCRIPT_LOG_MAX_CHARS = 200;
2506
- let realtimeTranscriptionRuntimePromise;
2507
- let responseGeneratorModulePromise;
2508
- function loadRealtimeTranscriptionRuntime() {
2509
- realtimeTranscriptionRuntimePromise ??= import("./realtime-transcription.runtime-CbJAs5t_.js");
2510
- return realtimeTranscriptionRuntimePromise;
2511
- }
2512
- function loadResponseGeneratorModule() {
2513
- responseGeneratorModulePromise ??= import("./response-generator-DBzGujH_.js");
2514
- return responseGeneratorModulePromise;
2515
- }
2510
+ const loadRealtimeTranscriptionRuntime = createLazyRuntimeModule(() => import("./realtime-transcription.runtime-CbJAs5t_.js"));
2511
+ const loadResponseGeneratorModule = createLazyRuntimeModule(() => import("./response-generator-CXBGKbAp.js"));
2516
2512
  function sanitizeTranscriptForLog(value) {
2517
2513
  const sanitized = value.replace(/\p{Cc}/gu, " ").replace(/\s+/g, " ").trim();
2518
2514
  if (sanitized.length <= TRANSCRIPT_LOG_MAX_CHARS) return sanitized;
@@ -3156,7 +3152,7 @@ var VoiceCallWebhookServer = class {
3156
3152
  }
3157
3153
  try {
3158
3154
  const { generateVoiceResponse } = await loadResponseGeneratorModule();
3159
- const numberRouteKey = typeof call.metadata?.numberRouteKey === "string" ? call.metadata.numberRouteKey : call.to;
3155
+ const numberRouteKey = resolveVoiceCallNumberRouteKeyForCall(call);
3160
3156
  const effectiveConfig = resolveVoiceCallEffectiveConfig(this.config, numberRouteKey).config;
3161
3157
  const result = await generateVoiceResponse({
3162
3158
  voiceConfig: effectiveConfig,
@@ -3191,43 +3187,19 @@ const REALTIME_VOICE_CONSULT_SYSTEM_PROMPT = [
3191
3187
  "Do not print secret values or dump environment variables; only check whether required configuration is present.",
3192
3188
  "Be accurate, brief, and speakable."
3193
3189
  ].join(" ");
3194
- let telnyxProviderPromise;
3195
- let twilioProviderPromise;
3196
- let plivoProviderPromise;
3197
- let mockProviderPromise;
3198
- let realtimeVoiceRuntimePromise;
3199
- let realtimeHandlerPromise;
3200
- function loadTelnyxProvider() {
3201
- telnyxProviderPromise ??= import("./telnyx-CYS_IihO.js");
3202
- return telnyxProviderPromise;
3203
- }
3204
- function loadTwilioProvider() {
3205
- twilioProviderPromise ??= import("./twilio-Bn1ta3tQ.js");
3206
- return twilioProviderPromise;
3207
- }
3208
- function loadPlivoProvider() {
3209
- plivoProviderPromise ??= import("./plivo-CPxvHw8k.js");
3210
- return plivoProviderPromise;
3211
- }
3212
- function loadMockProvider() {
3213
- mockProviderPromise ??= import("./mock-YAo3QDri.js");
3214
- return mockProviderPromise;
3215
- }
3216
- function loadRealtimeVoiceRuntime() {
3217
- realtimeVoiceRuntimePromise ??= import("./realtime-voice.runtime-vtdCOWg-.js");
3218
- return realtimeVoiceRuntimePromise;
3219
- }
3220
- function loadRealtimeHandler() {
3221
- realtimeHandlerPromise ??= import("./realtime-handler-rYbJUshl.js");
3222
- return realtimeHandlerPromise;
3223
- }
3190
+ const loadTelnyxProvider = createLazyRuntimeModule(() => import("./telnyx-DuUBsPW2.js"));
3191
+ const loadTwilioProvider = createLazyRuntimeModule(() => import("./twilio-CsJBvC3a.js"));
3192
+ const loadPlivoProvider = createLazyRuntimeModule(() => import("./plivo-wmeT9r_t.js"));
3193
+ const loadMockProvider = createLazyRuntimeModule(() => import("./mock-YAo3QDri.js"));
3194
+ const loadRealtimeVoiceRuntime = createLazyRuntimeModule(() => import("./realtime-voice.runtime-vtdCOWg-.js"));
3195
+ const loadRealtimeHandler = createLazyRuntimeModule(() => import("./realtime-handler-DdViwdCW.js"));
3224
3196
  function resolveVoiceCallConsultSessionKey(call) {
3225
- if (call.sessionKey) return call.sessionKey;
3226
- const phone = call.direction === "outbound" ? call.to : call.from;
3227
3197
  return resolveVoiceCallSessionKey({
3228
3198
  config: call.config,
3229
3199
  callId: call.callId,
3230
- phone
3200
+ phone: call.direction === "outbound" ? call.to : call.from,
3201
+ explicitSessionKey: call.sessionKey,
3202
+ coreSession: call.coreSession
3231
3203
  });
3232
3204
  }
3233
3205
  function mapVoiceCallConsultTranscript(call, context) {
@@ -3339,7 +3311,7 @@ async function createVoiceCallRuntime(params) {
3339
3311
  if (!validation.valid) throw new Error(`Invalid voice-call config: ${validation.errors.join("; ")}`);
3340
3312
  const provider = await resolveProvider(config);
3341
3313
  if (stateRuntime) setVoiceCallStateRuntime({ state: stateRuntime });
3342
- const manager = new CallManager(config);
3314
+ const manager = new CallManager(config, void 0, cfg.session);
3343
3315
  const realtimeProvider = config.realtime.enabled ? await resolveRealtimeProvider({
3344
3316
  config,
3345
3317
  fullConfig: cfg
@@ -3361,11 +3333,12 @@ async function createVoiceCallRuntime(params) {
3361
3333
  if (config.realtime.toolPolicy !== "none") realtimeHandler.registerToolHandler(REALTIME_VOICE_AGENT_CONSULT_TOOL_NAME, async (args, callId, handlerContext) => {
3362
3334
  const call = manager.getCall(callId);
3363
3335
  if (!call) return { error: `Call "${callId}" not found` };
3364
- const effectiveConfig = resolveVoiceCallEffectiveConfig(config, typeof call.metadata?.numberRouteKey === "string" ? call.metadata.numberRouteKey : call.to).config;
3336
+ const effectiveConfig = resolveVoiceCallEffectiveConfig(config, resolveVoiceCallNumberRouteKeyForCall(call)).config;
3365
3337
  const agentId = effectiveConfig.agentId ?? "main";
3366
3338
  const sessionKey = resolveVoiceCallConsultSessionKey({
3367
3339
  ...call,
3368
- config: effectiveConfig
3340
+ config: effectiveConfig,
3341
+ coreSession: cfg.session
3369
3342
  });
3370
3343
  const requesterSessionKey = typeof call.metadata?.requesterSessionKey === "string" ? call.metadata.requesterSessionKey : void 0;
3371
3344
  const fastContext = await resolveRealtimeFastContextConsult({
@@ -1,2 +1,2 @@
1
- import { t as createVoiceCallRuntime } from "./runtime-entry-DIAae7sB.js";
1
+ import { t as createVoiceCallRuntime } from "./runtime-entry-DN7HllXj.js";
2
2
  export { createVoiceCallRuntime };
package/dist/setup-api.js CHANGED
@@ -1,4 +1,4 @@
1
- import { n as migrateVoiceCallLegacyConfigInput } from "./config-compat-qDCcKTUt.js";
1
+ import { n as migrateVoiceCallLegacyConfigInput } from "./config-compat-CCEjHjAJ.js";
2
2
  import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
3
3
  import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
4
4
  //#region extensions/voice-call/setup-api.ts
@@ -1,4 +1,4 @@
1
- import { s as verifyTelnyxWebhook, t as guardedJsonApiRequest } from "./guarded-json-api-BXeBxB7w.js";
1
+ import { s as verifyTelnyxWebhook, t as guardedJsonApiRequest } from "./guarded-json-api-7ITxTL6G.js";
2
2
  import crypto from "node:crypto";
3
3
  //#region extensions/voice-call/src/providers/telnyx.ts
4
4
  function normalizeTelnyxDirection(direction) {
@@ -1,7 +1,7 @@
1
1
  import { fetchWithSsrFGuard } from "./runtime-api.js";
2
2
  import "./api.js";
3
- import { a as getHeader, d as chunkAudio, h as mapVoiceToPolly, i as normalizeProviderStatus, m as escapeXml, n as isProviderStatusTerminal, r as mapProviderStatusToEndReason } from "./runtime-entry-DIAae7sB.js";
4
- import { c as verifyTwilioWebhook, i as readProviderJsonResponseText, n as cancelProviderResponseBody, r as readProviderErrorResponseSnippet, t as guardedJsonApiRequest } from "./guarded-json-api-BXeBxB7w.js";
3
+ import { a as getHeader, d as chunkAudio, h as mapVoiceToPolly, i as normalizeProviderStatus, m as escapeXml, n as isProviderStatusTerminal, r as mapProviderStatusToEndReason } from "./runtime-entry-DN7HllXj.js";
4
+ import { c as verifyTwilioWebhook, i as readProviderJsonResponseText, n as cancelProviderResponseBody, r as readProviderErrorResponseSnippet, t as guardedJsonApiRequest } from "./guarded-json-api-7ITxTL6G.js";
5
5
  import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
6
6
  import crypto from "node:crypto";
7
7
  import { safeEqualSecret } from "openclaw/plugin-sdk/security-runtime";
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@openclaw/voice-call",
3
- "version": "2026.6.11",
3
+ "version": "2026.7.1-beta.2",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@openclaw/voice-call",
9
- "version": "2026.6.11",
9
+ "version": "2026.7.1-beta.2",
10
10
  "dependencies": {
11
11
  "commander": "14.0.3",
12
12
  "typebox": "1.1.39",
@@ -14,7 +14,7 @@
14
14
  "zod": "4.4.3"
15
15
  },
16
16
  "peerDependencies": {
17
- "openclaw": ">=2026.6.11"
17
+ "openclaw": ">=2026.7.1-beta.2"
18
18
  },
19
19
  "peerDependenciesMeta": {
20
20
  "openclaw": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/voice-call",
3
- "version": "2026.6.11",
3
+ "version": "2026.7.1-beta.2",
4
4
  "description": "OpenClaw voice-call plugin for Twilio, Telnyx, and Plivo phone calls.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -14,7 +14,7 @@
14
14
  "zod": "4.4.3"
15
15
  },
16
16
  "peerDependencies": {
17
- "openclaw": ">=2026.6.11"
17
+ "openclaw": ">=2026.7.1-beta.2"
18
18
  },
19
19
  "peerDependenciesMeta": {
20
20
  "openclaw": {
@@ -31,10 +31,10 @@
31
31
  "minHostVersion": ">=2026.4.10"
32
32
  },
33
33
  "compat": {
34
- "pluginApi": ">=2026.6.11"
34
+ "pluginApi": ">=2026.7.1-beta.2"
35
35
  },
36
36
  "build": {
37
- "openclawVersion": "2026.6.11"
37
+ "openclawVersion": "2026.7.1-beta.2"
38
38
  },
39
39
  "release": {
40
40
  "publishToClawHub": true,