voyager
Nagi-ovo
Enhancement suite for Gemini, AI Studio, Claude & ChatGPT — plus a prompt manager for any websites, DeepSeek Harness included. / 面向 Gemini、AI Studio、Claude 与 ChatGPT 的增强套件;其中的提示词管理器可用于任意网站,如 DeepSeek Harness。
PROJECT TOPICS
INSTALL REFERENCE
dsh plugin --profile web add github:cheesehaqi/dsh-qq-onebot-bridge
该命令指向仓库当前默认分支;尚无绑定当前 commit 的完整验证结果。
PROJECT README
QQ ↔ DeepSeek Harness 双向桥插件(独立 bundle)。QQ 消息直接驱动 DSH agent 会话,agent 回复自动发回 QQ。
v0.4.0 主题:一切皆可调试。每条消息一个 traceId、每个"没回复"都有中文原因、任意历史消息都能离线重跑、假事件能喂进真实管线——而且这 6 条硬约束在自带控制台里随时可验收(见「调试」与「硬约束验收台」)。
这里只列最近两个大版本新增的能力(v0.5 线与 v0.4 线)。更早的基础能力——双向消息桥、会话续接、持久化记忆、定时提醒、群管套件、TTS、生图、签到打卡、积分与小游戏、防撤回、敏感词过滤……——全都还在,逐版本记录见 CHANGELOG.md,开关与配置见下方「配置」。
v0.5 线
get_forward_msg 展开成正文交给模型;同时接通了早已封装却没人调用的能力:/成员(名单/详情)、/群信息、/好友(默认关)、/退群(默认关)、agent 工具 qq_recent_history / qq_member_info / qq_react(表情回应)/文件、/文件 文件夹名、/取 文件名(下载后只发私聊)、/相册、/ocr(图片转文字);消息按天归档到 cwd/qq-history/,/找 关键词 与 agent 工具 qq_search_history 在最近 N 天里检索(控制台也有「群资产 · 历史检索」卡片)127.0.0.1:8798,token / HMAC 二选一鉴权,限频 + 64 KiB 上限)把外部系统事件推进会话;定时播报(RSS / 天气 / MC,/播报 管理);掉线自愈(只拉起、不杀进程,带冷却与每小时上限)/戳 主动戳一戳、被戳回戳、私聊「正在输入」、自动贴表情、表情回应统计 /赞榜 /谁赞了、/点赞、标记已读(真机探针结论:该 NapCat 构建发不出内联按钮,故按能力探测降级)/群打卡、/全体余量、/禁言名单、/群详细、/入群通知、/批量踢(两步确认 + 分批执行)、/待办 /完成待办 /取消待办、/移动文件 /重命名文件 /删文件 /新建文件夹、/传图、/群名 /群备注、/群权限、/历史可见、/周报v0.4 线
control/(端口 8799)——会话列表、实时事件流、trace 检索、体检报告、诊断包导出、录制与离线回放、事件注入、硬约束验收台@deepseek-ai/schemastery,修好干净环境的安装依赖(issue #1)QQ 客户端 ←→ OneBot 实现(NapCat / LLOneBot / OpenShamrock / Lagrange…)
│ 反向 WebSocket(OneBot 连我们;端口 6700)
▼
dsh-qq-onebot-bridge(本插件)
│ ctx.agents.create / followup
▼
DSH agent 会话(每群/每私聊用户一个)
旁路(都不参与回复决策,出问题也不影响发消息):
每条入站事件 ──► qq-inbox.jsonl (录制:可离线回放)
每个决策点 ──► qq-trace.jsonl (结构化事件:stage/ok/reason/耗时/traceId)
快照每 2s ──► qq-runtime.json (会话/闸门/生效配置/录制与注入状态)
qq-inject.jsonl ◄── 控制台写、桥轮询读 (注入:默认 dry-run,出站全拦截)
独立控制台 control/(进程 8799,不依赖 DSH 桌面端)
├─ 读:端口/进程、事件流、决策链、体检、录制列表、运行快照
├─ 写:启停宿主/NapCat/TTS、释放端口、离线回放(沙箱 + dry-run)、事件注入
└─ 鉴权:仅 127.0.0.1 + token + 同源 Origin 校验
# 安装(本地目录):先在被安装的目录里装运行时依赖,再注册插件
# 本地目录走 pnpm 的 link:,不会自动安装被链接包自己的依赖(ws)
cd <本目录> && npm install --omit=dev
dsh plugin --profile web add <本目录>
# 卸载(随时可移除,独立 bundle 不影响其它插件)
dsh plugin --profile web remove dsh-qq-onebot-bridge
装/卸后重启 dsh web 生效。
官方依赖(
@deepseek-ai/dsh-*、@deepseek-ai/schemastery)声明为peerDependencies,由 DSH 随 profile 一起装好,插件目录里不需要重复安装。
profile 的 cordis.patch.yml 覆盖 id: dsh-qq-onebot-bridge 的 config(完整示例见 examples/cordis.patch.example.yml):
| 键 | 默认 | 说明 |
|---|---|---|
host |
127.0.0.1 |
反向 WS 监听地址 |
port |
6700 |
反向 WS 监听端口 |
accessToken |
'' |
OneBot 端须携带的 Bearer token(空=不校验) |
allowUsers |
[] |
私聊用户白名单(空=拒绝所有私聊,务必填入自己的 QQ 号) |
allowGroups |
[] |
群白名单(空=拒绝所有群消息,列出机器人服务的群号) |
botQq |
0 |
机器人 QQ 号(用于群内 @ 检测;0=任何群消息视为@) |
replyOnlyWhenMentioned |
true |
群聊仅 @机器人 才回复 |
acceptPrivate |
true |
是否回复私聊(私聊仍需 allowUsers 放行) |
autoCollectStickers |
false |
自动收藏消息里的图片表情到本地图库 |
faceEnabled |
true |
表情功能总开关([face:] 标记 + qqface* 工具) |
sessionMode |
chat |
群会话分组:chat=每群一会话;user=每群每人一会话 |
cwd |
'' |
会话工作目录(同时决定 qq-faces/、qq-replies/、qq-bridge-debug.log 的位置) |
provider |
'' |
LLM provider 覆盖(空=agent 默认) |
model |
'' |
LLM 模型覆盖(空=agent 默认) |
maxMessageLength |
1700 |
单条出站消息最大字符数(超出自动分段) |
botName |
小鲸鱼 |
机器人显示名(合并转发卡片的署名) |
sessionResumeEnabled |
true |
宿主重启后 resume 上次会话(完整记录续接);关掉则每次重启都新建会话 |
agentMediaToolsEnabled |
true |
暴露 qq_send_image / qq_send_file / qq_send_voice / qq_recall 工具 |
fileSendDirs |
[] |
agent 允许发送文件的额外目录(会话 cwd 始终允许) |
fileSendMaxBytes |
52428800 |
agent 可发送的单文件大小上限(字节,默认 50 MiB) |
imageSendMaxBytes |
4194304 |
图片超过此大小(默认 4 MiB)先用 ffmpeg 压缩再发 |
recallWindowSeconds |
110 |
出站消息可被 qq_recall / /撤回 撤回的时间窗(秒) |
forwardLongReplies |
false |
群聊超长回复改发合并转发卡片 |
forwardThresholdChars |
600 |
触发合并转发的字数阈值 |
actionRatePerMinute |
20 |
写操作闸门:全部会话合计每分钟上限 |
actionRatePerDay |
500 |
写操作闸门:全部会话合计每日上限 |
actionAuditEnabled |
true |
写操作与拒绝记录写入 cwd/qq-actions.log |
keywordEnabled |
false |
关键词问答库(默认关闭):命中本地词库直接回复,不走模型、不需要 @ |
keywordFile |
'' |
词库文件路径(空=cwd/qq-keywords.json);支持 exact/contains/regex、随机多答、图片、作用域与冷却 |
fortuneEnabled |
true |
今日人品/运势、抽签、塔罗(按 QQ 号+日期确定性生成,纯本地) |
diceEnabled |
true |
骰子(.r 3d6)与随机抽人(/抽一个 A B C) |
pointsEnabled |
false |
积分经济(默认关闭):/积分 /排行榜 /转账 @某人 数量 |
pointsPerMessage |
1 |
每条消息获得的积分(0=聊天不得分) |
pointsDailyCap |
20 |
每人每日通过聊天可得积分上限 |
pointsCheckinBonus |
5 |
每日签到额外奖励积分 |
gameEnabled |
false |
群内小游戏(默认关闭):成语接龙、猜数字 |
idiomChainTimeoutSeconds |
120 |
接龙闲置超时(秒) |
guessNumberMax |
100 |
猜数字上限(1~N) |
guessNumberMaxTries |
10 |
猜数字可用次数 |
antiRecallEnabled |
false |
防撤回(默认关闭):缓存最近消息,被撤回时补发内容 |
antiRecallInGroup |
true |
补发到群里(false=私聊发给第一个管理员) |
antiRecallImages |
true |
一并补发被撤回的图片(最多 3 张) |
antiRecallCacheSize |
50 |
每会话缓存的消息条数 |
antiRecallMaxAgeMinutes |
120 |
缓存消息可恢复时长(分钟) |
antiRecallCooldownSeconds |
5 |
同一会话两次补发的最小间隔 |
filterEnabled |
false |
敏感词过滤(默认关闭) |
filterWordsFile |
'' |
词表路径(空=cwd/qq-badwords.txt;# 注释、re: 正则、改动自动热重载) |
filterAction |
warn |
处置方式:warn 提醒 / recall 撤回 / mute 禁言 |
filterMuteSeconds |
300 |
mute 处置与刷屏升级时的禁言秒数 |
filterWhitelist |
[] |
白名单词/正则(命中即放行) |
floodEnabled |
false |
刷屏防护(默认关闭) |
floodWindowSeconds |
10 |
刷屏统计窗口(秒) |
floodMaxMessages |
8 |
窗口内允许的消息条数 |
floodMuteSeconds |
300 |
刷屏升级禁言秒数 |
floodStrikeLimit |
3 |
警告几次后禁言 |
verifyEnabled |
false |
入群/加好友验证(默认关闭):请求进队列并私聊推送管理员 |
verifyKeyword |
'' |
口令:验证消息包含它则自动放行(空=全部人工审批) |
verifyTimeoutSeconds |
300 |
请求超时时间(超时出队并提醒管理员) |
verifyMaxPending |
20 |
待审队列上限 |
statsEnabled |
false |
发言统计(默认关闭):/统计 今日活跃榜、/周榜 周榜,并作为日报数据源 |
statsKeepDays |
30 |
发言统计保留天数 |
groupReadEnabled |
true |
只读群信息:/荣誉 /公告 /群精华 |
mcStatusEnabled |
true |
/mc <host[:port]> 查询 Minecraft Java 服务器状态(Server List Ping,无 Key) |
mcStatusTimeoutMs |
5000 |
MC 状态查询超时(毫秒) |
recurringReminderEnabled |
true |
重复提醒:「每天8点」「每周一9点」「每个工作日15点」 |
dailyReportEnabled |
false |
每日群日报(默认关闭):到点让 agent 总结当天聊天并发到群里 |
dailyReportTime |
22:00 |
日报时间(本地 HH:mm) |
dailyReportChats |
[] |
固定接收日报的会话(如 ["g:100000001"];为空则用 /日报 on 的开关,再为空则回落到全部群白名单) |
sttEnabled |
false |
语音转文字总开关 |
sttBaseUrl |
https://open.bigmodel.cn/api/paas/v4 |
STT 端点(OpenAI 兼容 /audio/transcriptions) |
sttModel |
glm-asr-2512 |
STT 模型(智谱 glm-asr-2512 / SiliconFlow FunAudioLLM/SenseVoiceSmall) |
sttApiKey |
'' |
STT API Key(可复用智谱 GLM 系列的 key) |
privateImageView |
true |
私聊中主动下载查看对方发送的图片/动画表情(存 cwd/qq-images/,agent 用 describe_image 查看) |
visionMode |
tool |
识图方式:tool=存盘后由 visionToolName 工具查看(稳定);native=原生多模态附件直传模型(DSH 0.1.1+,文本模型自动降级) |
visionToolName |
describe_image |
tool 模式下使用的识图工具名 |
imageRetentionDays |
14 |
下载图片(qq-images/qq-replies)保留天数,宿主启动时清理更旧的 |
imageTrashEnabled |
true |
删除策略:只回收不销毁——过期图片移动到 cwd/qq-trash/<日期>/ 而不是删除(失败则保留原文件) |
imageTrashDir |
'' |
回收目录(空=cwd/qq-trash);该目录不会自动清理,由你自行处理(本机可 scripts/safe-delete.ps1 送进回收站) |
memoryEnabled |
true |
每会话持久化记忆(最近对话存 cwd/qq-memory/,宿主重启后自动恢复;/new 清除) |
memoryMaxEntries |
30 |
每个会话保留的对话条数上限 |
rateLimitEnabled |
false |
回复限流开关(默认关闭);开启后每会话窗口内最多回复 rateLimitMaxReplies 条 |
rateLimitMaxReplies |
10 |
限流窗口内每会话最大回复数 |
rateLimitWindowSeconds |
60 |
限流滑动窗口(秒) |
dedupEnabled |
true |
消息去重(同一 message_id 窗口内重复投递忽略,防重连重发) |
dedupWindowSeconds |
300 |
去重窗口(秒) |
reminderEnabled |
true |
定时提醒总开关(群聊需 @,私聊直接说;存 cwd/qq-reminders.json 跨重启保留) |
reminderMaxPerChat |
10 |
每个会话最多同时保留的提醒数 |
quietHoursEnabled |
false |
避开高峰期开关(默认关闭);开启后工作日静默时段内不回复任何入站消息(不消耗模型调用),已排定的定时提醒/投票开奖照常 |
quietHours |
['9:00-12:00', '14:00-18:00'] |
静默时段(本地时间 H:MM-H:MM,全角冒号自动归一化;可跨午夜如 22:00-2:00) |
quietWeekendExempt |
true |
周六/周日不受静默时段限制 |
ttsEnabled |
false |
语音回复总开关(默认关闭;开启后每条文字回复后跟随一条语音) |
ttsProvider |
azure |
合成方案:azure(微软晓晓)/ openai(任意 OpenAI 兼容 /audio/speech)/ local(本地 GPT-SoVITS 语音克隆,零 API 成本) |
ttsApiKey |
'' |
Azure / OpenAI 兼容服务的 key(local 不需要) |
ttsVoice |
zh-CN-XiaoxiaoNeural |
云端音色名 |
ttsStyle |
chat |
Azure 语气风格(cheerful/sad…) |
ttsMaxChars |
120 |
语音朗读最大字符数(超出截断,只影响语音不影响文字) |
ttsLocalUrl |
http://127.0.0.1:9880 |
本地 GPT-SoVITS api_v2 服务地址 |
ttsLocalRefAudio |
'' |
本地 TTS 必填:音色参考音频绝对路径(3-10 秒 wav,如 D:/voice/xiaojingyu.wav) |
ttsLocalPromptText |
'' |
参考音频的台词(可留空) |
ttsLocalTextLang |
zh |
合成文本语言 |
ttsLocalPromptLang |
zh |
参考音频台词语言 |
ttsLocalConvertToMp3 |
true |
本地 wav 输出用 ffmpeg 自动转 mp3 再发送(QQ/NapCat 兼容性更好) |
pokeEnabled |
true |
戳一戳回复开关(白名单会话内被戳随机卖萌回复) |
pokeReplies |
[...] |
戳一戳回复文案列表(随机选一条) |
pokeCooldownSeconds |
15 |
每会话戳一戳回复最小间隔(秒,防刷) |
voiceReadingEnabled |
true |
语音朗读:@机器人引用文字说「读一下/念出来」,或 /读 <文字>(走 ttsProvider 合成) |
checkinEnabled |
false |
每日签到(默认关闭):说「签到」打卡,连续/累计天数存 cwd/qq-checkin/;「签到榜」看排行 |
checkinKeyword |
签到 |
签到触发词 |
welcomeEnabled |
false |
入群欢迎语(默认关闭):新人进群自动 @+欢迎文案(机器人自己入群不触发) |
welcomeText |
'' |
欢迎文案(空=内置默认文案) |
imageGenEnabled |
false |
生图开关(默认关闭):/画 <描述词> 生成图片(群聊需 @机器人) |
imageGenProvider |
openai |
生图后端:openai=任意 OpenAI 兼容 /images/generations(DALL·E/CogView/SiliconFlow…);local=本地 SD WebUI(AUTOMATIC1111) |
imageGenBaseUrl |
'' |
后端地址(空=按 provider 取默认:api.openai.com 或 127.0.0.1:7860) |
imageGenApiKey |
'' |
OpenAI 兼容服务 key(local 不需要) |
imageGenModel |
'' |
模型 id(空=服务默认,如 gpt-image-1;local 忽略) |
imageGenSize |
1024x1024 |
图片尺寸 WxH(local 支持任意尺寸如 768x512) |
imageGenSteps |
20 |
采样步数(仅 local) |
imageGenCfgScale |
7 |
CFG 提示词强度(仅 local) |
imageGenSampler |
'' |
采样器(仅 local,空=WebUI 默认) |
imageGenCooldownSeconds |
60 |
每会话两次生图最小间隔(秒,成本/刷屏防护) |
imageGenDailyLimit |
20 |
每会话每日生图上限 |
imageGenMaxPromptChars |
400 |
描述词最大字数(超出截断) |
imageGenCommand |
/画 |
生图触发命令 |
traceEnabled |
true |
全链路结构化事件(每条消息一个 traceId,每个分支带 reason);关掉则控制台只剩端口/日志能力 |
traceLevel |
debug |
debug 记录全部事件(含每次静默/拒绝);warn 只留问题,用于长期运行省磁盘 |
traceMemorySize |
500 |
内存里保留的最近事件数(控制台决策链用),落盘另受 4MiB 轮转上限约束 |
traceFile |
'' |
事件文件路径(空=cwd/qq-trace.jsonl) |
recordInbound |
true |
录制:把收到的每条消息/通知/请求写进 qq-inbox.jsonl(可离线回放);只写本机、不影响回复 |
inboxFile |
'' |
录制文件路径(空=cwd/qq-inbox.jsonl,按 2MiB 轮转) |
inboxRedact |
false |
录制时把 6 位以上数字(QQ 号)脱敏后再落盘,便于把录制文件发给别人 |
injectEnabled |
false |
事件注入通道(默认关闭):开启后桥每 injectIntervalMs 轮询 qq-inject.jsonl,把新行喂进真实管线 |
injectFile |
'' |
注入队列路径(空=cwd/qq-inject.jsonl);启动时已有的历史行会被跳过并记一条原因 |
injectDryRun |
true |
强烈建议保持 true:注入触发的所有出站调用(发消息/撤回/群管…)都被拦截并计数,绝不真发 QQ;异步 agent 回合的回复同样被拦下(原文记进事件流) |
injectIntervalMs |
2000 |
注入队列轮询间隔(毫秒,最小 500) |
以 NapCat 为例:OneBot11 配置里把 WebSocket 客户端地址填成:
ws://127.0.0.1:6700/
其它实现同理(LLOneBot 填反向 WebSocket、OpenShamrock 填被动 WebSocket、go-cqhttp 填 ws-reverse)。若本插件配了 accessToken,OneBot 端填同一 token。
触发规则(最终版):
| 场景 | 行为 |
|---|---|
| 群聊:@机器人 + 引用(回复)一条语音 | ✅ 转写被引用语音并以文字回复 |
| 群聊:单独发语音(不@/不引用) | ❌ 不触发 |
| 私聊:直接发语音 | ✅ 转写并回复(不受 acceptPrivate 限制) |
| 私聊:文字 + 引用语音 | ✅ 转写被引用语音 |
实现链路:消息里的引用 → get_msg 找到被引用消息 → 其中含 record 段 → OneBot get_record(out_format mp3/wav,响应含 base64)→ POST {sttBaseUrl}/audio/transcriptions(multipart 字段 file 二进制)→ 转写文本注入会话。
注意事项:
file(二进制)——文档里写的 file_base64 实测会报 1214 错误sessionMode: chat(默认)下每个群一个独立会话,全群共享上下文;user 下每群每人一个会话/new 仅重置当前会话;会话存内存,宿主重启后重建(不持久化)[face:鼓掌] 等标记会替换为对应 CQ 表情段(黄脸表见 lib/faces.js,约 70 个)faceEnabled=true 时每个会话注册 qq_face_list / qq_face_send 工具cwd/qq-faces/ 自动登记为可发送表情(文件名=表情名),删除文件自动剔除autoCollectStickers=true 时自动收藏群消息里的图片表情/new:结束当前会话并开新会话/status:查看当前会话状态与 sessionId 前缀{cwd}/qq-bridge-debug.log(消息路由、语音转写、agent 事件,按时间戳追加)D:\qq-work\qq-host-err.log)可查启动崩溃voice fetched via get_record、quoted voice transcribed、followup sent (voice)、group msg without @bot ignored55 个单测脚本,共 3434 项断言(test/*-unit.mjs)+ 3 个真机脚本:
# 1) 单元测试:不联网、不起宿主,纯逻辑 + 临时目录(推荐每次改完都跑)
node test/control-unit.mjs # 也可以逐个跑:node test/<name>-unit.mjs
# 36 个文件:桥的分支/命令/守卫、控制台 HTTP 与体检、录制回放与注入、注入安全边界、硬约束验收台…
# 一次性全跑(PowerShell):
# Get-ChildItem test -Filter '*-unit.mjs' | ForEach-Object { node $_.FullName }
# 2) 回放的端到端验收:真实桥代码 + 真实 OneBot 服务端,沙箱 + dry-run,不需要宿主
node test/replay-live.mjs
# 3) 真机脚本(需要宿主/控制台已在运行,自己扮演 OneBot 客户端连 6700)
node test/live-e2e.mjs # 消息 → 回复 全链路
node test/live-stream.mjs # 实时事件流 / 决策链 / 体检接口(控制台 8799)
node test/replay-live-host.mjs --token <控制台 token> # 录制 → 离线回放 → 注入
老版本的 sim-*.mjs 协议模拟脚本保留在 test/ 下,仍可用于手工排查(node test/sim-group.mjs 等,需宿主运行)。
语音转文字链路建议直接用 QQ 实测(模拟脚本需真实 STT 调用)。
本插件不内置任何人设、偏好或群规则。小鲸鱼人设、问答偏好、群内行为规则等记忆内容由 dsh-mnemon 插件的运行时记忆(~/.mnemon/runtime/USER.md + MEMORY.md)注入每个 QQ 会话——插件只负责"功能",记忆只负责"灵魂",两者完全解耦。换人设只改 Mnemon 记忆,换功能只动本插件。
allowUsers / allowGroups 未配置(为空)时,插件默认拒绝所有私聊与群消息——请显式填入自己的 QQ 号与群号后再使用;配置白名单后,白名单外的任何人都无法驱动你的 agent127.0.0.1,不要改成 0.0.0.0 暴露公网本插件仅供技术学习与个人研究使用。使用者应自行评估并承担使用第三方 QQ 协议的全部风险与后果。
allowUsers / allowGroups 为空时默认拒绝一切消息——使用前务必填入自己的 QQ 号与群号每条入站消息都有一个 traceId,每个决策点(包括每一次"不回复/丢弃/降级")都会留下 stage + ok + reason + 耗时。这是本版本的核心设计理念:出问题时不用猜。
| 想看什么 | 在哪看 |
|---|---|
| 6 条硬约束此刻达标吗 | 控制台最上面「硬约束验收台」:6 行状态灯 + 机器上现有的证据 + 该点哪里的提示,每 30 秒自动重算 |
| 单条消息的完整决策链 | 控制台「实时事件流」点任意一行 → 「决策链」显示时间轴、停在哪一步、为什么 |
| 为什么机器人不回复 | 「实时事件流」筛"仅被拒/失败",最常见原因直接列出(如 mention: 群聊未 @ 机器人) |
| 每个端口的占用与在线 | 控制台「端口 / 进程」(6700 的连接数即机器人在线) |
| 一键排查 | 控制台「一键体检」:15-19 项 pass/fail + 修复建议(依赖路径、端口、链路、快照、错误、闸门拒绝…) |
| 生效配置(为什么功能没生效) | 控制台「运行快照」里的"关闭中的开关";完整字段在 qq-runtime.json 的 features(不含任何密钥) |
| 打包给别人看 | 「导出诊断包」→ 一个 zip(事件流/审计/桥日志/宿主日志/运行快照/体检报告/环境信息) |
| 文件级排查 | qq-trace.jsonl(结构化事件,可 jq/grep)、qq-bridge-debug.log、qq-actions.log(写操作审计)、qq-host-out.log/err.log |
相关配置:traceEnabled(默认开)、traceLevel(debug 全量 / warn 只留问题)、traceMemorySize、traceFile。
调试接口(控制台,token + Origin 双重校验):/api/trace、/api/stream(SSE)、/api/runtime、/api/diagnose、/api/export。
前两项解决了"现在发生了什么",这三项解决"这条消息当时为什么这样、换成别的输入会怎样"。
| 能力 | 怎么用 | 说明 |
|---|---|---|
| 录制 | 自动 | 桥收到的每条消息/通知/请求都追加到 qq-inbox.jsonl(可 JSON 逐行解析、按大小轮转),只写本机、不改任何回复行为 |
| 离线回放 | 控制台「录制 · 回放 · 注入」→ 某行「回放这条」/「回放最近 5 条」 | 在沙箱目录里用真实桥代码重跑这条消息:dry-run 拦截所有出站、不建任何 QQ 连接、源目录一个字节都不改。结论逐条给出"会回复/静默/出错 + 原因 + 会发送什么" |
| 事件注入 | 控制台填 群号/QQ 号/文本 → 「注入」 | 写一行到 qq-inject.jsonl,桥按 injectIntervalMs(默认 2s)轮询后走真实管线处理;injectDryRun(默认开)下所有出站调用被拦截并计数,注入内容永远不会真的发到 QQ |
回放的保真度来自运行时快照里的线上决策配置(白名单、安静时段、各功能开关等 28 项,见 qq-runtime.json 的 replay):否则插件默认值里"白名单为空 = 拒绝一切"会让回放全部判成静默。回放里不含真实模型输出——模型那一轮用一条带 [回放] 前缀的模拟回复代替,用来验证链路与分支,不验证措辞。
排障要点:
injectEnabled),不会静默丢进队列;注入回合的模型回复已被拦截(dry-run,未发送):<原文>)——注入既能走真实管线,又不会漏发一条;真人消息不受影响(收到真实消息立即解除该标记);replay: 离线回放(沙箱 + dry-run,不碰 QQ),注入的帧标为 inject: 来自注入器,两者不会互相误判;qq-replay/_trash/<日期>/),不做物理删除。相关配置:recordInbound(默认开)、inboxFile、inboxRedact、injectEnabled(默认关)、injectFile、injectDryRun(默认开)、injectIntervalMs。
调试接口:/api/inbox、/api/replay、/api/inject、/api/queue/clear。
上面两条解决了"现在发生了什么"和"当时为什么这样"。验收台解决第三个问题:设计理念有没有真的落地。
GET /api/acceptance 把 6 条硬约束逐条用机器上现有的产物算成 ✅ 达标 / ⚠️ 有提示 / ❌ 不达标 / ❔ 证据不足,并给出证据与下一步:
| 约束 | 判定用的证据 |
|---|---|
| ① 无静默分支 | 最近 500 条事件里所有 ok:false(被拒/失败)事件是否都带非空 reason;缺的会点名 stage |
| ② 可关联(traceId) | 消息级事件的 traceId 覆盖率、有多少条消息真正走完 inbound→reply |
| ③ 可回放 | 录制条数 + 最近一次回放的统计与安全保证(dry-run 开、QQ 连接 0、cwd 已沙箱化);dry-run 被关掉直接判不达标 |
| ④ 可体检 | 体检通过数/失败数/blocker 数与结论,失败项点名 |
| ⑤ 可导出 | 诊断包会收集的产物有几类在位(或最近一次导出的体积与文件名) |
| ⑥ 可注入 | 通道开关、dry-run 开关(关掉→不达标,因为会真发 QQ)、已消费行数、以及拦下过几次异步 agent 回合的回复 |
每一项都能点「去看 →」跳到对应卡片(事件流/决策链/回放结果/体检/日志/注入);结论为 all-green / partial / unknown / broken 四种。
纯函数实现(control/lib/acceptance.mjs),所以每条分支都有断言覆盖;面板每 30 秒自动刷新,回放结束后立刻重算。
v0.5 做两件事:把已经封装好、却从没接上线的能力接通,再补上"群里的东西能找回来"。
以前别人把「聊天记录」合并转发给机器人时,parseMessage 里根本没有 forward 分支,这条消息在传输层就被静默丢掉——连 trace 都没有,直接违反 v0.4 的第一条硬约束。现在:
forwards 会被识别,经 get_forward_msg 展开成 [转发聊天记录] 昵称: 内容 交给模型(默认最多 50 条 / 4000 字,只展开一层,不递归)acceptPrivate 门——这两个门都补上了)forwardExpandEnabled: false 可关闭;回放/注入模式(dry-run)不访问 QQ,注入时可用 forwardText 直接喂一份正文| 能力 | 用法 |
|---|---|
| 群成员 | /成员 看名单(按身份/等级排序,标注头衔与禁言中)· /成员 @某人 / /成员 昵称 / /成员 QQ号 看详情(身份/等级/头衔/入群时间/最后发言/禁言状态);agent 工具 qq_member_info |
| 群资料 | /群信息(群名/群号/人数与上限/群主/建群时间) |
| 好友列表 | /好友(隐私项,默认关闭 friendListEnabled,且仅私聊里的管理员可用) |
| 群历史 | agent 工具 qq_recent_history(拉本会话最近 N 条,群聊/私聊都支持) |
| 表情回应 | agent 工具 qq_react(给消息贴 👍 之类,而不是发一条消息;写操作,过闸门) |
| 退群 | /退群 确认(默认关闭 leaveGroupEnabled,必须显式二次确认,走闸门) |
| 命令 | 说明 |
|---|---|
/文件 |
列群文件与文件夹(文件名/大小/上传者/时间) |
/文件 <文件夹名> |
进文件夹列文件 |
/取 <文件名> |
下载群文件到 cwd/qq-files/ 并只发到发起人私聊(不往群里丢文件);精确/前缀/模糊匹配,文件名消毒(去路径、去 Windows 非法字符、保留名加前缀) |
/相册 |
列群相册(NapCat get_qun_album_list) |
/ocr |
引用一张图片发 /ocr,用 NapCat 的 ocr_image 读出图里的文字(不消耗模型) |
cwd/qq-history/YYYY-MM-DD.jsonl;注入/回放的假事件不入档,/ 开头的命令也不入档(否则每次 /找 X 都会命中自己刚敲的查询词)/找 关键词(空格分隔 = 同时包含,大小写不敏感)检索本会话最近 N 天;agent 工具 qq_search_history 同源同口径qq-trash/<日期>/ 而不是删除/找 复用同一套解析)新增配置(括号内为默认值):forwardExpandEnabled(true) forwardMaxNodes(50) forwardMaxChars(4000) memberQueryEnabled(true) memberListLimit(20) friendListEnabled(false) historyQueryEnabled(true) historyQueryLimit(20) reactToolEnabled(true) leaveGroupEnabled(false) ocrEnabled(true) ocrMaxImages(3) groupFileEnabled(true) groupFileDownloadEnabled(true) groupFileListLimit(20) groupFileMaxBytes(50 MiB) albumEnabled(true) historyArchiveEnabled(true) historyArchiveDir("") historyArchiveKeepDays(90) historySearchEnabled(true) historySearchDays(7) historySearchLimit(20)。
插件自带一个独立的本地运维端,不依赖 DSH 桌面端:宿主挂掉时它照常可用,端口与进程一目了然。
# 启动(默认 http://127.0.0.1:8799,启动后打印带 token 的地址)
npm run control # 或 node control/bin/qq-control.mjs --open
# 也可以双击 control/启动控制台.bat
| 能力 | 说明 |
|---|---|
| 端口总览 | 控制台 8799 / 宿主 3080 / OneBot 6700 / NapCat 6099 / GPT-SoVITS 9880 的监听状态、占用 PID 与进程名;6700 的连接数即"机器人在线" |
| 启停 | 启动/停止/重启宿主(自动带 --no-open,日志重定向到 qq-host-out.log/qq-host-err.log)、启动/停止 NapCat 与 QQ、启动/停止 GPT-SoVITS、一键全停 |
| 启动预检 | 起宿主前检查 3080/6700,被占则直接报「端口←进程#PID」,而不是静默失败 |
| 释放端口 | 对占用受管端口的进程一键 taskkill /T /F(护栏:只允许受管端口占用者与已知机器人进程,绝不误杀无关 PID) |
| 日志 | 宿主 stdout / stderr / 桥调试日志,自动跟随、可切行数 |
| 扫码状态 | 显示 NapCat 二维码图片是否存在、是否新鲜,并一键打开 6099 扫码页 |
| 配置 | qq-control.json 是端口与路径的唯一真源(自动探测 node、dsh bin.js、NapCat、TTS 脚本;可在 UI 里改路径);6700 被 NapCat 配置写死,勿改 |
| 调试 | 「录制 · 回放 · 注入」:列出 qq-inbox.jsonl 里录到的每条消息,可一键离线回放(沙箱 + dry-run)或注入合成事件;注入队列状态(行数 / 本次已消费 / dry-run)直接显示 |
| 验收 | 顶部「硬约束验收台」:6 条硬约束的实时证据(无静默分支 / traceId 贯穿 / 可回放 / 可体检 / 可导出 / 可注入),不达标项直接说明该点哪里;GET /api/acceptance |
| 安全 | 只绑 127.0.0.1;所有 API 需要 token;带 Origin 的跨站请求一律拒绝 |
以后若想做真正的托盘/桌面程序,直接包一层 Electron/Tauri 复用同一套 HTTP API 即可,逻辑无需重写。
出问题排查往往要把日志发给别人,所以本插件对"数据会去哪"有明确约定:
| 项 | 约定 |
|---|---|
| 诊断包 | 「导出诊断包」默认勾选脱敏:QQ 号按位掩码(保留前两位)、消息原文替换为「[已脱敏 N 字]」,包内附 REDACTED.json 说明口径;取消勾选才导出明文(会有提醒) |
| 运行产物 | qq-inbox.jsonl(消息原文)、qq-trace.jsonl(会话键 + 文本片段)、qq-runtime.json、qq-actions.log 等只写本机工作目录,且全部被 .gitignore 覆盖(qq-*/、qq-*.json、qq-*.jsonl、qq-*.log 通配 + 逐项列出),cwd 恰好在仓库里也不会误提交 |
| 录制脱敏 | inboxRedact: true 可在录制阶段就把 QQ 号脱敏 |
| 仓库本身 | 不含任何密钥/口令/真实 QQ 号:密钥只存在于你的 DSH profile 配置(仓库外);test/privacy-unit.mjs 每次跑测试都会重新扫描全部被跟踪文件(真实号从你本机私有配置或 DSH_QQ_PRIVATE_IDS 现取,测试文件里不含真实号) |
| 控制台 | 只绑 127.0.0.1,所有接口需 token(存在被忽略的 qq-control.json),拒绝跨站 Origin |
部署事实:本插件是 DSH bundle,运行在 DSH profile 内,
@deepseek-ai/dsh-*、@deepseek-ai/schemastery等官方 peer 依赖随 profile 一起装好。官方包必须写全作用域名:不带作用域的schemastery是另一个包(3.18.0),只有在"别的插件恰好把它 hoist 到共享 node_modules"时才能解析——v0.4.0 之前lib/正是这么写的,换到干净环境立刻ERR_MODULE_NOT_FOUND(issue #1);现已统一用作用域名,并由test/static-unit.mjs静态守住(裸名/未声明的 import 直接测试失败)。ws是普通运行时依赖,本地目录安装要按上面「安装」一节先npm install。把lib/单独拷出来裸跑仍然跑不起来(设计如此,不是缺依赖)。
最近五个版本(始终滚动展示):
napcat.bat(三行、不带参数不提权,实测秒退 exit 0),改成提权调 launcher.bat(-Verb RunAs),并把目标从 napcat.bat 换成真正需要的脚本;② 「打开扫码页」在 6099 未监听时是死链,现在禁用并说明原因;③ 满屏「token 无效」:页面不带 ?token= 打开就等于空 token,现在直接弹中文横幅告诉你 token 存在 qq-control.json、且宿主 3080 的 token 每次重启都会变、两者不能混用。新增登录二维码面板(GET /api/qr 直接给出图片 + 「这张码是几秒前生成的」+ 过期提醒)与「重启登录流程」按钮。随后做了一轮独立对抗性审查(只读、禁止改文件/动 git/启停进程),把发现的问题全部修掉:【严重】重启流程原来按镜像名 taskkill /F /IM QQ.exe,会把你自己的 QQ 客户端一起杀掉(真机上就有一个非提权的个人 QQ 在跑),护栏还能被"6099 被别人占用"绕过 → 改为按加载器 PID 清理(taskkill /PID <pid> /T /F),命令里不再出现 QQ.exe//IM,拿不到 PID 直接拒绝;/api/port/free 加上受管端口白名单(原来能借它杀任意占用者);stopNapcat 不再把「6099 在听」当成「这就是 NapCat」(复现过 nginx 占 6099);启动命令改用 call "<path>"(路径含括号时 cmd 会剥引号导致静默不执行)并转义单引号;jobsView/groupsView 对元素级畸形快照不再 500;控制台侧不再整对象透传 webhook/自愈字段;自愈命令原文不再进 trace(只留 basename + 参数个数,trace 会进控制台页面与诊断包);二维码只认 PNG 魔数;qrStatus 区分「读不到」与「不存在」;ops 规划器改用 Object.hasOwn(kind:'constructor' 不再命中原型拿到函数)。全量 55 套 / 3434 断言全绿GET /api/perf):端到端延迟按「一条消息的 trace 链」算,给出整体与分会话的 P50/P95、阶段耗时画像(用 trace 里已有的 ms)、最慢几条与失败画像——debug 级「功能没开」不算事故,另列;定时任务面板(GET /api/jobs):播报任务的下次时间/上次原因/成功失败次数与「一直在失败」标记、webhook 来源计数、自愈状态、注入队列;群配置页(GET /api/groups):每个群的白名单状态、会话与最近回合、挂在该群的定时任务、16 项生效开关与实时计数(并说明开关真源在插件配置里);注入场景库(17 个内置场景,GET /api/scenarios + POST /api/inject 的 scenario 字段):群里 @我 / 普通聊天 / 撤回 / 戳一戳 / 入群 / 表情回应 / 入群与加好友申请 / 私聊文本与图片 / OCR / 转发卡片 / 管理员命令 / 原生打卡 / 批量踢 / 敏感词 / 超长消息,每个都声明需要什么参数、专门验哪条链路,缺参数点名缺什么;回放 diff(POST /api/replay-diff):同一批消息跑两次回放(基线 vs 你写的配置覆盖),机械比出「决策变了 / 原因变了 / 回复文本变了」(带相似度,避免措辞微调被误判),只有一侧有结果时标 unknown。为此桥在运行快照里多写一个 jobs 块(只有描述性字段,自愈命令原文与任何 token 都不进快照)。新增 2 套测试(perf 61 / scenario 49),全量 55 套 / 3384 断言全绿set_group_kick_members(user_id 是数组,不用循环)、群待办是三个 action(set/complete/cancel_group_todo)、相册上传叫 upload_image_to_qun_album;另外 set_group_member_permissions 是局部更新(没传的项保持不变),所以 /群权限 只提交写出来的项。新增:/群打卡(QQ 原生群签到,与本地积分「签到」区分)、/全体余量、/禁言名单、/群详细(扩展群资料)、/入群通知、/批量踢(管理员,两步确认 + 分批不截断)、/待办 /完成待办 /取消待办、/移动文件 /重命名文件 /删文件 /新建文件夹、/传图(按名字换相册 ID)、/群名 /群备注、/群权限、/历史可见、/周报(本地统计:消息/入群/退群/踢出/禁言/打卡/待办/文件整理/相册上传 + 最忙的一天)。红线:写操作全过 ActionGate,每个新 API 都有"注入回合 0 出站"断言,每个关闭分支点名是哪个开关,/周报 纯本地读。新增配置键 13 个(总数 204 → 217);新增 2 套测试(ops 94 / 桥层 ops-bridge 96),全量 53 套 / 3252 断言全绿bootmain/napcat.mjs,QQ 9.9.32-50969)确认能力面,其中最重要的是一条否定结论:该构建 "keyboard"/"button" 段名出现 0 次,发不了内联按钮,所以本版没做按钮面板,而是把轻互动真正落地。新增:主动戳一戳 /戳 @某人(管理员,group_poke/friend_poke)、被戳回戳(真的戳回去,可配文案)、私聊正在输入(set_input_status,探针确认只支持 C2C,群聊如实记 reason)、自动贴表情(set_msg_emoji_like,emojiLikeMentionOnly 默认只对叫我/引用我生效)、表情回应统计(/赞榜 本地榜单 + /谁赞了 群里走 get_emoji_likes 拿实时名单、失败/注入回合回落本地并标注来源)、点赞 /点赞 [@某人](send_like,每天每目标限量)、标记已读(mark_*_msg_as_read,按会话)。红线:set_msg_emoji_like 只带 message_id、scoped dry-run 拦不住 → 桥里自己判注入并给真实 reason,注入回合逐条断言 0 出站;每个开关的关闭分支与配额拒绝都带真实 reason。顺手修掉两个 v0.5.2 的静默缺陷:桥对 JsonStore 调了不存在的 load()/save()(真实 API 是 read()/write()),异常被吞 → 播报去重/统计跨重启丢失且毫无提示;#loadEngageState() 在配额对象构造前调用导致 restore 打空、配额跨重启失效。新增配置键 17 个(总数 187 → 204);新增 2 套测试(engage 109 / 桥层 engage-bridge 90),全量 51 套 / 3062 断言全绿更早的版本(v0.4 线及之前)见 CHANGELOG.md —— 完整历史、每个版本的缺陷清单与测试计数都在那里。
CLASSIFICATION EVIDENCE
系统优先读取 GitHub Topics,再与站内分类词典和词根规则比对。当前命中: dsh-qq-bot。