Skip to main content
reference

Channel capability matrix

Not every channel supports every feature. Telegram edits messages in place; email cannot. Slack uploads files; WhatsApp does not. The gateway reads each adapter's declared capabilities and degrades gracefully — a streamed reply falls back to a single message where edits are unavailable, and outbound media falls back to text where uploads are unsupported. This page is the authoritative lookup for what each adapter supports today.

Each capability is declared on the adapter itself (the canSendTyping / canEditMessage / canReact / canSendFiles flags and the capabilities manifest), so this table tracks source, not aspiration.

Support matrix​

CapabilityTelegramSlackDiscordWhatsAppEmail
Typing indicator✓✓ (probed)✓✗✗
Streaming draft edits✓✓✓✗✗
Message edit (editMessage)✓✓✓✗✗
Inbound media✓✓✓✓✗
Outbound media✓✓✗✗✗
Reactions✓✓✓✓✗
Threads / topics✓ (forum topics)✓ (thread_ts)✓✗✗
Reply-to a message✓✗✗✓✗
Approval buttons✓✓✓✗✗
Slash commands✓ (menu)✓✓✓ (typed)✓ (first line of body)
Voice in (transcribed)✓✓✓✓✗
Voice out (TTS)✓✓✓✓✗
Voice-out renderingvoice bubblefile + inline playerfile + inline playervoice bubble (ptt)—
Webhook mode✓✗✗✗✗
Max message length40963000200065536100000

✓ (probed) — Slack's typing indicator uses an unofficial API; the adapter probes it once at runtime and reports the real result thereafter, so canSendTyping reflects what actually works on the workspace.

Slash commands. The gateway commands — /stop, /new, /usage, /budget, /personality, /mute and the rest of the gateway surface in SLASH_COMMANDS (packages/surface-kit/src/slash-commands.ts) — are parsed by Gateway.handleMessage with no platform gate, so they work on every channel. WhatsApp has no command menu: type the command as a message. On Email, put the command on the first line of the body and reply in the same thread (the subject picks the conversation); the adapter drops the quoted reply and signature below a command line (commandOrBody in extensions/platform-email/src/index.ts). A message from an unverified email sender is never read as a command: the adapter prefixes its body with a sender warning, so the first word is not a command (pinned by extensions/platform-email/src/__tests__/email-adapter.test.ts, 'EmailAdapter slash commands'). Telegram's / menu is built from the same registry at startup.

Voice in requires the adapter to classify an inbound upload as type: 'audio' — the gateway's transcription gate keys on nothing else. All four chat adapters do: Telegram from voice and audio messages, Slack and Discord from the upload's extension or content type, WhatsApp from audioMessage. A .webm upload on Slack or Discord stays a video and is not transcribed.

Voice out is a separate declaration — voiceCaps plus sendVoiceNote, see Voice output caps below — which is why a channel can speak without being able to listen.

Voice output caps​

Voice output is declared per adapter as an AdapterVoiceCaps object, not inferred from method names. The gateway reads it through isVoiceOutboundAdapter and transcodes each reply into the first format the target accepts.

PlatformAccepted outbound formats (preferred first)Rendering (kind)Platform flagsSize cap
Telegramopus, ogg, mp3voice_note—50 MB
Slackmp3, m4a, wavfile—25 MB
Discordmp3, ogg, wavfile—25 MB
WhatsAppopus, oggvoice_noteptt: true16 MB
Email————
  • voice_note renders as a playable bubble; file renders as an upload the platform gives an inline player. Neither Slack nor Discord has a bot-accessible voice-bubble primitive, so both declare file rather than claiming one.
  • Producing the accepted container needs ffmpeg on the gateway host unless the TTS provider already emits it. Without ffmpeg, a mismatched reply is skipped (gateway.voice_format_unsupported) rather than delivered unplayable.
  • A reply over the size cap is skipped with gateway.voice_too_large; the text reply has already been delivered.

How degradation works​

The gateway never assumes a capability. Three behaviors depend directly on this matrix:

  • Streaming draft edits (W3.1). When a chat is streaming-enabled and the adapter reports canEditMessage, the gateway delivers the reply as throttled editMessage updates that grow in place. When the adapter cannot edit (WhatsApp, Email), the reply is delivered as a single final message instead. Streaming defaults on for direct messages and off for group chats; set display.streaming_edits in ~/.ethos/config.yaml to off, dms, or all.
  • Outbound media (W3.2). When a tool produces media and the adapter reports canSendFiles (Telegram, Slack), the gateway maps it to native attachments (sendPhoto / sendDocument on Telegram, files.uploadV2 on Slack). When the adapter cannot send files, the reply degrades to the text summary — no error, no dropped turn.
  • Spoken replies. When a conversation's voice mode says speak and the adapter declares voiceCaps, the gateway synthesizes the reply once, transcodes it to the first accepted format, and calls sendVoiceNote. When the adapter declares no caps, nothing is synthesized and the turn ends with the text reply already delivered — the skip is recorded as a gateway.voice_no_caps event rather than passing silently. See Send and receive voice notes on a channel.

Source​

Each adapter declares its capabilities in source. Consult the adapter for the exact runtime behavior:

PlatformAdapter
Telegramextensions/platform-telegram/src/index.ts
Slackextensions/platform-slack/src/adapter.ts
Discordextensions/platform-discord/src/index.ts
WhatsAppextensions/platform-whatsapp/src/index.ts
Emailextensions/platform-email/src/index.ts

The capability contract itself — ChannelCapabilities and the legacy AdapterCapabilities — lives in packages/types/src/platform.ts.

See also​