fix: narrow mattermost setup entry seam

This commit is contained in:
Peter Steinberger
2026-04-06 14:40:53 +01:00
parent 55f18f67e2
commit 00dcc1744e
4 changed files with 127 additions and 32 deletions

View File

@@ -0,0 +1,38 @@
import { execFile } from "node:child_process";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { promisify } from "node:util";
import { describe, expect, it } from "vitest";
const execFileAsync = promisify(execFile);
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
const importEnv = {
HOME: process.env.HOME,
NODE_OPTIONS: process.env.NODE_OPTIONS,
NODE_PATH: process.env.NODE_PATH,
PATH: process.env.PATH,
TERM: process.env.TERM,
} satisfies NodeJS.ProcessEnv;
describe("mattermost bundled api seam", () => {
it("loads the narrow channel plugin api in direct smoke", async () => {
const { stdout } = await execFileAsync(
process.execPath,
[
"--import",
"tsx",
"-e",
'const mod = await import("./extensions/mattermost/channel-plugin-api.ts"); process.stdout.write(JSON.stringify({keys:Object.keys(mod).sort(), id: mod.mattermostPlugin.id, setupId: mod.mattermostSetupPlugin.id}));',
],
{
cwd: repoRoot,
env: importEnv,
timeout: 40_000,
},
);
expect(stdout).toBe(
'{"keys":["mattermostPlugin","mattermostSetupPlugin"],"id":"mattermost","setupId":"mattermost"}',
);
}, 45_000);
});

View File

@@ -1,33 +1,4 @@
import { loadBundledEntryExportSync } from "openclaw/plugin-sdk/channel-entry-contract";
type ChannelPluginModule = typeof import("./channel-plugin-runtime.js");
function createLazyObjectValue<T extends object>(load: () => T): T {
return new Proxy({} as T, {
get(_target, property, receiver) {
return Reflect.get(load(), property, receiver);
},
has(_target, property) {
return property in load();
},
ownKeys() {
return Reflect.ownKeys(load());
},
getOwnPropertyDescriptor(_target, property) {
const descriptor = Object.getOwnPropertyDescriptor(load(), property);
return descriptor ? { ...descriptor, configurable: true } : undefined;
},
});
}
function loadChannelPluginModule(): ChannelPluginModule {
return loadBundledEntryExportSync<ChannelPluginModule>(import.meta.url, {
specifier: "./channel-plugin-runtime.js",
});
}
// Keep bundled channel entry imports narrow so bootstrap/discovery paths do
// not drag the broader Mattermost helper surfaces into lightweight plugin loads.
export const mattermostPlugin: ChannelPluginModule["mattermostPlugin"] = createLazyObjectValue(
() => loadChannelPluginModule().mattermostPlugin as object,
) as ChannelPluginModule["mattermostPlugin"];
export { mattermostPlugin } from "./channel-plugin-runtime.js";
export { mattermostSetupPlugin } from "./src/channel.setup.js";

View File

@@ -4,6 +4,6 @@ export default defineBundledChannelSetupEntry({
importMetaUrl: import.meta.url,
plugin: {
specifier: "./channel-plugin-api.js",
exportName: "mattermostPlugin",
exportName: "mattermostSetupPlugin",
},
});

View File

@@ -0,0 +1,86 @@
import { describeAccountSnapshot } from "openclaw/plugin-sdk/account-helpers";
import { formatNormalizedAllowFromEntries } from "openclaw/plugin-sdk/allow-from";
import {
adaptScopedAccountAccessor,
createScopedChannelConfigAdapter,
} from "openclaw/plugin-sdk/channel-config-helpers";
import type { ChannelPlugin } from "./channel-api.js";
import { MattermostChannelConfigSchema } from "./config-surface.js";
import {
listMattermostAccountIds,
resolveDefaultMattermostAccountId,
resolveMattermostAccount,
type ResolvedMattermostAccount,
} from "./mattermost/accounts.js";
import { mattermostSetupAdapter } from "./setup-core.js";
import { mattermostSetupWizard } from "./setup-surface.js";
const mattermostMeta = {
id: "mattermost",
label: "Mattermost",
selectionLabel: "Mattermost (plugin)",
detailLabel: "Mattermost Bot",
docsPath: "/channels/mattermost",
docsLabel: "mattermost",
blurb: "self-hosted Slack-style chat; install the plugin to enable.",
systemImage: "bubble.left.and.bubble.right",
order: 65,
quickstartAllowFrom: true,
} as const;
function formatAllowEntry(entry: string): string {
const trimmed = entry.trim();
if (!trimmed) {
return "";
}
if (trimmed.startsWith("@")) {
const username = trimmed.slice(1).trim();
return username ? `@${username.toLowerCase()}` : "";
}
return trimmed.replace(/^(mattermost|user):/i, "").toLowerCase();
}
const mattermostConfigAdapter = createScopedChannelConfigAdapter<ResolvedMattermostAccount>({
sectionKey: "mattermost",
listAccountIds: listMattermostAccountIds,
resolveAccount: adaptScopedAccountAccessor(resolveMattermostAccount),
defaultAccountId: resolveDefaultMattermostAccountId,
clearBaseFields: ["botToken", "baseUrl", "name"],
resolveAllowFrom: (account) => account.config.allowFrom,
formatAllowFrom: (allowFrom) =>
formatNormalizedAllowFromEntries({
allowFrom,
normalizeEntry: formatAllowEntry,
}),
});
export const mattermostSetupPlugin: ChannelPlugin<ResolvedMattermostAccount> = {
id: "mattermost",
meta: {
...mattermostMeta,
},
capabilities: {
chatTypes: ["direct", "channel", "group", "thread"],
reactions: true,
threads: true,
media: true,
nativeCommands: true,
},
reload: { configPrefixes: ["channels.mattermost"] },
configSchema: MattermostChannelConfigSchema,
config: {
...mattermostConfigAdapter,
isConfigured: (account) => Boolean(account.botToken && account.baseUrl),
describeAccount: (account) =>
describeAccountSnapshot({
account,
configured: Boolean(account.botToken && account.baseUrl),
extra: {
botTokenSource: account.botTokenSource,
baseUrl: account.baseUrl,
},
}),
},
setup: mattermostSetupAdapter,
setupWizard: mattermostSetupWizard,
};