Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
3c540d9
feat(channel): add QQ Bot channel adapter
Eric-GoodBoy-Tech Jun 16, 2026
4cde4c4
feat(qqbot): add QR login, group chat support with typed events
Eric-GoodBoy-Tech Jun 16, 2026
9ed5370
fix(qqbot): address PR review — lint errors, token refresh, security
Eric-GoodBoy-Tech Jun 16, 2026
27cb587
fix(qqbot): add qqbot to build order, fix ESLint default-case
Eric-GoodBoy-Tech Jun 16, 2026
8cd19f1
feat(qqbot): prepend sender name in group messages for shared context
Eric-GoodBoy-Tech Jun 17, 2026
518caaa
feat(qqbot): cross-server context continuation via SessionRouter pers…
Eric-GoodBoy-Tech Jun 17, 2026
db18055
feat(qqbot): add Session Resume + reconnect retry resilience
Eric-GoodBoy-Tech Jun 17, 2026
4faa0de
fix(qqbot): address review feedback from wenshao
Eric-GoodBoy-Tech Jun 17, 2026
bc7713d
fix(qqbot): address 3rd review from doudouOUC (12 issues)
Eric-GoodBoy-Tech Jun 17, 2026
f189903
fix(qqbot): robustness round — RESUMED, token expiry, SSRF, disposed,…
Eric-GoodBoy-Tech Jun 18, 2026
ba28d20
refactor(qqbot): split into modules — api, accounts, login
Eric-GoodBoy-Tech Jun 18, 2026
9a0c347
feat(qqbot): markdown message support (msg_type: 2)
Eric-GoodBoy-Tech Jun 18, 2026
36a9345
fix(qqbot): defensive patches from complete review
Eric-GoodBoy-Tech Jun 18, 2026
56b8937
fix(qqbot): guard against empty content in C2C and group handlers
Eric-GoodBoy-Tech Jun 18, 2026
c571eb5
fix(qqbot): close remaining review gaps — disposed guard, connectReje…
Eric-GoodBoy-Tech Jun 18, 2026
a497370
fix(qqbot): address wenshao review — RESUME restore removal, disposed…
Eric-GoodBoy-Tech Jun 18, 2026
27b7f1a
fix(qqbot): markdown fallback to plain text on rejection
Eric-GoodBoy-Tech Jun 18, 2026
ab1deb4
docs(qqbot): clarify markdown permission — Open Platform has no gate,…
Eric-GoodBoy-Tech Jun 18, 2026
dab4aff
feat(qqbot): add Ark (msg_type=3) and Media (msg_type=7) message support
Eric-GoodBoy-Tech Jun 18, 2026
e85263a
feat(qqbot): auto-route !ark / !media commands from LLM text via send…
Eric-GoodBoy-Tech Jun 18, 2026
b5d68f3
feat(qqbot): inject channel instructions for ark/media commands
Eric-GoodBoy-Tech Jun 18, 2026
b7aebf7
feat(qqbot): gate ark/media behind config flags (enableArk/enableMedia)
Eric-GoodBoy-Tech Jun 18, 2026
648fabe
refactor(qqbot): extract resolveRoute() to eliminate duplication acro…
Eric-GoodBoy-Tech Jun 18, 2026
1b52ce1
chore(qqbot): remove Ark and Media message support
Eric-GoodBoy-Tech Jun 18, 2026
14229c0
fix(qqbot): robustness patches for review findings
Eric-GoodBoy-Tech Jun 18, 2026
de5cec8
docs(channel): add QQ Bot user documentation
Eric-GoodBoy-Tech Jun 18, 2026
3ff9f0e
docs(qqbot): fix prerequisites — QR login needs no developer account
Eric-GoodBoy-Tech Jun 18, 2026
ab337aa
docs(qqbot): emphasize QR login, keep developer portal as secondary path
Eric-GoodBoy-Tech Jun 18, 2026
ce3505c
docs(qqbot): remove Images and Files section — not supported in chann…
Eric-GoodBoy-Tech Jun 18, 2026
1edf627
Merge branch 'main' into feat/channel-qqbot
Eric-GoodBoy-Tech Jun 18, 2026
7219ff0
test(qqbot): add unit tests for send utilities
Eric-GoodBoy-Tech Jun 18, 2026
c14a6e5
test(qqbot): add sendMessage flow tests with mocked API
Eric-GoodBoy-Tech Jun 18, 2026
d89b2e3
test(qqbot): fix review issues — add missing edge cases
Eric-GoodBoy-Tech Jun 18, 2026
c616752
test(qqbot): add api and accounts unit tests
Eric-GoodBoy-Tech Jun 18, 2026
141486f
chore(qqbot): suppress CodeQL ReDoS false positives
Eric-GoodBoy-Tech Jun 18, 2026
39b92fa
fix(qqbot): allow QR-code-only login and guard qrConnect return
Eric-GoodBoy-Tech Jun 18, 2026
09e75ef
chore(qqbot): add comments for requiredConfigFields and qrConnect guard
Eric-GoodBoy-Tech Jun 18, 2026
c2f25a3
fix(qqbot): replace quadratic regexes with linear patterns, remove fa…
Eric-GoodBoy-Tech Jun 18, 2026
9e598ec
fix(qqbot): split hasMarkdownSyntax into individual tests to pass CodeQL
Eric-GoodBoy-Tech Jun 18, 2026
5da1646
fix(qqbot): replace markdown link regex with indexOf to eliminate Cod…
Eric-GoodBoy-Tech Jun 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/users/features/channels/_meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ export default {
weixin: 'WeChat',
dingtalk: 'DingTalk',
feishu: 'Feishu',
qqbot: 'QQ Bot',
plugins: 'Plugins',
};
8 changes: 4 additions & 4 deletions docs/users/features/channels/overview.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Channels

Channels let you interact with a Qwen Code agent from messaging platforms like Telegram, WeChat, or DingTalk, instead of the terminal. You send messages from your phone or desktop chat app, and the agent responds just like it would in the CLI.
Channels let you interact with a Qwen Code agent from messaging platforms like Telegram, WeChat, QQ, or DingTalk, instead of the terminal. You send messages from your phone or desktop chat app, and the agent responds just like it would in the CLI.

## How It Works

Expand All @@ -15,7 +15,7 @@ All channels share one agent process with isolated sessions per user. Each chann

## Quick Start

1. Set up a bot on your messaging platform (see channel-specific guides: [Telegram](./telegram), [WeChat](./weixin), [DingTalk](./dingtalk))
1. Set up a bot on your messaging platform (see channel-specific guides: [Telegram](./telegram), [WeChat](./weixin), [QQ Bot](./qqbot), [DingTalk](./dingtalk))
2. Add the channel configuration to `~/.qwen/settings.json`
3. Run `qwen channel start` to start all channels, or `qwen channel start <name>` for a single channel

Expand Down Expand Up @@ -49,7 +49,7 @@ Channels are configured under the `channels` key in `settings.json`. Each channe

| Option | Required | Description |
| ------------------------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `type` | Yes | Channel type: `telegram`, `weixin`, `dingtalk`, or a custom type from an extension (see [Plugins](./plugins)) |
| `type` | Yes | Channel type: `telegram`, `weixin`, `qq`, `dingtalk`, `feishu`, or a custom type from an extension (see [Plugins](./plugins)) |
| `token` | Telegram | Bot token. Supports `$ENV_VAR` syntax to read from environment variables. Not needed for WeChat or DingTalk |
| `clientId` | DingTalk | DingTalk AppKey. Supports `$ENV_VAR` syntax |
| `clientSecret` | DingTalk | DingTalk AppSecret. Supports `$ENV_VAR` syntax |
Expand Down Expand Up @@ -292,7 +292,7 @@ Channels support slash commands. These are handled locally (no agent round-trip)

All other slash commands (e.g., `/compress`, `/summary`) are forwarded to the agent.

These commands work on all channel types (Telegram, WeChat, DingTalk).
These commands work on all channel types (Telegram, WeChat, QQ, DingTalk).

## Running

Expand Down
179 changes: 179 additions & 0 deletions docs/users/features/channels/qqbot.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
# QQ Bot (QQ机器人)

This guide covers setting up a Qwen Code channel on QQ via the official QQ Bot Open Platform API.

## Prerequisites

- A QQ account (mobile app for scanning the QR code)

## Setup

### QR Code Login

Start the channel — the first time it will show a QR code. Scan it with your QQ app to activate. No developer account or manual registration needed. Credentials are saved and reused automatically.

```json
{
"channels": {
"my-qq": {
"type": "qq"
}
}
}
```

```bash
qwen channel start my-qq
# Scan the QR code in the terminal with your QQ app
```

### Manual Configuration (Developer Portal)

You can also use credentials from the [QQ Bot Open Platform](https://q.qq.com/) developer portal if you already have an app registered there:

```json
{
"channels": {
"my-qq": {
"type": "qq",
"appID": "YOUR_APP_ID",
"appSecret": "$QQ_APP_SECRET"
}
}
}
```

Set the secret as an environment variable:

```bash
export QQ_APP_SECRET=<your-app-secret>
```

## Configuration

```json
{
"channels": {
"my-qq": {
"type": "qq",
"appID": "YOUR_APP_ID",
"appSecret": "$QQ_APP_SECRET",
"sandbox": false,
"senderPolicy": "open",
"sessionScope": "user",
"cwd": "/path/to/your/project",
"instructions": "你是一个通过 QQ Bot 对话的 AI 助手。回复控制在 2000 字符以内。",
"blockStreaming": "on",
"groupPolicy": "disabled",
"groups": {
"*": { "requireMention": true }
}
}
}
}
```

### QQ-Specific Options

| Option | Default | Description |
| ----------- | ------- | --------------------------------------------------------------------------------- |
| `appID` | — | QQ Bot AppID from developer portal. If omitted, QR code login is used. |
| `appSecret` | — | QQ Bot AppSecret. Supports `$ENV_VAR` syntax. If omitted, QR code login is used. |
| `sandbox` | `false` | Set to `true` to use the QQ sandbox API environment (`sandbox.api.sgroup.qq.com`) |

All standard channel options (see [Channel Overview](./overview#options)) are also supported:
`senderPolicy`, `allowedUsers`, `sessionScope`, `cwd`, `instructions`, `groupPolicy`, `groups`, `dispatchMode`, `blockStreaming`, `blockStreamingChunk`, `blockStreamingCoalesce`.

## Running

```bash
# Start only the QQ channel
qwen channel start my-qq

# Or start all configured channels together
qwen channel start
```

Open QQ and send a message to your bot. You should see the response arrive in your chat.

## Group Chats

To use the bot in QQ groups:

1. Set `groupPolicy` to `"allowlist"` or `"open"` in your channel config
2. Add the bot to a QQ group via the QQ Bot Open Platform dashboard or by having a group admin invite it
3. Group members must **@mention** the bot to trigger a response

QQ Bot API V2 only delivers group messages that @mention the bot — the bot does not see all group messages. By default, `requireMention` is `true` and should be left that way for QQ.

See [Group Chats](./overview#group-chats) for full details on group policies and mention gating.

## Markdown Support

The QQ Bot channel supports Markdown formatting (`msg_type=2`). The agent's Markdown responses are sent as-is, and QQ renders them with rich formatting (bold, italic, code blocks, links, lists).

If the QQ server rejects a Markdown message for any reason, the channel automatically retries it as plain text — so your messages always go through even if the bot's Markdown capability is restricted server-side.

This is the opposite of the WeChat channel, which strips all Markdown. You can let the agent use full Markdown with the QQ channel.

## Token Management

Access tokens expire after approximately 2 hours. The channel automatically refreshes them at 80% of their TTL (typically ~1.6 hours). If a refresh fails, it retries after 60 seconds.

Token refresh continues across WebSocket reconnects — the channel never goes offline due to an expired token as long as the AppID and AppSecret remain valid.

## Connection Resilience

- **Auto-reconnect:** On WebSocket disconnect, the channel retries with exponential backoff (up to 20 attempts, max 30 seconds between retries)
- **Session resume:** If the WebSocket drops briefly, the channel uses QQ's `RESUME` opcode to restore the session without losing in-flight messages
- **Cross-server context continuation:** Chat sessions and routing state are persisted to disk. If the daemon restarts, conversations continue from where they left off
- **Heartbeat monitoring:** HEARTBEAT_ACK timeouts are detected and force a reconnection to avoid zombie connections
- **Message deduplication:** Replayed messages after a reconnect are detected and skipped

## Tips

- **Use Markdown freely** — Unlike WeChat, QQ renders Markdown natively. Bold, code blocks, lists, and links all work.
- **Keep responses under 2000 characters** — Longer responses are automatically split into chunks. Adding a length hint to your instructions helps the agent stay concise.
- **Sandbox for testing** — Set `"sandbox": true` to use the sandbox API during development. No production messages will be affected.
- **Restrict access** — Use `senderPolicy: "allowlist"` for a fixed set of QQ users, or `"pairing"` to approve new users from the CLI. See [DM Pairing](./overview#dm-pairing) for details.

## Key Differences from Telegram

| Area | QQ Bot | Telegram |
| ---------------- | ------------------------------------------- | --------------------------------------------- |
| Authentication | QR code login or AppID/AppSecret | Static bot token from BotFather |
| Markdown | Native QQ Markdown with plaintext fallback | HTML-formatted from agent Markdown |
| Token lifecycle | 2h TTL, auto-refresh at 80% | Permanent bot token |
| Group messages | Only @mention messages are delivered to bot | Bot sees all messages (with privacy mode off) |
| Typing indicator | Not available (QQ API limitation) | "Working..." message |
| Sandbox mode | Supported for testing | Not available |

## Troubleshooting

### Bot doesn't respond

- Check the terminal output for errors
- Verify the channel is running (`qwen channel status`)
- If using `senderPolicy: "allowlist"`, make sure your QQ user ID is in `allowedUsers`
- On first start, a QR code will appear in the terminal — scan it with your QQ app

### Bot doesn't respond in groups

- Check that `groupPolicy` is set to `"allowlist"` or `"open"` (default is `"disabled"`)
- **You must @mention the bot** — QQ only delivers messages that tag the bot
- Verify the bot has been added to the group

### QR code login is stuck

- The QR code is displayed in the terminal. Scan it with your QQ mobile app (Me → Scan)
- If the QR code expires (typically after a few minutes), restart the channel to get a new one

### Markdown messages appear as plain text

- The QQ server may have rejected the Markdown message and the channel silently fell back to plain text. Check the terminal for `"Markdown rejected"` log messages
- This is unusual on the QQ Bot Open Platform but can happen if the bot's Markdown capability is restricted server-side

### Token expired after long downtime

- If the channel is offline for more than 2 hours, the access token will have expired. The channel fetches a fresh token on reconnect — no action needed
- If the AppSecret itself is invalid (e.g., rotated in the developer portal), update the `appSecret` field or delete `~/.qwen/channels/<name>-credentials.json` to re-trigger QR code login
39 changes: 38 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"packages/channels/weixin",
"packages/channels/dingtalk",
"packages/channels/feishu",
"packages/channels/qqbot",
"packages/channels/plugin-example",
"!packages/desktop"
],
Expand Down
29 changes: 29 additions & 0 deletions packages/channels/qqbot/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"name": "@qwen-code/channel-qqbot",
"version": "0.18.1",
"description": "QQ Bot (QQ机器人) channel adapter for Qwen Code",
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"files": [
"dist"
],
"scripts": {
"build": "tsc --build",
"test": "vitest run"
},
"dependencies": {
"@qwen-code/channel-base": "file:../base",
"@tencent-connect/qqbot-connector": "^1.1.0",
"ws": "^8.18.0"
},
"devDependencies": {
"typescript": "^5.0.0"
}
}
Loading
Loading