feat(telegram): add configurable silent error replies (#19776)
Port and complete #19776 on top of the current Telegram extension layout. Adds a default-off `channels.telegram.silentErrorReplies` setting. When enabled, Telegram bot replies marked as errors are delivered silently across the regular bot reply flow, native/slash command replies, and fallback sends. Thanks @auspic7 Co-authored-by: Myeongwon Choi <36367286+auspic7@users.noreply.github.com> Co-authored-by: ImLukeF <92253590+ImLukeF@users.noreply.github.com>
This commit is contained in:
parent
fdfa98cda8
commit
6a8f5bc12f
@ -24,6 +24,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Android/nodes: add `callLog.search` plus shared Call Log permission wiring so Android nodes can search recent call history through the gateway. (#44073) Thanks @lxk7280.
|
||||
- Plugins/MiniMax: merge the bundled MiniMax API and MiniMax OAuth plugin surfaces into a single default-on `minimax` plugin, while keeping legacy `minimax-portal-auth` config ids aliased for compatibility.
|
||||
- Telegram/actions: add `topic-edit` for forum-topic renames and icon updates while sharing the same Telegram topic-edit transport used by the plugin runtime. (#47798) Thanks @obviyus.
|
||||
- Telegram/error replies: add a default-off `channels.telegram.silentErrorReplies` setting so bot error replies can be delivered silently across regular replies, native commands, and fallback sends. (#19776) Thanks @ImLukeF.
|
||||
- Refactor/channels: remove the legacy channel shim directories and point channel-specific imports directly at the extension-owned implementations. (#45967) thanks @scoootscooob.
|
||||
- Docs/Zalo: clarify the Marketplace-bot support matrix and config guidance so the Zalo channel docs match current Bot Creator behavior more closely. (#47552) Thanks @No898.
|
||||
- secrets: harden read-only SecretRef command paths and diagnostics. (#47794) Thanks @joshavant.
|
||||
|
||||
@ -298,6 +298,43 @@ describe("dispatchTelegramMessage draft streaming", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("sends error replies silently when silentErrorReplies is enabled", async () => {
|
||||
dispatchReplyWithBufferedBlockDispatcher.mockImplementation(async ({ dispatcherOptions }) => {
|
||||
await dispatcherOptions.deliver({ text: "oops", isError: true }, { kind: "final" });
|
||||
return { queuedFinal: true };
|
||||
});
|
||||
deliverReplies.mockResolvedValue({ delivered: true });
|
||||
|
||||
await dispatchWithContext({
|
||||
context: createContext(),
|
||||
telegramCfg: { silentErrorReplies: true },
|
||||
});
|
||||
|
||||
expect(deliverReplies).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
silent: true,
|
||||
replies: [expect.objectContaining({ isError: true })],
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps error replies notifying by default", async () => {
|
||||
dispatchReplyWithBufferedBlockDispatcher.mockImplementation(async ({ dispatcherOptions }) => {
|
||||
await dispatcherOptions.deliver({ text: "oops", isError: true }, { kind: "final" });
|
||||
return { queuedFinal: true };
|
||||
});
|
||||
deliverReplies.mockResolvedValue({ delivered: true });
|
||||
|
||||
await dispatchWithContext({ context: createContext() });
|
||||
|
||||
expect(deliverReplies).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
silent: false,
|
||||
replies: [expect.objectContaining({ isError: true })],
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps block streaming enabled when session reasoning level is on", async () => {
|
||||
loadSessionStore.mockReturnValue({
|
||||
s1: { reasoningLevel: "on" },
|
||||
|
||||
@ -465,6 +465,7 @@ export const dispatchTelegramMessage = async ({
|
||||
linkPreview: telegramCfg.linkPreview,
|
||||
replyQuoteText,
|
||||
};
|
||||
const silentErrorReplies = telegramCfg.silentErrorReplies === true;
|
||||
const applyTextToPayload = (payload: ReplyPayload, text: string): ReplyPayload => {
|
||||
if (payload.text === text) {
|
||||
return payload;
|
||||
@ -476,6 +477,7 @@ export const dispatchTelegramMessage = async ({
|
||||
...deliveryBaseOptions,
|
||||
replies: [payload],
|
||||
onVoiceRecording: sendRecordVoice,
|
||||
silent: silentErrorReplies && payload.isError === true,
|
||||
});
|
||||
if (result.delivered) {
|
||||
deliveryState.markDelivered();
|
||||
@ -809,6 +811,7 @@ export const dispatchTelegramMessage = async ({
|
||||
const result = await deliverReplies({
|
||||
replies: [{ text: fallbackText }],
|
||||
...deliveryBaseOptions,
|
||||
silent: silentErrorReplies && dispatchError != null,
|
||||
});
|
||||
sentFallback = result.delivered;
|
||||
}
|
||||
|
||||
@ -187,18 +187,20 @@ function registerAndResolveStatusHandler(params: {
|
||||
cfg: OpenClawConfig;
|
||||
allowFrom?: string[];
|
||||
groupAllowFrom?: string[];
|
||||
telegramCfg?: RegisterTelegramNativeCommandsParams["telegramCfg"];
|
||||
resolveTelegramGroupConfig?: RegisterTelegramHandlerParams["resolveTelegramGroupConfig"];
|
||||
}): {
|
||||
handler: TelegramCommandHandler;
|
||||
sendMessage: ReturnType<typeof vi.fn>;
|
||||
} {
|
||||
const { cfg, allowFrom, groupAllowFrom, resolveTelegramGroupConfig } = params;
|
||||
const { cfg, allowFrom, groupAllowFrom, telegramCfg, resolveTelegramGroupConfig } = params;
|
||||
return registerAndResolveCommandHandlerBase({
|
||||
commandName: "status",
|
||||
cfg,
|
||||
allowFrom: allowFrom ?? ["*"],
|
||||
groupAllowFrom: groupAllowFrom ?? [],
|
||||
useAccessGroups: true,
|
||||
telegramCfg,
|
||||
resolveTelegramGroupConfig,
|
||||
});
|
||||
}
|
||||
@ -209,6 +211,7 @@ function registerAndResolveCommandHandlerBase(params: {
|
||||
allowFrom: string[];
|
||||
groupAllowFrom: string[];
|
||||
useAccessGroups: boolean;
|
||||
telegramCfg?: RegisterTelegramNativeCommandsParams["telegramCfg"];
|
||||
resolveTelegramGroupConfig?: RegisterTelegramHandlerParams["resolveTelegramGroupConfig"];
|
||||
}): {
|
||||
handler: TelegramCommandHandler;
|
||||
@ -220,6 +223,7 @@ function registerAndResolveCommandHandlerBase(params: {
|
||||
allowFrom,
|
||||
groupAllowFrom,
|
||||
useAccessGroups,
|
||||
telegramCfg,
|
||||
resolveTelegramGroupConfig,
|
||||
} = params;
|
||||
const commandHandlers = new Map<string, TelegramCommandHandler>();
|
||||
@ -239,6 +243,7 @@ function registerAndResolveCommandHandlerBase(params: {
|
||||
allowFrom,
|
||||
groupAllowFrom,
|
||||
useAccessGroups,
|
||||
telegramCfg,
|
||||
resolveTelegramGroupConfig,
|
||||
}),
|
||||
});
|
||||
@ -254,6 +259,7 @@ function registerAndResolveCommandHandler(params: {
|
||||
allowFrom?: string[];
|
||||
groupAllowFrom?: string[];
|
||||
useAccessGroups?: boolean;
|
||||
telegramCfg?: RegisterTelegramNativeCommandsParams["telegramCfg"];
|
||||
resolveTelegramGroupConfig?: RegisterTelegramHandlerParams["resolveTelegramGroupConfig"];
|
||||
}): {
|
||||
handler: TelegramCommandHandler;
|
||||
@ -265,6 +271,7 @@ function registerAndResolveCommandHandler(params: {
|
||||
allowFrom,
|
||||
groupAllowFrom,
|
||||
useAccessGroups,
|
||||
telegramCfg,
|
||||
resolveTelegramGroupConfig,
|
||||
} = params;
|
||||
return registerAndResolveCommandHandlerBase({
|
||||
@ -273,6 +280,7 @@ function registerAndResolveCommandHandler(params: {
|
||||
allowFrom: allowFrom ?? [],
|
||||
groupAllowFrom: groupAllowFrom ?? [],
|
||||
useAccessGroups: useAccessGroups ?? true,
|
||||
telegramCfg,
|
||||
resolveTelegramGroupConfig,
|
||||
});
|
||||
}
|
||||
@ -443,6 +451,31 @@ describe("registerTelegramNativeCommands — session metadata", () => {
|
||||
expect(deliveryMocks.deliverReplies).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("sends native command error replies silently when silentErrorReplies is enabled", async () => {
|
||||
replyMocks.dispatchReplyWithBufferedBlockDispatcher.mockImplementationOnce(
|
||||
async ({ dispatcherOptions }: DispatchReplyWithBufferedBlockDispatcherParams) => {
|
||||
await dispatcherOptions.deliver({ text: "oops", isError: true }, { kind: "final" });
|
||||
return dispatchReplyResult;
|
||||
},
|
||||
);
|
||||
|
||||
const { handler } = registerAndResolveStatusHandler({
|
||||
cfg: {},
|
||||
telegramCfg: { silentErrorReplies: true },
|
||||
});
|
||||
await handler(buildStatusCommandContext());
|
||||
|
||||
const deliveredCall = deliveryMocks.deliverReplies.mock.calls[0]?.[0] as
|
||||
| DeliverRepliesParams
|
||||
| undefined;
|
||||
expect(deliveredCall).toEqual(
|
||||
expect.objectContaining({
|
||||
silent: true,
|
||||
replies: [expect.objectContaining({ isError: true })],
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("routes Telegram native commands through configured ACP topic bindings", async () => {
|
||||
const boundSessionKey = "agent:codex:acp:binding:telegram:default:feedface";
|
||||
persistentBindingMocks.resolveConfiguredAcpBindingRecord.mockReturnValue(
|
||||
|
||||
@ -290,4 +290,56 @@ describe("registerTelegramNativeCommands", () => {
|
||||
);
|
||||
expect(sendMessage).not.toHaveBeenCalledWith(123, "Command not found.");
|
||||
});
|
||||
|
||||
it("sends plugin command error replies silently when silentErrorReplies is enabled", async () => {
|
||||
const commandHandlers = new Map<string, (ctx: unknown) => Promise<void>>();
|
||||
|
||||
pluginCommandMocks.getPluginCommandSpecs.mockReturnValue([
|
||||
{
|
||||
name: "plug",
|
||||
description: "Plugin command",
|
||||
},
|
||||
] as never);
|
||||
pluginCommandMocks.matchPluginCommand.mockReturnValue({
|
||||
command: { key: "plug", requireAuth: false },
|
||||
args: undefined,
|
||||
} as never);
|
||||
pluginCommandMocks.executePluginCommand.mockResolvedValue({
|
||||
text: "plugin failed",
|
||||
isError: true,
|
||||
} as never);
|
||||
|
||||
registerTelegramNativeCommands({
|
||||
...buildParams({}),
|
||||
bot: {
|
||||
api: {
|
||||
setMyCommands: vi.fn().mockResolvedValue(undefined),
|
||||
sendMessage: vi.fn().mockResolvedValue(undefined),
|
||||
},
|
||||
command: vi.fn((name: string, cb: (ctx: unknown) => Promise<void>) => {
|
||||
commandHandlers.set(name, cb);
|
||||
}),
|
||||
} as unknown as Parameters<typeof registerTelegramNativeCommands>[0]["bot"],
|
||||
telegramCfg: { silentErrorReplies: true } as TelegramAccountConfig,
|
||||
});
|
||||
|
||||
const handler = commandHandlers.get("plug");
|
||||
expect(handler).toBeTruthy();
|
||||
await handler?.({
|
||||
match: "",
|
||||
message: {
|
||||
message_id: 1,
|
||||
date: Math.floor(Date.now() / 1000),
|
||||
chat: { id: 123, type: "private" },
|
||||
from: { id: 456, username: "alice" },
|
||||
},
|
||||
});
|
||||
|
||||
expect(deliveryMocks.deliverReplies).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
silent: true,
|
||||
replies: [expect.objectContaining({ isError: true })],
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -363,6 +363,7 @@ export const registerTelegramNativeCommands = ({
|
||||
shouldSkipUpdate,
|
||||
opts,
|
||||
}: RegisterTelegramNativeCommandsParams) => {
|
||||
const silentErrorReplies = telegramCfg.silentErrorReplies === true;
|
||||
const boundRoute =
|
||||
nativeEnabled && nativeSkillsEnabled
|
||||
? resolveAgentRoute({ cfg, channel: "telegram", accountId })
|
||||
@ -734,7 +735,6 @@ export const registerTelegramNativeCommands = ({
|
||||
typeof telegramCfg.blockStreaming === "boolean"
|
||||
? !telegramCfg.blockStreaming
|
||||
: undefined;
|
||||
|
||||
const deliveryState = {
|
||||
delivered: false,
|
||||
skippedNonSilent: 0,
|
||||
@ -766,6 +766,7 @@ export const registerTelegramNativeCommands = ({
|
||||
const result = await deliverReplies({
|
||||
replies: [payload],
|
||||
...deliveryBaseOptions,
|
||||
silent: silentErrorReplies && payload.isError === true,
|
||||
});
|
||||
if (result.delivered) {
|
||||
deliveryState.delivered = true;
|
||||
@ -885,6 +886,7 @@ export const registerTelegramNativeCommands = ({
|
||||
await deliverReplies({
|
||||
replies: [result],
|
||||
...deliveryBaseOptions,
|
||||
silent: silentErrorReplies && result.isError === true,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@ -103,6 +103,7 @@ async function deliverTextReply(params: {
|
||||
replyMarkup?: ReturnType<typeof buildInlineKeyboard>;
|
||||
replyQuoteText?: string;
|
||||
linkPreview?: boolean;
|
||||
silent?: boolean;
|
||||
replyToId?: number;
|
||||
replyToMode: ReplyToMode;
|
||||
progress: DeliveryProgress;
|
||||
@ -129,6 +130,7 @@ async function deliverTextReply(params: {
|
||||
textMode: "html",
|
||||
plainText: chunk.text,
|
||||
linkPreview: params.linkPreview,
|
||||
silent: params.silent,
|
||||
replyMarkup,
|
||||
},
|
||||
);
|
||||
@ -149,6 +151,7 @@ async function sendPendingFollowUpText(params: {
|
||||
text: string;
|
||||
replyMarkup?: ReturnType<typeof buildInlineKeyboard>;
|
||||
linkPreview?: boolean;
|
||||
silent?: boolean;
|
||||
replyToId?: number;
|
||||
replyToMode: ReplyToMode;
|
||||
progress: DeliveryProgress;
|
||||
@ -167,6 +170,7 @@ async function sendPendingFollowUpText(params: {
|
||||
textMode: "html",
|
||||
plainText: chunk.text,
|
||||
linkPreview: params.linkPreview,
|
||||
silent: params.silent,
|
||||
replyMarkup,
|
||||
});
|
||||
},
|
||||
@ -196,6 +200,7 @@ async function sendTelegramVoiceFallbackText(opts: {
|
||||
replyToId?: number;
|
||||
thread?: TelegramThreadSpec | null;
|
||||
linkPreview?: boolean;
|
||||
silent?: boolean;
|
||||
replyMarkup?: ReturnType<typeof buildInlineKeyboard>;
|
||||
replyQuoteText?: string;
|
||||
}): Promise<number | undefined> {
|
||||
@ -213,6 +218,7 @@ async function sendTelegramVoiceFallbackText(opts: {
|
||||
textMode: "html",
|
||||
plainText: chunk.text,
|
||||
linkPreview: opts.linkPreview,
|
||||
silent: opts.silent,
|
||||
replyMarkup: !appliedReplyTo ? opts.replyMarkup : undefined,
|
||||
});
|
||||
if (firstDeliveredMessageId == null) {
|
||||
@ -237,6 +243,7 @@ async function deliverMediaReply(params: {
|
||||
chunkText: ChunkTextFn;
|
||||
onVoiceRecording?: () => Promise<void> | void;
|
||||
linkPreview?: boolean;
|
||||
silent?: boolean;
|
||||
replyQuoteText?: string;
|
||||
replyMarkup?: ReturnType<typeof buildInlineKeyboard>;
|
||||
replyToId?: number;
|
||||
@ -282,6 +289,7 @@ async function deliverMediaReply(params: {
|
||||
...buildTelegramSendParams({
|
||||
replyToMessageId,
|
||||
thread: params.thread,
|
||||
silent: params.silent,
|
||||
}),
|
||||
};
|
||||
if (isGif) {
|
||||
@ -375,6 +383,7 @@ async function deliverMediaReply(params: {
|
||||
replyToId: voiceFallbackReplyTo,
|
||||
thread: params.thread,
|
||||
linkPreview: params.linkPreview,
|
||||
silent: params.silent,
|
||||
replyMarkup: params.replyMarkup,
|
||||
replyQuoteText: params.replyQuoteText,
|
||||
});
|
||||
@ -404,6 +413,7 @@ async function deliverMediaReply(params: {
|
||||
replyToId: undefined,
|
||||
thread: params.thread,
|
||||
linkPreview: params.linkPreview,
|
||||
silent: params.silent,
|
||||
replyMarkup: params.replyMarkup,
|
||||
});
|
||||
}
|
||||
@ -451,6 +461,7 @@ async function deliverMediaReply(params: {
|
||||
text: pendingFollowUpText,
|
||||
replyMarkup: params.replyMarkup,
|
||||
linkPreview: params.linkPreview,
|
||||
silent: params.silent,
|
||||
replyToId: params.replyToId,
|
||||
replyToMode: params.replyToMode,
|
||||
progress: params.progress,
|
||||
@ -557,6 +568,8 @@ export async function deliverReplies(params: {
|
||||
onVoiceRecording?: () => Promise<void> | void;
|
||||
/** Controls whether link previews are shown. Default: true (previews enabled). */
|
||||
linkPreview?: boolean;
|
||||
/** When true, messages are sent with disable_notification. */
|
||||
silent?: boolean;
|
||||
/** Optional quote text for Telegram reply_parameters. */
|
||||
replyQuoteText?: string;
|
||||
}): Promise<{ delivered: boolean }> {
|
||||
@ -637,6 +650,7 @@ export async function deliverReplies(params: {
|
||||
replyMarkup,
|
||||
replyQuoteText: params.replyQuoteText,
|
||||
linkPreview: params.linkPreview,
|
||||
silent: params.silent,
|
||||
replyToId,
|
||||
replyToMode: params.replyToMode,
|
||||
progress,
|
||||
@ -654,6 +668,7 @@ export async function deliverReplies(params: {
|
||||
chunkText,
|
||||
onVoiceRecording: params.onVoiceRecording,
|
||||
linkPreview: params.linkPreview,
|
||||
silent: params.silent,
|
||||
replyQuoteText: params.replyQuoteText,
|
||||
replyMarkup,
|
||||
replyToId,
|
||||
|
||||
@ -76,6 +76,7 @@ export async function sendTelegramWithThreadFallback<T>(params: {
|
||||
export function buildTelegramSendParams(opts?: {
|
||||
replyToMessageId?: number;
|
||||
thread?: TelegramThreadSpec | null;
|
||||
silent?: boolean;
|
||||
}): Record<string, unknown> {
|
||||
const threadParams = buildTelegramThreadParams(opts?.thread);
|
||||
const params: Record<string, unknown> = {};
|
||||
@ -85,6 +86,9 @@ export function buildTelegramSendParams(opts?: {
|
||||
if (threadParams) {
|
||||
params.message_thread_id = threadParams.message_thread_id;
|
||||
}
|
||||
if (opts?.silent === true) {
|
||||
params.disable_notification = true;
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
@ -100,12 +104,14 @@ export async function sendTelegramText(
|
||||
textMode?: "markdown" | "html";
|
||||
plainText?: string;
|
||||
linkPreview?: boolean;
|
||||
silent?: boolean;
|
||||
replyMarkup?: ReturnType<typeof buildInlineKeyboard>;
|
||||
},
|
||||
): Promise<number> {
|
||||
const baseParams = buildTelegramSendParams({
|
||||
replyToMessageId: opts?.replyToMessageId,
|
||||
thread: opts?.thread,
|
||||
silent: opts?.silent,
|
||||
});
|
||||
// Add link_preview_options when link preview is disabled.
|
||||
const linkPreviewEnabled = opts?.linkPreview ?? true;
|
||||
|
||||
@ -211,6 +211,30 @@ describe("deliverReplies", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("sets disable_notification when silent is true", async () => {
|
||||
const runtime = createRuntime();
|
||||
const sendMessage = vi.fn().mockResolvedValue({
|
||||
message_id: 5,
|
||||
chat: { id: "123" },
|
||||
});
|
||||
const bot = createBot({ sendMessage });
|
||||
|
||||
await deliverWith({
|
||||
replies: [{ text: "hello" }],
|
||||
runtime,
|
||||
bot,
|
||||
silent: true,
|
||||
});
|
||||
|
||||
expect(sendMessage).toHaveBeenCalledWith(
|
||||
"123",
|
||||
expect.any(String),
|
||||
expect.objectContaining({
|
||||
disable_notification: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("emits internal message:sent when session hook context is available", async () => {
|
||||
const runtime = createRuntime(false);
|
||||
const sendMessage = vi.fn().mockResolvedValue({ message_id: 9, chat: { id: "123" } });
|
||||
@ -645,6 +669,36 @@ describe("deliverReplies", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps disable_notification on voice fallback text when silent is true", async () => {
|
||||
const runtime = createRuntime();
|
||||
const sendVoice = vi.fn().mockRejectedValue(createVoiceMessagesForbiddenError());
|
||||
const sendMessage = vi.fn().mockResolvedValue({
|
||||
message_id: 5,
|
||||
chat: { id: "123" },
|
||||
});
|
||||
const bot = createBot({ sendVoice, sendMessage });
|
||||
|
||||
mockMediaLoad("note.ogg", "audio/ogg", "voice");
|
||||
|
||||
await deliverWith({
|
||||
replies: [
|
||||
{ mediaUrl: "https://example.com/note.ogg", text: "Hello there", audioAsVoice: true },
|
||||
],
|
||||
runtime,
|
||||
bot,
|
||||
silent: true,
|
||||
});
|
||||
|
||||
expect(sendVoice).toHaveBeenCalledTimes(1);
|
||||
expect(sendMessage).toHaveBeenCalledWith(
|
||||
"123",
|
||||
expect.stringContaining("Hello there"),
|
||||
expect.objectContaining({
|
||||
disable_notification: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("voice fallback applies reply-to only on first chunk when replyToMode is first", async () => {
|
||||
const { runtime, sendVoice, sendMessage, bot } = createVoiceFailureHarness({
|
||||
voiceError: createVoiceMessagesForbiddenError(),
|
||||
|
||||
@ -1530,6 +1530,8 @@ export const FIELD_HELP: Record<string, string> = {
|
||||
"Override Node autoSelectFamily for Telegram (true=enable, false=disable).",
|
||||
"channels.telegram.timeoutSeconds":
|
||||
"Max seconds before Telegram API requests are aborted (default: 500 per grammY).",
|
||||
"channels.telegram.silentErrorReplies":
|
||||
"When true, Telegram bot replies marked as errors are sent silently (no notification sound). Default: false.",
|
||||
"channels.telegram.threadBindings.enabled":
|
||||
"Enable Telegram conversation binding features (/focus, /unfocus, /agents, and /session idle|max-age). Overrides session.threadBindings.enabled when set.",
|
||||
"channels.telegram.threadBindings.idleHours":
|
||||
|
||||
@ -737,6 +737,7 @@ export const FIELD_LABELS: Record<string, string> = {
|
||||
"channels.telegram.retry.jitter": "Telegram Retry Jitter",
|
||||
"channels.telegram.network.autoSelectFamily": "Telegram autoSelectFamily",
|
||||
"channels.telegram.timeoutSeconds": "Telegram API Timeout (seconds)",
|
||||
"channels.telegram.silentErrorReplies": "Telegram Silent Error Replies",
|
||||
"channels.telegram.capabilities.inlineButtons": "Telegram Inline Buttons",
|
||||
"channels.telegram.execApprovals": "Telegram Exec Approvals",
|
||||
"channels.telegram.execApprovals.enabled": "Telegram Exec Approvals Enabled",
|
||||
|
||||
@ -188,6 +188,8 @@ export type TelegramAccountConfig = {
|
||||
healthMonitor?: ChannelHealthMonitorConfig;
|
||||
/** Controls whether link previews are shown in outbound messages. Default: true. */
|
||||
linkPreview?: boolean;
|
||||
/** Send Telegram bot error replies silently (no notification sound). Default: false. */
|
||||
silentErrorReplies?: boolean;
|
||||
/**
|
||||
* Per-channel outbound response prefix override.
|
||||
*
|
||||
|
||||
@ -277,6 +277,7 @@ export const TelegramAccountSchemaBase = z
|
||||
heartbeat: ChannelHeartbeatVisibilitySchema,
|
||||
healthMonitor: ChannelHealthMonitorSchema,
|
||||
linkPreview: z.boolean().optional(),
|
||||
silentErrorReplies: z.boolean().optional(),
|
||||
responsePrefix: z.string().optional(),
|
||||
ackReaction: z.string().optional(),
|
||||
})
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user