Channels: add message action capabilities

This commit is contained in:
Vincent Koc 2026-03-15 20:24:45 -07:00 committed by Peter Steinberger
parent 69a85325c3
commit 92bea9704e
15 changed files with 242 additions and 146 deletions

View File

@ -106,8 +106,10 @@ export const discordMessageActions: ChannelMessageActionAdapter = {
}
return Array.from(actions);
},
supportsInteractive: ({ cfg }) =>
listTokenSourcedAccounts(listEnabledDiscordAccounts(cfg)).length > 0,
getCapabilities: ({ cfg }) =>
listTokenSourcedAccounts(listEnabledDiscordAccounts(cfg)).length > 0
? (["interactive", "components"] as const)
: [],
extractToolSend: ({ args }) => {
const action = typeof args.action === "string" ? args.action.trim() : "";
if (action === "sendMessage") {

View File

@ -219,11 +219,11 @@ export const feishuPlugin: ChannelPlugin<ResolvedFeishuAccount> = {
}
return Array.from(actions);
},
supportsCards: ({ cfg }) => {
return (
cfg.channels?.feishu?.enabled !== false &&
getCapabilities: ({ cfg }) => {
return cfg.channels?.feishu?.enabled !== false &&
Boolean(resolveFeishuCredentials(cfg.channels?.feishu as FeishuConfig | undefined))
);
? (["cards"] as const)
: [];
},
handleAction: async (ctx) => {
const account = resolveFeishuAccount({ cfg: ctx.cfg, accountId: ctx.accountId ?? undefined });

View File

@ -71,11 +71,11 @@ const mattermostMessageActions: ChannelMessageActionAdapter = {
supportsAction: ({ action }) => {
return action === "send" || action === "react";
},
supportsButtons: ({ cfg }) => {
getCapabilities: ({ cfg }) => {
const accounts = listMattermostAccountIds(cfg)
.map((id) => resolveMattermostAccount({ cfg, accountId: id }))
.filter((a) => a.enabled && a.botToken?.trim() && a.baseUrl?.trim());
return accounts.length > 0;
return accounts.length > 0 ? (["buttons"] as const) : [];
},
handleAction: async ({ action, params, cfg, accountId }) => {
if (action === "react") {

View File

@ -366,11 +366,11 @@ export const msteamsPlugin: ChannelPlugin<ResolvedMSTeamsAccount> = {
}
return ["poll"] satisfies ChannelMessageActionName[];
},
supportsCards: ({ cfg }) => {
return (
cfg.channels?.msteams?.enabled !== false &&
getCapabilities: ({ cfg }) => {
return cfg.channels?.msteams?.enabled !== false &&
Boolean(resolveMSTeamsCredentials(cfg.channels?.msteams))
);
? (["cards"] as const)
: [];
},
handleAction: async (ctx) => {
// Handle send action with card parameter

View File

@ -292,6 +292,16 @@ export const slackPlugin: ChannelPlugin<ResolvedSlackAccount> = {
},
actions: {
listActions: ({ cfg }) => listSlackMessageActions(cfg),
getCapabilities: ({ cfg }) => {
const capabilities = new Set<"interactive" | "blocks">();
if (listSlackMessageActions(cfg).includes("send")) {
capabilities.add("blocks");
}
if (isSlackInteractiveRepliesEnabled({ cfg })) {
capabilities.add("interactive");
}
return Array.from(capabilities);
},
extractToolSend: ({ args }) => extractSlackToolSend(args),
handleAction: async (ctx) =>
await handleSlackMessageAction({

View File

@ -24,8 +24,8 @@ import {
listEnabledTelegramAccounts,
resolveTelegramPollActionGateState,
} from "./accounts.js";
import { buildTelegramInteractiveButtons } from "./button-types.js";
import { isTelegramInlineButtonsEnabled } from "./inline-buttons.js";
import { buildTelegramInteractiveButtons } from "./shared-interactive.js";
const providerId = "telegram";
@ -124,23 +124,15 @@ export const telegramMessageActions: ChannelMessageActionAdapter = {
}
return Array.from(actions);
},
supportsInteractive: ({ cfg }) => {
getCapabilities: ({ cfg }) => {
const accounts = listTokenSourcedAccounts(listEnabledTelegramAccounts(cfg));
if (accounts.length === 0) {
return false;
return [];
}
return accounts.some((account) =>
isTelegramInlineButtonsEnabled({ cfg, accountId: account.accountId }),
);
},
supportsButtons: ({ cfg }) => {
const accounts = listTokenSourcedAccounts(listEnabledTelegramAccounts(cfg));
if (accounts.length === 0) {
return false;
}
return accounts.some((account) =>
const buttonsEnabled = accounts.some((account) =>
isTelegramInlineButtonsEnabled({ cfg, accountId: account.accountId }),
);
return buttonsEnabled ? (["interactive", "buttons"] as const) : [];
},
extractToolSend: ({ args }) => {
return extractToolSend(args, "sendMessage");

View File

@ -24,7 +24,7 @@ export const zaloMessageActions: ChannelMessageActionAdapter = {
const actions = new Set<ChannelMessageActionName>(["send"]);
return Array.from(actions);
},
supportsButtons: () => false,
getCapabilities: () => [],
extractToolSend: ({ args }) => extractToolSend(args, "sendMessage"),
handleAction: async ({ action, params, cfg, accountId }) => {
if (action === "send") {

View File

@ -1,4 +1,5 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import type { ChannelMessageCapability } from "../../channels/plugins/message-capabilities.js";
import type { ChannelMessageActionName, ChannelPlugin } from "../../channels/plugins/types.js";
import type { MessageActionRunResult } from "../../infra/outbound/message-action-runner.js";
import { setActivePluginRegistry } from "../../plugins/runtime.js";
@ -47,9 +48,10 @@ function createChannelPlugin(params: {
blurb: string;
actions?: ChannelMessageActionName[];
listActions?: NonNullable<NonNullable<ChannelPlugin["actions"]>["listActions"]>;
supportsButtons?: boolean;
capabilities?: readonly ChannelMessageCapability[];
messaging?: ChannelPlugin["messaging"];
}): ChannelPlugin {
const actionCapabilities = params.capabilities;
return {
id: params.id as ChannelPlugin["id"],
meta: {
@ -71,7 +73,9 @@ function createChannelPlugin(params: {
(() => {
return (params.actions ?? []) as never;
}),
...(params.supportsButtons ? { supportsButtons: () => true } : {}),
...(actionCapabilities
? { getCapabilities: (_params: { cfg: unknown }) => actionCapabilities }
: {}),
},
};
}
@ -145,7 +149,7 @@ describe("message tool schema scoping", () => {
docsPath: "/channels/telegram",
blurb: "Telegram test plugin.",
actions: ["send", "react", "poll"],
supportsButtons: true,
capabilities: ["interactive", "buttons"],
});
const discordPlugin = createChannelPlugin({
@ -154,6 +158,16 @@ describe("message tool schema scoping", () => {
docsPath: "/channels/discord",
blurb: "Discord test plugin.",
actions: ["send", "poll", "poll-vote"],
capabilities: ["interactive", "components"],
});
const slackPlugin = createChannelPlugin({
id: "slack",
label: "Slack",
docsPath: "/channels/slack",
blurb: "Slack test plugin.",
actions: ["send", "react"],
capabilities: ["interactive", "blocks"],
});
afterEach(() => {
@ -164,6 +178,7 @@ describe("message tool schema scoping", () => {
{
provider: "telegram",
expectComponents: false,
expectBlocks: false,
expectButtons: true,
expectButtonStyle: true,
expectTelegramPollExtras: true,
@ -172,16 +187,27 @@ describe("message tool schema scoping", () => {
{
provider: "discord",
expectComponents: true,
expectBlocks: false,
expectButtons: false,
expectButtonStyle: false,
expectTelegramPollExtras: true,
expectedActions: ["send", "poll", "poll-vote", "react"],
},
{
provider: "slack",
expectComponents: false,
expectBlocks: true,
expectButtons: false,
expectButtonStyle: false,
expectTelegramPollExtras: true,
expectedActions: ["send", "react", "poll", "poll-vote"],
},
])(
"scopes schema fields for $provider",
({
provider,
expectComponents,
expectBlocks,
expectButtons,
expectButtonStyle,
expectTelegramPollExtras,
@ -191,6 +217,7 @@ describe("message tool schema scoping", () => {
createTestRegistry([
{ pluginId: "telegram", source: "test", plugin: telegramPlugin },
{ pluginId: "discord", source: "test", plugin: discordPlugin },
{ pluginId: "slack", source: "test", plugin: slackPlugin },
]),
);
@ -206,6 +233,11 @@ describe("message tool schema scoping", () => {
} else {
expect(properties.components).toBeUndefined();
}
if (expectBlocks) {
expect(properties.blocks).toBeDefined();
} else {
expect(properties.blocks).toBeUndefined();
}
if (expectButtons) {
expect(properties.buttons).toBeDefined();
} else {
@ -263,7 +295,7 @@ describe("message tool schema scoping", () => {
.channels?.telegram;
return telegramCfg?.actions?.poll === false ? ["send", "react"] : ["send", "react", "poll"];
},
supportsButtons: true,
capabilities: ["interactive", "buttons"],
});
setActivePluginRegistry(

View File

@ -2,14 +2,11 @@ import { Type } from "@sinclair/typebox";
import { BLUEBUBBLES_GROUP_ACTIONS } from "../../channels/plugins/bluebubbles-actions.js";
import { listChannelPlugins } from "../../channels/plugins/index.js";
import {
supportsChannelMessageInteractive,
supportsChannelMessageInteractiveForChannel,
channelSupportsMessageCapability,
channelSupportsMessageCapabilityForChannel,
listChannelMessageActions,
supportsChannelMessageButtons,
supportsChannelMessageButtonsForChannel,
supportsChannelMessageCards,
supportsChannelMessageCardsForChannel,
} from "../../channels/plugins/message-actions.js";
import type { ChannelMessageCapability } from "../../channels/plugins/message-capabilities.js";
import {
CHANNEL_MESSAGE_ACTION_NAMES,
type ChannelMessageActionName,
@ -199,6 +196,7 @@ function buildSendSchema(options: {
includeButtons: boolean;
includeCards: boolean;
includeComponents: boolean;
includeBlocks: boolean;
}) {
const props: Record<string, unknown> = {
message: Type.Optional(Type.String()),
@ -265,6 +263,17 @@ function buildSendSchema(options: {
),
),
components: Type.Optional(discordComponentMessageSchema),
blocks: Type.Optional(
Type.Array(
Type.Object(
{},
{
additionalProperties: true,
description: "Slack Block Kit payload blocks (Slack only).",
},
),
),
),
};
if (!options.includeButtons) {
delete props.buttons;
@ -278,6 +287,9 @@ function buildSendSchema(options: {
if (!options.includeComponents) {
delete props.components;
}
if (!options.includeBlocks) {
delete props.blocks;
}
return props;
}
@ -487,6 +499,7 @@ function buildMessageToolSchemaProps(options: {
includeButtons: boolean;
includeCards: boolean;
includeComponents: boolean;
includeBlocks: boolean;
includeTelegramPollExtras: boolean;
}) {
return {
@ -513,6 +526,7 @@ function buildMessageToolSchemaFromActions(
includeButtons: boolean;
includeCards: boolean;
includeComponents: boolean;
includeBlocks: boolean;
includeTelegramPollExtras: boolean;
},
) {
@ -528,6 +542,7 @@ const MessageToolSchema = buildMessageToolSchemaFromActions(AllMessageActions, {
includeButtons: true,
includeCards: true,
includeComponents: true,
includeBlocks: true,
includeTelegramPollExtras: true,
});
@ -578,30 +593,59 @@ function resolveMessageToolSchemaActions(params: {
return actions.length > 0 ? actions : ["send"];
}
function resolveIncludeCapability(
params: {
cfg: OpenClawConfig;
currentChannelProvider?: string;
},
capability: ChannelMessageCapability,
): boolean {
const currentChannel = normalizeMessageChannel(params.currentChannelProvider);
if (currentChannel) {
return channelSupportsMessageCapabilityForChannel(
{
cfg: params.cfg,
channel: currentChannel,
},
capability,
);
}
return channelSupportsMessageCapability(params.cfg, capability);
}
function resolveIncludeComponents(params: {
cfg: OpenClawConfig;
currentChannelProvider?: string;
}): boolean {
const currentChannel = normalizeMessageChannel(params.currentChannelProvider);
if (currentChannel) {
return currentChannel === "discord";
}
// Components are currently Discord-specific.
return listChannelSupportedActions({ cfg: params.cfg, channel: "discord" }).length > 0;
return resolveIncludeCapability(params, "components");
}
function resolveIncludeInteractive(params: {
cfg: OpenClawConfig;
currentChannelProvider?: string;
}): boolean {
const currentChannel = normalizeMessageChannel(params.currentChannelProvider);
if (currentChannel) {
return supportsChannelMessageInteractiveForChannel({
cfg: params.cfg,
channel: currentChannel,
});
}
return supportsChannelMessageInteractive(params.cfg);
return resolveIncludeCapability(params, "interactive");
}
function resolveIncludeButtons(params: {
cfg: OpenClawConfig;
currentChannelProvider?: string;
}): boolean {
return resolveIncludeCapability(params, "buttons");
}
function resolveIncludeCards(params: {
cfg: OpenClawConfig;
currentChannelProvider?: string;
}): boolean {
return resolveIncludeCapability(params, "cards");
}
function resolveIncludeBlocks(params: {
cfg: OpenClawConfig;
currentChannelProvider?: string;
}): boolean {
return resolveIncludeCapability(params, "blocks");
}
function resolveIncludeTelegramPollExtras(params: {
@ -619,22 +663,19 @@ function buildMessageToolSchema(params: {
currentChannelProvider?: string;
currentChannelId?: string;
}) {
const currentChannel = normalizeMessageChannel(params.currentChannelProvider);
const actions = resolveMessageToolSchemaActions(params);
const includeInteractive = resolveIncludeInteractive(params);
const includeButtons = currentChannel
? supportsChannelMessageButtonsForChannel({ cfg: params.cfg, channel: currentChannel })
: supportsChannelMessageButtons(params.cfg);
const includeCards = currentChannel
? supportsChannelMessageCardsForChannel({ cfg: params.cfg, channel: currentChannel })
: supportsChannelMessageCards(params.cfg);
const includeButtons = resolveIncludeButtons(params);
const includeCards = resolveIncludeCards(params);
const includeComponents = resolveIncludeComponents(params);
const includeBlocks = resolveIncludeBlocks(params);
const includeTelegramPollExtras = resolveIncludeTelegramPollExtras(params);
return buildMessageToolSchemaFromActions(actions.length > 0 ? actions : ["send"], {
includeInteractive,
includeButtons,
includeCards,
includeComponents,
includeBlocks,
includeTelegramPollExtras,
});
}

View File

@ -6,22 +6,19 @@ import {
createTestRegistry,
} from "../../test-utils/channel-plugins.js";
import {
supportsChannelMessageButtons,
supportsChannelMessageButtonsForChannel,
supportsChannelMessageCards,
supportsChannelMessageCardsForChannel,
supportsChannelMessageInteractive,
supportsChannelMessageInteractiveForChannel,
channelSupportsMessageCapability,
channelSupportsMessageCapabilityForChannel,
listChannelMessageCapabilities,
listChannelMessageCapabilitiesForChannel,
} from "./message-actions.js";
import type { ChannelMessageCapability } from "./message-capabilities.js";
import type { ChannelPlugin } from "./types.js";
const emptyRegistry = createTestRegistry([]);
function createMessageActionsPlugin(params: {
id: "discord" | "telegram";
supportsInteractive: boolean;
supportsButtons: boolean;
supportsCards: boolean;
capabilities: readonly ChannelMessageCapability[];
}): ChannelPlugin {
return {
...createChannelTestPluginBase({
@ -34,25 +31,19 @@ function createMessageActionsPlugin(params: {
}),
actions: {
listActions: () => ["send"],
supportsInteractive: () => params.supportsInteractive,
supportsButtons: () => params.supportsButtons,
supportsCards: () => params.supportsCards,
getCapabilities: () => params.capabilities,
},
};
}
const buttonsPlugin = createMessageActionsPlugin({
id: "discord",
supportsInteractive: true,
supportsButtons: true,
supportsCards: false,
capabilities: ["interactive", "buttons"],
});
const cardsPlugin = createMessageActionsPlugin({
id: "telegram",
supportsInteractive: false,
supportsButtons: false,
supportsCards: true,
capabilities: ["cards"],
});
function activateMessageActionTestRegistry() {
@ -69,38 +60,66 @@ describe("message action capability checks", () => {
setActivePluginRegistry(emptyRegistry);
});
it("aggregates buttons/card support across plugins", () => {
it("aggregates capabilities across plugins", () => {
activateMessageActionTestRegistry();
expect(supportsChannelMessageInteractive({} as OpenClawConfig)).toBe(true);
expect(supportsChannelMessageButtons({} as OpenClawConfig)).toBe(true);
expect(supportsChannelMessageCards({} as OpenClawConfig)).toBe(true);
expect(listChannelMessageCapabilities({} as OpenClawConfig).toSorted()).toEqual([
"buttons",
"cards",
"interactive",
]);
expect(channelSupportsMessageCapability({} as OpenClawConfig, "interactive")).toBe(true);
expect(channelSupportsMessageCapability({} as OpenClawConfig, "buttons")).toBe(true);
expect(channelSupportsMessageCapability({} as OpenClawConfig, "cards")).toBe(true);
});
it("checks per-channel capabilities", () => {
activateMessageActionTestRegistry();
expect(
supportsChannelMessageInteractiveForChannel({
listChannelMessageCapabilitiesForChannel({
cfg: {} as OpenClawConfig,
channel: "discord",
}),
).toBe(true);
).toEqual(["interactive", "buttons"]);
expect(
supportsChannelMessageInteractiveForChannel({
listChannelMessageCapabilitiesForChannel({
cfg: {} as OpenClawConfig,
channel: "telegram",
}),
).toBe(false);
).toEqual(["cards"]);
expect(
supportsChannelMessageButtonsForChannel({ cfg: {} as OpenClawConfig, channel: "discord" }),
channelSupportsMessageCapabilityForChannel(
{ cfg: {} as OpenClawConfig, channel: "discord" },
"interactive",
),
).toBe(true);
expect(
supportsChannelMessageButtonsForChannel({ cfg: {} as OpenClawConfig, channel: "telegram" }),
channelSupportsMessageCapabilityForChannel(
{ cfg: {} as OpenClawConfig, channel: "telegram" },
"interactive",
),
).toBe(false);
expect(
supportsChannelMessageCardsForChannel({ cfg: {} as OpenClawConfig, channel: "telegram" }),
channelSupportsMessageCapabilityForChannel(
{ cfg: {} as OpenClawConfig, channel: "discord" },
"buttons",
),
).toBe(true);
expect(supportsChannelMessageCardsForChannel({ cfg: {} as OpenClawConfig })).toBe(false);
expect(
channelSupportsMessageCapabilityForChannel(
{ cfg: {} as OpenClawConfig, channel: "telegram" },
"buttons",
),
).toBe(false);
expect(
channelSupportsMessageCapabilityForChannel(
{ cfg: {} as OpenClawConfig, channel: "telegram" },
"cards",
),
).toBe(true);
expect(channelSupportsMessageCapabilityForChannel({ cfg: {} as OpenClawConfig }, "cards")).toBe(
false,
);
});
});

View File

@ -1,6 +1,7 @@
import type { AgentToolResult } from "@mariozechner/pi-agent-core";
import type { OpenClawConfig } from "../../config/config.js";
import { getChannelPlugin, listChannelPlugins } from "./index.js";
import type { ChannelMessageCapability } from "./message-capabilities.js";
import type { ChannelMessageActionContext, ChannelMessageActionName } from "./types.js";
const trustedRequesterRequiredByChannel: Readonly<
@ -30,72 +31,52 @@ export function listChannelMessageActions(cfg: OpenClawConfig): ChannelMessageAc
return Array.from(actions);
}
export function supportsChannelMessageButtons(cfg: OpenClawConfig): boolean {
return supportsMessageFeature(cfg, (actions) => actions?.supportsButtons?.({ cfg }) === true);
}
export function supportsChannelMessageInteractive(cfg: OpenClawConfig): boolean {
return supportsMessageFeature(cfg, (actions) => actions?.supportsInteractive?.({ cfg }) === true);
}
export function supportsChannelMessageButtonsForChannel(params: {
cfg: OpenClawConfig;
channel?: string;
}): boolean {
return supportsMessageFeatureForChannel(
params,
(actions) => actions.supportsButtons?.(params) === true,
);
}
export function supportsChannelMessageInteractiveForChannel(params: {
cfg: OpenClawConfig;
channel?: string;
}): boolean {
return supportsMessageFeatureForChannel(
params,
(actions) => actions.supportsInteractive?.(params) === true,
);
}
export function supportsChannelMessageCards(cfg: OpenClawConfig): boolean {
return supportsMessageFeature(cfg, (actions) => actions?.supportsCards?.({ cfg }) === true);
}
export function supportsChannelMessageCardsForChannel(params: {
cfg: OpenClawConfig;
channel?: string;
}): boolean {
return supportsMessageFeatureForChannel(
params,
(actions) => actions.supportsCards?.(params) === true,
);
}
function supportsMessageFeature(
function listCapabilities(
actions: ChannelActions,
cfg: OpenClawConfig,
check: (actions: ChannelActions) => boolean,
): boolean {
): readonly ChannelMessageCapability[] {
return actions.getCapabilities?.({ cfg }) ?? [];
}
export function listChannelMessageCapabilities(cfg: OpenClawConfig): ChannelMessageCapability[] {
const capabilities = new Set<ChannelMessageCapability>();
for (const plugin of listChannelPlugins()) {
if (plugin.actions && check(plugin.actions)) {
return true;
if (!plugin.actions) {
continue;
}
for (const capability of listCapabilities(plugin.actions, cfg)) {
capabilities.add(capability);
}
}
return false;
return Array.from(capabilities);
}
function supportsMessageFeatureForChannel(
export function listChannelMessageCapabilitiesForChannel(params: {
cfg: OpenClawConfig;
channel?: string;
}): ChannelMessageCapability[] {
if (!params.channel) {
return [];
}
const plugin = getChannelPlugin(params.channel as Parameters<typeof getChannelPlugin>[0]);
return plugin?.actions ? Array.from(listCapabilities(plugin.actions, params.cfg)) : [];
}
export function channelSupportsMessageCapability(
cfg: OpenClawConfig,
capability: ChannelMessageCapability,
): boolean {
return listChannelMessageCapabilities(cfg).includes(capability);
}
export function channelSupportsMessageCapabilityForChannel(
params: {
cfg: OpenClawConfig;
channel?: string;
},
check: (actions: ChannelActions) => boolean,
capability: ChannelMessageCapability,
): boolean {
if (!params.channel) {
return false;
}
const plugin = getChannelPlugin(params.channel as Parameters<typeof getChannelPlugin>[0]);
return plugin?.actions ? check(plugin.actions) : false;
return listChannelMessageCapabilitiesForChannel(params).includes(capability);
}
export async function dispatchChannelMessageAction(

View File

@ -0,0 +1,9 @@
export const CHANNEL_MESSAGE_CAPABILITIES = [
"interactive",
"buttons",
"cards",
"components",
"blocks",
] as const;
export type ChannelMessageCapability = (typeof CHANNEL_MESSAGE_CAPABILITIES)[number];

View File

@ -11,7 +11,16 @@ import type { ChannelMessageActionAdapter } from "./types.js";
export function createSlackActions(providerId: string): ChannelMessageActionAdapter {
return {
listActions: ({ cfg }) => listSlackMessageActions(cfg),
supportsInteractive: ({ cfg }) => isSlackInteractiveRepliesEnabled({ cfg }),
getCapabilities: ({ cfg }) => {
const capabilities = new Set<"interactive" | "blocks">();
if (listSlackMessageActions(cfg).includes("send")) {
capabilities.add("blocks");
}
if (isSlackInteractiveRepliesEnabled({ cfg })) {
capabilities.add("interactive");
}
return Array.from(capabilities);
},
extractToolSend: ({ args }) => extractSlackToolSend(args),
handleAction: async (ctx) => {
return await handleSlackMessageAction({

View File

@ -7,6 +7,7 @@ import type { GatewayClientMode, GatewayClientName } from "../../utils/message-c
import type { ChatType } from "../chat-type.js";
import type { ChatChannelId } from "../registry.js";
import type { ChannelMessageActionName as ChannelMessageActionNameFromList } from "./message-action-names.js";
import type { ChannelMessageCapability } from "./message-capabilities.js";
export type ChannelId = ChatChannelId | (string & {});
@ -372,9 +373,7 @@ export type ChannelMessageActionAdapter = {
*/
listActions?: (params: { cfg: OpenClawConfig }) => ChannelMessageActionName[];
supportsAction?: (params: { action: ChannelMessageActionName }) => boolean;
supportsInteractive?: (params: { cfg: OpenClawConfig }) => boolean;
supportsButtons?: (params: { cfg: OpenClawConfig }) => boolean;
supportsCards?: (params: { cfg: OpenClawConfig }) => boolean;
getCapabilities?: (params: { cfg: OpenClawConfig }) => readonly ChannelMessageCapability[];
extractToolSend?: (params: { args: Record<string, unknown> }) => ChannelToolSend | null;
handleAction?: (ctx: ChannelMessageActionContext) => Promise<AgentToolResult<unknown>>;
};

View File

@ -1,8 +1,10 @@
import type { ChannelMessageActionName as ChannelMessageActionNameFromList } from "./message-action-names.js";
export { CHANNEL_MESSAGE_ACTION_NAMES } from "./message-action-names.js";
export { CHANNEL_MESSAGE_CAPABILITIES } from "./message-capabilities.js";
export type ChannelMessageActionName = ChannelMessageActionNameFromList;
export type { ChannelMessageCapability } from "./message-capabilities.js";
export type {
ChannelAuthAdapter,