Llamadas de voz (plugin)

Llamadas de voz para OpenClaw mediante un plugin. Soporta notificaciones salientes y conversaciones multi-turno con políticas de llamadas entrantes.

Proveedores actuales:

  • twilio (Programmable Voice + Media Streams)
  • telnyx (Call Control v2)
  • plivo (Voice API + XML transfer + GetInput speech)
  • mock (desarrollo/sin red)

Modelo mental rápido:

  • Instalar el plugin
  • Reiniciar el Gateway
  • Configurar bajo plugins.entries.voice-call.config
  • Usar openclaw voicecall ... o la herramienta voice_call

Dónde se ejecuta (local vs remoto)

El plugin de llamadas de voz se ejecuta dentro del proceso del Gateway.

Si usas un Gateway remoto, instala/configura el plugin en la máquina que ejecuta el Gateway, luego reinicia el Gateway para cargarlo.

Instalación

Opción A: instalar desde npm (recomendado)

openclaw plugins install @openclaw/voice-call

Reinicia el Gateway después.

Opción B: instalar desde una carpeta local (desarrollo, sin copiado)

openclaw plugins install ./extensions/voice-call
cd ./extensions/voice-call && pnpm install

Reinicia el Gateway después.

Configuración

Configura bajo plugins.entries.voice-call.config:

{
  plugins: {
    entries: {
      "voice-call": {
        enabled: true,
        config: {
          provider: "twilio", // o "telnyx" | "plivo" | "mock"
          fromNumber: "+15550001234",
          toNumber: "+15550005678",

          twilio: {
            accountSid: "ACxxxxxxxx",
            authToken: "...",
          },

          telnyx: {
            apiKey: "...",
            connectionId: "...",
            // Clave pública de webhook de Telnyx del Portal Mission Control de Telnyx
            // (cadena Base64; también se puede configurar vía TELNYX_PUBLIC_KEY).
            publicKey: "...",
          },

          plivo: {
            authId: "MAxxxxxxxxxxxxxxxxxxxx",
            authToken: "...",
          },

          // Servidor webhook
          serve: {
            port: 3334,
            path: "/voice/webhook",
          },

          // Seguridad de webhook (recomendado para túneles/proxies)
          webhookSecurity: {
            allowedHosts: ["voice.example.com"],
            trustedProxyIPs: ["100.64.0.1"],
          },

          // Exposición pública (elige una)
          // 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",
            preStartTimeoutMs: 5000,
            maxPendingConnections: 32,
            maxPendingConnectionsPerIp: 4,
            maxConnections: 128,
          },
        },
      },
    },
  },
}

Notas:

  • Twilio/Telnyx requieren una URL de webhook accesible públicamente.
  • Plivo requiere una URL de webhook accesible públicamente.
  • mock es un proveedor de desarrollo local (sin llamadas de red).
  • Telnyx requiere telnyx.publicKey (o TELNYX_PUBLIC_KEY) a menos que skipSignatureVerification sea true.
  • skipSignatureVerification es solo para pruebas locales.
  • Si usas el nivel gratuito de ngrok, configura publicUrl con la URL exacta de ngrok; la verificación de firma siempre se aplica.
  • tunnel.allowNgrokFreeTierLoopbackBypass: true permite webhooks de Twilio con firmas inválidas solo cuando tunnel.provider="ngrok" y serve.bind es loopback (agente local de ngrok). Usar solo para desarrollo local.
  • Las URLs del nivel gratuito de ngrok pueden cambiar o agregar comportamiento intersticial; si publicUrl se desincroniza, las firmas de Twilio fallarán. Para producción, prefiere un dominio estable o Tailscale funnel.
  • Valores por defecto de seguridad de streaming:
    • streaming.preStartTimeoutMs cierra sockets que nunca envían un frame start válido.
    • streaming.maxPendingConnections limita el total de sockets no autenticados pre-start.
    • streaming.maxPendingConnectionsPerIp limita los sockets no autenticados pre-start por IP de origen.
    • streaming.maxConnections limita el total de sockets de media stream abiertos (pendientes + activos).

Recolector de llamadas inactivas

Usa staleCallReaperSeconds para finalizar llamadas que nunca reciben un webhook terminal (por ejemplo, llamadas en modo notify que nunca se completan). El valor por defecto es 0 (deshabilitado).

Rangos recomendados:

  • Producción: 120-300 segundos para flujos estilo notify.
  • Mantén este valor más alto que maxDurationSeconds para que las llamadas normales puedan terminar. Un buen punto de partida es maxDurationSeconds + 30-60 segundos.

Ejemplo:

{
  plugins: {
    entries: {
      "voice-call": {
        config: {
          maxDurationSeconds: 300,
          staleCallReaperSeconds: 360,
        },
      },
    },
  },
}

Seguridad de webhook

Cuando un proxy o túnel está delante del Gateway, el plugin reconstruye la URL pública para la verificación de firma. Estas opciones controlan en qué headers de reenvío se confía.

webhookSecurity.allowedHosts crea una allowlist de hosts desde headers de reenvío.

webhookSecurity.trustForwardingHeaders confía en los headers de reenvío sin allowlist.

webhookSecurity.trustedProxyIPs solo confía en los headers de reenvío cuando la IP remota de la solicitud coincide con la lista.

La protección contra repetición de webhooks está habilitada para Twilio y Plivo. Las solicitudes de webhook válidas repetidas se confirman pero se omiten para efectos secundarios.

Los turnos de conversación de Twilio incluyen un token por turno en los callbacks de <Gather>, por lo que los callbacks de habla obsoletos/repetidos no pueden satisfacer un turno de transcripción pendiente más reciente.

Ejemplo con un host público estable:

{
  plugins: {
    entries: {
      "voice-call": {
        config: {
          publicUrl: "https://voice.example.com/voice/webhook",
          webhookSecurity: {
            allowedHosts: ["voice.example.com"],
          },
        },
      },
    },
  },
}

TTS para llamadas

Las llamadas de voz usan la configuración core de messages.tts (OpenAI o ElevenLabs) para el habla por streaming en las llamadas. Puedes sobreescribirlo bajo la configuración del plugin con la misma estructura — se fusiona profundamente con messages.tts.

{
  tts: {
    provider: "elevenlabs",
    elevenlabs: {
      voiceId: "pMsXgVXv3BLzUgSXRplE",
      modelId: "eleven_multilingual_v2",
    },
  },
}

Notas:

  • Edge TTS se ignora para llamadas de voz (el audio telefónico necesita PCM; la salida de Edge es poco fiable).
  • El TTS core se usa cuando el streaming de medios de Twilio está habilitado; en caso contrario, las llamadas recurren a voces nativas del proveedor.

Más ejemplos

Usar solo TTS core (sin sobreescritura):

{
  messages: {
    tts: {
      provider: "openai",
      openai: { voice: "alloy" },
    },
  },
}

Sobreescribir a ElevenLabs solo para llamadas (mantener el core por defecto en otros sitios):

{
  plugins: {
    entries: {
      "voice-call": {
        config: {
          tts: {
            provider: "elevenlabs",
            elevenlabs: {
              apiKey: "elevenlabs_key",
              voiceId: "pMsXgVXv3BLzUgSXRplE",
              modelId: "eleven_multilingual_v2",
            },
          },
        },
      },
    },
  },
}

Sobreescribir solo el modelo OpenAI para llamadas (ejemplo de fusión profunda):

{
  plugins: {
    entries: {
      "voice-call": {
        config: {
          tts: {
            openai: {
              model: "gpt-4o-mini-tts",
              voice: "marin",
            },
          },
        },
      },
    },
  },
}

Llamadas entrantes

La política de entrantes por defecto es disabled. Para habilitar llamadas entrantes, configura:

{
  inboundPolicy: "allowlist",
  allowFrom: ["+15550001234"],
  inboundGreeting: "Hello! How can I help?",
}

inboundPolicy: "allowlist" es un filtrado de caller-ID de baja confianza. El plugin normaliza el valor From proporcionado por el proveedor y lo compara con allowFrom. La verificación de webhook autentica la entrega del proveedor y la integridad del payload, pero no demuestra la propiedad del número de llamante PSTN/VoIP. Trata allowFrom como filtrado de caller-ID, no como identidad fuerte del llamante.

Las respuestas automáticas usan el sistema de agentes. Ajusta con:

  • responseModel
  • responseSystemPrompt
  • responseTimeoutMs

CLI

openclaw voicecall call --to "+15555550123" --message "Hello from OpenClaw"
openclaw voicecall continue --call-id <id> --message "Any questions?"
openclaw voicecall speak --call-id <id> --message "One moment"
openclaw voicecall end --call-id <id>
openclaw voicecall status --call-id <id>
openclaw voicecall tail
openclaw voicecall expose --mode funnel

Herramienta de agente

Nombre de herramienta: voice_call

Acciones:

  • initiate_call (message, to?, mode?)
  • continue_call (callId, message)
  • speak_to_user (callId, message)
  • end_call (callId)
  • get_status (callId)

Este repositorio incluye un documento de skill correspondiente en skills/voice-call/SKILL.md.

RPC del Gateway

  • voicecall.initiate (to?, message, mode?)
  • voicecall.continue (callId, message)
  • voicecall.speak (callId, message)
  • voicecall.end (callId)
  • voicecall.status (callId)