fix: harden discord media fallback regressions (#28906) (thanks @Sid-Qin)

This commit is contained in:
Peter Steinberger
2026-03-02 03:04:56 +00:00
parent 0a67033fe3
commit 25b731c34a
2 changed files with 78 additions and 0 deletions

View File

@@ -111,6 +111,7 @@ Docs: https://docs.openclaw.ai
### Fixes
- Discord/Inbound media fallback: preserve attachment and sticker metadata when Discord CDN fetch/save fails by keeping URL-based media entries in context, with regression coverage for save failures and mixed success/failure ordering. Landed from contributor PR #28906 by @Sid-Qin. Thanks @Sid-Qin.
- Docs/Docker images: clarify the official GHCR image source and tag guidance (`main`, `latest`, `<version>`), and document that `OPENCLAW_IMAGE` skips local image builds but still uses the repo-local compose/setup flow. (#27214, #31180) Fixes #15655. Thanks @ipl31.
- Agents/Model fallback: classify additional network transport errors (`ECONNREFUSED`, `ENETUNREACH`, `EHOSTUNREACH`, `ENETRESET`, `EAI_AGAIN`) as failover-worthy so fallback chains advance when primary providers are unreachable. Landed from contributor PR #19077 by @ayanesakura. Thanks @ayanesakura.
- Agents/Copilot token refresh: refresh GitHub Copilot runtime API tokens after auth-expiry failures and re-run with the renewed token so long-running embedded/subagent turns do not fail on mid-session 401 expiry. Landed from contributor PR #8805 by @Arthur742Ramos. Thanks @Arthur742Ramos.

View File

@@ -334,6 +334,83 @@ describe("resolveMediaList", () => {
]);
});
it("falls back to URL when saveMediaBuffer fails", async () => {
const attachment = {
id: "att-save-fail",
url: "https://cdn.discordapp.com/attachments/1/photo.png",
filename: "photo.png",
content_type: "image/png",
};
fetchRemoteMedia.mockResolvedValueOnce({
buffer: Buffer.from("image"),
contentType: "image/png",
});
saveMediaBuffer.mockRejectedValueOnce(new Error("disk full"));
const result = await resolveMediaList(
asMessage({
attachments: [attachment],
}),
512,
);
expect(fetchRemoteMedia).toHaveBeenCalledTimes(1);
expect(saveMediaBuffer).toHaveBeenCalledTimes(1);
expect(result).toEqual([
{
path: attachment.url,
contentType: "image/png",
placeholder: "<media:image>",
},
]);
});
it("preserves downloaded attachments alongside failed ones", async () => {
const goodAttachment = {
id: "att-good",
url: "https://cdn.discordapp.com/attachments/1/good.png",
filename: "good.png",
content_type: "image/png",
};
const badAttachment = {
id: "att-bad",
url: "https://cdn.discordapp.com/attachments/1/bad.pdf",
filename: "bad.pdf",
content_type: "application/pdf",
};
fetchRemoteMedia
.mockResolvedValueOnce({
buffer: Buffer.from("image"),
contentType: "image/png",
})
.mockRejectedValueOnce(new Error("network timeout"));
saveMediaBuffer.mockResolvedValueOnce({
path: "/tmp/good.png",
contentType: "image/png",
});
const result = await resolveMediaList(
asMessage({
attachments: [goodAttachment, badAttachment],
}),
512,
);
expect(result).toEqual([
{
path: "/tmp/good.png",
contentType: "image/png",
placeholder: "<media:image>",
},
{
path: badAttachment.url,
contentType: "application/pdf",
placeholder: "<media:document>",
},
]);
});
it("keeps sticker metadata when sticker download fails", async () => {
const sticker = {
id: "sticker-fallback",