fix(bot,matrix): use resolved conversation key for audio storage
Replace hardcoded 'default' conversation ID with convKey in core bot path and chatId in adapter-internal audio calls. Prevents mapping inconsistencies in per-chat/per-channel routing.
This commit is contained in:
@@ -227,8 +227,11 @@ export class MatrixAdapter implements ChannelAdapter {
|
||||
if (!this.client) throw new Error("Matrix client not initialized");
|
||||
|
||||
const { chatId, text } = msg;
|
||||
const { plain, html } = formatMatrixHTML(text);
|
||||
const htmlBody = (msg.htmlPrefix || '') + html;
|
||||
// If parseMode is HTML, text is already formatted — skip markdown conversion
|
||||
const { plain, html } = msg.parseMode === 'HTML'
|
||||
? { plain: text.replace(/<[^>]+>/g, ''), html: text }
|
||||
: formatMatrixHTML(text);
|
||||
const htmlBody = html;
|
||||
|
||||
const content = {
|
||||
msgtype: MsgType.Text,
|
||||
@@ -273,11 +276,11 @@ export class MatrixAdapter implements ChannelAdapter {
|
||||
return true; // 'all'
|
||||
}
|
||||
|
||||
async editMessage(chatId: string, messageId: string, text: string, htmlPrefix?: string): Promise<void> {
|
||||
async editMessage(chatId: string, messageId: string, text: string): Promise<void> {
|
||||
if (!this.client) throw new Error("Matrix client not initialized");
|
||||
|
||||
const { plain, html } = formatMatrixHTML(text);
|
||||
const htmlBody = (htmlPrefix || '') + html;
|
||||
const htmlBody = html;
|
||||
const prefixedPlain = this.config.messagePrefix ? `${this.config.messagePrefix}\n\n${plain}` : plain;
|
||||
const prefixedHtml = this.config.messagePrefix ? `${this.config.messagePrefix}<br><br>${htmlBody}` : htmlBody;
|
||||
|
||||
@@ -1454,7 +1457,7 @@ export class MatrixAdapter implements ChannelAdapter {
|
||||
if (kind === 'audio') {
|
||||
this.ourAudioEvents.add(eventId);
|
||||
if (caption) {
|
||||
this.storage.storeAudioMessage(eventId, 'default', chatId, caption);
|
||||
this.storage.storeAudioMessage(eventId, chatId, chatId, caption);
|
||||
}
|
||||
const reactionContent: ReactionEventContent = {
|
||||
"m.relates_to": {
|
||||
@@ -1485,7 +1488,7 @@ export class MatrixAdapter implements ChannelAdapter {
|
||||
const audioEventId = await this.uploadAndSendAudio(roomId, audioData);
|
||||
if (audioEventId) {
|
||||
// Store mapping so 🎤 on the regenerated audio works too
|
||||
this.storage.storeAudioMessage(audioEventId, "default", roomId, text);
|
||||
this.storage.storeAudioMessage(audioEventId, roomId, roomId, text);
|
||||
}
|
||||
return audioEventId;
|
||||
} catch (err) {
|
||||
@@ -1516,7 +1519,7 @@ export class MatrixAdapter implements ChannelAdapter {
|
||||
const audioEventId = await this.uploadAndSendAudio(chatId, audioData);
|
||||
if (audioEventId) {
|
||||
// Store for 🎤 regeneration
|
||||
this.storage.storeAudioMessage(audioEventId, "default", chatId, text);
|
||||
this.storage.storeAudioMessage(audioEventId, chatId, chatId, text);
|
||||
}
|
||||
} catch (err) {
|
||||
log.error("TTS failed (non-fatal):", err);
|
||||
|
||||
@@ -13,7 +13,7 @@ import { extname, resolve, join } from 'node:path';
|
||||
import type { ChannelAdapter } from '../channels/types.js';
|
||||
import type { BotConfig, InboundMessage, TriggerContext, TriggerType, StreamMsg } from './types.js';
|
||||
import { formatApiErrorForUser } from './errors.js';
|
||||
import { formatToolCallDisplay, formatReasoningDisplay, formatQuestionsForChannel, formatReasoningAsCodeBlock } from './display.js';
|
||||
import { formatToolCallDisplay, formatReasoningDisplay, formatQuestionsForChannel } from './display.js';
|
||||
import type { AgentSession } from './interfaces.js';
|
||||
import { Store } from './store.js';
|
||||
import { getPendingApprovals, rejectApproval, cancelRuns, cancelConversation, recoverOrphanedConversationApproval, getLatestRunError, getAgentModel, updateAgentModel, isRecoverableConversationId, recoverPendingApprovalsForAgent } from '../tools/letta-api.js';
|
||||
@@ -1880,42 +1880,20 @@ export class LettaBot implements AgentSession {
|
||||
await new Promise(resolve => setTimeout(resolve, waitMs));
|
||||
}
|
||||
|
||||
// Determine if reasoning should be shown for this room
|
||||
const chatId = msg.chatId;
|
||||
const noReasoningRooms = this.config.display?.noReasoningRooms || [];
|
||||
const reasoningRooms = this.config.display?.reasoningRooms;
|
||||
const shouldShowReasoning = this.config.display?.showReasoning &&
|
||||
!noReasoningRooms.includes(chatId) &&
|
||||
(!reasoningRooms || reasoningRooms.length === 0 || reasoningRooms.includes(chatId));
|
||||
|
||||
// Build reasoning HTML prefix if available (injected into formatted_body only)
|
||||
let reasoningHtmlPrefix: string | undefined;
|
||||
if (collectedReasoning.trim() && shouldShowReasoning) {
|
||||
const reasoningBlock = formatReasoningAsCodeBlock(
|
||||
collectedReasoning,
|
||||
adapter.id,
|
||||
this.config.display?.reasoningMaxChars
|
||||
);
|
||||
if (reasoningBlock) {
|
||||
reasoningHtmlPrefix = reasoningBlock.text;
|
||||
log.info(`Reasoning block generated (${reasoningHtmlPrefix.length} chars) for ${chatId}`);
|
||||
}
|
||||
}
|
||||
|
||||
const finalResponse = this.prefixResponse(response);
|
||||
|
||||
try {
|
||||
if (messageId) {
|
||||
await adapter.editMessage(msg.chatId, messageId, finalResponse, reasoningHtmlPrefix);
|
||||
await adapter.editMessage(msg.chatId, messageId, finalResponse);
|
||||
} else {
|
||||
await adapter.sendMessage({ chatId: msg.chatId, text: finalResponse, threadId: msg.threadId, htmlPrefix: reasoningHtmlPrefix });
|
||||
await adapter.sendMessage({ chatId: msg.chatId, text: finalResponse, threadId: msg.threadId });
|
||||
}
|
||||
sentAnyMessage = true;
|
||||
this.store.resetRecoveryAttempts();
|
||||
} catch (sendErr) {
|
||||
log.warn('Final message delivery failed:', sendErr instanceof Error ? sendErr.message : sendErr);
|
||||
try {
|
||||
const result = await adapter.sendMessage({ chatId: msg.chatId, text: finalResponse, threadId: msg.threadId, htmlPrefix: reasoningHtmlPrefix });
|
||||
const result = await adapter.sendMessage({ chatId: msg.chatId, text: finalResponse, threadId: msg.threadId });
|
||||
messageId = result.messageId ?? null;
|
||||
sentAnyMessage = true;
|
||||
this.store.resetRecoveryAttempts();
|
||||
@@ -1931,7 +1909,7 @@ export class LettaBot implements AgentSession {
|
||||
// 🎤 on bot's TEXT message (tap to regenerate TTS audio)
|
||||
adapter.addReaction?.(msg.chatId, messageId, '🎤').catch(() => {});
|
||||
// Store raw text — adapter's TTS layer will clean it at synthesis time
|
||||
adapter.storeAudioMessage?.(messageId, 'default', msg.chatId, response);
|
||||
adapter.storeAudioMessage?.(messageId, convKey, msg.chatId, response);
|
||||
// Generate TTS audio only in response to voice input
|
||||
if (msg.isVoiceInput) {
|
||||
adapter.sendAudio?.(msg.chatId, response).catch((err) => {
|
||||
|
||||
Reference in New Issue
Block a user