Skip to content

Commit 0ad5884

Browse files
zhangguiping-xydtobviyus
authored andcommitted
fix(telegram): stop duplicate fallback when dispatch fails after final reply
Telegram no longer sends a generic "Something went wrong" fallback after a final answer was already delivered and a later dispatch/cleanup step failed. Failures with only partial or no visible output still send the error fallback and stay retryable. Related: #87299 Closes #90152
1 parent 48e8965 commit 0ad5884

3 files changed

Lines changed: 73 additions & 13 deletions

File tree

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

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2766,6 +2766,59 @@ describe("dispatchTelegramMessage draft streaming", () => {
27662766
expect(deliverReplies).toHaveBeenCalledTimes(1);
27672767
});
27682768

2769+
it("sends an error fallback when dispatch fails after only partial output", async () => {
2770+
dispatchReplyWithBufferedBlockDispatcher.mockImplementation(async ({ dispatcherOptions }) => {
2771+
await dispatcherOptions.deliver({ text: "partial answer" }, { kind: "block" });
2772+
throw new Error("dispatch failed after partial output");
2773+
});
2774+
2775+
await dispatchWithContext({
2776+
context: createContext({
2777+
ctxPayload: createDirectSessionPayload(),
2778+
}),
2779+
streamMode: "off",
2780+
});
2781+
2782+
expect(deliverReplies).toHaveBeenCalledTimes(2);
2783+
expectDeliveredReply(0, { text: "partial answer" });
2784+
expectDeliveredReply(
2785+
0,
2786+
{
2787+
text: "Something went wrong while processing your request. Please try again.",
2788+
},
2789+
1,
2790+
);
2791+
});
2792+
2793+
it("returns retryable when dispatch fails after partial output and the fallback is not delivered", async () => {
2794+
deliverReplies.mockResolvedValueOnce({ delivered: true });
2795+
deliverReplies.mockResolvedValueOnce({ delivered: false });
2796+
dispatchReplyWithBufferedBlockDispatcher.mockImplementation(async ({ dispatcherOptions }) => {
2797+
await dispatcherOptions.deliver({ text: "partial answer" }, { kind: "block" });
2798+
throw new Error("dispatch failed after partial output");
2799+
});
2800+
2801+
const result = await dispatchWithContext({
2802+
context: createContext({
2803+
ctxPayload: createDirectSessionPayload(),
2804+
}),
2805+
retryDispatchErrors: true,
2806+
streamMode: "off",
2807+
});
2808+
2809+
expect(result).toMatchObject({ kind: "failed-retryable" });
2810+
expect((result as { error?: unknown }).error).toBeInstanceOf(Error);
2811+
expect(deliverReplies).toHaveBeenCalledTimes(2);
2812+
expectDeliveredReply(0, { text: "partial answer" });
2813+
expectDeliveredReply(
2814+
0,
2815+
{
2816+
text: "Something went wrong while processing your request. Please try again.",
2817+
},
2818+
1,
2819+
);
2820+
});
2821+
27692822
it("returns retryable when spooled replay suppresses fallback after non-silent delivery skip", async () => {
27702823
dispatchReplyWithBufferedBlockDispatcher.mockImplementation(async ({ dispatcherOptions }) => {
27712824
dispatcherOptions.onSkip?.({ text: "final answer" }, { kind: "final", reason: "empty" });

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

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -109,10 +109,6 @@ import {
109109
retainTelegramGroupHistoryPromptContext,
110110
selectTelegramGroupHistoryAfterLastSelf,
111111
} from "./group-history-window.js";
112-
import {
113-
createTelegramProgressSummaryTracker,
114-
formatTelegramProgressSummaryLine,
115-
} from "./progress-summary.js";
116112
import { beginTelegramInboundEventDeliveryCorrelation } from "./inbound-event-delivery.js";
117113
import {
118114
createLaneDeliveryStateTracker,
@@ -126,6 +122,10 @@ import {
126122
recordOutboundMessageForPromptContext,
127123
withTelegramPromptContextTimestampMs,
128124
} from "./outbound-message-context.js";
125+
import {
126+
createTelegramProgressSummaryTracker,
127+
formatTelegramProgressSummaryLine,
128+
} from "./progress-summary.js";
129129
import {
130130
createTelegramReasoningStepState,
131131
splitTelegramReasoningText,
@@ -2949,9 +2949,8 @@ export const dispatchTelegramMessage = async ({
29492949
const shouldSendFailureFallback =
29502950
!isRoomEvent &&
29512951
!suppressFailureFallback &&
2952-
(dispatchError ||
2953-
(!deliverySummary.delivered &&
2954-
(deliverySummary.skippedNonSilent > 0 || deliverySummary.failedNonSilent > 0)));
2952+
!finalAnswerDelivered &&
2953+
(dispatchError || deliverySummary.skippedNonSilent > 0 || deliverySummary.failedNonSilent > 0);
29552954
if (shouldSendFailureFallback) {
29562955
const fallbackText = dispatchError
29572956
? "Something went wrong while processing your request. Please try again."
@@ -3002,9 +3001,11 @@ export const dispatchTelegramMessage = async ({
30023001
}
30033002

30043003
const hasFinalResponse =
3004+
finalAnswerDelivered || sentFallback || suppressSilentReplyFallback || queuedFinal;
3005+
const hasVisibleResponse =
30053006
deliverySummary.delivered || sentFallback || suppressSilentReplyFallback || queuedFinal;
30063007
const deliveryFailureWithoutFinalResponse =
3007-
!deliverySummary.delivered &&
3008+
!finalAnswerDelivered &&
30083009
(deliverySummary.skippedNonSilent > 0 || deliverySummary.failedNonSilent > 0);
30093010
const retryableDispatchFailure =
30103011
dispatchError ??
@@ -3014,19 +3015,24 @@ export const dispatchTelegramMessage = async ({
30143015
)
30153016
: null);
30163017

3017-
if (statusReactionController && !hasFinalResponse) {
3018+
if (statusReactionController && !hasVisibleResponse) {
30183019
void finalizeTelegramStatusReaction({ outcome: "error", hasFinalResponse: false }).catch(
30193020
(err: unknown) => {
30203021
logVerbose(`telegram: status reaction error finalize failed: ${String(err)}`);
30213022
},
30223023
);
30233024
}
30243025

3025-
if (retryableDispatchFailure && retryDispatchErrors && !hasFinalResponse) {
3026+
const shouldReturnRetryableDispatchFailure =
3027+
retryDispatchErrors &&
3028+
((dispatchError != null && !hasFinalResponse) ||
3029+
(dispatchError == null && deliveryFailureWithoutFinalResponse && !hasVisibleResponse));
3030+
3031+
if (retryableDispatchFailure && shouldReturnRetryableDispatchFailure) {
30263032
return { kind: "failed-retryable", error: retryableDispatchFailure };
30273033
}
30283034

3029-
if (!hasFinalResponse) {
3035+
if (!hasVisibleResponse) {
30303036
return { kind: "completed" };
30313037
}
30323038

@@ -3071,7 +3077,8 @@ export const dispatchTelegramMessage = async ({
30713077
}
30723078

30733079
if (statusReactionController) {
3074-
const statusReactionOutcome = dispatchError || sentFallback ? "error" : "done";
3080+
const statusReactionOutcome =
3081+
!finalAnswerDelivered && (dispatchError != null || sentFallback) ? "error" : "done";
30753082
void finalizeTelegramStatusReaction({
30763083
outcome: statusReactionOutcome,
30773084
hasFinalResponse: true,

extensions/telegram/src/reasoning-lane-coordinator.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ describe("splitTelegramReasoningText", () => {
1919

2020
it("formats tagged text when the payload is explicitly reasoning", () => {
2121
expect(splitTelegramReasoningText("<think>example</think>Done", true)).toEqual({
22-
reasoningText: "Thinking\n\n_example_",
22+
reasoningText: "🧠 _example_",
2323
});
2424
});
2525

0 commit comments

Comments
 (0)