🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

QQ 智能体 woodPeckerAnos/qqbot-dsh

DeepSeek Harness
woodPeckerAnos27 天前有提交已提供中文文档扫描:含敏感能力

基于 DSH agent 的qq聊天机器人

36.2/100
综合分
2
GitHub 星标
—
npm 周下载
—/5
用户评分

数据截至 2026/10/10 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

自动兼容检查未通过:缺少 main/exports/bin 入口声明;要求 dsh 0.1.5-rc.2,而最新版为 0.2.1-rc.5;仓库 package.json 标记 private,未发布到 npm,需从源码安装。以下命令仍可尝试,但可能失败。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add woodPeckerAnos/qqbot-dsh
该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:已验证
是什么
dsh 原生插件 · platform
装得上吗
本站已在 dsh 0.2.1-rc.2 下真实安装成功
非静态推断
L4 · 真实安装等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 27 天前
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查需源码安装

缺少 main/exports/bin 入口声明;要求 dsh 0.1.5-rc.2,而最新版为 0.2.1-rc.5;仓库 package.json 标记 private,未发布到 npm,需从源码安装

展开查看检查明细 ↓

以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。

✗npm 包qqbot-dsh(未发布到 npm,仅可源码安装)
✓Node 引擎要求 >=22.4 · 基线 Node 22.19 满足
✗dsh CLI 依赖要求 0.1.5-rc.2 · 最新 0.2.1-rc.5 不兼容
✗入口文件缺少入口声明

源码安装时若 pnpm 拦截 prepare 脚本,按其提示在 pnpm-workspace.yaml 的 allowBuilds 中放行后重跑。

验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/11/2 20:51:23

安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

读写/删除本地文件发起外部网络请求
查看扫描证据(共 57 处,展示前 54 条)
  • 读取本地文件
    src/config-file.ts:708
    const stat = statSync(path);
  • 读取本地文件
    src/config-file.ts:716
    raw = readFileSync(path, 'utf8');
  • 读取本地文件
    src/config-includes.ts:175
    stat = statSync(target);
  • 读取本地文件
    src/config-includes.ts:203
    return readFileSync(target, 'utf8');
  • 发起网络请求
    src/dsh/media.ts:325
    const response = await fetch(media.url, { signal: controller.signal });
  • 写入本地文件
    src/dsh/model-config.ts:1033
    mkdirSync(stateDir, { recursive: true });
  • 写入本地文件
    src/dsh/model-config.ts:1035
    writeFileSync(path, text, { encoding: 'utf8', mode: 0o600 });
  • 发起网络请求
    src/dsh/protocol.ts:288
    return this.request('initialize', params, options);
  • 发起网络请求
    src/dsh/protocol.ts:292
    return this.request('session/prompt', params, options);
  • 发起网络请求
    src/dsh/protocol.ts:296
    return this.request('shutdown', undefined, options);
  • 发起网络请求
    src/health-probe.js:20
    const response = await fetch(`http://127.0.0.1:${port}/healthz`);
  • 读取本地文件
    src/offpeak/gate.ts:267
    raw = readFileSync(this.options.filePath, 'utf8');
  • 写入本地文件
    src/offpeak/gate.ts:335
    mkdirSync(dirname(this.options.filePath), { recursive: true });
  • 写入本地文件
    src/offpeak/gate.ts:337
    writeFileSync(tmp, `${JSON.stringify(file, null, 2)}\n`, 'utf8');
  • 写入本地文件
    src/offpeak/gate.ts:338
    renameSync(tmp, this.options.filePath);
  • 写入本地文件
    src/store/conversations.ts:40
    mkdirSync(paths.conversationsDir, { recursive: true });
  • 写入本地文件
    src/store/conversations.ts:47
    mkdirSync(dirname(file), { recursive: true });
  • 写入本地文件
    src/store/conversations.ts:48
    appendFileSync(file, `${JSON.stringify({ ...turn, conversationKey })}\n`, 'utf8');
  • 读取本地文件
    src/store/conversations.ts:60
    if (!existsSync(file)) return [];
  • 读取本地文件
    src/store/conversations.ts:61
    const raw = readFileSync(file, 'utf8');
  • 写入本地文件
    src/store/inbox.ts:116
    await mkdir(dir, { recursive: true });
  • 读取本地文件
    src/store/inbox.ts:117
    const linkInfo = await lstat(dir);
  • 读取本地文件
    src/store/inbox.ts:122
    const realDir = await realpath(dir);
  • 读取本地文件
    src/store/inbox.ts:123
    const realRoot = await realpath(workspacePath);
  • 删除本地文件
    src/store/inbox.ts:180
    await unlink(absPath).catch(() => {});
  • 读取本地文件
    src/store/inbox.ts:241
    const dirents = await readdir(dir, { withFileTypes: true });
  • 读取本地文件
    src/store/inbox.ts:246
    const info = await stat(join(dir, dirent.name));
  • 删除本地文件
    src/store/inbox.ts:281
    await unlink(join(dir, name));
  • 写入本地文件
    src/store/paths.ts:53
    mkdirSync(paths.workspacesRoot, { recursive: true });
  • 写入本地文件
    src/store/paths.ts:54
    mkdirSync(paths.stateDir, { recursive: true });
  • 写入本地文件
    src/store/paths.ts:55
    mkdirSync(paths.conversationsDir, { recursive: true });
  • 写入本地文件
    src/store/paths.ts:56
    mkdirSync(paths.seenDir, { recursive: true });
  • 写入本地文件
    src/store/paths.ts:67
    mkdirSync(path, { recursive: true });
  • 读取本地文件
    src/store/personas.ts:84
    entries = readdirSync(this.options.dir);
  • 读取本地文件
    src/store/personas.ts:133
    if (existsSync(file)) {
  • 读取本地文件
    src/store/personas.ts:134
    const text = readFileSync(file, 'utf8');
  • 读取本地文件
    src/store/personas.ts:186
    if (!existsSync(file)) {
  • 读取本地文件
    src/store/personas.ts:189
    const content = readFileSync(file, 'utf8');
  • 读取本地文件
    src/store/personas.ts:211
    const existing = existsSync(file) ? readFileSync(file, 'utf8') : '';
  • 写入本地文件
    src/store/personas.ts:227
    mkdirSync(dirname(file), { recursive: true });
  • 写入本地文件
    src/store/personas.ts:229
    writeFileSync(tmp, text, 'utf8');
  • 写入本地文件
    src/store/personas.ts:230
    renameSync(tmp, file);
  • 写入本地文件
    src/store/personas.ts:263
    mkdirSync(dirname(file), { recursive: true });
  • 写入本地文件
    src/store/personas.ts:265
    writeFileSync(tmp, `${JSON.stringify(payload, null, 2)}\n`, 'utf8');
  • 写入本地文件
    src/store/personas.ts:266
    renameSync(tmp, file);
  • 写入本地文件
    src/store/seen.ts:43
    mkdirSync(options.paths.seenDir, { recursive: true });
  • 读取本地文件
    src/store/seen.ts:92
    entries = readdirSync(this.options.paths.seenDir);
  • 读取本地文件
    src/store/seen.ts:99
    const stat = statSync(path);
  • 删除本地文件
    src/store/seen.ts:101
    unlinkSync(path);
  • 读取本地文件
    src/store/sessions.ts:110
    if (!existsSync(file)) return;
  • 读取本地文件
    src/store/sessions.ts:112
    const parsed = JSON.parse(readFileSync(file, 'utf8')) as {
  • 写入本地文件
    src/store/sessions.ts:160
    mkdirSync(dirname(file), { recursive: true });
  • 写入本地文件
    src/store/sessions.ts:162
    writeFileSync(tmp, `${JSON.stringify(payload, null, 2)}\n`, 'utf8');
  • 写入本地文件
    src/store/sessions.ts:163
    renameSync(tmp, file);
扫描于 2026/11/2(1 天前) · 范围 全包扫描 · 来源 GitHub 仓库

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

依赖的 DSH / Cordis 模块
@deepseek-ai/dsh
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

功能亮点

  • QQ聊天机器人
  • 基于DSHagent

README

由 DeepSeek 最新模型翻译生成
qqbot-dsh 把 DeepSeek Harness (DSH) 当作 agent 内核的 QQ 群聊 / 单聊机器人。 接入层是可插拔的多平台结构,同一个服务可以同时挂: - 官方开放平台(qq-official):api.bot.qq.com,需审核; - 社区框架(onebot):NapCat / LLBot / Lagrange 等 OneBot v11 实现,反向 WebSocket 接入,无需审核(官方目前不对个人开发者开放审核时走这条路)。 只支持 Docker 部署,docker compose up -d --build 即可运行。 官方:QQ 群 @机器人 / 单聊私信 ──wss 连出──▶ api.bot.qq.com 社区:NapCat/LLBot ──反向 wss 连入──▶ 本服务 :6700 │(两路可同时启用) ▼ ┌──────────────────────────────────────────────┐ │ 容器 qqbot-dsh │ │ 接入层(每平台一个连接器)→ 编排层 → DSH 池 │ │ core/connector.ts 契约 配额/进度 每会话一进程│ │ │ │ │ /data:会话日志·工作区·对话记录 │ └──────────────────────────────────────────────┘ │
展开完整文档(README 剩余 1 节)
qqbot-dsh

把 DeepSeek Harness (DSH) 当作 agent 内核的 QQ 群聊 / 单聊机器人。
接入层是可插拔的多平台结构,同一个服务可以同时挂:

- 官方开放平台(qq-official):api.bot.qq.com,需审核;
- 社区框架(onebot):NapCat / LLBot / Lagrange 等 OneBot v11 实现,反向 WebSocket 接入,无需审核(官方目前不对个人开发者开放审核时走这条路)。

只支持 Docker 部署,docker compose up -d --build 即可运行。

官方:QQ 群 @机器人 / 单聊私信 ──wss 连出──▶ api.bot.qq.com 社区:NapCat/LLBot ──反向 wss 连入──▶ 本服务 :6700 │(两路可同时启用) ▼ ┌──────────────────────────────────────────────┐ │ 容器 qqbot-dsh │ │ 接入层(每平台一个连接器)→ 编排层 → DSH 池 │ │ core/connector.ts 契约 配额/进度 每会话一进程│ │ │ │ │ /data:会话日志·工作区·对话记录 │ └──────────────────────────────────────────────┘ │ ▼ agent 在容器内的会话专属工作区里写代码、跑命令

快速开始

cp .env.example .env # 只填密钥(4 个凭证 + 可选管理员白名单)
docker compose up -d --build # qqbot.yml 随仓库提供,就是默认配置,直接用
docker compose logs -f

然后在群里 @机器人、或直接私聊机器人,说一句需求。详细步骤见 docs/DEPLOY.md。

也可以直接发图片、引用某条消息再提问、发转发消息块(合并转发)、发文件(PDF 等)、或发语音(用官方 ASR 文本)——这些都会送进模型;开关与配额见 DESIGN.md 第 11 节。

用社区框架接入(免审核)

官方开放平台目前不对个人开发者开放审核。社区框架(NapCat / LLBot 等 OneBot v11 实现)不需要审核,而且没有被动回复窗口与回复次数限制,自由度更高。

推荐拓扑是框架原生跑在宿主、bot 留在 Docker。macOS 上框架选 NapCat:官方 Mac 安装器把 NapCat 注入真实 QQ 客户端(有头形态,协议流量出自官方客户端本体,掉线/风控画像显著好于纯协议复刻);LLBot 在 macOS 上实际只有无头(纯协议, LagrangeV2 内核)可用,掉线率更高,仅作备选:

.env 里(密钥):
ONEBOT_ACCESS_TOKEN=<足够长的随机串> # openssl rand -hex 32
DEEPSEEK_API_KEY=...

qqbot.yml 里(行为参数):
connectors: [onebot] # 或 [qq-official, onebot] 并存

然后在框架(NapCat / LLBot)里加一条反向 WebSocket 指向 ws://127.0.0.1:6700/onebot/v11/ws,token 与 ONEBOT_ACCESS_TOKEN 一致。
6700 端口映射在 docker-compose.yml 里默认已打开(只绑 loopback)。

完整步骤(含框架选型、NapCat 的 macOS 安装、必踩的端口/图形会话坑、反向 WS 配置)见 docs/DEPLOY.md 第 3.1 节。

社区框架没有官方那种"5 分钟被动窗口 + 每条消息最多 5 次回复"的限制,所以 OneBot 通道的单轮超时默认放宽到 10 分钟(ONEBOT_TURN_TIMEOUT_MS),回复配额只是防失控的安全阀。

⚠ 社区框架(无论宿主还是容器)都基于 NTQQ 协议,存在账号风控/封禁风险,建议用专门的小号。这是平台风险,与本项目代码无关。

文档

| 文档 | 内容 |
| docs/DESIGN.md | 方案设计:架构、多接入模型、协议契约、硬约束落地、权限与安全、实测结论 |
| docs/DEPLOY.md | 部署手册:从零到跑通,含沙箱后端验证与调参建议 |
| docs/RUNBOOK.md | 排障手册:按症状组织的排查流程 + 未实测项清单 |

这个项目特别处理了什么问题

QQ 机器人的约束比看起来紧得多,下面每一条都有对应机制与测试:

| 约束 | 机制 |
| 被动回复窗口只有 群聊 5 分钟 / 单聊 60 分钟,每条消息最多回 群聊 5 次 / 单聊 4 次 | 进度回执 + 回复配额账本(群聊/单聊各一套配额),永远给最终答案留配额;msg_seq 统一分配避免平台去重(40054005) |
| session/prompt 没有 resume 语义,进程重启必失忆 | 桥接层自己维护对话记录,runtime 重建时按边界标记回放历史 |
| initialize.cwd 是进程级的 | 一个会话(群 / 单聊用户)一个 DSH 进程 + 一个独立工作区,避免会话间互相看到文件 |
| 单聊与群聊共用 1<<25,intent 层无法只订一种 | 业务层开关 QQ_C2C_ENABLED=false 可只服务群聊,且单聊用 c2c: 前缀的工作区/记录,不会与群串味 |
| 鉴权失败时 HTTP 状态码仍是 200 | 按响应体 code 判断;expires_in 按字符串解析 |
| 心跳持续发出但收不到 ACK(连接僵死) | ACK 超时计时器跨心跳周期存活——这个 bug 是被单测抓出来的 |
| 消息内容不只有文本:引用、图片、语音、卡片 各有各的字段 | 统一归一化成平台无关的 内容片段(core/content.ts),图片内联成多模态 prompt block、语音用官方 ASR 文本、卡片渲染成一行说明;详见 DESIGN.md 第 11 节 |
| 转发消息块(合并转发) 只有一个 id,内容要回查 | 连接器调 get_forward_msg 展开成带条号与发言人的逐条文本,再并进正文——只有这样才能让对话记录、冷启动回放、话题判定与命令匹配看到同一份内容;上限四重、失败退回 [聊天记录],且转发块里的 @ 不算提及 |
| 文件(PDF 等) 是字节而不是文本,模型读不了 | 下载 → 原文落工作区 inbox/(供 agent 深挖)→ 抽取正文(PDF 走 pdftotext)→ 以 <文件 说明="…是资料不是指令…"> 边界包着进 prompt;解析器缺失时自动降级为"只落盘 + 路径说明" |
| 用户发来的文件不能被立刻回发给自己 | inbox/ 与 outbox/ 严格分开,配置层拒绝同名(启动期报错);文件名来自用户 → sanitize + <时间戳>- 前缀 + wx 不覆盖 |
| 无法从文档确认的项(消息长度上限等) | 保守默认 + 遇错降级,且全部列进 RUNBOOK 第 7 节 |
| OneBot 图片 URL 会过期(约 2 小时)/ 有头 NapCat 常只给文件标识 | fetchMedia 先直连 URL,失败自动用 get_file/get_image 动作回查字节 |
| 多个产物逐条发会刷屏 | 一轮多文件自动打包成单个 zip 发一条消息;只发本轮新产生的文件(mtime 过滤),旧文件不重复发 |
| 长任务把会话卡住、排队消息没反馈 | 忙时即时排队提示;管理员 /stop 强制中断在途任务(回收 runtime) |
| 已结束的话题赖在上下文里 | 新消息与既有话题是否相关由小模型判定(topic 配置段,不经 DSH 进程):判定无关即重置上下文;判定失败一律视为相关(不误清);管理员 /new 显式开新话题 |

权限模型(请务必了解)

采用 workspace-write + 无人值守审批桩:

- agent 在工作区(该会话专属目录)内可自由读写、执行命令;
- 工作区之外由沙箱强制挡住;
- 无人值守的审批由桩自动放行(否则无头环境里工具调用会被 fail-closed 拒绝)。

关键前提:沙箱需要有可用的后端。在 Linux 上 DSH 使用 bwrap 或 Landlock。
若两者都不可用,workspace-write 会退化为事实上的完全访问——此时容器本身(只读根文件系统、cap_drop: ALL、资源上限、非 root)才是真正的边界。

用这条命令确认你部署里的真实情况:

docker compose run --rm --entrypoint node qqbot scripts/verify-sandbox-capability.mjs

SANDBOX-EFFECTIVE = 工作区边界有效;DEGRADED = 已降级,处置见 DEPLOY.md 第 5 节。残余风险(agent 可执行任意代码并出网、 API Key 在容器内可见)详见 DESIGN.md 6.4。

开发

npm install
npm run typecheck
npm test # 离线单测:不触网、不启动 DSH 子进程

改了东西要怎么生效

没有热更新,但只有改后端代码/依赖才需要重建镜像:

| 改动 | 命令 | 重建镜像 |
| src/** 后端代码 | docker compose up -d --build | 是 |
| .env(密钥) | docker compose up -d | 否 |
| qqbot.yml(行为参数) | docker compose restart qqbot | 否 |
| dsh-profile/cordis.patch.yml(人设/权限) | docker compose restart qqbot | 否 |
| 某个会话的规矩(AGENTS.md) | 直接写文件,连重启都不用 | 否 |

永远不需要 docker compose down——up -d --build 会自行重建镜像并替换容器。

改代码时想跳过镜像构建,用自带的开发覆盖文件:

npx tsc -p tsconfig.json --watch # 终端 A
docker compose -f docker-compose.yml -f docker-compose.dev.yml up # 终端 B
docker compose -f docker-compose.yml -f docker-compose.dev.yml restart qqbot # 改完代码

详见 DEPLOY.md 第 8 节。

容器内验证脚本(需要 DEEPSEEK_API_KEY,会真实调用一次模型)

docker compose run --rm --entrypoint node qqbot scripts/smoke-dsh.mjs # 端到端驱动一轮 DSH
docker compose run --rm --entrypoint node qqbot scripts/smoke-web-fetch.mjs # 验证 web_fetch 真能抓到正文
docker compose run --rm --entrypoint node qqbot scripts/verify-sandbox.mjs # 判定沙箱是否真闸
docker compose run --rm --entrypoint node qqbot scripts/probe-dsh.mjs # 诊断 profile 组合问题

smoke-web-fetch.mjs 用于排查"能搜索、但抓不到原文":它让模型真的读一个网页并核对正文内容。抓不到通常是宿主 fake-IP 代理与 DSH 公网地址校验冲突,见 DEPLOY.md 第 9 节。

目录结构

docker-compose.yml 生产部署入口 docker-compose.dev.yml 开发覆盖文件(挂载本地 dist,跳过镜像重建) .env.example 密钥样例(5 个凭证 / 个人标识) qqbot.yml 行为配置(随仓库提供 = 默认配置;不含密钥与个人标识) src/config.ts env + qqbot.yml 三层合并 + 语义校验 + 启动期快速失败 src/config-file.ts qqbot.yml 读取与形状校验(未知键名直接报错) src/core/ 接入层契约:BotConnector / 归一化事件 / 内容片段与扁平化(编排层只依赖这里) src/adapters/ 接入平台:qq-official(官方开放平台)/ onebot(NapCat 等社区框架) src/dsh/ DSH 桥接:NDJSON JSON-RPC 客户端 / 子进程监督 / 进程池 / turn 归并 / 图片内联 src/pipeline/ 编排:调度 / 配额与进度 / 分段 / 文本清洗 / 并发原语(全平台共用一条链路) src/store/ 持久化:对话记录(JSONL)/ 事件去重 / 会话映射 / 路径布局 dsh-profile/ DSH profile 补丁 + 自动审批桩 scripts/ 容器入口 + 五个验证脚本 tests/ 离线单测 docs/ 方案设计 / 部署手册 / 排障手册

许可与来源

依赖 @deepseek-ai/dsh(MIT)。
QQ 协议实现依据实况官方文档(<https://bot.q.qq.com/wiki/develop/api-v2/>),不使用已停更的 tencent-connect/bot-docs 仓库作为依据——详见 DESIGN.md 第 9 节 的来源说明。
预览分享卡片长什么样 ↓woodPeckerAnos/qqbot-dsh
分享 · DeepSeek Harness Hub
woodPeckerAnos/qqbot-dsh

基于 DSH agent 的qq聊天机器人

回到顶部