From 7d343b0b1000cd866c66b7b733f97adb8c2f57c6 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 25 Apr 2026 22:12:28 +0100 Subject: [PATCH] fix(plugins): resolve bundled channel doctor metadata from package root --- CHANGELOG.md | 4 ++ .../bundled-package-channel-metadata.test.ts | 51 +++++++++++++++++++ .../bundled-package-channel-metadata.ts | 14 +---- 3 files changed, 57 insertions(+), 12 deletions(-) create mode 100644 src/plugins/bundled-package-channel-metadata.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index d7633e83ee8..7f165133033 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -63,6 +63,10 @@ Docs: https://docs.openclaw.ai - Windows/native: keep CLI startup and bundled provider plugin loading off Windows ESM raw-path failure paths, fixing native onboarding/install smoke on Node 24. Thanks @steipete. +- Plugins/doctor: read bundled channel doctor capabilities through the same + packaged plugin directory resolver used by plugin loading, so published + installs keep Matrix DM allowlist repairs on `channels.matrix.dm.*` instead + of writing invalid top-level `dmPolicy` keys. Fixes #71757. - Providers/Google: transcode Gemini TTS PCM to Opus for voice-note targets so WhatsApp and other native voice-note replies can play as voice messages. - Plugins/runtime deps: reuse existing external bundled-plugin stage roots when diff --git a/src/plugins/bundled-package-channel-metadata.test.ts b/src/plugins/bundled-package-channel-metadata.test.ts new file mode 100644 index 00000000000..0083b8e4014 --- /dev/null +++ b/src/plugins/bundled-package-channel-metadata.test.ts @@ -0,0 +1,51 @@ +import path from "node:path"; +import { afterEach, describe, expect, it, vi } from "vitest"; +import { cleanupTempDirs, makeTempRepoRoot, writeJsonFile } from "../../test/helpers/temp-repo.js"; + +vi.mock("./bundled-dir.js", () => ({ + resolveBundledPluginsDir: vi.fn(), +})); + +import { resolveBundledPluginsDir } from "./bundled-dir.js"; +import { findBundledPackageChannelMetadata } from "./bundled-package-channel-metadata.js"; + +const tempDirs: string[] = []; + +afterEach(() => { + cleanupTempDirs(tempDirs); + vi.restoreAllMocks(); + vi.mocked(resolveBundledPluginsDir).mockReset(); +}); + +describe("bundled package channel metadata", () => { + it("reads doctor capabilities from the resolved bundled plugin dir", () => { + const root = makeTempRepoRoot(tempDirs, "bpcm-"); + const extensionsRoot = path.join(root, "dist", "extensions"); + writeJsonFile(path.join(extensionsRoot, "matrix", "package.json"), { + name: "@openclaw/matrix", + openclaw: { + channel: { + id: "matrix", + label: "Matrix", + docsPath: "/channels/matrix", + doctorCapabilities: { + dmAllowFromMode: "nestedOnly", + groupModel: "sender", + groupAllowFromFallbackToAllowFrom: false, + warnOnEmptyGroupSenderAllowlist: true, + }, + }, + }, + }); + vi.mocked(resolveBundledPluginsDir).mockReturnValue(extensionsRoot); + + const matrix = findBundledPackageChannelMetadata("matrix"); + + expect(matrix?.doctorCapabilities).toEqual({ + dmAllowFromMode: "nestedOnly", + groupModel: "sender", + groupAllowFromFallbackToAllowFrom: false, + warnOnEmptyGroupSenderAllowlist: true, + }); + }); +}); diff --git a/src/plugins/bundled-package-channel-metadata.ts b/src/plugins/bundled-package-channel-metadata.ts index 7d8694e282e..b2d108059f7 100644 --- a/src/plugins/bundled-package-channel-metadata.ts +++ b/src/plugins/bundled-package-channel-metadata.ts @@ -1,19 +1,12 @@ import fs from "node:fs"; import path from "node:path"; -import { fileURLToPath } from "node:url"; -import { resolveBundledPluginScanDir } from "./bundled-plugin-scan.js"; +import { resolveBundledPluginsDir } from "./bundled-dir.js"; import { getPackageManifestMetadata, type PackageManifest, type PluginPackageChannel, } from "./manifest.js"; -const PACKAGE_ROOT = fileURLToPath(new URL("../..", import.meta.url)); -const CURRENT_MODULE_PATH = fileURLToPath(import.meta.url); -const RUNNING_FROM_BUILT_ARTIFACT = - CURRENT_MODULE_PATH.includes(`${path.sep}dist${path.sep}`) || - CURRENT_MODULE_PATH.includes(`${path.sep}dist-runtime${path.sep}`); - let bundledPackageChannelMetadataCache: readonly PluginPackageChannel[] | undefined; function readPackageManifest(pluginDir: string): PackageManifest | undefined { @@ -32,10 +25,7 @@ export function listBundledPackageChannelMetadata(): readonly PluginPackageChann if (bundledPackageChannelMetadataCache) { return bundledPackageChannelMetadataCache; } - const scanDir = resolveBundledPluginScanDir({ - packageRoot: PACKAGE_ROOT, - runningFromBuiltArtifact: RUNNING_FROM_BUILT_ARTIFACT, - }); + const scanDir = resolveBundledPluginsDir(); if (!scanDir || !fs.existsSync(scanDir)) { bundledPackageChannelMetadataCache = []; return bundledPackageChannelMetadataCache;