Skip to content

feat(dsh): adapt to DSH 0.1.5 via the official SDK runtime; add QQ commands and bot interop - #4

Open
RickyidRickyid wants to merge 1 commit into
Derpyu520:mainfrom
RickyidRickyid:feat/dsh-0.1.5-sdk-adaptation
Open

RickyidRickyid wants to merge 1 commit into
Derpyu520:mainfrom
RickyidRickyid:feat/dsh-0.1.5-sdk-adaptation

Conversation

@RickyidRickyid

Copy link
Copy Markdown

适配 DSH 0.1.5:改走官方 SDK stdio 运行时,并补上 QQ 指令系统 / 机器人互认 / 引用回复增强。

关联 #3 —— 那条 issue 描述的正是这个问题(401 BrowserAuth、host.describe 等端点 404、连接 DSH 事件流… 每 3 秒重试)。issue 里给出的斜杠式端点协议是准确的,本 PR 走的是另一条并行路线(官方 SDK),原因见下。

为什么不用 /api 斜杠式端点

  • session/listsession/create(支持 agentPreset)、session/promptsession/selectModel 确实可用,控制面能力更全。
  • events.mux / events.host 已不存在,实时事件需要 session/page 轮询或 session/follow 流载体;另外 web 端 /api 的 BrowserAuth 需要注入浏览器会话 cookie。
  • DSH 自带的官方 SDK 面(dsh --profile sdk,stdio 上的换行分隔 JSON-RPC)没有 HTTP、cookie、WS,事件流是干净的原生通知,所以先走它把服务恢复起来。斜杠式端点更适合作为后续方案,因为它能拿回审批回传与 per-session 换模型。

改动

DSH 传输

  • 新增 src/dsh-sdk-client.js:以子进程方式拉起 dsh --profile sdk,按官方协议通信(initialize / session/prompt / shutdown + session.event 等通知)。
  • 一个适配器把该传输还原成 bridge.js 原本就在用的 api.* 形状,因此适配器之外只改了 2 行。
  • SDK 运行时无法恢复已存在的会话:复用它共享会话库里已有的 session id 会直接报 session "..." already exists(实测与 cwd 无关,全新空 cwd 一样报)。现在启动时丢弃陈旧映射,并使用带随机 runId 的全新 id。
  • 新增 scripts/test-adapter-contract.mjs 钉住适配器返回形状——第一次尝试正是因为形状静默不匹配(value.sessionId 拿到 undefined)而失败。
  • 配置:dsh.provider/model 改为 deepseek-official/deepseek-flash(sdk profile 只挂内置适配器),新增 dsh.sdkProfile
  • 需要在 ~/.dsh/profiles/sdk/cordis.patch.yml 里把 agent-presets.default 设为 qq-chat-v2 并挂三个 qq MCP server(README 有说明)。

QQ 指令(本地应答,不消耗模型 token)

  • 新增 src/qq-commands.js + bridge.js 里的路由:/指令 /状态 /ping /机器人 /我是谁 所有人可用;/潜水 /唤醒 /暂停 /继续 /重置 /模型 /角色 /静默 /活跃 /调机器人 仅管理员。
  • 不认识的 /xxx 不再统一回"仅管理员可用":管理员的原样转给 DSH,其他人的当普通聊天继续走 AI。
  • 限频按「会话 + 发送者」,一个人刷指令不会挤掉别人。

机器人互认

  • 新增 src/bot-registry.js:消息事件里没有机器人标识,但 SnowLuma 的群成员列表带 is_robot,据此缓存每群机器人名册(state/bots.json),网关不支持时退化为昵称启发式。
  • 其他机器人的消息会标记 isBot、发送者名字也会标出;唤醒提示里会列出本群机器人。
  • 新增 qq_list_group_bots / qq_call_bot,可让 agent 以你的身份调用别的机器人的指令。

引用回复

  • qq_reply / qq_send_group_message 支持可选 atUserId
  • qq_send_burst 支持 replyToMessageId + atUserId(只作用于第一条)。
  • 发送成功后回传被引用者信息。

其他

  • src/sticker-lib.js 补一条收藏表情的行为约束(说过"我收下了"就必须真的调用 qq_collect_sticker)。

测试

  • scripts/test-qq-commands.mjs:16 项(指令解析/权限/帮助渲染/别名冲突)
  • scripts/test-bot-registry.mjs:13 项(权威 is_robot / 启发式兜底 / 手工名单 / 缓存 / 持久化)
  • scripts/test-adapter-contract.mjs:适配器返回形状契约
  • scripts/test-e2e-qq-commands.mjs:23 项,用假 OneBot 网关驱动真实 bridge 进程(需 3001/3002 空闲;跑完自动还原 state/

已知取舍

  • SDK 协议只有 3 个方法,没有审批/提问回传通道,所以「把工具审批转发到 QQ、回复通过/拒绝」这条链路在 SDK 下失效;sessions.selectModel 也无法真正切换(provider/model 是每进程一次)。这两点是斜杠式端点方案的优势所在。
  • 因为 SDK 不能恢复会话,每次重启 bridge,各 QQ 会话的 DSH 上下文会重新开始

…mmands and bot interop

DSH 0.1.5-rc.1 replaced the host-apiproxy HTTP/WS transport this bridge was built
on: /api gained a browser-session auth fence, the dot-named RPC endpoints are gone,
and the shipped 0.1.1-rc.2 apiproxy cannot even be loaded against 0.1.5 (it imports
ApiRemoteSessionNotFound, which no longer exists). This moves the bridge onto the
official SDK surface instead.

DSH transport
- new src/dsh-sdk-client.js: spawns `dsh --profile sdk` as a child process and speaks
  the documented newline-delimited JSON-RPC protocol over stdio (initialize /
  session/prompt / shutdown + session.event notifications)
- an adapter reshapes that transport back into the api.* surface bridge.js already
  used, so only two lines changed outside the adapter
- the SDK runtime cannot resume an existing session, and reusing an id already
  present in DSH's shared session store fails with `session "..." already exists`;
  the bridge now drops stale mappings on boot and allocates fresh run-scoped ids
- adapter return shapes are pinned by scripts/test-adapter-contract.mjs (a silent
  shape mismatch is what broke the first attempt)
- config: dsh.provider/model -> deepseek-official/deepseek-flash (the sdk profile
  only mounts built-in adapters), plus dsh.sdkProfile
- the SDK profile needs ~/.dsh/profiles/sdk/cordis.patch.yml with
  agent-presets default=qq-chat-v2 and the three qq MCP servers

QQ commands (answered locally, no model tokens)
- new src/qq-commands.js + a router in bridge.js: /指令 /状态 /ping /机器人 /我是谁
  for everyone; /潜水 /唤醒 /暂停 /继续 /重置 /模型 /角色 /静默 /活跃 /调机器人
  for the owner
- unknown "/xxx" no longer gets a blanket "admin only" reply: the owner's is
  forwarded to DSH, everyone else's continues as ordinary chat
- rate limiting is per conversation+sender so one person cannot starve others

Bot interop
- new src/bot-registry.js: SnowLuma returns is_robot in the group member list
  (message events carry no bot flag), so the bridge caches a per-group robot roster
  (state/bots.json) with a nickname heuristic as fallback
- other bots' messages are tagged isBot and marked in the sender label; the wake
  prompt lists the group's robots
- qq_list_group_bots / qq_call_bot let the agent invoke another bot's commands

Quote replies
- qq_reply and qq_send_group_message take an optional atUserId
- qq_send_burst now accepts replyToMessageId + atUserId (first message only)
- responses carry the resolved quoted sender

Tests: 16 command-parser assertions, 13 bot-registry assertions, and a 23-check
end-to-end suite that drives a real bridge against a fake OneBot gateway.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant