From b8a0fa2e7a947e9f38e9141e22559170532358ba Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Wed, 12 Aug 2026 16:03:34 +0800 Subject: [PATCH] fix(plugins): derive media aliases from exports (#122523) --- .../plugin-sdk-native-resolver.test.ts | 39 ++++++++++++- src/plugins/plugin-sdk-native-resolver.ts | 18 +----- src/plugins/sdk-alias.test.ts | 56 ++++++++++++++++++- src/plugins/sdk-alias.ts | 26 +-------- 4 files changed, 94 insertions(+), 45 deletions(-) diff --git a/src/plugins/plugin-sdk-native-resolver.test.ts b/src/plugins/plugin-sdk-native-resolver.test.ts index afb6f39f5de1..135226b92ed7 100644 --- a/src/plugins/plugin-sdk-native-resolver.test.ts +++ b/src/plugins/plugin-sdk-native-resolver.test.ts @@ -93,6 +93,23 @@ function writeInternalCorePackageSource( return sourcePath; } +function writeInternalCorePackageExports( + root: string, + packageDir: string, + subpaths: readonly string[], +): void { + writeJsonFile(path.join(root, "packages", packageDir, "package.json"), { + name: `@openclaw/${packageDir}`, + exports: Object.fromEntries( + subpaths.map((subpath) => { + const exportKey = subpath ? `./${subpath}` : "."; + const distFile = `./dist/${subpath || "index"}.mjs`; + return [exportKey, { import: distFile, default: distFile }]; + }), + ), + }); +} + function addFakePluginSdkDistExport(root: string, subpath: string): string { const packageJsonPath = path.join(root, "package.json"); const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8")) as { @@ -521,7 +538,19 @@ describe("installOpenClawPluginSdkNativeResolver", () => { ); const resultSource = writeInternalCorePackageSource(root, "normalization-core", "result.ts"); const agentIdSource = writeInternalCorePackageSource(root, "normalization-core", "agent-id.ts"); - const mediaCoreSource = writeInternalCorePackageSource(root, "media-core", "mime.ts"); + writeInternalCorePackageExports(root, "normalization-core", [ + "agent-id", + "boolean-coercion", + "result", + "string-coerce", + ]); + writeInternalCorePackageExports(root, "media-core", ["attachment-classify", "mime"]); + const mediaMimeSource = writeInternalCorePackageSource(root, "media-core", "mime.ts"); + const mediaAttachmentClassifySource = writeInternalCorePackageSource( + root, + "media-core", + "attachment-classify.ts", + ); const markdownCoreSource = writeInternalCorePackageSource( root, "markdown-core", @@ -543,6 +572,7 @@ describe("installOpenClawPluginSdkNativeResolver", () => { "acp-core", path.join("runtime", "types.ts"), ); + writeInternalCorePackageExports(root, "acp-core", ["runtime/types"]); const llmCoreSource = writeInternalCorePackageSource(root, "llm-core", "index.ts"); const externalPluginEntry = writeExternalPluginEntry(path.join(root, "external-plugin")); const coreSourceParent = path.join(root, "src", "config", "plugin-web-search-config.ts"); @@ -560,6 +590,7 @@ describe("installOpenClawPluginSdkNativeResolver", () => { expect(installedAliases).toContain("@openclaw/normalization-core/result"); expect(installedAliases).toContain("@openclaw/normalization-core/agent-id"); expect(installedAliases).toContain("@openclaw/media-core/mime"); + expect(installedAliases).toContain("@openclaw/media-core/attachment-classify"); expect(installedAliases).toContain("@openclaw/markdown-core/code-spans"); expect(installedAliases).toContain("@openclaw/ai/transports"); expect(installedAliases).toContain("@openclaw/ai/internal/retry-after"); @@ -583,8 +614,11 @@ describe("installOpenClawPluginSdkNativeResolver", () => { fs.realpathSync(requireFromCoreSource.resolve("@openclaw/normalization-core/agent-id")), ).toBe(fs.realpathSync(agentIdSource)); expect(fs.realpathSync(requireFromCoreSource.resolve("@openclaw/media-core/mime"))).toBe( - fs.realpathSync(mediaCoreSource), + fs.realpathSync(mediaMimeSource), ); + expect( + fs.realpathSync(requireFromCoreSource.resolve("@openclaw/media-core/attachment-classify")), + ).toBe(fs.realpathSync(mediaAttachmentClassifySource)); expect( fs.realpathSync(requireFromCoreSource.resolve("@openclaw/markdown-core/code-spans")), ).toBe(fs.realpathSync(markdownCoreSource)); @@ -609,6 +643,7 @@ describe("installOpenClawPluginSdkNativeResolver", () => { ).toThrow(); expect(() => requireFromPlugin.resolve("@openclaw/normalization-core/result")).toThrow(); expect(() => requireFromPlugin.resolve("@openclaw/media-core/mime")).toThrow(); + expect(() => requireFromPlugin.resolve("@openclaw/media-core/attachment-classify")).toThrow(); expect(() => requireFromPlugin.resolve("@openclaw/markdown-core/code-spans")).toThrow(); expect(() => requireFromPlugin.resolve("@openclaw/ai/transports")).toThrow(); expect(() => requireFromPlugin.resolve("@openclaw/ai/internal/retry-after")).toThrow(); diff --git a/src/plugins/plugin-sdk-native-resolver.ts b/src/plugins/plugin-sdk-native-resolver.ts index 85c1e83588db..ff1792273611 100644 --- a/src/plugins/plugin-sdk-native-resolver.ts +++ b/src/plugins/plugin-sdk-native-resolver.ts @@ -91,22 +91,6 @@ const INTERNAL_CORE_PACKAGE_ALIASES = [ ["internal/shared", path.join("internal", "shared.ts")], ], }, - { - packageName: "@openclaw/media-core", - packageDir: "media-core", - subpaths: [ - ["", "index.ts"], - ["base64", "base64.ts"], - ["constants", "constants.ts"], - ["content-length", "content-length.ts"], - ["file-name", "file-name.ts"], - ["inbound-path-policy", "inbound-path-policy.ts"], - ["inline-image-data-url", "inline-image-data-url.ts"], - ["media-source-url", "media-source-url.ts"], - ["mime", "mime.ts"], - ["read-byte-stream-with-limit", "read-byte-stream-with-limit.ts"], - ], - }, { packageName: "@openclaw/llm-core", packageDir: "llm-core", @@ -341,7 +325,7 @@ function listInternalCorePackageNativeAliases( }> = []; const internalCorePackageAliases = [ ...INTERNAL_CORE_PACKAGE_ALIASES, - ...["normalization-core", "acp-core"].map((packageDir) => ({ + ...["media-core", "normalization-core", "acp-core"].map((packageDir) => ({ packageName: `@openclaw/${packageDir}`, packageDir, subpaths: listWorkspacePackageExportAliasEntries({ diff --git a/src/plugins/sdk-alias.test.ts b/src/plugins/sdk-alias.test.ts index 5aee9dc45c5d..83c90946cda6 100644 --- a/src/plugins/sdk-alias.test.ts +++ b/src/plugins/sdk-alias.test.ts @@ -154,6 +154,32 @@ function writeWorkspacePackageEntry(params: { return { srcFile, distFile }; } +function writeWorkspacePackageExports( + root: string, + packageDir: string, + subpaths: readonly string[], +) { + mkdirSafeDir(path.join(root, "packages", packageDir)); + fs.writeFileSync( + path.join(root, "packages", packageDir, "package.json"), + JSON.stringify( + { + name: `@openclaw/${packageDir}`, + exports: Object.fromEntries( + subpaths.map((subpath) => { + const exportKey = subpath ? `./${subpath}` : "."; + const distFile = `./dist/${subpath || "index"}.mjs`; + return [exportKey, { import: distFile, default: distFile }]; + }), + ), + }, + null, + 2, + ), + "utf-8", + ); +} + type WorkspaceAliasFixture = readonly [ alias: `@openclaw/${string}`, packageDir: string, @@ -1148,6 +1174,15 @@ describe("plugin sdk alias helpers", () => { it("aliases workspace packages to source when dist artifacts are missing", () => { const fixture = createPluginSdkAliasFixture(); + writeWorkspacePackageExports(fixture.root, "media-core", ["", "attachment-classify", "mime"]); + writeWorkspacePackageExports(fixture.root, "acp-core", ["", "runtime/types"]); + writeWorkspacePackageExports(fixture.root, "normalization-core", [ + "", + "agent-id", + "boolean-coercion", + "result", + "string-coerce", + ]); const workspaceAliases = writeWorkspaceAliasFixtures(fixture.root, [ ["@openclaw/gateway-client", "gateway-client", "index"], ["@openclaw/gateway-client/timeouts", "gateway-client", "timeouts"], @@ -1160,6 +1195,7 @@ describe("plugin sdk alias helpers", () => { ["@openclaw/media-generation-core", "media-generation-core", "index"], ["@openclaw/media-generation-core/model-ref", "media-generation-core", "model-ref"], ["@openclaw/media-core", "media-core", "index"], + ["@openclaw/media-core/attachment-classify", "media-core", "attachment-classify"], ["@openclaw/media-core/mime", "media-core", "mime"], ["@openclaw/acp-core", "acp-core", "index"], ["@openclaw/acp-core/runtime/types", "acp-core", "runtime/types"], @@ -1193,6 +1229,9 @@ describe("plugin sdk alias helpers", () => { it("aliases workspace package subpaths to dist when available", () => { const fixture = createPluginSdkAliasFixture(); + writeWorkspacePackageExports(fixture.root, "media-core", ["attachment-classify"]); + writeWorkspacePackageExports(fixture.root, "acp-core", ["normalize-text"]); + writeWorkspacePackageExports(fixture.root, "normalization-core", ["record-coerce"]); const workspaceAliases = writeWorkspaceAliasFixtures(fixture.root, [ ["@openclaw/gateway-client/readiness", "gateway-client", "readiness"], [ @@ -1203,6 +1242,7 @@ describe("plugin sdk alias helpers", () => { ["@openclaw/gateway-protocol/frame-guards", "gateway-protocol", "frame-guards"], ["@openclaw/markdown-core/render", "markdown-core", "render"], ["@openclaw/media-generation-core/catalog", "media-generation-core", "catalog"], + ["@openclaw/media-core/attachment-classify", "media-core", "attachment-classify"], [ "@openclaw/acp-core/normalize-text", "acp-core", @@ -1254,9 +1294,18 @@ describe("plugin sdk alias helpers", () => { ); mkdirSafeDir(path.dirname(normalizationAgentId)); fs.writeFileSync(normalizationAgentId, "export {};\n", "utf-8"); - const cwdWithoutOpenClawPackage = makeTempDir(); + const mediaAttachmentClassify = path.join( + fixture.root, + "dist", + "media-core", + "attachment-classify.js", + ); + mkdirSafeDir(path.dirname(mediaAttachmentClassify)); + fs.writeFileSync(mediaAttachmentClassify, "export {};\n", "utf-8"); + const staleCheckout = createPluginSdkAliasFixture(); + writeWorkspacePackageExports(staleCheckout.root, "media-core", ["mime"]); - const aliases = withCwd(cwdWithoutOpenClawPackage, () => + const aliases = withCwd(staleCheckout.root, () => withEnv({ NODE_ENV: undefined }, () => buildPluginLoaderAliasMap(sourcePluginEntry, undefined, undefined, "dist"), ), @@ -1268,6 +1317,9 @@ describe("plugin sdk alias helpers", () => { expect(fs.realpathSync(aliases["@openclaw/normalization-core/agent-id"] ?? "")).toBe( fs.realpathSync(normalizationAgentId), ); + expect(fs.realpathSync(aliases["@openclaw/media-core/attachment-classify"] ?? "")).toBe( + fs.realpathSync(mediaAttachmentClassify), + ); }); it("aliases bundled plugin package public surfaces for source plugin transforms", () => { diff --git a/src/plugins/sdk-alias.ts b/src/plugins/sdk-alias.ts index 9df3b3f9ab7e..84b871140c0a 100644 --- a/src/plugins/sdk-alias.ts +++ b/src/plugins/sdk-alias.ts @@ -517,21 +517,6 @@ const WORKSPACE_PACKAGE_ALIAS_SUBPATHS = [ ], ], ["media-generation-core", ["", "capability-model-ref", "catalog", "model-ref", "normalization"]], - [ - "media-core", - [ - "", - "base64", - "constants", - "content-length", - "file-name", - "inbound-path-policy", - "inline-image-data-url", - "media-source-url", - "mime", - "read-byte-stream-with-limit", - ], - ], ["retry", [""]], [ "terminal-core", @@ -669,14 +654,7 @@ export function listWorkspacePackageExportAliasEntries(params: { params.packageDir, "package.json", ); - const fallbackPackageRoot = resolveOpenClawPackageRootSync({ cwd: process.cwd() }); - const packageJson = - tryReadJsonSync(packageJsonPath) ?? - (fallbackPackageRoot - ? tryReadJsonSync( - path.join(fallbackPackageRoot, "packages", params.packageDir, "package.json"), - ) - : null); + const packageJson = tryReadJsonSync(packageJsonPath); const exports = packageJson?.exports; if (!exports || typeof exports !== "object" || Array.isArray(exports)) { return listRootPackagedWorkspacePackageAliasEntries(params); @@ -914,7 +892,7 @@ function resolveWorkspacePackageAliasMap(params: { const aliasMap: Record = {}; const workspacePackageAliasEntries = [ ...WORKSPACE_PACKAGE_ALIAS_ENTRIES, - ...["normalization-core", "acp-core"].flatMap((packageDir) => + ...["media-core", "normalization-core", "acp-core"].flatMap((packageDir) => listWorkspacePackageExportAliasEntries({ packageRoot, packageName: `@openclaw/${packageDir}`,