mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-05-01 21:20:44 +00:00
docs(channels): add design documentation for channels feature
- Architecture overview with platform adapters and ACP bridge - Plugin system contract and extension loading - Implementation guides for Telegram, WeChat, DingTalk - Testing guide with mock servers and E2E scenarios - Feature roadmap and known limitations These docs provide the foundation for the external messaging integrations. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
parent
7962d4f790
commit
bac0ba0cc2
4 changed files with 504 additions and 0 deletions
190
docs/design/channels/channels-design.md
Normal file
190
docs/design/channels/channels-design.md
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
# Channels Design
|
||||
|
||||
> External messaging integrations for Qwen Code — interact with an agent from Telegram, WeChat, and more.
|
||||
>
|
||||
> Channel-implementation status: `channels-implementation.md`. Testing: `channels-testing-guide.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 andy, 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 (`<channel>:<sender>`). **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<string, ChannelPlugin>` 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 `<channelName>:<key>`.
|
||||
|
||||
### 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 <ch> # pending pairing requests
|
||||
qwen channel pairing approve <ch> <code> # approve a request
|
||||
|
||||
# Extensions
|
||||
qwen extensions install <path-or-package> # install
|
||||
qwen extensions link <local-path> # symlink for dev
|
||||
qwen extensions list # show installed
|
||||
qwen extensions remove <name> # 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
|
||||
```
|
||||
|
||||
## What's Next
|
||||
|
||||
- **DingTalk: quoted bot responses** — persist outbound text keyed by `processQueryKey` (see `channels-dingtalk.md`)
|
||||
- **Streaming responses** — edit messages in-place as chunks arrive
|
||||
- **Structured logging** — pino; JSON by default, human-readable on TTY
|
||||
- **E2E tests** — mock servers for platform APIs + mock ACP agent
|
||||
- **Daemon mode** — background operation, systemd/launchd unit generation
|
||||
|
||||
## Known Limitations
|
||||
|
||||
- **Shared workspace conflicts** — multiple users editing the same `cwd` may cause file conflicts
|
||||
- **Crash-recovery sessions only** — sessions persist for bridge restarts but cleared on clean shutdown
|
||||
- **Sequential prompts per session** — messages queue within a session; different sessions run independently
|
||||
- **Single instance** — PID file prevents duplicates; `qwen channel stop` first
|
||||
- **Shared bridge model** — all channels share one ACP bridge process; if channels configure different models, only the first is used (warning shown)
|
||||
107
docs/design/channels/channels-implementation.md
Normal file
107
docs/design/channels/channels-implementation.md
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
# Channels
|
||||
|
||||
Qwen Code supports three messaging channels — Telegram, WeChat, and DingTalk. All adapters extend the shared channel architecture (`ChannelBase`, `AcpBridge`, `SessionRouter`) in `packages/channels/base/src/`. Each channel can be started individually or all together with `node dist/cli.js channel start`.
|
||||
|
||||
---
|
||||
|
||||
## Telegram
|
||||
|
||||
Source: `packages/channels/telegram/src/TelegramAdapter.ts`, built on the Telegraf library.
|
||||
|
||||
The adapter supports plain text messaging, slash commands, a working indicator ("typing" chat action), DM pairing, and group chat (supergroups with @mention gating). Image receiving works via `bot.on('photo')` → `getFileLink` → download → base64, with captions passed as envelope text. File/document receiving saves downloaded files to `/tmp/channel-files/` and includes the path in the envelope so the agent can read them via `read-file` (works with any model, no multimodal required). Referenced messages include the quoted text as context in the prompt. Output is formatted as Telegram HTML (converted from markdown). Authentication uses a static bot token. Session persistence and pairing state are stored under `~/.qwen/channels/`.
|
||||
|
||||
```jsonc
|
||||
// ~/.qwen/settings.json
|
||||
{
|
||||
"channels": {
|
||||
"my-telegram": {
|
||||
"type": "telegram",
|
||||
"token": "$TELEGRAM_BOT_TOKEN",
|
||||
"senderPolicy": "pairing",
|
||||
"allowedUsers": [],
|
||||
"sessionScope": "user",
|
||||
"instructions": "Keep responses concise.",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
```bash
|
||||
source /home/andy/projects/telegram/.env
|
||||
npm run bundle && node dist/cli.js channel start my-telegram
|
||||
```
|
||||
|
||||
**Future work:** Streaming responses via in-place `editMessageText` (throttled at ~2s to respect rate limits, best-effort fallback to single message). Slash command polish — register with BotFather via `setMyCommands()`, fix `/help` timing, add `/status` command.
|
||||
|
||||
---
|
||||
|
||||
## WeChat (Weixin)
|
||||
|
||||
Source: `packages/channels/weixin/src/`, ported from the cc-weixin project. Uses the iLink Bot API at `ilinkai.weixin.qq.com`.
|
||||
|
||||
The adapter supports plain text messaging via a custom long-poll loop (`/ilink/bot/getupdates`, cursor-based), with `context_token` caching per user for reply context. Authentication uses QR code login (`qwen channel configure-weixin`), producing a bearer token stored in `~/.qwen/channels/weixin/account.json`. A typing indicator fires before each ACP prompt using the `sendTyping` API (ticket obtained from `getConfig`). Image and file/PDF receiving works through CDN download with AES-128-ECB decryption — images are forwarded as base64 content blocks, files are saved to `/tmp/channel-files/` and referenced by path. Referenced messages (user replies) include quoted text as context in the prompt. Formatting is plain text only (all markdown is stripped). The adapter handles session expiry (`errcode -14`) with automatic reconnection, uses backoff after consecutive errors, and persists the polling cursor to `~/.qwen/channels/weixin/cursor.txt` for crash recovery.
|
||||
|
||||
```jsonc
|
||||
// ~/.qwen/settings.json
|
||||
{
|
||||
"channels": {
|
||||
"my-weixin": {
|
||||
"type": "weixin",
|
||||
"senderPolicy": "pairing",
|
||||
"allowedUsers": [],
|
||||
"sessionScope": "user",
|
||||
"instructions": "Keep responses concise, plain text only.",
|
||||
"baseUrl": "https://ilinkai.weixin.qq.com", // optional override
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Credentials are stored separately in `~/.qwen/channels/weixin/account.json`, created by `qwen channel configure-weixin`.
|
||||
|
||||
```bash
|
||||
# First time: login via QR code
|
||||
node dist/cli.js channel configure-weixin
|
||||
|
||||
# Start
|
||||
npm run bundle && node dist/cli.js channel start my-weixin
|
||||
```
|
||||
|
||||
**Future work:** Media send (upload to WeChat CDN with AES encryption). Voice/video receive. Streaming responses via `message_state: GENERATING` → `FINISH` (pending client-side investigation). Multi-account support. Message chunking for long responses.
|
||||
|
||||
---
|
||||
|
||||
## DingTalk (钉钉)
|
||||
|
||||
Source: `packages/channels/dingtalk/src/`, using Stream mode (WebSocket, no public IP required). Referenced from openclaw-channel-dingtalk.
|
||||
|
||||
The adapter connects via the `dingtalk-stream` SDK, which handles WebSocket connection, reconnection, heartbeats, and callback ACKs (DingTalk retries unACKed messages). Authentication reuses the SDK's built-in token (`client.getConfig().access_token`) from AppKey + AppSecret. Responses are sent back through a per-message `sessionWebhook` URL — a temporary, conversation-scoped endpoint that supports text, markdown, images, and files. Both DM and group chat are supported, with group messages gated by `@mention` detection (`isInAtList`). A 👀 emoji reaction serves as a working indicator while the agent processes (posted via the emotion API and recalled on completion). Output is formatted as DingTalk markdown, with tables converted to plain text, messages split at ~3800 characters, and code fences maintained across chunks. Image, file, audio, and video receiving works through a two-step download flow (`downloadCode` → `downloadUrl` → buffer); images are forwarded as base64, files saved to `/tmp/channel-files/`. Quoted message context is extracted from `text.repliedMsg` and `quoteMessage`, with bot-reply detection via `chatbotUserId`.
|
||||
|
||||
```jsonc
|
||||
// ~/.qwen/settings.json
|
||||
{
|
||||
"channels": {
|
||||
"my-dingtalk": {
|
||||
"type": "dingtalk",
|
||||
"clientId": "$DINGTALK_CLIENT_ID",
|
||||
"clientSecret": "$DINGTALK_CLIENT_SECRET",
|
||||
"senderPolicy": "open",
|
||||
"sessionScope": "user",
|
||||
"cwd": "/path/to/project",
|
||||
"instructions": "Keep responses concise. Use DingTalk markdown.",
|
||||
"groupPolicy": "open",
|
||||
"groups": {
|
||||
"*": { "requireMention": true },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
```bash
|
||||
export DINGTALK_CLIENT_ID=<your-app-key>
|
||||
export DINGTALK_CLIENT_SECRET=<your-app-secret>
|
||||
npm run bundle && node dist/cli.js channel start my-dingtalk
|
||||
```
|
||||
|
||||
**Future work:** Quoted bot responses (persisting outbound messages keyed by `processQueryKey` for lookup on reply). AI Card streaming via `/v1.0/card/instances` and `/v1.0/card/streaming` with graceful markdown fallback.
|
||||
51
docs/design/channels/channels-roadmap.md
Normal file
51
docs/design/channels/channels-roadmap.md
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
# Channels Roadmap
|
||||
|
||||
## Implemented (MVP)
|
||||
|
||||
- **3 built-in channels** — Telegram, WeChat, DingTalk
|
||||
- **Plugin system** — `ChannelBase` SDK with `connect`/`sendMessage`/`disconnect`, extension manifest, compiled JS + `.d.ts`
|
||||
- **Access control** — `allowlist`, `pairing` (8-char codes, CLI approval), `open` policies
|
||||
- **Group chat** — `open`/`disabled`/`allowlist` group policy, `requireMention` per group, reply-as-mention
|
||||
- **Session routing** — `user`, `thread`, `single` scopes with per-channel `cwd`, `model`, `instructions`
|
||||
- **Dispatch modes** — `steer` (default: cancel + re-prompt), `collect` (buffer + coalesce), `followup` (sequential queue). Per-channel and per-group config.
|
||||
- **Working indicators** — centralized `onPromptStart`/`onPromptEnd` hooks. Telegram: typing bar. WeChat: typing API. DingTalk: 👀 emoji reaction.
|
||||
- **Block streaming** — progressive multi-message delivery with paragraph-aware chunking
|
||||
- **Streaming hooks** — `onResponseChunk`/`onResponseComplete` for plugins to implement progressive display
|
||||
- **Media support** — images (vision input), files/audio/video (saved to temp, path in prompt), `Attachment` interface on `Envelope`
|
||||
- **Slash commands** — `/help`, `/clear` (`/reset`, `/new`), `/status`, custom via `registerCommand()`
|
||||
- **Service management** — `qwen channel start/stop/status`, PID tracking, crash recovery (auto-restart, session persistence)
|
||||
- **Token security** — `$ENV_VAR` syntax in config
|
||||
|
||||
## 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
|
||||
|
||||
## Reference: OpenClaw Comparison
|
||||
|
||||
See [channels-comparison.md](channels-comparison.md) for the detailed feature comparison between OpenClaw and Qwen-Code channels.
|
||||
156
docs/design/channels/channels-testing-guide.md
Normal file
156
docs/design/channels/channels-testing-guide.md
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
# Channels Testing Guide
|
||||
|
||||
How to test channel integrations end-to-end.
|
||||
|
||||
## Credentials
|
||||
|
||||
- Telegram bot: `@qwencod_test_1_bot` (远弟)
|
||||
- Bot token env var: `TELEGRAM_BOT_TOKEN`
|
||||
- Bot token file: `/home/andy/projects/telegram/.env`
|
||||
- Andy's Telegram user ID: `8513463076`
|
||||
- WeChat credentials: `~/.qwen/channels/weixin/account.json`
|
||||
|
||||
## Before testing
|
||||
|
||||
**Important:** Stop any running service first. Duplicate instances cause duplicate responses.
|
||||
|
||||
```bash
|
||||
# Stop the service if running
|
||||
qwen channel stop
|
||||
|
||||
# Or check status first
|
||||
qwen channel status
|
||||
|
||||
# If processes are stuck (e.g. from manual kill -9), clean up manually
|
||||
pkill -9 -f "cli.js --acp"
|
||||
pkill -9 -f "channel start"
|
||||
rm -f ~/.qwen/channels/service.pid ~/.qwen/channels/sessions.json
|
||||
```
|
||||
|
||||
## Sending messages via Bot API (no bot process needed)
|
||||
|
||||
```bash
|
||||
# Source the token
|
||||
export TELEGRAM_BOT_TOKEN=$(grep TELEGRAM_BOT_TOKEN /home/andy/projects/telegram/.env | cut -d= -f2)
|
||||
|
||||
# Send a message to Andy
|
||||
curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"chat_id": "8513463076", "text": "Hello from the bot!"}'
|
||||
```
|
||||
|
||||
## Starting channels
|
||||
|
||||
```bash
|
||||
export TELEGRAM_BOT_TOKEN=$(grep TELEGRAM_BOT_TOKEN /home/andy/projects/telegram/.env | cut -d= -f2)
|
||||
cd /home/andy/projects/qwen-code
|
||||
npm run bundle
|
||||
|
||||
# Single channel
|
||||
node dist/cli.js channel start my-telegram
|
||||
|
||||
# All channels (shared bridge)
|
||||
node dist/cli.js channel start
|
||||
```
|
||||
|
||||
Settings config: `~/.qwen/settings.json` under `channels.*`.
|
||||
|
||||
## Checking registered commands
|
||||
|
||||
```bash
|
||||
curl -s "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/getMyCommands" | python3 -m json.tool
|
||||
```
|
||||
|
||||
## Test scenarios
|
||||
|
||||
### 1. Slash commands (shared across all channels)
|
||||
|
||||
Start the service, then send on Telegram or WeChat:
|
||||
|
||||
| Command | Expected |
|
||||
| --------- | --------------------------------------------------------------- |
|
||||
| `/help` | List of all commands |
|
||||
| `/status` | "Session: none, Access: ..." |
|
||||
| `/clear` | "No active session to clear." (or "Session cleared." if active) |
|
||||
| `/reset` | Same as `/clear` (alias) |
|
||||
| `/new` | Same as `/clear` (alias) |
|
||||
|
||||
### 2. Basic text round-trip
|
||||
|
||||
1. Start the bot
|
||||
2. Send any text (e.g. "hello")
|
||||
3. Bot should respond via the agent
|
||||
4. `/status` should now show "Session: active"
|
||||
|
||||
### 3. Multi-turn conversation
|
||||
|
||||
1. Send "my name is Andy"
|
||||
2. Send "what is my name?"
|
||||
3. Agent should remember "Andy" from same session
|
||||
|
||||
### 4. Session clear
|
||||
|
||||
1. Have an active session (send a message first)
|
||||
2. Send `/clear` (or `/reset` or `/new`)
|
||||
3. Send "what is my name?"
|
||||
4. Agent should NOT remember — fresh session
|
||||
|
||||
### 5. Tool calls (internal)
|
||||
|
||||
1. Send "list the files in /home/andy/projects/qwen-code"
|
||||
2. Agent should use shell/ls internally and return file listing
|
||||
3. Verify response contains actual file names
|
||||
|
||||
### 6. Markdown formatting
|
||||
|
||||
1. Send "write me a hello world in python with explanation"
|
||||
2. Response should render with proper Telegram HTML formatting (bold, code blocks, etc.)
|
||||
|
||||
### 7. Multi-channel mode
|
||||
|
||||
1. Ensure both `my-telegram` and `my-weixin` are configured in `~/.qwen/settings.json`
|
||||
2. For WeChat: run `node dist/cli.js channel configure-weixin` if token expired
|
||||
3. Start all: `node dist/cli.js channel start`
|
||||
4. Should show: `Starting 2 channel(s): my-weixin, my-telegram`
|
||||
5. Send messages on both platforms — each should get exactly one response
|
||||
6. Check `~/.qwen/channels/sessions.json` — each channel should have its own cwd
|
||||
|
||||
### 8. Crash recovery
|
||||
|
||||
1. Start multi-channel mode and send a message to create sessions
|
||||
2. Find the ACP bridge PID: `ps --ppid <parent-pid> -o pid,args | grep acp`
|
||||
3. Kill it: `kill -9 <acp-pid>`
|
||||
4. Log should show: `Bridge crashed (1/3). Restarting in 3s...` then `Sessions restored: 2, failed: 0`
|
||||
5. Send a message — should work, and session context (e.g. "what is my name?") should be preserved
|
||||
|
||||
### 9. Clean shutdown
|
||||
|
||||
1. Start channels, send a message to create sessions
|
||||
2. Press Ctrl+C (or `qwen channel stop` from another terminal)
|
||||
3. `~/.qwen/channels/sessions.json` should be deleted
|
||||
4. `~/.qwen/channels/service.pid` should be deleted
|
||||
|
||||
### 10. Service management
|
||||
|
||||
1. Start service: `qwen channel start`
|
||||
2. Check status from another terminal: `qwen channel status` — should show running, uptime, channels
|
||||
3. Try starting again: `qwen channel start` — should fail with "already running" error
|
||||
4. Stop from another terminal: `qwen channel stop` — should stop gracefully
|
||||
5. Confirm stopped: `qwen channel status` — should show "No channel service is running."
|
||||
|
||||
### 11. Referenced messages (quoted replies)
|
||||
|
||||
1. Send a message and get a bot response
|
||||
2. Reply to (quote) the bot's response with a follow-up question (e.g. "summarize that")
|
||||
3. Agent should see the quoted text as context and respond accordingly
|
||||
4. Test on both Telegram and WeChat
|
||||
|
||||
## Useful debug commands
|
||||
|
||||
```bash
|
||||
# Check recent updates the bot received
|
||||
curl -s "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/getUpdates?limit=5" | python3 -m json.tool
|
||||
|
||||
# Get bot info
|
||||
curl -s "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/getMe" | python3 -m json.tool
|
||||
```
|
||||
Loading…
Add table
Add a link
Reference in a new issue