mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 00:00:23 +00:00
fix: add Telegram native progress placeholder opt-in for plugin commands (#59300)
Merged via squash.
Prepared head SHA: 4f5bc22a89
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { createChannelReplyPipeline } from "openclaw/plugin-sdk/channel-reply-pipeline";
|
||||
import { deliverReplies } from "./bot/delivery.js";
|
||||
import { deliverReplies, emitTelegramMessageSentHooks } from "./bot/delivery.js";
|
||||
|
||||
export { createChannelReplyPipeline, deliverReplies };
|
||||
export { createChannelReplyPipeline, deliverReplies, emitTelegramMessageSentHooks };
|
||||
|
||||
@@ -18,6 +18,7 @@ type CreateCommandBotResult = {
|
||||
bot: RegisterTelegramNativeCommandsParams["bot"];
|
||||
commandHandlers: Map<string, (ctx: unknown) => Promise<void>>;
|
||||
sendMessage: ReturnType<typeof vi.fn>;
|
||||
deleteMessage: ReturnType<typeof vi.fn>;
|
||||
setMyCommands: ReturnType<typeof vi.fn>;
|
||||
};
|
||||
|
||||
@@ -29,10 +30,14 @@ const skillCommandMocks = vi.hoisted(() => ({
|
||||
|
||||
const deliveryMocks = vi.hoisted(() => ({
|
||||
deliverReplies: vi.fn(async () => ({ delivered: true })),
|
||||
editMessageTelegram: vi.fn(async () => ({ ok: true as const, messageId: "999", chatId: "100" })),
|
||||
emitTelegramMessageSentHooks: vi.fn(),
|
||||
}));
|
||||
|
||||
export const listSkillCommandsForAgents = skillCommandMocks.listSkillCommandsForAgents;
|
||||
export const deliverReplies = deliveryMocks.deliverReplies;
|
||||
export const editMessageTelegram = deliveryMocks.editMessageTelegram;
|
||||
export const emitTelegramMessageSentHooks = deliveryMocks.emitTelegramMessageSentHooks;
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/command-auth", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("openclaw/plugin-sdk/command-auth")>();
|
||||
@@ -44,6 +49,7 @@ vi.mock("openclaw/plugin-sdk/command-auth", async (importOriginal) => {
|
||||
|
||||
vi.mock("./bot/delivery.js", () => ({
|
||||
deliverReplies,
|
||||
emitTelegramMessageSentHooks,
|
||||
}));
|
||||
|
||||
vi.mock("./bot/delivery.replies.js", () => ({
|
||||
@@ -64,22 +70,27 @@ export function resetNativeCommandMenuMocks() {
|
||||
listSkillCommandsForAgents.mockReturnValue([]);
|
||||
deliverReplies.mockClear();
|
||||
deliverReplies.mockResolvedValue({ delivered: true });
|
||||
editMessageTelegram.mockClear();
|
||||
editMessageTelegram.mockResolvedValue({ ok: true as const, messageId: "999", chatId: "100" });
|
||||
emitTelegramMessageSentHooks.mockClear();
|
||||
}
|
||||
|
||||
export function createCommandBot(): CreateCommandBotResult {
|
||||
const commandHandlers = new Map<string, (ctx: unknown) => Promise<void>>();
|
||||
const sendMessage = vi.fn().mockResolvedValue(undefined);
|
||||
const sendMessage = vi.fn().mockResolvedValue({ message_id: 999 });
|
||||
const deleteMessage = vi.fn().mockResolvedValue(true);
|
||||
const setMyCommands = vi.fn().mockResolvedValue(undefined);
|
||||
const bot = {
|
||||
api: {
|
||||
setMyCommands,
|
||||
sendMessage,
|
||||
deleteMessage,
|
||||
},
|
||||
command: vi.fn((name: string, cb: (ctx: unknown) => Promise<void>) => {
|
||||
commandHandlers.set(name, cb);
|
||||
}),
|
||||
} as unknown as RegisterTelegramNativeCommandsParams["bot"];
|
||||
return { bot, commandHandlers, sendMessage, setMyCommands };
|
||||
return { bot, commandHandlers, sendMessage, deleteMessage, setMyCommands };
|
||||
}
|
||||
|
||||
export function createNativeCommandTestParams(
|
||||
@@ -122,6 +133,7 @@ export function createNativeCommandTestParams(
|
||||
return bot.api.setMyCommands(commandsToRegister);
|
||||
}) as TelegramBotDeps["syncTelegramMenuCommands"],
|
||||
wasSentByBot: vi.fn(() => false) as TelegramBotDeps["wasSentByBot"],
|
||||
editMessageTelegram,
|
||||
};
|
||||
return createBaseNativeCommandTestParams({
|
||||
cfg,
|
||||
|
||||
@@ -8,16 +8,21 @@ let createCommandBot: typeof import("./bot-native-commands.menu-test-support.js"
|
||||
let createNativeCommandTestParams: typeof import("./bot-native-commands.menu-test-support.js").createNativeCommandTestParams;
|
||||
let createPrivateCommandContext: typeof import("./bot-native-commands.menu-test-support.js").createPrivateCommandContext;
|
||||
let deliverReplies: typeof import("./bot-native-commands.menu-test-support.js").deliverReplies;
|
||||
let editMessageTelegram: typeof import("./bot-native-commands.menu-test-support.js").editMessageTelegram;
|
||||
let resetNativeCommandMenuMocks: typeof import("./bot-native-commands.menu-test-support.js").resetNativeCommandMenuMocks;
|
||||
let waitForRegisteredCommands: typeof import("./bot-native-commands.menu-test-support.js").waitForRegisteredCommands;
|
||||
|
||||
function registerPairPluginCommand(params?: {
|
||||
nativeNames?: { telegram?: string; discord?: string };
|
||||
telegramNativeProgressMessage?: string;
|
||||
}) {
|
||||
expect(
|
||||
registerPluginCommand("demo-plugin", {
|
||||
name: "pair",
|
||||
...(params?.nativeNames ? { nativeNames: params.nativeNames } : {}),
|
||||
...(params?.telegramNativeProgressMessage
|
||||
? { telegramNativeProgressMessage: params.telegramNativeProgressMessage }
|
||||
: {}),
|
||||
description: "Pair device",
|
||||
acceptsArgs: true,
|
||||
requireAuth: false,
|
||||
@@ -30,9 +35,13 @@ async function registerPairMenu(params: {
|
||||
bot: ReturnType<typeof createCommandBot>["bot"];
|
||||
setMyCommands: ReturnType<typeof createCommandBot>["setMyCommands"];
|
||||
nativeNames?: { telegram?: string; discord?: string };
|
||||
telegramNativeProgressMessage?: string;
|
||||
}) {
|
||||
registerPairPluginCommand({
|
||||
...(params.nativeNames ? { nativeNames: params.nativeNames } : {}),
|
||||
...(params.telegramNativeProgressMessage
|
||||
? { telegramNativeProgressMessage: params.telegramNativeProgressMessage }
|
||||
: {}),
|
||||
});
|
||||
|
||||
registerTelegramNativeCommands({
|
||||
@@ -54,6 +63,7 @@ describe("registerTelegramNativeCommands real plugin registry", () => {
|
||||
createNativeCommandTestParams,
|
||||
createPrivateCommandContext,
|
||||
deliverReplies,
|
||||
editMessageTelegram,
|
||||
resetNativeCommandMenuMocks,
|
||||
waitForRegisteredCommands,
|
||||
} = await import("./bot-native-commands.menu-test-support.js"));
|
||||
@@ -89,6 +99,35 @@ describe("registerTelegramNativeCommands real plugin registry", () => {
|
||||
expect(sendMessage).not.toHaveBeenCalledWith(123, "Command not found.");
|
||||
});
|
||||
|
||||
it("uses plugin command metadata to send and edit a Telegram progress placeholder", async () => {
|
||||
const { bot, commandHandlers, setMyCommands, sendMessage } = createCommandBot();
|
||||
|
||||
await registerPairMenu({
|
||||
bot,
|
||||
setMyCommands,
|
||||
telegramNativeProgressMessage:
|
||||
"Running pair now...\n\nI'll edit this message with the final result when it's ready.",
|
||||
});
|
||||
|
||||
const handler = commandHandlers.get("pair");
|
||||
expect(handler).toBeTruthy();
|
||||
|
||||
await handler?.(createPrivateCommandContext({ match: "now" }));
|
||||
|
||||
expect(sendMessage).toHaveBeenCalledWith(
|
||||
100,
|
||||
expect.stringContaining("Running pair now"),
|
||||
undefined,
|
||||
);
|
||||
expect(editMessageTelegram).toHaveBeenCalledWith(
|
||||
100,
|
||||
999,
|
||||
"paired:now",
|
||||
expect.objectContaining({ accountId: "default" }),
|
||||
);
|
||||
expect(deliverReplies).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("round-trips Telegram native aliases through the real plugin registry", async () => {
|
||||
const { bot, commandHandlers, sendMessage, setMyCommands } = createCommandBot();
|
||||
|
||||
|
||||
@@ -13,6 +13,8 @@ import {
|
||||
createNativeCommandTestParams,
|
||||
createPrivateCommandContext,
|
||||
deliverReplies,
|
||||
editMessageTelegram,
|
||||
emitTelegramMessageSentHooks,
|
||||
listSkillCommandsForAgents,
|
||||
resetNativeCommandMenuMocks,
|
||||
waitForRegisteredCommands,
|
||||
@@ -242,6 +244,251 @@ describe("registerTelegramNativeCommands", () => {
|
||||
expect(sendMessage).not.toHaveBeenCalledWith(123, "Command not found.");
|
||||
});
|
||||
|
||||
it("uses plugin command metadata to send and edit a Telegram progress placeholder", async () => {
|
||||
const { bot, commandHandlers, sendMessage, deleteMessage } = createCommandBot();
|
||||
|
||||
pluginCommandMocks.getPluginCommandSpecs.mockReturnValue([
|
||||
{
|
||||
name: "plug",
|
||||
description: "Plugin command",
|
||||
},
|
||||
] as never);
|
||||
pluginCommandMocks.matchPluginCommand.mockReturnValue({
|
||||
command: {
|
||||
key: "plug",
|
||||
requireAuth: false,
|
||||
telegramNativeProgressMessage:
|
||||
"Running this command now...\n\nI'll edit this message with the final result when it's ready.",
|
||||
},
|
||||
args: "now",
|
||||
} as never);
|
||||
pluginCommandMocks.executePluginCommand.mockResolvedValue({
|
||||
text: "Command completed successfully",
|
||||
} as never);
|
||||
|
||||
registerTelegramNativeCommands({
|
||||
...createNativeCommandTestParams({}, { bot }),
|
||||
});
|
||||
|
||||
const handler = commandHandlers.get("plug");
|
||||
expect(handler).toBeTruthy();
|
||||
await handler?.(
|
||||
createPrivateCommandContext({
|
||||
match: "now",
|
||||
}),
|
||||
);
|
||||
|
||||
expect(sendMessage).toHaveBeenCalledWith(
|
||||
100,
|
||||
expect.stringContaining("Running this command now"),
|
||||
undefined,
|
||||
);
|
||||
expect(editMessageTelegram).toHaveBeenCalledWith(
|
||||
100,
|
||||
999,
|
||||
expect.stringContaining("Command completed successfully"),
|
||||
expect.objectContaining({
|
||||
accountId: "default",
|
||||
}),
|
||||
);
|
||||
expect(deleteMessage).not.toHaveBeenCalled();
|
||||
expect(deliverReplies).not.toHaveBeenCalled();
|
||||
expect(emitTelegramMessageSentHooks).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
chatId: "100",
|
||||
content: "Command completed successfully",
|
||||
messageId: 999,
|
||||
success: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("preserves Telegram buttons when editing a metadata-driven progress placeholder", async () => {
|
||||
const { bot, commandHandlers, sendMessage, deleteMessage } = createCommandBot();
|
||||
|
||||
pluginCommandMocks.getPluginCommandSpecs.mockReturnValue([
|
||||
{
|
||||
name: "plug",
|
||||
description: "Plugin command",
|
||||
},
|
||||
] as never);
|
||||
pluginCommandMocks.matchPluginCommand.mockReturnValue({
|
||||
command: {
|
||||
key: "plug",
|
||||
requireAuth: false,
|
||||
telegramNativeProgressMessage: "Working on it...",
|
||||
},
|
||||
args: "now",
|
||||
} as never);
|
||||
pluginCommandMocks.executePluginCommand.mockResolvedValue({
|
||||
text: "Choose an option",
|
||||
channelData: {
|
||||
telegram: {
|
||||
buttons: [[{ text: "Approve", callback_data: "approve" }]],
|
||||
},
|
||||
},
|
||||
} as never);
|
||||
|
||||
registerTelegramNativeCommands({
|
||||
...createNativeCommandTestParams({}, { bot }),
|
||||
});
|
||||
|
||||
const handler = commandHandlers.get("plug");
|
||||
expect(handler).toBeTruthy();
|
||||
await handler?.(createPrivateCommandContext({ match: "now" }));
|
||||
|
||||
expect(sendMessage).toHaveBeenCalledWith(100, "Working on it...", undefined);
|
||||
expect(editMessageTelegram).toHaveBeenCalledWith(
|
||||
100,
|
||||
999,
|
||||
"Choose an option",
|
||||
expect.objectContaining({
|
||||
buttons: [[{ text: "Approve", callback_data: "approve" }]],
|
||||
}),
|
||||
);
|
||||
expect(deleteMessage).not.toHaveBeenCalled();
|
||||
expect(deliverReplies).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("falls back to a normal reply when a metadata-driven progress result is not editable", async () => {
|
||||
const { bot, commandHandlers, sendMessage, deleteMessage } = createCommandBot();
|
||||
|
||||
pluginCommandMocks.getPluginCommandSpecs.mockReturnValue([
|
||||
{
|
||||
name: "plug",
|
||||
description: "Plugin command",
|
||||
},
|
||||
] as never);
|
||||
pluginCommandMocks.matchPluginCommand.mockReturnValue({
|
||||
command: {
|
||||
key: "plug",
|
||||
requireAuth: false,
|
||||
telegramNativeProgressMessage: "Working on it...",
|
||||
},
|
||||
args: "now",
|
||||
} as never);
|
||||
pluginCommandMocks.executePluginCommand.mockResolvedValue({
|
||||
text: "rich output",
|
||||
mediaUrl: "/tmp/render.png",
|
||||
} as never);
|
||||
|
||||
registerTelegramNativeCommands({
|
||||
...createNativeCommandTestParams({}, { bot }),
|
||||
});
|
||||
|
||||
const handler = commandHandlers.get("plug");
|
||||
expect(handler).toBeTruthy();
|
||||
await handler?.(
|
||||
createPrivateCommandContext({
|
||||
match: "now",
|
||||
}),
|
||||
);
|
||||
|
||||
expect(sendMessage).toHaveBeenCalledWith(100, "Working on it...", undefined);
|
||||
expect(editMessageTelegram).not.toHaveBeenCalled();
|
||||
expect(deleteMessage).toHaveBeenCalledWith(100, 999);
|
||||
expect(deliverReplies).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
replies: [expect.objectContaining({ mediaUrl: "/tmp/render.png" })],
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("cleans up the progress placeholder before falling back after an edit failure", async () => {
|
||||
const { bot, commandHandlers, sendMessage, deleteMessage } = createCommandBot();
|
||||
|
||||
pluginCommandMocks.getPluginCommandSpecs.mockReturnValue([
|
||||
{
|
||||
name: "plug",
|
||||
description: "Plugin command",
|
||||
},
|
||||
] as never);
|
||||
pluginCommandMocks.matchPluginCommand.mockReturnValue({
|
||||
command: {
|
||||
key: "plug",
|
||||
requireAuth: false,
|
||||
telegramNativeProgressMessage: "Working on it...",
|
||||
},
|
||||
args: "now",
|
||||
} as never);
|
||||
pluginCommandMocks.executePluginCommand.mockResolvedValue({
|
||||
text: "Command completed successfully",
|
||||
} as never);
|
||||
editMessageTelegram.mockRejectedValueOnce(new Error("message to edit not found"));
|
||||
|
||||
registerTelegramNativeCommands({
|
||||
...createNativeCommandTestParams({}, { bot }),
|
||||
});
|
||||
|
||||
const handler = commandHandlers.get("plug");
|
||||
expect(handler).toBeTruthy();
|
||||
await handler?.(createPrivateCommandContext({ match: "now" }));
|
||||
|
||||
expect(sendMessage).toHaveBeenCalledWith(100, "Working on it...", undefined);
|
||||
expect(editMessageTelegram).toHaveBeenCalledTimes(1);
|
||||
expect(deleteMessage).toHaveBeenCalledWith(100, 999);
|
||||
expect(deliverReplies).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
replies: [expect.objectContaining({ text: "Command completed successfully" })],
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("cleans up the progress placeholder when Telegram suppresses a local exec approval reply", async () => {
|
||||
const { bot, commandHandlers, sendMessage, deleteMessage } = createCommandBot();
|
||||
|
||||
pluginCommandMocks.getPluginCommandSpecs.mockReturnValue([
|
||||
{
|
||||
name: "plug",
|
||||
description: "Plugin command",
|
||||
},
|
||||
] as never);
|
||||
pluginCommandMocks.matchPluginCommand.mockReturnValue({
|
||||
command: {
|
||||
key: "plug",
|
||||
requireAuth: false,
|
||||
telegramNativeProgressMessage: "Working on it...",
|
||||
},
|
||||
args: "now",
|
||||
} as never);
|
||||
pluginCommandMocks.executePluginCommand.mockResolvedValue({
|
||||
text: "Approval required.\n\n```txt\n/approve 7f423fdc allow-once\n```",
|
||||
channelData: {
|
||||
execApproval: {
|
||||
approvalId: "7f423fdc-1111-2222-3333-444444444444",
|
||||
approvalSlug: "7f423fdc",
|
||||
allowedDecisions: ["allow-once", "allow-always", "deny"],
|
||||
},
|
||||
},
|
||||
} as never);
|
||||
|
||||
registerTelegramNativeCommands({
|
||||
...createNativeCommandTestParams(
|
||||
{
|
||||
channels: {
|
||||
telegram: {
|
||||
execApprovals: {
|
||||
enabled: true,
|
||||
approvers: ["12345"],
|
||||
target: "dm",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{ bot },
|
||||
),
|
||||
});
|
||||
|
||||
const handler = commandHandlers.get("plug");
|
||||
expect(handler).toBeTruthy();
|
||||
await handler?.(createPrivateCommandContext({ match: "now" }));
|
||||
|
||||
expect(sendMessage).toHaveBeenCalledWith(100, "Working on it...", undefined);
|
||||
expect(deleteMessage).toHaveBeenCalledWith(100, 999);
|
||||
expect(editMessageTelegram).not.toHaveBeenCalled();
|
||||
expect(deliverReplies).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("sends plugin command error replies silently when silentErrorReplies is enabled", async () => {
|
||||
const commandHandlers = new Map<string, (ctx: unknown) => Promise<void>>();
|
||||
const cfg: OpenClawConfig = {
|
||||
|
||||
@@ -58,6 +58,7 @@ import {
|
||||
resolveTelegramThreadSpec,
|
||||
} from "./bot/helpers.js";
|
||||
import type { TelegramContext, TelegramGetChat } from "./bot/types.js";
|
||||
import type { TelegramInlineButtons } from "./button-types.js";
|
||||
import {
|
||||
resolveTelegramConversationBaseSessionKey,
|
||||
resolveTelegramConversationRoute,
|
||||
@@ -70,6 +71,7 @@ import {
|
||||
} from "./group-access.js";
|
||||
import { resolveTelegramGroupPromptSettings } from "./group-config-helpers.js";
|
||||
import { buildInlineKeyboard } from "./send.js";
|
||||
import { recordSentMessage } from "./sent-message-cache.js";
|
||||
|
||||
const EMPTY_RESPONSE_FALLBACK = "No response generated. Please try again.";
|
||||
const TELEGRAM_NATIVE_COMMAND_CALLBACK_PREFIX = "tgcmd:";
|
||||
@@ -78,6 +80,11 @@ type TelegramNativeCommandContext = Context & { match?: string };
|
||||
type TelegramChunkMode = ReturnType<
|
||||
typeof import("openclaw/plugin-sdk/reply-dispatch-runtime").resolveChunkMode
|
||||
>;
|
||||
type TelegramNativeReplyPayload = import("openclaw/plugin-sdk/reply-dispatch-runtime").ReplyPayload;
|
||||
type TelegramNativeReplyChannelData = {
|
||||
buttons?: TelegramInlineButtons;
|
||||
pin?: boolean;
|
||||
};
|
||||
|
||||
type TelegramCommandAuthResult = {
|
||||
chatId: number;
|
||||
@@ -110,6 +117,53 @@ async function loadTelegramNativeCommandRuntime() {
|
||||
return await telegramNativeCommandRuntimePromise;
|
||||
}
|
||||
|
||||
function resolveTelegramProgressPlaceholder(command: {
|
||||
telegramNativeProgressMessage?: string;
|
||||
}): string | null {
|
||||
const text = command.telegramNativeProgressMessage?.trim();
|
||||
return text ? text : null;
|
||||
}
|
||||
|
||||
function resolveTelegramNativeReplyChannelData(
|
||||
result: TelegramNativeReplyPayload,
|
||||
): TelegramNativeReplyChannelData | undefined {
|
||||
return result.channelData?.telegram as TelegramNativeReplyChannelData | undefined;
|
||||
}
|
||||
|
||||
function isEditableTelegramProgressResult(result: TelegramNativeReplyPayload): boolean {
|
||||
const telegramData = resolveTelegramNativeReplyChannelData(result);
|
||||
return Boolean(
|
||||
typeof result.text === "string" &&
|
||||
result.text.trim() &&
|
||||
!result.mediaUrl &&
|
||||
(!result.mediaUrls || result.mediaUrls.length === 0) &&
|
||||
!result.interactive &&
|
||||
!result.btw &&
|
||||
telegramData?.pin !== true,
|
||||
);
|
||||
}
|
||||
|
||||
async function cleanupTelegramProgressPlaceholder(params: {
|
||||
bot: Bot;
|
||||
chatId: number;
|
||||
progressMessageId?: number;
|
||||
runtime: RuntimeEnv;
|
||||
}): Promise<void> {
|
||||
const progressMessageId = params.progressMessageId;
|
||||
if (progressMessageId == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await withTelegramApiErrorLogging({
|
||||
operation: "deleteMessage",
|
||||
runtime: params.runtime,
|
||||
fn: () => params.bot.api.deleteMessage(params.chatId, progressMessageId),
|
||||
});
|
||||
} catch {
|
||||
// Best-effort cleanup before fallback or suppression exits.
|
||||
}
|
||||
}
|
||||
|
||||
export type RegisterTelegramHandlerParams = {
|
||||
cfg: OpenClawConfig;
|
||||
accountId: string;
|
||||
@@ -956,7 +1010,31 @@ export const registerTelegramNativeCommands = ({
|
||||
});
|
||||
const from = isGroup ? buildTelegramGroupFrom(chatId, threadSpec.id) : `telegram:${chatId}`;
|
||||
const to = `telegram:${chatId}`;
|
||||
const { deliverReplies } = await loadTelegramNativeCommandDeliveryRuntime();
|
||||
const { deliverReplies, emitTelegramMessageSentHooks } =
|
||||
await loadTelegramNativeCommandDeliveryRuntime();
|
||||
let progressMessageId: number | undefined;
|
||||
const progressPlaceholder = resolveTelegramProgressPlaceholder(match.command);
|
||||
|
||||
if (progressPlaceholder) {
|
||||
try {
|
||||
const sent = await withTelegramApiErrorLogging({
|
||||
operation: "sendMessage",
|
||||
runtime,
|
||||
fn: () =>
|
||||
bot.api.sendMessage(
|
||||
chatId,
|
||||
progressPlaceholder,
|
||||
buildTelegramThreadParams(threadSpec),
|
||||
),
|
||||
});
|
||||
const maybeMessageId = (sent as { message_id?: unknown } | undefined)?.message_id;
|
||||
if (typeof maybeMessageId === "number") {
|
||||
progressMessageId = maybeMessageId;
|
||||
}
|
||||
} catch {
|
||||
// Fall back to the normal final reply path if the placeholder send fails.
|
||||
}
|
||||
}
|
||||
|
||||
const result = await nativeCommandRuntime.executePluginCommand({
|
||||
command: match.command,
|
||||
@@ -974,18 +1052,65 @@ export const registerTelegramNativeCommands = ({
|
||||
});
|
||||
|
||||
if (
|
||||
!shouldSuppressLocalTelegramExecApprovalPrompt({
|
||||
shouldSuppressLocalTelegramExecApprovalPrompt({
|
||||
cfg: runtimeCfg,
|
||||
accountId: route.accountId,
|
||||
payload: result,
|
||||
})
|
||||
) {
|
||||
await deliverReplies({
|
||||
replies: [result],
|
||||
...deliveryBaseOptions,
|
||||
silent: runtimeTelegramCfg.silentErrorReplies === true && result.isError === true,
|
||||
await cleanupTelegramProgressPlaceholder({
|
||||
bot,
|
||||
chatId,
|
||||
progressMessageId,
|
||||
runtime,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const progressResultText =
|
||||
typeof result.text === "string" && result.text.trim().length > 0 ? result.text : null;
|
||||
const telegramResultData = resolveTelegramNativeReplyChannelData(result);
|
||||
if (
|
||||
progressMessageId != null &&
|
||||
telegramDeps.editMessageTelegram &&
|
||||
progressResultText &&
|
||||
isEditableTelegramProgressResult(result)
|
||||
) {
|
||||
try {
|
||||
await telegramDeps.editMessageTelegram(chatId, progressMessageId, progressResultText, {
|
||||
cfg: runtimeCfg,
|
||||
accountId: route.accountId,
|
||||
textMode: "markdown",
|
||||
linkPreview: runtimeTelegramCfg.linkPreview,
|
||||
buttons: telegramResultData?.buttons,
|
||||
});
|
||||
recordSentMessage(chatId, progressMessageId);
|
||||
emitTelegramMessageSentHooks({
|
||||
sessionKeyForInternalHooks: route.sessionKey,
|
||||
chatId: String(chatId),
|
||||
accountId: route.accountId,
|
||||
content: progressResultText,
|
||||
success: true,
|
||||
messageId: progressMessageId,
|
||||
isGroup,
|
||||
groupId: isGroup ? String(chatId) : undefined,
|
||||
});
|
||||
return;
|
||||
} catch {
|
||||
// Fall through to cleanup + normal delivered reply if editing fails.
|
||||
}
|
||||
}
|
||||
await cleanupTelegramProgressPlaceholder({
|
||||
bot,
|
||||
chatId,
|
||||
progressMessageId,
|
||||
runtime,
|
||||
});
|
||||
await deliverReplies({
|
||||
replies: [result],
|
||||
...deliveryBaseOptions,
|
||||
silent: runtimeTelegramCfg.silentErrorReplies === true && result.isError === true,
|
||||
});
|
||||
});
|
||||
}
|
||||
} else if (nativeDisabledExplicit) {
|
||||
|
||||
@@ -570,6 +570,15 @@ function emitMessageSentHooks(
|
||||
emitInternalMessageSentHook(params);
|
||||
}
|
||||
|
||||
export function emitTelegramMessageSentHooks(params: EmitMessageSentHookParams): void {
|
||||
const hookRunner = getGlobalHookRunner();
|
||||
emitMessageSentHooks({
|
||||
...params,
|
||||
hookRunner,
|
||||
enabled: hookRunner?.hasHooks("message_sent") ?? false,
|
||||
});
|
||||
}
|
||||
|
||||
export async function deliverReplies(params: {
|
||||
replies: ReplyPayload[];
|
||||
chatId: string;
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
export { deliverReplies, emitInternalMessageSentHook } from "./delivery.replies.js";
|
||||
export {
|
||||
deliverReplies,
|
||||
emitInternalMessageSentHook,
|
||||
emitTelegramMessageSentHooks,
|
||||
} from "./delivery.replies.js";
|
||||
export { resolveMedia } from "./delivery.resolve-media.js";
|
||||
|
||||
Reference in New Issue
Block a user