# 语音通话插件

> 通过 Twilio、Telnyx 或 Plivo 拨打和接听语音电话，并可选择使用实时语音和流式转录功能

- 网址：https://funcoding.ai/agents/openclaw/plugins/voice-call/
- 来源：OpenClaw 官方文档原文（中文），MIT 许可，同步于 2026-10-11
- 官方原文：https://docs.openclaw.ai/zh-CN/plugins/voice-call

---
通过插件为 OpenClaw 提供语音通话：出站通知、多轮
对话、全双工实时语音、流式转录，以及
采用允许列表策略的入站通话。

**提供商：**`mock`（开发，无网络）、`plivo`（Voice API + XML 转接 +
GetInput 语音）、`telnyx`（Call Control v2）、`twilio`（Programmable Voice +
Media Streams）。

<div class="callout callout-note">

语音通话插件在 **Gateway 网关进程内**运行。如果使用
远程 Gateway 网关，请在运行 Gateway 网关的机器上安装并配置插件，
然后重启 Gateway 网关以加载插件。

</div>

## 快速开始

**安装插件**

**从 npm 安装**

```bash
openclaw plugins install @openclaw/voice-call
```

**从本地文件夹安装（开发）**

```bash
PLUGIN_SRC=./path/to/local/voice-call-plugin
openclaw plugins install "$PLUGIN_SRC"
cd "$PLUGIN_SRC" && pnpm install
```

使用不带版本号的包以跟随当前发布标签。仅在需要可复现安装时
固定确切版本。之后重启 Gateway 网关，使插件加载。

**配置提供商和 webhook**

在 `plugins.entries.voice-call.config` 下设置配置（请参阅下方的
[配置](#configuration)）。至少需要：`provider`、提供商
凭据、`fromNumber`，以及可从公网访问的 webhook URL。

**验证设置**

```bash
openclaw voicecall setup
openclaw voicecall setup --json
```

检查插件是否启用、提供商凭据、webhook 暴露情况，以及
是否仅启用一种音频模式（`streaming` 或 `realtime`）。

**冒烟测试**

```bash
openclaw voicecall smoke
openclaw voicecall smoke --to "+15555550123"
```

两者默认都进行试运行。添加 `--yes` 可发起一次简短的出站
通知通话：

```bash
openclaw voicecall smoke --to "+15555550123" --yes
```

<div class="callout callout-warning">

对于 Twilio、Telnyx 和 Plivo，设置必须解析为**公共 webhook URL**。
如果 `publicUrl`、隧道 URL、Tailscale URL 或 serve 回退
解析为环回地址或私有网络空间，设置将失败，而不会
启动无法接收运营商 webhook 的提供商。

</div>

## 配置

如果 `enabled: true`，但所选提供商缺少凭据，Gateway 网关
启动时会记录设置未完成警告，列出缺失的键，并跳过
启动运行时。命令、RPC 调用和智能体工具在使用时仍会返回
确切缺失的配置。

<div class="callout callout-note">

语音通话凭据接受 SecretRef。`plugins.entries.voice-call.config.twilio.authToken`、`plugins.entries.voice-call.config.realtime.providers.*.apiKey`、`plugins.entries.voice-call.config.streaming.providers.*.apiKey` 和 `plugins.entries.voice-call.config.tts.providers.*.apiKey` 通过标准 SecretRef 界面解析；请参阅 [SecretRef 凭据界面](https://funcoding.ai/agents/openclaw/reference/secretref-credential-surface/)。

</div>

```json5
{
  plugins: {
    entries: {
      "voice-call": {
        enabled: true,
        config: {
          provider: "twilio", // 或 "telnyx" | "plivo" | "mock"
          fromNumber: "+15550001234", // 对于 Twilio，也可使用 TWILIO_FROM_NUMBER
          toNumber: "+15550005678",
          sessionScope: "per-phone", // per-phone | per-call
          numbers: {
            "+15550009999": {
              inboundGreeting: "这里是 Silver Fox Cards，请问有什么可以帮你？",
              responseSystemPrompt: "你是一名言简意赅的棒球卡专家。",
              tts: {
                providers: {
                  openai: { speakerVoice: "alloy" },
                },
              },
            },
          },

          twilio: {
            accountSid: "ACxxxxxxxx",
            authToken: "...",
            // region: "ie1", // 可选：us1 | ie1 | au1；默认为 us1
          },
          telnyx: {
            apiKey: "...",
            connectionId: "...",
            // 来自 Mission Control Portal 的 Telnyx webhook 公钥
            //（Base64；也可通过 TELNYX_PUBLIC_KEY 设置）。
            publicKey: "...",
          },
          plivo: {
            authId: "MAxxxxxxxxxxxxxxxxxxxx",
            authToken: "...",
          },

          // Webhook 服务器
          serve: {
            port: 3334,
            path: "/voice/webhook",
          },

          // Webhook 安全性（建议用于隧道/代理）
          webhookSecurity: {
            allowedHosts: ["voice.example.com"],
            trustedProxyIPs: ["100.64.0.1"],
          },

          // 公网暴露（任选一种）
          // publicUrl: "https://example.ngrok.app/voice/webhook",
          // tunnel: { provider: "ngrok" },
          // tailscale: { mode: "funnel", path: "/voice/webhook" },

          outbound: {
            defaultMode: "notify", // notify | conversation
          },

          streaming: { enabled: true /* 仅限 Twilio；请参阅“流式转录” */ },
          realtime: { enabled: false /* 请参阅“实时语音对话” */ },
        },
      },
    },
  },
}
```

### 配置参考

上面未显示的 `plugins.entries.voice-call.config` 下的顶层键：

| 键                              | 默认值       | 说明                                                                                               |
| ------------------------------- | ------------ | -------------------------------------------------------------------------------------------------- |
| `enabled`                       | `false`      | 总开关。                                                                                           |
| `inboundPolicy`                 | `"disabled"` | `disabled` \| `allowlist` \| `pairing` \| `open`。请参阅[入站通话](#inbound-calls)。             |
| `allowFrom`                     | `[]`         | `inboundPolicy: "allowlist"` 的 E.164 允许列表。                                                  |
| `maxDurationSeconds`            | `300`        | 每次通话时长的硬性上限，无论是否接听都会强制执行。                                     |
| `staleCallReaperSeconds`        | `120`        | 请参阅[过期通话回收器](#stale-call-reaper)。`0` 可将其禁用。                                      |
| `silenceTimeoutMs`              | `800`        | 经典（非实时）流程的语音结束静音检测。                                                 |
| `transcriptTimeoutMs`           | `180000`     | 放弃某轮次前等待来电者转录文本的最长时间。                                               |
| `ringTimeoutMs`                 | `30000`      | 出站通话的响铃超时时间。                                                                 |
| `maxConcurrentCalls`            | `1`          | 超出此限制的出站通话将被拒绝。                                                           |
| `outbound.notifyHangupDelaySec` | `3`          | 通知模式下，TTS 结束后等待自动挂断的秒数。                                               |
| `skipSignatureVerification`     | `false`      | 仅供本地测试；切勿在生产环境中启用。                                                     |
| `store`                         | 未设置       | 覆盖默认的 `$OPENCLAW_STATE_DIR/voice-calls` 路径（通常为 `~/.openclaw/voice-calls`）。 |
| `agentId`                       | `"main"`     | 用于生成响应和存储会话的智能体。                                                         |
| `responseModel`                 | 未设置       | 覆盖经典（非实时）响应的默认模型。                                                       |
| `responseSystemPrompt`          | 自动生成     | 经典响应的自定义系统提示词。                                                             |
| `responseTimeoutMs`             | `30000`      | 经典响应生成的超时时间（毫秒）。                                                         |

Twilio 默认使用其 US1 REST 端点。要在受支持的
非美国区域处理通话，请将 `twilio.region` 设置为 `ie1` 或 `au1`，并使用该
区域的凭据。请参阅
[Twilio 的非美国区域 REST API 指南](https://www.twilio.com/docs/global-infrastructure/using-the-twilio-rest-api-in-a-non-us-region)。

<details>
<summary>提供商暴露和安全说明</summary>

- Twilio、Telnyx 和 Plivo 都要求使用**可从公网访问的** webhook URL。
- `mock` 是本地开发提供商（无网络调用）。
- Telnyx 要求提供 `telnyx.publicKey`（或 `TELNYX_PUBLIC_KEY`），除非 `skipSignatureVerification` 为 true。
- `skipSignatureVerification` 仅供本地测试。
- 使用 ngrok 免费层时，请将 `publicUrl` 设置为确切的 ngrok URL；始终强制执行签名验证。
- 仅当 `tunnel.provider="ngrok"` 且 `serve.bind` 为环回地址（ngrok 本地智能体）时，`tunnel.allowNgrokFreeTierLoopbackBypass: true` 才允许签名无效的 Twilio webhook。仅供本地开发。
- ngrok 免费层 URL 可能会更改或增加中间页行为；如果 `publicUrl` 发生偏移，Twilio 签名将失败。生产环境：优先使用稳定域名或 Tailscale funnel。

</details>

<details>
<summary>流式连接上限</summary>

- `streaming.preStartTimeoutMs`（默认 `5000`）会关闭从未发送有效 `start` 帧的套接字。
- `streaming.maxPendingConnections`（默认 `32`）限制未经身份验证的启动前套接字总数。
- `streaming.maxPendingConnectionsPerIp`（默认 `4`）限制每个源 IP 未经身份验证的启动前套接字数量。
- `streaming.maxConnections`（默认 `128`）限制所有打开的媒体流套接字（待处理 + 活跃）。

</details>

<details>
<summary>旧版配置迁移</summary>

配置解析会自动规范化这些旧版键，并记录一条
指明替代路径的警告；此兼容层将在未来版本
（`2026.6.0`）中移除，因此请运行 `openclaw doctor --fix`，将已提交的
配置重写为规范形式：

- `provider: "log"` → `provider: "mock"`
- `twilio.from` → `fromNumber`
- `streaming.sttProvider` → `streaming.provider`
- `streaming.openaiApiKey` → `streaming.providers.openai.apiKey`
- `streaming.sttModel` → `streaming.providers.openai.model`
- `streaming.silenceDurationMs` → `streaming.providers.openai.silenceDurationMs`
- `streaming.vadThreshold` → `streaming.providers.openai.vadThreshold`
- `realtime.agentContext.includeSystemPrompt` 已移除（实时上下文现在使用生成的智能体提示词）

</details>

## 会话范围

默认情况下，语音通话使用 `sessionScope: "per-phone"`，因此来自
同一来电者的重复通话会保留对话记忆。当每个运营商通话
都应使用全新上下文开始时，请设置 `sessionScope: "per-call"`，例如前台接待、
预订、IVR 或 Google Meet 桥接流程，在这些流程中，同一电话号码可能
代表不同的会议。

语音通话将生成的会话键存储在已配置的智能体命名空间下
（`agent:<agentId>:voice:*`）。原始的显式集成键会解析到
同一命名空间：规范的 `agent:<configuredAgentId>:*` 键会保留该
所有者，并遵循核心 `session.mainKey`/全局范围别名规则；外部或
格式错误的 `agent:*` 输入会作为不透明键限定在已配置的
智能体下；`global` 和 `unknown` 仍是全局哨兵值。

## 实时语音对话

`realtime` 为实时通话音频选择全双工实时语音提供商。
它与 `streaming` 相互独立，后者仅将音频转发给实时
转录提供商。

<div class="callout callout-warning">

`realtime.enabled` 不能与 `streaming.enabled` 结合使用。每次通话只能选择一种
音频模式。

</div>

当前运行时行为：

- `realtime.enabled` 支持 Twilio 和 Telnyx。
- `realtime.provider` 是可选的。如果未设置，语音通话将使用首个已注册的实时语音提供商。
- 内置实时语音提供商：Google Gemini Live（`google`）和 OpenAI（`openai`），由各自的提供商插件注册。
- 提供商自有的原始配置位于 `realtime.providers.<providerId>` 下。
- 默认情况下，语音通话会公开共享的 `openclaw_agent_consult` 实时工具。当呼叫者要求进行更深入的推理、获取当前信息或使用常规 OpenClaw 工具时，实时模型可以调用该工具。
- `realtime.consultPolicy` 可选择性地添加指导，说明实时模型应在何时调用 `openclaw_agent_consult`。
- `realtime.agentContext.enabled` 默认关闭。启用后，语音通话会在设置会话时，将有界的智能体身份信息和选定的工作区文件信息包注入实时提供商指令。
- `realtime.fastContext.enabled` 默认关闭。启用后，语音通话会先在已建立索引的记忆/会话上下文中搜索咨询问题，并在 `realtime.fastContext.timeoutMs` 范围内将这些片段返回给实时模型；仅当 `realtime.fastContext.fallbackToConsult` 为 true 时，才会回退到完整的咨询智能体。
- 如果 `realtime.provider` 指向未注册的提供商，或者根本没有注册实时语音提供商，语音通话会记录警告并跳过实时媒体处理，而不会让整个插件失败。
- 当 `realtime.enabled` 为 true 时，`inboundPolicy` 不得为 `"disabled"`；`validateProviderConfig` 会拒绝该组合。
- 咨询会话键会优先复用已存储的通话会话（如果有），然后回退到配置的 `sessionScope`（默认为 `per-phone`，隔离通话则为 `per-call`）。

### 工具策略

`realtime.toolPolicy` 控制咨询运行：

| 策略           | 行为                                                                                                                                 |
| ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `safe-read-only` | 公开咨询工具，并将常规智能体限制为只能使用 `read`、`web_search`、`web_fetch`、`x_search`、`memory_search` 和 `memory_get`。 |
| `owner`          | 公开咨询工具，并允许常规智能体使用正常的智能体工具策略。                                                      |
| `none`           | 不公开咨询工具。自定义 `realtime.tools` 仍会传递给实时提供商。                               |

`realtime.consultPolicy` 仅控制实时模型指令：

| 策略        | 指导                                                                                        |
| ------------- | ----------------------------------------------------------------------------------------------- |
| `auto`        | 保留默认提示词，由提供商决定何时调用咨询工具。              |
| `substantive` | 直接回答简单的对话衔接内容；涉及事实、记忆、工具或上下文时，先进行咨询。 |
| `always`      | 在每次实质性回答前进行咨询。                                                        |

### 智能体语音上下文

如果希望语音桥接听起来像已配置的 OpenClaw 智能体，同时又不想让
普通轮次承担完整的智能体咨询往返开销，请启用 `realtime.agentContext`。
上下文信息包仅在创建实时会话时添加一次，因此不会增加每轮延迟。
调用 `openclaw_agent_consult` 时仍会运行完整的 OpenClaw 智能体，并且应将其用于
工具操作、当前信息、记忆查找或工作区状态。

```json5
{
  plugins: {
    entries: {
      "voice-call": {
        config: {
          agentId: "main",
          realtime: {
            enabled: true,
            provider: "google",
            toolPolicy: "safe-read-only",
            consultPolicy: "substantive",
            agentContext: {
              enabled: true,
              maxChars: 6000,
              includeIdentity: true,
              includeWorkspaceFiles: true,
              files: ["SOUL.md", "IDENTITY.md", "USER.md"],
            },
          },
        },
      },
    },
  },
}
```

### 实时提供商示例

**Google Gemini Live**

默认值：API key 来自 `realtime.providers.google.apiKey`、`GEMINI_API_KEY`
或 `GOOGLE_API_KEY`；模型为 `gemini-3.1-flash-live-preview`；
语音为 `Kore`。`sessionResumption` 和 `contextWindowCompression` 默认开启，
以支持更长时间且可重新连接的通话。使用 `silenceDurationMs`、
`startSensitivity` 和 `endSensitivity` 可针对电话音频调节更快的
轮次交接。

```json5
{
  plugins: {
    entries: {
      "voice-call": {
        config: {
          provider: "twilio",
          inboundPolicy: "allowlist",
          allowFrom: ["+15550005678"],
          realtime: {
            enabled: true,
            provider: "google",
            instructions: "简短回答。在使用更深入的工具之前调用 openclaw_agent_consult。",
            toolPolicy: "safe-read-only",
            consultPolicy: "substantive",
            consultThinkingLevel: "low",
            consultFastMode: true,
            agentContext: { enabled: true },
            providers: {
              google: {
                apiKey: "${GEMINI_API_KEY}",
                model: "gemini-3.1-flash-live-preview",
                speakerVoice: "Kore",
                silenceDurationMs: 500,
                startSensitivity: "high",
              },
            },
          },
        },
      },
    },
  },
}
```

**OpenAI**

```json5
{
  plugins: {
    entries: {
      "voice-call": {
        config: {
          realtime: {
            enabled: true,
            provider: "openai",
            providers: {
              openai: { apiKey: "${OPENAI_API_KEY}" },
            },
          },
        },
      },
    },
  },
}
```

有关特定提供商的实时语音选项，请参阅 [Google 提供商](https://funcoding.ai/agents/openclaw/providers/google/) 和
[OpenAI provider](https://funcoding.ai/agents/openclaw/providers/openai/)。

## 流式转录

`streaming` 将 Twilio Media Streams 连接到实时转录提供商。
经典流式传输路径要求使用 `provider: "twilio"`；使用
Telnyx、Plivo 或 mock 的配置会被拒绝。Telnyx 实时音频则使用单独进行
身份验证的 `realtime.enabled` 路径。

当前运行时行为：

- `streaming.provider` 是可选的。如果未设置，语音通话将使用首个已注册的实时转录提供商。
- 内置实时转录提供商：Deepgram（`deepgram`）、ElevenLabs（`elevenlabs`）、Mistral（`mistral`）、OpenAI（`openai`）和 xAI（`xai`），由各自的提供商插件注册。
- 提供商自有的原始配置位于 `streaming.providers.<providerId>` 下。
- Twilio 发送已接受的流 `start` 消息后，语音通话会立即注册该流，在提供商连接期间将入站媒体排队交给转录提供商，并仅在实时转录准备就绪后开始初始问候语。
- 如果 `streaming.provider` 指向未注册的提供商，或者没有注册任何提供商，语音通话会记录警告并跳过媒体流式传输，而不会让整个插件失败。

### 流式传输提供商示例

**OpenAI**

默认值：API key 为 `streaming.providers.openai.apiKey` 或
`OPENAI_API_KEY`；模型为 `gpt-4o-transcribe`；`silenceDurationMs: 800`；
`vadThreshold: 0.5`。

```json5
{
  plugins: {
    entries: {
      "voice-call": {
        config: {
          streaming: {
            enabled: true,
            provider: "openai",
            streamPath: "/voice/stream",
            providers: {
              openai: {
                apiKey: "sk-...", // 如果已设置 OPENAI_API_KEY，则此项可选
                model: "gpt-4o-transcribe",
                silenceDurationMs: 800,
                vadThreshold: 0.5,
              },
            },
          },
        },
      },
    },
  },
}
```

**xAI**

默认值：API key 为 `streaming.providers.xai.apiKey` 或 `XAI_API_KEY`（如果两者均未设置，
则回退到 xAI OAuth 身份验证配置文件）；端点为
`wss://api.x.ai/v1/stt`；编码为 `mulaw`；采样率为 `8000`；
`endpointingMs: 800`；`interimResults: true`。

```json5
{
  plugins: {
    entries: {
      "voice-call": {
        config: {
          streaming: {
            enabled: true,
            provider: "xai",
            streamPath: "/voice/stream",
            providers: {
              xai: {
                apiKey: "${XAI_API_KEY}", // 如果已设置 XAI_API_KEY，则此项可选
                endpointingMs: 800,
                language: "en",
              },
            },
          },
        },
      },
    },
  },
}
```

## 通话 TTS

语音通话使用核心 `tts` 配置进行通话中的流式语音合成。
你可以在插件配置下使用**相同的结构**覆盖它——该配置会与
`tts` 进行深度合并。

```json5
{
  tts: {
    provider: "elevenlabs",
    providers: {
      elevenlabs: {
        speakerVoiceId: "pMsXgVXv3BLzUgSXRplE",
        modelId: "eleven_multilingual_v2",
      },
    },
  },
}
```

<div class="callout callout-warning">

**语音通话会忽略 Microsoft speech。** 电话语音合成要求提供商实现
面向电话的输出；Microsoft speech 提供商不支持该功能，因此在通话中会被跳过，
并改为尝试回退链中的其他提供商。

</div>

行为说明：

- 插件配置中的旧版 `tts.<provider>` 键（`openai`、`elevenlabs`、`microsoft`、`edge`）由 `openclaw doctor --fix` 修复；提交的配置应使用 `tts.providers.<provider>`。
- 启用 Twilio 媒体流式传输时使用核心 TTS；否则通话会回退到提供商原生语音。
- 如果 Twilio 媒体流已处于活动状态，语音通话不会回退到 TwiML ``。如果在该状态下电话 TTS 不可用，播放请求将失败，而不会混用两种播放路径。
- 当电话 TTS 回退到次要提供商时，语音通话会记录包含提供商链（`from`、`to`、`attempts`）的警告，以便调试。
- 当 Twilio 插话或流拆除清除待处理的 TTS 队列时，已排队的播放请求会得到结算，而不会导致等待播放完成的呼叫者一直挂起。

### TTS 示例

**仅核心 TTS**

```json5
{
  tts: {
    provider: "openai",
    providers: {
      openai: { speakerVoice: "alloy" },
    },
  },
}
```

**覆盖为 ElevenLabs（仅通话）**

```json5
{
  plugins: {
    entries: {
      "voice-call": {
        config: {
          tts: {
            provider: "elevenlabs",
            providers: {
              elevenlabs: {
                apiKey: "elevenlabs_key",
                speakerVoiceId: "pMsXgVXv3BLzUgSXRplE",
                modelId: "eleven_multilingual_v2",
              },
            },
          },
        },
      },
    },
  },
}
```

**OpenAI 模型覆盖（深度合并）**

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

## 呼入通话

呼入策略默认为 `disabled`。要启用呼入通话，请设置：

```json5
{
  inboundPolicy: "allowlist",
  allowFrom: ["+15550001234"],
  inboundGreeting: "你好！我能帮你做什么？",
}
```

<div class="callout callout-warning">

`inboundPolicy: "allowlist"` 是一种低可信度的来电显示筛选机制。该插件会规范化提供商提供的 `From` 值，并将其与 `allowFrom` 进行比较。
Webhook 验证可以确认消息由提供商投递且负载完整，
但它**不能**证明 PSTN/VoIP 来电号码的所有权。应将
`allowFrom` 视为来电显示过滤，而非强身份验证。

</div>

自动响应使用智能体系统。可通过 `responseModel`、
`responseSystemPrompt` 和 `responseTimeoutMs` 进行调整。

### 按号码路由

当一个语音通话插件接收多个电话号码的来电，并且每个号码应像不同线路一样运行时，请使用 `numbers`。例如，
一个号码可以使用随和的个人助理，另一个号码则使用商务
角色、不同的响应智能体和不同的 TTS 语音。

路由根据提供商给出的被叫 `To` 号码进行选择。键必须
为 E.164 号码。来电到达时，语音通话插件会解析一次匹配的
路由，将匹配的路由存储到通话记录中，并在问候语、经典自动响应路径、实时
咨询路径和 TTS 播放中复用该有效配置。如果没有匹配的路由，则使用全局语音通话
配置。呼出通话不使用 `numbers`；发起通话时，应显式传入呼出
目标、消息和会话。

路由覆盖目前支持：

- `inboundGreeting`
- `tts`
- `agentId`
- `responseModel`
- `responseSystemPrompt`
- `responseTimeoutMs`

`tts` 路由值会深度合并到全局语音通话 `tts` 配置之上，因此
通常只需覆盖提供商语音：

```json5
{
  inboundGreeting: "你好，这里是总机。",
  responseSystemPrompt: "你是默认的语音助理。",
  tts: {
    provider: "openai",
    providers: {
      openai: { speakerVoice: "coral" },
    },
  },
  numbers: {
    "+15550001111": {
      inboundGreeting: "这里是 Silver Fox Cards，请问有什么可以帮你？",
      responseSystemPrompt: "你是一名言简意赅的棒球卡专家。",
      tts: {
        providers: {
          openai: { speakerVoice: "alloy" },
        },
      },
    },
  },
}
```

### 语音输出契约

对于自动响应，语音通话插件会在系统提示词末尾附加严格的语音输出契约，
要求返回 `{"spoken":"..."}` JSON 响应。语音通话插件会以防御性方式
提取语音文本：

- 忽略标记为推理/错误内容的负载。
- 解析直接 JSON、围栏 JSON 或内联 `"spoken"` 键。
- 回退到纯文本，并移除可能属于规划/元信息引导的段落。

这样可使语音播放聚焦于面向来电者的文本，并避免将
规划文本泄露到音频中。

### 对话启动行为

对于呼出的 `conversation` 通话，首条消息的处理与实时
播放状态关联：

- 仅在初始问候语正在播放时，才会抑制插话队列清除和自动响应。
- 如果初始播放失败，通话将返回 `listening`，且初始消息会保留在队列中以供重试。
- Twilio 流式传输的初始播放会在流连接时立即开始，不会增加额外延迟。
- 插话会中止正在进行的播放，并清除已排队但尚未播放的 Twilio TTS 条目。被清除的条目会以“已跳过”状态完成，因此后续响应逻辑可以继续执行，而不必等待永远不会播放的音频。
- 实时语音对话使用实时流自身的开场轮次。语音通话插件**不会**为该初始消息发送旧版 `` TwiML 更新，因此呼出的 `` 会话会保持连接。

### Twilio 流断开宽限期

当 Twilio 媒体流断开连接时，语音通话插件会等待 **2000 ms**，然后
自动结束通话：

- 如果流在此时间窗口内重新连接，则取消自动结束。
- 如果宽限期过后仍没有流重新注册，则结束通话，以防止通话卡在活动状态。

## 过期通话清理器

使用 `staleCallReaperSeconds`（默认值为 **120**）结束从未
接听且从未进入实时对话状态的通话，例如提供商始终未投递终止 Webhook 的通知模式
通话。将其设置为 `0` 可
禁用此功能。

清理器每 30 秒运行一次，并且仅结束没有
`answeredAt` 时间戳、且尚未处于终止或实时
（`speaking`/`listening`）状态的通话，因此已接听的对话绝不会被此计时器清理；
`maxDurationSeconds`（默认值为 300）是单独的上限，用于
结束持续时间过长的已接听通话。

对于运营商可能延迟投递响铃/接听
Webhook 的通知式流程，请将 `staleCallReaperSeconds` 提高到默认值以上，以免正常但缓慢的
通话被过早清理；`120`-`300` 秒是合理的生产环境
范围。

```json5
{
  plugins: {
    entries: {
      "voice-call": {
        config: {
          maxDurationSeconds: 300,
          staleCallReaperSeconds: 120,
        },
      },
    },
  },
}
```

## Webhook 安全

当 Gateway 网关前置代理或隧道时，插件会重建
用于签名验证的公共 URL。以下选项控制信任哪些
转发请求头：

允许从转发请求头获取的主机列表。

在没有允许列表的情况下信任转发请求头。

仅当请求的远程 IP 与列表匹配时才信任转发请求头。

其他保护措施：

- Twilio、Telnyx 和 Plivo 已启用 Webhook **重放保护**。对于重放的有效 Webhook 请求，系统会确认接收，但跳过其副作用。
- Twilio 对话轮次在 `` 回调中包含每轮令牌，因此过期或重放的语音回调无法满足较新的待处理转写轮次。
- 当缺少提供商要求的签名请求头时，未经身份验证的 Webhook 请求会在读取正文前被拒绝。
- 语音通话 Webhook 使用共享的身份验证前正文读取配置（正文最大 64 KB、读取超时 5 秒），并在签名验证前应用按键限制的进行中请求上限（默认每个键可同时处理 8 个请求）。

使用稳定公共主机的示例：

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

## CLI

```bash
openclaw voicecall call --to "+15555550123" --message "来自 OpenClaw 的问候"
openclaw voicecall start --to "+15555550123"   # call 的别名
openclaw voicecall continue --call-id <id> --message "还有什么问题吗？"
openclaw voicecall speak --call-id <id> --message "请稍等"
openclaw voicecall dtmf --call-id <id> --digits "ww123456#"
openclaw voicecall end --call-id <id>
openclaw voicecall status --call-id <id>
openclaw voicecall tail
openclaw voicecall latency                      # 根据日志汇总轮次延迟
openclaw voicecall expose --mode funnel
```

当 Gateway 网关已在运行时，操作类 `voicecall` 命令
会委托给 Gateway 网关拥有的语音通话运行时，因此 CLI 不会绑定第二个
Webhook 服务器。如果无法连接任何 Gateway 网关，这些命令会回退到
独立的 CLI 运行时。

`latency` 从默认语音通话存储路径读取 `calls.jsonl`。使用
`--file <path>` 指向不同的日志，并使用 `--last <n>` 将
分析限制为最后 N 条记录（默认 200）。输出包括轮次延迟和收听等待时间的
最小值/最大值/平均值、p50 和 p95。

## 智能体工具

工具名称：`voice_call`。

| 操作          | 参数                                       |
| --------------- | ------------------------------------------ |
| `initiate_call` | `message`, `to?`, `mode?`, `dtmfSequence?` |
| `continue_call` | `callId`, `message`                        |
| `speak_to_user` | `callId`, `message`                        |
| `send_dtmf`     | `callId`, `digits`                         |
| `end_call`      | `callId`                                   |
| `get_status`    | `callId`                                   |

语音通话插件附带一个匹配的智能体技能。

## Gateway RPC 参考

| 方法                      | 参数                                                             | 说明                                                                     |
| --------------------------- | ---------------------------------------------------------------- | ------------------------------------------------------------------------- |
| `voicecall.initiate`        | `to?`, `message`, `mode?`, `sessionKey?`, `requesterSessionKey?` | 省略 `to` 时，回退使用 `toNumber` 配置。                     |
| `voicecall.start`           | `to`, `message?`, `mode?`, `dtmfSequence?`, `sessionKey?`        | 与 `initiate` 相同，但还接受连接前的 `dtmfSequence`。           |
| `voicecall.continue`        | `callId`, `message`                                              | 阻塞至该轮次完成；返回转录文本。                   |
| `voicecall.continue.start`  | `callId`, `message`                                              | 异步变体：立即返回一个 `operationId`。                      |
| `voicecall.continue.result` | `operationId`                                                    | 轮询待处理的 `voicecall.continue.start` 操作以获取结果。      |
| `voicecall.speak`           | `callId`, `message`                                              | 无需等待即可播报；当 `realtime.enabled` 时使用实时桥接。 |
| `voicecall.dtmf`            | `callId`, `digits`                                               |                                                                           |
| `voicecall.end`             | `callId`                                                         |                                                                           |
| `voicecall.status`          | `callId?`                                                        | 省略 `callId` 可列出所有活动通话。                                   |

`dtmfSequence` 仅可与 `mode: "conversation"` 一起使用；如果通知模式通话需要在连接后发送
数字，应在通话建立后使用 `voicecall.dtmf`。

## 故障排查

### 设置因 webhook 暴露失败

在运行 Gateway 网关的同一环境中运行设置：

```bash
openclaw voicecall setup
openclaw voicecall setup --json
```

对于 `twilio`、`telnyx` 和 `plivo`，`webhook-exposure` 必须为绿色。即使已
配置 `publicUrl`，当它指向本地或私有
网络空间时仍会失败，因为运营商无法回调这些地址。
请勿将 `localhost`、`127.0.0.1`、`0.0.0.0`、`10.x`、`172.16.x`-`172.31.x`、
`192.168.x`、`169.254.x`、`fc00::/7`、`fd00::/8` 或其他运营商级 NAT
地址范围用作 `publicUrl`。

Twilio 通知模式的出站通话会在创建通话的请求中直接发送初始 `` TwiML，
因此第一条语音消息不依赖 Twilio 获取 webhook TwiML。状态
回调、对话通话、连接前 DTMF、实时流以及
连接后通话控制仍需要公共 webhook。

使用一种公共暴露方式：

```json5
{
  plugins: {
    entries: {
      "voice-call": {
        config: {
          publicUrl: "https://voice.example.com/voice/webhook",
          // 或
          tunnel: { provider: "ngrok" },
          // 或
          tailscale: { mode: "funnel", path: "/voice/webhook" },
        },
      },
    },
  },
}
```

更改配置后，重启或重新加载 Gateway 网关，然后运行：

```bash
openclaw voicecall setup
openclaw voicecall smoke
```

除非传入 `--yes`，否则 `voicecall smoke` 仅执行试运行。

### 提供商凭据失败

检查所选提供商和必需的凭据字段：

- Twilio：`twilio.accountSid`、`twilio.authToken` 和 `fromNumber`，或者
  `TWILIO_ACCOUNT_SID`、`TWILIO_AUTH_TOKEN` 和 `TWILIO_FROM_NUMBER`。
- Telnyx：`telnyx.apiKey`、`telnyx.connectionId`、`telnyx.publicKey` 和
  `fromNumber`，或者 `TELNYX_API_KEY`、`TELNYX_CONNECTION_ID` 和
  `TELNYX_PUBLIC_KEY`。
- Plivo：`plivo.authId`、`plivo.authToken` 和 `fromNumber`，或者
  `PLIVO_AUTH_ID` 和 `PLIVO_AUTH_TOKEN`。

凭据必须存在于 Gateway 网关主机上。编辑本地 shell 配置文件
不会影响已经运行的 Gateway 网关，必须重启或重新加载其
环境后才会生效。

### 通话已开始，但未收到提供商 webhook

确认提供商控制台指向准确的公共 webhook URL：

```text
https://voice.example.com/voice/webhook
```

然后检查运行时状态：

```bash
openclaw voicecall status --call-id <id>
openclaw voicecall tail
openclaw logs --follow
```

常见原因：

- `publicUrl` 指向的路径与 `serve.path` 不同。
- Gateway 网关启动后，隧道 URL 发生了变化。
- 代理转发了请求，但移除或改写了 host/proto 标头。
- 防火墙或 DNS 将公共主机名路由到了 Gateway 网关以外的位置。
- Gateway 网关重启时未启用语音通话插件。

当 Gateway 网关前面有反向代理或隧道时，将
`webhookSecurity.allowedHosts` 设置为公共主机名，或对已知代理地址使用
`webhookSecurity.trustedProxyIPs`。仅当代理边界
由你控制时才使用 `webhookSecurity.trustForwardingHeaders`。

### 签名验证失败

系统根据 OpenClaw 从传入请求中重建的公共 URL
检查提供商签名。如果签名失败：

- 确认提供商 webhook URL 与 `publicUrl` 完全匹配，包括协议、主机和路径。
- 对于 ngrok 免费套餐 URL，当隧道主机名变化时更新 `publicUrl`。
- 确保代理保留原始 host 和 proto 标头，或配置 `webhookSecurity.allowedHosts`。
- 除本地测试外，请勿启用 `skipSignatureVerification`。

### Google Meet Twilio 加入失败

Google Meet 使用此插件通过 Twilio 拨号加入。首先验证语音
通话：

```bash
openclaw voicecall setup
openclaw voicecall smoke --to "+15555550123"
```

然后明确验证 Google Meet 传输：

```bash
openclaw googlemeet setup --transport twilio
```

如果语音通话状态正常，但 Meet 参与者始终未加入，请检查 Meet
拨入号码、PIN 和 `--dtmf-sequence`。电话通话可能正常，
但会议可能会拒绝或忽略错误的 DTMF 序列。

Google Meet 通过 `voicecall.start` 启动 Twilio 电话链路，并附带
连接前 DTMF 序列。由 PIN 派生的序列会将 Google Meet
插件的 `voiceCall.dtmfDelayMs`（默认 **12000 ms**）作为开头的 Twilio
等待数字，因为 Meet 拨号提示可能延迟出现。随后，语音通话会在请求
介绍问候语之前重定向回实时处理。

使用 `openclaw logs --follow` 查看实时阶段跟踪。正常的 Twilio Meet
加入会按以下顺序记录日志：

- Google Meet 将 Twilio 加入操作委托给语音通话。
- 语音通话存储连接前 DTMF TwiML。
- 在实时处理之前，系统使用并提供 Twilio 初始 TwiML。
- 语音通话为 Twilio 通话提供实时 TwiML。
- Google Meet 在 DTMF 后延迟结束后，使用 `voicecall.speak` 请求介绍语音。

`openclaw voicecall tail` 仍会显示持久化的通话记录；它适用于查看
通话状态和转录文本，但并非所有 webhook/实时转换
都会显示在其中。

### 实时通话没有语音

确认仅启用一种音频模式：`realtime.enabled` 和
`streaming.enabled` 不能同时为 true。

对于实时 Twilio/Telnyx 通话，还需验证：

- 已加载并注册实时提供商插件。
- `realtime.provider` 未设置，或指定了已注册的提供商。
- Gateway 网关进程可以使用提供商 API key。
- `openclaw logs --follow` 显示已提供实时 TwiML、实时桥接已启动且初始问候语已加入队列。

## 相关内容

- [Talk 模式](https://funcoding.ai/agents/openclaw/nodes/talk/)
- [文本转语音](https://funcoding.ai/agents/openclaw/tools/tts/)
- [语音唤醒](https://funcoding.ai/agents/openclaw/nodes/voicewake/)
