refactor(agents): unify subagent announce delivery pipeline

Co-authored-by: Smith Labs <SmithLabsLLC@users.noreply.github.com>
Co-authored-by: Do Cao Hieu <docaohieu2808@users.noreply.github.com>
This commit is contained in:
Peter Steinberger
2026-02-26 00:30:19 +00:00
parent aedf62ac7e
commit 4258a3307f
14 changed files with 623 additions and 132 deletions

View File

@@ -196,6 +196,10 @@ describe("sendMessageTelegram", () => {
for (const testCase of cases) {
botCtorSpy.mockClear();
loadConfig.mockReturnValue(testCase.cfg);
botApi.sendMessage.mockResolvedValue({
message_id: 1,
chat: { id: "123" },
});
await sendMessageTelegram("123", "hi", testCase.opts);
expect(botCtorSpy, testCase.name).toHaveBeenCalledWith(
"tok",
@@ -325,6 +329,40 @@ describe("sendMessageTelegram", () => {
}
});
it("fails when Telegram text send returns no message_id", async () => {
const sendMessage = vi.fn().mockResolvedValue({
chat: { id: "123" },
});
const api = { sendMessage } as unknown as {
sendMessage: typeof sendMessage;
};
await expect(
sendMessageTelegram("123", "hi", {
token: "tok",
api,
}),
).rejects.toThrow(/returned no message_id/i);
});
it("fails when Telegram media send returns no message_id", async () => {
mockLoadedMedia({ contentType: "image/png", fileName: "photo.png" });
const sendPhoto = vi.fn().mockResolvedValue({
chat: { id: "123" },
});
const api = { sendPhoto } as unknown as {
sendPhoto: typeof sendPhoto;
};
await expect(
sendMessageTelegram("123", "caption", {
token: "tok",
api,
mediaUrl: "https://example.com/photo.png",
}),
).rejects.toThrow(/returned no message_id/i);
});
it("uses native fetch for BAN compatibility when api is omitted", async () => {
const originalFetch = globalThis.fetch;
const originalBun = (globalThis as { Bun?: unknown }).Bun;

View File

@@ -86,6 +86,16 @@ type TelegramReactionOpts = {
retry?: RetryConfig;
};
function resolveTelegramMessageIdOrThrow(
result: TelegramMessageLike | null | undefined,
context: string,
): number {
if (typeof result?.message_id === "number" && Number.isFinite(result.message_id)) {
return Math.trunc(result.message_id);
}
throw new Error(`Telegram ${context} returned no message_id`);
}
const PARSE_ERR_RE = /can't parse entities|parse entities|find end of the entity/i;
const THREAD_NOT_FOUND_RE = /400:\s*Bad Request:\s*message thread not found/i;
const MESSAGE_NOT_MODIFIED_RE =
@@ -685,11 +695,9 @@ export async function sendMessageTelegram(
})();
const result = await sendMedia(mediaSender.label, mediaSender.sender);
const mediaMessageId = String(result?.message_id ?? "unknown");
const mediaMessageId = resolveTelegramMessageIdOrThrow(result, "media send");
const resolvedChatId = String(result?.chat?.id ?? chatId);
if (result?.message_id) {
recordSentMessage(chatId, result.message_id);
}
recordSentMessage(chatId, mediaMessageId);
recordChannelActivity({
channel: "telegram",
accountId: account.accountId,
@@ -708,13 +716,15 @@ export async function sendMessageTelegram(
: undefined;
const textRes = await sendTelegramText(followUpText, textParams);
// Return the text message ID as the "main" message (it's the actual content).
const textMessageId = resolveTelegramMessageIdOrThrow(textRes, "text follow-up send");
recordSentMessage(chatId, textMessageId);
return {
messageId: String(textRes?.message_id ?? mediaMessageId),
messageId: String(textMessageId),
chatId: resolvedChatId,
};
}
return { messageId: mediaMessageId, chatId: resolvedChatId };
return { messageId: String(mediaMessageId), chatId: resolvedChatId };
}
if (!text || !text.trim()) {
@@ -728,16 +738,14 @@ export async function sendMessageTelegram(
}
: undefined;
const res = await sendTelegramText(text, textParams, opts.plainText);
const messageId = String(res?.message_id ?? "unknown");
if (res?.message_id) {
recordSentMessage(chatId, res.message_id);
}
const messageId = resolveTelegramMessageIdOrThrow(res, "text send");
recordSentMessage(chatId, messageId);
recordChannelActivity({
channel: "telegram",
accountId: account.accountId,
direction: "outbound",
});
return { messageId, chatId: String(res?.chat?.id ?? chatId) };
return { messageId: String(messageId), chatId: String(res?.chat?.id ?? chatId) };
}
export async function reactMessageTelegram(
@@ -1013,18 +1021,16 @@ export async function sendStickerTelegram(
requestWithChatNotFound(() => api.sendSticker(chatId, fileId.trim(), effectiveParams), label),
);
const messageId = String(result?.message_id ?? "unknown");
const messageId = resolveTelegramMessageIdOrThrow(result, "sticker send");
const resolvedChatId = String(result?.chat?.id ?? chatId);
if (result?.message_id) {
recordSentMessage(chatId, result.message_id);
}
recordSentMessage(chatId, messageId);
recordChannelActivity({
channel: "telegram",
accountId: account.accountId,
direction: "outbound",
});
return { messageId, chatId: resolvedChatId };
return { messageId: String(messageId), chatId: resolvedChatId };
}
type TelegramPollOpts = {
@@ -1121,12 +1127,10 @@ export async function sendPollTelegram(
),
);
const messageId = String(result?.message_id ?? "unknown");
const messageId = resolveTelegramMessageIdOrThrow(result, "poll send");
const resolvedChatId = String(result?.chat?.id ?? chatId);
const pollId = result?.poll?.id;
if (result?.message_id) {
recordSentMessage(chatId, result.message_id);
}
recordSentMessage(chatId, messageId);
recordChannelActivity({
channel: "telegram",
@@ -1134,7 +1138,7 @@ export async function sendPollTelegram(
direction: "outbound",
});
return { messageId, chatId: resolvedChatId, pollId };
return { messageId: String(messageId), chatId: resolvedChatId, pollId };
}
// ---------------------------------------------------------------------------