diff --git a/src/service/attachment-uploader.test.ts b/src/service/attachment-uploader.test.ts index 0237b1a..aedd8af 100644 --- a/src/service/attachment-uploader.test.ts +++ b/src/service/attachment-uploader.test.ts @@ -218,6 +218,31 @@ describe("attachment uploader", () => { expect(attachmentsApi.startMultiPartUpload).not.toHaveBeenCalled(); }); + test("ignores OpenClaw media:// authority URIs without attempting an upload or warning", async () => { + const attachmentsApi = createAttachmentsApi(); + const client = { api: { attachments: attachmentsApi } }; + const onWarn = vi.fn(); + + const uploader = createAttachmentUploader({ + getClient: () => client as unknown as Opik, + getAttachmentBaseUrl: () => "https://www.comet.com/opik/api", + onWarn, + formatError: (err) => String(err), + }); + + uploader.scheduleMediaAttachmentUploads({ + entityType: "trace", + entity: { id: "trace-1" }, + projectName: "openclaw", + reason: "media-authority-uri", + payloads: ["media://inbound/example.jpg"], + }); + await uploader.waitForUploads(); + + expect(attachmentsApi.startMultiPartUpload).not.toHaveBeenCalled(); + expect(onWarn).not.toHaveBeenCalled(); + }); + test("uploads multipart attachments without loading the whole file into one request body", async () => { const largeContents = Buffer.alloc(ATTACHMENT_UPLOAD_PART_SIZE_BYTES + 32, 0x61); const { dir, filePath } = await createTempMediaFile(".png", largeContents); diff --git a/src/service/media.test.ts b/src/service/media.test.ts index f946fe4..c179004 100644 --- a/src/service/media.test.ts +++ b/src/service/media.test.ts @@ -14,6 +14,18 @@ describe("media path extraction", () => { expect([...target]).toEqual(["/tmp/image.png"]); }); + test("ignores media:// authority URIs instead of normalizing them to local paths", () => { + const target = new Set(); + collectMediaPathsFromString("media://inbound/example.jpg", target); + expect(target.size).toBe(0); + }); + + test("ignores media:// authority URIs with a host segment", () => { + const target = new Set(); + collectMediaPathsFromString("see media://host/path/to/example.jpg now", target); + expect(target.size).toBe(0); + }); + test("collects file:// local path references", () => { const target = new Set(); collectMediaPathsFromString("open file:///tmp/image.png", target); diff --git a/src/service/media.ts b/src/service/media.ts index 3b46c72..a9a8c3c 100644 --- a/src/service/media.ts +++ b/src/service/media.ts @@ -30,8 +30,10 @@ const MEDIA_EXTENSIONS = new Set([ ".mkv", ]); +// Reject the media:// authority form (e.g. OpenClaw media://inbound/...) — those are +// logical URIs, not local paths, and must not be normalized to /inbound/... and stat'd. const MEDIA_SCHEME_LOCAL_PATH_RE = - /\bmedia:((?:~\/|\/)[^\s"'`]+?\.(?:png|jpe?g|gif|webp|bmp|tiff?|heic|heif|svg|mp3|wav|m4a|aac|ogg|oga|flac|opus|caf|weba|webm|mp4|mov|mkv))(?=[\s"'`]|$)/gi; + /\bmedia:(?!\/\/)((?:~\/|\/)[^\s"'`]+?\.(?:png|jpe?g|gif|webp|bmp|tiff?|heic|heif|svg|mp3|wav|m4a|aac|ogg|oga|flac|opus|caf|weba|webm|mp4|mov|mkv))(?=[\s"'`]|$)/gi; const FILE_SCHEME_LOCAL_PATH_RE = /\bfile:\/\/((?:~\/|\/)[^\s"'`]+?\.(?:png|jpe?g|gif|webp|bmp|tiff?|heic|heif|svg|mp3|wav|m4a|aac|ogg|oga|flac|opus|caf|weba|webm|mp4|mov|mkv))(?:\?[^\s"'`]*)?(?=[\s"'`]|$)/gi;