# Channels Design > External messaging integrations for Qwen Code — interact with an agent from Telegram, WeChat, and more. > > User documentation: [Channels Overview](../../users/features/channels/overview.md). ## Overview A **channel** connects an external messaging platform to a Qwen Code agent. Configured in `settings.json`, managed via `qwen channel` subcommands, multi-user (each user gets an isolated ACP session). ## Architecture ``` ┌──────────┐ ┌─────────────────────────────────────┐ │ Telegram │ Platform API │ Channel Service │ │ User A │◄──────────────────────►│ │ ├──────────┤ (WebSocket/polling) │ ┌───────────┐ ┌──────────────┐ │ │ WeChat │◄──────────────────────►│ │ Platform │ │ ACP Bridge │ │ │ User B │ │ │ Adapter │ │ (shared) │ │ └──────────┘ │ │ │ │ │ │ │ │ - connect │ │ - spawns │ │ │ │ - receive │ │ qwen-code │ │ │ │ - send │ │ - manages │ │ │ │ │ │ sessions │ │ │ └─────┬──────┘ └──────┬───────┘ │ │ │ │ │ │ ▼ ▼ │ │ ┌─────────────────────────────────┐ │ │ │ SenderGate · GroupGate │ │ │ │ SessionRouter · ChannelBase │ │ │ └─────────────────────────────────┘ │ └─────────────────────────────────────┘ │ │ stdio (ACP ndjson) ▼ ┌─────────────────────────────────────┐ │ qwen-code --acp │ │ Session A (user alice, id: "abc") │ │ Session B (user bob, id: "def") │ └─────────────────────────────────────┘ ``` **Platform Adapter** — connects to external API, translates messages to/from Envelopes. **ACP Bridge** — spawns `qwen-code --acp`, manages sessions, emits `textChunk`/`toolCall`/`disconnected` events. **Session Router** — maps senders to ACP sessions via namespaced keys (`:`). **Sender Gate** / **Group Gate** — access control (allowlist / pairing / open) and mention gating. **Channel Base** — abstract base with Template Method pattern: plugins override `connect`, `sendMessage`, `disconnect`. **Channel Registry** — `Map` with collision detection. ### Envelope Normalized message format all platforms convert to: - **Identity**: `senderId`, `senderName`, `chatId`, `channelName` - **Content**: `text`, optional `imageBase64`/`imageMimeType`, optional `referencedText` - **Context**: `isGroup`, `isMentioned`, `isReplyToBot`, optional `threadId` Plugin responsibilities: `senderId` must be stable/unique; `chatId` must distinguish DMs from groups; boolean flags must be accurate for gate logic; @mentions stripped from `text`. ### Message Flow ``` Inbound: User message → Adapter → GroupGate → SenderGate → Slash commands → SessionRouter → AcpBridge → Agent Outbound: Agent response → AcpBridge → SessionRouter → Adapter → User ``` Slash commands (`/clear`, `/help`, `/status`) are handled in ChannelBase before reaching the agent. ### Sessions One `qwen-code --acp` process with multiple ACP sessions. Scope per channel: **`user`** (default), **`thread`**, or **`single`**. Routing keys namespaced as `:`. ### Error Handling - **Connection failures** — logged; service continues if at least one channel connects - **Bridge crashes** — exponential backoff (max 3 retries), `setBridge()` on all channels, session restore - **Session serialization** — per-session promise chains prevent concurrent prompt collisions ## Plugin System The architecture is extensible — new adapters (including third-party) can be added without modifying core. Built-in channels use the same plugin interface (dogfooding). ### Plugin Contract A `ChannelPlugin` declares `channelType`, `displayName`, `requiredConfigFields`, and a `createChannel()` factory. Plugins implement three methods: | Method | Responsibility | | --------------------------- | ------------------------------------------------- | | `connect()` | Connect to platform and register message handlers | | `sendMessage(chatId, text)` | Format and deliver agent response | | `disconnect()` | Clean up on shutdown | On inbound messages, plugins build an `Envelope` and call `this.handleInbound(envelope)` — the base class handles the rest: access control, group gating, pairing, session routing, prompt serialization, slash commands, instructions injection, reply context, and crash recovery. ### Extension Points - Custom slash commands via `registerCommand()` - Working indicators by wrapping `handleInbound()` with typing/reaction display - Tool call hooks via `onToolCall()` - Media handling by attaching to Envelope before `handleInbound()` ### Discovery & Loading External plugins are **extensions** managed by `ExtensionManager`, declared in `qwen-extension.json`: ```json { "name": "my-channel-extension", "version": "1.0.0", "channels": { "my-platform": { "entry": "dist/index.js", "displayName": "My Platform Channel" } } } ``` Loading sequence at `qwen channel start`: load settings → register built-ins → scan extensions → dynamic import + validate → register (reject collisions) → validate config → `createChannel()` → `connect()`. Plugins run in-process (no sandbox), same trust model as npm dependencies. ## Configuration ```jsonc { "channels": { "my-telegram": { "type": "telegram", "token": "$TELEGRAM_BOT_TOKEN", // env var reference "senderPolicy": "allowlist", // allowlist | pairing | open "allowedUsers": ["123456"], "sessionScope": "user", // user | thread | single "cwd": "/path/to/project", "model": "qwen3.5-plus", "instructions": "Keep responses short.", "groupPolicy": "disabled", // disabled | allowlist | open "groups": { "*": { "requireMention": true } }, }, }, } ``` Auth is plugin-specific: static token (Telegram), app credentials (DingTalk), QR code login (WeChat), proxy token (TMCP). ## CLI Commands ```bash # Channels qwen channel start [name] # start all or one channel qwen channel stop # stop running service qwen channel status # show channels, sessions, uptime qwen channel pairing list # pending pairing requests qwen channel pairing approve # approve a request # Extensions qwen extensions install # install qwen extensions link # symlink for dev qwen extensions list # show installed qwen extensions remove # uninstall ``` ## Package Structure ``` packages/channels/ ├── base/ # @qwen-code/channel-base │ └── src/ │ ├── AcpBridge.ts # ACP process lifecycle, session management │ ├── SessionRouter.ts # sender ↔ session mapping, persistence │ ├── SenderGate.ts # allowlist / pairing / open │ ├── GroupGate.ts # group chat policy + mention gating │ ├── PairingStore.ts # pairing code generation + approval │ ├── ChannelBase.ts # abstract base: routing, slash commands │ └── types.ts # Envelope, ChannelConfig, etc. ├── telegram/ # @qwen-code/channel-telegram ├── weixin/ # @qwen-code/channel-weixin └── dingtalk/ # @qwen-code/channel-dingtalk ``` ## Future Work ### Safety & Group Chat - **Per-group tool restrictions** — `tools`/`toolsBySender` deny/allow lists per group - **Group context history** — ring buffer of recent skipped messages, prepended on @mention - **Regex mention patterns** — fallback `mentionPatterns` for unreliable @mention metadata - **Per-group instructions** — `instructions` field on `GroupConfig` for per-group personas - **`/activation` command** — runtime toggle for `requireMention`, persisted to disk ### Operational Tooling - **`qwen channel doctor`** — config validation, env vars, bot tokens, network checks - **`qwen channel status --probe`** — real connectivity checks per channel ### Platform Expansion - **Discord** — Bot API + Gateway, servers/channels/DMs/threads - **Slack** — Bolt SDK, Socket Mode, workspaces/channels/DMs/threads ### Multi-Agent - **Multi-agent routing** — multiple agents with bindings per channel/group/user - **Broadcast groups** — multiple agents respond to the same message ### Plugin Ecosystem - **Community plugin template** — `create-qwen-channel` scaffolding tool - **Plugin registry/discovery** — `qwen extensions search`, version compatibility