Skip to main content
We can’t develop against every third-party IM platform, so our channel adapters follow a single normalization and routing contract, and each adapter is pinned to that contract’s behavior. This is the conformance contract; the tests live in apps/monad/test/integration/channels/channel-conformance.test.ts (+ telegram normalization is a pure, tested function normalizeTelegramMessage).

Shared contracts

Contracts this pass added: command lowercasing (/NEW == /new), outbound chunking at maxMessageChars, the group require-mention gate, and the per-channel agent hint. We also grew from one channel (Telegram) to seventeen adapters spanning every inbound style — long-poll (Telegram), dial-out WebSocket (Discord Gateway, Slack Socket Mode, QQ, WhatsApp Web), raw TCP (IRC), inbound webhook (LINE, WhatsApp Business, Twilio, Feishu, WeCom, Teams, Google Chat, iMessage, generic), IMAP polling (email), and child process (Signal).

Design decisions (where platform conventions differ, we pick a stance)

Deliberately NOT matched (out of scope / by design)

  • Markdown: our Telegram adapter sends plain text (markdown:false) to avoid MarkdownV2 parse failures. (We chose robustness over rich formatting for v1.) (Discord/Slack adapters set their own markdown capability.)
  • Progressive-edit throttle constants: we throttle edits at ~1.2s. Same shape, different constants from any given platform’s batch delays.
  • Media passthrough: media[] is captured-but-empty; attachments aren’t forwarded yet.

Per-platform review (16 adapters)

A review of every adapter confirmed the inbound-normalization, mention-gating (entity/ mentioned-array, not substring), reply-to-bot-as-implicit-mention, token caching, and signature schemes are aligned with each platform’s documented behavior. Dedup is intentionally core-side (ChannelService.seen), not per-adapter. Four issues were found and fixed:
  • WeCom decrypt (correctness bug): WeCom pads PKCS7 to a 32-byte block (pad can exceed 16), which Web Crypto’s AES-CBC rejects. Switched decryptWecom to node:crypto + setAutoPadding(false) with manual strip, and added the receive_id === corpId anti-spoof check.
  • IRC outbound injection (security): agent output is hostile — sanitizeIrcText strips CR/LF + control chars and sanitizeIrcTarget validates the target so a reply can’t smuggle a raw IRC command.
  • Teams serviceUrl SSRF (security): without inbound JWT validation a forged activity could point serviceUrl at an attacker host and leak the AAD token; isAllowedTeamsServiceUrl allowlists Bot Framework hosts before a serviceUrl is remembered.
  • Feishu signature (security): verify SHA256(timestamp+nonce+encryptKey+body) on the raw body when an encrypt key is configured, and reject AES-encrypt payloads (disable encryption in the console) rather than mis-parsing them.
Accepted divergences (low value to change): Telegram text_mention entity unhandled; Slack thread-reply not treated as implicit mention; outbound length uses hard platform caps (the core splitForLimit already splits at boundaries) rather than headroom constants. WhatsApp linked-device self-chat is an intentional A3 exception: the adapter assigns every Monad outbound a tracked native message ID and drops only those echoes. A fromMe message authored on another linked device is accepted only when its chat JID matches the connected account, which makes the account’s self-chat usable without treating messages sent to other contacts as inbound turns.

How to extend the conformance suite

  1. Derive the rule from the platform’s documented behavior, and express it as an INPUT → expected-OUTPUT pair.
  2. If it’s adapter-level (platform payload → ChannelInbound), test normalizeTelegramMessage (or your adapter’s pure normalizer) directly.
  3. If it’s core-level (routing/keying/auth/render), drive ChannelService with a mock adapter (see coreHarness in the test) or createRenderer with a capturing adapter.