diff --git a/CHANGELOG.md b/CHANGELOG.md index b1e03b84271..332693bafba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,6 +56,7 @@ Docs: https://docs.openclaw.ai ### Fixes +- Media/images: keep HEIC/HEIF attachments fail-closed when optional Sharp conversion is unavailable instead of sending originals that still need conversion. Thanks @vincentkoc. - Telegram/streaming: sanitize tool-progress draft preview backticks before shared compaction, so long backtick-heavy progress text still renders inside the safe code-formatted preview instead of collapsing to an ellipsis. - UI/chat: remove the unsupported `line-clamp` declaration from the chat queue text rule to eliminate Firefox console noise without changing visible truncation behavior. Thanks @ZanderH-code. - Agents/Pi: suppress persistence for synthetic mid-turn overflow continuation prompts, so transcript-retry recovery does not write the "continue from transcript" prompt as a new user turn. Thanks @vincentkoc. diff --git a/src/media/web-media.test.ts b/src/media/web-media.test.ts index 634b1763fe0..8ac66305273 100644 --- a/src/media/web-media.test.ts +++ b/src/media/web-media.test.ts @@ -176,7 +176,9 @@ describe("loadWebMedia", () => { throw new Error("should not optimize png"); }), resizeToJpeg: vi.fn(async () => { - throw new Error("should not resize jpeg"); + throw new Error( + "Optional dependency sharp is required for image attachment processing | Cannot find package 'sharp' imported from image-ops.js", + ); }), })); try { @@ -210,6 +212,17 @@ describe("loadWebMedia", () => { }); }); + it("does not send original HEIC media when optional sharp conversion is unavailable", async () => { + await withUnavailableImageOptimizer(async () => { + const heicFile = path.join(fixtureRoot, "photo.heic"); + await fs.writeFile(heicFile, Buffer.from("heic-source")); + const { loadWebMedia: loadWebMediaWithMissingOptimizer } = await import("./web-media.js"); + await expect( + loadWebMediaWithMissingOptimizer(heicFile, createLocalWebMediaOptions()), + ).rejects.toThrow(/Optional dependency sharp is required/); + }); + }); + it("resolves relative local media paths against the provided workspace directory", async () => { const result = await loadWebMedia("chart.png", { maxBytes: 1024 * 1024, diff --git a/src/media/web-media.ts b/src/media/web-media.ts index f77661680ff..edf37dbad35 100644 --- a/src/media/web-media.ts +++ b/src/media/web-media.ts @@ -413,7 +413,11 @@ async function loadWebMediaInternal( try { optimized = await optimizeImageWithFallback({ buffer, cap, meta }); } catch (err) { - if (isOptionalImageOptimizerUnavailable(err) && buffer.length <= cap) { + if ( + isOptionalImageOptimizerUnavailable(err) && + !isHeicSource(meta ?? {}) && + buffer.length <= cap + ) { if (shouldLogVerbose()) { logVerbose( `Image optimizer unavailable; sending original ${formatMb(buffer.length)}MB media without optimization`,