Connect a QQ bot to DeepSeek Harness: mention @bot in private or group chats to chat, send files, and call tools. Scan QR code to bind QQ credentials, with independent sessions spawned per message.
- Language
- TypeScript
- License
- MIT
- Branch
- main
Install
$ dsh plugin --profile web add github:tencent-connect/dsh-qqbotRun the command above in your terminal to install this plugin via the dsh CLI. You can switch Profile in the top-right corner. New to dsh? Read the beginner tutorial
Install via your agent
Install the DeepSeek Harness plugin tencent-connect/dsh-qqbot for me: review the repository at https://github.com/tencent-connect/dsh-qqbot.git first, then run the install command and verify the plugin loads successfully.
Paste this instruction to the DSH Web GUI assistant — it will install and verify for you.
One-Sentence Overview
dsh-qqbot connects a QQ bot to DeepSeek Harness: turning the QQ messaging platform into a frontend channel for dsh agents. Just @ the bot in private or group chats to converse, send files, and invoke agent tools. Scan a QR code on first launch to bind QQ credentials; each chat window holds its own independent session.
Core Capabilities
- Private & Group Chat: Private chats always respond; group chats trigger only when @bot by default, with an option to disable the gate and respond to all messages
- QR Code Credential Binding: On first launch, the terminal prints a QR code; scan it with QQ mobile to obtain AppID/AppSecret and automatically write to profile
- Attachment Handling: Images include size descriptions automatically; voice messages are transcribed to text; file attachments are downloaded locally and the model is prompted to read them via path
- Markdown Streaming Replies: Private chats push replies paragraph by paragraph (streaming) by default; group chats send entire paragraphs at once due to QQ protocol limitations; long replies are split by code block/table awareness
- Independent Sessions: Each QQ user/group maps to a dsh agent; sessions are persisted via SHA-256 derived sessionIds and can be restored after restart
- Built-in Slash Commands + Access Control:
/new/compact/model/stopplus/bot-ping/bot-version/bot-status/bot-help, with openid/groupOpenid whitelist support to allow or disable private and group chats
Technical Implementation
- Language: TypeScript (ESM,
tsconfigcompiles todist/,mainpoints todist/index.js) - Key Dependencies:
@tencent-connect/qqbot-nodejs(QQ OpenAPI + WebSocket client + middleware chain),@tencent-connect/qqbot-connector(QR code binding),js-yaml(cordis.patch.yml read/write) - Architecture Pattern: Pure Cordis plugin,
inject = ['agents']; entryapply()runs credential guidance then callsbootstrapGateway()to assemble SDK middleware chain (error handling → filtering → access control → group history buffer → @ gate → cleaning → rate limiting → slash commands → concurrent serialization → input state → quoting → attachments → envelope), and routes dsh outbound events back to QQ viactx.on('session/event', ...) - Entry File:
src/index.ts(Cordis plugin entry, includes credential guidance); gateway assembly insrc/gateway/bootstrap.ts
Use Cases
Ideal for users who want to interact with DeepSeek Harness via QQ: ask questions in QQ daily, let the agent read files, write code, or run commands—no need to open the web console. Can also serve as a team assistant in QQ groups, with each person's conversation context isolated from others. Resources are automatically released after 30 minutes of inactivity.
Prerequisites and Compatibility
| Dependency | Minimum Version | Description |
|---|---|---|
| DeepSeek Harness | 0.1.0-rc.6+ | peerDependencies locks @deepseek-ai/dsh-agent / dsh-llm / dsh-session / cordis / schemastery all to 0.1.0-rc.6 / 4.0.1 / 3.18.1 or above |
| QQ Bot Credentials | — | AppID + AppSecret required; can be obtained via QR scan or manually injected via QQBOT_APPID / QQBOT_SECRET environment variables |
| Node.js | Not declared | No engines field in package.json; devDependencies include @types/node ^26.2.0, actual runtime requires Node 18+ supporting built-in APIs like fetch, AbortSignal.timeout, node:dns/promises |
| Platform | macOS / Windows / Linux | QR scan guidance auto-switches to set syntax on Windows (src/setup.ts:186), other platforms use export; no other platform-specific branches in code |
| Native Modules | None | Only depends on built-in modules like node:crypto / node:fs / node:path / node:dns, no native bindings like node-pty / sqlite |
Installation
dsh plugin --profile web add github:tencent-connect/dsh-qqbot
Configuration Options
The plugin declares fields via Schemastery Schema (src/config.ts:56); the "Description" column in the table below is in plain language. Default values work for normal scenarios after installation; regular users don't need to adjust.
| Config | Type | Description | Default |
|---|---|---|---|
appId | string | QQ bot AppID; if left empty, QR scan guidance runs on startup | empty (requires QR scan or env var) |
appSecret | string | QQ bot AppSecret; same as above | empty |
provider | string | Default LLM provider; if empty, inherits host config | inherits |
model | string | Default model; if empty, inherits host config | inherits |
preset | string | Associated agent preset id (toolset, prompt, etc.) | none |
cwd | string | Root directory for agent command execution | current process directory |
requireMention | boolean | Whether group chats require @bot to respond | true |
groupPrompt | string | Additional content injected into system prompt for group chats | none |
directPrompt | string | Additional content injected into system prompt for private chats | none |
textChunkLimit | number | Maximum characters per QQ message (QQ limit ~5000) | 4500 |
streaming | boolean | Whether private chats enable streaming replies (group chats never stream) | true |
sessionIdleTimeout | number | Milliseconds before idle sessions are auto-reclaimed | 1800000 (30 minutes) |
maxQueue | number | Maximum messages queued for the same session | 20 |
processingTimeoutMs | number | Milliseconds before a single LLM call auto-terminates | 120000 (2 minutes) |
historyLimit | number | Number of recent messages included as context when responding in group chats | 10 |
access.c2cMode | string | Private chat access mode: open allow all / allowlist whitelist / disabled deny all | open |
access.c2cAllow | string array | Private chat whitelist (enter user openid) | [] |
access.groupMode | string | Group chat access mode (same values) | open |
access.groupAllow | string array | Group chat whitelist (enter group openid) | [] |
showToolResults | boolean | Whether to display successful tool call results (errors always shown) | false |
debug | boolean | Enable debug logs (includes middleware hit details) | false |
FAQ
Q: What do I need to do on first launch?
A: If AppID/AppSecret is not detected on startup, the plugin prints a QR code in the terminal; scan to complete binding. Credentials are automatically written to the current profile's cordis.patch.yml, no need to scan again next time. When QR code is misaligned, use the browser link output by version 0.4.0+ instead.
Q: Do I need to @bot in group chats for it to respond?
A: requireMention is enabled by default; group chats only trigger responses when @bot. Set to false to respond to any message. Private chats are unaffected by this switch and always respond.
Q: Will conversation context leak between different QQ groups?
A: No. Each QQ private chat user and each QQ group has an independent sessionKey. sessionId is deterministically derived via SHA-256 from qqbot:${appId}:${scope}:${peerId}, restorable after restart. Dialogues between different groups/people are isolated.
Q: Does it support images, voice, and files?
A: Images include size descriptions in context. Voice messages prefer ASR-transcribed text; if the model can't access audio, the original link is attached as fallback. File attachments are automatically downloaded to ${cwd}/.qqbot/${messageId}/ with path prompts, then the model reads them via tools. File downloads include HTTPS + SSRF protection and 20MB size limits.
Q: How to enable streaming replies?
A: Streaming is on by default for private chats (streaming: true), replies push paragraph by paragraph. Group chats never stream due to QQ group message API limitations, only sending entire Markdown blocks. If global streaming is disabled, private chats also switch to sending complete replies at once.
Q: How to switch models?
A: Send /model in chat to list available models (click to switch), or /model provider/model to specify a route. Preferences are persisted per sessionKey to ~/.dsh-qqbot/model-prefs.json, so the same person/group retains settings on next restart.
Q: How to completely reset the current conversation?
A: Send /new (alias /reset, /clear) to discard the current session and start a new one. To preserve context but compress history, send /compact (requires host with compaction service). Long idle sessions over 30 minutes are auto-disposed and restored on next message.
Q: "Credentials not configured" error—now what?
A: QR scanning automatically writes to profile. In dev mode (loading source via cordis.dev.yml), the profile directory can't be located, so the plugin prints export/set environment variable instructions to terminal. For manual configuration, write appId and appSecret to the corresponding plugin entry in the profile's cordis.patch.yml.
Learning Curve
Beginner — scan QR code to bind QQ credentials, start chatting in private messages immediately. Advanced users can adjust group gating, whitelists, Markdown split thresholds, and other parameters as needed.
Known Issues and Limitations
- Model preferences are persisted per sessionKey to
~/.dsh-qqbot/model-prefs.json; deleting this file resets all preferences. No UI-based batch cleanup currently available. - Group chat streaming is always disabled: QQ group message API doesn't support incremental updates, so group chats only send entire Markdown blocks, with single-message limits controlled by
textChunkLimit(default 4500). - File attachment downloads have 20MB size limits and HTTPS + SSRF protection. When either rule is triggered, download is skipped and only path description is retained; oversized files need to be manually placed in the agent's working directory.
- Sessions idle for 30 minutes are automatically disposed; the next message triggers a "resume → create" flow. If the host lacks
sessionsservice, resume fails and falls back to creating a brand new session (history lost). - Model switching uses fork + rebuild strategy. If
sessions.forkservice doesn't exist at the lower level, it falls back to disposing old session and creating new one with new model (equivalent to/new, context lost). /compactcommand requires host to haveagent-presetsorcompactionservice loaded; returns "compression capability unavailable" when not loaded.- In streaming replies, both
assistant/chunkandassistant/messageenter the buffer. Ifturn/endends abnormally mid-stream,底层错误码 likeSTREAM_CLOSEDare silently ignored (won't appear in user messages).
基于 deepseek-harness (dsh) 的 QQ Bot IM 插件,将 QQ 消息平台作为 dsh agent 的前端协议驱动。
中文 | English
架构
QQ 用户 → QQ WebSocket → dsh-im-qqbot → ctx.agents → dsh agent loop → LLM
↑ │
└── session/event ──────────┘
(assistant reply → QQ sendMarkdown)
安装
方式一:手动执行
# 安装到 profile
npx @deepseek-ai/dsh plugin --profile qqbot add @tencent-connect/dsh-qqbot
# 启动
npx @deepseek-ai/dsh --profile qqbot
首次启动时,插件检测到凭据未配置会自动进入扫码引导:终端输出二维码 → 手机 QQ 扫码绑定 → 凭据自动保存到 profile,后续启动无需再次扫码。
提示:建议升级至
0.4.0以上版本扫码,支持点击链接在浏览器打开,避免部分终端二维码渲染错位的问题。
方式二:本地路径安装
# 构建
cd /path/to/dsh-qqbot
pnpm install && pnpm build
# 安装到 profile(本地路径)
npx @deepseek-ai/dsh plugin --profile qqbot add /path/to/dsh-qqbot
# 启动
export QQBOT_APPID="你的AppID" QQBOT_SECRET="你的AppSecret"
npx @deepseek-ai/dsh --profile qqbot
方式三:--patch 开发模式
export QQBOT_APPID="你的AppID" QQBOT_SECRET="你的AppSecret"
npx @deepseek-ai/dsh web --patch /path/to/dsh-qqbot/cordis.dev.yml
配置项
| 配置 | 类型 | 默认值 | 说明 |
|---|---|---|---|
appId | string | 必填 | QQ Bot AppID(或通过 QQBOT_APPID 环境变量) |
appSecret | string | 必填 | QQ Bot AppSecret(或通过 QQBOT_SECRET 环境变量) |
provider | string | deepseek-official | LLM 提供商名称 |
model | string | deepseek-chat | 模型名称 |
preset | string | - | Agent preset id |
cwd | string | process.cwd() | Agent 工作目录 |
requireMention | boolean | true | 群聊是否需要 @bot 才触发 |
groupPrompt | string | - | 群聊额外 system prompt |
directPrompt | string | - | 私聊额外 system prompt |
textChunkLimit | number | 4500 | 单条消息最大字符数 |
sessionIdleTimeout | number | 1800000 | 会话闲置超时(ms),默认 30 分钟 |
debug | boolean | false | 调试模式 |
内置命令
| 命令 | 说明 |
|---|---|
/new(别名 /reset /clear) | 开始新会话(清空上下文) |
/compact | 压缩会话历史(摘要替换旧记录,保留上下文) |
/model | 查看或切换模型 |
/stop | 中止当前生成 |
/bot-ping | 连通性测试 |
/bot-version | 查看版本信息 |
/bot-status | 查看当前会话状态 |
/bot-help | 查看所有指令 |
核心模块
src/
├── index.ts # Cordis 插件入口(async apply)
├── config.ts # 配置 Schema
├── types.ts # 全局类型定义
├── setup.ts # 凭据绑定(扫码)
├── transport/ # 传输层
│ ├── inbound.ts # QQ 入站消息 → agent.followup()
│ ├── outbound.ts # session/event → QQ sendMarkdown
│ ├── outbound-buffer.ts # 流式缓冲
│ └── chunker.ts # Markdown 文本切分
├── session/ # 会话管理层
│ ├── session-manager.ts # QQ peer → Agent 映射
│ └── idle-evictor.ts # 闲置回收
├── model/ # 模型路由层
│ ├── model-resolver.ts # 路由解析
│ ├── prefs-store.ts # per-peer 偏好持久化
│ └── settings-reader.ts # settings.yaml 只读
├── shared/ # 共享工具
│ ├── utils.ts # 通用函数
│ ├── scope.ts # scope/peer 提取
│ └── send-helper.ts # 分块发送
├── commands/ # 斜杠命令
└── typings/ # 外部模块声明
会话路由
sessionKey: qqbot:${appId}:${scope}:${peerId},由 SHA-256 确定性派生 SessionId,重启后可恢复。
解析策略:进程内复用 → 持久化恢复 → 全新创建。
设计原则
- 纯 Cordis 插件 — 遵循 dsh "Plugins, not loop changes" 原则
- 声明式依赖 —
inject = ['agents'],不直接耦合其他插件 - 会话隔离 — 每个 QQ 私聊用户/群聊各一个独立 Agent
- Preset 支持 — 可通过
agent-presets服务挂载预设(工具集、prompt 等) - 闲置回收 — 超时自动 dispose Agent,防止内存泄漏
- Markdown 输出 — 回复以 Markdown 格式发送,支持代码块/表格感知切分
本地开发
# 安装依赖
pnpm install
# 构建
pnpm build
# 开发模式(watch)
pnpm dev
# 用 --patch 方式调试
export QQBOT_APPID="xxx" QQBOT_SECRET="xxx"
npx @deepseek-ai/dsh web --patch /path/to/dsh-qqbot/cordis.dev.yml
License
Listing badge
[](https://deepseek-plugin.org/plugins/tencent-connect/dsh-qqbot)Paste this markdown into your GitHub README to link back to this listing. The badge only states the listing — not a security endorsement.