Vendor OpenClaw source as Adolf fork baseline
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled

Adolf is a fork/vendored clone of github.com/openclaw/openclaw (v2026.6.11),
free to diverge. Tree copied sans upstream .git; upstream remote added for
future syncs. Node pinned to 24 (.nvmrc); engines already require >=22.19.
Preserves docs/ARCHITECTURE.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
This commit is contained in:
2026-07-05 09:36:54 +00:00
parent 3216769225
commit bedb527145
21108 changed files with 6010766 additions and 0 deletions

View File

@@ -0,0 +1,715 @@
// Voice Call tests cover config plugin behavior.
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import {
VoiceCallConfigSchema,
resolveVoiceCallAgentSessionKey,
resolveTwilioAuthToken,
resolveVoiceCallEffectiveConfig,
resolveVoiceCallNumberRouteKey,
resolveVoiceCallNumberRouteKeyForCall,
resolveVoiceCallSessionKey,
validateProviderConfig,
normalizeVoiceCallConfig,
resolveVoiceCallConfig,
type VoiceCallConfig,
} from "./config.js";
import { createVoiceCallBaseConfig } from "./test-fixtures.js";
function createBaseConfig(provider: "telnyx" | "twilio" | "plivo" | "mock"): VoiceCallConfig {
return createVoiceCallBaseConfig({ provider });
}
function envRef(id: string) {
return { source: "env" as const, provider: "default", id };
}
function requireElevenLabsTtsConfig(config: Pick<VoiceCallConfig, "tts">) {
const tts = config.tts;
const elevenlabs = tts?.providers?.elevenlabs;
if (!elevenlabs || typeof elevenlabs !== "object") {
throw new Error("voice-call config did not preserve nested elevenlabs TTS config");
}
return { tts, elevenlabs };
}
describe("validateProviderConfig", () => {
const originalEnv = { ...process.env };
const clearProviderEnv = () => {
delete process.env.TWILIO_ACCOUNT_SID;
delete process.env.TWILIO_AUTH_TOKEN;
delete process.env.TWILIO_FROM_NUMBER;
delete process.env.TELNYX_API_KEY;
delete process.env.TELNYX_CONNECTION_ID;
delete process.env.TELNYX_PUBLIC_KEY;
delete process.env.PLIVO_AUTH_ID;
delete process.env.PLIVO_AUTH_TOKEN;
};
beforeEach(() => {
clearProviderEnv();
});
afterEach(() => {
// Restore original env
process.env = { ...originalEnv };
});
describe("provider credential sources", () => {
it("passes validation when credentials come from config or environment", () => {
for (const provider of ["twilio", "telnyx", "plivo"] as const) {
clearProviderEnv();
const fromConfig = createBaseConfig(provider);
if (provider === "twilio") {
fromConfig.twilio = { accountSid: "AC123", authToken: "secret" };
} else if (provider === "telnyx") {
fromConfig.telnyx = {
apiKey: "KEY123",
connectionId: "CONN456",
publicKey: "public-key",
};
} else {
fromConfig.plivo = { authId: "MA123", authToken: "secret" };
}
expect(validateProviderConfig(fromConfig)).toEqual({ valid: true, errors: [] });
clearProviderEnv();
if (provider === "twilio") {
process.env.TWILIO_ACCOUNT_SID = "AC123";
process.env.TWILIO_AUTH_TOKEN = "secret";
process.env.TWILIO_FROM_NUMBER = "+15550001234";
} else if (provider === "telnyx") {
process.env.TELNYX_API_KEY = "KEY123";
process.env.TELNYX_CONNECTION_ID = "CONN456";
process.env.TELNYX_PUBLIC_KEY = "public-key";
} else {
process.env.PLIVO_AUTH_ID = "MA123";
process.env.PLIVO_AUTH_TOKEN = "secret";
}
const fromEnv = resolveVoiceCallConfig(createBaseConfig(provider));
expect(validateProviderConfig(fromEnv)).toEqual({ valid: true, errors: [] });
}
});
});
describe("twilio provider", () => {
it("accepts SecretRef-backed auth tokens before runtime resolution", () => {
const config = VoiceCallConfigSchema.parse({
enabled: true,
provider: "twilio",
fromNumber: "+15550001234",
twilio: {
accountSid: "AC123",
authToken: envRef("TWILIO_AUTH_TOKEN"),
},
});
expect(config.twilio?.authToken).toEqual(envRef("TWILIO_AUTH_TOKEN"));
expect(validateProviderConfig(config)).toEqual({ valid: true, errors: [] });
expect(() => resolveTwilioAuthToken(config)).toThrow(
'plugins.entries.voice-call.config.twilio.authToken: unresolved SecretRef "env:default:TWILIO_AUTH_TOKEN"',
);
});
it("passes validation with mixed config and env vars", () => {
process.env.TWILIO_AUTH_TOKEN = "secret";
let config = createBaseConfig("twilio");
config.twilio = { accountSid: "AC123" };
config = resolveVoiceCallConfig(config);
const result = validateProviderConfig(config);
expect(result.valid).toBe(true);
expect(result.errors).toStrictEqual([]);
});
it("resolves the Twilio from number from environment", () => {
process.env.TWILIO_ACCOUNT_SID = "AC123";
process.env.TWILIO_AUTH_TOKEN = "secret";
process.env.TWILIO_FROM_NUMBER = "+15550001234";
const config = resolveVoiceCallConfig({
...createBaseConfig("twilio"),
fromNumber: undefined,
});
expect(config.fromNumber).toBe("+15550001234");
expect(validateProviderConfig(config)).toEqual({ valid: true, errors: [] });
});
it("fails validation when required twilio credentials are missing", () => {
process.env.TWILIO_AUTH_TOKEN = "secret";
const missingSid = validateProviderConfig(resolveVoiceCallConfig(createBaseConfig("twilio")));
expect(missingSid.valid).toBe(false);
expect(missingSid.errors).toContain(
"plugins.entries.voice-call.config.twilio.accountSid is required (or set TWILIO_ACCOUNT_SID env)",
);
delete process.env.TWILIO_AUTH_TOKEN;
process.env.TWILIO_ACCOUNT_SID = "AC123";
const missingToken = validateProviderConfig(
resolveVoiceCallConfig(createBaseConfig("twilio")),
);
expect(missingToken.valid).toBe(false);
expect(missingToken.errors).toContain(
"plugins.entries.voice-call.config.twilio.authToken is required (or set TWILIO_AUTH_TOKEN env)",
);
});
});
describe("telnyx provider", () => {
it("fails validation when apiKey is missing everywhere", () => {
process.env.TELNYX_CONNECTION_ID = "CONN456";
let config = createBaseConfig("telnyx");
config = resolveVoiceCallConfig(config);
const result = validateProviderConfig(config);
expect(result.valid).toBe(false);
expect(result.errors).toContain(
"plugins.entries.voice-call.config.telnyx.apiKey is required (or set TELNYX_API_KEY env)",
);
});
it("requires a public key unless signature verification is skipped", () => {
const missingPublicKey = createBaseConfig("telnyx");
missingPublicKey.inboundPolicy = "allowlist";
missingPublicKey.telnyx = { apiKey: "KEY123", connectionId: "CONN456" };
const missingPublicKeyResult = validateProviderConfig(missingPublicKey);
expect(missingPublicKeyResult.valid).toBe(false);
expect(missingPublicKeyResult.errors).toContain(
"plugins.entries.voice-call.config.telnyx.publicKey is required (or set TELNYX_PUBLIC_KEY env)",
);
const withPublicKey = createBaseConfig("telnyx");
withPublicKey.inboundPolicy = "allowlist";
withPublicKey.telnyx = {
apiKey: "KEY123",
connectionId: "CONN456",
publicKey: "public-key",
};
expect(validateProviderConfig(withPublicKey)).toEqual({ valid: true, errors: [] });
const skippedVerification = createBaseConfig("telnyx");
skippedVerification.skipSignatureVerification = true;
skippedVerification.telnyx = { apiKey: "KEY123", connectionId: "CONN456" };
expect(validateProviderConfig(skippedVerification)).toEqual({
valid: true,
errors: [],
});
});
});
describe("plivo provider", () => {
it("fails validation when authId is missing everywhere", () => {
process.env.PLIVO_AUTH_TOKEN = "secret";
let config = createBaseConfig("plivo");
config = resolveVoiceCallConfig(config);
const result = validateProviderConfig(config);
expect(result.valid).toBe(false);
expect(result.errors).toContain(
"plugins.entries.voice-call.config.plivo.authId is required (or set PLIVO_AUTH_ID env)",
);
});
});
describe("disabled config", () => {
it("skips validation when enabled is false", () => {
const config = createBaseConfig("twilio");
config.enabled = false;
const result = validateProviderConfig(config);
expect(result.valid).toBe(true);
expect(result.errors).toStrictEqual([]);
});
});
describe("realtime config", () => {
it("rejects disabled inbound policy for realtime mode", () => {
const config = createBaseConfig("twilio");
config.realtime.enabled = true;
config.inboundPolicy = "disabled";
const result = validateProviderConfig(config);
expect(result.valid).toBe(false);
expect(result.errors).toContain(
'plugins.entries.voice-call.config.inboundPolicy must not be "disabled" when realtime.enabled is true',
);
});
it("rejects enabling realtime and streaming together", () => {
const config = createBaseConfig("twilio");
config.realtime.enabled = true;
config.streaming.enabled = true;
config.inboundPolicy = "allowlist";
const result = validateProviderConfig(config);
expect(result.valid).toBe(false);
expect(result.errors).toContain(
"plugins.entries.voice-call.config.realtime.enabled and plugins.entries.voice-call.config.streaming.enabled cannot both be true",
);
});
it("accepts realtime.enabled with provider=telnyx", () => {
const config = createBaseConfig("telnyx");
config.realtime.enabled = true;
config.inboundPolicy = "allowlist";
const result = validateProviderConfig(config);
expect(result.errors).not.toContain(
'plugins.entries.voice-call.config.provider must be "twilio" or "telnyx" when realtime.enabled is true',
);
});
it("rejects realtime.enabled with providers that do not support it yet", () => {
const config = createBaseConfig("plivo");
config.realtime.enabled = true;
config.inboundPolicy = "allowlist";
const result = validateProviderConfig(config);
expect(result.valid).toBe(false);
expect(result.errors).toContain(
'plugins.entries.voice-call.config.provider must be "twilio" or "telnyx" when realtime.enabled is true',
);
});
});
});
describe("resolveVoiceCallConfig session routing", () => {
it("enables the pre-answer stale call reaper by default", () => {
const config = resolveVoiceCallConfig({ enabled: true, provider: "mock" });
expect(config.staleCallReaperSeconds).toBe(120);
});
it("keeps voice sessions scoped by phone by default", () => {
const config = resolveVoiceCallConfig({ enabled: true, provider: "mock" });
expect(config.sessionScope).toBe("per-phone");
expect(
resolveVoiceCallSessionKey({
config,
callId: "call-123",
phone: "+1 (555) 000-1111",
}),
).toBe("agent:main:voice:15550001111");
});
it("scopes generated voice session keys by configured agent", () => {
const config = resolveVoiceCallConfig({
enabled: true,
provider: "mock",
agentId: "Voice",
});
expect(
resolveVoiceCallSessionKey({
config,
callId: "CALL-123",
phone: "+1 (555) 000-1111",
}),
).toBe("agent:voice:voice:15550001111");
});
it("can scope voice sessions to each call", () => {
const config = resolveVoiceCallConfig({
enabled: true,
provider: "mock",
sessionScope: "per-call",
});
expect(config.sessionScope).toBe("per-call");
expect(
resolveVoiceCallSessionKey({
config,
callId: "call-123",
phone: "+1 (555) 000-1111",
}),
).toBe("agent:main:voice:call:call-123");
});
it("scopes explicit voice session keys by configured agent", () => {
const config = resolveVoiceCallConfig({
enabled: true,
provider: "mock",
sessionScope: "per-call",
});
expect(
resolveVoiceCallSessionKey({
config,
callId: "call-123",
phone: "+1 (555) 000-1111",
explicitSessionKey: "Meet-Room-1",
}),
).toBe("agent:main:meet-room-1");
});
it("scopes persisted and explicit keys at the agent session boundary", () => {
const config = resolveVoiceCallConfig({
enabled: true,
provider: "mock",
agentId: "Voice",
});
expect(
resolveVoiceCallAgentSessionKey({
config,
sessionKey: "voice:call:legacy-call",
}),
).toBe("agent:voice:voice:call:legacy-call");
expect(
resolveVoiceCallAgentSessionKey({
config,
sessionKey: "meet-room-1",
}),
).toBe("agent:voice:meet-room-1");
expect(
resolveVoiceCallAgentSessionKey({
config,
sessionKey: "agent:main:shared-room",
}),
).toBe("agent:voice:agent:main:shared-room");
expect(
resolveVoiceCallAgentSessionKey({
config,
sessionKey: "agent:other:Matrix:Channel:!RoomAbC:example.org",
}),
).toBe("agent:voice:agent:other:matrix:channel:!RoomAbC:example.org");
expect(
resolveVoiceCallAgentSessionKey({
config,
sessionKey: "agent:voice:agent:other:matrix:channel:!RoomAbC:example.org",
}),
).toBe("agent:voice:agent:other:matrix:channel:!RoomAbC:example.org");
expect(
resolveVoiceCallAgentSessionKey({
config,
sessionKey: "Signal:Group:AbC123=",
}),
).toBe("agent:voice:signal:group:AbC123=");
expect(
resolveVoiceCallAgentSessionKey({
config,
sessionKey: "agent:broken",
}),
).toBe("agent:voice:agent:broken");
expect(
resolveVoiceCallAgentSessionKey({
config,
sessionKey: "agent::broken",
}),
).toBe("agent:voice:agent::broken");
expect(
resolveVoiceCallAgentSessionKey({
config,
sessionKey: "agent::Matrix:Channel:!RoomAbC:example.org",
}),
).toBe("agent:voice:agent::matrix:channel:!RoomAbC:example.org");
expect(
resolveVoiceCallAgentSessionKey({
config,
sessionKey: "agent:other:room::part",
}),
).toBe("agent:voice:agent:other:room::part");
expect(
resolveVoiceCallAgentSessionKey({
config,
sessionKey: "agent:voice:room::part",
}),
).toBe("agent:voice:room::part");
expect(
resolveVoiceCallAgentSessionKey({
config,
sessionKey: "agent:voice::Matrix:Channel:!RoomAbC:example.org",
}),
).toBe("agent:voice:agent:voice::matrix:channel:!RoomAbC:example.org");
expect(
resolveVoiceCallAgentSessionKey({
config,
sessionKey: "agent:bad/id:room",
}),
).toBe("agent:voice:agent:bad/id:room");
});
it("canonicalizes raw and scoped main aliases with the core session config", () => {
const config = resolveVoiceCallConfig({
enabled: true,
provider: "mock",
agentId: "Voice",
});
for (const sessionKey of ["main", "agent:voice:main"]) {
expect(
resolveVoiceCallAgentSessionKey({
config,
sessionKey,
coreSession: { mainKey: "work" },
}),
).toBe("agent:voice:work");
}
expect(
resolveVoiceCallAgentSessionKey({
config,
sessionKey: "main",
coreSession: { scope: "global" },
}),
).toBe("global");
expect(
resolveVoiceCallAgentSessionKey({
config,
sessionKey: "agent:main:main",
coreSession: { mainKey: "work" },
}),
).toBe("agent:voice:agent:main:main");
expect(
resolveVoiceCallAgentSessionKey({
config,
sessionKey: "agent:main:main",
coreSession: { scope: "global" },
}),
).toBe("agent:voice:agent:main:main");
});
it("resolves per-number inbound route overrides over global voice settings", () => {
const config = resolveVoiceCallConfig({
enabled: true,
provider: "mock",
inboundGreeting: "Hello from global.",
agentId: "main",
responseModel: "openai/gpt-5.4-mini",
responseSystemPrompt: "Global voice assistant.",
responseTimeoutMs: 10000,
tts: {
provider: "openai",
providers: {
openai: { voice: "coral", speed: 1 },
},
},
numbers: {
"+15550001111": {
inboundGreeting: "Silver Fox Cards, how can I help?",
agentId: "cards",
responseModel: "openai/gpt-5.5",
responseSystemPrompt: "You are a baseball card expert.",
responseTimeoutMs: 20000,
tts: {
providers: {
openai: { voice: "alloy" },
},
},
},
},
});
expect(resolveVoiceCallNumberRouteKey(config, "+1 (555) 000-1111")).toBe("+15550001111");
const effective = resolveVoiceCallEffectiveConfig(config, "+1 (555) 000-1111");
expect(effective.numberRouteKey).toBe("+15550001111");
expect(effective.config.inboundGreeting).toBe("Silver Fox Cards, how can I help?");
expect(effective.config.agentId).toBe("cards");
expect(effective.config.responseModel).toBe("openai/gpt-5.5");
expect(effective.config.responseSystemPrompt).toBe("You are a baseball card expert.");
expect(effective.config.responseTimeoutMs).toBe(20000);
expect(effective.config.tts?.provider).toBe("openai");
expect(effective.config.tts?.providers?.openai).toEqual({ voice: "alloy", speed: 1 });
});
it("falls back to global voice settings when no per-number route matches", () => {
const config = resolveVoiceCallConfig({
enabled: true,
provider: "mock",
inboundGreeting: "Hello from global.",
numbers: {
"+15550001111": {
inboundGreeting: "Hello from route.",
},
},
});
const effective = resolveVoiceCallEffectiveConfig(config, "+15550002222");
expect(effective.numberRouteKey).toBeUndefined();
expect(effective.config).toBe(config);
expect(effective.config.inboundGreeting).toBe("Hello from global.");
});
it("uses dialed-number fallback only for inbound calls", () => {
expect(
resolveVoiceCallNumberRouteKeyForCall({
direction: "inbound",
to: "+15550001111",
}),
).toBe("+15550001111");
expect(
resolveVoiceCallNumberRouteKeyForCall({
direction: "outbound",
to: "+15550001111",
}),
).toBeUndefined();
expect(
resolveVoiceCallNumberRouteKeyForCall({
direction: "inbound",
to: "+15550001111",
metadata: { numberRouteKey: "+15550002222" },
}),
).toBe("+15550002222");
expect(
resolveVoiceCallNumberRouteKeyForCall({
direction: "outbound",
to: "+15550001111",
metadata: { numberRouteKey: "+15550002222" },
}),
).toBeUndefined();
});
});
describe("normalizeVoiceCallConfig", () => {
it("fills nested runtime defaults from a partial config boundary", () => {
const normalized = normalizeVoiceCallConfig({
enabled: true,
provider: "mock",
streaming: {
enabled: true,
streamPath: "/custom-stream",
},
});
expect(normalized.serve.path).toBe("/voice/webhook");
expect(normalized.streaming.streamPath).toBe("/custom-stream");
expect(normalized.streaming.provider).toBeUndefined();
expect(normalized.streaming.providers).toStrictEqual({});
expect(normalized.realtime.streamPath).toBe("/voice/stream/realtime");
expect(normalized.realtime.toolPolicy).toBe("safe-read-only");
expect(normalized.realtime.consultPolicy).toBe("auto");
expect(normalized.realtime.fastContext).toEqual({
enabled: false,
timeoutMs: 800,
maxResults: 3,
sources: ["memory", "sessions"],
fallbackToConsult: false,
});
expect(normalized.realtime.consultThinkingLevel).toBeUndefined();
expect(normalized.realtime.consultFastMode).toBeUndefined();
expect(normalized.realtime.agentContext).toEqual({
enabled: false,
maxChars: 6000,
includeIdentity: true,
includeWorkspaceFiles: true,
files: ["SOUL.md", "IDENTITY.md", "USER.md"],
});
expect(normalized.realtime.instructions).toContain("openclaw_agent_consult");
expect(normalized.tunnel.provider).toBe("none");
expect(normalized.webhookSecurity.allowedHosts).toStrictEqual([]);
});
it("derives the realtime stream path from a custom webhook path", () => {
const normalized = normalizeVoiceCallConfig({
enabled: true,
provider: "twilio",
serve: {
path: "/custom/webhook",
},
});
expect(normalized.realtime.streamPath).toBe("/custom/stream/realtime");
});
it("accepts partial nested TTS overrides and preserves nested objects", () => {
const normalized = normalizeVoiceCallConfig({
tts: {
provider: "elevenlabs",
providers: {
elevenlabs: {
apiKey: {
source: "env",
provider: "elevenlabs",
id: "ELEVENLABS_API_KEY",
},
voiceSettings: {
speed: 1.1,
},
},
},
},
});
const { tts, elevenlabs } = requireElevenLabsTtsConfig(normalized);
expect(tts.provider).toBe("elevenlabs");
expect(elevenlabs.apiKey).toEqual({
source: "env",
provider: "elevenlabs",
id: "ELEVENLABS_API_KEY",
});
expect(elevenlabs.voiceSettings).toEqual({ speed: 1.1 });
});
});
describe("resolveVoiceCallConfig realtime settings", () => {
it("preserves configured realtime instructions without env indirection", () => {
const resolved = resolveVoiceCallConfig({
enabled: true,
provider: "twilio",
realtime: {
enabled: true,
instructions: "Stay concise.",
},
});
expect(resolved.realtime.instructions).toBe("Stay concise.");
expect(resolved.realtime.toolPolicy).toBe("safe-read-only");
expect(resolved.realtime.consultPolicy).toBe("auto");
expect(resolved.realtime.provider).toBeUndefined();
});
it("preserves configured realtime consult overrides", () => {
const resolved = resolveVoiceCallConfig({
enabled: true,
provider: "mock",
realtime: {
consultThinkingLevel: "low",
consultFastMode: true,
},
});
expect(resolved.realtime.consultThinkingLevel).toBe("low");
expect(resolved.realtime.consultFastMode).toBe(true);
});
it("rejects invalid realtime consult thinking levels", () => {
expect(() =>
resolveVoiceCallConfig({
enabled: true,
provider: "mock",
realtime: {
consultThinkingLevel: "turbo",
},
} as never),
).toThrow(/Invalid option/);
});
it("leaves responseModel unset so voice responses can inherit runtime defaults", () => {
const resolved = resolveVoiceCallConfig({
enabled: true,
provider: "mock",
});
expect(resolved.responseModel).toBeUndefined();
});
it("preserves the configured voice response agent id", () => {
const resolved = resolveVoiceCallConfig({
enabled: true,
provider: "mock",
agentId: "voice",
});
expect(resolved.agentId).toBe("voice");
});
});