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:
parseMessageEvent → convertMessageContent returns {content, resources} where resources contains the audio ResourceDescriptor ✅
resolveMedia → downloads audio file, returns mediaList ✅
buildFeishuMediaPayload → sets MediaType, MediaPaths, MediaTypes ✅
buildInboundPayload → spreads mediaPayload into context ✅
isInboundAudioContext(ctx) → correctly returns true (detects MediaType: audio/ogg and .ogg extension in MediaPaths) ✅
inboundAudio is passed to maybeApplyTtsToReplyPayload ✅
maybeApplyTtsToReplyPayload → does 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.
Summary
When
messages.tts.autois 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
messages.tts.auto = "inbound", provider = "microsoft"What works
~/.openclaw/media/inbound/message_typeis correctly identified asaudioMediaTypeis correctly set toaudio/ogg; codecs=opusMediaPathscontains the correct.oggfile pathisInboundAudioContext(ctx)returnstrue(verified by debugging)What fails
maybeApplyTtsToReplyPayloaddoes not proceed to TTS synthesisRoot Cause Analysis
After extensive debugging, I found the issue in
dispatch-Dw_9PM8V.js:The function
maybeApplyTtsToReplyPayloadonly checksshouldAttemptTtsPayload(which checksttsAutoconfig), but does NOT checkinboundAudiofor"inbound"auto mode:However, in
dispatch-acp-CEqX6KWn.js(ACP dispatch), there IS a correct check:The inconsistency: ACP dispatch has the
inboundAudiogate, but the regular dispatch (dispatch-Dw_9PM8V.js) does not.Data Flow Verified
I added diagnostic logging and confirmed the complete data flow:
parseMessageEvent→convertMessageContentreturns{content, resources}whereresourcescontains the audio ResourceDescriptor ✅resolveMedia→ downloads audio file, returnsmediaList✅buildFeishuMediaPayload→ setsMediaType,MediaPaths,MediaTypes✅buildInboundPayload→ spreadsmediaPayloadinto context ✅isInboundAudioContext(ctx)→ correctly returnstrue(detectsMediaType: audio/oggand.oggextension inMediaPaths) ✅inboundAudiois passed tomaybeApplyTtsToReplyPayload✅maybeApplyTtsToReplyPayload→ does not checkinboundAudio, proceeds toloadTtsRuntime()but the condition fails somewhere else, no TTS synthesis ❌Suggested Fix
Add
inboundAudiocheck tomaybeApplyTtsToReplyPayloadindispatch-Dw_9PM8V.js:Related Issues
Why this matters
The documented / expected behaviour for
messages.tts.auto = "inbound"is voice in → voice out.Currently, Feishu voice messages become:
with no TTS attempt.