diff --git a/extensions/discord/package.json b/extensions/discord/package.json index 030193ad5ad..5b2470105c4 100644 --- a/extensions/discord/package.json +++ b/extensions/discord/package.json @@ -34,7 +34,8 @@ "docsPath": "/channels/discord", "docsLabel": "discord", "blurb": "very well supported right now.", - "systemImage": "bubble.left.and.bubble.right" + "systemImage": "bubble.left.and.bubble.right", + "markdownCapable": true }, "install": { "npmSpec": "@openclaw/discord", diff --git a/extensions/googlechat/package.json b/extensions/googlechat/package.json index 24cea61c76e..119e511c5b4 100644 --- a/extensions/googlechat/package.json +++ b/extensions/googlechat/package.json @@ -30,12 +30,14 @@ "detailLabel": "Google Chat", "docsPath": "/channels/googlechat", "docsLabel": "googlechat", - "blurb": "Google Workspace Chat app via HTTP webhooks.", + "blurb": "Google Workspace Chat app with HTTP webhook.", "aliases": [ "gchat", "google-chat" ], - "order": 55 + "order": 55, + "systemImage": "message.badge", + "markdownCapable": true }, "install": { "npmSpec": "@openclaw/googlechat", diff --git a/extensions/irc/package.json b/extensions/irc/package.json index 33116af83f7..1b9d8f62684 100644 --- a/extensions/irc/package.json +++ b/extensions/irc/package.json @@ -19,6 +19,9 @@ "docsPath": "/channels/irc", "docsLabel": "irc", "blurb": "classic IRC networks with DM/channel routing and pairing controls.", + "aliases": [ + "internet-relay-chat" + ], "systemImage": "network" } } diff --git a/extensions/line/package.json b/extensions/line/package.json index 850c3e8e7a9..7a086fcdd3d 100644 --- a/extensions/line/package.json +++ b/extensions/line/package.json @@ -24,9 +24,11 @@ "id": "line", "label": "LINE", "selectionLabel": "LINE (Messaging API)", + "detailLabel": "LINE Bot", "docsPath": "/channels/line", "docsLabel": "line", - "blurb": "LINE Messaging API bot for Japan/Taiwan/Thailand markets.", + "blurb": "LINE Messaging API webhook bot.", + "systemImage": "message", "order": 75, "quickstartAllowFrom": true }, diff --git a/extensions/signal/package.json b/extensions/signal/package.json index f2594a061fc..0df7c5f8685 100644 --- a/extensions/signal/package.json +++ b/extensions/signal/package.json @@ -17,7 +17,8 @@ "docsPath": "/channels/signal", "docsLabel": "signal", "blurb": "signal-cli linked device; more setup (David Reagans: \"Hop on Discord.\").", - "systemImage": "antenna.radiowaves.left.and.right" + "systemImage": "antenna.radiowaves.left.and.right", + "markdownCapable": true } } } diff --git a/extensions/slack/package.json b/extensions/slack/package.json index 7fd530c22cf..844b4938f8c 100644 --- a/extensions/slack/package.json +++ b/extensions/slack/package.json @@ -21,7 +21,8 @@ "docsPath": "/channels/slack", "docsLabel": "slack", "blurb": "supported (Socket Mode).", - "systemImage": "number" + "systemImage": "number", + "markdownCapable": true }, "bundle": { "stageRuntimeDependencies": true diff --git a/extensions/telegram/package.json b/extensions/telegram/package.json index 9128ad95aac..88ef8b2505f 100644 --- a/extensions/telegram/package.json +++ b/extensions/telegram/package.json @@ -22,7 +22,13 @@ "docsPath": "/channels/telegram", "docsLabel": "telegram", "blurb": "simplest way to get started — register a bot with @BotFather and get going.", - "systemImage": "paperplane" + "systemImage": "paperplane", + "selectionDocsPrefix": "", + "selectionDocsOmitLabel": true, + "selectionExtras": [ + "https://openclaw.ai" + ], + "markdownCapable": true }, "bundle": { "stageRuntimeDependencies": true diff --git a/src/channels/chat-meta.ts b/src/channels/chat-meta.ts index f4ddd75143c..eaf42510e1e 100644 --- a/src/channels/chat-meta.ts +++ b/src/channels/chat-meta.ts @@ -1,112 +1,111 @@ +import { GENERATED_BUNDLED_PLUGIN_METADATA } from "../plugins/bundled-plugin-metadata.generated.js"; +import type { PluginPackageChannel } from "../plugins/manifest.js"; import { CHAT_CHANNEL_ORDER, type ChatChannelId } from "./ids.js"; import type { ChannelMeta } from "./plugins/types.js"; export type ChatChannelMeta = ChannelMeta; -const WEBSITE_URL = "https://openclaw.ai"; +const CHAT_CHANNEL_ID_SET = new Set(CHAT_CHANNEL_ORDER); -const CHAT_CHANNEL_META: Record = { - telegram: { - id: "telegram", - label: "Telegram", - selectionLabel: "Telegram (Bot API)", - detailLabel: "Telegram Bot", - docsPath: "/channels/telegram", - docsLabel: "telegram", - blurb: "simplest way to get started — register a bot with @BotFather and get going.", - systemImage: "paperplane", - selectionDocsPrefix: "", - selectionDocsOmitLabel: true, - selectionExtras: [WEBSITE_URL], - }, - whatsapp: { - id: "whatsapp", - label: "WhatsApp", - selectionLabel: "WhatsApp (QR link)", - detailLabel: "WhatsApp Web", - docsPath: "/channels/whatsapp", - docsLabel: "whatsapp", - blurb: "works with your own number; recommend a separate phone + eSIM.", - systemImage: "message", - }, - discord: { - id: "discord", - label: "Discord", - selectionLabel: "Discord (Bot API)", - detailLabel: "Discord Bot", - docsPath: "/channels/discord", - docsLabel: "discord", - blurb: "very well supported right now.", - systemImage: "bubble.left.and.bubble.right", - }, - irc: { - id: "irc", - label: "IRC", - selectionLabel: "IRC (Server + Nick)", - detailLabel: "IRC", - docsPath: "/channels/irc", - docsLabel: "irc", - blurb: "classic IRC networks with DM/channel routing and pairing controls.", - systemImage: "network", - }, - googlechat: { - id: "googlechat", - label: "Google Chat", - selectionLabel: "Google Chat (Chat API)", - detailLabel: "Google Chat", - docsPath: "/channels/googlechat", - docsLabel: "googlechat", - blurb: "Google Workspace Chat app with HTTP webhook.", - systemImage: "message.badge", - }, - slack: { - id: "slack", - label: "Slack", - selectionLabel: "Slack (Socket Mode)", - detailLabel: "Slack Bot", - docsPath: "/channels/slack", - docsLabel: "slack", - blurb: "supported (Socket Mode).", - systemImage: "number", - }, - signal: { - id: "signal", - label: "Signal", - selectionLabel: "Signal (signal-cli)", - detailLabel: "Signal REST", - docsPath: "/channels/signal", - docsLabel: "signal", - blurb: 'signal-cli linked device; more setup (David Reagans: "Hop on Discord.").', - systemImage: "antenna.radiowaves.left.and.right", - }, - imessage: { - id: "imessage", - label: "iMessage", - selectionLabel: "iMessage (imsg)", - detailLabel: "iMessage", - docsPath: "/channels/imessage", - docsLabel: "imessage", - blurb: "this is still a work in progress.", - systemImage: "message.fill", - }, - line: { - id: "line", - label: "LINE", - selectionLabel: "LINE (Messaging API)", - detailLabel: "LINE Bot", - docsPath: "/channels/line", - docsLabel: "line", - blurb: "LINE Messaging API webhook bot.", - systemImage: "message", - }, -}; +function toChatChannelMeta(params: { + id: ChatChannelId; + channel: PluginPackageChannel; +}): ChatChannelMeta { + const label = params.channel.label?.trim(); + if (!label) { + throw new Error(`Missing label for bundled chat channel "${params.id}"`); + } -export const CHAT_CHANNEL_ALIASES: Record = { - imsg: "imessage", - "internet-relay-chat": "irc", - "google-chat": "googlechat", - gchat: "googlechat", -}; + return { + id: params.id, + label, + selectionLabel: params.channel.selectionLabel?.trim() || label, + docsPath: params.channel.docsPath?.trim() || `/channels/${params.id}`, + docsLabel: params.channel.docsLabel?.trim() || undefined, + blurb: params.channel.blurb?.trim() || "", + ...(params.channel.aliases?.length ? { aliases: params.channel.aliases } : {}), + ...(params.channel.order !== undefined ? { order: params.channel.order } : {}), + ...(params.channel.selectionDocsPrefix !== undefined + ? { selectionDocsPrefix: params.channel.selectionDocsPrefix } + : {}), + ...(params.channel.selectionDocsOmitLabel !== undefined + ? { selectionDocsOmitLabel: params.channel.selectionDocsOmitLabel } + : {}), + ...(params.channel.selectionExtras?.length + ? { selectionExtras: params.channel.selectionExtras } + : {}), + ...(params.channel.detailLabel?.trim() + ? { detailLabel: params.channel.detailLabel.trim() } + : {}), + ...(params.channel.systemImage?.trim() + ? { systemImage: params.channel.systemImage.trim() } + : {}), + ...(params.channel.markdownCapable !== undefined + ? { markdownCapable: params.channel.markdownCapable } + : {}), + ...(params.channel.showConfigured !== undefined + ? { showConfigured: params.channel.showConfigured } + : {}), + ...(params.channel.quickstartAllowFrom !== undefined + ? { quickstartAllowFrom: params.channel.quickstartAllowFrom } + : {}), + ...(params.channel.forceAccountBinding !== undefined + ? { forceAccountBinding: params.channel.forceAccountBinding } + : {}), + ...(params.channel.preferSessionLookupForAnnounceTarget !== undefined + ? { + preferSessionLookupForAnnounceTarget: params.channel.preferSessionLookupForAnnounceTarget, + } + : {}), + ...(params.channel.preferOver?.length ? { preferOver: params.channel.preferOver } : {}), + }; +} + +function buildChatChannelMetaById(): Record { + const entries = new Map(); + + for (const entry of GENERATED_BUNDLED_PLUGIN_METADATA) { + const channel = + entry.packageManifest && "channel" in entry.packageManifest + ? entry.packageManifest.channel + : undefined; + if (!channel) { + continue; + } + const rawId = channel?.id?.trim(); + if (!rawId || !CHAT_CHANNEL_ID_SET.has(rawId)) { + continue; + } + const id = rawId as ChatChannelId; + entries.set( + id, + toChatChannelMeta({ + id, + channel, + }), + ); + } + + const missingIds = CHAT_CHANNEL_ORDER.filter((id) => !entries.has(id)); + if (missingIds.length > 0) { + throw new Error(`Missing bundled chat channel metadata for: ${missingIds.join(", ")}`); + } + + return Object.freeze(Object.fromEntries(entries)) as Record; +} + +const CHAT_CHANNEL_META = buildChatChannelMetaById(); + +export const CHAT_CHANNEL_ALIASES: Record = Object.freeze( + Object.fromEntries( + Object.values(CHAT_CHANNEL_META) + .flatMap((meta) => + (meta.aliases ?? []).map((alias) => [alias.trim().toLowerCase(), meta.id] as const), + ) + .filter(([alias]) => alias.length > 0) + .toSorted(([left], [right]) => left.localeCompare(right)), + ), +) as Record; function normalizeChannelKey(raw?: string | null): string | undefined { const normalized = raw?.trim().toLowerCase(); diff --git a/src/channels/plugins/catalog.ts b/src/channels/plugins/catalog.ts index 08d3eda3ab1..3bc54ebb48c 100644 --- a/src/channels/plugins/catalog.ts +++ b/src/channels/plugins/catalog.ts @@ -200,6 +200,9 @@ function toChannelMeta(params: { : {}), ...(params.channel.selectionExtras ? { selectionExtras: params.channel.selectionExtras } : {}), ...(systemImage ? { systemImage } : {}), + ...(params.channel.markdownCapable !== undefined + ? { markdownCapable: params.channel.markdownCapable } + : {}), ...(params.channel.showConfigured !== undefined ? { showConfigured: params.channel.showConfigured } : {}), diff --git a/src/channels/plugins/types.core.ts b/src/channels/plugins/types.core.ts index e007ffd9443..a941f5f2ec2 100644 --- a/src/channels/plugins/types.core.ts +++ b/src/channels/plugins/types.core.ts @@ -127,17 +127,18 @@ export type ChannelMeta = { docsLabel?: string; blurb: string; order?: number; - aliases?: string[]; + aliases?: readonly string[]; selectionDocsPrefix?: string; selectionDocsOmitLabel?: boolean; - selectionExtras?: string[]; + selectionExtras?: readonly string[]; detailLabel?: string; systemImage?: string; + markdownCapable?: boolean; showConfigured?: boolean; quickstartAllowFrom?: boolean; forceAccountBinding?: boolean; preferSessionLookupForAnnounceTarget?: boolean; - preferOver?: string[]; + preferOver?: readonly string[]; }; /** Snapshot row returned by channel status and lifecycle surfaces. */ diff --git a/src/channels/registry.ts b/src/channels/registry.ts index f2152bb9917..35d2473958b 100644 --- a/src/channels/registry.ts +++ b/src/channels/registry.ts @@ -8,14 +8,14 @@ import { type ChatChannelMeta, } from "./chat-meta.js"; import { CHANNEL_IDS, CHAT_CHANNEL_ORDER, type ChatChannelId } from "./ids.js"; -import type { ChannelId } from "./plugins/types.js"; +import type { ChannelId, ChannelMeta } from "./plugins/types.js"; export { CHANNEL_IDS, CHAT_CHANNEL_ORDER } from "./ids.js"; export type { ChatChannelId } from "./ids.js"; type RegisteredChannelPluginEntry = { plugin: { id?: string | null; - meta?: { aliases?: string[] | null } | null; + meta?: Pick | null; }; }; @@ -39,6 +39,18 @@ function findRegisteredChannelPluginEntry( }); } +function findRegisteredChannelPluginEntryById( + id: string, +): RegisteredChannelPluginEntry | undefined { + const normalizedId = normalizeChannelKey(id); + if (!normalizedId) { + return undefined; + } + return listRegisteredChannelPluginEntries().find( + (entry) => normalizeChannelKey(entry.plugin.id) === normalizedId, + ); +} + const normalizeChannelKey = (raw?: string | null): string | undefined => { const normalized = raw?.trim().toLowerCase(); return normalized || undefined; @@ -80,6 +92,12 @@ export function listRegisteredChannelPluginAliases(): string[] { return listRegisteredChannelPluginEntries().flatMap((entry) => entry.plugin.meta?.aliases ?? []); } +export function getRegisteredChannelPluginMeta( + id: string, +): Pick | null { + return findRegisteredChannelPluginEntryById(id)?.plugin.meta ?? null; +} + export function formatChannelPrimerLine(meta: ChatChannelMeta): string { return `${meta.label}: ${meta.blurb}`; } diff --git a/src/config/plugin-auto-enable.ts b/src/config/plugin-auto-enable.ts index 7f275484d0b..e02cfb68923 100644 --- a/src/config/plugin-auto-enable.ts +++ b/src/config/plugin-auto-enable.ts @@ -349,16 +349,16 @@ function resolvePreferredOverIds( ): string[] { const normalized = normalizeChatChannelId(pluginId); if (normalized) { - return getChatChannelMeta(normalized).preferOver ?? []; + return [...(getChatChannelMeta(normalized).preferOver ?? [])]; } const installedPlugin = registry.plugins.find((record) => record.id === pluginId); const manifestChannelPreferOver = installedPlugin?.channelConfigs?.[pluginId]?.preferOver; if (manifestChannelPreferOver?.length) { - return manifestChannelPreferOver; + return [...manifestChannelPreferOver]; } const installedChannelMeta = installedPlugin?.channelCatalogMeta; if (installedChannelMeta?.preferOver?.length) { - return installedChannelMeta.preferOver; + return [...installedChannelMeta.preferOver]; } return resolveExternalCatalogPreferOver(pluginId, env); } diff --git a/src/infra/outbound/current-conversation-bindings.ts b/src/infra/outbound/current-conversation-bindings.ts index 0d8eeec7b65..e8117e2894f 100644 --- a/src/infra/outbound/current-conversation-bindings.ts +++ b/src/infra/outbound/current-conversation-bindings.ts @@ -125,7 +125,7 @@ function resolveChannelSupportsCurrentConversationBinding(channel: string): bool if (!normalized) { return false; } - const matchesPluginId = (plugin: { id: string; meta?: { aliases?: string[] } }) => + const matchesPluginId = (plugin: { id: string; meta?: { aliases?: readonly string[] } }) => plugin.id === normalized || (plugin.meta?.aliases ?? []).some((alias) => alias.trim().toLowerCase() === normalized); const plugin = diff --git a/src/plugins/bundled-plugin-metadata.generated.ts b/src/plugins/bundled-plugin-metadata.generated.ts index 0bb7ec82112..dba1027cb9d 100644 --- a/src/plugins/bundled-plugin-metadata.generated.ts +++ b/src/plugins/bundled-plugin-metadata.generated.ts @@ -1369,6 +1369,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ docsLabel: "discord", blurb: "very well supported right now.", systemImage: "bubble.left.and.bubble.right", + markdownCapable: true, }, install: { npmSpec: "@openclaw/discord", @@ -5574,9 +5575,11 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ detailLabel: "Google Chat", docsPath: "/channels/googlechat", docsLabel: "googlechat", - blurb: "Google Workspace Chat app via HTTP webhooks.", + blurb: "Google Workspace Chat app with HTTP webhook.", aliases: ["gchat", "google-chat"], order: 55, + systemImage: "message.badge", + markdownCapable: true, }, install: { npmSpec: "@openclaw/googlechat", @@ -6368,7 +6371,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ additionalProperties: false, }, label: "Google Chat", - description: "Google Workspace Chat app via HTTP webhooks.", + description: "Google Workspace Chat app with HTTP webhook.", }, }, }, @@ -7094,6 +7097,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ docsPath: "/channels/irc", docsLabel: "irc", blurb: "classic IRC networks with DM/channel routing and pairing controls.", + aliases: ["internet-relay-chat"], systemImage: "network", }, install: { @@ -7851,9 +7855,11 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ id: "line", label: "LINE", selectionLabel: "LINE (Messaging API)", + detailLabel: "LINE Bot", docsPath: "/channels/line", docsLabel: "line", - blurb: "LINE Messaging API bot for Japan/Taiwan/Thailand markets.", + blurb: "LINE Messaging API webhook bot.", + systemImage: "message", order: 75, quickstartAllowFrom: true, }, @@ -8105,7 +8111,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ additionalProperties: false, }, label: "LINE", - description: "LINE Messaging API bot for Japan/Taiwan/Thailand markets.", + description: "LINE Messaging API webhook bot.", }, }, }, @@ -11938,6 +11944,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ docsLabel: "signal", blurb: 'signal-cli linked device; more setup (David Reagans: "Hop on Discord.").', systemImage: "antenna.radiowaves.left.and.right", + markdownCapable: true, }, }, manifest: { @@ -12634,6 +12641,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ docsLabel: "slack", blurb: "supported (Socket Mode).", systemImage: "number", + markdownCapable: true, }, }, manifest: { @@ -14549,6 +14557,10 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ docsLabel: "telegram", blurb: "simplest way to get started — register a bot with @BotFather and get going.", systemImage: "paperplane", + selectionDocsPrefix: "", + selectionDocsOmitLabel: true, + selectionExtras: ["https://openclaw.ai"], + markdownCapable: true, }, }, manifest: { diff --git a/src/plugins/manifest-registry.ts b/src/plugins/manifest-registry.ts index 297d21314f4..2999ba99578 100644 --- a/src/plugins/manifest-registry.ts +++ b/src/plugins/manifest-registry.ts @@ -74,7 +74,7 @@ export type PluginManifestRecord = { id: string; label?: string; blurb?: string; - preferOver?: string[]; + preferOver?: readonly string[]; }; }; diff --git a/src/plugins/manifest.ts b/src/plugins/manifest.ts index 516a3d96af6..b5cdf1b58f6 100644 --- a/src/plugins/manifest.ts +++ b/src/plugins/manifest.ts @@ -343,12 +343,13 @@ export type PluginPackageChannel = { docsLabel?: string; blurb?: string; order?: number; - aliases?: string[]; - preferOver?: string[]; + aliases?: readonly string[]; + preferOver?: readonly string[]; systemImage?: string; selectionDocsPrefix?: string; selectionDocsOmitLabel?: boolean; - selectionExtras?: string[]; + selectionExtras?: readonly string[]; + markdownCapable?: boolean; showConfigured?: boolean; quickstartAllowFrom?: boolean; forceAccountBinding?: boolean; diff --git a/src/test-utils/channel-plugins.ts b/src/test-utils/channel-plugins.ts index d6b3f571f05..2e4e19fa86a 100644 --- a/src/test-utils/channel-plugins.ts +++ b/src/test-utils/channel-plugins.ts @@ -43,6 +43,7 @@ export const createChannelTestPluginBase = (params: { id: ChannelId; label?: string; docsPath?: string; + markdownCapable?: boolean; capabilities?: ChannelCapabilities; config?: Partial; }): Pick => ({ @@ -53,6 +54,7 @@ export const createChannelTestPluginBase = (params: { selectionLabel: params.label ?? String(params.id), docsPath: params.docsPath ?? `/channels/${params.id}`, blurb: "test stub.", + ...(params.markdownCapable !== undefined ? { markdownCapable: params.markdownCapable } : {}), }, capabilities: params.capabilities ?? { chatTypes: ["direct"] }, config: { diff --git a/src/utils/message-channel.test.ts b/src/utils/message-channel.test.ts index 185ddb52226..823b3e0e7dd 100644 --- a/src/utils/message-channel.test.ts +++ b/src/utils/message-channel.test.ts @@ -2,7 +2,10 @@ import { afterEach, beforeEach, describe, expect, it } from "vitest"; import type { ChannelPlugin } from "../channels/plugins/types.js"; import { setActivePluginRegistry } from "../plugins/runtime.js"; import { createChannelTestPluginBase, createTestRegistry } from "../test-utils/channel-plugins.js"; -import { resolveGatewayMessageChannel } from "./message-channel.js"; +import { + isMarkdownCapableMessageChannel, + resolveGatewayMessageChannel, +} from "./message-channel.js"; const emptyRegistry = createTestRegistry([]); const demoAliasPlugin: ChannelPlugin = { @@ -21,6 +24,15 @@ const demoAliasPlugin: ChannelPlugin = { }, }; +const demoMarkdownPlugin: ChannelPlugin = { + ...createChannelTestPluginBase({ + id: "demo-markdown-channel", + label: "Demo Markdown Channel", + docsPath: "/channels/demo-markdown-channel", + markdownCapable: true, + }), +}; + describe("message-channel", () => { beforeEach(() => { setActivePluginRegistry(emptyRegistry); @@ -45,4 +57,15 @@ describe("message-channel", () => { ); expect(resolveGatewayMessageChannel("workspace-chat")).toBe("demo-alias-channel"); }); + + it("reads markdown capability from channel metadata", () => { + expect(isMarkdownCapableMessageChannel("telegram")).toBe(true); + expect(isMarkdownCapableMessageChannel("whatsapp")).toBe(false); + setActivePluginRegistry( + createTestRegistry([ + { pluginId: "demo-markdown-channel", plugin: demoMarkdownPlugin, source: "test" }, + ]), + ); + expect(isMarkdownCapableMessageChannel("demo-markdown-channel")).toBe(true); + }); }); diff --git a/src/utils/message-channel.ts b/src/utils/message-channel.ts index 1113911a2ad..e4946b381f3 100644 --- a/src/utils/message-channel.ts +++ b/src/utils/message-channel.ts @@ -1,6 +1,8 @@ import type { ChannelId } from "../channels/plugins/types.js"; import { CHANNEL_IDS, + getChatChannelMeta, + getRegisteredChannelPluginMeta, listRegisteredChannelPluginAliases, listRegisteredChannelPluginIds, listChatChannelAliases, @@ -19,16 +21,6 @@ import { export const INTERNAL_MESSAGE_CHANNEL = "webchat" as const; export type InternalMessageChannel = typeof INTERNAL_MESSAGE_CHANNEL; -const MARKDOWN_CAPABLE_CHANNELS = new Set([ - "slack", - "telegram", - "signal", - "discord", - "googlechat", - "tui", - INTERNAL_MESSAGE_CHANNEL, -]); - export { GATEWAY_CLIENT_NAMES, GATEWAY_CLIENT_MODES }; export type { GatewayClientName, GatewayClientMode }; export { normalizeGatewayClientName, normalizeGatewayClientMode }; @@ -139,5 +131,12 @@ export function isMarkdownCapableMessageChannel(raw?: string | null): boolean { if (!channel) { return false; } - return MARKDOWN_CAPABLE_CHANNELS.has(channel); + if (channel === INTERNAL_MESSAGE_CHANNEL || channel === "tui") { + return true; + } + const builtInChannel = normalizeChatChannelId(channel); + if (builtInChannel) { + return getChatChannelMeta(builtInChannel).markdownCapable === true; + } + return getRegisteredChannelPluginMeta(channel)?.markdownCapable === true; }