From cb5afdf108fe3779c09d07f960cb189783cc9191 Mon Sep 17 00:00:00 2001 From: Kenny Xie <77253505+aquaright1@users.noreply.github.com> Date: Sat, 28 Mar 2026 01:57:54 -0700 Subject: [PATCH] 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 Co-authored-by: Ayaan Zaidi --- CHANGELOG.md | 1 + .../src/matrix/client/file-sync-store.ts | 6 +-- extensions/matrix/src/matrix/sdk.test.ts | 4 +- extensions/matrix/src/matrix/sdk.ts | 2 +- .../matrix/src/matrix/sdk/decrypt-bridge.ts | 2 +- src/plugin-sdk/index.bundle.test.ts | 42 ++++++++++++++++++- 6 files changed, 48 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5deddada4a..ad67c5d6694 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/extensions/matrix/src/matrix/client/file-sync-store.ts b/extensions/matrix/src/matrix/client/file-sync-store.ts index 4ead73c9abf..ef69ed19a54 100644 --- a/extensions/matrix/src/matrix/client/file-sync-store.ts +++ b/extensions/matrix/src/matrix/client/file-sync-store.ts @@ -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"; diff --git a/extensions/matrix/src/matrix/sdk.test.ts b/extensions/matrix/src/matrix/sdk.test.ts index df43e031cb3..6acc3b71191 100644 --- a/extensions/matrix/src/matrix/sdk.test.ts +++ b/extensions/matrix/src/matrix/sdk.test.ts @@ -175,8 +175,8 @@ function createMatrixJsClientStub(): MatrixJsClientStub { let matrixJsClient = createMatrixJsClientStub(); let lastCreateClientOpts: Record | null = null; -vi.mock("matrix-js-sdk", async (importOriginal) => { - const actual = await importOriginal(); +vi.mock("matrix-js-sdk/lib/matrix.js", async (importOriginal) => { + const actual = await importOriginal(); return { ...actual, ClientEvent: { Event: "event", Room: "Room" }, diff --git a/extensions/matrix/src/matrix/sdk.ts b/extensions/matrix/src/matrix/sdk.ts index 2d92cc77c27..de883b519b1 100644 --- a/extensions/matrix/src/matrix/sdk.ts +++ b/extensions/matrix/src/matrix/sdk.ts @@ -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"; diff --git a/extensions/matrix/src/matrix/sdk/decrypt-bridge.ts b/extensions/matrix/src/matrix/sdk/decrypt-bridge.ts index 1ca35993e91..411a09169e1 100644 --- a/extensions/matrix/src/matrix/sdk/decrypt-bridge.ts +++ b/extensions/matrix/src/matrix/sdk/decrypt-bridge.ts @@ -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 = { diff --git a/src/plugin-sdk/index.bundle.test.ts b/src/plugin-sdk/index.bundle.test.ts index f364aac1d30..77955017c41 100644 --- a/src/plugin-sdk/index.bundle.test.ts +++ b/src/plugin-sdk/index.bundle.test.ts @@ -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 { + 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