Skip to content

[Bug] Feishu TTS auto:inbound not triggering - missing inboundAudio check in dispatch #72034

Description

@motor820

Summary

When messages.tts.auto is set to "inbound" on Feishu channel, the system should respond with voice when receiving audio messages, but it responds with text instead. No TTS synthesis attempt is made.

Environment

  • OpenClaw: 2026.4.24
  • Channel: Feishu (Lark)
  • TTS config: messages.tts.auto = "inbound", provider = "microsoft"
  • OS: Linux x64

What works

  • Feishu inbound voice messages reach the bot
  • Audio files are downloaded successfully to ~/.openclaw/media/inbound/
  • message_type is correctly identified as audio
  • MediaType is correctly set to audio/ogg; codecs=opus
  • MediaPaths contains the correct .ogg file path
  • isInboundAudioContext(ctx) returns true (verified by debugging)

What fails

  • Voice in → voice out does not happen
  • Replies arrive as plain text
  • Gateway logs show no TTS synthesis attempt for those replies
  • maybeApplyTtsToReplyPayload does not proceed to TTS synthesis

Root Cause Analysis

After extensive debugging, I found the issue in dispatch-Dw_9PM8V.js:

The function maybeApplyTtsToReplyPayload only checks shouldAttemptTtsPayload (which checks ttsAuto config), but does NOT check inboundAudio for "inbound" auto mode:

async function maybeApplyTtsToReplyPayload(params) {
    if (!shouldAttemptTtsPayload({
        cfg: params.cfg,
        ttsAuto: params.ttsAuto
    })) return params.payload;
    // ❌ Missing: inboundAudio check for "inbound" mode
    const { maybeApplyTtsToPayload } = await loadTtsRuntime();
    return maybeApplyTtsToPayload(params);
}

However, in dispatch-acp-CEqX6KWn.js (ACP dispatch), there IS a correct check:

async function maybeApplyAcpTts(params) {
    const ttsStatus = resolveStatusTtsSnapshot({...});
    if (!ttsStatus) return params.payload;
    // ✅ Correct check:
    if (ttsStatus.autoMode === "inbound" && !params.inboundAudio) return params.payload;
    ...
}

The inconsistency: ACP dispatch has the inboundAudio gate, but the regular dispatch (dispatch-Dw_9PM8V.js) does not.

Data Flow Verified

I added diagnostic logging and confirmed the complete data flow:

  1. parseMessageEventconvertMessageContent returns {content, resources} where resources contains the audio ResourceDescriptor ✅
  2. resolveMedia → downloads audio file, returns mediaList
  3. buildFeishuMediaPayload → sets MediaType, MediaPaths, MediaTypes
  4. buildInboundPayload → spreads mediaPayload into context ✅
  5. isInboundAudioContext(ctx) → correctly returns true (detects MediaType: audio/ogg and .ogg extension in MediaPaths) ✅
  6. inboundAudio is passed to maybeApplyTtsToReplyPayload
  7. maybeApplyTtsToReplyPayloaddoes not check inboundAudio, proceeds to loadTtsRuntime() but the condition fails somewhere else, no TTS synthesis ❌

Suggested Fix

Add inboundAudio check to maybeApplyTtsToReplyPayload in dispatch-Dw_9PM8V.js:

async function maybeApplyTtsToReplyPayload(params) {
    if (!shouldAttemptTtsPayload({
        cfg: params.cfg,
        ttsAuto: params.ttsAuto
    })) return params.payload;
    // Add inboundAudio check for "inbound" auto mode
    const ttsAuto = normalizeTtsAutoMode(params.ttsAuto);
    if (ttsAuto === "inbound" && !params.inboundAudio) return params.payload;
    const { maybeApplyTtsToPayload } = await loadTtsRuntime();
    return maybeApplyTtsToPayload(params);
}

Related Issues

Why this matters

The documented / expected behaviour for messages.tts.auto = "inbound" is voice in → voice out.

Currently, Feishu voice messages become:

  • voice in
  • transcript in agent context
  • text out only

with no TTS attempt.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions