fix: prevent matrix-js-sdk plugin load crash (#56273) (thanks @aquaright1)

* Fix matrix-js-sdk multiple entrypoint crash on plugin load

* test(matrix): cover runtime bundle import regression

* fix: prevent matrix-js-sdk plugin load crash (#56273) (thanks @aquaright1)

* fix: widen matrix-js-sdk bundle import guard (#56273) (thanks @aquaright1)

---------

Co-authored-by: Kenny Xie <kennyxie@Mac-mini-von-Kenny.local>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
This commit is contained in:
Kenny Xie 2026-03-28 01:57:54 -07:00 committed by GitHub
parent 3c0cf26e16
commit cb5afdf108
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 48 additions and 9 deletions

View File

@ -74,6 +74,7 @@ Docs: https://docs.openclaw.ai
- Feishu: use the original message `create_time` instead of `Date.now()` for inbound timestamps so offline-retried messages carry the correct authoring time, preventing mis-targeted agent actions on stale instructions. (#52809) Thanks @schumilin.
- Control UI/Skills: open skill detail dialogs with the browser modal lifecycle so clicking a skill row keeps the panel centered instead of rendering it off-screen at the bottom of the page.
- Matrix/replies: include quoted poll question/options in inbound reply context so the agent sees the original poll content when users reply to Matrix poll messages. (#55056) Thanks @alberthild.
- Matrix/plugins: keep plugin bootstrap from crashing when built runtime mixes bare and deep `matrix-js-sdk` entrypoints, so unrelated channels do not get taken down during plugin load. (#56273) Thanks @aquaright1.
- Agents/sandbox: honor `tools.sandbox.tools.alsoAllow`, let explicit sandbox re-allows remove matching built-in default-deny tools, and keep sandbox explain/error guidance aligned with the effective sandbox tool policy. (#54492) Thanks @ngutman.
- Agents/sandbox: make blocked-tool guidance glob-aware again, redact/sanitize session-specific explain hints for safer copy-paste, and avoid leaking control-character session keys in those hints. (#54684) Thanks @ngutman.
- Agents/compaction: trigger timeout recovery compaction before retrying high-context LLM timeouts so embedded runs stop repeating oversized requests. (#46417) thanks @joeykrug.

View File

@ -2,13 +2,13 @@ import { readFileSync } from "node:fs";
import fs from "node:fs/promises";
import {
Category,
MemoryStore,
SyncAccumulator,
type ISyncData,
type IRooms,
type ISyncResponse,
type IStoredClientOpts,
} from "matrix-js-sdk";
import { MemoryStore } from "matrix-js-sdk/lib/store/memory.js";
import { SyncAccumulator } from "matrix-js-sdk/lib/sync-accumulator.js";
} from "matrix-js-sdk/lib/matrix.js";
import { writeJsonFileAtomically } from "../../runtime-api.js";
import { createAsyncLock } from "../async-lock.js";
import { LogService } from "../sdk/logger.js";

View File

@ -175,8 +175,8 @@ function createMatrixJsClientStub(): MatrixJsClientStub {
let matrixJsClient = createMatrixJsClientStub();
let lastCreateClientOpts: Record<string, unknown> | null = null;
vi.mock("matrix-js-sdk", async (importOriginal) => {
const actual = await importOriginal<typeof import("matrix-js-sdk")>();
vi.mock("matrix-js-sdk/lib/matrix.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("matrix-js-sdk/lib/matrix.js")>();
return {
...actual,
ClientEvent: { Event: "event", Room: "Room" },

View File

@ -8,7 +8,7 @@ import {
createClient as createMatrixJsClient,
type MatrixClient as MatrixJsClient,
type MatrixEvent,
} from "matrix-js-sdk";
} from "matrix-js-sdk/lib/matrix.js";
import { VerificationMethod } from "matrix-js-sdk/lib/types.js";
import { KeyedAsyncQueue } from "openclaw/plugin-sdk/core";
import type { SsrFPolicy } from "../runtime-api.js";

View File

@ -1,5 +1,5 @@
import { MatrixEventEvent, type MatrixEvent } from "matrix-js-sdk";
import { CryptoEvent } from "matrix-js-sdk/lib/crypto-api/CryptoEvent.js";
import { MatrixEventEvent, type MatrixEvent } from "matrix-js-sdk/lib/matrix.js";
import { LogService, noop } from "./logger.js";
type MatrixDecryptIfNeededClient = {

View File

@ -8,7 +8,28 @@ import { buildPluginSdkEntrySources, pluginSdkEntrypoints } from "./entrypoints.
const require = createRequire(import.meta.url);
const tsdownModuleUrl = pathToFileURL(require.resolve("tsdown")).href;
const bundledRepresentativeEntrypoints = ["matrix-runtime-heavy"] as const;
const bundledCoverageEntrySources = buildPluginSdkEntrySources(bundledRepresentativeEntrypoints);
const matrixRuntimeCoverageEntries = {
"matrix-runtime-sdk": "extensions/matrix/src/matrix/sdk.ts",
} as const;
const bundledCoverageEntrySources = {
...buildPluginSdkEntrySources(bundledRepresentativeEntrypoints),
...matrixRuntimeCoverageEntries,
};
const bareMatrixSdkImportPattern = /(?:from|require|import)\s*\(?\s*["']matrix-js-sdk["']/;
async function listBuiltJsFiles(rootDir: string): Promise<string[]> {
const entries = await fs.readdir(rootDir, { withFileTypes: true });
const nested = await Promise.all(
entries.map(async (entry) => {
const entryPath = path.join(rootDir, entry.name);
if (entry.isDirectory()) {
return await listBuiltJsFiles(entryPath);
}
return entry.isFile() && entry.name.endsWith(".js") ? [entryPath] : [];
}),
);
return nested.flat();
}
describe("plugin-sdk bundled exports", () => {
it("emits importable bundled subpath entries", { timeout: 120_000 }, async () => {
@ -34,7 +55,9 @@ describe("plugin-sdk bundled exports", () => {
},
// Full plugin-sdk coverage belongs to `pnpm build`, package contract
// guardrails, and `subpaths.test.ts`. This file only keeps the expensive
// bundler path honest across representative entrypoint families.
// bundler path honest across representative entrypoint families plus the
// Matrix SDK runtime import surface that historically crashed plugin
// loading when bare and deep SDK entrypoints mixed.
entry: bundledCoverageEntrySources,
env: { NODE_ENV: "production" },
fixedExtension: false,
@ -49,6 +72,21 @@ describe("plugin-sdk bundled exports", () => {
await expect(fs.stat(path.join(outDir, `${entry}.js`))).resolves.toBeTruthy();
}),
);
await Promise.all(
Object.keys(matrixRuntimeCoverageEntries).map(async (entry) => {
await expect(fs.stat(path.join(outDir, `${entry}.js`))).resolves.toBeTruthy();
}),
);
const builtJsFiles = await listBuiltJsFiles(outDir);
const filesWithBareMatrixSdkImports = (
await Promise.all(
builtJsFiles.map(async (filePath) => {
const contents = await fs.readFile(filePath, "utf8");
return bareMatrixSdkImportPattern.test(contents) ? filePath : null;
}),
)
).filter((filePath): filePath is string => filePath !== null);
expect(filesWithBareMatrixSdkImports).toEqual([]);
// Export list and package-specifier coverage already live in
// package-contract-guardrails.test.ts and subpaths.test.ts. Keep this file