openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
777 lines (776 loc) • 30.2 kB
JavaScript
import { c as normalizeOptionalString, p as readStringValue } from "./string-coerce-mnp54Vah.js";
import { i as formatErrorMessage } from "./errors-BXgSefBE.js";
import { r as logVerbose } from "./globals-GTrXU4s9.js";
import { i as getRuntimeConfig } from "./io-Gi7-pyU-.js";
import { u as resolveStorePath } from "./paths-NEwU8m3X.js";
import { o as readSessionUpdatedAt } from "./store-Qsgtu-0y.js";
import { a as enqueueSystemEvent } from "./system-events-C5WI3S5a.js";
import { a as resolveInboundLastRouteSessionKey } from "./resolve-route-xcIS8NFL.js";
import { l as resolvePinnedMainDmOwnerFromAllowlist } from "./dm-policy-shared-BjnswXJt.js";
import { n as loadWebMedia } from "./web-media-D8G2d_q7.js";
import "./error-runtime-C8vbtAJt.js";
import "./runtime-config-snapshot-CDoFLjqb.js";
import "./runtime-env-D_2q8-VK.js";
import "./security-runtime-CQm7DD1u.js";
import "./string-coerce-runtime-CEGJWkQ_.js";
import { o as isSafeToRetrySendError, s as isTelegramClientRejection } from "./request-timeouts-DYFbgXTJ.js";
import "./routing-CQ63ICPX.js";
import { y as buildChannelInboundEventContext } from "./kernel-BdraY_qZ.js";
import { t as recordInboundSession } from "./session-Do0yFgU8.js";
import { t as createChannelReplyPipeline } from "./reply-pipeline-eJPstCAz.js";
import { n as recordChannelActivity } from "./channel-activity-4piA219h.js";
import { a as readChannelAllowFromStore, d as upsertChannelPairingRequest } from "./pairing-store-CxANz_ON.js";
import { t as resolveApprovalOverGateway } from "./approval-gateway-resolver-kRkWoLay.js";
import "./approval-gateway-runtime-D7fO1U7E.js";
import { t as listSkillCommandsForAgents } from "./chat-commands-DKDF6ilZ.js";
import { t as dispatchReplyWithBufferedBlockDispatcher } from "./reply-dispatch-runtime-B3glKPcA.js";
import { t as deliverInboundReplyWithMessageSendContext } from "./channel-outbound-B3_Zy-kG.js";
import { a as takeMessageIdAfterStop, i as createFinalizableDraftStreamControlsForState } from "./draft-stream-controls-C8Kxicze.js";
import "./web-media-Nj3kZBh5.js";
import "./system-event-runtime-D80J5tzu.js";
import "./conversation-runtime-VjM1-D7a.js";
import { t as buildModelsProviderData } from "./commands-models-BhAJq6Rj.js";
import "./channel-inbound-bl7VmTdr.js";
import { t as loadSessionStore } from "./session-store-runtime-rvUx2Fil.js";
import "./models-provider-runtime-wrU609z3.js";
import "./skill-commands-runtime-CejwLNRH.js";
import { S as buildTelegramThreadParams, a as wasSentByBot, rt as normalizeTelegramReplyToMessageId } from "./sent-message-cache-IpiNMjf9.js";
import { r as normalizeTelegramCommandName, t as TELEGRAM_COMMAND_NAME_PATTERN } from "./command-config-CnxQsFly.js";
import { D as withTelegramApiErrorLogging, a as editMessageTelegram, y as recordOutboundMessageForPromptContext } from "./send-f0zZCDGg.js";
import { n as deliverReplies, r as emitInternalMessageSentHook } from "./delivery-5Fl83GEE.js";
import { createHash } from "node:crypto";
//#region extensions/telegram/src/bot-native-command-menu.ts
const TELEGRAM_MAX_COMMANDS = 100;
const TELEGRAM_TOTAL_COMMAND_TEXT_BUDGET = 5700;
const TELEGRAM_COMMAND_RETRY_RATIO = .8;
const TELEGRAM_MIN_COMMAND_DESCRIPTION_LENGTH = 1;
const TELEGRAM_MAX_COMMAND_DESCRIPTION_LENGTH = 256;
const TELEGRAM_MENU_RESULT_CACHE_MAX = 128;
const TELEGRAM_COMMAND_MENU_SCOPES = [{ label: "default" }, {
label: "all_group_chats",
options: { scope: { type: "all_group_chats" } }
}];
const cappedTelegramMenuCache = /* @__PURE__ */ new Map();
function countTelegramCommandText(value) {
let count = 0;
for (let index = 0; index < value.length;) {
const codePoint = value.codePointAt(index);
index += codePoint && codePoint > 65535 ? 2 : 1;
count += 1;
}
return count;
}
function truncateTelegramCommandText(value, maxLength) {
if (maxLength <= 0) return "";
const suffix = maxLength > 1 ? "…" : "";
const prefixLimit = maxLength - countTelegramCommandText(suffix);
let count = 0;
let prefixEnd = 0;
for (const char of value) {
count += 1;
if (count <= prefixLimit) prefixEnd += char.length;
if (count > maxLength) return `${value.slice(0, prefixEnd)}${suffix}`;
}
return value;
}
function fitTelegramCommandsWithinTextBudget(commands, maxTotalChars) {
let candidateCommands = [...commands];
while (candidateCommands.length > 0) {
const descriptionBudget = maxTotalChars - candidateCommands.reduce((total, command) => total + countTelegramCommandText(command.command), 0);
if (descriptionBudget < candidateCommands.length * TELEGRAM_MIN_COMMAND_DESCRIPTION_LENGTH) {
candidateCommands = candidateCommands.slice(0, -1);
continue;
}
const descriptionCap = Math.max(TELEGRAM_MIN_COMMAND_DESCRIPTION_LENGTH, Math.floor(descriptionBudget / candidateCommands.length));
let descriptionTrimmed = false;
const fittedCommands = candidateCommands.map((command) => {
const description = truncateTelegramCommandText(command.description, Math.min(descriptionCap, TELEGRAM_MAX_COMMAND_DESCRIPTION_LENGTH));
if (description !== command.description) {
descriptionTrimmed = true;
return Object.assign({}, command, { description });
}
return command;
});
return {
commands: fittedCommands,
descriptionTrimmed,
textBudgetDropCount: commands.length - fittedCommands.length
};
}
return {
commands: [],
descriptionTrimmed: false,
textBudgetDropCount: commands.length
};
}
function readErrorTextField(value, key) {
if (!value || typeof value !== "object" || !(key in value)) return;
return readStringValue(value[key]);
}
function isBotCommandsTooMuchError(err) {
if (!err) return false;
const pattern = /\bBOT_COMMANDS_TOO_MUCH\b/i;
if (typeof err === "string") return pattern.test(err);
if (err instanceof Error) {
if (pattern.test(err.message)) return true;
}
const description = readErrorTextField(err, "description");
if (description && pattern.test(description)) return true;
const message = readErrorTextField(err, "message");
if (message && pattern.test(message)) return true;
return false;
}
function formatTelegramCommandRetrySuccessLog(params) {
const omittedCount = Math.max(0, params.initialCount - params.acceptedCount);
return `Telegram accepted ${params.acceptedCount} commands after BOT_COMMANDS_TOO_MUCH (started with ${params.initialCount}; omitted ${omittedCount}). Reduce plugin/skill/custom commands to expose more menu entries.`;
}
function buildPluginTelegramMenuCommands(params) {
const { specs, existingCommands } = params;
const commands = [];
const issues = [];
const pluginCommandNames = /* @__PURE__ */ new Set();
for (const spec of specs) {
const rawName = typeof spec.name === "string" ? spec.name : "";
const normalized = normalizeTelegramCommandName(rawName);
if (!normalized || !TELEGRAM_COMMAND_NAME_PATTERN.test(normalized)) {
const invalidName = rawName.trim() ? rawName : "<unknown>";
issues.push(`Plugin command "/${invalidName}" is invalid for Telegram (use a-z, 0-9, underscore; max 32 chars).`);
continue;
}
const description = normalizeOptionalString(spec.description) ?? "";
if (!description) {
issues.push(`Plugin command "/${normalized}" is missing a description.`);
continue;
}
if (existingCommands.has(normalized)) {
if (pluginCommandNames.has(normalized)) issues.push(`Plugin command "/${normalized}" is duplicated.`);
else issues.push(`Plugin command "/${normalized}" conflicts with an existing Telegram command.`);
continue;
}
pluginCommandNames.add(normalized);
existingCommands.add(normalized);
const menuCommand = {
command: normalized,
description
};
if (spec.descriptionLocalizations) menuCommand.descriptionLocalizations = spec.descriptionLocalizations;
commands.push(menuCommand);
}
return {
commands,
issues
};
}
function buildCappedTelegramMenuCommands(params) {
const maxCommands = params.maxCommands ?? TELEGRAM_MAX_COMMANDS;
const maxTotalChars = params.maxTotalChars ?? 5700;
const cacheKey = buildTelegramMenuResultCacheKey({
allCommands: params.allCommands,
maxCommands,
maxTotalChars
});
const cached = cappedTelegramMenuCache.get(cacheKey);
if (cached) return cached;
const result = buildUncachedCappedTelegramMenuCommands({
allCommands: params.allCommands,
maxCommands,
maxTotalChars
});
rememberCappedTelegramMenuResult(cacheKey, result);
return result;
}
function buildUncachedCappedTelegramMenuCommands(params) {
const { allCommands } = params;
const { maxCommands, maxTotalChars } = params;
const totalCommands = allCommands.length;
const overflowCount = Math.max(0, totalCommands - maxCommands);
const canonicalCommands = allCommands.filter((command) => !command.isAlias);
const aliasCommands = allCommands.filter((command) => command.isAlias);
const aliasBudget = Math.max(0, maxCommands - canonicalCommands.length);
const { commands: fittedCommands, descriptionTrimmed, textBudgetDropCount } = fitTelegramCommandsWithinTextBudget((overflowCount === 0 ? allCommands : [...canonicalCommands, ...aliasCommands.slice(0, aliasBudget)]).slice(0, maxCommands), maxTotalChars);
return {
commandsToRegister: fittedCommands.map(({ isAlias: _isAlias, ...command }) => command),
totalCommands,
maxCommands,
overflowCount,
maxTotalChars,
descriptionTrimmed,
textBudgetDropCount
};
}
function buildTelegramMenuResultCacheKey(params) {
const digest = createHash("sha256");
updateTelegramCommandDigestField(digest, String(params.maxCommands));
updateTelegramCommandDigestField(digest, String(params.maxTotalChars));
for (const command of params.allCommands) {
updateTelegramCommandDigestField(digest, command.command);
updateTelegramCommandDigestField(digest, command.description);
updateTelegramCommandDigestField(digest, command.isAlias ? "1" : "0");
updateTelegramCommandLocalizationDigest(digest, command.descriptionLocalizations);
}
return digest.digest("hex").slice(0, 16);
}
function updateTelegramCommandDigestField(digest, value) {
digest.update(String(value.length));
digest.update(":");
digest.update(value);
}
function updateTelegramCommandLocalizationDigest(digest, localizations) {
const entries = Object.entries(localizations ?? {}).toSorted(([a], [b]) => a.localeCompare(b));
updateTelegramCommandDigestField(digest, String(entries.length));
for (const [locale, description] of entries) {
updateTelegramCommandDigestField(digest, locale);
updateTelegramCommandDigestField(digest, description);
}
}
function rememberCappedTelegramMenuResult(key, result) {
cappedTelegramMenuCache.set(key, result);
if (cappedTelegramMenuCache.size <= TELEGRAM_MENU_RESULT_CACHE_MAX) return;
const oldestKey = cappedTelegramMenuCache.keys().next().value;
if (oldestKey) cappedTelegramMenuCache.delete(oldestKey);
}
function hashCommandList(commands) {
const sorted = [...commands].toSorted((a, b) => a.command.localeCompare(b.command));
return createHash("sha256").update(JSON.stringify(sorted)).digest("hex").slice(0, 16);
}
const syncedCommandHashes = /* @__PURE__ */ new Map();
function getCommandHashKey(accountId, botIdentity) {
return `${accountId ?? "default"}:${botIdentity ?? ""}`;
}
function readCachedCommandHash(accountId, botIdentity) {
const key = getCommandHashKey(accountId, botIdentity);
return syncedCommandHashes.get(key) ?? null;
}
function writeCachedCommandHash(accountId, botIdentity, hash) {
const key = getCommandHashKey(accountId, botIdentity);
syncedCommandHashes.set(key, hash);
}
function normalizeTelegramLanguageCode(languageCode) {
const normalized = languageCode.trim().toLowerCase();
return /^[a-z]{2}$/.test(normalized) ? normalized : null;
}
function readLocalizedDescription(command, languageCode) {
for (const [rawLanguageCode, rawDescription] of Object.entries(command.descriptionLocalizations ?? {})) {
if (normalizeTelegramLanguageCode(rawLanguageCode) !== languageCode) continue;
const description = normalizeOptionalString(rawDescription);
if (description) return description;
}
}
function toTelegramBotCommands(commands) {
return commands.map((command) => ({
command: command.command,
description: command.description
}));
}
function buildLocalizedCommandVariants(commands) {
const locales = /* @__PURE__ */ new Set();
const unsupportedLanguageCodes = /* @__PURE__ */ new Set();
for (const cmd of commands) if (cmd.descriptionLocalizations) for (const lang of Object.keys(cmd.descriptionLocalizations)) {
const normalized = normalizeTelegramLanguageCode(lang);
if (normalized) locales.add(normalized);
else unsupportedLanguageCodes.add(lang);
}
return {
variants: [...locales].toSorted().map((languageCode) => {
return {
languageCode,
commands: fitTelegramCommandsWithinTextBudget(commands.map((cmd) => ({
command: cmd.command,
description: readLocalizedDescription(cmd, languageCode) ?? cmd.description
})), TELEGRAM_TOTAL_COMMAND_TEXT_BUDGET).commands
};
}),
unsupportedLanguageCodes: [...unsupportedLanguageCodes].toSorted()
};
}
function formatTelegramCommandScopeOperation(operation, scope, languageCode) {
const base = scope.label === "default" ? operation : `${operation}(${scope.label})`;
return languageCode ? `${base}(${languageCode})` : base;
}
async function deleteTelegramMenuCommandsForScopes(params) {
const { bot, runtime } = params;
if (typeof bot.api.deleteMyCommands !== "function") return true;
let allDeleted = true;
for (const scope of TELEGRAM_COMMAND_MENU_SCOPES) {
const deleted = await withTelegramApiErrorLogging({
operation: formatTelegramCommandScopeOperation("deleteMyCommands", scope),
runtime,
fn: () => scope.options ? bot.api.deleteMyCommands(scope.options) : bot.api.deleteMyCommands()
}).then(() => true).catch(() => false);
allDeleted &&= deleted;
}
return allDeleted;
}
async function setTelegramMenuCommandsForScopes(params) {
const { bot, runtime, commands, languageCode, shouldLog } = params;
for (const scope of TELEGRAM_COMMAND_MENU_SCOPES) await withTelegramApiErrorLogging({
operation: formatTelegramCommandScopeOperation("setMyCommands", scope, languageCode),
runtime,
shouldLog,
fn: () => {
const botCommands = toTelegramBotCommands(commands);
const opts = {
...scope.options,
...languageCode ? { language_code: languageCode } : void 0
};
return Object.keys(opts).length > 0 ? bot.api.setMyCommands(botCommands, opts) : bot.api.setMyCommands(botCommands);
}
});
}
function syncTelegramMenuCommands(params) {
const { bot, runtime, commandsToRegister, accountId, botIdentity } = params;
const sync = async () => {
const currentHash = hashCommandList(commandsToRegister);
if (readCachedCommandHash(accountId, botIdentity) === currentHash) {
logVerbose("telegram: command menu unchanged; skipping sync");
return;
}
const deleteSucceeded = await deleteTelegramMenuCommandsForScopes({
bot,
runtime
});
if (commandsToRegister.length === 0) {
if (!deleteSucceeded) {
runtime.log?.("telegram: deleteMyCommands failed; skipping empty-menu hash cache write");
return;
}
if (typeof bot.api.deleteMyCommands !== "function") await setTelegramMenuCommandsForScopes({
bot,
runtime,
commands: []
});
writeCachedCommandHash(accountId, botIdentity, currentHash);
return;
}
let retryCommands = commandsToRegister;
let acceptedCommands = null;
const initialCommandCount = commandsToRegister.length;
while (retryCommands.length > 0) try {
await setTelegramMenuCommandsForScopes({
bot,
runtime,
commands: retryCommands,
shouldLog: (err) => !isBotCommandsTooMuchError(err)
});
if (retryCommands.length < initialCommandCount) runtime.log?.(formatTelegramCommandRetrySuccessLog({
initialCount: initialCommandCount,
acceptedCount: retryCommands.length
}));
acceptedCommands = retryCommands;
break;
} catch (err) {
if (!isBotCommandsTooMuchError(err)) throw err;
const nextCount = Math.floor(retryCommands.length * TELEGRAM_COMMAND_RETRY_RATIO);
const reducedCount = nextCount < retryCommands.length ? nextCount : retryCommands.length - 1;
if (reducedCount <= 0) {
runtime.error?.("Telegram rejected native command registration (BOT_COMMANDS_TOO_MUCH); leaving menu empty. Reduce commands or disable channels.telegram.commands.native.");
return;
}
runtime.log?.(`Telegram rejected ${retryCommands.length} commands (BOT_COMMANDS_TOO_MUCH); retrying with ${reducedCount}.`);
retryCommands = retryCommands.slice(0, reducedCount);
}
if (!acceptedCommands) return;
const { variants, unsupportedLanguageCodes } = buildLocalizedCommandVariants(acceptedCommands);
if (unsupportedLanguageCodes.length > 0) runtime.log?.(`Telegram command menu ignored unsupported description localization codes: ${unsupportedLanguageCodes.join(", ")}.`);
for (const variant of variants) await setTelegramMenuCommandsForScopes({
bot,
runtime,
commands: variant.commands,
languageCode: variant.languageCode
});
writeCachedCommandHash(accountId, botIdentity, currentHash);
};
sync().catch((err) => {
runtime.error?.(`Telegram command sync failed: ${String(err)}`);
});
}
//#endregion
//#region extensions/telegram/src/draft-stream.ts
const TELEGRAM_STREAM_MAX_CHARS = 4096;
const DEFAULT_THROTTLE_MS = 1e3;
function renderTelegramDraftPreview(text, renderText) {
const trimmed = text.trimEnd();
return renderText?.(trimmed) ?? { text: trimmed };
}
function findTelegramDraftChunkLength(text, maxChars, renderText) {
let best = 0;
let low = 1;
let high = text.length;
while (low <= high) {
const mid = Math.floor((low + high) / 2);
const renderedText = renderTelegramDraftPreview(text.slice(0, mid), renderText).text.trimEnd();
if (renderedText && renderedText.length <= maxChars) {
best = mid;
low = mid + 1;
} else high = mid - 1;
}
return best;
}
function createTelegramDraftStream(params) {
const maxChars = Math.min(params.maxChars ?? TELEGRAM_STREAM_MAX_CHARS, TELEGRAM_STREAM_MAX_CHARS);
const throttleMs = Math.max(250, params.throttleMs ?? DEFAULT_THROTTLE_MS);
const minInitialChars = params.minInitialChars;
const chatId = params.chatId;
const threadParams = buildTelegramThreadParams(params.thread);
const replyToMessageId = normalizeTelegramReplyToMessageId(params.replyToMessageId);
const replyParams = replyToMessageId != null ? {
...threadParams,
reply_to_message_id: replyToMessageId,
allow_sending_without_reply: true
} : threadParams;
const streamState = {
stopped: false,
final: false
};
let messageSendAttempted = false;
let streamMessageId;
let streamVisibleSinceMs;
let lastSentText = "";
let lastDeliveredText = "";
let lastRequestedText = "";
let lastSentParseMode;
let previewRevision = 0;
let generation = 0;
let deliveredTextOffset = 0;
const sendRenderedMessage = async (sendArgs) => {
const sendParams = sendArgs.renderedParseMode ? {
...replyParams,
parse_mode: sendArgs.renderedParseMode
} : replyParams;
return await params.api.sendMessage(chatId, sendArgs.renderedText, sendParams);
};
const sendMessageTransportPreview = async ({ renderedText, renderedParseMode, sendGeneration }) => {
if (typeof streamMessageId === "number") {
streamVisibleSinceMs ??= Date.now();
if (renderedParseMode) await params.api.editMessageText(chatId, streamMessageId, renderedText, { parse_mode: renderedParseMode });
else await params.api.editMessageText(chatId, streamMessageId, renderedText);
return true;
}
messageSendAttempted = true;
let sent;
try {
sent = await sendRenderedMessage({
renderedText,
renderedParseMode
});
} catch (err) {
if (isSafeToRetrySendError(err) || isTelegramClientRejection(err)) messageSendAttempted = false;
throw err;
}
const sentMessageId = sent?.message_id;
if (typeof sentMessageId !== "number" || !Number.isFinite(sentMessageId)) {
streamState.stopped = true;
params.warn?.("telegram stream preview stopped (missing message id from sendMessage)");
return false;
}
const normalizedMessageId = Math.trunc(sentMessageId);
const visibleSinceMs = Date.now();
if (sendGeneration !== generation) {
params.onSupersededPreview?.({
messageId: normalizedMessageId,
textSnapshot: renderedText,
parseMode: renderedParseMode,
visibleSinceMs,
retain: true
});
return true;
}
streamMessageId = normalizedMessageId;
streamVisibleSinceMs = visibleSinceMs;
return true;
};
const stopOversizedPreview = (renderedText) => {
streamState.stopped = true;
params.warn?.(`telegram stream preview stopped (text length ${renderedText.length} > ${maxChars})`);
return false;
};
const sendOrEditStreamMessage = async (text) => {
if (streamState.stopped && !streamState.final) return false;
const trimmed = text.trimEnd();
if (!trimmed) return false;
const currentText = trimmed.slice(deliveredTextOffset).trimStart();
if (!currentText) return false;
const rendered = renderTelegramDraftPreview(currentText, params.renderText);
const renderedText = rendered.text.trimEnd();
const renderedParseMode = rendered.parseMode;
if (!renderedText) return false;
if (renderedText.length > maxChars) {
const chunkLength = findTelegramDraftChunkLength(currentText, maxChars, params.renderText);
if (!streamState.final) {
if (chunkLength > 0) return await sendOrEditStreamMessage(trimmed.slice(0, deliveredTextOffset) + currentText.slice(0, chunkLength));
return stopOversizedPreview(renderedText);
}
if (lastDeliveredText.length > deliveredTextOffset) {
const supersededMessageId = streamMessageId;
const supersededTextSnapshot = lastSentText;
const supersededParseMode = lastSentParseMode;
const supersededVisibleSinceMs = streamVisibleSinceMs;
deliveredTextOffset = lastDeliveredText.length;
resetStreamToNewMessage({
keepFinal: true,
keepPending: true,
resetOffset: false
});
if (typeof supersededMessageId === "number") params.onSupersededPreview?.({
messageId: supersededMessageId,
textSnapshot: supersededTextSnapshot,
parseMode: supersededParseMode,
visibleSinceMs: supersededVisibleSinceMs,
retain: true
});
return await sendOrEditStreamMessage(trimmed);
}
if (chunkLength > 0) {
if (!await sendOrEditStreamMessage(trimmed.slice(0, deliveredTextOffset) + currentText.slice(0, chunkLength))) return false;
return await sendOrEditStreamMessage(trimmed);
}
return stopOversizedPreview(renderedText);
}
if (renderedText === lastSentText && renderedParseMode === lastSentParseMode) return true;
const sendGeneration = generation;
if (typeof streamMessageId !== "number" && minInitialChars != null && !streamState.final) {
if (renderedText.length < minInitialChars) return false;
}
lastSentText = renderedText;
lastSentParseMode = renderedParseMode;
try {
const sent = await sendMessageTransportPreview({
renderedText,
renderedParseMode,
sendGeneration
});
if (sent) {
previewRevision += 1;
lastDeliveredText = trimmed;
}
return sent;
} catch (err) {
streamState.stopped = true;
params.warn?.(`telegram stream preview failed: ${formatErrorMessage(err)}`);
return false;
}
};
const { loop, update: updateDraft, stopForClear } = createFinalizableDraftStreamControlsForState({
throttleMs,
state: streamState,
sendOrEditStreamMessage
});
const update = (text) => {
if (streamState.stopped || streamState.final) return;
lastRequestedText = text;
updateDraft(text);
};
const stop = async () => {
streamState.final = true;
await loop.flush();
if (streamState.stopped) return;
const finalText = lastRequestedText.trimEnd();
if (finalText && finalText !== lastDeliveredText.trimEnd()) await sendOrEditStreamMessage(finalText);
streamState.final = true;
};
const resetStreamToNewMessage = (options) => {
streamState.stopped = false;
streamState.final = options?.keepFinal === true;
generation += 1;
messageSendAttempted = false;
streamMessageId = void 0;
streamVisibleSinceMs = void 0;
lastSentText = "";
lastSentParseMode = void 0;
if (options?.resetOffset !== false) {
deliveredTextOffset = 0;
lastRequestedText = "";
}
if (!options?.keepPending) loop.resetPending();
loop.resetThrottleWindow();
};
const clear = async () => {
const messageId = await takeMessageIdAfterStop({
stopForClear,
readMessageId: () => streamMessageId,
clearMessageId: () => {
streamMessageId = void 0;
}
});
if (typeof messageId === "number" && Number.isFinite(messageId)) try {
await params.api.deleteMessage(chatId, messageId);
params.log?.(`telegram stream preview deleted (chat=${chatId}, message=${messageId})`);
} catch (err) {
params.warn?.(`telegram stream preview cleanup failed: ${formatErrorMessage(err)}`);
}
};
const discard = async () => {
await stopForClear();
};
const forceNewMessage = () => {
resetStreamToNewMessage();
};
const materialize = async () => {
await stop();
return streamMessageId;
};
params.log?.(`telegram stream preview ready (maxChars=${maxChars}, throttleMs=${throttleMs})`);
return {
update,
flush: loop.flush,
messageId: () => streamMessageId,
visibleSinceMs: () => streamVisibleSinceMs,
previewRevision: () => previewRevision,
lastDeliveredText: () => lastDeliveredText,
clear,
stop,
discard,
materialize,
forceNewMessage,
sendMayHaveLanded: () => messageSendAttempted && typeof streamMessageId !== "number"
};
}
//#endregion
//#region extensions/telegram/src/exec-approval-resolver.ts
async function resolveTelegramExecApproval(params) {
await resolveApprovalOverGateway({
cfg: params.cfg,
approvalId: params.approvalId,
decision: params.decision,
senderId: params.senderId,
gatewayUrl: params.gatewayUrl,
allowPluginFallback: params.allowPluginFallback,
clientDisplayName: `Telegram approval (${params.senderId?.trim() || "unknown"})`
});
}
//#endregion
//#region extensions/telegram/src/native-tool-progress-draft.ts
const TELEGRAM_NATIVE_DRAFT_MAX_CHARS = 4096;
const TELEGRAM_DRAFT_ID_STATE_KEY = Symbol.for("openclaw.telegramNativeDraftIdState");
function resolveSendMessageDraftApi(api) {
const sendMessageDraft = api.sendMessageDraft;
if (typeof sendMessageDraft !== "function") return;
return sendMessageDraft.bind(api);
}
function allocateTelegramDraftId() {
const globalStore = globalThis;
const state = globalStore[TELEGRAM_DRAFT_ID_STATE_KEY] ?? {};
const nextDraftId = Math.trunc(state.nextDraftId ?? 0) + 1;
state.nextDraftId = nextDraftId;
globalStore[TELEGRAM_DRAFT_ID_STATE_KEY] = state;
return nextDraftId;
}
function normalizeDraftText(text) {
const trimmed = text.trimEnd();
return trimmed.length > TELEGRAM_NATIVE_DRAFT_MAX_CHARS ? trimmed.slice(0, TELEGRAM_NATIVE_DRAFT_MAX_CHARS) : trimmed;
}
function createNativeTelegramToolProgressDraft(params) {
const sendMessageDraft = resolveSendMessageDraftApi(params.api);
if (!sendMessageDraft) return;
const draftId = allocateTelegramDraftId();
const threadParams = buildTelegramThreadParams(params.thread) ?? {};
let stopped = false;
let lastSentText;
return {
update: async (text) => {
if (stopped) return false;
const normalizedText = normalizeDraftText(text);
if (!normalizedText) return false;
if (normalizedText === lastSentText) return true;
try {
await sendMessageDraft(params.chatId, draftId, normalizedText, Object.keys(threadParams).length > 0 ? threadParams : void 0);
lastSentText = normalizedText;
return true;
} catch (err) {
stopped = true;
params.log?.(`telegram native tool-progress draft disabled: ${formatErrorMessage(err)}`);
return false;
}
},
stop: () => {
stopped = true;
}
};
}
//#endregion
//#region extensions/telegram/src/bot-deps.ts
const defaultTelegramBotDeps = {
get getRuntimeConfig() {
return getRuntimeConfig;
},
get resolveStorePath() {
return resolveStorePath;
},
get readChannelAllowFromStore() {
return readChannelAllowFromStore;
},
get loadSessionStore() {
return loadSessionStore;
},
get readSessionUpdatedAt() {
return readSessionUpdatedAt;
},
get recordInboundSession() {
return recordInboundSession;
},
get recordChannelActivity() {
return recordChannelActivity;
},
get resolveInboundLastRouteSessionKey() {
return resolveInboundLastRouteSessionKey;
},
get resolvePinnedMainDmOwnerFromAllowlist() {
return resolvePinnedMainDmOwnerFromAllowlist;
},
get buildChannelInboundEventContext() {
return buildChannelInboundEventContext;
},
get upsertChannelPairingRequest() {
return upsertChannelPairingRequest;
},
get enqueueSystemEvent() {
return enqueueSystemEvent;
},
get dispatchReplyWithBufferedBlockDispatcher() {
return dispatchReplyWithBufferedBlockDispatcher;
},
get loadWebMedia() {
return loadWebMedia;
},
get buildModelsProviderData() {
return buildModelsProviderData;
},
get listSkillCommandsForAgents() {
return listSkillCommandsForAgents;
},
get syncTelegramMenuCommands() {
return syncTelegramMenuCommands;
},
get wasSentByBot() {
return wasSentByBot;
},
get resolveExecApproval() {
return resolveTelegramExecApproval;
},
get createTelegramDraftStream() {
return createTelegramDraftStream;
},
get createNativeTelegramToolProgressDraft() {
return createNativeTelegramToolProgressDraft;
},
get deliverReplies() {
return deliverReplies;
},
get deliverInboundReplyWithMessageSendContext() {
return deliverInboundReplyWithMessageSendContext;
},
get emitInternalMessageSentHook() {
return emitInternalMessageSentHook;
},
get editMessageTelegram() {
return editMessageTelegram;
},
get recordOutboundMessageForPromptContext() {
return recordOutboundMessageForPromptContext;
},
get createChannelMessageReplyPipeline() {
return createChannelReplyPipeline;
}
};
//#endregion
export { buildCappedTelegramMenuCommands as a, createTelegramDraftStream as i, createNativeTelegramToolProgressDraft as n, buildPluginTelegramMenuCommands as o, resolveTelegramExecApproval as r, syncTelegramMenuCommands as s, defaultTelegramBotDeps as t };