From 48ebed3ed380ca8c214ccc82ec04e055d12534a9 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 27 Apr 2026 12:35:02 +0100 Subject: [PATCH] fix(plugins): normalize bundled sidecar jiti imports --- CHANGELOG.md | 1 + src/plugin-sdk/channel-entry-contract.test.ts | 44 +++++++++++++++++++ src/plugin-sdk/channel-entry-contract.ts | 5 ++- 3 files changed, 48 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 21cf043dea4..d7d7369f55a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ Docs: https://docs.openclaw.ai ### Fixes - 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. - Agents/fallback: classify internal live-session model switch conflicts as unknown fallback failures instead of provider overloads, preventing local vLLM endpoints from receiving misleading overloaded cooldowns. Refs #63229. Thanks @clawdia-lobster. - Control UI: keep session-specific assistant identity loads authoritative after WebSocket connect, so non-main agent chat sessions do not show the main agent name in the header after bootstrap refreshes. Fixes #72776. Thanks @rockytian-top. diff --git a/src/plugin-sdk/channel-entry-contract.test.ts b/src/plugin-sdk/channel-entry-contract.test.ts index 429fafee6d5..293e9d8ca3e 100644 --- a/src/plugin-sdk/channel-entry-contract.test.ts +++ b/src/plugin-sdk/channel-entry-contract.test.ts @@ -265,6 +265,50 @@ describe("loadBundledEntryExportSync", () => { } }); + it("normalizes Windows absolute sidecar paths before Jiti loads them", async () => { + const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-channel-entry-contract-")); + tempDirs.push(tempRoot); + const openedFdPath = path.join(tempRoot, "opened"); + fs.writeFileSync(openedFdPath, "opened\n", "utf8"); + const jitiLoad = vi.fn(() => ({ load: 42 })); + const createJiti = vi.fn(() => jitiLoad); + vi.doMock("jiti", () => ({ + createJiti, + })); + vi.doMock("../infra/boundary-file-read.js", () => ({ + openBoundaryFileSync: () => ({ + ok: true, + path: "C:\\Users\\alice\\openclaw\\dist\\extensions\\feishu\\helper.ts", + fd: fs.openSync(openedFdPath, "r"), + }), + })); + const platformSpy = vi.spyOn(process, "platform", "get").mockReturnValue("win32"); + + try { + const channelEntryContract = await importFreshModule< + typeof import("./channel-entry-contract.js") + >(import.meta.url, "./channel-entry-contract.js?scope=windows-safe-jiti-path"); + + expect( + channelEntryContract.loadBundledEntryExportSync( + "file:///C:/Users/alice/openclaw/dist/extensions/feishu/index.js", + { + specifier: "./helper.ts", + exportName: "load", + }, + { installRuntimeDeps: false }, + ), + ).toBe(42); + expect(jitiLoad).toHaveBeenCalledWith( + "file:///C:/Users/alice/openclaw/dist/extensions/feishu/helper.ts", + ); + } finally { + platformSpy.mockRestore(); + vi.doUnmock("../infra/boundary-file-read.js"); + vi.doUnmock("jiti"); + } + }); + it("loads packaged telegram setup sidecars from dist-facing api modules", () => { const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-channel-entry-contract-")); tempDirs.push(tempRoot); diff --git a/src/plugin-sdk/channel-entry-contract.ts b/src/plugin-sdk/channel-entry-contract.ts index 5c732a049ad..309eb13f201 100644 --- a/src/plugin-sdk/channel-entry-contract.ts +++ b/src/plugin-sdk/channel-entry-contract.ts @@ -24,6 +24,7 @@ import { import type { PluginRuntime } from "../plugins/runtime/types.js"; import { resolveLoaderPackageRoot } from "../plugins/sdk-alias.js"; import type { AnyAgentTool, OpenClawPluginApi, PluginCommandContext } from "../plugins/types.js"; +import { toSafeImportPath } from "../shared/import-specifier.js"; import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js"; export type { AnyAgentTool, OpenClawPluginApi, PluginCommandContext }; @@ -370,12 +371,12 @@ function loadBundledEntryModuleSync( } catch { const jiti = getJiti(modulePath); getJitiEndMs = profile ? performance.now() : 0; - loaded = jiti(modulePath); + loaded = jiti(toSafeImportPath(modulePath)); } } else { const jiti = getJiti(modulePath); getJitiEndMs = profile ? performance.now() : 0; - loaded = jiti(modulePath); + loaded = jiti(toSafeImportPath(modulePath)); } if (profile) { const endMs = performance.now();