Build: mirror Matrix crypto WASM runtime deps (#57163)

Merged via squash.

Prepared head SHA: b3aeb9d08a
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
This commit is contained in:
Gustavo Madeira Santana 2026-03-29 15:57:28 -04:00 committed by GitHub
parent 82f04ced27
commit dc192d7b2f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 357 additions and 15 deletions

View File

@ -51,6 +51,7 @@ Docs: https://docs.openclaw.ai
- Browser/plugins: auto-enable the bundled browser plugin when browser config or browser tool policy already references it, and show a clearer CLI error when `plugins.allow` excludes `browser`.
- Matrix/plugin loading: ship and source-load the crypto bootstrap runtime sidecar correctly so current `main` stops warning about failed Matrix bootstrap loads and `matrix/index` plugin-id mismatches on every invocation. (#53298) thanks @keithce.
- iOS/Live Activities: mark the `ActivityKit` import in `LiveActivityManager.swift` as `@preconcurrency` so Xcode 26.4 / Swift 6 builds stop failing on strict concurrency checks. (#57180) Thanks @ngutman.
- Plugins/Matrix: mirror the Matrix crypto WASM runtime dependency into the root packaged install and enforce root/plugin dependency parity so bundled Matrix E2EE crypto resolves correctly in shipped builds. (#57163) Thanks @gumadeiras.
## 2026.3.28

View File

@ -5,6 +5,7 @@
"type": "module",
"dependencies": {
"@matrix-org/matrix-sdk-crypto-nodejs": "^0.4.0",
"@matrix-org/matrix-sdk-crypto-wasm": "18.0.0",
"fake-indexeddb": "^6.2.5",
"markdown-it": "14.1.1",
"matrix-js-sdk": "41.2.0",
@ -44,9 +45,9 @@
},
"releaseChecks": {
"rootDependencyMirrorAllowlist": [
"@matrix-org/matrix-sdk-crypto-wasm",
"@matrix-org/matrix-sdk-crypto-nodejs",
"matrix-js-sdk",
"music-metadata"
"matrix-js-sdk"
]
}
}

View File

@ -5,7 +5,11 @@ import path from "node:path";
import { fileURLToPath } from "node:url";
import type { RuntimeEnv } from "../runtime-api.js";
const REQUIRED_MATRIX_PACKAGES = ["matrix-js-sdk", "@matrix-org/matrix-sdk-crypto-nodejs"];
const REQUIRED_MATRIX_PACKAGES = [
"matrix-js-sdk",
"@matrix-org/matrix-sdk-crypto-nodejs",
"@matrix-org/matrix-sdk-crypto-wasm",
];
type MatrixCryptoRuntimeDeps = {
requireFn?: (id: string) => unknown;
@ -184,11 +188,11 @@ export async function ensureMatrixSdkInstalled(params: {
const confirm = params.confirm;
if (confirm) {
const ok = await confirm(
"Matrix requires matrix-js-sdk and @matrix-org/matrix-sdk-crypto-nodejs. Install now?",
"Matrix requires matrix-js-sdk, @matrix-org/matrix-sdk-crypto-nodejs, and @matrix-org/matrix-sdk-crypto-wasm. Install now?",
);
if (!ok) {
throw new Error(
"Matrix requires matrix-js-sdk and @matrix-org/matrix-sdk-crypto-nodejs (install dependencies first).",
"Matrix requires matrix-js-sdk, @matrix-org/matrix-sdk-crypto-nodejs, and @matrix-org/matrix-sdk-crypto-wasm (install dependencies first).",
);
}
}

View File

@ -566,7 +566,7 @@ export const matrixOnboardingAdapter: MatrixOnboardingAdapter = {
channel,
configured: false,
statusLines: ['Matrix: set "channels.matrix.defaultAccount" to select a named account'],
selectionHint: !sdkReady ? "install matrix-js-sdk" : "set defaultAccount",
selectionHint: !sdkReady ? "install Matrix deps" : "set defaultAccount",
};
}
const account = resolveMatrixAccount({
@ -580,7 +580,7 @@ export const matrixOnboardingAdapter: MatrixOnboardingAdapter = {
statusLines: [
`Matrix: ${configured ? "configured" : "needs homeserver + access token or password"}`,
],
selectionHint: !sdkReady ? "install matrix-js-sdk" : configured ? "configured" : "needs auth",
selectionHint: !sdkReady ? "install Matrix deps" : configured ? "configured" : "needs auth",
};
},
configure: async ({

View File

@ -1183,6 +1183,7 @@
"@mariozechner/pi-ai": "0.63.2",
"@mariozechner/pi-coding-agent": "0.63.2",
"@mariozechner/pi-tui": "0.63.2",
"@matrix-org/matrix-sdk-crypto-wasm": "18.0.0",
"@modelcontextprotocol/sdk": "1.28.0",
"@mozilla/readability": "^0.6.0",
"@openclaw/plugin-package-contract": "workspace:*",

View File

@ -60,6 +60,9 @@ importers:
'@mariozechner/pi-tui':
specifier: 0.63.2
version: 0.63.2
'@matrix-org/matrix-sdk-crypto-wasm':
specifier: 18.0.0
version: 18.0.0
'@modelcontextprotocol/sdk':
specifier: 1.28.0
version: 1.28.0(zod@4.3.6)
@ -259,6 +262,8 @@ importers:
extensions/anthropic: {}
extensions/anthropic-vertex: {}
extensions/bluebubbles:
devDependencies:
openclaw:
@ -436,6 +441,9 @@ importers:
'@matrix-org/matrix-sdk-crypto-nodejs':
specifier: ^0.4.0
version: 0.4.0
'@matrix-org/matrix-sdk-crypto-wasm':
specifier: 18.0.0
version: 18.0.0
fake-indexeddb:
specifier: ^6.2.5
version: 6.2.5

View File

@ -11,6 +11,7 @@ export type ExtensionPackageJson = {
optionalDependencies?: Record<string, string>;
openclaw?: {
install?: unknown;
releaseChecks?: unknown;
};
};

View File

@ -60,18 +60,95 @@ function collectBundledExtensions(): BundledExtension[] {
});
}
function collectRuntimeDependencySpecs(packageJson: {
dependencies?: Record<string, string>;
optionalDependencies?: Record<string, string>;
}): Map<string, string> {
return new Map([
...Object.entries(packageJson.dependencies ?? {}),
...Object.entries(packageJson.optionalDependencies ?? {}),
]);
}
function checkBundledExtensionMetadata() {
const extensions = collectBundledExtensions();
const manifestErrors = collectBundledExtensionManifestErrors(extensions);
if (manifestErrors.length > 0) {
const rootPackage = JSON.parse(readFileSync(resolve("package.json"), "utf8")) as {
dependencies?: Record<string, string>;
optionalDependencies?: Record<string, string>;
};
const rootRuntimeDeps = collectRuntimeDependencySpecs(rootPackage);
const rootMirrorErrors = collectBundledExtensionRootDependencyMirrorErrors(
extensions,
rootRuntimeDeps,
);
const errors = [...manifestErrors, ...rootMirrorErrors];
if (errors.length > 0) {
console.error("release-check: bundled extension manifest validation failed:");
for (const error of manifestErrors) {
for (const error of errors) {
console.error(` - ${error}`);
}
process.exit(1);
}
}
export function collectBundledExtensionRootDependencyMirrorErrors(
extensions: BundledExtension[],
rootRuntimeDeps: ReadonlyMap<string, string>,
): string[] {
const errors: string[] = [];
for (const extension of extensions) {
const rawReleaseChecks = extension.packageJson.openclaw?.releaseChecks;
const allowlist = (rawReleaseChecks as { rootDependencyMirrorAllowlist?: unknown } | undefined)
?.rootDependencyMirrorAllowlist;
if (allowlist === undefined) {
continue;
}
if (!Array.isArray(allowlist)) {
errors.push(
`bundled extension '${extension.id}' manifest invalid | openclaw.releaseChecks.rootDependencyMirrorAllowlist must be an array`,
);
continue;
}
const extensionRuntimeDeps = collectRuntimeDependencySpecs(extension.packageJson);
for (const entry of allowlist) {
if (typeof entry !== "string" || entry.trim().length === 0) {
errors.push(
`bundled extension '${extension.id}' manifest invalid | openclaw.releaseChecks.rootDependencyMirrorAllowlist entries must be non-empty strings`,
);
continue;
}
const extensionSpec = extensionRuntimeDeps.get(entry);
if (!extensionSpec) {
errors.push(
`bundled extension '${extension.id}' manifest invalid | openclaw.releaseChecks.rootDependencyMirrorAllowlist entry '${entry}' must be declared in extension runtime dependencies`,
);
}
const rootSpec = rootRuntimeDeps.get(entry);
if (!rootSpec) {
errors.push(
`bundled extension '${extension.id}' manifest invalid | openclaw.releaseChecks.rootDependencyMirrorAllowlist entry '${entry}' must be mirrored in root runtime dependencies`,
);
}
if (!extensionSpec || !rootSpec) {
continue;
}
if (extensionSpec !== rootSpec) {
errors.push(
`bundled extension '${extension.id}' manifest invalid | openclaw.releaseChecks.rootDependencyMirrorAllowlist entry '${entry}' must match root runtime dependency version (extension '${extensionSpec}', root '${rootSpec}')`,
);
}
}
}
return errors;
}
function runPackDry(): PackResult[] {
const raw = execSync("npm pack --dry-run --json --ignore-scripts", {
encoding: "utf8",

View File

@ -1,6 +1,9 @@
import { readFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { execFileSync } from "node:child_process";
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { createRequire } from "node:module";
import os from "node:os";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import { describe, expect, it } from "vitest";
import { pluginSdkEntrypoints } from "./entrypoints.js";
@ -56,6 +59,62 @@ function readRootPackageJson(): {
};
}
function readMatrixPackageJson(): {
dependencies?: Record<string, string>;
optionalDependencies?: Record<string, string>;
openclaw?: {
releaseChecks?: {
rootDependencyMirrorAllowlist?: unknown;
};
};
} {
return JSON.parse(readFileSync(resolve(REPO_ROOT, "extensions/matrix/package.json"), "utf8")) as {
dependencies?: Record<string, string>;
optionalDependencies?: Record<string, string>;
openclaw?: {
releaseChecks?: {
rootDependencyMirrorAllowlist?: unknown;
};
};
};
}
function collectRuntimeDependencySpecs(packageJson: {
dependencies?: Record<string, string>;
optionalDependencies?: Record<string, string>;
}): Map<string, string> {
return new Map([
...Object.entries(packageJson.dependencies ?? {}),
...Object.entries(packageJson.optionalDependencies ?? {}),
]);
}
function createRootPackageRequire() {
return createRequire(pathToFileURL(resolve(REPO_ROOT, "package.json")).href);
}
function resolvePackageManagerCommand(name: "npm" | "pnpm"): string {
return process.platform === "win32" ? `${name}.cmd` : name;
}
function packOpenClawToTempDir(packDir: string): string {
const raw = execFileSync(
resolvePackageManagerCommand("npm"),
["pack", "--ignore-scripts", "--json", "--pack-destination", packDir],
{
cwd: REPO_ROOT,
encoding: "utf8",
env: { ...process.env, COREPACK_ENABLE_DOWNLOAD_PROMPT: "0" },
},
);
const parsed = JSON.parse(raw) as Array<{ filename?: string }>;
const filename = parsed[0]?.filename?.trim();
if (!filename) {
throw new Error(`npm pack did not return a filename: ${raw}`);
}
return join(packDir, filename);
}
function readGeneratedFacadeTypeMap(): string {
return readFileSync(
resolve(REPO_ROOT, "src/generated/plugin-sdk-facade-type-map.generated.ts"),
@ -97,10 +156,77 @@ describe("plugin-sdk package contract guardrails", () => {
});
it("mirrors matrix runtime deps needed by the bundled host graph", () => {
const { dependencies = {}, optionalDependencies = {} } = readRootPackageJson();
const rootRuntimeDeps = collectRuntimeDependencySpecs(readRootPackageJson());
const matrixPackageJson = readMatrixPackageJson();
const matrixRuntimeDeps = collectRuntimeDependencySpecs(matrixPackageJson);
const allowlist = matrixPackageJson.openclaw?.releaseChecks?.rootDependencyMirrorAllowlist;
expect(dependencies["matrix-js-sdk"]).toBe("41.2.0");
expect(optionalDependencies["@matrix-org/matrix-sdk-crypto-nodejs"]).toBe("^0.4.0");
expect(Array.isArray(allowlist)).toBe(true);
const matrixRootMirrorAllowlist = allowlist as string[];
expect(matrixRootMirrorAllowlist).toEqual(
expect.arrayContaining(["@matrix-org/matrix-sdk-crypto-wasm"]),
);
for (const dep of matrixRootMirrorAllowlist) {
expect(rootRuntimeDeps.get(dep)).toBe(matrixRuntimeDeps.get(dep));
}
});
it("resolves matrix crypto WASM from the root runtime surface", () => {
const rootRequire = createRootPackageRequire();
expect(rootRequire.resolve("@matrix-org/matrix-sdk-crypto-wasm")).toContain(
"@matrix-org/matrix-sdk-crypto-wasm",
);
});
it("resolves matrix crypto WASM from an installed packed artifact", () => {
const tempRoot = mkdtempSync(join(os.tmpdir(), "openclaw-matrix-wasm-pack-"));
try {
const packDir = join(tempRoot, "pack");
const consumerDir = join(tempRoot, "consumer");
mkdirSync(packDir, { recursive: true });
mkdirSync(consumerDir, { recursive: true });
writeFileSync(
join(consumerDir, "package.json"),
`${JSON.stringify({ name: "matrix-wasm-smoke", private: true }, null, 2)}\n`,
"utf8",
);
const archivePath = packOpenClawToTempDir(packDir);
execFileSync(
resolvePackageManagerCommand("pnpm"),
["add", "--offline", "--ignore-scripts", archivePath],
{
cwd: consumerDir,
encoding: "utf8",
env: { ...process.env, COREPACK_ENABLE_DOWNLOAD_PROMPT: "0" },
stdio: ["ignore", "pipe", "pipe"],
},
);
const installedPackageJsonPath = join(
consumerDir,
"node_modules",
"openclaw",
"package.json",
);
const installedPackageJson = JSON.parse(readFileSync(installedPackageJsonPath, "utf8")) as {
dependencies?: Record<string, string>;
};
const installedRequire = createRequire(pathToFileURL(installedPackageJsonPath).href);
const matrixPackageJson = readMatrixPackageJson();
expect(installedPackageJson.dependencies?.["@matrix-org/matrix-sdk-crypto-wasm"]).toBe(
matrixPackageJson.dependencies?.["@matrix-org/matrix-sdk-crypto-wasm"],
);
expect(installedRequire.resolve("@matrix-org/matrix-sdk-crypto-wasm")).toContain(
"@matrix-org/matrix-sdk-crypto-wasm",
);
} finally {
rmSync(tempRoot, { recursive: true, force: true });
}
});
it("keeps generated facade types on package-style module specifiers", () => {

View File

@ -4,6 +4,7 @@ import { listPluginSdkDistArtifacts } from "../scripts/lib/plugin-sdk-entries.mj
import {
collectAppcastSparkleVersionErrors,
collectBundledExtensionManifestErrors,
collectBundledExtensionRootDependencyMirrorErrors,
collectForbiddenPackPaths,
collectMissingPackPaths,
collectPackUnpackedSizeErrors,
@ -109,6 +110,128 @@ describe("collectBundledExtensionManifestErrors", () => {
});
});
describe("collectBundledExtensionRootDependencyMirrorErrors", () => {
it("flags a non-array mirror allowlist", () => {
expect(
collectBundledExtensionRootDependencyMirrorErrors(
[
{
id: "matrix",
packageJson: {
openclaw: {
releaseChecks: {
rootDependencyMirrorAllowlist: true,
},
},
},
},
],
new Map(),
),
).toEqual([
"bundled extension 'matrix' manifest invalid | openclaw.releaseChecks.rootDependencyMirrorAllowlist must be an array",
]);
});
it("flags mirror entries missing from extension runtime dependencies", () => {
expect(
collectBundledExtensionRootDependencyMirrorErrors(
[
{
id: "matrix",
packageJson: {
dependencies: {
"matrix-js-sdk": "41.2.0",
},
openclaw: {
releaseChecks: {
rootDependencyMirrorAllowlist: ["@matrix-org/matrix-sdk-crypto-wasm"],
},
},
},
},
],
new Map([["@matrix-org/matrix-sdk-crypto-wasm", "18.0.0"]]),
),
).toEqual([
"bundled extension 'matrix' manifest invalid | openclaw.releaseChecks.rootDependencyMirrorAllowlist entry '@matrix-org/matrix-sdk-crypto-wasm' must be declared in extension runtime dependencies",
]);
});
it("flags mirror entries missing from root runtime dependencies", () => {
expect(
collectBundledExtensionRootDependencyMirrorErrors(
[
{
id: "matrix",
packageJson: {
dependencies: {
"@matrix-org/matrix-sdk-crypto-wasm": "18.0.0",
},
openclaw: {
releaseChecks: {
rootDependencyMirrorAllowlist: ["@matrix-org/matrix-sdk-crypto-wasm"],
},
},
},
},
],
new Map(),
),
).toEqual([
"bundled extension 'matrix' manifest invalid | openclaw.releaseChecks.rootDependencyMirrorAllowlist entry '@matrix-org/matrix-sdk-crypto-wasm' must be mirrored in root runtime dependencies",
]);
});
it("flags mirror entries whose root version drifts from the extension", () => {
expect(
collectBundledExtensionRootDependencyMirrorErrors(
[
{
id: "matrix",
packageJson: {
dependencies: {
"@matrix-org/matrix-sdk-crypto-wasm": "18.0.0",
},
openclaw: {
releaseChecks: {
rootDependencyMirrorAllowlist: ["@matrix-org/matrix-sdk-crypto-wasm"],
},
},
},
},
],
new Map([["@matrix-org/matrix-sdk-crypto-wasm", "18.1.0"]]),
),
).toEqual([
"bundled extension 'matrix' manifest invalid | openclaw.releaseChecks.rootDependencyMirrorAllowlist entry '@matrix-org/matrix-sdk-crypto-wasm' must match root runtime dependency version (extension '18.0.0', root '18.1.0')",
]);
});
it("accepts mirror entries declared by both the extension and root package", () => {
expect(
collectBundledExtensionRootDependencyMirrorErrors(
[
{
id: "matrix",
packageJson: {
dependencies: {
"@matrix-org/matrix-sdk-crypto-wasm": "18.0.0",
},
openclaw: {
releaseChecks: {
rootDependencyMirrorAllowlist: ["@matrix-org/matrix-sdk-crypto-wasm"],
},
},
},
},
],
new Map([["@matrix-org/matrix-sdk-crypto-wasm", "18.0.0"]]),
),
).toEqual([]);
});
});
describe("collectForbiddenPackPaths", () => {
it("allows bundled plugin runtime deps under dist/extensions but still blocks other node_modules", () => {
expect(