From ed7269518fc783a1201f4e781bcb1a13c2246a67 Mon Sep 17 00:00:00 2001 From: Gustavo Madeira Santana Date: Wed, 18 Mar 2026 02:12:53 +0000 Subject: [PATCH 1/7] Tlon: fix plugin-sdk import boundaries --- extensions/tlon/src/monitor/media.ts | 2 +- src/plugin-sdk/tlon.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/extensions/tlon/src/monitor/media.ts b/extensions/tlon/src/monitor/media.ts index ea86328d2ce..8a17e982fad 100644 --- a/extensions/tlon/src/monitor/media.ts +++ b/extensions/tlon/src/monitor/media.ts @@ -5,7 +5,7 @@ import { homedir } from "node:os"; import * as path from "node:path"; import { Readable } from "node:stream"; import { pipeline } from "node:stream/promises"; -import { fetchWithSsrFGuard } from "../api.js"; +import { fetchWithSsrFGuard } from "../../api.js"; import { getDefaultSsrFPolicy } from "../urbit/context.js"; // Default to OpenClaw workspace media directory diff --git a/src/plugin-sdk/tlon.ts b/src/plugin-sdk/tlon.ts index 246c4b7093e..291834b9648 100644 --- a/src/plugin-sdk/tlon.ts +++ b/src/plugin-sdk/tlon.ts @@ -27,5 +27,5 @@ export type { RuntimeEnv } from "../runtime.js"; export { formatDocsLink } from "../terminal/links.js"; export type { WizardPrompter } from "../wizard/prompts.js"; export { createLoggerBackedRuntime } from "./runtime.js"; -export { tlonSetupAdapter } from "../../extensions/tlon/api.js"; -export { tlonSetupWizard } from "../../extensions/tlon/api.js"; +export { tlonSetupAdapter } from "../../extensions/tlon/src/setup-core.js"; +export { tlonSetupWizard } from "../../extensions/tlon/src/setup-surface.js"; From 1c6676cd57453ba8b29a9f908937df7b015beb95 Mon Sep 17 00:00:00 2001 From: Gustavo Madeira Santana Date: Wed, 18 Mar 2026 02:17:40 +0000 Subject: [PATCH 2/7] Plugins: remove first-party legacy message discovery shims --- extensions/discord/src/channel-actions.ts | 2 -- extensions/discord/src/channel.ts | 6 ---- extensions/feishu/src/channel.ts | 6 +--- extensions/mattermost/src/channel.ts | 32 +++++++++------------ extensions/msteams/src/channel.ts | 6 +--- extensions/telegram/src/channel-actions.ts | 2 -- extensions/telegram/src/channel.ts | 6 ---- src/channels/plugins/message-tool-legacy.ts | 13 --------- src/channels/plugins/slack.actions.ts | 2 -- src/commands/channels/capabilities.ts | 20 ++++++++++--- src/plugin-sdk/agent-runtime.ts | 12 -------- src/plugin-sdk/channel-runtime.ts | 1 - 12 files changed, 32 insertions(+), 76 deletions(-) delete mode 100644 src/channels/plugins/message-tool-legacy.ts diff --git a/extensions/discord/src/channel-actions.ts b/extensions/discord/src/channel-actions.ts index 960b08acdf6..c4be7728439 100644 --- a/extensions/discord/src/channel-actions.ts +++ b/extensions/discord/src/channel-actions.ts @@ -1,5 +1,4 @@ import { - createLegacyMessageToolDiscoveryMethods, createDiscordMessageToolComponentsSchema, createUnionActionGate, listTokenSourcedAccounts, @@ -133,7 +132,6 @@ function describeDiscordMessageTool({ export const discordMessageActions: ChannelMessageActionAdapter = { describeMessageTool: describeDiscordMessageTool, - ...createLegacyMessageToolDiscoveryMethods(describeDiscordMessageTool), extractToolSend: ({ args }) => { const action = typeof args.action === "string" ? args.action.trim() : ""; if (action === "sendMessage") { diff --git a/extensions/discord/src/channel.ts b/extensions/discord/src/channel.ts index c555ff89382..58076e1e67d 100644 --- a/extensions/discord/src/channel.ts +++ b/extensions/discord/src/channel.ts @@ -79,12 +79,6 @@ function formatDiscordIntents(intents?: { const discordMessageActions: ChannelMessageActionAdapter = { describeMessageTool: (ctx) => getDiscordRuntime().channel.discord.messageActions?.describeMessageTool?.(ctx) ?? null, - listActions: (ctx) => - getDiscordRuntime().channel.discord.messageActions?.listActions?.(ctx) ?? [], - getCapabilities: (ctx) => - getDiscordRuntime().channel.discord.messageActions?.getCapabilities?.(ctx) ?? [], - getToolSchema: (ctx) => - getDiscordRuntime().channel.discord.messageActions?.getToolSchema?.(ctx) ?? null, extractToolSend: (ctx) => getDiscordRuntime().channel.discord.messageActions?.extractToolSend?.(ctx) ?? null, handleAction: async (ctx) => { diff --git a/extensions/feishu/src/channel.ts b/extensions/feishu/src/channel.ts index da5cd8e4382..fda85f113e1 100644 --- a/extensions/feishu/src/channel.ts +++ b/extensions/feishu/src/channel.ts @@ -1,10 +1,7 @@ import { formatAllowFromLowercase } from "openclaw/plugin-sdk/allow-from"; import { mapAllowFromEntries } from "openclaw/plugin-sdk/channel-config-helpers"; import { collectAllowlistProviderRestrictSendersWarnings } from "openclaw/plugin-sdk/channel-policy"; -import { - createLegacyMessageToolDiscoveryMethods, - createMessageToolCardSchema, -} from "openclaw/plugin-sdk/channel-runtime"; +import { createMessageToolCardSchema } from "openclaw/plugin-sdk/channel-runtime"; import type { ChannelMessageActionAdapter, ChannelMessageToolDiscovery, @@ -453,7 +450,6 @@ export const feishuPlugin: ChannelPlugin = { }, actions: { describeMessageTool: describeFeishuMessageTool, - ...createLegacyMessageToolDiscoveryMethods(describeFeishuMessageTool), handleAction: async (ctx) => { const account = resolveFeishuAccount({ cfg: ctx.cfg, accountId: ctx.accountId ?? undefined }); if ( diff --git a/extensions/mattermost/src/channel.ts b/extensions/mattermost/src/channel.ts index 5688e13d8ae..4bc716ac27e 100644 --- a/extensions/mattermost/src/channel.ts +++ b/extensions/mattermost/src/channel.ts @@ -4,24 +4,8 @@ import { buildAccountScopedDmSecurityPolicy, collectAllowlistProviderRestrictSendersWarnings, } from "openclaw/plugin-sdk/channel-policy"; -import { - createLegacyMessageToolDiscoveryMethods, - createMessageToolButtonsSchema, -} from "openclaw/plugin-sdk/channel-runtime"; +import { createMessageToolButtonsSchema } from "openclaw/plugin-sdk/channel-runtime"; import type { ChannelMessageToolDiscovery } from "openclaw/plugin-sdk/channel-runtime"; -import { - buildComputedAccountStatusSnapshot, - buildChannelConfigSchema, - createAccountStatusSink, - DEFAULT_ACCOUNT_ID, - deleteAccountFromConfigSection, - resolveAllowlistProviderRuntimeGroupPolicy, - resolveDefaultGroupPolicy, - setAccountEnabledInConfigSection, - type ChannelMessageActionAdapter, - type ChannelMessageActionName, - type ChannelPlugin, -} from "./runtime-api.js"; import { buildPassiveProbedChannelStatusSummary } from "../../shared/channel-status-summary.js"; import { MattermostConfigSchema } from "./config-schema.js"; import { resolveMattermostGroupRequireMention } from "./group-mentions.js"; @@ -42,6 +26,19 @@ import { addMattermostReaction, removeMattermostReaction } from "./mattermost/re import { sendMessageMattermost } from "./mattermost/send.js"; import { resolveMattermostOpaqueTarget } from "./mattermost/target-resolution.js"; import { looksLikeMattermostTargetId, normalizeMattermostMessagingTarget } from "./normalize.js"; +import { + buildComputedAccountStatusSnapshot, + buildChannelConfigSchema, + createAccountStatusSink, + DEFAULT_ACCOUNT_ID, + deleteAccountFromConfigSection, + resolveAllowlistProviderRuntimeGroupPolicy, + resolveDefaultGroupPolicy, + setAccountEnabledInConfigSection, + type ChannelMessageActionAdapter, + type ChannelMessageActionName, + type ChannelPlugin, +} from "./runtime-api.js"; import { getMattermostRuntime } from "./runtime.js"; import { mattermostSetupAdapter } from "./setup-core.js"; import { mattermostSetupWizard } from "./setup-surface.js"; @@ -88,7 +85,6 @@ function describeMattermostMessageTool({ const mattermostMessageActions: ChannelMessageActionAdapter = { describeMessageTool: describeMattermostMessageTool, - ...createLegacyMessageToolDiscoveryMethods(describeMattermostMessageTool), supportsAction: ({ action }) => { return action === "send" || action === "react"; }, diff --git a/extensions/msteams/src/channel.ts b/extensions/msteams/src/channel.ts index 7458389efb1..5f3a6aa0b59 100644 --- a/extensions/msteams/src/channel.ts +++ b/extensions/msteams/src/channel.ts @@ -1,9 +1,6 @@ import { formatAllowFromLowercase } from "openclaw/plugin-sdk/allow-from"; import { collectAllowlistProviderRestrictSendersWarnings } from "openclaw/plugin-sdk/channel-policy"; -import { - createLegacyMessageToolDiscoveryMethods, - createMessageToolCardSchema, -} from "openclaw/plugin-sdk/channel-runtime"; +import { createMessageToolCardSchema } from "openclaw/plugin-sdk/channel-runtime"; import type { ChannelMessageActionAdapter, ChannelMessageToolDiscovery, @@ -398,7 +395,6 @@ export const msteamsPlugin: ChannelPlugin = { }, actions: { describeMessageTool: describeMSTeamsMessageTool, - ...createLegacyMessageToolDiscoveryMethods(describeMSTeamsMessageTool), handleAction: async (ctx) => { // Handle send action with card parameter if (ctx.action === "send" && ctx.params.card) { diff --git a/extensions/telegram/src/channel-actions.ts b/extensions/telegram/src/channel-actions.ts index cd757688835..56d27817921 100644 --- a/extensions/telegram/src/channel-actions.ts +++ b/extensions/telegram/src/channel-actions.ts @@ -7,7 +7,6 @@ import { import { readBooleanParam } from "openclaw/plugin-sdk/boolean-param"; import { resolveReactionMessageId } from "openclaw/plugin-sdk/channel-runtime"; import { - createLegacyMessageToolDiscoveryMethods, createMessageToolButtonsSchema, createTelegramPollExtraToolSchemas, createUnionActionGate, @@ -178,7 +177,6 @@ function readTelegramMessageIdParam(params: Record): number { export const telegramMessageActions: ChannelMessageActionAdapter = { describeMessageTool: describeTelegramMessageTool, - ...createLegacyMessageToolDiscoveryMethods(describeTelegramMessageTool), extractToolSend: ({ args }) => { return extractToolSend(args, "sendMessage"); }, diff --git a/extensions/telegram/src/channel.ts b/extensions/telegram/src/channel.ts index 6d536fb8513..56a2256f9c0 100644 --- a/extensions/telegram/src/channel.ts +++ b/extensions/telegram/src/channel.ts @@ -250,12 +250,6 @@ function hasTelegramExecApprovalDmRoute(cfg: OpenClawConfig): boolean { const telegramMessageActions: ChannelMessageActionAdapter = { describeMessageTool: (ctx) => getTelegramRuntime().channel.telegram.messageActions?.describeMessageTool?.(ctx) ?? null, - listActions: (ctx) => - getTelegramRuntime().channel.telegram.messageActions?.listActions?.(ctx) ?? [], - getCapabilities: (ctx) => - getTelegramRuntime().channel.telegram.messageActions?.getCapabilities?.(ctx) ?? [], - getToolSchema: (ctx) => - getTelegramRuntime().channel.telegram.messageActions?.getToolSchema?.(ctx) ?? null, extractToolSend: (ctx) => getTelegramRuntime().channel.telegram.messageActions?.extractToolSend?.(ctx) ?? null, handleAction: async (ctx) => { diff --git a/src/channels/plugins/message-tool-legacy.ts b/src/channels/plugins/message-tool-legacy.ts deleted file mode 100644 index 2c74213439f..00000000000 --- a/src/channels/plugins/message-tool-legacy.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { ChannelMessageActionAdapter } from "./types.js"; - -export function createLegacyMessageToolDiscoveryMethods( - describeMessageTool: NonNullable, -): Pick { - const describe = (ctx: Parameters[0]) => - describeMessageTool(ctx) ?? null; - return { - listActions: (ctx) => [...(describe(ctx)?.actions ?? [])], - getCapabilities: (ctx) => [...(describe(ctx)?.capabilities ?? [])], - getToolSchema: (ctx) => describe(ctx)?.schema ?? null, - }; -} diff --git a/src/channels/plugins/slack.actions.ts b/src/channels/plugins/slack.actions.ts index c9cf3e9d883..317b8a7d8db 100644 --- a/src/channels/plugins/slack.actions.ts +++ b/src/channels/plugins/slack.actions.ts @@ -10,7 +10,6 @@ import { resolveSlackChannelId, handleSlackMessageAction, } from "../../plugin-sdk/slack.js"; -import { createLegacyMessageToolDiscoveryMethods } from "./message-tool-legacy.js"; import { createSlackMessageToolBlocksSchema } from "./message-tool-schema.js"; import type { ChannelMessageActionAdapter, ChannelMessageToolDiscovery } from "./types.js"; @@ -52,7 +51,6 @@ export function createSlackActions( return { describeMessageTool, - ...createLegacyMessageToolDiscoveryMethods(describeMessageTool), extractToolSend: ({ args }) => extractSlackToolSend(args), handleAction: async (ctx) => { return await handleSlackMessageAction({ diff --git a/src/commands/channels/capabilities.ts b/src/commands/channels/capabilities.ts index acd28137b30..eccd96824da 100644 --- a/src/commands/channels/capabilities.ts +++ b/src/commands/channels/capabilities.ts @@ -1,5 +1,9 @@ import { resolveChannelDefaultAccountId } from "../../channels/plugins/helpers.js"; import { getChannelPlugin, listChannelPlugins } from "../../channels/plugins/index.js"; +import { + createMessageActionDiscoveryContext, + resolveMessageActionDiscoveryForPlugin, +} from "../../channels/plugins/message-action-discovery.js"; import type { ChannelCapabilities, ChannelCapabilitiesDiagnostics, @@ -133,10 +137,6 @@ async function resolveChannelReports(params: { : [resolveChannelDefaultAccountId({ plugin, cfg, accountIds: ids })]; })(); const reports: ChannelCapabilitiesReport[] = []; - const listedActions = plugin.actions?.listActions?.({ cfg }) ?? []; - const actions = Array.from( - new Set(["send", "broadcast", ...listedActions.map((action) => String(action))]), - ); for (const accountId of accountIds) { const resolvedAccount = plugin.config.resolveAccount(cfg, accountId); @@ -169,6 +169,18 @@ async function resolveChannelReports(params: { target: params.target, }) : undefined; + const discoveredActions = resolveMessageActionDiscoveryForPlugin({ + pluginId: plugin.id, + actions: plugin.actions, + context: createMessageActionDiscoveryContext({ + cfg, + accountId, + }), + includeActions: true, + }).actions; + const actions = Array.from( + new Set(["send", "broadcast", ...discoveredActions.map((action) => String(action))]), + ); reports.push({ channel: plugin.id, diff --git a/src/plugin-sdk/agent-runtime.ts b/src/plugin-sdk/agent-runtime.ts index 20ab0596a12..e267a458e16 100644 --- a/src/plugin-sdk/agent-runtime.ts +++ b/src/plugin-sdk/agent-runtime.ts @@ -23,15 +23,3 @@ export * from "../agents/vllm-defaults.js"; // Intentional public runtime surface: channel plugins use ingress agent helpers directly. export * from "../agents/agent-command.js"; export * from "../tts/tts.js"; -// Legacy channel action runtime re-exports. New bundled plugin code should use -// local extension-owned modules instead of adding more public SDK surface here. -export { - handleDiscordAction, - readDiscordParentIdParam, - isDiscordModerationAction, - readDiscordModerationCommand, -} from "../../extensions/discord/runtime-api.js"; -export { - handleTelegramAction, - readTelegramButtons, -} from "../../extensions/telegram/runtime-api.js"; diff --git a/src/plugin-sdk/channel-runtime.ts b/src/plugin-sdk/channel-runtime.ts index 1460acba87d..089e10609af 100644 --- a/src/plugin-sdk/channel-runtime.ts +++ b/src/plugin-sdk/channel-runtime.ts @@ -34,7 +34,6 @@ export type * from "../channels/plugins/types.js"; export * from "../channels/plugins/config-writes.js"; export * from "../channels/plugins/directory-config.js"; export * from "../channels/plugins/media-payload.js"; -export * from "../channels/plugins/message-tool-legacy.js"; export * from "../channels/plugins/message-tool-schema.js"; export * from "../channels/plugins/normalize/signal.js"; export * from "../channels/plugins/normalize/whatsapp.js"; From fb0d04c8342c1ee12b7eaae159573699b9a4bdd9 Mon Sep 17 00:00:00 2001 From: Gustavo Madeira Santana Date: Wed, 18 Mar 2026 02:17:47 +0000 Subject: [PATCH 3/7] Tests: migrate channel action discovery to describeMessageTool --- docs/tools/plugin.md | 6 +- extensions/feishu/src/channel.test.ts | 8 ++- extensions/mattermost/src/channel.test.ts | 12 ++-- src/channels/plugins/actions/actions.test.ts | 28 +++++++-- src/channels/plugins/contracts/registry.ts | 30 +++++---- src/channels/plugins/contracts/suites.ts | 42 +++++++++++-- .../plugins/message-capability-matrix.test.ts | 62 ++++++++++--------- 7 files changed, 125 insertions(+), 63 deletions(-) diff --git a/docs/tools/plugin.md b/docs/tools/plugin.md index 4dc95ae4fe6..7d49323892d 100644 --- a/docs/tools/plugin.md +++ b/docs/tools/plugin.md @@ -231,9 +231,9 @@ surface for the current turn. For channel-owned execution helpers, bundled plugins should keep the execution runtime inside their own extension modules. Core no longer owns the Discord, Slack, Telegram, or WhatsApp message-action runtimes under `src/agents/tools`. -`agent-runtime` still re-exports the Discord and Telegram helpers for backward -compatibility, but we do not publish separate `plugin-sdk/*-action-runtime` -subpaths and new plugins should import their own local runtime code directly. +We do not publish separate `plugin-sdk/*-action-runtime` subpaths, and bundled +plugins should import their own local runtime code directly from their +extension-owned modules. ## Capability ownership model diff --git a/extensions/feishu/src/channel.test.ts b/extensions/feishu/src/channel.test.ts index 7c4ae5d877a..df105f81919 100644 --- a/extensions/feishu/src/channel.test.ts +++ b/extensions/feishu/src/channel.test.ts @@ -54,6 +54,10 @@ vi.mock("./channel.runtime.js", () => ({ import { feishuPlugin } from "./channel.js"; +function getDescribedActions(cfg: OpenClawConfig): string[] { + return [...(feishuPlugin.actions?.describeMessageTool?.({ cfg })?.actions ?? [])]; +} + describe("feishuPlugin.status.probeAccount", () => { it("uses current account credentials for multi-account config", async () => { const cfg = { @@ -112,7 +116,7 @@ describe("feishuPlugin actions", () => { }); it("advertises the expanded Feishu action surface", () => { - expect(feishuPlugin.actions?.listActions?.({ cfg })).toEqual([ + expect(getDescribedActions(cfg)).toEqual([ "send", "read", "edit", @@ -142,7 +146,7 @@ describe("feishuPlugin actions", () => { }, } as OpenClawConfig; - expect(feishuPlugin.actions?.listActions?.({ cfg: disabledCfg })).toEqual([ + expect(getDescribedActions(disabledCfg)).toEqual([ "send", "read", "edit", diff --git a/extensions/mattermost/src/channel.test.ts b/extensions/mattermost/src/channel.test.ts index 5ac333b2e6c..29c4cc12e0e 100644 --- a/extensions/mattermost/src/channel.test.ts +++ b/extensions/mattermost/src/channel.test.ts @@ -17,6 +17,10 @@ import { withMockedGlobalFetch, } from "./mattermost/reactions.test-helpers.js"; +function getDescribedActions(cfg: OpenClawConfig): string[] { + return [...(mattermostPlugin.actions?.describeMessageTool?.({ cfg })?.actions ?? [])]; +} + describe("mattermostPlugin", () => { beforeEach(() => { sendMessageMattermostMock.mockReset(); @@ -132,7 +136,7 @@ describe("mattermostPlugin", () => { }, }; - const actions = mattermostPlugin.actions?.listActions?.({ cfg }) ?? []; + const actions = getDescribedActions(cfg); expect(actions).toContain("react"); expect(actions).toContain("send"); expect(mattermostPlugin.actions?.supportsAction?.({ action: "react" })).toBe(true); @@ -148,7 +152,7 @@ describe("mattermostPlugin", () => { }, }; - const actions = mattermostPlugin.actions?.listActions?.({ cfg }) ?? []; + const actions = getDescribedActions(cfg); expect(actions).toEqual([]); }); @@ -164,7 +168,7 @@ describe("mattermostPlugin", () => { }, }; - const actions = mattermostPlugin.actions?.listActions?.({ cfg }) ?? []; + const actions = getDescribedActions(cfg); expect(actions).not.toContain("react"); expect(actions).toContain("send"); }); @@ -187,7 +191,7 @@ describe("mattermostPlugin", () => { }, }; - const actions = mattermostPlugin.actions?.listActions?.({ cfg }) ?? []; + const actions = getDescribedActions(cfg); expect(actions).toContain("react"); }); diff --git a/src/channels/plugins/actions/actions.test.ts b/src/channels/plugins/actions/actions.test.ts index f1ff9c36dfd..b4631d03f2c 100644 --- a/src/channels/plugins/actions/actions.test.ts +++ b/src/channels/plugins/actions/actions.test.ts @@ -1,5 +1,6 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; import type { OpenClawConfig } from "../../../config/config.js"; +import type { ChannelMessageActionAdapter } from "../types.js"; const handleDiscordAction = vi.fn(async (..._args: unknown[]) => ({ details: { ok: true } })); const handleTelegramAction = vi.fn(async (..._args: unknown[]) => ({ ok: true })); @@ -30,6 +31,13 @@ let telegramMessageActions: typeof import("./telegram.js").telegramMessageAction let signalMessageActions: typeof import("./signal.js").signalMessageActions; let createSlackActions: typeof import("../slack.actions.js").createSlackActions; +function getDescribedActions(params: { + describeMessageTool?: ChannelMessageActionAdapter["describeMessageTool"]; + cfg: OpenClawConfig; +}) { + return [...(params.describeMessageTool?.({ cfg: params.cfg })?.actions ?? [])]; +} + function telegramCfg(): OpenClawConfig { return { channels: { telegram: { botToken: "tok" } } } as OpenClawConfig; } @@ -284,7 +292,10 @@ describe("discord message actions", () => { ] as const; for (const testCase of cases) { - const actions = discordMessageActions.listActions?.({ cfg: testCase.cfg }) ?? []; + const actions = getDescribedActions({ + describeMessageTool: discordMessageActions.describeMessageTool, + cfg: testCase.cfg, + }); if (testCase.expectUploads) { expect(actions, testCase.name).toContain("emoji-upload"); expect(actions, testCase.name).toContain("sticker-upload"); @@ -629,7 +640,10 @@ describe("telegramMessageActions", () => { expectTopicEdit: true, }, ]) { - const actions = telegramMessageActions.listActions?.({ cfg: testCase.cfg }) ?? []; + const actions = getDescribedActions({ + describeMessageTool: telegramMessageActions.describeMessageTool, + cfg: testCase.cfg, + }); if (testCase.expectPoll) { expect(actions, testCase.name).toContain("poll"); } else { @@ -680,7 +694,10 @@ describe("telegramMessageActions", () => { ] as const; for (const testCase of cases) { - const actions = telegramMessageActions.listActions?.({ cfg: testCase.cfg }) ?? []; + const actions = getDescribedActions({ + describeMessageTool: telegramMessageActions.describeMessageTool, + cfg: testCase.cfg, + }); if (testCase.expectSticker) { expect(actions, testCase.name).toContain("sticker"); expect(actions, testCase.name).toContain("sticker-search"); @@ -903,7 +920,10 @@ describe("telegramMessageActions", () => { }, }, } as OpenClawConfig; - const actions = telegramMessageActions.listActions?.({ cfg }) ?? []; + const actions = getDescribedActions({ + describeMessageTool: telegramMessageActions.describeMessageTool, + cfg, + }); expect(actions).toContain("sticker"); expect(actions).toContain("sticker-search"); diff --git a/src/channels/plugins/contracts/registry.ts b/src/channels/plugins/contracts/registry.ts index fd2d84e8b70..8b203c9b541 100644 --- a/src/channels/plugins/contracts/registry.ts +++ b/src/channels/plugins/contracts/registry.ts @@ -174,17 +174,14 @@ function expectClearedSessionBinding(params: { ).toBeNull(); } -const telegramListActionsMock = vi.fn(); -const telegramGetCapabilitiesMock = vi.fn(); -const discordListActionsMock = vi.fn(); -const discordGetCapabilitiesMock = vi.fn(); +const telegramDescribeMessageToolMock = vi.fn(); +const discordDescribeMessageToolMock = vi.fn(); bundledChannelRuntimeSetters.setTelegramRuntime({ channel: { telegram: { messageActions: { - listActions: telegramListActionsMock, - getCapabilities: telegramGetCapabilitiesMock, + describeMessageTool: telegramDescribeMessageToolMock, }, }, }, @@ -194,8 +191,7 @@ bundledChannelRuntimeSetters.setDiscordRuntime({ channel: { discord: { messageActions: { - listActions: discordListActionsMock, - getCapabilities: discordGetCapabilitiesMock, + describeMessageTool: discordDescribeMessageToolMock, }, }, }, @@ -358,10 +354,11 @@ export const actionContractRegistry: ActionsContractEntry[] = [ expectedActions: ["send", "poll", "react"], expectedCapabilities: ["interactive", "buttons"], beforeTest: () => { - telegramListActionsMock.mockReset(); - telegramGetCapabilitiesMock.mockReset(); - telegramListActionsMock.mockReturnValue(["send", "poll", "react"]); - telegramGetCapabilitiesMock.mockReturnValue(["interactive", "buttons"]); + telegramDescribeMessageToolMock.mockReset(); + telegramDescribeMessageToolMock.mockReturnValue({ + actions: ["send", "poll", "react"], + capabilities: ["interactive", "buttons"], + }); }, }, ], @@ -376,10 +373,11 @@ export const actionContractRegistry: ActionsContractEntry[] = [ expectedActions: ["send", "react", "poll"], expectedCapabilities: ["interactive", "components"], beforeTest: () => { - discordListActionsMock.mockReset(); - discordGetCapabilitiesMock.mockReset(); - discordListActionsMock.mockReturnValue(["send", "react", "poll"]); - discordGetCapabilitiesMock.mockReturnValue(["interactive", "components"]); + discordDescribeMessageToolMock.mockReset(); + discordDescribeMessageToolMock.mockReturnValue({ + actions: ["send", "react", "poll"], + capabilities: ["interactive", "components"], + }); }, }, ], diff --git a/src/channels/plugins/contracts/suites.ts b/src/channels/plugins/contracts/suites.ts index cc442b5ef20..58a62d62ed3 100644 --- a/src/channels/plugins/contracts/suites.ts +++ b/src/channels/plugins/contracts/suites.ts @@ -32,6 +32,30 @@ function sortStrings(values: readonly string[]) { return [...values].toSorted((left, right) => left.localeCompare(right)); } +function resolveContractMessageDiscovery(params: { + plugin: Pick; + cfg: OpenClawConfig; +}) { + const actions = params.plugin.actions; + if (!actions) { + return { + actions: [] as ChannelMessageActionName[], + capabilities: [] as readonly ChannelMessageCapability[], + }; + } + if (actions.describeMessageTool) { + const discovery = actions.describeMessageTool({ cfg: params.cfg }) ?? null; + return { + actions: Array.isArray(discovery?.actions) ? [...discovery.actions] : [], + capabilities: Array.isArray(discovery?.capabilities) ? discovery.capabilities : [], + }; + } + return { + actions: actions.listActions?.({ cfg: params.cfg }) ?? [], + capabilities: actions.getCapabilities?.({ cfg: params.cfg }) ?? [], + }; +} + const contractRuntime = createNonExitingRuntime(); function expectDirectoryEntryShape(entry: ChannelDirectoryEntry) { expect(["user", "group", "channel"]).toContain(entry.kind); @@ -132,15 +156,22 @@ export function installChannelActionsContractSuite(params: { }) { it("exposes the base message actions contract", () => { expect(params.plugin.actions).toBeDefined(); - expect(typeof params.plugin.actions?.listActions).toBe("function"); + expect( + typeof params.plugin.actions?.describeMessageTool === "function" || + typeof params.plugin.actions?.listActions === "function", + ).toBe(true); }); for (const testCase of params.cases) { it(`actions contract: ${testCase.name}`, () => { testCase.beforeTest?.(); - const actions = params.plugin.actions?.listActions?.({ cfg: testCase.cfg }) ?? []; - const capabilities = params.plugin.actions?.getCapabilities?.({ cfg: testCase.cfg }) ?? []; + const discovery = resolveContractMessageDiscovery({ + plugin: params.plugin, + cfg: testCase.cfg, + }); + const actions = discovery.actions; + const capabilities = discovery.capabilities; expect(actions).toEqual([...new Set(actions)]); expect(capabilities).toEqual([...new Set(capabilities)]); @@ -192,7 +223,10 @@ export function installChannelSurfaceContractSuite(params: { it(`exposes the ${surface} surface contract`, () => { if (surface === "actions") { expect(plugin.actions).toBeDefined(); - expect(typeof plugin.actions?.listActions).toBe("function"); + expect( + typeof plugin.actions?.describeMessageTool === "function" || + typeof plugin.actions?.listActions === "function", + ).toBe(true); return; } diff --git a/src/channels/plugins/message-capability-matrix.test.ts b/src/channels/plugins/message-capability-matrix.test.ts index 9ab42ad4c51..459193d0792 100644 --- a/src/channels/plugins/message-capability-matrix.test.ts +++ b/src/channels/plugins/message-capability-matrix.test.ts @@ -1,15 +1,16 @@ import { afterEach, describe, expect, it, vi } from "vitest"; import type { OpenClawConfig } from "../../config/config.js"; +import type { ChannelMessageActionAdapter, ChannelPlugin } from "./types.js"; -const telegramGetCapabilitiesMock = vi.fn(); -const discordGetCapabilitiesMock = vi.fn(); +const telegramDescribeMessageToolMock = vi.fn(); +const discordDescribeMessageToolMock = vi.fn(); vi.mock("../../../extensions/telegram/src/runtime.js", () => ({ getTelegramRuntime: () => ({ channel: { telegram: { messageActions: { - getCapabilities: telegramGetCapabilitiesMock, + describeMessageTool: telegramDescribeMessageToolMock, }, }, }, @@ -21,7 +22,7 @@ vi.mock("../../../extensions/discord/src/runtime.js", () => ({ channel: { discord: { messageActions: { - getCapabilities: discordGetCapabilitiesMock, + describeMessageTool: discordDescribeMessageToolMock, }, }, }, @@ -38,10 +39,16 @@ const { zaloPlugin } = await import("../../../extensions/zalo/src/channel.js"); describe("channel action capability matrix", () => { afterEach(() => { - telegramGetCapabilitiesMock.mockReset(); - discordGetCapabilitiesMock.mockReset(); + telegramDescribeMessageToolMock.mockReset(); + discordDescribeMessageToolMock.mockReset(); }); + function getCapabilities(plugin: Pick, cfg: OpenClawConfig) { + const describeMessageTool: ChannelMessageActionAdapter["describeMessageTool"] | undefined = + plugin.actions?.describeMessageTool; + return [...(describeMessageTool?.({ cfg })?.capabilities ?? [])]; + } + it("exposes Slack blocks by default and interactive when enabled", () => { const baseCfg = { channels: { @@ -61,26 +68,27 @@ describe("channel action capability matrix", () => { }, } as OpenClawConfig; - expect(slackPlugin.actions?.getCapabilities?.({ cfg: baseCfg })).toEqual(["blocks"]); - expect(slackPlugin.actions?.getCapabilities?.({ cfg: interactiveCfg })).toEqual([ - "blocks", - "interactive", - ]); + expect(getCapabilities(slackPlugin, baseCfg)).toEqual(["blocks"]); + expect(getCapabilities(slackPlugin, interactiveCfg)).toEqual(["blocks", "interactive"]); }); it("forwards Telegram action capabilities through the channel wrapper", () => { - telegramGetCapabilitiesMock.mockReturnValue(["interactive", "buttons"]); + telegramDescribeMessageToolMock.mockReturnValue({ + capabilities: ["interactive", "buttons"], + }); - const result = telegramPlugin.actions?.getCapabilities?.({ cfg: {} as OpenClawConfig }); + const result = getCapabilities(telegramPlugin, {} as OpenClawConfig); expect(result).toEqual(["interactive", "buttons"]); - expect(telegramGetCapabilitiesMock).toHaveBeenCalledWith({ cfg: {} }); - discordGetCapabilitiesMock.mockReturnValue(["interactive", "components"]); + expect(telegramDescribeMessageToolMock).toHaveBeenCalledWith({ cfg: {} }); + discordDescribeMessageToolMock.mockReturnValue({ + capabilities: ["interactive", "components"], + }); - const discordResult = discordPlugin.actions?.getCapabilities?.({ cfg: {} as OpenClawConfig }); + const discordResult = getCapabilities(discordPlugin, {} as OpenClawConfig); expect(discordResult).toEqual(["interactive", "components"]); - expect(discordGetCapabilitiesMock).toHaveBeenCalledWith({ cfg: {} }); + expect(discordDescribeMessageToolMock).toHaveBeenCalledWith({ cfg: {} }); }); it("exposes configured channel capabilities only when required credentials are present", () => { @@ -139,18 +147,12 @@ describe("channel action capability matrix", () => { }, } as OpenClawConfig; - expect(mattermostPlugin.actions?.getCapabilities?.({ cfg: configuredCfg })).toEqual([ - "buttons", - ]); - expect(mattermostPlugin.actions?.getCapabilities?.({ cfg: unconfiguredCfg })).toEqual([]); - expect(feishuPlugin.actions?.getCapabilities?.({ cfg: configuredFeishuCfg })).toEqual([ - "cards", - ]); - expect(feishuPlugin.actions?.getCapabilities?.({ cfg: disabledFeishuCfg })).toEqual([]); - expect(msteamsPlugin.actions?.getCapabilities?.({ cfg: configuredMsteamsCfg })).toEqual([ - "cards", - ]); - expect(msteamsPlugin.actions?.getCapabilities?.({ cfg: disabledMsteamsCfg })).toEqual([]); + expect(getCapabilities(mattermostPlugin, configuredCfg)).toEqual(["buttons"]); + expect(getCapabilities(mattermostPlugin, unconfiguredCfg)).toEqual([]); + expect(getCapabilities(feishuPlugin, configuredFeishuCfg)).toEqual(["cards"]); + expect(getCapabilities(feishuPlugin, disabledFeishuCfg)).toEqual([]); + expect(getCapabilities(msteamsPlugin, configuredMsteamsCfg)).toEqual(["cards"]); + expect(getCapabilities(msteamsPlugin, disabledMsteamsCfg)).toEqual([]); }); it("keeps Zalo actions on the empty capability set", () => { @@ -163,6 +165,6 @@ describe("channel action capability matrix", () => { }, } as OpenClawConfig; - expect(zaloPlugin.actions?.getCapabilities?.({ cfg })).toEqual([]); + expect(getCapabilities(zaloPlugin, cfg)).toEqual([]); }); }); From 8e98019b6af9d128cd32e2635c0a4386e386887b Mon Sep 17 00:00:00 2001 From: Gustavo Madeira Santana Date: Wed, 18 Mar 2026 02:17:56 +0000 Subject: [PATCH 4/7] Nostr: remove plugin API import cycle --- extensions/nostr/src/channel.ts | 10 +++++----- extensions/nostr/src/config-schema.ts | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/extensions/nostr/src/channel.ts b/extensions/nostr/src/channel.ts index 4296f71b9ac..21dfce3a9da 100644 --- a/extensions/nostr/src/channel.ts +++ b/extensions/nostr/src/channel.ts @@ -1,7 +1,3 @@ -import { - buildPassiveChannelStatusSummary, - buildTrafficStatusSummary, -} from "../../shared/channel-status-summary.js"; import { buildChannelConfigSchema, collectStatusIssuesFromLastError, @@ -10,7 +6,11 @@ import { formatPairingApproveHint, mapAllowFromEntries, type ChannelPlugin, -} from "../api.js"; +} from "openclaw/plugin-sdk/nostr"; +import { + buildPassiveChannelStatusSummary, + buildTrafficStatusSummary, +} from "../../shared/channel-status-summary.js"; import type { NostrProfile } from "./config-schema.js"; import { NostrConfigSchema } from "./config-schema.js"; import type { MetricEvent, MetricsSnapshot } from "./metrics.js"; diff --git a/extensions/nostr/src/config-schema.ts b/extensions/nostr/src/config-schema.ts index 2746d518fe6..53346b0789d 100644 --- a/extensions/nostr/src/config-schema.ts +++ b/extensions/nostr/src/config-schema.ts @@ -1,6 +1,6 @@ import { AllowFromListSchema, DmPolicySchema } from "openclaw/plugin-sdk/channel-config-schema"; +import { MarkdownConfigSchema, buildChannelConfigSchema } from "openclaw/plugin-sdk/nostr"; import { z } from "zod"; -import { MarkdownConfigSchema, buildChannelConfigSchema } from "../api.js"; /** * Validates https:// URLs only (no javascript:, data:, file:, etc.) From 09de192b770e851f9b5810b0818972b56c62f19f Mon Sep 17 00:00:00 2001 From: Gustavo Madeira Santana Date: Wed, 18 Mar 2026 02:18:02 +0000 Subject: [PATCH 5/7] Tlon: import channel account snapshot type --- extensions/tlon/src/channel.runtime.ts | 7 ++++++- extensions/tlon/src/channel.ts | 2 +- extensions/tlon/src/config-schema.ts | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/extensions/tlon/src/channel.runtime.ts b/extensions/tlon/src/channel.runtime.ts index c6523f61739..98da82480fa 100644 --- a/extensions/tlon/src/channel.runtime.ts +++ b/extensions/tlon/src/channel.runtime.ts @@ -1,6 +1,11 @@ import crypto from "node:crypto"; import { configureClient } from "@tloncorp/api"; -import type { ChannelOutboundAdapter, ChannelPlugin, OpenClawConfig } from "../api.js"; +import type { + ChannelAccountSnapshot, + ChannelOutboundAdapter, + ChannelPlugin, + OpenClawConfig, +} from "../api.js"; import { createLoggerBackedRuntime, createReplyPrefixOptions } from "../api.js"; import { monitorTlonProvider } from "./monitor/index.js"; import { tlonSetupWizard } from "./setup-surface.js"; diff --git a/extensions/tlon/src/channel.ts b/extensions/tlon/src/channel.ts index 0e22d237589..92d22feedd5 100644 --- a/extensions/tlon/src/channel.ts +++ b/extensions/tlon/src/channel.ts @@ -1,4 +1,5 @@ import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime"; +import type { ChannelAccountSnapshot, ChannelPlugin, OpenClawConfig } from "../api.js"; import { tlonChannelConfigSchema } from "./config-schema.js"; import { applyTlonSetupConfig, @@ -13,7 +14,6 @@ import { resolveTlonOutboundTarget, } from "./targets.js"; import { resolveTlonAccount, listTlonAccountIds } from "./types.js"; -import type { ChannelAccountSnapshot, ChannelPlugin, OpenClawConfig } from "../api.js"; import { validateUrbitBaseUrl } from "./urbit/base-url.js"; const TLON_CHANNEL_ID = "tlon" as const; diff --git a/extensions/tlon/src/config-schema.ts b/extensions/tlon/src/config-schema.ts index 7f12949f30d..e7ec5ef2ecf 100644 --- a/extensions/tlon/src/config-schema.ts +++ b/extensions/tlon/src/config-schema.ts @@ -1,5 +1,5 @@ -import { buildChannelConfigSchema } from "../api.js"; import { z } from "zod"; +import { buildChannelConfigSchema } from "../api.js"; const ShipSchema = z.string().min(1); const ChannelNestSchema = z.string().min(1); From bb803a42acb6c7ef90b1c902a117f3bd27e99756 Mon Sep 17 00:00:00 2001 From: Gustavo Madeira Santana Date: Wed, 18 Mar 2026 02:18:06 +0000 Subject: [PATCH 6/7] Mattermost: normalize plugin imports --- extensions/mattermost/src/config-schema.ts | 4 ++-- extensions/mattermost/src/group-mentions.ts | 2 +- extensions/mattermost/src/mattermost/directory.ts | 6 +----- extensions/mattermost/src/mattermost/interactions.ts | 6 +----- extensions/mattermost/src/mattermost/monitor-websocket.ts | 2 +- extensions/mattermost/src/mattermost/slash-http.ts | 2 +- extensions/mattermost/src/runtime.ts | 2 +- extensions/mattermost/src/setup-core.ts | 4 ++-- extensions/mattermost/src/setup-surface.ts | 8 ++++---- 9 files changed, 14 insertions(+), 22 deletions(-) diff --git a/extensions/mattermost/src/config-schema.ts b/extensions/mattermost/src/config-schema.ts index bd1f42dfd7f..e8e50371bd4 100644 --- a/extensions/mattermost/src/config-schema.ts +++ b/extensions/mattermost/src/config-schema.ts @@ -1,3 +1,5 @@ +import { z } from "zod"; +import { requireChannelOpenAllowFrom } from "../../shared/config-schema-helpers.js"; import { BlockStreamingCoalesceSchema, DmPolicySchema, @@ -5,8 +7,6 @@ import { MarkdownConfigSchema, requireOpenAllowFrom, } from "./runtime-api.js"; -import { z } from "zod"; -import { requireChannelOpenAllowFrom } from "../../shared/config-schema-helpers.js"; import { buildSecretInputSchema } from "./secret-input.js"; const DmChannelRetrySchema = z diff --git a/extensions/mattermost/src/group-mentions.ts b/extensions/mattermost/src/group-mentions.ts index 4996d115371..4d8d484d89c 100644 --- a/extensions/mattermost/src/group-mentions.ts +++ b/extensions/mattermost/src/group-mentions.ts @@ -1,6 +1,6 @@ import { resolveChannelGroupRequireMention } from "openclaw/plugin-sdk/channel-policy"; -import type { ChannelGroupContext } from "./runtime-api.js"; import { resolveMattermostAccount } from "./mattermost/accounts.js"; +import type { ChannelGroupContext } from "./runtime-api.js"; export function resolveMattermostGroupRequireMention( params: ChannelGroupContext & { requireMentionOverride?: boolean }, diff --git a/extensions/mattermost/src/mattermost/directory.ts b/extensions/mattermost/src/mattermost/directory.ts index 630ed7c7194..da6ce747f52 100644 --- a/extensions/mattermost/src/mattermost/directory.ts +++ b/extensions/mattermost/src/mattermost/directory.ts @@ -1,8 +1,4 @@ -import type { - ChannelDirectoryEntry, - OpenClawConfig, - RuntimeEnv, -} from "../runtime-api.js"; +import type { ChannelDirectoryEntry, OpenClawConfig, RuntimeEnv } from "../runtime-api.js"; import { listMattermostAccountIds, resolveMattermostAccount } from "./accounts.js"; import { createMattermostClient, diff --git a/extensions/mattermost/src/mattermost/interactions.ts b/extensions/mattermost/src/mattermost/interactions.ts index a51002667f8..fe11d037396 100644 --- a/extensions/mattermost/src/mattermost/interactions.ts +++ b/extensions/mattermost/src/mattermost/interactions.ts @@ -1,10 +1,6 @@ import { createHmac, timingSafeEqual } from "node:crypto"; import type { IncomingMessage, ServerResponse } from "node:http"; -import { - isTrustedProxyAddress, - resolveClientIp, - type OpenClawConfig, -} from "../runtime-api.js"; +import { isTrustedProxyAddress, resolveClientIp, type OpenClawConfig } from "../runtime-api.js"; import { getMattermostRuntime } from "../runtime.js"; import { updateMattermostPost, type MattermostClient, type MattermostPost } from "./client.js"; diff --git a/extensions/mattermost/src/mattermost/monitor-websocket.ts b/extensions/mattermost/src/mattermost/monitor-websocket.ts index c04affbae1d..09a1248c8cf 100644 --- a/extensions/mattermost/src/mattermost/monitor-websocket.ts +++ b/extensions/mattermost/src/mattermost/monitor-websocket.ts @@ -1,5 +1,5 @@ -import type { ChannelAccountSnapshot, RuntimeEnv } from "../runtime-api.js"; import WebSocket from "ws"; +import type { ChannelAccountSnapshot, RuntimeEnv } from "../runtime-api.js"; import type { MattermostPost } from "./client.js"; import { rawDataToString } from "./monitor-helpers.js"; diff --git a/extensions/mattermost/src/mattermost/slash-http.ts b/extensions/mattermost/src/mattermost/slash-http.ts index 401cc56172a..4d4d5f502a3 100644 --- a/extensions/mattermost/src/mattermost/slash-http.ts +++ b/extensions/mattermost/src/mattermost/slash-http.ts @@ -6,6 +6,7 @@ */ import type { IncomingMessage, ServerResponse } from "node:http"; +import type { ResolvedMattermostAccount } from "../mattermost/accounts.js"; import { buildModelsProviderData, createReplyPrefixOptions, @@ -17,7 +18,6 @@ import { type ReplyPayload, type RuntimeEnv, } from "../runtime-api.js"; -import type { ResolvedMattermostAccount } from "../mattermost/accounts.js"; import { getMattermostRuntime } from "../runtime.js"; import { createMattermostClient, diff --git a/extensions/mattermost/src/runtime.ts b/extensions/mattermost/src/runtime.ts index e238fa963e2..1fb88e059b7 100644 --- a/extensions/mattermost/src/runtime.ts +++ b/extensions/mattermost/src/runtime.ts @@ -1,5 +1,5 @@ -import type { PluginRuntime } from "./runtime-api.js"; import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store"; +import type { PluginRuntime } from "./runtime-api.js"; const { setRuntime: setMattermostRuntime, getRuntime: getMattermostRuntime } = createPluginRuntimeStore("Mattermost runtime not initialized"); diff --git a/extensions/mattermost/src/setup-core.ts b/extensions/mattermost/src/setup-core.ts index 13a4991fcd0..624a31a48c4 100644 --- a/extensions/mattermost/src/setup-core.ts +++ b/extensions/mattermost/src/setup-core.ts @@ -1,4 +1,6 @@ import type { ChannelSetupAdapter } from "openclaw/plugin-sdk/channel-runtime"; +import { resolveMattermostAccount, type ResolvedMattermostAccount } from "./mattermost/accounts.js"; +import { normalizeMattermostBaseUrl } from "./mattermost/client.js"; import { applyAccountNameToChannelSection, applySetupAccountConfigPatch, @@ -8,8 +10,6 @@ import { normalizeAccountId, type OpenClawConfig, } from "./runtime-api.js"; -import { resolveMattermostAccount, type ResolvedMattermostAccount } from "./mattermost/accounts.js"; -import { normalizeMattermostBaseUrl } from "./mattermost/client.js"; const channel = "mattermost" as const; diff --git a/extensions/mattermost/src/setup-surface.ts b/extensions/mattermost/src/setup-surface.ts index 385c4dc75e3..a439dd15006 100644 --- a/extensions/mattermost/src/setup-surface.ts +++ b/extensions/mattermost/src/setup-surface.ts @@ -1,13 +1,13 @@ +import { type ChannelSetupWizard } from "openclaw/plugin-sdk/setup"; +import { formatDocsLink } from "openclaw/plugin-sdk/setup"; +import { listMattermostAccountIds } from "./mattermost/accounts.js"; +import { normalizeMattermostBaseUrl } from "./mattermost/client.js"; import { applySetupAccountConfigPatch, DEFAULT_ACCOUNT_ID, hasConfiguredSecretInput, type OpenClawConfig, } from "./runtime-api.js"; -import { type ChannelSetupWizard } from "openclaw/plugin-sdk/setup"; -import { formatDocsLink } from "openclaw/plugin-sdk/setup"; -import { listMattermostAccountIds } from "./mattermost/accounts.js"; -import { normalizeMattermostBaseUrl } from "./mattermost/client.js"; import { isMattermostConfigured, mattermostSetupAdapter, From 9e8b9aba1fc3f482e4fe12bb236ce42690597efd Mon Sep 17 00:00:00 2001 From: Gustavo Madeira Santana Date: Wed, 18 Mar 2026 02:20:57 +0000 Subject: [PATCH 7/7] WhatsApp: isolate lazy action runtime boundary --- extensions/whatsapp/action-runtime.runtime.ts | 1 + src/plugins/runtime/runtime-whatsapp.ts | 4 ++-- src/plugins/runtime/types-channel.ts | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 extensions/whatsapp/action-runtime.runtime.ts diff --git a/extensions/whatsapp/action-runtime.runtime.ts b/extensions/whatsapp/action-runtime.runtime.ts new file mode 100644 index 00000000000..aeb44fc866b --- /dev/null +++ b/extensions/whatsapp/action-runtime.runtime.ts @@ -0,0 +1 @@ +export { handleWhatsAppAction } from "./src/action-runtime.js"; diff --git a/src/plugins/runtime/runtime-whatsapp.ts b/src/plugins/runtime/runtime-whatsapp.ts index 72bb3fd6af0..ba653942550 100644 --- a/src/plugins/runtime/runtime-whatsapp.ts +++ b/src/plugins/runtime/runtime-whatsapp.ts @@ -68,7 +68,7 @@ let webLoginQrPromise: Promise< > | null = null; let webChannelPromise: Promise | null = null; let whatsappActionsPromise: Promise< - typeof import("../../../extensions/whatsapp/runtime-api.js") + typeof import("../../../extensions/whatsapp/action-runtime.runtime.js") > | null = null; function loadWebLoginQr() { @@ -82,7 +82,7 @@ function loadWebChannel() { } function loadWhatsAppActions() { - whatsappActionsPromise ??= import("../../../extensions/whatsapp/runtime-api.js"); + whatsappActionsPromise ??= import("../../../extensions/whatsapp/action-runtime.runtime.js"); return whatsappActionsPromise; } diff --git a/src/plugins/runtime/types-channel.ts b/src/plugins/runtime/types-channel.ts index 6b0a0e3a8f6..f13dd010c0e 100644 --- a/src/plugins/runtime/types-channel.ts +++ b/src/plugins/runtime/types-channel.ts @@ -217,7 +217,7 @@ export type PluginRuntimeChannel = { startWebLoginWithQr: typeof import("../../../extensions/whatsapp/login-qr-api.js").startWebLoginWithQr; waitForWebLogin: typeof import("../../../extensions/whatsapp/login-qr-api.js").waitForWebLogin; monitorWebChannel: typeof import("../../channels/web/index.js").monitorWebChannel; - handleWhatsAppAction: typeof import("../../../extensions/whatsapp/runtime-api.js").handleWhatsAppAction; + handleWhatsAppAction: typeof import("../../../extensions/whatsapp/action-runtime.runtime.js").handleWhatsAppAction; createLoginTool: typeof import("./runtime-whatsapp-login-tool.js").createRuntimeWhatsAppLoginTool; }; line: {