Voice calls for CoderClaw via a plugin. Supports outbound notifications and multi-turn conversations with inbound policies.
Current providers:
twilio (Programmable Voice + Media Streams)telnyx (Call Control v2)plivo (Voice API + XML transfer + GetInput speech)mock (dev/no network)Quick mental model:
plugins.entries.voice-call.configcoderclaw voicecall ... or the voice_call toolThe Voice Call plugin runs inside the Gateway process.
If you use a remote Gateway, install/configure the plugin on the machine running the Gateway, then restart the Gateway to load it.
coderclaw plugins install @coderclaw/voice-call
Restart the Gateway afterwards.
coderclaw plugins install ./extensions/voice-call
cd ./extensions/voice-call && pnpm install
Restart the Gateway afterwards.
Set config under plugins.entries.voice-call.config:
{
plugins: {
entries: {
"voice-call": {
enabled: true,
config: {
provider: "twilio", // or "telnyx" | "plivo" | "mock"
fromNumber: "+15550001234",
toNumber: "+15550005678",
twilio: {
accountSid: "ACxxxxxxxx",
authToken: "...",
},
telnyx: {
apiKey: "...",
connectionId: "...",
// Telnyx webhook public key from the Telnyx Mission Control Portal
// (Base64 string; can also be set via TELNYX_PUBLIC_KEY).
publicKey: "...",
},
plivo: {
authId: "MAxxxxxxxxxxxxxxxxxxxx",
authToken: "...",
},
// Webhook server
serve: {
port: 3334,
path: "/voice/webhook",
},
// Webhook security (recommended for tunnels/proxies)
webhookSecurity: {
allowedHosts: ["voice.example.com"],
trustedProxyIPs: ["100.64.0.1"],
},
// Public exposure (pick one)
// publicUrl: "https://example.ngrok.app/voice/webhook",
// tunnel: { provider: "ngrok" },
// tailscale: { mode: "funnel", path: "/voice/webhook" }
outbound: {
defaultMode: "notify", // notify | conversation
},
streaming: {
enabled: true,
streamPath: "/voice/stream",
},
},
},
},
},
}
Notes:
mock is a local dev provider (no network calls).telnyx.publicKey (or TELNYX_PUBLIC_KEY) unless skipSignatureVerification is true.skipSignatureVerification is for local testing only.publicUrl to the exact ngrok URL; signature verification is always enforced.tunnel.allowNgrokFreeTierLoopbackBypass: true allows Twilio webhooks with invalid signatures only when tunnel.provider="ngrok" and serve.bind is loopback (ngrok local agent). Use for local dev only.publicUrl drifts, Twilio signatures will fail. For production, prefer a stable domain or Tailscale funnel.Use staleCallReaperSeconds to end calls that never receive a terminal webhook
(for example, notify-mode calls that never complete). The default is 0
(disabled).
Recommended ranges:
120–300 seconds for notify-style flows.maxDurationSeconds so normal calls can
finish. A good starting point is maxDurationSeconds + 30–60 seconds.Example:
{
plugins: {
entries: {
"voice-call": {
config: {
maxDurationSeconds: 300,
staleCallReaperSeconds: 360,
},
},
},
},
}
When a proxy or tunnel sits in front of the Gateway, the plugin reconstructs the public URL for signature verification. These options control which forwarded headers are trusted.
webhookSecurity.allowedHosts allowlists hosts from forwarding headers.
webhookSecurity.trustForwardingHeaders trusts forwarded headers without an allowlist.
webhookSecurity.trustedProxyIPs only trusts forwarded headers when the request
remote IP matches the list.
Example with a stable public host:
{
plugins: {
entries: {
"voice-call": {
config: {
publicUrl: "https://voice.example.com/voice/webhook",
webhookSecurity: {
allowedHosts: ["voice.example.com"],
},
},
},
},
},
}
Voice Call uses the core messages.tts configuration (OpenAI or ElevenLabs) for
streaming speech on calls. You can override it under the plugin config with the
same shape — it deep‑merges with messages.tts.
{
tts: {
provider: "elevenlabs",
elevenlabs: {
voiceId: "pMsXgVXv3BLzUgSXRplE",
modelId: "eleven_multilingual_v2",
},
},
}
Notes:
Use core TTS only (no override):
{
messages: {
tts: {
provider: "openai",
openai: { voice: "alloy" },
},
},
}
Override to ElevenLabs just for calls (keep core default elsewhere):
{
plugins: {
entries: {
"voice-call": {
config: {
tts: {
provider: "elevenlabs",
elevenlabs: {
apiKey: "elevenlabs_key",
voiceId: "pMsXgVXv3BLzUgSXRplE",
modelId: "eleven_multilingual_v2",
},
},
},
},
},
},
}
Override only the OpenAI model for calls (deep‑merge example):
{
plugins: {
entries: {
"voice-call": {
config: {
tts: {
openai: {
model: "gpt-4o-mini-tts",
voice: "marin",
},
},
},
},
},
},
}
Inbound policy defaults to disabled. To enable inbound calls, set:
{
inboundPolicy: "allowlist",
allowFrom: ["+15550001234"],
inboundGreeting: "Hello! How can I help?",
}
Auto-responses use the agent system. Tune with:
responseModelresponseSystemPromptresponseTimeoutMscoderclaw voicecall call --to "+15555550123" --message "Hello from CoderClaw"
coderclaw voicecall continue --call-id <id> --message "Any questions?"
coderclaw voicecall speak --call-id <id> --message "One moment"
coderclaw voicecall end --call-id <id>
coderclaw voicecall status --call-id <id>
coderclaw voicecall tail
coderclaw voicecall expose --mode funnel
Tool name: voice_call
Actions:
initiate_call (message, to?, mode?)continue_call (callId, message)speak_to_user (callId, message)end_call (callId)get_status (callId)This repo ships a matching skill doc at skills/voice-call/SKILL.md.
voicecall.initiate (to?, message, mode?)voicecall.continue (callId, message)voicecall.speak (callId, message)voicecall.end (callId)voicecall.status (callId)