Skip to content

[Feature]: 1: Feishu voice bubble — MP3/WAV sent as file instead of voice bubble、2: Per-agent TTS voice override for Microsoft Edge TTS #56231

Description

@olccamy

Summary

Issue 1: Feishu voice bubble — MP3/WAV files sent as file attachment instead of voice bubble

Description

When OpenClaw sends TTS-generated MP3 files to Feishu, they are delivered as file attachments instead of voice bubbles (audio messages). Feishu requires Opus format (audio msg_type) to render voice bubbles natively.

Current Behavior

  1. TTS provider (Microsoft Edge TTS) generates an MP3 file
  2. sendMediaFeishu() in the Feishu extension detects the file extension
  3. .mp3 is not in the Opus/OGG match list, so it falls through to fileType: "stream"msgType: "file"
  4. Feishu displays it as a downloadable file, not a playable voice bubble

Expected Behavior

MP3 and WAV files sent through the Feishu channel should be automatically converted to Opus (using ffmpeg) and sent as msgType: "audio", so they render as native voice bubbles in the Feishu client.

Suggested Implementation

In extensions/feishu/src/media.ts (or the compiled equivalent), before resolveFeishuOutboundMediaKind() is called in sendMediaFeishu():

  1. Detect audio format from buffer magic bytes (MP3: ID3 header or 0xFF 0xE0 frame sync; WAV: RIFF header)
  2. If the file is MP3 or WAV, convert to Opus via ffmpeg:
    ffmpeg -y -i input.mp3 -ac 1 -b:a 32k -application voip output.opus
  3. Update the filename to .opus and contentType to audio/opus
  4. Then resolveFeishuOutboundMediaKind() will correctly route it as msgType: "audio"

Minimal code sketch

// In sendMediaFeishu(), after buffer is loaded, before routing:
const ext = path.extname(name).toLowerCase();
const audioFormat = detectAudioFormat(buffer); // check magic bytes
if (['.wav', '.mp3'].includes(ext) && audioFormat !== 'unknown') {
  buffer = await convertToOpus(buffer, audioFormat); // ffmpeg conversion
  name = path.basename(name, ext) + '.opus';
  contentType = 'audio/opus';
}

Environment

  • OpenClaw: v2026.3.24
  • Channel: Feishu
  • TTS Provider: Microsoft Edge TTS (outputs MP3)
  • OS: Linux (Debian 13, ffmpeg 7.1.3 available)

Workaround

Currently patching the compiled send-*.js file manually to add ffmpeg conversion. This patch breaks on every OpenClaw update.


Issue 2: Per-agent TTS voice override for Microsoft Edge TTS

Description

In a multi-agent setup, different agents should be able to use different TTS voices to be distinguishable. Currently, [[tts:...]] directives only support OpenAI and ElevenLabs voice overrides — Microsoft Edge TTS voice cannot be overridden per-agent or per-message.

Current Behavior

  1. Global TTS config sets messages.tts.microsoft.voice: "zh-TW-HsiaoChenNeural"
  2. Agent "fanqie" (番茄) tries to use a different voice via [[tts:zh-CN-XiaoxiaoNeural]]
  3. parseTtsDirectives() parses [[tts:key=value]] format only — bare voice names without = are skipped (if (eqIndex === -1) continue;)
  4. Even with correct key=value format, only voice (OpenAI) and voiceId (ElevenLabs) keys are recognized — there is no microsoft_voice key
  5. Result: all agents use the same Microsoft TTS voice, making them indistinguishable

Expected Behavior

Option A: Per-agent TTS config (preferred)

Allow agents.list[] entries to include TTS overrides:

{
  agents: {
    list: [
      {
        id: "main",
        tts: { microsoft: { voice: "zh-TW-HsiaoChenNeural" } }
      },
      {
        id: "fanqie",
        tts: { microsoft: { voice: "zh-CN-XiaoxiaoNeural" } }
      }
    ]
  }
}

Option B: [[tts:...]] directive support for Microsoft voice

Add a microsoft_voice (or just extend voice to be provider-aware) key to parseTtsDirectives():

[[tts:microsoft_voice=zh-CN-XiaoxiaoNeural]]

This would allow agents to override the Microsoft voice inline, same as OpenAI/ElevenLabs voices today.

Option C: Both

Per-agent config as the default, with [[tts:...]] as a per-message override.

Additional Context

Use Case

Multi-agent team where each agent has a distinct persona:

  • 小师妹 (main): zh-TW-HsiaoChenNeural — sweet Taiwanese accent
  • 番茄 (fanqie): zh-CN-XiaoxiaoNeural — warm news-anchor voice

Cron delivery TTS gap

Related issue: when a cron job uses --announce delivery to a Feishu group, [[tts:...]] tags in the agent's response are not parsed — they appear as literal text in the message. The TTS post-processing pipeline seems to be skipped for cron announce delivery.

This means even if [[tts:...]] supported Microsoft voices, cron-delivered messages to Feishu groups would still not get voice output. The per-agent config approach (Option A) would solve this more cleanly since it doesn't depend on inline tags.

Environment

  • OpenClaw: v2026.3.24
  • TTS Provider: Microsoft Edge TTS
  • Multi-agent setup with Feishu channel
  • Agents: main (Claude Opus 4.6) + fanqie (GLM-5-Turbo)

Summary

Gap Impact Suggested Fix
No microsoft_voice in parseTtsDirectives() Can't override Microsoft TTS voice per-message Add microsoft_voice key
No per-agent TTS config in agents.list[] All agents sound the same Add tts field to agent config
Cron announce skips TTS tag parsing [[tts:...]] appears as literal text in group Process TTS tags in announce delivery pipeline

Problem to solve

Issue 1: Feishu voice bubble — MP3/WAV sent as file instead of voice bubble
Issue 2: Per-agent TTS voice override for Microsoft Edge TTS

Proposed solution

Issue 1: Feishu voice bubble — MP3/WAV files sent as file attachment instead of voice bubble

Description

When OpenClaw sends TTS-generated MP3 files to Feishu, they are delivered as file attachments instead of voice bubbles (audio messages). Feishu requires Opus format (audio msg_type) to render voice bubbles natively.

Current Behavior

  1. TTS provider (Microsoft Edge TTS) generates an MP3 file
  2. sendMediaFeishu() in the Feishu extension detects the file extension
  3. .mp3 is not in the Opus/OGG match list, so it falls through to fileType: "stream"msgType: "file"
  4. Feishu displays it as a downloadable file, not a playable voice bubble

Expected Behavior

MP3 and WAV files sent through the Feishu channel should be automatically converted to Opus (using ffmpeg) and sent as msgType: "audio", so they render as native voice bubbles in the Feishu client.

Suggested Implementation

In extensions/feishu/src/media.ts (or the compiled equivalent), before resolveFeishuOutboundMediaKind() is called in sendMediaFeishu():

  1. Detect audio format from buffer magic bytes (MP3: ID3 header or 0xFF 0xE0 frame sync; WAV: RIFF header)
  2. If the file is MP3 or WAV, convert to Opus via ffmpeg:
    ffmpeg -y -i input.mp3 -ac 1 -b:a 32k -application voip output.opus
  3. Update the filename to .opus and contentType to audio/opus
  4. Then resolveFeishuOutboundMediaKind() will correctly route it as msgType: "audio"

Minimal code sketch

// In sendMediaFeishu(), after buffer is loaded, before routing:
const ext = path.extname(name).toLowerCase();
const audioFormat = detectAudioFormat(buffer); // check magic bytes
if (['.wav', '.mp3'].includes(ext) && audioFormat !== 'unknown') {
  buffer = await convertToOpus(buffer, audioFormat); // ffmpeg conversion
  name = path.basename(name, ext) + '.opus';
  contentType = 'audio/opus';
}

Environment

  • OpenClaw: v2026.3.24
  • Channel: Feishu
  • TTS Provider: Microsoft Edge TTS (outputs MP3)
  • OS: Linux (Debian 13, ffmpeg 7.1.3 available)

Workaround

Currently patching the compiled send-*.js file manually to add ffmpeg conversion. This patch breaks on every OpenClaw update.


Issue 2: Per-agent TTS voice override for Microsoft Edge TTS

Description

In a multi-agent setup, different agents should be able to use different TTS voices to be distinguishable. Currently, [[tts:...]] directives only support OpenAI and ElevenLabs voice overrides — Microsoft Edge TTS voice cannot be overridden per-agent or per-message.

Current Behavior

  1. Global TTS config sets messages.tts.microsoft.voice: "zh-TW-HsiaoChenNeural"
  2. Agent "fanqie" (番茄) tries to use a different voice via [[tts:zh-CN-XiaoxiaoNeural]]
  3. parseTtsDirectives() parses [[tts:key=value]] format only — bare voice names without = are skipped (if (eqIndex === -1) continue;)
  4. Even with correct key=value format, only voice (OpenAI) and voiceId (ElevenLabs) keys are recognized — there is no microsoft_voice key
  5. Result: all agents use the same Microsoft TTS voice, making them indistinguishable

Expected Behavior

Option A: Per-agent TTS config (preferred)

Allow agents.list[] entries to include TTS overrides:

{
  agents: {
    list: [
      {
        id: "main",
        tts: { microsoft: { voice: "zh-TW-HsiaoChenNeural" } }
      },
      {
        id: "fanqie",
        tts: { microsoft: { voice: "zh-CN-XiaoxiaoNeural" } }
      }
    ]
  }
}

Option B: [[tts:...]] directive support for Microsoft voice

Add a microsoft_voice (or just extend voice to be provider-aware) key to parseTtsDirectives():

[[tts:microsoft_voice=zh-CN-XiaoxiaoNeural]]

This would allow agents to override the Microsoft voice inline, same as OpenAI/ElevenLabs voices today.

Option C: Both

Per-agent config as the default, with [[tts:...]] as a per-message override.

Additional Context

Use Case

Multi-agent team where each agent has a distinct persona:

  • 小师妹 (main): zh-TW-HsiaoChenNeural — sweet Taiwanese accent
  • 番茄 (fanqie): zh-CN-XiaoxiaoNeural — warm news-anchor voice

Cron delivery TTS gap

Related issue: when a cron job uses --announce delivery to a Feishu group, [[tts:...]] tags in the agent's response are not parsed — they appear as literal text in the message. The TTS post-processing pipeline seems to be skipped for cron announce delivery.

This means even if [[tts:...]] supported Microsoft voices, cron-delivered messages to Feishu groups would still not get voice output. The per-agent config approach (Option A) would solve this more cleanly since it doesn't depend on inline tags.

Environment

  • OpenClaw: v2026.3.24
  • TTS Provider: Microsoft Edge TTS
  • Multi-agent setup with Feishu channel
  • Agents: main (Claude Opus 4.6) + fanqie (GLM-5-Turbo)

Summary

Gap Impact Suggested Fix
No microsoft_voice in parseTtsDirectives() Can't override Microsoft TTS voice per-message Add microsoft_voice key
No per-agent TTS config in agents.list[] All agents sound the same Add tts field to agent config
Cron announce skips TTS tag parsing [[tts:...]] appears as literal text in group Process TTS tags in announce delivery pipeline

Alternatives considered

No response

Impact

Feishu voice bubble — MP3/WAV sent as file instead of voice bubble
Per-agent TTS voice override for Microsoft Edge TTS

Evidence/examples

No response

Additional information

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions