mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 13:11:22 +00:00
@@ -21,6 +21,8 @@ import { createTelegramDraftStream } from "./draft-stream.js";
|
||||
import { cacheSticker, describeStickerImage } from "./sticker-cache.js";
|
||||
import { resolveAgentDir } from "../agents/agent-scope.js";
|
||||
|
||||
const EMPTY_RESPONSE_FALLBACK = "No response generated. Please try again.";
|
||||
|
||||
async function resolveStickerVisionSupport(cfg, agentId) {
|
||||
try {
|
||||
const catalog = await loadModelCatalog({ config: cfg });
|
||||
@@ -198,6 +200,15 @@ export const dispatchTelegramMessage = async ({
|
||||
}
|
||||
}
|
||||
|
||||
const replyQuoteText =
|
||||
ctxPayload.ReplyToIsQuote && ctxPayload.ReplyToBody
|
||||
? ctxPayload.ReplyToBody.trim() || undefined
|
||||
: undefined;
|
||||
const deliveryState = {
|
||||
delivered: false,
|
||||
skippedNonSilent: 0,
|
||||
};
|
||||
|
||||
const { queuedFinal } = await dispatchReplyWithBufferedBlockDispatcher({
|
||||
ctx: ctxPayload,
|
||||
cfg,
|
||||
@@ -209,12 +220,7 @@ export const dispatchTelegramMessage = async ({
|
||||
await flushDraft();
|
||||
draftStream?.stop();
|
||||
}
|
||||
|
||||
const replyQuoteText =
|
||||
ctxPayload.ReplyToIsQuote && ctxPayload.ReplyToBody
|
||||
? ctxPayload.ReplyToBody.trim() || undefined
|
||||
: undefined;
|
||||
await deliverReplies({
|
||||
const result = await deliverReplies({
|
||||
replies: [payload],
|
||||
chatId: String(chatId),
|
||||
token: opts.token,
|
||||
@@ -228,8 +234,13 @@ export const dispatchTelegramMessage = async ({
|
||||
onVoiceRecording: sendRecordVoice,
|
||||
linkPreview: telegramCfg.linkPreview,
|
||||
replyQuoteText,
|
||||
notifyEmptyResponse: info.kind === "final",
|
||||
});
|
||||
if (result.delivered) {
|
||||
deliveryState.delivered = true;
|
||||
}
|
||||
},
|
||||
onSkip: (_payload, info) => {
|
||||
if (info.reason !== "silent") deliveryState.skippedNonSilent += 1;
|
||||
},
|
||||
onError: (err, info) => {
|
||||
runtime.error?.(danger(`telegram ${info.kind} reply failed: ${String(err)}`));
|
||||
@@ -261,7 +272,27 @@ export const dispatchTelegramMessage = async ({
|
||||
},
|
||||
});
|
||||
draftStream?.stop();
|
||||
if (!queuedFinal) {
|
||||
let sentFallback = false;
|
||||
if (!deliveryState.delivered && deliveryState.skippedNonSilent > 0) {
|
||||
const result = await deliverReplies({
|
||||
replies: [{ text: EMPTY_RESPONSE_FALLBACK }],
|
||||
chatId: String(chatId),
|
||||
token: opts.token,
|
||||
runtime,
|
||||
bot,
|
||||
replyToMode,
|
||||
textLimit,
|
||||
messageThreadId: resolvedThreadId,
|
||||
tableMode,
|
||||
chunkMode,
|
||||
linkPreview: telegramCfg.linkPreview,
|
||||
replyQuoteText,
|
||||
});
|
||||
sentFallback = result.delivered;
|
||||
}
|
||||
|
||||
const hasFinalResponse = queuedFinal || sentFallback;
|
||||
if (!hasFinalResponse) {
|
||||
if (isGroup && historyKey) {
|
||||
clearHistoryEntriesIfEnabled({ historyMap: groupHistories, historyKey, limit: historyLimit });
|
||||
}
|
||||
|
||||
@@ -50,6 +50,8 @@ import {
|
||||
import { firstDefined, isSenderAllowed, normalizeAllowFromWithStore } from "./bot-access.js";
|
||||
import { readTelegramAllowFromStore } from "./pairing-store.js";
|
||||
|
||||
const EMPTY_RESPONSE_FALLBACK = "No response generated. Please try again.";
|
||||
|
||||
type TelegramNativeCommandContext = Context & { match?: string };
|
||||
|
||||
type TelegramCommandAuthResult = {
|
||||
@@ -483,13 +485,18 @@ export const registerTelegramNativeCommands = ({
|
||||
: undefined;
|
||||
const chunkMode = resolveChunkMode(cfg, "telegram", route.accountId);
|
||||
|
||||
const deliveryState = {
|
||||
delivered: false,
|
||||
skippedNonSilent: 0,
|
||||
};
|
||||
|
||||
await dispatchReplyWithBufferedBlockDispatcher({
|
||||
ctx: ctxPayload,
|
||||
cfg,
|
||||
dispatcherOptions: {
|
||||
responsePrefix: resolveEffectiveMessagesConfig(cfg, route.agentId).responsePrefix,
|
||||
deliver: async (payload, info) => {
|
||||
await deliverReplies({
|
||||
deliver: async (payload, _info) => {
|
||||
const result = await deliverReplies({
|
||||
replies: [payload],
|
||||
chatId: String(chatId),
|
||||
token: opts.token,
|
||||
@@ -501,8 +508,13 @@ export const registerTelegramNativeCommands = ({
|
||||
tableMode,
|
||||
chunkMode,
|
||||
linkPreview: telegramCfg.linkPreview,
|
||||
notifyEmptyResponse: info.kind === "final",
|
||||
});
|
||||
if (result.delivered) {
|
||||
deliveryState.delivered = true;
|
||||
}
|
||||
},
|
||||
onSkip: (_payload, info) => {
|
||||
if (info.reason !== "silent") deliveryState.skippedNonSilent += 1;
|
||||
},
|
||||
onError: (err, info) => {
|
||||
runtime.error?.(danger(`telegram slash ${info.kind} reply failed: ${String(err)}`));
|
||||
@@ -513,6 +525,21 @@ export const registerTelegramNativeCommands = ({
|
||||
disableBlockStreaming,
|
||||
},
|
||||
});
|
||||
if (!deliveryState.delivered && deliveryState.skippedNonSilent > 0) {
|
||||
await deliverReplies({
|
||||
replies: [{ text: EMPTY_RESPONSE_FALLBACK }],
|
||||
chatId: String(chatId),
|
||||
token: opts.token,
|
||||
runtime,
|
||||
bot,
|
||||
replyToMode,
|
||||
textLimit,
|
||||
messageThreadId: threadIdForSend,
|
||||
tableMode,
|
||||
chunkMode,
|
||||
linkPreview: telegramCfg.linkPreview,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -44,8 +44,6 @@ export async function deliverReplies(params: {
|
||||
linkPreview?: boolean;
|
||||
/** Optional quote text for Telegram reply_parameters. */
|
||||
replyQuoteText?: string;
|
||||
/** If true, send a fallback message when all replies are empty. Default: false */
|
||||
notifyEmptyResponse?: boolean;
|
||||
}): Promise<{ delivered: boolean }> {
|
||||
const {
|
||||
replies,
|
||||
@@ -60,7 +58,10 @@ export async function deliverReplies(params: {
|
||||
} = params;
|
||||
const chunkMode = params.chunkMode ?? "length";
|
||||
let hasReplied = false;
|
||||
let skippedEmpty = 0;
|
||||
let hasDelivered = false;
|
||||
const markDelivered = () => {
|
||||
hasDelivered = true;
|
||||
};
|
||||
const chunkText = (markdown: string) => {
|
||||
const markdownChunks =
|
||||
chunkMode === "newline"
|
||||
@@ -88,7 +89,6 @@ export async function deliverReplies(params: {
|
||||
continue;
|
||||
}
|
||||
runtime.error?.(danger("reply missing text/media"));
|
||||
skippedEmpty++;
|
||||
continue;
|
||||
}
|
||||
const replyToId = replyToMode === "off" ? undefined : resolveTelegramReplyId(reply.replyToId);
|
||||
@@ -118,6 +118,7 @@ export async function deliverReplies(params: {
|
||||
linkPreview,
|
||||
replyMarkup: shouldAttachButtons ? replyMarkup : undefined,
|
||||
});
|
||||
markDelivered();
|
||||
if (replyToId && !hasReplied) {
|
||||
hasReplied = true;
|
||||
}
|
||||
@@ -169,18 +170,21 @@ export async function deliverReplies(params: {
|
||||
runtime,
|
||||
fn: () => bot.api.sendAnimation(chatId, file, { ...mediaParams }),
|
||||
});
|
||||
markDelivered();
|
||||
} else if (kind === "image") {
|
||||
await withTelegramApiErrorLogging({
|
||||
operation: "sendPhoto",
|
||||
runtime,
|
||||
fn: () => bot.api.sendPhoto(chatId, file, { ...mediaParams }),
|
||||
});
|
||||
markDelivered();
|
||||
} else if (kind === "video") {
|
||||
await withTelegramApiErrorLogging({
|
||||
operation: "sendVideo",
|
||||
runtime,
|
||||
fn: () => bot.api.sendVideo(chatId, file, { ...mediaParams }),
|
||||
});
|
||||
markDelivered();
|
||||
} else if (kind === "audio") {
|
||||
const { useVoice } = resolveTelegramVoiceSend({
|
||||
wantsVoice: reply.audioAsVoice === true, // default false (backward compatible)
|
||||
@@ -199,6 +203,7 @@ export async function deliverReplies(params: {
|
||||
shouldLog: (err) => !isVoiceMessagesForbidden(err),
|
||||
fn: () => bot.api.sendVoice(chatId, file, { ...mediaParams }),
|
||||
});
|
||||
markDelivered();
|
||||
} catch (voiceErr) {
|
||||
// Fall back to text if voice messages are forbidden in this chat.
|
||||
// This happens when the recipient has Telegram Premium privacy settings
|
||||
@@ -225,6 +230,7 @@ export async function deliverReplies(params: {
|
||||
replyMarkup,
|
||||
replyQuoteText,
|
||||
});
|
||||
markDelivered();
|
||||
// Skip this media item; continue with next.
|
||||
continue;
|
||||
}
|
||||
@@ -237,6 +243,7 @@ export async function deliverReplies(params: {
|
||||
runtime,
|
||||
fn: () => bot.api.sendAudio(chatId, file, { ...mediaParams }),
|
||||
});
|
||||
markDelivered();
|
||||
}
|
||||
} else {
|
||||
await withTelegramApiErrorLogging({
|
||||
@@ -244,6 +251,7 @@ export async function deliverReplies(params: {
|
||||
runtime,
|
||||
fn: () => bot.api.sendDocument(chatId, file, { ...mediaParams }),
|
||||
});
|
||||
markDelivered();
|
||||
}
|
||||
if (replyToId && !hasReplied) {
|
||||
hasReplied = true;
|
||||
@@ -264,6 +272,7 @@ export async function deliverReplies(params: {
|
||||
linkPreview,
|
||||
replyMarkup: i === 0 ? replyMarkup : undefined,
|
||||
});
|
||||
markDelivered();
|
||||
if (replyToId && !hasReplied) {
|
||||
hasReplied = true;
|
||||
}
|
||||
@@ -273,17 +282,7 @@ export async function deliverReplies(params: {
|
||||
}
|
||||
}
|
||||
|
||||
// If all replies were empty and notifyEmptyResponse is enabled, send a fallback message
|
||||
// Check both: (1) replies with no content (skippedEmpty), (2) no replies at all (empty array)
|
||||
if (!hasReplied && (skippedEmpty > 0 || replies.length === 0) && params.notifyEmptyResponse) {
|
||||
const fallbackText = "No response generated. Please try again.";
|
||||
await sendTelegramText(bot, chatId, fallbackText, runtime, {
|
||||
messageThreadId,
|
||||
});
|
||||
hasReplied = true;
|
||||
}
|
||||
|
||||
return { delivered: hasReplied };
|
||||
return { delivered: hasDelivered };
|
||||
}
|
||||
|
||||
export async function resolveMedia(
|
||||
|
||||
Reference in New Issue
Block a user