From 13636c4521911d64f226a16b82fa9f2f925ed32b Mon Sep 17 00:00:00 2001 From: Gustavo Madeira Santana Date: Tue, 21 Apr 2026 12:50:53 -0400 Subject: [PATCH] perf(matrix): narrow register-time runtime surface (#69782) Merged via squash. Prepared head SHA: ec32828b52af8ac062e8b15b94420fed6e5c4232 Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com> Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com> Reviewed-by: @gumadeiras --- CHANGELOG.md | 1 + extensions/matrix/index.test.ts | 3 +- extensions/matrix/index.ts | 2 +- extensions/matrix/runtime-setter-api.ts | 3 + extensions/matrix/setup-entry.ts | 2 +- extensions/matrix/src/actions.ts | 20 +-- extensions/matrix/src/channel.ts | 45 +++++- extensions/matrix/src/onboarding.ts | 6 +- extensions/matrix/src/setup-core.test.ts | 137 +++++++++++++++++- extensions/matrix/src/setup-core.ts | 96 ++++++++++++ extensions/matrix/src/setup-dm-policy.ts | 15 ++ src/plugins/bundled-plugin-metadata.test.ts | 7 + .../plugin-sdk-runtime-api-guardrails.test.ts | 11 ++ 13 files changed, 325 insertions(+), 23 deletions(-) create mode 100644 extensions/matrix/runtime-setter-api.ts create mode 100644 extensions/matrix/src/setup-dm-policy.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index fcf6ad60478..37975a1e8ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Docs: https://docs.openclaw.ai - Onboard/wizard: simplify the security disclaimer copy (drop the yellow banner and warning icon in favor of plain-prose paragraphs), and flip remaining onboarding pickers with long dynamic option lists to searchable autocompletes (search provider, plugin configure, model provider filter). - Ollama/onboard: populate the cloud-only model list from `ollama.com/api/tags` so `openclaw onboard` reflects the live cloud catalog instead of a static three-model seed; cap the discovered list at 500 and fall back to the previous hardcoded suggestions when ollama.com is unreachable or returns no models. (#68463) Thanks @BruceMacD. +- Matrix/startup: narrow Matrix runtime registration and defer setup/doctor surfaces so cold plugin registration spends about 1.8s less in `setChannelRuntime`. (#69782) Thanks @gumadeiras. ### Fixes diff --git a/extensions/matrix/index.test.ts b/extensions/matrix/index.test.ts index 5089d266ebd..f1a9cc70833 100644 --- a/extensions/matrix/index.test.ts +++ b/extensions/matrix/index.test.ts @@ -22,7 +22,7 @@ vi.mock("./src/cli.js", () => { }); vi.mock("./plugin-entry.handlers.runtime.js", () => runtimeMocks); -vi.mock("./runtime-api.js", () => ({ setMatrixRuntime: runtimeMocks.setMatrixRuntime })); +vi.mock("./runtime-setter-api.js", () => ({ setMatrixRuntime: runtimeMocks.setMatrixRuntime })); describe("matrix plugin", () => { it("registers matrix CLI through a descriptor-backed lazy registrar", async () => { @@ -66,6 +66,7 @@ describe("matrix plugin", () => { expect(entry.kind).toBe("bundled-channel-entry"); expect(entry.id).toBe("matrix"); expect(entry.name).toBe("Matrix"); + expect(entry.setChannelRuntime).toEqual(expect.any(Function)); }); it("registers subagent lifecycle hooks during full runtime registration", () => { diff --git a/extensions/matrix/index.ts b/extensions/matrix/index.ts index 6747f244dbc..6857cc6f579 100644 --- a/extensions/matrix/index.ts +++ b/extensions/matrix/index.ts @@ -77,7 +77,7 @@ export default defineBundledChannelEntry({ exportName: "channelSecrets", }, runtime: { - specifier: "./runtime-api.js", + specifier: "./runtime-setter-api.js", exportName: "setMatrixRuntime", }, registerCliMetadata: registerMatrixCliMetadata, diff --git a/extensions/matrix/runtime-setter-api.ts b/extensions/matrix/runtime-setter-api.ts new file mode 100644 index 00000000000..7e1cc3f0561 --- /dev/null +++ b/extensions/matrix/runtime-setter-api.ts @@ -0,0 +1,3 @@ +// Narrow entry point for setMatrixRuntime. The full runtime-api barrel is kept +// for external/runtime callers, but bundled plugin register only needs this. +export { setMatrixRuntime } from "./src/runtime.js"; diff --git a/extensions/matrix/setup-entry.ts b/extensions/matrix/setup-entry.ts index e148cc933f8..bb229db97bc 100644 --- a/extensions/matrix/setup-entry.ts +++ b/extensions/matrix/setup-entry.ts @@ -11,7 +11,7 @@ export default defineBundledChannelSetupEntry({ exportName: "channelSecrets", }, runtime: { - specifier: "./runtime-api.js", + specifier: "./runtime-setter-api.js", exportName: "setMatrixRuntime", }, }); diff --git a/extensions/matrix/src/actions.ts b/extensions/matrix/src/actions.ts index aaa0c6e630d..e1f956cb87a 100644 --- a/extensions/matrix/src/actions.ts +++ b/extensions/matrix/src/actions.ts @@ -1,18 +1,20 @@ import { Type } from "@sinclair/typebox"; -import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime"; -import { extractToolSend } from "openclaw/plugin-sdk/tool-send"; -import { requiresExplicitMatrixDefaultAccount } from "./account-selection.js"; -import { resolveDefaultMatrixAccountId, resolveMatrixAccount } from "./matrix/accounts.js"; import { createActionGate, readNumberParam, readStringParam, ToolAuthorizationError, - type ChannelMessageActionAdapter, - type ChannelMessageActionContext, - type ChannelMessageActionName, - type ChannelMessageToolDiscovery, -} from "./runtime-api.js"; +} from "openclaw/plugin-sdk/channel-actions"; +import type { + ChannelMessageActionAdapter, + ChannelMessageActionContext, + ChannelMessageActionName, + ChannelMessageToolDiscovery, +} from "openclaw/plugin-sdk/channel-contract"; +import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime"; +import { extractToolSend } from "openclaw/plugin-sdk/tool-send"; +import { requiresExplicitMatrixDefaultAccount } from "./account-selection.js"; +import { resolveDefaultMatrixAccountId, resolveMatrixAccount } from "./matrix/accounts.js"; import type { CoreConfig } from "./types.js"; const MATRIX_PLUGIN_HANDLED_ACTIONS = new Set([ diff --git a/extensions/matrix/src/channel.ts b/extensions/matrix/src/channel.ts index 8c7aa5a5a6d..b6712f50dad 100644 --- a/extensions/matrix/src/channel.ts +++ b/extensions/matrix/src/channel.ts @@ -4,6 +4,7 @@ import { createScopedDmSecurityResolver, } from "openclaw/plugin-sdk/channel-config-helpers"; import { buildChannelConfigSchema } from "openclaw/plugin-sdk/channel-config-primitives"; +import type { ChannelDoctorAdapter } from "openclaw/plugin-sdk/channel-contract"; import { createChatChannelPlugin, type ChannelPlugin } from "openclaw/plugin-sdk/channel-core"; import { createAllowlistProviderOpenWarningCollector, @@ -15,7 +16,6 @@ import { createResolvedDirectoryEntriesLister, createRuntimeDirectoryLiveAdapter, } from "openclaw/plugin-sdk/directory-runtime"; -import { buildTrafficStatusSummary } from "openclaw/plugin-sdk/extension-shared"; import { createLazyRuntimeNamedExport } from "openclaw/plugin-sdk/lazy-runtime"; import { createRuntimeOutboundDelegates } from "openclaw/plugin-sdk/outbound-runtime"; import { @@ -34,7 +34,10 @@ import { matrixApprovalCapability } from "./approval-native.js"; import { createMatrixPairingText, createMatrixProbeAccount } from "./channel-account-paths.js"; import { DEFAULT_ACCOUNT_ID, matrixConfigAdapter } from "./config-adapter.js"; import { MatrixConfigSchema } from "./config-schema.js"; -import { matrixDoctor } from "./doctor.js"; +import { + legacyConfigRules as MATRIX_LEGACY_CONFIG_RULES, + normalizeCompatibilityConfig as normalizeMatrixCompatibilityConfig, +} from "./doctor-contract.js"; import { shouldSuppressLocalMatrixExecApprovalPrompt } from "./exec-approvals.js"; import { resolveMatrixGroupRequireMention, @@ -64,14 +67,17 @@ import { resolveSingleAccountPromotionTarget, singleAccountKeysToMove, } from "./setup-contract.js"; -import { matrixSetupAdapter } from "./setup-core.js"; -import { matrixSetupWizard } from "./setup-surface.js"; +import { createMatrixSetupWizardProxy, matrixSetupAdapter } from "./setup-core.js"; import { runMatrixStartupMaintenance } from "./startup-maintenance.js"; import { resolveMatrixInboundConversation } from "./thread-binding-api.js"; import type { CoreConfig } from "./types.js"; // Mutex for serializing account startup (workaround for concurrent dynamic import race condition) let matrixStartupLock: Promise = Promise.resolve(); +const loadMatrixSetupWizard = createLazyRuntimeNamedExport( + () => import("./setup-surface.js"), + "matrixSetupWizard", +); const loadMatrixChannelRuntime = createLazyRuntimeNamedExport( () => import("./channel.runtime.js"), "matrixChannelRuntime", @@ -88,6 +94,31 @@ const meta = { quickstartAllowFrom: true, }; +function buildMatrixTrafficStatusSummary( + snapshot?: { + lastInboundAt?: number | null; + lastOutboundAt?: number | null; + } | null, +) { + return { + lastInboundAt: snapshot?.lastInboundAt ?? null, + lastOutboundAt: snapshot?.lastOutboundAt ?? null, + }; +} + +const matrixDoctor: ChannelDoctorAdapter = { + dmAllowFromMode: "nestedOnly", + groupModel: "sender", + groupAllowFromFallbackToAllowFrom: false, + warnOnEmptyGroupSenderAllowlist: true, + legacyConfigRules: MATRIX_LEGACY_CONFIG_RULES, + normalizeCompatibilityConfig: normalizeMatrixCompatibilityConfig, + runConfigSequence: async ({ cfg, env, shouldRepair }) => + await (await import("./doctor.js")).runMatrixDoctorSequence({ cfg, env, shouldRepair }), + cleanStaleConfig: async ({ cfg }) => + await (await import("./doctor.js")).cleanStaleMatrixPluginConfig(cfg), +}; + const listMatrixDirectoryPeersFromConfig = createResolvedDirectoryEntriesLister({ kind: "user", @@ -294,7 +325,9 @@ export const matrixPlugin: ChannelPlugin = base: { id: "matrix", meta, - setupWizard: matrixSetupWizard, + setupWizard: createMatrixSetupWizardProxy(async () => ({ + matrixSetupWizard: await loadMatrixSetupWizard(), + })), capabilities: { chatTypes: ["direct", "group", "thread"], polls: true, @@ -432,7 +465,7 @@ export const matrixPlugin: ChannelPlugin = extra: { baseUrl: account.homeserver, lastProbeAt: runtime?.lastProbeAt ?? null, - ...buildTrafficStatusSummary(runtime), + ...buildMatrixTrafficStatusSummary(runtime), }, }), }), diff --git a/extensions/matrix/src/onboarding.ts b/extensions/matrix/src/onboarding.ts index b192295a7cd..1c48c1e1a6a 100644 --- a/extensions/matrix/src/onboarding.ts +++ b/extensions/matrix/src/onboarding.ts @@ -5,7 +5,6 @@ import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime"; import { type ChannelSetupDmPolicy, type ChannelSetupWizardAdapter, - addWildcardAllowFrom, formatDocsLink, hasConfiguredSecretInput, mergeAllowFromEntries, @@ -36,6 +35,7 @@ import { import { resolveMatrixConfigFieldPath, updateMatrixAccountConfig } from "./matrix/config-update.js"; import { ensureMatrixSdkInstalled, isMatrixSdkAvailable } from "./matrix/deps.js"; import { moveSingleMatrixAccountConfigToNamedAccount } from "./setup-config.js"; +import { resolveMatrixSetupDmAllowFrom } from "./setup-dm-policy.js"; import type { CoreConfig, MatrixConfig } from "./types.js"; const channel = "matrix" as const; @@ -84,12 +84,12 @@ function setMatrixDmPolicy(cfg: CoreConfig, policy: DmPolicy, accountId?: string cfg, accountId: resolvedAccountId, }); - const allowFrom = policy === "open" ? addWildcardAllowFrom(existing.dm?.allowFrom) : undefined; + const allowFrom = resolveMatrixSetupDmAllowFrom(policy, existing.dm?.allowFrom); return updateMatrixAccountConfig(cfg, resolvedAccountId, { dm: { ...existing.dm, policy, - ...(allowFrom ? { allowFrom } : {}), + allowFrom, }, }); } diff --git a/extensions/matrix/src/setup-core.test.ts b/extensions/matrix/src/setup-core.test.ts index bbe11dbcf19..76b3f9f3754 100644 --- a/extensions/matrix/src/setup-core.test.ts +++ b/extensions/matrix/src/setup-core.test.ts @@ -1,5 +1,6 @@ -import { describe, expect, it } from "vitest"; -import { matrixSetupAdapter } from "./setup-core.js"; +import type { ChannelSetupWizardAdapter } from "openclaw/plugin-sdk/setup"; +import { describe, expect, it, vi } from "vitest"; +import { createMatrixSetupWizardProxy, matrixSetupAdapter } from "./setup-core.js"; import type { CoreConfig } from "./types.js"; function applyOpsAccountConfig(cfg: CoreConfig): CoreConfig { @@ -35,6 +36,138 @@ function expectOpsAccount(next: CoreConfig): void { }); } +function makeFakeSetupWizard( + overrides: Partial = {}, +): ChannelSetupWizardAdapter { + return { + channel: "matrix", + getStatus: vi.fn(async () => ({ + channel: "matrix", + configured: false, + statusLines: [], + })), + configure: vi.fn(async ({ cfg }) => ({ cfg })), + ...overrides, + } as ChannelSetupWizardAdapter; +} + +describe("createMatrixSetupWizardProxy", () => { + it("does not load the setup surface when constructing the proxy", () => { + const loader = vi.fn(async () => ({ matrixSetupWizard: makeFakeSetupWizard() })); + + const proxy = createMatrixSetupWizardProxy(loader); + + expect(proxy.channel).toBe("matrix"); + expect(loader).not.toHaveBeenCalled(); + }); + + it("loads the setup surface when setup status is requested", async () => { + const status = { + channel: "matrix" as const, + configured: true, + statusLines: ["Matrix: configured"], + }; + const getStatus = vi.fn(async () => status); + const configure = vi.fn(async ({ cfg }) => ({ cfg })); + const loader = vi.fn(async () => ({ + matrixSetupWizard: makeFakeSetupWizard({ configure, getStatus }), + })); + const proxy = createMatrixSetupWizardProxy(loader); + const cfg = { channels: { matrix: {} } } as CoreConfig; + + const result = await proxy.getStatus({ cfg, accountOverrides: {} }); + const configured = await proxy.configure({ + cfg, + runtime: {} as never, + prompter: {} as never, + forceAllowFrom: false, + accountOverrides: {}, + shouldPromptAccountIds: false, + }); + + expect(loader).toHaveBeenCalledTimes(1); + expect(getStatus).toHaveBeenCalledWith({ cfg, accountOverrides: {} }); + expect(configure).toHaveBeenCalledTimes(1); + expect(result).toBe(status); + expect(configured).toEqual({ cfg }); + }); + + it("keeps sync dmPolicy helpers local and lazy-loads only promptAllowFrom", async () => { + const promptAllowFrom = vi.fn(async ({ cfg }) => cfg); + const loader = vi.fn(async () => ({ + matrixSetupWizard: makeFakeSetupWizard({ + dmPolicy: { + label: "Matrix", + channel: "matrix", + policyKey: "unused", + allowFromKey: "unused", + getCurrent: () => "pairing", + setPolicy: (cfg) => cfg, + promptAllowFrom, + }, + }), + })); + const proxy = createMatrixSetupWizardProxy(loader); + const cfg = { + channels: { + matrix: { + accounts: { + ops: { + dm: { + allowFrom: [" @ops:example.org ", "", "*"], + }, + }, + }, + }, + }, + } as CoreConfig; + + expect(proxy.dmPolicy?.getCurrent(cfg, "ops")).toBe("pairing"); + const next = proxy.dmPolicy?.setPolicy(cfg, "open", "ops") as CoreConfig; + + expect(next.channels?.matrix?.accounts?.ops?.dm).toMatchObject({ + policy: "open", + allowFrom: ["@ops:example.org", "*"], + }); + expect(loader).not.toHaveBeenCalled(); + + await proxy.dmPolicy?.promptAllowFrom?.({ + cfg, + prompter: {} as never, + }); + + expect(loader).toHaveBeenCalledTimes(1); + expect(promptAllowFrom).toHaveBeenCalledTimes(1); + }); + + it("removes wildcard allowFrom when switching from open to a restrictive policy", () => { + const loader = vi.fn(async () => ({ matrixSetupWizard: makeFakeSetupWizard() })); + const proxy = createMatrixSetupWizardProxy(loader); + const cfg = { + channels: { + matrix: { + accounts: { + ops: { + dm: { + policy: "open", + allowFrom: ["*", " @ops:example.org "], + }, + }, + }, + }, + }, + } as CoreConfig; + + const next = proxy.dmPolicy?.setPolicy(cfg, "allowlist", "ops") as CoreConfig; + + expect(next.channels?.matrix?.accounts?.ops?.dm).toMatchObject({ + policy: "allowlist", + allowFrom: ["@ops:example.org"], + }); + expect(loader).not.toHaveBeenCalled(); + }); +}); + describe("matrixSetupAdapter", () => { it("moves legacy default config before writing a named account", () => { const cfg = { diff --git a/extensions/matrix/src/setup-core.ts b/extensions/matrix/src/setup-core.ts index e66c1dc6174..a730f5ee6ae 100644 --- a/extensions/matrix/src/setup-core.ts +++ b/extensions/matrix/src/setup-core.ts @@ -1,18 +1,114 @@ +import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime"; import { DEFAULT_ACCOUNT_ID, + type DmPolicy, normalizeAccountId, prepareScopedSetupConfig, type ChannelSetupAdapter, + type ChannelSetupWizardAdapter, } from "openclaw/plugin-sdk/setup"; +import { resolveDefaultMatrixAccountId, resolveMatrixAccountConfig } from "./matrix/accounts.js"; +import { resolveMatrixConfigFieldPath, updateMatrixAccountConfig } from "./matrix/config-update.js"; import { applyMatrixSetupAccountConfig, validateMatrixSetupInput } from "./setup-config.js"; +import { resolveMatrixSetupDmAllowFrom } from "./setup-dm-policy.js"; import type { CoreConfig } from "./types.js"; const channel = "matrix" as const; +type MatrixSetupWizardModule = { matrixSetupWizard: ChannelSetupWizardAdapter }; function resolveMatrixSetupAccountId(params: { accountId?: string; name?: string }): string { return normalizeAccountId(params.accountId?.trim() || params.name?.trim() || DEFAULT_ACCOUNT_ID); } +function resolveMatrixSetupWizardAccountId(cfg: CoreConfig, accountId?: string): string { + return normalizeAccountId( + accountId?.trim() || resolveDefaultMatrixAccountId(cfg) || DEFAULT_ACCOUNT_ID, + ); +} + +function setMatrixDmPolicy(cfg: CoreConfig, policy: DmPolicy, accountId?: string): CoreConfig { + const resolvedAccountId = resolveMatrixSetupWizardAccountId(cfg, accountId); + const existing = resolveMatrixAccountConfig({ + cfg, + accountId: resolvedAccountId, + }); + const allowFrom = resolveMatrixSetupDmAllowFrom(policy, existing.dm?.allowFrom); + return updateMatrixAccountConfig(cfg, resolvedAccountId, { + dm: { + ...existing.dm, + policy, + allowFrom, + }, + }); +} + +export function createMatrixSetupWizardProxy( + loadWizardModule: () => Promise, +): ChannelSetupWizardAdapter { + let wizardPromise: Promise | null = null; + const loadWizard = () => { + wizardPromise ??= loadWizardModule().then((module) => module.matrixSetupWizard); + return wizardPromise; + }; + return { + channel, + getStatus: async (ctx) => await (await loadWizard()).getStatus(ctx), + configure: async (ctx) => await (await loadWizard()).configure(ctx), + configureInteractive: async (ctx) => { + const wizard = await loadWizard(); + return await (wizard.configureInteractive ?? wizard.configure)(ctx); + }, + configureWhenConfigured: async (ctx) => { + const wizard = await loadWizard(); + return await ( + wizard.configureWhenConfigured ?? + wizard.configureInteractive ?? + wizard.configure + )(ctx); + }, + afterConfigWritten: async (ctx) => await (await loadWizard()).afterConfigWritten?.(ctx), + dmPolicy: { + label: "Matrix", + channel, + policyKey: "channels.matrix.dm.policy", + allowFromKey: "channels.matrix.dm.allowFrom", + resolveConfigKeys: (cfg, accountId) => { + const resolvedAccountId = resolveMatrixSetupWizardAccountId(cfg as CoreConfig, accountId); + return { + policyKey: resolveMatrixConfigFieldPath( + cfg as CoreConfig, + resolvedAccountId, + "dm.policy", + ), + allowFromKey: resolveMatrixConfigFieldPath( + cfg as CoreConfig, + resolvedAccountId, + "dm.allowFrom", + ), + }; + }, + getCurrent: (cfg, accountId) => + resolveMatrixAccountConfig({ + cfg: cfg as CoreConfig, + accountId: resolveMatrixSetupWizardAccountId(cfg as CoreConfig, accountId), + }).dm?.policy ?? "pairing", + setPolicy: (cfg, policy, accountId) => + setMatrixDmPolicy(cfg as CoreConfig, policy, accountId) as OpenClawConfig, + promptAllowFrom: async (params) => { + const promptAllowFrom = (await loadWizard()).dmPolicy?.promptAllowFrom; + return promptAllowFrom ? await promptAllowFrom(params) : params.cfg; + }, + }, + disable: (cfg) => ({ + ...(cfg as CoreConfig), + channels: { + ...(cfg as CoreConfig).channels, + matrix: { ...(cfg as CoreConfig).channels?.matrix, enabled: false }, + }, + }), + }; +} + export const matrixSetupAdapter: ChannelSetupAdapter = { resolveAccountId: ({ accountId, input }) => resolveMatrixSetupAccountId({ diff --git a/extensions/matrix/src/setup-dm-policy.ts b/extensions/matrix/src/setup-dm-policy.ts new file mode 100644 index 00000000000..2ae2d51b808 --- /dev/null +++ b/extensions/matrix/src/setup-dm-policy.ts @@ -0,0 +1,15 @@ +import type { DmPolicy } from "openclaw/plugin-sdk/config-runtime"; +import { addWildcardAllowFrom, normalizeAllowFromEntries } from "openclaw/plugin-sdk/setup"; +import type { MatrixConfig } from "./types.js"; + +type MatrixDmAllowFrom = NonNullable["allowFrom"]; + +export function resolveMatrixSetupDmAllowFrom( + policy: DmPolicy, + allowFrom: MatrixDmAllowFrom, +): string[] { + if (policy === "open") { + return addWildcardAllowFrom(allowFrom); + } + return normalizeAllowFromEntries(allowFrom ?? []).filter((entry) => entry !== "*"); +} diff --git a/src/plugins/bundled-plugin-metadata.test.ts b/src/plugins/bundled-plugin-metadata.test.ts index 4c4e725fbcd..8fbcfdc6ed5 100644 --- a/src/plugins/bundled-plugin-metadata.test.ts +++ b/src/plugins/bundled-plugin-metadata.test.ts @@ -187,6 +187,13 @@ describe("bundled plugin metadata", () => { }); }); + it("keeps Matrix's narrow runtime-setter sidecar on the bundled public surface", () => { + const matrix = listRepoBundledPluginMetadata().find((entry) => entry.dirName === "matrix"); + expectArtifactPresence(matrix?.publicSurfaceArtifacts, { + contains: ["runtime-setter-api.js"], + }); + }); + it("keeps bundled configured-state metadata on channel package manifests", () => { const configuredChannels = listRepoBundledPluginMetadata() .filter((entry) => ["discord", "irc", "slack", "telegram"].includes(entry.dirName)) diff --git a/src/plugins/contracts/plugin-sdk-runtime-api-guardrails.test.ts b/src/plugins/contracts/plugin-sdk-runtime-api-guardrails.test.ts index f27e2ae9301..3eff5b98e16 100644 --- a/src/plugins/contracts/plugin-sdk-runtime-api-guardrails.test.ts +++ b/src/plugins/contracts/plugin-sdk-runtime-api-guardrails.test.ts @@ -238,4 +238,15 @@ describe("runtime api guardrails", () => { ); } }); + + it("keeps Matrix's narrow runtime-setter entrypoint pinned to a single export", () => { + const setterFile = bundledPluginFile({ + rootDir: ROOT_DIR, + pluginId: "matrix", + relativePath: "runtime-setter-api.ts", + }); + expect(readExportStatements(setterFile)).toEqual([ + 'export { setMatrixRuntime } from "./src/runtime.js";', + ]); + }); });