dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
dsh 插件:QQ 通道(OneBot 11 标准客户端)——正向 WebSocket + HTTP,把你的 Agent 接进 QQ 群和私聊,零依赖 · OneBot 11 client channel for dsh: your agent in QQ groups and DMs, zero dependencies.
English | 简体中文
给 DeepSeek Harness(dsh)用的 QQ 通道:一个标准的 OneBot 11 客户端 —— 连上你自己跑的那个 OneBot 实现, 把你的 dsh Agent 接进 QQ 群和私聊。
零依赖:只用 Node 内置的东西(fetch / WebSocket / node:crypto),没有 npm 包要装。
同类里更成熟的选择:想一份代码接多平台(微信 / 飞书 / 钉钉 / 企微 / Slack / TG / Discord / QQ…),走官方机器人凭据的,用 xmanrui/dsh-im;只接 QQ 的,还有 cheesehaqi/dsh-qq-onebot-bridge 和 Hoshino-Yumetsuki/dsh-onebot。 我们这份留着的理由:OneBot 11 标准通道、零依赖——它就是我们自己线上那台机器在用的版本(个人号路线), 按协议写,换 OneBot 实现不用换插件。 要装先去装上面那几个,它们覆盖的面比这份宽。
因为换实现不用换插件。
中文社区的 QQ 机器人实现(go-cqhttp、Lagrange、NapCat、LLOneBot…)几乎都实现了 OneBot 11。 如果这个插件是「某某框架的适配器」,那你换一个实现就得换一套代码;而按协议写, 你那边跑着哪个都行 —— 配置文件里改一行地址就完事。
所以这里做的事很窄:把协议接进来,把消息交给 dsh 的 agent。 人设、记忆、内容策略、日结、表情包那些,都属于「接上之后怎么用」,应该由你自己的插件或系统提示决定, 不该塞进一个通道插件里(塞进来的东西,别人没法关)。
QQ 用户 ──> 你的 OneBot 实现 ──ws──> [dsh-onebot] ──> dsh agent(一个会话一个 session)
│
QQ 用户 <── 你的 OneBot 实现 <──API───────┘
message 事件(群 / 私聊)。CQ 码字符串和 segment 数组两种长相都认。send_group_msg / send_private_msg,消息段支持 text / at / image / record / reply / face。group:<群号>、私聊 private:<QQ号>,一个 key 一个 dsh 会话,互不串线;
同一个会话里的消息串行处理,一条消息换一条回复。get_msg 兜底)、
群成员名册(@123456789 → @爱丽丝)、断线指数退避重连、日志收敛。群里有人 @ 机器人:
【群987654321 · 22:15】爱丽丝(QQ 111222333)
↩ 回复 bob(QQ 444555666):「(图)」
[在 @ 你] 这图啥意思
行头是「在哪 · 几点」,第二行是被回复的那句(人话化过的),最后是正文。
被回复的那条本地没留到就用 get_msg 去捞;捞不到会写「原文我这边没拿到」,
模型就知道别硬编 —— 而不是收到一句孤零零的「这个什么意思」。
dsh plugin --profile web add github:JackZo400/dsh-onebot
或者 clone 到本地,在 bundle patch 里 insert(cordis.patch.yml 里那份示例可以直接抄)。
环境:Node 18+(HTTP 那半边够用)。要收消息需要 WebSocket:
Node 22+ 自带全局 WebSocket;Node 18/20 请在配置里注入(WebSocketImpl)或者
先用 HTTP 半通(能发不能收)。插件本体不依赖任何 npm 包。
随便选一个(go-cqhttp / Lagrange / NapCat / LLOneBot 都行),把 QQ 登上去,然后 在它的配置里开「正向 WebSocket 服务」(有的叫 "WebSocket 服务" / "正向 WS" / "ws server"), 记下:
3001)/,NapCat 常见是 /onebot/v11/ws)⚠️ 两个容易踩的:端口别和别的东西撞;一个 QQ 不要被两个实现同时登(会互相踢)。
先不急着装 dsh 插件,用 Node 自带的能力空手验一次(Node 22+):
node -e "const ws=new WebSocket('ws://127.0.0.1:3001');
ws.onopen=()=>{console.log('连上了');ws.send(JSON.stringify({action:'get_login_info',params:{},echo:1}))};
ws.onerror=(e)=>console.log('连不上:',e.message||e);
ws.onmessage=(e)=>console.log('回包:',e.data)"
看到 连上了 和一条 {"status":"ok","retcode":0,"data":{"user_id":...,"nickname":...}}
就说明地址、token、路径都对;这一步不通的话,后面不用试了 —— 问题在实现那边。
(设了 token 就换成 ws://127.0.0.1:3001?access_token=你的token。)
改你的 profile patch(cordis.patch.yml 里那份抄过去改):
- insert:
- id: onebot
name: dsh-onebot
config:
ws: ws://127.0.0.1:3001 # 第一步验通过的那个地址
accessToken: '' # 实现那边设了才填
selfId: '' # 留空就行(用事件里的 self_id)
groups: [] # 留空 = 所有群;想限定就写群号
private: false # 私聊默认关(要开就 true,并考虑填 users 白名单)
requireMention: true # 群里只有 @ 我才花模型的钱
重启那个 profile,然后在日志里找 [dsh-onebot](插件直接写 stderr):
dsh ... 2>&1 | grep '\[dsh-onebot\]'
应该依次看到这几行:
[dsh-onebot] 就绪:ws=ws://127.0.0.1:3001 群=不限 私聊=关 群内需@=是
[dsh-onebot] 已暴露 onebot 服务(别的插件可以调 send / call)
[dsh-onebot] 已连上 OneBot 服务
[dsh-onebot] 已登录:你的昵称(123456789)
已连上 + 已登录 就是通了。接下来在群里 @ 一下机器人说句话,
日志里会出现 收到 group group:<群号> "…" @我 · 谁、新建会话 …、已回复 group:… N 字。
| 现象 | 大概是什么 | 怎么办 |
|---|---|---|
日志里根本没有 [dsh-onebot] | 插件没加载,或 patch 没生效 | 查 profile 的 patch 路径;插件名要写 dsh-onebot |
一直 建连接失败 / 连接断开 | 地址/端口/路径不对,或实现没开正向 WS | 回到第一步用那段 node -e 验 |
已连上 但取不到登录信息 | 有的实现没开 get_login_info | 不影响收发(self_id 用事件里的),日志里会说明 |
| 收不到任何消息 | 只配了 http 没配 ws | OneBot 的 HTTP API 不推事件;配 ws |
| 私聊不理 | private: false(默认) | 改 true;想只理自己就把 users 填上 |
| 群里 @ 了它不理 | 群不在 groups 里 / requireMention / 实现没给 at 段 | 打开 debug: true 看日志里的 不派发:<原因> |
| 它自己跟自己说话(循环) | 那个实现的回声形态没见过 | 提 issue,把 debug 日志里那条事件贴上来 |
回复失败:… retcode=xxx | 被实现/风控拒了 | 看日志里的 wording;短消息能发、长消息发不出通常是长度上限 |
| 回复能发出去但内容缺图 | 图那部分这一版不搬(见下) | 开 imageLinks: true 把直链给模型 |
插件 ctx.provide('onebot', …),别的插件(比如定时任务、主动推送)可以直接用:
const onebot = ctx.get('onebot')
await onebot.send('group:987654321', '开会了', { replyTo: undefined })
await onebot.sendGroupMsg(987654321, [{ text: '嗨 ' }, { at: '111222333' }, { image: '/tmp/a.png' }])
const stats = onebot.stats() // { events, messages, dispatched, replied, skipped, errors, sessions, connected, pending, attempts }
两条都是离线的:假 WebSocket、假 fetch、假事件 JSON、假 ctx,不需要网络也不需要 dsh。
node test/selftest.mjs # 协议层(纯函数):CQ 编解码 / 事件解析 / 发送构造 / API 错误 / 退避
node test/plugin-selftest.mjs # 插件层:拿假 ctx 把 apply() 真跑起来,灌事件看它有没有真的收发
覆盖的关键点(都是踩过坑才写的):
& 的还原顺序)@ 我 的判定([CQ:at,qq=<self_id>] 与 segment 形式的 at;@全体 不算)user_id == self_id / message_sent)不会被派给 agentreply 段)能取到被引用那条的内容(本地缓存 + get_msg 兜底)text + at + image 混合会构造成正确的消息段(reply 段永远在最前)retcode≠0 的错误路径这两份自检是会失败的:把 escapeCq 里的逗号删掉、把回声过滤去掉、把退避改成常数,
自检分别报错并以非 0 退出(这也是我们验证"测试不是摆设"的办法)。
支持
post_type=message 的群/私聊消息;message 字段两种形态(CQ 码串 / 段数组)text / at / image / record / reply / facesend_group_msg、send_private_msg、get_msg、get_group_member_list、
get_group_member_info、get_group_info、get_login_infomessage_sent 与 user_id == self_id 两种)、self_id 自动识别@数字 → 真 at 段、人话化((图)/[语音]/[卡片]…)get_msg 兜底(同一条 5 分钟内只捞一次)requireMention、quoteReply、format、imageLinks 等开关不支持(有意不做,或这一版没做)
[CQ:image] 只变成「(图)」,语音只变成「[语音]」。
想让它真看得见图,给 dsh 配一个能取图的工具,再打开 imageLinks: true 把直链写进正文。forward 段):只有「[合并转发]」占位,没有展开。onebot 服务(见下),别的插件可以用。selfId,但没测过。它是把一条真在跑的私有 QQ 通道重写成标准协议实现的:协议解析(CQ 码、@ 判定、引用、 回声、消息段构造)、会话调度(一会话一 agent、串行队列、碎话切割)、 名册与缓存、重连退避这些踩过坑的部分都搬过来了。
刻意没有搬的是原来那套部署策略:私有平台的登录/事件流、人设与记忆分级、 内容门禁、消息攒批与日结、表情包库、语音转写、主人指令 —— 那些跟协议无关, 在一个通用插件里只会变成"别人关不掉的行为"。
MIT,见 LICENSE。
→ Full English README: README.en.md