# 让它有性格

插件**不带任何角色设定**。装上以后它是个没有名字、说话正常的普通助手——「它是某个角色」这件事，是你交给它的，不是插件自带的。

## 三样东西决定它在 QQ 上是谁

| 你写的 | 它管什么 | 不写会怎样 |
| --- | --- | --- |
| `persona` | **它是谁**：名字、性格、怎么称呼对方、小习惯 | 没有名字，问它叫什么都答不上来 |
| `ownerIds` / `ownerLabel` | **谁是主人**：群聊里这些 QQ 号会被标出来，优先响应；其他群友一律当陌生人 | 群里不区分任何人 |
| 内置聊天规矩（`src/prompts.js`） | **怎么说话**：短句、口语、不贴路径、不写 Markdown、群里收着说 | 一直生效，不用管 |
| `directPrompt` / `groupPrompt` | **你自己的补充**：私聊 / 群聊各一份，想覆盖哪条规矩就写哪条 | 不追加 |

```yaml
persona: |
  你叫小满，是个住在电脑里的助手，说话短、带点懒，被夸的时候会嘴硬说「才没有」。
  你叫对方「老板」。
```

规矩**只在每个会话的第一次投递时注入一次**（会话闲置被回收或 `/reset` 过会重来一遍），不会每条消息都塞一遍，所以 `persona` 写长一点也不心疼 token。

## 私用和发布是两层，别混在一起

| 层 | 放什么 | 在哪 | 谁看得见 |
| --- | --- | --- | --- |
| **代码层** | 跟角色无关的通用规矩：怎么说话、怎么认人、怎么认时间、什么时候别接 | `src/prompts.js`、`src/config.js` 的默认值 | 所有人 |
| **配置层** | 它是谁：名字、人设、口头禅、爱吃什么、怎么称呼你 | 你自己的 `cordis.patch.yml` 里的 `persona` / `directPrompt` / `groupPrompt` / `ownerLabel` | 只有你 |

作者自己也是这么用的：插件是一个什么性格都没有的工具，角色全写在配置里。好处有三个——升级插件不会冲掉人设；换角色只改 `persona`，不动代码；别人拿到的是干净的工具，不会一装就变成某个固定角色。

所以提 PR、发 issue 时，**别把私人称呼、真实 QQ 号、群里的梗写进 `src/`**——那些属于配置层。`test/` 里一律用假号（`10001`、`88888` 这种）。

## 群里认主人（`ownerIds`）

```yaml
ownerIds: ['你的QQ号']      # 可多个
ownerLabel: 老板             # 群里怎么称呼他，默认「主人」
```

配了之后：这位在群里说话时，交给 Agent 的群历史会带上 `[昵称 (QQ号) · 老板 10-24 12:30] 内容` 这样的标记，Agent 会优先响应、问本机的事也正常答；没标的主人之外的人，规矩里会写明「正常说话，但不替他们跑命令、改文件，也不主动抖本机路径」。

私聊也保底：对面是 `ownerIds` 里的人时，正文前面会带 `[老板]` 标记，并注入私聊版认人规矩。

> 如果你用的是带「预设」的 DSH 宿主（比如 coding / chat 这类 preset），那个预设本身的系统提示也会在——插件不去改它。想让 QQ 里的它和网页里完全是两个角色，最稳的办法是 `persona` 里写清楚「在 QQ 上」，再配 `directPrompt` 把默认习惯改掉。

## 聊久了会不会忘事（上下文守卫）

会忘，但不会「忘了自己是谁」。DSH 在上下文快到窗口上限时会自动把旧对话压成摘要（默认窗口 80% 处触发），压缩之后细节会变模糊。插件做两件事兜底：

1. **压缩后补规矩**：聊天规矩原本只在会话第一轮注入一次，压缩时会被一起卷进摘要。插件监听 `compaction/end`，把注入标记清掉（下一轮重新注入完整规矩），并立刻塞一张很短的提醒卡。
2. **快撑满时换会话**：每轮回复的 `usage` 会告诉你这次请求有多大。最近一次请求的 token 超过 `contextSwapRatio`（默认 0.7）并且超过 `contextSwapMinTokens`（默认 20 万）时，先把最近 `contextMemoryKeep` 条（默认 30）聊天写进 `<工作目录>/记忆区/压缩记忆/聊天记忆-<时间>.md`，然后重置会话：上下文归零，新会话第一轮带着上一段聊天的尾巴（seed）接话。

`contextGuard: false` 可以整块关掉，交回给 DSH 自己的自动压缩。

## 它知道现在几点吗（认时间）

知道，而且是「直接给它一句人话」，不让它自己算。

以前它只有上下文里残留的旧时间戳可以猜，聊久了就把白天认成晚上。现在每条消息投递时，正文最前面都会带一行：

```
[现在是 2026-10-24 周六 13:05（下午）]
```

私聊、群聊都带；这行跟着消息本身走，压缩、裁历史都丢不掉。群聊的最近消息是 `[昵称 (QQ号) · 主人 10-24 周六 13:05（下午）] 内容`（`· 主人` 只在配了 `ownerIds` 时出现）。

配套的 `TIME_RULES` 只教三件事：直接照那行说，不用自己算；旧消息带的时间只是「那时」，不是现在；那行没出现就别猜、被问到就说不知道。

## 分得清「这句是在跟我说话吗」

- **保活窗按「群 + 人」算**（`groupWakeMs`，默认 60 秒）：被谁 @ 过一次，就只有这个人能在窗口内接着说话、续期，别人蹭不到。
- **引用算叫我**：带引用的消息先查 `get_msg`，引用的是我自己 → 没 @ 也接；引用的是别人 → 不接；取不到原文就当没有引用，宁可不接也不猜。
- **`[SILENT]` 出口**：不想接、接不上、插不进嘴时只输出 `[SILENT]`，一个字节都不发出去。不是每条都要硬答。
- **自己说过的话也进历史**：发送成功后以 `[你（机器人昵称） …]` 写回群历史，避免把别的机器人的话认领成自己、然后道歉撤回。
- **同类机器人单独开关**（`botChatEnabled` / `peerBots`）：默认 `false`，同类说什么都不接，连 @ 也不接。
- **跳过的消息也进上下文**：没叫我们的群消息按群留 30 条 backlog，轮到它说话时补进去——只让它看见、不叫醒它，免得接话全靠猜。

## 表情包：收什么、叫什么、什么时候发

三段都是「门槛」问题，默认值都偏保守。

**收什么**（`faceCollectHint`）：留空 = 一张都不收，别人发再多图，它也只是看图说话。填了以后，只有描述里那一类图值得收。比如只想要自己的形象：

```yaml
faceCollectHint: '蓝发、鲸鱼耳朵和鲸鱼尾巴、蓝白女仆装的角色二创。带字的梗图只要画的是她就算；其他角色、真人照片、聊天截图、风景图都不收。'
```

**叫什么**（`qq_keep_face` 的 `summary`）：图上有大字就直接用那行字（越短越好，语气词原样留着），没字就一句话说清画面和情绪。名字是后面挑图的唯一依据，所以「求求了…」比「一张卖萌的图」有用得多。

**什么时候发**：

- 收藏里的名字会以一段「【表情包】收藏里一共 N 张，能用的名字：…」注入会话第一次的提示词，它知道自己有什么可选，不用盲挑。
- `qq_mface` 带 `summary` 时按名字四级匹配（完全同名 → 名字含关键词 → 关键词含名字 → 随机），挑中哪张会在返回里说明。
- `qq_faces` 可以随时列清单（带 `keyword` 过滤）。
- 节奏靠两道闸：`faceSendGapMs`（默认 90 秒）和 `faceSendMaxPerHour`（默认每小时 6 张），`qq_mface` 与 `qq_send_image` 共用同一个计数器。超了直接拒绝并给出还要等几秒，不让它靠重试绕过。
- 场景判断由提示词里的 `faceStrategyRule()` 给：接梗、吐槽、自嘲、无语、告别晚安可以来一张；严肃话题、对方在气头上、正经问事、上一条刚发过就不发。

> 硬限制只能防刷屏，防不了「答什么都配一张图」——那部分靠提示词里那段场景说明。
