> ## ⚠️ Modified fork — not a Tencent official release > > `dsh-qqbot-merge` is a maintained fork of **[@tencent-connect/dsh-qqbot](https://github.com/tencent-connect/dsh-qqbot) 0.5.3 (MIT)**. > It adds **consecutive-message merging with non-interrupting `agent.steer()`** and > **single-message reply revision** (one QQ message updated across model steps), and exposes a > `qqbotMerge` cordis service consumed by > [dsh-longterm-memory](https://github.com/bufan528/dsh-longterm-memory)'s QQ tab. > > - Original copyright: Tencent Connect, see [LICENSE](./LICENSE); the fork's changes are MIT too. > - **It cannot run together with the official `@tencent-connect/dsh-qqbot`**: both would open a > gateway connection and bind routes for the same `appId`. Installing this package **replaces** > the official QQ plugin. > - Secrets: the upstream plugin stores `appId` / `appSecret` as plain YAML in the profile's > `cordis.patch.yml`; this fork keeps that mechanism and never stores/echoes anything extra. > > ### Install / migrate > > ```powershell > npx @deepseek-ai/dsh plugin --profile desktop add dsh-qqbot-merge > npx @deepseek-ai/dsh plugin --profile desktop remove @tencent-connect/dsh-qqbot > # replace "@tencent-connect/dsh-qqbot" with "dsh-qqbot-merge" in the profile bundle list, > # move appId/appSecret into the `qqbot-merge` row (or re-scan the QR code), then restart DSH. > ``` > > Merging is off by default; enable it in DSH **Settings → "连续消息智能合并"** > (the plugin's own section, no other plugin needed) or set `qqbotMerge.enabled: true` > in the profile patch. > > > **Build from source** > > ```powershell > npm install > npm run build # tsc -> dist/ > npm run build:client # tsdown -> lib/client.js (binding UI + settings panels) > npx vitest run # 18 files / 243 tests > npm pack # 291 files, ~1.2 MB > ``` > > Then `dsh plugin --profile add .\dsh-qqbot-merge-0.1.1.tgz`. A real QQ end-to-end run has not been verified by the publisher (replacing a running bot and a > second gateway connection for the same `appId` were out of scope). Test in a scratch profile and > keep the rollback: restore `@tencent-connect/dsh-qqbot@0.5.3` in `dependencies` / `bundles`.
QQ Bot **A QQ Bot plugin for [deepseek-harness](https://github.com/deepseek-ai/deepseek-harness) (dsh), connecting DeepSeek AI assistants to QQ private and group chats.** [![npm version](https://img.shields.io/npm/v/@tencent-connect/dsh-qqbot)](https://www.npmjs.com/package/@tencent-connect/dsh-qqbot) [![License](https://img.shields.io/badge/license-MIT-green)](./LICENSE) [![GitHub stars](https://img.shields.io/github/stars/tencent-connect/dsh-qqbot)](https://github.com/tencent-connect/dsh-qqbot) [![QQ Bot](https://img.shields.io/badge/QQ_Bot-API_v2-red)](https://bot.q.qq.com/wiki/)
**[简体中文](./README.md) | English** Scan to join the QQ group / channel
QQ Group QR Code
QQ Developer Group
Group ID: 1032635674
QQ Channel QR Code
QQ Developer Channel
Channel ID: 20dnumts4z
## Architecture ``` QQ User → QQ WebSocket → dsh-qqbot → ctx.agents → dsh agent loop → LLM ↑ │ └── session/event ──────────┘ (assistant reply → QQ sendMarkdown) ``` ## Installation ### Method 1: Desktop install (recommended) No command line needed — everything happens inside the app: 1. **Install**: click "Plugins" in the left sidebar → "Add plugin" → enter `@tencent-connect/dsh-qqbot@latest` → install and start now In-app plugin install 2. **Connect**: avatar at the bottom-left → Settings → "QQ Bots" → scan the QR code with mobile QQ Settings QR binding Credentials are saved automatically after the scan; later launches need no re-scan. ### Method 2: Manual ```bash # Add to a profile npx @deepseek-ai/dsh plugin --profile qqbot add @tencent-connect/dsh-qqbot # Start npx @deepseek-ai/dsh --profile qqbot ``` On first launch, the plugin detects missing credentials and automatically starts the QR flow: a QR code is printed in the terminal → scan it with the QQ mobile app → credentials are saved to the profile, so subsequent launches require no re-scan. In environments without the in-app binding page (pure CLI), set `QQBOT_BIND_OPEN_BROWSER=1` to additionally open a browser QR page as a fallback. QR code scan example > **Note**: Upgrade to `0.4.0` or later for browser-link scanning, which avoids QR code misalignment in some terminals. ### Method 3: Local path ```bash # Build cd /path/to/dsh-qqbot pnpm install && pnpm build # Add to a profile (local path) npx @deepseek-ai/dsh plugin --profile qqbot add /path/to/dsh-qqbot # Start export QQBOT_APPID="yourAppID" QQBOT_SECRET="yourAppSecret" npx @deepseek-ai/dsh --profile qqbot ``` ### Method 4: --patch dev mode ```bash export QQBOT_APPID="yourAppID" QQBOT_SECRET="yourAppSecret" npx @deepseek-ai/dsh web --patch /path/to/dsh-qqbot/cordis.dev.yml ``` ## Configuration | Config | Type | Default | Description | |------|------|--------|------| | `appId` | string | **required** | QQ Bot AppID (or via `QQBOT_APPID` env var) | | `appSecret` | string | **required** | QQ Bot AppSecret (or via `QQBOT_SECRET` env var) | | `provider` | string | `deepseek-official` | LLM provider name | | `model` | string | `deepseek-chat` | Model name | | `preset` | string | - | Agent preset id | | `cwd` | string | `process.cwd()` | Agent working directory | | `requireMention` | boolean | `true` | Whether group messages require @bot to trigger | | `groupPrompt` | string | - | Extra system prompt for group chats | | `directPrompt` | string | - | Extra system prompt for direct chats | | `textChunkLimit` | number | `4500` | Max chars per message | | `streaming` | boolean | `true` | Enable streaming output (always disabled in groups) | | `sessionIdleTimeout` | number | `1800000` | Session idle timeout (ms), default 30 min | | `processingTimeoutMs` | number | `1800000` | Processing timeout (ms), aborts the current LLM call | | `maxQueue` | number | `20` | Max concurrent queue length | | `historyLimit` | number | `10` | Group history buffer size | | `askTimeoutMs` | number | `300000` | Question timeout (ms), default 5 min (ask_user_question) | | `showToolResults` | boolean | `false` | Show successful tool-call results (errors always shown) | | `debug` | boolean | `false` | Debug mode | ### Access control (access) | Config | Type | Default | Description | |------|------|--------|------| | `access.c2cMode` | `open`/`allowlist`/`disabled` | `open` | Direct-chat access mode | | `access.c2cAllow` | string[] | `[]` | Direct-chat allowlist (user openid) | | `access.groupMode` | `open`/`allowlist`/`disabled` | `open` | Group access mode | | `access.groupAllow` | string[] | `[]` | Group allowlist (group openid) | ### Rich media (media) | Config | Type | Default | Description | |------|------|--------|------| | `media.enabled` | boolean | `true` | Enable rich media understanding (image/video download + tool analysis) | | `media.maxMB` | number | `200` | Max download size (MB) | | `media.ttlHours` | number | `24` | Media lifetime (hours), 0 = never expire | ### Vision (vision) | Config | Type | Default | Description | |------|------|--------|------| | `vision.enabled` | boolean | `false` | Enable vision (qqbot_describe_image tool) | | `vision.provider` | string | - | Vision model provider (e.g. pi-ai) | | `vision.model` | string | - | Vision model id (e.g. qwen-vl-max) | | `vision.maxBytes` | number | `10MB` | Max image bytes | | `vision.maxTokens` | number | `1024` | Max output tokens | | `vision.timeoutMs` | number | `120000` | Vision call timeout (ms) | ### File sending (sendFile) | Config | Type | Default | Description | |------|------|--------|------| | `sendFile.restrictPaths` | boolean | `true` | Enable path allowlist (media + cwd + extraRoots only) | | `sendFile.extraRoots` | string[] | `[]` | Extra allowed root directories | ### Consecutive message merging (qqbotMerge) Merging is configured from this plugin's own settings section — **no other plugin required**: open DSH **Settings →「连续消息智能合并」** (`settings.section` id `qqbot-merge`) to edit the fields below and inspect runtime diagnostics. The panel reads/writes through the host route `/qqbot/im-qqbot/merge` (GET status, POST config); when the route is unavailable (not bound yet, older build) it shows a "尚未就绪" notice instead of crashing. Values can also be edited directly in the `qqbotMerge` field of the profile's `qqbot-merge` row. | Config | Type | Default | Description | |------|------|--------|------| | `qqbotMerge.enabled` | boolean | `false` | Enable consecutive-message merging (off = stock 0.5.3 behaviour) | | `qqbotMerge.windowMs` | number | `800` | Quiet-window length (ms), 100–10000 | | `qqbotMerge.maxBatchMessages` | number | `8` | Max messages per batch, 1–50 | | `qqbotMerge.maxBatchChars` | number | `4000` | Max characters per batch, 200–20000 | | `qqbotMerge.scope.c2c` | boolean | `true` | Enable merging in direct chats | | `qqbotMerge.scope.group` | boolean | `false` | Enable merging in groups (grouped by sender) | | `qqbotMerge.busyPolicy` | `steer`/`queue` | `steer` | While the agent is busy: non-interrupting steer / queue to the next turn (legacy) | | `qqbotMerge.replyMode` | `single-message`/`per-step` | `single-message` | One QQ message revised across steps / one message per step | The same panel shows runtime diagnostics: whether steer is available, the steer mechanism, the integration, a note, eight counters (batches merged, messages steered, follow-ups, overflowed, steer fallbacks, reply rewrites, replies completed, batches aborted) and the most recent batch. Saving takes effect immediately and persists to this plugin's own cordis row config (the host reloads the plugin, so `revision` resets to 0 afterwards — that is expected). ## Built-in Commands | Command | Description | |------|------| | `/new` (aliases `/reset` `/clear`) | Start a new session (clear context) | | `/compact` | Compact session history (replace old records with a summary) | | `/model` | View or switch model | | `/preset` | View or switch agent preset (applies to new sessions) | | `/stop` | Abort the current generation | | `/bot-ping` | Network latency test (transport & processing time) | | `/bot-version` | View version info | | `/bot-status` | View current session status | | `/bot-help` | View all commands | ## Core Modules ``` src/ ├── index.ts # Cordis plugin entry (async apply) ├── config.ts # Config schema ├── types.ts # Global types ├── setup.ts # Credential binding (QR) ├── client/ # Browser half (dsh.client web bundle) │ ├── index.ts # Sidebar tab + settings section registration │ ├── bind-page.ts # In-app binding page (QR + state polling) │ ├── config-panel.ts # Settings: base configuration panel │ └── merge-panel.ts # Settings: consecutive-message merging panel ├── merge/ # Consecutive-message merging │ ├── contract.ts # Cross-plugin contract (qqbot-merge@v1) │ ├── coordinator.ts # Quiet-window batching + steer/queue scheduling │ ├── service.ts # qqbotMerge service + single-message reply hooks │ └── routes.ts # Settings route (/qqbot/im-qqbot/merge) ├── gateway/ # Gateway assembly │ ├── bootstrap.ts # Startup wiring (session/event listeners, etc.) │ └── middleware-setup.ts # Middleware chain config ├── transport/ # Transport layer │ ├── inbound.ts # QQ inbound message → agent.followup() │ ├── outbound.ts # session/event → QQ sendMarkdown │ ├── outbound-buffer.ts # Streaming buffer │ ├── streaming-writer.ts # Streaming writer │ ├── reply-target.ts # Reply target resolution │ ├── msgid-cache.ts # Passive reply msgid cache │ ├── reply-limiter.ts # Passive reply rate limiter │ ├── tool-presenter.ts # Tool-call presentation │ └── chunker.ts # Markdown chunking ├── session/ # Session management │ ├── session-manager.ts # QQ peer → Agent mapping │ └── idle-evictor.ts # Idle eviction ├── model/ # Model routing │ ├── model-resolver.ts # Route resolution │ ├── prefs-store.ts # Per-peer preference persistence │ └── settings-reader.ts # settings.yaml read-only ├── features/ # Interaction features │ ├── question-channel.ts # ask_user_question Q&A channel │ ├── approval-channel.ts # approval confirmation channel │ └── answer-parser.ts # Answer parsing ├── media/ # Media │ ├── vision-tool.ts # Image vision understanding │ ├── send-file-tool.ts # Send local files │ └── media-cleaner.ts # Media TTL cleanup ├── middleware/ # Middleware │ ├── question-answer.ts # Q&A answer handling │ └── attachment.ts # Attachment handling ├── shared/ # Shared utilities │ ├── utils.ts # Common helpers │ ├── scope.ts # scope/peer extraction │ ├── bind-protocol.ts # Binding UI protocol (paths/constants) │ ├── merge-protocol.ts # Merge settings protocol (path/constants) │ └── send-helper.ts # Chunked send └── commands/ # Slash commands ``` ## Session Routing sessionKey: `qqbot:${appId}:${scope}:${peerId}`, with the SessionId derived deterministically via SHA-256 so sessions survive restarts. Resolution strategy: in-process reuse → persisted resume → fresh create. ## Design Principles - **Pure Cordis plugin** — follows the dsh "Plugins, not loop changes" principle - **Declarative dependencies** — `inject = ['agents']`, no direct coupling to other plugins - **Session isolation** — one independent Agent per QQ direct user / group - **Preset support** — mount presets (toolkits, prompts, etc.) via the `agent-presets` service - **Idle eviction** — auto-dispose Agents on timeout to prevent memory leaks - **Markdown output** — replies sent as Markdown with code-block/table-aware chunking - **Image understanding** — supports `qqbot_describe_image` vision to analyze images sent by users - **File sending** — supports `qqbot_send_file` to send local files to users (path allowlist enabled by default) - **Question interaction** — supports `ask_user_question` with inline keyboard buttons (mutually exclusive) or numbered replies, one question at a time with timeout - **Approval confirmation** — supports `approval/request` to ask for user confirmation on critical actions via allow/deny buttons ## Local Development ```bash # Install dependencies pnpm install # Build pnpm build # Dev mode (watch) pnpm dev # Debug via --patch export QQBOT_APPID="xxx" QQBOT_SECRET="xxx" npx @deepseek-ai/dsh web --patch /path/to/dsh-qqbot/cordis.dev.yml ``` ## License [MIT](./LICENSE)