mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 16:50:43 +00:00
fix(telegram): fall back when current preview is missing
This commit is contained in:
@@ -1958,7 +1958,7 @@ describe("dispatchTelegramMessage draft streaming", () => {
|
||||
expect(finalTextSentViaDeliverReplies).toBe(true);
|
||||
});
|
||||
|
||||
it("keeps preview when Telegram reports the final edit target missing", async () => {
|
||||
it("falls back when Telegram reports the current final edit target missing", async () => {
|
||||
const draftStream = createDraftStream(999);
|
||||
createTelegramDraftStream.mockReturnValue(draftStream);
|
||||
dispatchReplyWithBufferedBlockDispatcher.mockImplementation(
|
||||
@@ -1980,6 +1980,6 @@ describe("dispatchTelegramMessage draft streaming", () => {
|
||||
(r: { text?: string }) => r.text === "Final answer",
|
||||
),
|
||||
);
|
||||
expect(finalTextSentViaDeliverReplies).toBe(false);
|
||||
expect(finalTextSentViaDeliverReplies).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -25,8 +25,9 @@ function isMessageNotModifiedError(err: unknown): boolean {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true when Telegram reports the target message no longer exists.
|
||||
* In this case the preview is gone and a fallback send is safe (no duplicate).
|
||||
* Returns true when Telegram rejects an edit because the target message can no
|
||||
* longer be resolved or edited. The caller still needs preview context to
|
||||
* decide whether to retain a different visible preview or fall back to send.
|
||||
*/
|
||||
function isMissingPreviewMessageError(err: unknown): boolean {
|
||||
return MESSAGE_NOT_FOUND_RE.test(extractErrorText(err));
|
||||
@@ -207,6 +208,7 @@ export function createLaneTextDeliverer(params: CreateLaneTextDelivererParams) {
|
||||
updateLaneSnapshot: boolean;
|
||||
lane: DraftLaneState;
|
||||
finalTextAlreadyLanded: boolean;
|
||||
retainAlternatePreviewOnMissingTarget: boolean;
|
||||
}): Promise<PreviewEditResult> => {
|
||||
try {
|
||||
await params.editPreview({
|
||||
@@ -244,11 +246,17 @@ export function createLaneTextDeliverer(params: CreateLaneTextDelivererParams) {
|
||||
return "fallback";
|
||||
}
|
||||
if (isMissingPreviewMessageError(err)) {
|
||||
if (args.retainAlternatePreviewOnMissingTarget) {
|
||||
params.log(
|
||||
`telegram: ${args.laneName} preview final edit target missing; keeping alternate preview without fallback (${String(err)})`,
|
||||
);
|
||||
params.markDelivered();
|
||||
return "retained";
|
||||
}
|
||||
params.log(
|
||||
`telegram: ${args.laneName} preview final edit target missing; keeping existing preview without fallback (${String(err)})`,
|
||||
`telegram: ${args.laneName} preview final edit target missing with no alternate preview; falling back to standard send (${String(err)})`,
|
||||
);
|
||||
params.markDelivered();
|
||||
return "retained";
|
||||
return "fallback";
|
||||
}
|
||||
if (isRecoverableTelegramNetworkError(err, { allowMessageMatch: true })) {
|
||||
params.log(
|
||||
@@ -281,7 +289,11 @@ export function createLaneTextDeliverer(params: CreateLaneTextDelivererParams) {
|
||||
previewMessageId: previewMessageIdOverride,
|
||||
previewTextSnapshot,
|
||||
}: TryUpdatePreviewParams): Promise<PreviewEditResult> => {
|
||||
const editPreview = (messageId: number, finalTextAlreadyLanded: boolean) =>
|
||||
const editPreview = (
|
||||
messageId: number,
|
||||
finalTextAlreadyLanded: boolean,
|
||||
retainAlternatePreviewOnMissingTarget: boolean,
|
||||
) =>
|
||||
tryEditPreviewMessage({
|
||||
laneName,
|
||||
messageId,
|
||||
@@ -291,11 +303,13 @@ export function createLaneTextDeliverer(params: CreateLaneTextDelivererParams) {
|
||||
updateLaneSnapshot,
|
||||
lane,
|
||||
finalTextAlreadyLanded,
|
||||
retainAlternatePreviewOnMissingTarget,
|
||||
});
|
||||
const finalizePreview = (
|
||||
previewMessageId: number,
|
||||
finalTextAlreadyLanded: boolean,
|
||||
hadPreviewMessage: boolean,
|
||||
retainAlternatePreviewOnMissingTarget = false,
|
||||
): PreviewEditResult | Promise<PreviewEditResult> => {
|
||||
const currentPreviewText = previewTextSnapshot ?? getLanePreviewText(lane);
|
||||
const shouldSkipRegressive = shouldSkipRegressivePreviewUpdate({
|
||||
@@ -308,7 +322,11 @@ export function createLaneTextDeliverer(params: CreateLaneTextDelivererParams) {
|
||||
params.markDelivered();
|
||||
return "edited";
|
||||
}
|
||||
return editPreview(previewMessageId, finalTextAlreadyLanded);
|
||||
return editPreview(
|
||||
previewMessageId,
|
||||
finalTextAlreadyLanded,
|
||||
retainAlternatePreviewOnMissingTarget,
|
||||
);
|
||||
};
|
||||
if (!lane.stream) {
|
||||
return "fallback";
|
||||
@@ -346,10 +364,13 @@ export function createLaneTextDeliverer(params: CreateLaneTextDelivererParams) {
|
||||
if (typeof previewTargetAfterStop.previewMessageId !== "number") {
|
||||
return "fallback";
|
||||
}
|
||||
const activePreviewMessageId = lane.stream?.messageId();
|
||||
return finalizePreview(
|
||||
previewTargetAfterStop.previewMessageId,
|
||||
false,
|
||||
previewTargetAfterStop.hadPreviewMessage,
|
||||
typeof activePreviewMessageId === "number" &&
|
||||
activePreviewMessageId !== previewTargetAfterStop.previewMessageId,
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -193,7 +193,7 @@ describe("createLaneTextDeliverer", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps preview when Telegram reports the final edit target missing", async () => {
|
||||
it("falls back when Telegram reports the current final edit target missing", async () => {
|
||||
const harness = createHarness({ answerMessageId: 999 });
|
||||
harness.editPreview.mockRejectedValue(new Error("400: Bad Request: message to edit not found"));
|
||||
|
||||
@@ -204,11 +204,13 @@ describe("createLaneTextDeliverer", () => {
|
||||
infoKind: "final",
|
||||
});
|
||||
|
||||
expect(result).toBe("preview-retained");
|
||||
expect(result).toBe("sent");
|
||||
expect(harness.editPreview).toHaveBeenCalledTimes(1);
|
||||
expect(harness.sendPayload).not.toHaveBeenCalled();
|
||||
expect(harness.sendPayload).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ text: "Hello final" }),
|
||||
);
|
||||
expect(harness.log).toHaveBeenCalledWith(
|
||||
expect.stringContaining("edit target missing; keeping existing preview without fallback"),
|
||||
expect.stringContaining("edit target missing with no alternate preview; falling back"),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -445,7 +447,7 @@ describe("createLaneTextDeliverer", () => {
|
||||
expect(harness.sendPayload).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("keeps archived preview when its final edit target is missing", async () => {
|
||||
it("falls back when an archived preview edit target is missing and no alternate preview exists", async () => {
|
||||
const harness = createHarness();
|
||||
harness.archivedAnswerPreviews.push({
|
||||
messageId: 5555,
|
||||
@@ -461,9 +463,36 @@ describe("createLaneTextDeliverer", () => {
|
||||
infoKind: "final",
|
||||
});
|
||||
|
||||
expect(harness.editPreview).toHaveBeenCalledTimes(1);
|
||||
expect(harness.sendPayload).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ text: "Complete final answer" }),
|
||||
);
|
||||
expect(result).toBe("sent");
|
||||
expect(harness.deletePreviewMessage).toHaveBeenCalledWith(5555);
|
||||
});
|
||||
|
||||
it("keeps the active preview when an archived final edit target is missing", async () => {
|
||||
const harness = createHarness({ answerMessageId: 999 });
|
||||
harness.archivedAnswerPreviews.push({
|
||||
messageId: 5555,
|
||||
textSnapshot: "Partial streaming...",
|
||||
deleteIfUnused: true,
|
||||
});
|
||||
harness.editPreview.mockRejectedValue(new Error("400: Bad Request: message to edit not found"));
|
||||
|
||||
const result = await harness.deliverLaneText({
|
||||
laneName: "answer",
|
||||
text: "Complete final answer",
|
||||
payload: { text: "Complete final answer" },
|
||||
infoKind: "final",
|
||||
});
|
||||
|
||||
expect(harness.editPreview).toHaveBeenCalledTimes(1);
|
||||
expect(harness.sendPayload).not.toHaveBeenCalled();
|
||||
expect(result).toBe("preview-retained");
|
||||
expect(harness.log).toHaveBeenCalledWith(
|
||||
expect.stringContaining("edit target missing; keeping alternate preview without fallback"),
|
||||
);
|
||||
});
|
||||
|
||||
it("deletes consumed boundary previews after fallback final send", async () => {
|
||||
|
||||
Reference in New Issue
Block a user