diff --git a/src/plugin-sdk/root-alias.cjs b/src/plugin-sdk/root-alias.cjs index a818c8d8e4e..8fcac41d018 100644 --- a/src/plugin-sdk/root-alias.cjs +++ b/src/plugin-sdk/root-alias.cjs @@ -125,14 +125,16 @@ function buildPluginSdkAliasMap(useDist) { const packageRoot = getPackageRoot(); const pluginSdkDir = path.join(packageRoot, useDist ? "dist" : "src", "plugin-sdk"); const ext = useDist ? ".js" : ".ts"; + const normalizeTarget = (target) => + process.platform === "win32" ? target.replace(/\\/g, "/") : target; const aliasMap = { - "openclaw/plugin-sdk": __filename, + "openclaw/plugin-sdk": normalizeTarget(__filename), }; for (const subpath of listPluginSdkExportedSubpaths()) { const candidate = path.join(pluginSdkDir, `${subpath}${ext}`); if (fs.existsSync(candidate)) { - aliasMap[`openclaw/plugin-sdk/${subpath}`] = candidate; + aliasMap[`openclaw/plugin-sdk/${subpath}`] = normalizeTarget(candidate); } } @@ -140,20 +142,22 @@ function buildPluginSdkAliasMap(useDist) { } function getJiti(tryNative) { - if (jitiLoaders.has(tryNative)) { - return jitiLoaders.get(tryNative); + const effectiveTryNative = process.platform === "win32" ? false : tryNative; + + if (jitiLoaders.has(effectiveTryNative)) { + return jitiLoaders.get(effectiveTryNative); } const { createJiti } = require("jiti"); const jitiLoader = createJiti(__filename, { - alias: buildPluginSdkAliasMap(tryNative), + alias: buildPluginSdkAliasMap(effectiveTryNative), interopDefault: true, // Prefer Node's native sync ESM loader for built dist/plugin-sdk/*.js files // so local plugins do not create a second transpiled OpenClaw core graph. - tryNative, + tryNative: effectiveTryNative, extensions: [".ts", ".tsx", ".mts", ".cts", ".mtsx", ".ctsx", ".js", ".mjs", ".cjs", ".json"], }); - jitiLoaders.set(tryNative, jitiLoader); + jitiLoaders.set(effectiveTryNative, jitiLoader); return jitiLoader; } diff --git a/src/plugins/contracts/plugin-sdk-root-alias.test.ts b/src/plugins/contracts/plugin-sdk-root-alias.test.ts index 5b547074f1a..9e02fbc83bf 100644 --- a/src/plugins/contracts/plugin-sdk-root-alias.test.ts +++ b/src/plugins/contracts/plugin-sdk-root-alias.test.ts @@ -26,6 +26,7 @@ function loadRootAliasWithStubs(options?: { env?: Record; monolithicExports?: Record; aliasPath?: string; + platform?: string; }) { let createJitiCalls = 0; let jitiLoadCalls = 0; @@ -39,6 +40,7 @@ function loadRootAliasWithStubs(options?: { { process: { env: options?.env ?? {}, + platform: options?.platform ?? "darwin", }, }, { filename: rootAliasPath }, @@ -206,6 +208,18 @@ describe("plugin-sdk root alias", () => { }, expectedTryNative: false, }, + { + name: "prefers source loading on Windows even when compat resolves to dist", + options: { + distExists: true, + env: { NODE_ENV: "production" }, + platform: "win32", + monolithicExports: { + slowHelper: (): string => "loaded", + }, + }, + expectedTryNative: false, + }, ])("$name", ({ options, expectedTryNative }) => { const lazyModule = loadRootAliasWithStubs(options); diff --git a/src/plugins/sdk-alias.test.ts b/src/plugins/sdk-alias.test.ts index 2fbcaf20916..26de9da9501 100644 --- a/src/plugins/sdk-alias.test.ts +++ b/src/plugins/sdk-alias.test.ts @@ -13,6 +13,7 @@ import { buildPluginLoaderJitiOptions, listPluginSdkAliasCandidates, listPluginSdkExportedSubpaths, + normalizeJitiAliasTargetPath, resolveExtensionApiAlias, resolvePluginRuntimeModulePath, resolvePluginSdkAliasFile, @@ -661,6 +662,45 @@ describe("plugin sdk alias helpers", () => { } }); + it("disables native Jiti loads on Windows even for built JavaScript entries", () => { + const originalPlatform = process.platform; + Object.defineProperty(process, "platform", { + configurable: true, + value: "win32", + }); + + try { + expect(shouldPreferNativeJiti("/repo/dist/plugins/runtime/index.js")).toBe(false); + expect(shouldPreferNativeJiti(`/repo/${bundledDistPluginFile("browser", "index.js")}`)).toBe( + false, + ); + } finally { + Object.defineProperty(process, "platform", { + configurable: true, + value: originalPlatform, + }); + } + }); + + it("normalizes Windows alias targets before handing them to Jiti", () => { + const originalPlatform = process.platform; + Object.defineProperty(process, "platform", { + configurable: true, + value: "win32", + }); + + try { + expect(normalizeJitiAliasTargetPath(String.raw`C:\repo\dist\plugin-sdk\root-alias.cjs`)).toBe( + "C:/repo/dist/plugin-sdk/root-alias.cjs", + ); + } finally { + Object.defineProperty(process, "platform", { + configurable: true, + value: originalPlatform, + }); + } + }); + it("loads source runtime shims through the non-native Jiti boundary", async () => { const copiedExtensionRoot = path.join(makeTempDir(), bundledPluginRoot("discord")); const copiedSourceDir = path.join(copiedExtensionRoot, "src"); diff --git a/src/plugins/sdk-alias.ts b/src/plugins/sdk-alias.ts index 796a784ce39..239a4bc55c6 100644 --- a/src/plugins/sdk-alias.ts +++ b/src/plugins/sdk-alias.ts @@ -21,6 +21,10 @@ type PluginSdkPackageJson = { const STARTUP_ARGV1 = process.argv[1]; +export function normalizeJitiAliasTargetPath(targetPath: string): string { + return process.platform === "win32" ? targetPath.replace(/\\/g, "/") : targetPath; +} + function resolveLoaderModulePath(params: LoaderModuleResolveParams = {}): string { return params.modulePath ?? fileURLToPath(params.moduleUrl ?? import.meta.url); } @@ -368,9 +372,17 @@ export function buildPluginLoaderAliasMap( }); const extensionApiAlias = resolveExtensionApiAlias({ modulePath, pluginSdkResolution }); return { - ...(extensionApiAlias ? { "openclaw/extension-api": extensionApiAlias } : {}), - ...(pluginSdkAlias ? { "openclaw/plugin-sdk": pluginSdkAlias } : {}), - ...resolvePluginSdkScopedAliasMap({ modulePath, argv1, moduleUrl, pluginSdkResolution }), + ...(extensionApiAlias + ? { "openclaw/extension-api": normalizeJitiAliasTargetPath(extensionApiAlias) } + : {}), + ...(pluginSdkAlias + ? { "openclaw/plugin-sdk": normalizeJitiAliasTargetPath(pluginSdkAlias) } + : {}), + ...Object.fromEntries( + Object.entries( + resolvePluginSdkScopedAliasMap({ modulePath, argv1, moduleUrl, pluginSdkResolution }), + ).map(([key, value]) => [key, normalizeJitiAliasTargetPath(value)]), + ), }; } @@ -426,6 +438,9 @@ export function shouldPreferNativeJiti(modulePath: string): boolean { if (typeof versions.bun === "string") { return false; } + if (process.platform === "win32") { + return false; + } switch (path.extname(modulePath).toLowerCase()) { case ".js": case ".mjs":