mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 14:10:51 +00:00
fix(plugins): normalize Windows Jiti paths
This commit is contained in:
@@ -21,6 +21,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- Plugins/Windows: normalize Windows absolute paths before handing bundled plugin modules to Jiti, so Feishu/Lark message sending no longer fails with unsupported `c:` ESM loader URLs. Fixes #72783. Thanks @jackychen-png.
|
||||
- CLI/doctor: run bundled plugin runtime-dependency repairs through the async npm installer with spinner/line progress and heartbeat updates, so long `openclaw doctor --fix` installs no longer look hung in TTY or piped output. Fixes #72775. Thanks @dfpalhano.
|
||||
- Feishu/Windows: normalize bundled channel sidecar loads before Jiti evaluates them, so Feishu outbound sends no longer fail with raw `C:` ESM loader errors on Windows. Fixes #72783. Thanks @jackychen-png.
|
||||
- Agents/tools: ignore volatile `exec` runtime metadata when comparing tool-loop outcomes, so enabled loop detection can stop repeated identical shell-command results instead of resetting on duration, PID, session, or cwd changes. Fixes #34574; supersedes #41502. Thanks @gucasbrg and @Zcg2021.
|
||||
|
||||
@@ -2,6 +2,7 @@ import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { importFreshModule } from "../../test/helpers/import-fresh.ts";
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
vi.resetModules();
|
||||
vi.doUnmock("jiti");
|
||||
});
|
||||
@@ -259,6 +260,39 @@ describe("getCachedPluginJitiLoader", () => {
|
||||
expect(jitiLoader).toHaveBeenCalledWith("/repo/dist/extensions/demo/api.js");
|
||||
});
|
||||
|
||||
it("normalizes Windows absolute paths before creating and calling jiti", async () => {
|
||||
vi.spyOn(process, "platform", "get").mockReturnValue("win32");
|
||||
const jitiLoader = vi.fn(() => ({ fromJiti: true }));
|
||||
const createJiti = vi.fn(() => jitiLoader);
|
||||
vi.doMock("jiti", () => ({ createJiti }));
|
||||
vi.doMock("./native-module-require.js", () => ({
|
||||
isJavaScriptModulePath: () => true,
|
||||
tryNativeRequireJavaScriptModule: () => ({ ok: false }),
|
||||
}));
|
||||
const { getCachedPluginJitiLoader } = await importFreshModule<
|
||||
typeof import("./jiti-loader-cache.js")
|
||||
>(import.meta.url, "./jiti-loader-cache.js?scope=windows-jiti-paths");
|
||||
|
||||
const cache = new Map();
|
||||
const loader = getCachedPluginJitiLoader({
|
||||
cache,
|
||||
modulePath: "C:\\Users\\alice\\openclaw\\dist\\extensions\\feishu\\api.js",
|
||||
importerUrl: "file:///C:/Users/alice/openclaw/dist/src/plugins/public-surface-loader.js",
|
||||
jitiFilename: "C:\\Users\\alice\\openclaw\\dist\\extensions\\feishu\\api.js",
|
||||
tryNative: true,
|
||||
});
|
||||
|
||||
loader("C:\\Users\\alice\\openclaw\\dist\\extensions\\feishu\\api.js");
|
||||
|
||||
expect(createJiti).toHaveBeenCalledWith(
|
||||
"file:///C:/Users/alice/openclaw/dist/extensions/feishu/api.js",
|
||||
expect.objectContaining({ tryNative: true }),
|
||||
);
|
||||
expect(jitiLoader).toHaveBeenCalledWith(
|
||||
"file:///C:/Users/alice/openclaw/dist/extensions/feishu/api.js",
|
||||
);
|
||||
});
|
||||
|
||||
it("skips the native-require fast path when tryNative is explicitly false", async () => {
|
||||
const jitiLoader = vi.fn(() => ({ fromJiti: true }));
|
||||
const createJiti = vi.fn(() => jitiLoader);
|
||||
@@ -290,6 +324,41 @@ describe("getCachedPluginJitiLoader", () => {
|
||||
expect(jitiLoader).toHaveBeenCalledWith("/repo/dist/extensions/demo/api.js");
|
||||
});
|
||||
|
||||
it("normalizes Windows absolute paths when native loading is disabled", async () => {
|
||||
vi.spyOn(process, "platform", "get").mockReturnValue("win32");
|
||||
const jitiLoader = vi.fn(() => ({ fromJiti: true }));
|
||||
const createJiti = vi.fn(() => jitiLoader);
|
||||
vi.doMock("jiti", () => ({ createJiti }));
|
||||
const nativeStub = vi.fn(() => ({ ok: true, moduleExport: { fromNative: true } }));
|
||||
vi.doMock("./native-module-require.js", () => ({
|
||||
isJavaScriptModulePath: () => true,
|
||||
tryNativeRequireJavaScriptModule: nativeStub,
|
||||
}));
|
||||
const { getCachedPluginJitiLoader } = await importFreshModule<
|
||||
typeof import("./jiti-loader-cache.js")
|
||||
>(import.meta.url, "./jiti-loader-cache.js?scope=windows-jiti-no-native");
|
||||
|
||||
const cache = new Map();
|
||||
const loader = getCachedPluginJitiLoader({
|
||||
cache,
|
||||
modulePath: "C:\\Users\\alice\\openclaw\\extensions\\feishu\\api.ts",
|
||||
importerUrl: "file:///C:/Users/alice/openclaw/src/plugins/loader.ts",
|
||||
jitiFilename: "C:\\Users\\alice\\openclaw\\extensions\\feishu\\api.ts",
|
||||
tryNative: false,
|
||||
});
|
||||
|
||||
loader("C:\\Users\\alice\\openclaw\\extensions\\feishu\\api.ts");
|
||||
|
||||
expect(nativeStub).not.toHaveBeenCalled();
|
||||
expect(createJiti).toHaveBeenCalledWith(
|
||||
"file:///C:/Users/alice/openclaw/extensions/feishu/api.ts",
|
||||
expect.objectContaining({ tryNative: false }),
|
||||
);
|
||||
expect(jitiLoader).toHaveBeenCalledWith(
|
||||
"file:///C:/Users/alice/openclaw/extensions/feishu/api.ts",
|
||||
);
|
||||
});
|
||||
|
||||
it("forwards extra loader arguments through to the jiti fallback", async () => {
|
||||
const jitiLoader = vi.fn(() => ({ fromJiti: true }));
|
||||
const createJiti = vi.fn(() => jitiLoader);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createJiti } from "jiti";
|
||||
import { toSafeImportPath } from "../shared/import-specifier.js";
|
||||
import { tryNativeRequireJavaScriptModule } from "./native-module-require.js";
|
||||
import {
|
||||
buildPluginLoaderJitiOptions,
|
||||
@@ -24,7 +25,7 @@ export function getCachedPluginJitiLoader(params: {
|
||||
pluginSdkResolution?: PluginSdkResolutionPreference;
|
||||
cacheScopeKey?: string;
|
||||
}): PluginJitiLoader {
|
||||
const jitiFilename = params.jitiFilename ?? params.modulePath;
|
||||
const jitiFilename = toSafeImportPath(params.jitiFilename ?? params.modulePath);
|
||||
if (params.cacheScopeKey) {
|
||||
const scopedCacheKey = `${jitiFilename}::${params.cacheScopeKey}`;
|
||||
const cached = params.cache.get(scopedCacheKey);
|
||||
@@ -79,13 +80,22 @@ export function getCachedPluginJitiLoader(params: {
|
||||
...buildPluginLoaderJitiOptions(aliasMap),
|
||||
tryNative,
|
||||
});
|
||||
const loadWithJiti = new Proxy(jitiLoader, {
|
||||
apply(target, thisArg, argArray) {
|
||||
const [first, ...rest] = argArray as [unknown, ...unknown[]];
|
||||
if (typeof first === "string") {
|
||||
return Reflect.apply(target, thisArg, [toSafeImportPath(first), ...rest] as never) as never;
|
||||
}
|
||||
return Reflect.apply(target, thisArg, argArray as never) as never;
|
||||
},
|
||||
});
|
||||
// When the caller has explicitly opted out of native loading (for example
|
||||
// `bundled-capability-runtime` in Vitest+dist mode, which depends on
|
||||
// jiti's alias rewriting to surface a narrow SDK slice), route every
|
||||
// target through jiti so those alias rewrites still apply.
|
||||
if (!tryNative) {
|
||||
params.cache.set(scopedCacheKey, jitiLoader);
|
||||
return jitiLoader;
|
||||
params.cache.set(scopedCacheKey, loadWithJiti);
|
||||
return loadWithJiti;
|
||||
}
|
||||
// Otherwise prefer native require() for already-compiled JS artifacts
|
||||
// (the bundled plugin public surfaces shipped in dist/). jiti's transform
|
||||
@@ -99,7 +109,7 @@ export function getCachedPluginJitiLoader(params: {
|
||||
if (native.ok) {
|
||||
return native.moduleExport;
|
||||
}
|
||||
return (jitiLoader as (t: string, ...a: unknown[]) => unknown)(target, ...rest);
|
||||
return (loadWithJiti as (t: string, ...a: unknown[]) => unknown)(target, ...rest);
|
||||
}) as PluginJitiLoader;
|
||||
params.cache.set(scopedCacheKey, loader);
|
||||
return loader;
|
||||
|
||||
Reference in New Issue
Block a user