mirror of https://github.com/openclaw/openclaw.git
fix: retain ambiguous Telegram first preview sends
This commit is contained in:
parent
e75cb04ef0
commit
567cf246cd
|
|
@ -91,6 +91,7 @@ Docs: https://docs.openclaw.ai
|
|||
- Commands/config writes: enforce `configWrites` against both the originating account and the targeted account scope for `/config` and config-backed `/allowlist` edits, blocking sibling-account mutations while preserving gateway `operator.admin` flows. Thanks @tdjackey for reporting.
|
||||
- Security/system.run: fail closed for approval-backed interpreter/runtime commands when OpenClaw cannot bind exactly one concrete local file operand, while extending best-effort direct-file binding to additional runtime forms. Thanks @tdjackey for reporting.
|
||||
- Gateway/session reset auth: split conversation `/new` and `/reset` handling away from the admin-only `sessions.reset` control-plane RPC so write-scoped gateway callers can no longer reach the privileged reset path through `agent`. Thanks @tdjackey for reporting.
|
||||
- Telegram/final preview delivery followup: keep ambiguous first preview sends without a returned `message_id` instead of falling back to a second final send, so slow-provider Telegram replies stop duplicating on the first preview-final seam. (#41932) thanks @hougangdev.
|
||||
|
||||
## 2026.3.8
|
||||
|
||||
|
|
|
|||
|
|
@ -435,6 +435,46 @@ describe("createTelegramDraftStream", () => {
|
|||
expect(api.editMessageText).not.toHaveBeenCalledWith(123, 17, "Message B partial");
|
||||
});
|
||||
|
||||
it("marks sendMayHaveLanded after an ambiguous first preview send failure", async () => {
|
||||
const api = createMockDraftApi();
|
||||
api.sendMessage.mockRejectedValueOnce(new Error("timeout after Telegram accepted send"));
|
||||
const stream = createDraftStream(api);
|
||||
|
||||
stream.update("Hello");
|
||||
await stream.flush();
|
||||
|
||||
expect(api.sendMessage).toHaveBeenCalledTimes(1);
|
||||
expect(stream.sendMayHaveLanded?.()).toBe(true);
|
||||
});
|
||||
|
||||
it("clears sendMayHaveLanded on pre-connect first preview send failures", async () => {
|
||||
const api = createMockDraftApi();
|
||||
api.sendMessage.mockRejectedValueOnce(
|
||||
Object.assign(new Error("connect ECONNREFUSED"), { code: "ECONNREFUSED" }),
|
||||
);
|
||||
const stream = createDraftStream(api);
|
||||
|
||||
stream.update("Hello");
|
||||
await stream.flush();
|
||||
|
||||
expect(api.sendMessage).toHaveBeenCalledTimes(1);
|
||||
expect(stream.sendMayHaveLanded?.()).toBe(false);
|
||||
});
|
||||
|
||||
it("clears sendMayHaveLanded on Telegram 4xx client rejections", async () => {
|
||||
const api = createMockDraftApi();
|
||||
api.sendMessage.mockRejectedValueOnce(
|
||||
Object.assign(new Error("403: Forbidden"), { error_code: 403 }),
|
||||
);
|
||||
const stream = createDraftStream(api);
|
||||
|
||||
stream.update("Hello");
|
||||
await stream.flush();
|
||||
|
||||
expect(api.sendMessage).toHaveBeenCalledTimes(1);
|
||||
expect(stream.sendMayHaveLanded?.()).toBe(false);
|
||||
});
|
||||
|
||||
it("supports rendered previews with parse_mode", async () => {
|
||||
const api = createMockDraftApi();
|
||||
const stream = createTelegramDraftStream({
|
||||
|
|
|
|||
|
|
@ -365,9 +365,13 @@ export function createLaneTextDeliverer(params: CreateLaneTextDelivererParams) {
|
|||
context,
|
||||
});
|
||||
if (typeof previewTargetAfterStop.previewMessageId !== "number") {
|
||||
// This is the first preview creation — no prior visible message exists.
|
||||
// Even if sendMayHaveLanded, prefer fallback over silence: a duplicate
|
||||
// is better than the user seeing nothing at all.
|
||||
if (lane.stream?.sendMayHaveLanded?.()) {
|
||||
params.log(
|
||||
`telegram: ${laneName} first preview send may have landed despite missing message id; keeping to avoid duplicate`,
|
||||
);
|
||||
params.markDelivered();
|
||||
return "retained";
|
||||
}
|
||||
return "fallback";
|
||||
}
|
||||
return finalizePreview(previewTargetAfterStop.previewMessageId, true, false);
|
||||
|
|
|
|||
|
|
@ -538,10 +538,9 @@ describe("createLaneTextDeliverer", () => {
|
|||
);
|
||||
});
|
||||
|
||||
it("falls back when sendMayHaveLanded is true but no prior visible preview exists", async () => {
|
||||
it("retains when the first preview send may have landed without a message id", async () => {
|
||||
const stream = createTestDraftStream();
|
||||
stream.sendMayHaveLanded.mockReturnValue(true);
|
||||
// No messageId and no prior preview → nothing visible for user to see
|
||||
const harness = createHarness({ answerStream: stream });
|
||||
|
||||
const result = await harness.deliverLaneText({
|
||||
|
|
@ -551,10 +550,10 @@ describe("createLaneTextDeliverer", () => {
|
|||
infoKind: "final",
|
||||
});
|
||||
|
||||
// Prefer fallback (possible duplicate) over silence (no message at all)
|
||||
expect(result).toBe("sent");
|
||||
expect(harness.sendPayload).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ text: "Hello final" }),
|
||||
expect(result).toBe("preview-retained");
|
||||
expect(harness.sendPayload).not.toHaveBeenCalled();
|
||||
expect(harness.log).toHaveBeenCalledWith(
|
||||
expect.stringContaining("first preview send may have landed despite missing message id"),
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1734,6 +1734,22 @@ describe("editMessageTelegram", () => {
|
|||
expect(botApi.editMessageText).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("retries editMessageTelegram on Telegram 5xx errors", async () => {
|
||||
botApi.editMessageText
|
||||
.mockRejectedValueOnce(Object.assign(new Error("502: Bad Gateway"), { error_code: 502 }))
|
||||
.mockResolvedValueOnce({ message_id: 1, chat: { id: "123" } });
|
||||
|
||||
await expect(
|
||||
editMessageTelegram("123", 1, "hi", {
|
||||
token: "tok",
|
||||
cfg: {},
|
||||
retry: { attempts: 2, minDelayMs: 0, maxDelayMs: 0, jitter: 0 },
|
||||
}),
|
||||
).resolves.toEqual({ ok: true, messageId: "1", chatId: "123" });
|
||||
|
||||
expect(botApi.editMessageText).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("disables link previews when linkPreview is false", async () => {
|
||||
botApi.editMessageText.mockResolvedValue({ message_id: 1, chat: { id: "123" } });
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue