# 实例配置(`config.json` v2) > 本文是**配置键的完整参考**。所有示例值都是占位(`10001`、`~/...`、`https://example.invalid`), > 真实值填在你自己的实例配置里;**不要**把真实 QQ 号/群号/token 提交到任何仓库。 ## 一、配置分几层 | 层 | 载体 | 放什么 | 谁改 | |---|---|---|---| | ① bundle 引导配置 | profile patch 里 `dsh-onebot-qq` 的 `config:` | `dataDir` / `configPath` / `autoConnect` / `compatServer` / `compatPort` | 安装时写一次,很少动 | | ② **实例配置** | `/config.json` | 本文的全部业务键 | 手改 / 控制台热改 | | ③ 环境变量 | `QQ_IMAGEGEN_*` | 生图凭据(覆盖 ② 的 `credentials.gptImage`) | 启动 DSH 前 export | | ④ 数据目录 | `/…` | 人设、表情库、群记忆、日志、会话状态等运行数据 | 机器人自己写 | **位置**: ``` 优先级:bundle 配置 dataDir > 环境变量 DSH_QQ_DATA_DIR > $DSH_HOME/dsh-onebot-qq = bundle 配置 configPath;留空 = /config.json $DSH_HOME = 环境变量;留空 = ~/.dsh ``` > **改名兼容**:本插件原名 `dsh-qq-bot`。升级到 0.1.1 后,若新默认目录 `$DSH_HOME/dsh-onebot-qq` > 不存在、而旧目录 `$DSH_HOME/dsh-qq-bot` 存在,会**自动继续使用旧目录**(配置、人设、表情库都不动)。 > 想换成新名字:把整个目录搬过去,或在 bundle 配置里显式写 `dataDir`。 > 自定义数据目录有两种正确做法(**只跑 `bin init --data-dir X` 不够**:init 只在 X 下写 config, > 插件默认仍读 `$DSH_HOME/dsh-onebot-qq`): > 1. 启动 DSH 前设 `DSH_QQ_DATA_DIR=X`(推荐,最省事); > 2. 或改 profile 的 `cordis.patch.yml` 里 `dsh-onebot-qq` 行 `config.dataDir: '...'` > (profile patch 行的 `config` 是**整体替换**,要把 `dataDir/configPath/autoConnect/compatServer/compatPort` 写全)。 典型路径:Linux/macOS `~/.dsh/dsh-onebot-qq/config.json`;Windows `%USERPROFILE%\.dsh\dsh-onebot-qq\config.json`。 **要不要整个换位置**:可以。`dataDir` 写绝对路径即可;`configPath` 单独指到别处也支持。 相对路径(含人设/表情库等目录)**一律相对 `dataDir`** 解析,所以整个数据目录可以整体搬走。 ### 控制台页面 ↔ 配置归属 | 控制台页 | 里面是什么(配置路径不变,只是归属/分组调整) | |---|---| | 群聊 | 核心项(主人 QQ / 入站条数上限 / 上文图片入站)+ `commands.enabled` + 每群卡片 | | 唤醒 | `gate-wake` 全部可见项(门禁与唤醒、唤醒词、必须被叫、关键词冷却、答应声、被戳回一句)+ `anti-repeat`(防刷屏) | | 人设与拆条 | `persona-voice` 全部可见项(句池开关、人设目录、当前人设、拆条、引用纪律) | | 生图 | `imagegen` 全部可见项,**含生图凭据** `credentials.gptImage.*`(存储路径不变,旧配置零迁移) | | 文件与图片 | `file-transfer` 行为类开关(总开关 / 下载时机 / 大小上限 / 数量上限 / 保留天数) | | 其他 | 门禁 / 文件 / 发图 / 头像 四组低频配置(原隐藏页统一并到这里,含入站目录、发图白名单、头像缓存)+ `private-friend-gate`(私聊只回好友,可选)。主人 QQ 只在「群聊」页 | | 群记忆 | `group-memory`(固定页里的「工具配置」面板) | > 子插件页是 **12 个子插件**的启停汇总,和各自功能页里的 `enabled` 开关**同源**(改哪边都一样)。 ## 二、最小可用配置 ```json { "version": 2, "adminQq": 10001, "onebot": { "wsUrl": "ws://127.0.0.1:3001", "wsToken": "YOUR_WS_TOKEN", "httpUrl": "http://127.0.0.1:3000", "httpToken": "YOUR_HTTP_TOKEN" } } ``` 除上面的必填项外,其余键不写就用默认值。完整默认值可用 `node bin/dsh-onebot-qq.mjs init` 生成一份。 > **不用先手改 JSON**:缺 `adminQq` 时插件进入「待配置模式」(控制台可用、工具已注册、不连协议端), > 打开 设置 → QQ 控制台 →「新手向导」填主人 QQ 与协议端地址即可,保存后自动连接。 > 上面的 JSON 是手改方式(改完需重启 dsh-web)。 ## 三、路径规则 - 键名以 **`dir` / `file` / `path` 结尾**的字符串 → 相对 `dataDir` 解析; - 键名以 **`whitelist` / `roots` / `dirs` / `paths` 结尾**的字符串数组 → 逐项解析; - 以 `~` 开头 → 相对用户主目录展开; - 已是绝对路径(POSIX `/…`;Windows `C:\…` / `C:/…` / `\\server\share` / `file://`)→ 原样使用; - `http(s)://` URL 不当路径处理(如 `link-parser.parserUrl`)。 > 自己加配置项时照这个命名法,路径就会被自动解析,不用改代码。 ## 四、核心键 ### `version` / `adminQq` | 键 | 类型 | 默认 | 说明 | |---|---|---|---| | `version` | int | `2` | 配置格式版本;当前只有 v2 | | `adminQq` | int | `0` | **必填**。主人 QQ 号(唯一)。`0`/缺失 → 启动时报错;出口脱敏只有对主人私聊豁免 | ### `onebot` **协议端连接配置**。**键名是 `onebot.*`,可对接任意 OneBot v11 协议端**;旧版配置里的 `napcat.*` 仍能读(自动兼容),接 SnowLuma 也用这一段。 | 键 | 类型 | 默认 | 说明 | |---|---|---|---| | `wsUrl` | str | `ws://127.0.0.1:3001` | OneBot v11 **正向 WebSocket** 地址(收消息) | | `wsToken` | str | `""` | WS 鉴权 token。协议端开了鉴权才填(多数实现可选);**SnowLuma 必填**(它强制生成 accessToken) | | `httpUrl` | str | `http://127.0.0.1:3000` | OneBot **HTTP API** 地址(发消息/取文件/查群) | | `httpToken` | str | `""` | HTTP 鉴权 token。同上:SnowLuma 必填 | > **不同实现的差别**:多数实现的 token 可以留空(裸跑);SnowLuma 的 3000/3001 **强制带 `accessToken`** > (每个网络自动生成 32 字节 base64url,在它 WebUI 或 `config/onebot.json` 里看),所以要分别填 `wsToken`/`httpToken`。 > 端口的常见默认值基本一致,换协议端通常**只改 token**、不用改地址。 ### `dsh` | 键 | 类型 | 默认 | 说明 | |---|---|---|---| | `chatModelSelection` | object | 未设置 | 新建 QQ 会话的默认模型,形如 `{ "provider": "渠道ID", "model": "模型ID" }`;两项为空则沿用 DSH 默认。控制台「OneBot 连接」页成对保存;仅影响新建 QQ 会话,不改已有会话或记忆分析。新建时无法应用则沿用 DSH 默认并记录提示。 | | `agentPreset` | str | `"qq-chat"` | 给 QQ 线用的 agent preset 名。**内置 `qq-chat` 随包声明**(纯聊天 + 联网,裁掉 shell / 文件 / 任务 / 技能 / 插件管理类工具),留空时插件自动启用;填别的名字 = 用你自己的 preset;该 preset 不存在时回退 DSH 默认工具面 | | `qqWorkspacePath` | path | `"workspace"` | QQ 线的会话工作目录(`AGENTS.md` / `CLAUDE.local.md` / 人设投递文件在这里);插件启动时把这个目录注册成 DSH 工作区 | | `workspaceTitle` | str | `"QQ 聊天"` | QQ 工作区在 DSH 侧栏的显示名。启动/保存配置时按路径创建(或复用),同路径只复用不重复创建 | | `sessionsFile` | path | `"state/sessions.json"` | chatKey → DSH sessionId 映射(重启后能续聊) | ### `outbound` | 键 | 类型 | 默认 | 说明 | |---|---|---|---| | `replyMaxLength` | int | `3000` | 单条回复最大长度(超出截断/分段) | | `longForwardThreshold` | int | `400` | 超过多少字改用「合并转发卡片」;带 @/引用的长文不套卡片 | | `longForwardChunkSize` | int | `800` | 卡片里每段节点的长度 | | `quoteAfterMsgs` | int | `3` | 回复发出前同群插了几条才自动挂引用;`0` = 关掉自动引用 | | `groupContextCount` | int | `10` | **入站条数上限**:每轮最多带几条"还没看过的"群消息(只取上次回复后新增、30 分钟内);超过就只给最后 N 条,更早的丢弃不补。每条约 20 token;`0` = 不带上文 | | `groupContextImages` | bool | `false` | **上文图片入站**:开 = 群聊上文窗口里的**真实图片**(表情包不算)随上文送进模型,数量与 `groupContextCount` 同步封顶(最多 N 张),长边缩到 768px(实测约 180~380 token/张,均值约 270);关 = 只留 `[图片]` 占位符。默认关,按需开 | ### `archive` | 键 | 类型 | 默认 | 说明 | |---|---|---|---| | `enabled` | bool | `true` | 是否把进出消息写成 JSONL 留档 | | `dir` | path | `"logs/qq_chat"` | 留档目录(每个会话一个 `_.jsonl`) | ### `toolPrefix` / `plugins` | 键 | 类型 | 默认 | 说明 | |---|---|---|---| | `toolPrefix` | str | `"mcp__qq__"` | 原生工具名前缀(改它会影响人设/指令里的工具名,谨慎) | | `plugins` | object | `{}` | 子插件**硬开关**:`{"stickers": false}` = 这一轮完全不加载该插件 | ### `pacing`(回复节奏) `pacing.merge.*`:唤醒后的等待槽(等对方说完再入站)。 | 键 | 类型 | 默认 | 说明 | |---|---|---|---| | `enabled` | bool | `true` | 等待槽总开关;关掉 = 唤醒消息立即入站 | | `waitMs` | int | `5000` | 唤醒后等多久再入站(毫秒;控制台按秒填,0–120 秒) | | `loadImages` | bool | `true` | 识别合批内图片;开启按本轮消息及图片顺序加载,含群内并入的非唤醒消息;关闭只加载首条图片,后续占位 | 行为(2026-11-04 重做,旧的 5 个窗口参数已删除): - **私聊 = 合批**:等待期内每来一条消息都并入同一轮并**刷新计时**(debounce),安静 `waitMs` 后一次入站; - **群聊 = 排队**:唤醒消息各自独立成轮;等待期内**非唤醒**消息并入当前轮;又来唤醒则**当前轮立即入站**、新唤醒另开一轮; - **非唤醒消息**永远只并入当前打开的槽,不会单独成轮; - **机器人正在回答时**新来的唤醒直接排队(不合批、不等待); - **合批图片**:`loadImages=true`(默认)时,按本轮消息顺序、每条内部图片顺序加载并映射图片,包括并入本轮的群非唤醒消息;单图加载失败保留占位,其余继续,成功图片仍对应原消息顺序。`false` 时只加载首条消息的图片,后续图片仅留 `[图片]` 占位。引用图仍按各事件现有处理,不新增跨事件引用解析规则。开启会增加视觉输入成本;这个开关不改变唤醒、等待、合批或排队时机。 `pacing.reply.*`:主动接话(没人叫也可能自然插话)。 | 键 | 类型 | 默认 | 说明 | |---|---|---|---| | `enabled` | bool | `false` | 总开关 | | `quietHours.enabled` | bool | `true` | 主动聊天免打扰;正常用户发言后的回复不受影响 | | `quietHours.start` | string | `23:00` | 免打扰起点(含),24小时制 `HH:mm` | | `quietHours.end` | string | `08:00` | 免打扰终点(不含);起止相同表示全天免打扰 | | `quietHours.timeZone` | string | `Asia/Shanghai` | IANA时区,不随服务器本地时区漂移 | | `interval.minMs` | number | `3600000` | 正常随机窗最短 1 小时;控制台按小时填,可填 0.5 | | `interval.maxMs` | number | `14400000` | 正常随机窗最长 4 小时 | | `interval.unansweredMinMs` | number | `14400000` | 未回复随机窗最短 4 小时 | | `interval.unansweredMaxMs` | number | `36000000` | 未回复随机窗最长 10 小时 | | `interval.maxBackoffMs` | number | `86400000` | 持续未回复退避上限 24 小时,不得小于两窗上界 | | `silenceEnabled` | bool | `true` | 冷场找话;`false` = 只在群里有新消息时才可能接话(不再定时找话) | | `topicWindowMs` | int | `600000` | 话题统计窗口 | | `topicMinMsgs` | int | `3` | 窗口内至少几条消息才算有话题 | | `sensitivity` | float | `0.45` | 语境敏感度(0–1,越大越挑时机;群话题/续聊打分使用) | | `maxChars` | int | `140` | 主动接话字数上限 | | `groups` | array | `[]` | 允许主动接话的群号;空数组只关闭群主动,不影响已允许的私聊 | | `privateChats` | array | `[]` | 允许主动找话的私聊 QQ 数字数组;默认不主动联系任何私聊,控制台逐个输入点+/回车添加、点标签×移除;API仍兼容逗号/空白文本 | | `continuation.enabled` | bool | `false` | 连续对话总开关(控制台「主动接话 → 连续对话(展开)」里的第一项) | | `continuation.delayMs` | int | `120000` | 从"回复真的发出"起等多久检查(毫秒) | | `continuation.bonus` | float | `1.5` | 情境加分:复用上面的 `weights` 算出 logit,再加这个值换算成"续聊分" | | `continuation.hourlyMax` | int | `5` | 每群每小时最多补几次 | | `continuation.minGapMs` | int | `300000` | 两次续聊最小间隔(毫秒,默认 5 分钟) | | `continuation.maxPerChain` | int | `1` | 一次唤醒最多检查几次(防自转核心,建议保持 1) | | `continuation.requireNewMessage` | bool | `true` | 必须有人类新消息才检查(关掉容易变成自问自答) | | `continuation.quoteLastHuman` | bool | `false` | 续聊时是否引用最后一条人类消息 | > 控制台里这 8 项收在「主动接话」页的 **「连续对话」折叠块**里(默认收起,点开才显示),不和常用项平铺竞争版面。 | `weights.*` | float | 见下 | 接话打分特征权重,一般不用动 | 免打扰默认北京时间23:00–次日08:00,覆盖群话题接话、群冷场找话、群连续对话和私聊主动。候选提交前与实际发送前复查,跨点的拆条或媒体也停止发送;已送达消息不撤回。夜间到期机会不排队、不在早晨集中补发,恢复后仍等待对应机会间隔;正常被叫回复不受影响。免打扰配置可在同一「主动接话」页修改,非法时段/时区在控制台拒绝;直接改文件写错则运行时回落默认安全时段。 群与私聊共用 `interval` 这一套随机窗、一次调度定时器和候选轮入口,不再有独立的群/私聊时间配置或每小时次数。正常抽 1–4 小时,实际主动发出且未获回应时抽 4–10 小时;持续未回默认扩大为 8–20、12–24 小时,之后保持 12–24 小时随机窗,避免固定每日同刻。两窗分别要求最短不大于最长,五项必须大于 0;提高最短先提高最长,提高窗上界超过退避上限时先提高上限。修改间隔或免打扰策略会重排一次,刷新页面、重复应用相同配置不会重抽。旧主控 `hourlyMax` / `minGapMs` / `coldSilenceMs` / `privatePolicy` 已被替代,不再使用。 `enabled=false` 停止全部主动聊天;`silenceEnabled=false` 停止冷场找话(包括私聊),群的话题接话仍可运行,但也遵守共享随机机会。群连续对话保留独立参数。私聊不启用群话题打分或群连续对话,也不读取群上文。内容依当前人设、原会话上下文及获准的本会话记忆,没有固定风格轮播。 实际发出一轮才增加未回次数,拆条/表情不重复计数;候选不说话或失败只消耗机会、不增加未回。私聊真人新消息视为回应;群内只有明确叫它、@它或引用它等指向机器人的互动才重置未回,普通群友发言不等于回应,不能把热闹群一直当作有人接话。首次允许的目标从启用基线等待,不立即补发。群私聊计时与退避统一保存在 `/state/proactive.json`,不保存聊天正文或 DSH 会话映射;旧私聊状态文件迁入统一状态。重启保留频控,离线错过的机会不集中补发。控制台各会话显示下次最早机会与连续未回复次数,不保证届时一定发送。 `pacing.reply.weights` 默认值: `intercept -1.2005`、`mentionBot 1.5852`、`atOther -1.1157`、`hasQuestion 0.4807`、 `placeholderOnly -1.0281`、`selfRatio 2.6525`、`recentCount -0.0367`、 `logSecondsSinceBot 0.0571`、`pendingCount 0.1012`。 > 一般只改 `enabled` / 目标名单 / `interval` / `sensitivity`;权重是调参细节。 > > **拆条 / 表情概率不在本段**:它们是各自插件自己的配置 —— 拆条在 > `pluginConfig['persona-voice'].splitReply*`,表情在 `pluginConfig.stickers.{stickerEnabled,offerPercent}`, > 控制台分别在「人设与拆条」「表情包」页改。旧版存在 `pacing.reply.style` 的值启动时会自动迁过去。 ### `credentials` | 键 | 类型 | 默认 | 说明 | |---|---|---|---| | `gptImage.protocol` | enum | `"openai-images"` | 生图协议:`openai-images`(`/images/generations` + `/images/edits`)或 `openai-chat`(`/chat/completions`,部分中转站/模型用对话接口出图) | | `gptImage.baseUrl` | str | `""` | 生图 API 基址(OpenAI 兼容),如 `https://example.invalid/v1` | | `gptImage.apiKey` | str | `""` | **不要写进仓库**;建议用环境变量 `QQ_IMAGEGEN_API_KEY` | | `gptImage.model` | str | `"gpt-image-2"` | 生图模型名(以你的中转/服务为准) | 环境变量优先级(**覆盖**实例配置/控制台输入框,`loadConfig` 时就生效): | 环境变量 | 覆盖字段 | |---|---| | `QQ_IMAGEGEN_PROTOCOL` | `credentials.gptImage.protocol` | | `QQ_IMAGEGEN_BASE_URL` | `credentials.gptImage.baseUrl` | | `QQ_IMAGEGEN_API_KEY` | `credentials.gptImage.apiKey` | | `QQ_IMAGEGEN_MODEL` | `credentials.gptImage.model` | > 协议选哪个:**默认先试 `openai-images`**;如果中转站/模型只支持对话接口出图(返回 chat 响应),再切 > `openai-chat`。控制台「运维」页有「检查生图配置」按钮可验证地址/Key/模型是否可用。 ### `compat`(可选兼容网关) | 键 | 类型 | 默认 | 说明 | |---|---|---|---| | `imageRoots` | array | `[]` | `:3971` 兼容网关可发的图片白名单根目录(`compatServer: true` 时才有意义) | 是否启动兼容网关由 bundle 配置 `compatServer` / `compatPort` 控制(默认关、端口 3971)。 ## 五、子插件配置(`pluginConfig..*`) 每个子插件的键来自它的 `configSchema`;不写 = 用默认值。控制台「QQ 控制台」按 `group` 分页渲染。 **路径类默认值都相对 `dataDir`**,例如 `personaDir: "personas"` = `/personas`。 ### `message-monitor`(order 55,消息监测) `pluginConfig['message-monitor']`默认`{enabled:true,rules:[]}`,空规则不监听任何内容。规则由专属页管理,不显示为通用文本配置;现有`monitor`端点的`list/save/test`操作走原控制台RPC,不新增HTTP桥。 每条规则字段: | 字段 | 默认/类型 | 说明 | |---|---|---| | `id` | string,必填 | 稳定唯一标识,最长64字符,排序不换ID | | `name` | string | 显示名,最长100字符 | | `enabled` | `true` | 单条开关 | | `match` | `contains`或`exact` | 包含关键词/整条匹配,区分大小写,不支持正则 | | `text` | string,必填 | 监听文本,最长1000字符;整条比较去首尾空白 | | `groups` | 数字数组 | 明确生效群,空数组不生效;最多100群,不支持通配所有群 | | `instruction` | string,必填 | 附加给模型的控制台指令,最长8000字符,不替换原消息正文 | | `cooldownMs` | `30000` | 同规则同群冷却,0至24小时,控制台按秒填写 | 最多100条规则,按数组顺序首条匹配;命中规则冷却中不继续尝试后续规则,已有@/唤醒仍正常但不追加监测指令。门禁、命令截获、本账号消息/notice过滤照常;身份不提升,合批/排队复用。测试只针对已保存规则,不调用模型/发送消息、不消耗冷却;保存携带读取到的revision防止覆盖另一处已改规则。配置保存后热同步,下次消息读取。 ### `gate-wake`(order 10,门禁与唤醒) | 键 | 类型 | 默认 | 说明 | |---|---|---|---| | `enabled` | bool | `true` | 插件总开关 | | `ignoreGroups` | array | `[]` | 屏蔽群号:整群完全不理 | | `groupUserBlocklist` | str | `""` | 群内黑名单,格式 `群号:QQ,QQ;群号:QQ`(优先于一切,命中连留档都不写)。旧的对象存法 `{群号:[QQ]}` 启动时自动转成文本 | | `groupUserAllowlist` | str | `""` | 群内白名单,格式同上;某群配了非空白名单时只有名单内的人+主人能触发。旧对象存法启动时自动转文本;空条目 `群号:` = 已纳入管理但名单为空(不限制) | | `allowedUsers` | array | `[]` | 全局允许用户 QQ;空 = 不限制(主人永远放行) | | `groupAtRequired` | bool | `true` | 群聊是否必须被叫(@/引用/唤醒词/接续)才回 | | `keywordCooldownMs` | int | `60000` | 同群关键词冷却(毫秒;纯呼名不参与冷却) | | `callAck` | bool | `true` | 被叫但没正文时先回一句罐头话(零 token) | | `pokeBack` | bool | `true` | 被戳一戳时回一句 | | `keywords` | text | `""` | 群唤醒词:每行 `*: 词1,词2`(全局)或 `<群号>: 词1,词2`(群专属,永远叠加)。全局行与**人设 `wake.txt` 合并生效**(都算,去重);`#` 开头是注释。控制台用**填空 + 添加**维护;`wake.txt` 自带的词也能点 × 删除(会写回人设文件,加词仍写 config) | ### `anti-repeat`(order 15,防刷屏) | 键 | 类型 | 默认 | 说明 | |---|---|---|---| | `enabled` | bool | `true` | 插件总开关 | | `repeatLimit` | int | `3` | 同一人同一句话在窗口内超过几次算刷屏(第 4 次起拦) | | `windowMin` | int(分钟) | `10` | 计数窗口;窗口外的不算 | | `cooldownMin` | int(分钟) | `10` | 命中后冷却时长(期间不回话) | | `notice` | text | 内置一句 | 命中时发的话(零 token,不走模型);留空 = 不吭声 | | `silent` | bool | `false` | 只冷却、不吭声(防“回一句反而帮他刷屏”) | ### `commands`(order 20,斜杠命令) | 键 | 类型 | 默认 | 说明 | |---|---|---|---| | `enabled` | bool | `true` | 插件总开关 | ### `private-friend-gate`(order 20,私聊好友门禁) | 键 | 类型 | 默认 | 说明 | |---|---|---|---| | `enabled` | bool | `true` | 开:私聊只回好友列表里的人;关:谁私聊都回 | | `allowList` | array | `[]` | 额外放行的 QQ 号(不是好友也让聊;主人永远放行) | | `failOpen` | bool | `true` | 好友列表拉不到时放行(建议保持开,否则协议端一卡就静默所有人) | | `cacheMin` | int(分钟) | `10` | 好友列表缓存时长 | ### `file-transfer`(order 30,文件与发图) | 键 | 类型 | 默认 | 说明 | |---|---|---|---| | `enabled` | bool | `true` | 插件总开关:关 = **不接收入站文件、`send_image` 拒绝发图**(纯文本照常) | | `downloadPolicy` | enum | `"woken"` | 什么时候下载入站文件:`woken` = **被叫到才下**(默认;群里没叫它的文件不落盘);`all` = 过门禁的消息都下(旧行为) | | `uploadDir` | path | `"qq_uploads"` | 入站文件落盘目录 | | `maxFileMB` | int(MB) | `20` | 单个入站文件大小上限:超了不下载、只在提示里写「未接收」(`0` = 不限) | | `maxFilesPerMsg` | int | `5` | 单条消息最多处理几个文件(1~20),超出的只提示 | | `keepDays` | int(天) | `7` | 启动时清理超过 N 天的入站文件(`0` = 不清理) | | `imageWhitelist` | array | `[]`(内核注入 `[, ]`) | 允许 `send_image` 发送的目录白名单(realpath 判定,软链穿不出去)。自己填了就**只认**填的目录 | > 以上都在控制台「**文件与图片**」页(开关 / 目录 / 大小上限 / 数量上限 / 保留天数 / 发图白名单)。 ### `persona-voice`(order 40,人设与句池) | 键 | 类型 | 默认 | 说明 | |---|---|---|---| | `enabled` | bool | `true` | 插件总开关 | | `personaDir` | path | `"personas"` | 人设库目录(`<名字>.md` / `.lines.txt` / `.wake.txt` / `_default.lines.txt`) | | `activePersona` | str | `""` | 当前人设名;空 = 按 `workspace` 里的投递指针/软链自动解析。在控制台「人设与拆条」页填写 = 钉死并同时切换投递文件(与总览页人设标签同一条事务);`persona use` 在 dsh 运行中只写盘不热更新,建议停服时用 | | `splitReplyEnabled` | bool | `true` | 是否拆条;控制台「人设与拆条」页 | | `splitReplyProbability` | int | `50` | 拆条概率(%);控制台「人设与拆条」页 | | `splitReplyMinLen` | int | `12` | 短于该长度不拆;控制台「人设与拆条」页(高级) | | `splitReplyMaxChars` | int | `200` | 整条超过该字数不拆(交给长文);控制台「人设与拆条」页(高级) | | `splitReplyDelayMs` | int | `1` | 拆条间隔(秒);控制台「人设与拆条」页(高级) | | `splitReplyMaxParts` | int | `3` | 最多拆几条(2–4);控制台「人设与拆条」页(高级) | | `quoteEnforce` | bool | `true` | 抹掉对「刚触发本轮的这条消息」的冗余引用 | > 句池/唤醒词是**文件**:`/personas/<名字>.lines.txt`、`<名字>.wake.txt`; > 投递到 `/workspace/{lines.txt,wake.txt}`(Linux 软链 / Windows 复制)。改完热读。 ### `group-welcome`(order 55,入群欢迎) | 键 | 类型 | 默认 | 说明 | |---|---|---|---| | `enabled` | bool | `true` | 插件总开关 | | `mode` | enum | `"fixed"` | `fixed` 固定句池 / `model` 模型定制 | | `groups` | array | `[]` | 欢迎群;`*` = 所有未屏蔽群;空 = 不欢迎 | | `lines` | text | 内置句池 | 固定句池,一行一句,支持 `{name}` `{qq}` `{group}` | | `modelPrompt` | text | `""` | 模型模式风格要求(留空用内置提示) | | `atNewcomer` | bool | `true` | 固定句池是否 @ 新人 | | `delayMs` | int | `3` | 延迟欢迎(秒) | | `cooldownMs` | int | `10` | 同一人同群防重复(分钟) | | `imageChance` | int | `100` | 附图概率(%) | | `imageDir` | path | `"qq-welcome-images"` | 欢迎图片池目录(png/jpg/jpeg/gif/webp) | | `modelFallback` | bool | `true` | 模型没发话时用句池兜底 | | `modelFallbackMs` | int | `45` | 兜底等待(秒) | | `modelMinGapMs` | int | `60` | 模型模式两条欢迎的最小间隔(秒) | ### `imagegen`(order 60,生图与头像) | 键 | 类型 | 默认 | 说明 | |---|---|---|---| | `enabled` | bool | `true` | 插件总开关 | | `cooldownMs` | int | `60` | 普通用户共享生图冷却(秒);本轮可信主人身份自动豁免,主人请求仍更新此冷却;所有人仍受单任务限制,混合成员合批不豁免 | | `ackCooldownMs` | int | `30` | 「在画了」罐头话冷却(秒) | | `timeoutMs` | int | `240` | 单次生图超时(秒);异步任务站轮询取图的整体等待也受它约束 | | `qualitySizeTiers` | bool | `false` | **质量/尺寸档位总闸**。关闭(默认):不向上游发送 `quality` 与 `size`,由上游按自身默认出图;工具参数(`quality` / `resolution` / `aspect_ratio` / `size`)与换算代码保留,传了也不生效。打开:恢复 `quality`(`low` / `medium` / `high`)+ `resolution`(`1k` / `2k`)+ `aspect_ratio` 换算并上送。换回支持档位的上游再打开。 | | `extraBody` | text | `""` | **额外请求字段(JSON 对象)**:不同平台/中转站字段不同,由用户自己补。文生图并进 JSON 请求体(可覆盖 model/prompt/size/n);图生图逐项追加为 multipart 字段(布尔/数字转字符串、数组=重复同名字段);`openai-chat` 协议并进 `/chat/completions` 顶层。留空 = 不加任何字段(与旧版一致)。示例:关服务端水印(sensenova/商汤默认开)→ `{"watermark": false}`。非法 JSON 会直接报错、不发请求。 | | `refDir` | path | `"qq-refs"` | 角色参考图目录 | | `avatarDir` | path | `"qq_avatars"` | 群友头像缓存目录 | | `avatarCacheHours` | int | `24` | 头像缓存时长(小时) | > **同步站 / 异步任务站自动兼容**(不用另选协议):响应里直接带 `data[0].url` / `b64_json` 就照旧下载;只回 `task_id` 的异步站自动轮询 `GET {base}/tasks/{id}`,`completed` 后取 `result.images[0].url`(字符串或数组都认)。超时报错里带 `task_id`,可到中转站后台按 id 查图。 ### `link-parser`(order 70,链接解析) | 键 | 类型 | 默认 | 说明 | |---|---|---|---| | `enabled` | bool | `false` | 插件总开关(**默认关**;开了才解析分享链接) | | `parserUrl` | str | `"http://127.0.0.1:3972"` | 解析侧车地址(挂了自动降级给模型) | | `maxBytes` | int | `104857600` | 单条媒体上限(字节) | | `continueWhenAddressed` | bool | `true` | 同时在被叫时:解析后是否继续交给模型 | | `modelTextThreshold` | int | `4` | 剥链接后正文至少多少字算「在跟我说话」 | | `platforms` | array | `["bilibili","douyin"]` | 只解析这些平台 | ### `stickers`(order 80,表情包) | 键 | 类型 | 默认 | 说明 | |---|---|---|---| | `enabled` | bool | `true` | 插件总开关(关掉 = 不挑图也不发) | | `collect` | bool | **`false`** | **收表情(会写索引)**:开 = 采集新图/打标/清理/记使用,会写 `index.json`;关 = 库只读(仍能挑现有图发)。默认关,防运行中被内存副本覆盖索引 | | `dir` | path | `"qq-stickers"` | 库目录(`index.json` + 图片),相对路径按数据目录解析 | | `max` | int | `200` | 库上限(张);满了跳过新图 | | `tagging` | bool | `false` | 新图自动打标(需内核注入 `bot.describeImage`;当前内核无此能力,控制台暂不展示) | | `skipGif` | bool | `true` | 只收静态图(按魔数判断) | | `collectGroupsOnly` | bool | `true` | 只在群里采集 | | `stickerEnabled` | bool | `true` | 回话时是否可能掷骰提醒发表情(关 = 不再提醒) | | `offerPercent` | int | `80` | 每轮「提醒它可以发一张」的概率(%);**群聊与私聊都适用**(2026-11-04 起私聊也提醒) | | `offerCooldownMs` | int | `20` | 同一会话两次提醒的最小间隔(秒) | | `maxPerTurn` | int | `1` | 每轮最多发几张 | ### `group-memory`(order 90,群记忆) | 键 | 类型 | 默认 | 说明 | |---|---|---|---| | `enabled` | bool | `true` | 插件总开关(关 = 不注入记忆指针、`read_memory` 报错;不影响积累) | | `dir` | path | `"qq-memory"` | 记忆目录:群 `<群号>.md`,私聊 `private/.md`(分目录防撞名) | | `maxBytes` | int | `24000` | **单篇记忆文件上限(字节)**:既是读取上限(按整行截断),也是整理时要求模型压到的上限;控制台保存同样按它限制。整理结果超限会退回重做一次,不为塞进去而丢弃内容。 | | `mutedChats` | str | `""` | **关闭记忆的会话**:列在这的会话不读也不积累(自动/手动积累都跳过),已有记忆仍可在控制台查看/编辑/删除。格式 `group:群号` / `private:QQ号`,逗号或换行分隔;留空 = 全部开启。控制台「群记忆」页每个会话右边的 🔔/🔕 改的就是它。 | | `accumulateEnabled` | bool | `true` | 自动处理开关(内置调度,不依赖 cron);关掉仍可从控制台手动处理 | | `accumulateModelProvider` | str | `""` | 记忆整理后台任务专用提供商 ID;控制台从当前 DSH 宿主模型目录选择,并与 `accumulateModel` 一次保存。两项都空则继承默认模型,不改变 QQ 聊天模型 | | `accumulateModel` | str | `""` | 记忆整理专用模型内部 ID(不是显示名);与提供商都填写才执行指定模型。缺一项、模型不可用或调用失败会报错,不静默回退 | | `accumulateDailyTime` | str | `"04:00"` | 每日执行时间,24 小时 `HH:mm`;按下面的时区解释 | | `accumulateTimezone` | str | `"Asia/Shanghai"` | IANA 时区名,不跟随浏览器/服务器本地时区 | | `accumulateRecentDays` | int | `14` | 近期经历保留天数;不影响稳定记忆与未完成约定 | | `accumulateRetainRevisions` | int | `20` | `.revisions/` 保留多少份历史版本(0 = 不备份)。控制台手改留下的 `-manual` 备份也按这个数清理。 | | `accumulateMaxGroupsPerRound` | int | `12` | 单轮最多处理多少个会话(群与私聊合计);其余下轮轮转,避免大号段永远轮不到 | > **处理流程(唯一实现)**:按 `accumulateDailyTime` + `accumulateTimezone` 到期,或控制台「立即处理」→ 只收集首次启用之后、**本机器人实际参与**的会话互动(触发消息、引用、合批消息都算)→ 每个会话单独跑一次一次性后台会话,把「现有记忆 + 新互动」交给模型 → 模型通过 `memory_append` 提交 `operations`(新增/修改/移除,带本任务来源 ID 与批次令牌)→ 本地校验后原子写回 `/<群号>.md` 或 `/private/.md`,旧版备份到 `.revisions/`。 > > **预算与失败**:聊天输入不做条数/字数预算,本机器人参与的互动整份交给模型(仅保留 400 万字符的崩溃保护);**预算约束的是记忆文件本体** `maxBytes`。整理结果超过该上限时,先带提示重做一次压缩;仍超才记待办。模型没提交、批次令牌不匹配、来源校验失败或写入冲突都记为失败并保留待办,下轮重试。成功但无新增也会推进进度,不会重复处理同一批。 > > **记忆文件格式**:三个分节(稳定记忆 / 近期记忆 / 约定与承诺),每条 `- [数字ID] 文本 (更新:时间)`,文末 `` 元数据只存最小溯源(id/分节/更新时间/到期时间/正文短哈希、下一个 ID、renderHash),正文与来源回执都不再抄进页脚。**来源回执不再写入正文文件**,进度只存在 `state/group-memory-daily.json`,避免文件被撑大。模型读取与控制台读取都隐藏 ID、时间戳和页脚;控制台保存按可见正文重新生成页脚(缺失分节自动补齐),缺失元数据或格式非法会报错,不静默覆盖。近期记忆按 `accumulateRecentDays` 到期清理;另外**超过 3 天的条目会按概率随机淡忘**(每天约 25%,越旧概率越高,上限 60%;约定与承诺不参与),模拟人的记忆衰减。 > > **后台会话隔离**:模型跑在独立的一次性会话里,只放行 `memory_append`(提交结果);`send_message` / 生图 / `bash` 等一律禁用,不占 QQ 线、不会往群里发消息。指定专用模型时只对新会话生效,不改 DSH 全局默认;模型不可用会失败并保留待办,不静默换模型。 > > 控制台「群记忆」页主面板显示自动开关、每日时间与时区、专用模型选择;状态显示待办原因、上次结果、最近更新会话、失败详情和下次预计时间。预算与历史版本折叠在「高级配置」。 > 单个会话想停记忆:点列表右侧 🔔/🔕(写 `mutedChats`);关闭期间不读也不处理,已有内容保留。 > > **旧版 `interval` 模式已移除**(2026-11-12):`accumulateSchedule`、`accumulateIntervalHours`、`accumulateMode`、`accumulateLookbackHours`、`accumulateKeepDays`、`accumulateKeepRows`、`accumulateMinNewMsgs`、`accumulateSectionMaxItems` 不再读取;旧 `config.json` 里残留这些键会被忽略,不需要手工清理。 ## 六、控制台(新手向导与热改) ### 新手向导的三个入口 | 入口 | 位置 | 用途 | |---|---|---| | **新手向导清单** | 总览页顶部 | 逐项 ✅/❌:配置文件 / 主人 QQ / 协议端连接 / 人设 / 生图(可选),每项一句「怎么做」;未完成时顶部有醒目提示条;小字显示 `版本 · 数据目录 · 配置路径` | | **OneBot 连接** | 「OneBot 连接」页(任意 OneBot v11 协议端都填这里) | 图文步骤 + `wsUrl` / `httpUrl` / token 输入;**「测试 OneBot 连接」/「重新连接 OneBot」按钮在「运维」页**(先保存再测) | | **生图配置** | 「生图」页 | 协议(`openai-images` / `openai-chat`)、API 地址、模型(带建议)、API Key(掩码);**「检查生图配置」按钮在「运维」页** | > 人设不用手动创建:插件首次启动会**自动投递内置默认人设「蓝色大肥鱼」**;换/加人设的写法见 > [使用说明书 §6](使用说明书.md#6-人设怎么写) 的「人设怎么写」。 > 首次启动缺 `adminQq` 时插件处于「待配置模式」(见 §七):控制台可用、工具已注册、**不连协议端**; > 填完保存后**自动连接**,不需要重启 dsh-web。 ### 内置维护与配置备份 控制台「运维」的离线自检、临时缓存清理和配置备份去重没有新增配置键,也不读取本机扩展环境变量或可执行扩展文件。离线自检不联网、不写报告。清理只处理插件专用临时缓存,不改变上传文件的 `keepDays` 策略,不在启动时自动删缓存;备份去重以当前 `configPath` 为基准(仅配置位于 `dataDir` 内时扫描,目录外备份不动),只处理合法 JSON 且字节相同的 `.bak-console-<17位数字时间戳>` 备份,至少保留最新 3 份,不修改当前配置。两项删除动作必须预演并用本次 `previewToken` 确认,执行重验、变化跳过,详见[内置运维](<运维接口.md>)。 ### 热改说明 - 入口:DSH Web → 设置 →「QQ 控制台」。 - 写配置流程:按 canonical 配置路径在**同一进程内**串行读改/校验 → **备份** `config.json.bak-console-<时间戳>` → **原子写** → `kernel.reloadConfig()` → 失败回滚。update 在事务内读最新文件;整份 save 仍替换全量配置,不是旧副本自动合并,也不是跨进程锁。 - **下次读取/轮次生效**:常规门禁、概率、冷却、欢迎参数、生图凭据等由回调读取新配置;不追溯修改已排队/已发出的操作。 - **资源边界生效**:表情 `dir` 在串行队列的任务边界切库,已排队任务保留原目的库;`max` 动态读取。OneBot 参数同步后,已有连接需要时在「运维」重连。 - **需重载插件/重启**:`plugins.` 装配开关、bundle 的 dataDir/configPath/兼容网关引导项,以及没有运行期重建逻辑的初始化资源。不能从配置对象已更新推断这些资源已更新。 - 手改 `config.json` 后需显式重载或重启;没有自动文件监听。 - Windows 记事本可能给文件加 UTF-8 BOM —— 内核读配置会剥 BOM;但保存后建议确认 JSON 合法。 ## 七、校验与失败行为 - 启动时校验**必填项**:`adminQq` / `onebot.wsUrl` / `onebot.httpUrl`,以及 `dsh.sessionsFile` / `dsh.qqWorkspacePath` / `archive.dir` 必须是字符串路径; `credentials.gptImage.protocol` 只允许 `openai-images` / `openai-chat`。 - **只有 `adminQq` 缺失时 = 「待配置模式」**:**不报错退出**,`kernel.configReady=false`; 控制台可用、工具已注册、**不连接协议端**;日志 warn 「实例配置未完成(缺 adminQq)…控制台可继续配置」。 - 其它必填项缺失/非法(协议端地址、路径、protocol):仍抛中文错误(含配置文件路径),修好重启。 - 控制台保存成功(或 `kernel.reloadConfig()` / 应用运行时配置成功)后:`configReady=true`; 若此前是待配置模式且 bundle 配置 `autoConnect` 未关 → **自动连接协议端**(失败只 warn 不抛)。 - 配置文件不存在:自动创建一份默认配置(`adminQq: 0`)→ 进入待配置模式(不是直接报错)。 - 旧格式(含 `bridge` 段的 v1 配置):默认启动读取时先备份 `*.bak-legacy-<时间戳>` 再迁移落盘;`loadConfig({createIfMissing:false})` 只在内存转换(例如 doctor)。显式导入用 `node bin/dsh-onebot-qq.mjs import-legacy <旧配置>`(见 [排错](排错.md) §七)。 ## 八、配置示例 见 [`examples/config.example.json`](../examples/config.example.json)(由代码里的 `defaultConfig()` 生成, `adminQq` 为占位 `10001`)。改完可用 `node bin/dsh-onebot-qq.mjs doctor` 检查必填项。