Skip to content

Commit 4266e26

Browse files
bugkill3rgreptile-apps[bot]obviyus
authored
fix: emit message:sent hook on Telegram streaming preview finalization (openclaw#50917)
* fix: emit message:sent hook on Telegram streaming preview finalization * fix: include messageId in preview-delivered hook callback * fix: skip message:sent hook for preview-retained paths * fix: correct JSDoc for onPreviewDelivered callback * fix: pass visible preview text on regressive-skip path * fix: remove dead fallbacks and add stopCreatesFirstPreview test * Update extensions/telegram/src/lane-delivery-text-deliverer.ts Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * fix: align telegram preview sent hooks (openclaw#50917) (thanks @bugkill3r) --------- Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> Co-authored-by: Ayaan Zaidi <[email protected]>
1 parent 85a5d64 commit 4266e26

File tree

6 files changed

+207
-67
lines changed

6 files changed

+207
-67
lines changed

extensions/telegram/src/bot-message-dispatch.test.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
const createTelegramDraftStream = vi.hoisted(() => vi.fn());
1212
const dispatchReplyWithBufferedBlockDispatcher = vi.hoisted(() => vi.fn());
1313
const deliverReplies = vi.hoisted(() => vi.fn());
14+
const emitInternalMessageSentHook = vi.hoisted(() => vi.fn());
1415
const createForumTopicTelegram = vi.hoisted(() => vi.fn());
1516
const deleteMessageTelegram = vi.hoisted(() => vi.fn());
1617
const editForumTopicTelegram = vi.hoisted(() => vi.fn());
@@ -46,6 +47,7 @@ vi.mock("./draft-stream.js", () => ({
4647

4748
vi.mock("./bot/delivery.js", () => ({
4849
deliverReplies,
50+
emitInternalMessageSentHook,
4951
}));
5052

5153
vi.mock("./send.js", () => ({
@@ -103,6 +105,7 @@ describe("dispatchTelegramMessage draft streaming", () => {
103105
createTelegramDraftStream.mockClear();
104106
dispatchReplyWithBufferedBlockDispatcher.mockClear();
105107
deliverReplies.mockClear();
108+
emitInternalMessageSentHook.mockClear();
106109
createForumTopicTelegram.mockClear();
107110
deleteMessageTelegram.mockClear();
108111
editForumTopicTelegram.mockClear();
@@ -521,6 +524,38 @@ describe("dispatchTelegramMessage draft streaming", () => {
521524
expect(draftStream.stop).toHaveBeenCalled();
522525
});
523526

527+
it("emits only the internal message:sent hook when a final answer stays in preview", async () => {
528+
const draftStream = createDraftStream(999);
529+
createTelegramDraftStream.mockReturnValue(draftStream);
530+
dispatchReplyWithBufferedBlockDispatcher.mockImplementation(async ({ dispatcherOptions }) => {
531+
await dispatcherOptions.deliver({ text: "Primary result" }, { kind: "final" });
532+
return { queuedFinal: true };
533+
});
534+
535+
await dispatchWithContext({
536+
context: createContext({
537+
ctxPayload: { SessionKey: "s1" } as unknown as TelegramMessageContext["ctxPayload"],
538+
}),
539+
});
540+
541+
expect(deliverReplies).not.toHaveBeenCalled();
542+
expect(editMessageTelegram).toHaveBeenCalledWith(
543+
123,
544+
999,
545+
"Primary result",
546+
expect.any(Object),
547+
);
548+
expect(emitInternalMessageSentHook).toHaveBeenCalledWith(
549+
expect.objectContaining({
550+
sessionKeyForInternalHooks: "s1",
551+
chatId: "123",
552+
content: "Primary result",
553+
success: true,
554+
messageId: 999,
555+
}),
556+
);
557+
});
558+
524559
it("keeps streamed preview visible when final text regresses after a tool warning", async () => {
525560
const draftStream = createDraftStream(999);
526561
createTelegramDraftStream.mockReturnValue(draftStream);

extensions/telegram/src/bot-message-dispatch.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env";
3030
import { defaultTelegramBotDeps, type TelegramBotDeps } from "./bot-deps.js";
3131
import type { TelegramMessageContext } from "./bot-message-context.js";
3232
import type { TelegramBotOptions } from "./bot.js";
33-
import { deliverReplies } from "./bot/delivery.js";
33+
import { deliverReplies, emitInternalMessageSentHook } from "./bot/delivery.js";
3434
import type { TelegramStreamMode } from "./bot/types.js";
3535
import type { TelegramInlineButtons } from "./button-types.js";
3636
import { createTelegramDraftStream } from "./draft-stream.js";
@@ -41,6 +41,7 @@ import {
4141
createLaneDeliveryStateTracker,
4242
createLaneTextDeliverer,
4343
type DraftLaneState,
44+
type LaneDeliveryResult,
4445
type LaneName,
4546
type LanePreviewLifecycle,
4647
} from "./lane-delivery.js";
@@ -480,6 +481,21 @@ export const dispatchTelegramMessage = async ({
480481
}
481482
return result.delivered;
482483
};
484+
const emitPreviewFinalizedHook = (result: LaneDeliveryResult) => {
485+
if (result.kind !== "preview-finalized") {
486+
return;
487+
}
488+
emitInternalMessageSentHook({
489+
sessionKeyForInternalHooks: deliveryBaseOptions.sessionKeyForInternalHooks,
490+
chatId: deliveryBaseOptions.chatId,
491+
accountId: deliveryBaseOptions.accountId,
492+
content: result.delivery.content,
493+
success: true,
494+
messageId: result.delivery.messageId,
495+
isGroup: deliveryBaseOptions.mirrorIsGroup,
496+
groupId: deliveryBaseOptions.mirrorGroupId,
497+
});
498+
};
483499
const deliverLaneText = createLaneTextDeliverer({
484500
lanes,
485501
archivedAnswerPreviews,
@@ -612,8 +628,11 @@ export const dispatchTelegramMessage = async ({
612628
previewButtons,
613629
allowPreviewUpdateForNonFinal: segment.lane === "reasoning",
614630
});
631+
if (info.kind === "final") {
632+
emitPreviewFinalizedHook(result);
633+
}
615634
if (segment.lane === "reasoning") {
616-
if (result !== "skipped") {
635+
if (result.kind !== "skipped") {
617636
reasoningStepState.noteReasoningDelivered();
618637
await flushBufferedFinalAnswer();
619638
}

extensions/telegram/src/bot/delivery.replies.ts

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -491,9 +491,7 @@ async function maybePinFirstDeliveredMessage(params: {
491491
}
492492
}
493493

494-
function emitMessageSentHooks(params: {
495-
hookRunner: ReturnType<typeof getGlobalHookRunner>;
496-
enabled: boolean;
494+
type EmitMessageSentHookParams = {
497495
sessionKeyForInternalHooks?: string;
498496
chatId: string;
499497
accountId?: string;
@@ -503,11 +501,10 @@ function emitMessageSentHooks(params: {
503501
messageId?: number;
504502
isGroup?: boolean;
505503
groupId?: string;
506-
}): void {
507-
if (!params.enabled && !params.sessionKeyForInternalHooks) {
508-
return;
509-
}
510-
const canonical = buildCanonicalSentMessageHookContext({
504+
};
505+
506+
function buildTelegramSentHookContext(params: EmitMessageSentHookParams) {
507+
return buildCanonicalSentMessageHookContext({
511508
to: params.chatId,
512509
content: params.content,
513510
success: params.success,
@@ -519,20 +516,13 @@ function emitMessageSentHooks(params: {
519516
isGroup: params.isGroup,
520517
groupId: params.groupId,
521518
});
522-
if (params.enabled) {
523-
fireAndForgetHook(
524-
Promise.resolve(
525-
params.hookRunner!.runMessageSent(
526-
toPluginMessageSentEvent(canonical),
527-
toPluginMessageContext(canonical),
528-
),
529-
),
530-
"telegram: message_sent plugin hook failed",
531-
);
532-
}
519+
}
520+
521+
export function emitInternalMessageSentHook(params: EmitMessageSentHookParams): void {
533522
if (!params.sessionKeyForInternalHooks) {
534523
return;
535524
}
525+
const canonical = buildTelegramSentHookContext(params);
536526
fireAndForgetHook(
537527
triggerInternalHook(
538528
createInternalHookEvent(
@@ -546,6 +536,30 @@ function emitMessageSentHooks(params: {
546536
);
547537
}
548538

539+
function emitMessageSentHooks(
540+
params: EmitMessageSentHookParams & {
541+
hookRunner: ReturnType<typeof getGlobalHookRunner>;
542+
enabled: boolean;
543+
},
544+
): void {
545+
if (!params.enabled && !params.sessionKeyForInternalHooks) {
546+
return;
547+
}
548+
const canonical = buildTelegramSentHookContext(params);
549+
if (params.enabled) {
550+
fireAndForgetHook(
551+
Promise.resolve(
552+
params.hookRunner!.runMessageSent(
553+
toPluginMessageSentEvent(canonical),
554+
toPluginMessageContext(canonical),
555+
),
556+
),
557+
"telegram: message_sent plugin hook failed",
558+
);
559+
}
560+
emitInternalMessageSentHook(params);
561+
}
562+
549563
export async function deliverReplies(params: {
550564
replies: ReplyPayload[];
551565
chatId: string;
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
export { deliverReplies } from "./delivery.replies.js";
1+
export { deliverReplies, emitInternalMessageSentHook } from "./delivery.replies.js";
22
export { resolveMedia } from "./delivery.resolve-media.js";

0 commit comments

Comments
 (0)