mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 13:00:44 +00:00
fix: enable native require fast path on Windows for bundled plugins (#74173)
Removes the win32 exclusion from supportsNativeJitiRuntime() and adds { allowWindows: true } to all tryNativeRequireJavaScriptModule call sites, so bundled plugin modules use native require() instead of Jiti on Windows. Also adds an attempted-load counter to the debug timing log and a changelog entry.
Fixes #68656
Co-authored-by: Galin Iliev <galiniliev@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -315,6 +315,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Auth/device pairing: bound bootstrap handoff token issuance, redemption, and approved pairing baselines to the documented per-role scope allowlist, so bootstrap approvals cannot persistently grant `operator.admin`, `operator.pairing`, or `node.exec` scopes. Thanks @eleqtrizit.
|
||||
- Providers/GitHub Copilot: support the GUI/RPC wizard device-code auth flow so onboarding from non-TTY clients (gateway RPC bridge, GUI wizards) completes instead of returning empty profiles. Dangerous-state handling now distinguishes `access_denied` and `expired_token` from transport errors. (#73290) Thanks @indierawk2k2.
|
||||
- Installer/Linux: warn before switching an unwritable npm global prefix to `~/.npm-global`, then tell users to run future global updates with `npm i -g openclaw@latest` without `sudo` so npm keeps using the redirected user prefix. Fixes #44365; carries forward #50479. Thanks @Sayeem3051.
|
||||
- Gateway/plugins: enable the native `require()` fast path on Windows for bundled plugin modules so plugin loading uses `require()` instead of Jiti's transform pipeline, reducing startup from ~39s to ~2s on typical 6-plugin setups. Fixes #68656. (#74173) Thanks @galiniliev.
|
||||
|
||||
## 2026.4.27
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ function getJiti(modulePath: string) {
|
||||
}
|
||||
|
||||
function loadPluginDoctorContractModule(modulePath: string): PluginDoctorContractModule {
|
||||
const nativeModule = tryNativeRequireJavaScriptModule(modulePath);
|
||||
const nativeModule = tryNativeRequireJavaScriptModule(modulePath, { allowWindows: true });
|
||||
if (nativeModule.ok) {
|
||||
return nativeModule.moduleExport as PluginDoctorContractModule;
|
||||
}
|
||||
|
||||
@@ -208,13 +208,14 @@ describe("getCachedPluginJitiLoader", () => {
|
||||
const jitiLoader = vi.fn();
|
||||
const createJiti = vi.fn(() => jitiLoader);
|
||||
vi.doMock("jiti", () => ({ createJiti }));
|
||||
const nativeStub = vi.fn((target: string) => ({
|
||||
ok: true as const,
|
||||
moduleExport: { loadedFrom: target },
|
||||
}));
|
||||
vi.doMock("./native-module-require.js", () => ({
|
||||
isJavaScriptModulePath: (p: string) =>
|
||||
p.endsWith(".js") || p.endsWith(".mjs") || p.endsWith(".cjs"),
|
||||
tryNativeRequireJavaScriptModule: (target: string) => ({
|
||||
ok: true,
|
||||
moduleExport: { loadedFrom: target },
|
||||
}),
|
||||
tryNativeRequireJavaScriptModule: nativeStub,
|
||||
}));
|
||||
const { getCachedPluginJitiLoader } = await importFreshModule<
|
||||
typeof import("./jiti-loader-cache.js")
|
||||
@@ -233,6 +234,10 @@ describe("getCachedPluginJitiLoader", () => {
|
||||
// jiti is created eagerly, but its loader must NOT be invoked for .js
|
||||
// targets that `tryNativeRequireJavaScriptModule` resolves.
|
||||
expect(jitiLoader).not.toHaveBeenCalled();
|
||||
// allowWindows must be passed so the native fast path works on Windows too.
|
||||
expect(nativeStub).toHaveBeenCalledWith("/repo/dist/extensions/demo/api.js", {
|
||||
allowWindows: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("falls back to jiti when the native-require helper declines", async () => {
|
||||
|
||||
@@ -105,7 +105,7 @@ export function getCachedPluginJitiLoader(params: {
|
||||
// async-module fallbacks `tryNativeRequireJavaScriptModule` declines to
|
||||
// handle.
|
||||
const loader = ((target: string, ...rest: unknown[]) => {
|
||||
const native = tryNativeRequireJavaScriptModule(target);
|
||||
const native = tryNativeRequireJavaScriptModule(target, { allowWindows: true });
|
||||
if (native.ok) {
|
||||
return native.moduleExport;
|
||||
}
|
||||
|
||||
@@ -1316,6 +1316,8 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
|
||||
let selectedMemoryPluginId: string | null = null;
|
||||
let memorySlotMatched = false;
|
||||
const dreamingEngineId = resolveDreamingSidecarEngineId({ cfg, memorySlot });
|
||||
const pluginLoadStartMs = performance.now();
|
||||
let pluginLoadAttemptCount = 0;
|
||||
|
||||
for (const candidate of orderedCandidates) {
|
||||
const manifestRecord = manifestByRoot.get(candidate.rootDir);
|
||||
@@ -1702,6 +1704,8 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
|
||||
// Track the plugin as imported once module evaluation begins. Top-level
|
||||
// code may have already executed even if evaluation later throws.
|
||||
recordImportedPluginId(record.id);
|
||||
pluginLoadAttemptCount++;
|
||||
logger.debug?.(`[plugins] loading ${record.id} from ${safeSource}`);
|
||||
mod = withProfile(
|
||||
{ pluginId: record.id, source: safeSource },
|
||||
registrationMode,
|
||||
@@ -2065,6 +2069,13 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
|
||||
}
|
||||
}
|
||||
|
||||
const pluginLoadElapsedMs = performance.now() - pluginLoadStartMs;
|
||||
if (pluginLoadAttemptCount > 0) {
|
||||
logger.debug?.(
|
||||
`[plugins] loaded ${registry.plugins.length} plugin(s) (${pluginLoadAttemptCount} attempted) in ${pluginLoadElapsedMs.toFixed(1)}ms`,
|
||||
);
|
||||
}
|
||||
|
||||
// Scoped snapshot loads may intentionally omit the configured memory plugin, so only
|
||||
// emit the missing-memory diagnostic for full registry loads.
|
||||
if (!onlyPluginIdSet && typeof memorySlot === "string" && !memorySlotMatched) {
|
||||
|
||||
@@ -111,7 +111,7 @@ afterEach(() => {
|
||||
});
|
||||
|
||||
describe("bundled plugin public surface loader", () => {
|
||||
it("uses transpiled Jiti import for Windows dist public artifact loads", async () => {
|
||||
it("uses native Jiti import for Windows dist public artifact loads", async () => {
|
||||
const createJiti = vi.fn(() => vi.fn(() => ({ marker: "windows-dist-ok" })));
|
||||
vi.doMock("jiti", () => ({
|
||||
createJiti,
|
||||
@@ -140,7 +140,7 @@ describe("bundled plugin public surface loader", () => {
|
||||
expect(createJiti).toHaveBeenCalledWith(
|
||||
expect.any(String),
|
||||
expect.objectContaining({
|
||||
tryNative: false,
|
||||
tryNative: true,
|
||||
}),
|
||||
);
|
||||
} finally {
|
||||
|
||||
@@ -952,7 +952,7 @@ describe("plugin sdk alias helpers", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("disables native Jiti loads on Windows for built JavaScript entries", () => {
|
||||
it("enables native Jiti loads on Windows for built JavaScript entries", () => {
|
||||
const originalPlatform = process.platform;
|
||||
Object.defineProperty(process, "platform", {
|
||||
configurable: true,
|
||||
@@ -960,9 +960,9 @@ describe("plugin sdk alias helpers", () => {
|
||||
});
|
||||
|
||||
try {
|
||||
expect(shouldPreferNativeJiti("/repo/dist/plugins/runtime/index.js")).toBe(false);
|
||||
expect(shouldPreferNativeJiti("/repo/dist/plugins/runtime/index.js")).toBe(true);
|
||||
expect(shouldPreferNativeJiti(`/repo/${bundledDistPluginFile("browser", "index.js")}`)).toBe(
|
||||
false,
|
||||
true,
|
||||
);
|
||||
} finally {
|
||||
Object.defineProperty(process, "platform", {
|
||||
@@ -972,7 +972,7 @@ describe("plugin sdk alias helpers", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps plugin loader dist shortcuts on transpiled Jiti on Windows", () => {
|
||||
it("keeps plugin loader dist shortcuts on native Jiti on Windows for JS entries", () => {
|
||||
const originalPlatform = process.platform;
|
||||
Object.defineProperty(process, "platform", {
|
||||
configurable: true,
|
||||
@@ -984,7 +984,7 @@ describe("plugin sdk alias helpers", () => {
|
||||
resolvePluginLoaderJitiTryNative(`/repo/${bundledDistPluginFile("browser", "index.js")}`, {
|
||||
preferBuiltDist: true,
|
||||
}),
|
||||
).toBe(false);
|
||||
).toBe(true);
|
||||
expect(
|
||||
resolvePluginLoaderJitiTryNative(`/repo/${bundledDistPluginFile("browser", "helper.ts")}`, {
|
||||
preferBuiltDist: true,
|
||||
|
||||
@@ -695,7 +695,7 @@ export function buildPluginLoaderJitiOptions(aliasMap: Record<string, string>) {
|
||||
|
||||
function supportsNativeJitiRuntime(): boolean {
|
||||
const versions = process.versions as { bun?: string };
|
||||
return typeof versions.bun !== "string" && process.platform !== "win32";
|
||||
return typeof versions.bun !== "string";
|
||||
}
|
||||
|
||||
function isBundledPluginDistModulePath(modulePath: string): boolean {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
export function shouldExpectNativeJitiForJavaScriptTestRuntime(): boolean {
|
||||
return (
|
||||
typeof (process.versions as { bun?: string }).bun !== "string" && process.platform !== "win32"
|
||||
);
|
||||
return typeof (process.versions as { bun?: string }).bun !== "string";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user