wechat-robot-skills/skills/send-complex-message/SKILL.md
2026-09-12 17:26:47 +08:00

224 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
name: send-complex-message
description: "在当前微信会话中发送纯文本、引用回复或群聊艾特/@/提及消息。用户要求发送一段文字、仅@成员或@所有人、引用某条消息回复、引用时同时@成员时使用;文本和引用支持私聊及群聊,可在发送后结束当前 Agent 对话。"
---
# Send Complex Message Skill
## 描述
本技能是当前微信会话中纯文本、艾特和引用回复的统一发送入口。仅艾特时不需要正文;引用消息必须有正文,也可以附带成员艾特参数。艾特支持指定一个或多个成员,也支持微信原生的 `@所有人`。
技能脚本位于 `scripts/send_complex_message.py`,统一调用客户端的 `/message/send/refermessage` 接口。`refer_message_id` 是可选参数:不传时由客户端调用普通文本消息方法,传入时发送引用消息。指定成员时,根据昵称或备注查询当前群内未退群成员;@所有人时使用客户端协议值 `notify@all`,不要把 `@昵称` 或 `@所有人` 当普通正文拼接。
本技能不带引用 ID 时通过普通文本消息实现原生艾特,支持只艾特而不附加正文。带引用 ID 时,客户端接收 `at` 并显示艾特名称,但尚未实现引用消息中的原生艾特提醒,不能把引用发送成功表述为已经提醒成员。
## 触发条件
- 用户要求将一段话分成多条发送。
- 用户要求「引用这条消息回复」「引用我刚才的话说收到」「回复我引用的那条消息」。
- 需要发送微信原生引用回复,而不是在普通文本中复述原文。
- 需要艾特、@、提及某个群成员或多个群成员。
- 用户要求「帮我艾特下 xxx」「@ 一下 xxx」「提一下 xxx 和 yyy」。
- 用户要求「@所有人」「提醒全体成员」「通知群里所有人」。
- 需要在群聊里点名提醒某人。
- 其它时候不应该使用本技能
不带艾特的纯文本和引用回复可用于私聊和群聊;只要提供艾特参数,`ROBOT_FROM_WX_ID` 就必须是群聊 ID。
## 入参规范
`refer_message_id` 不全局必填,只在用户明确要求引用时传入,值为 `messages.id`。仅文本、仅艾特时省略该参数,不查询引用目标,也不从环境变量自动补齐引用 ID。
| 发送方式 | 艾特参数 | `refer_message_id` | 正文 `content` |
| ---------- | ---------------------- | ------------------ | ------------------ |
| 仅艾特 | 指定成员或 `all: true` | 不传 | 可省略或为空 |
| 仅文本 | 不传 | 不传 | 必填且不能是纯空白 |
| 仅引用 | 不传 | 传入 `messages.id` | 必填且不能是纯空白 |
| 引用并艾特 | 指定成员或 `all: true` | 传入 `messages.id` | 必填且不能是纯空白 |
不引用时也支持正文加艾特。下方 schema 没有全局必填字段,组合校验由 schema 和脚本共同约束。
```json
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": [],
"properties": {
"mention": {
"type": "string",
"description": "要艾特的群成员昵称或备注。按用户原话提取,不要改写。"
},
"mentions": {
"type": "array",
"items": {
"type": "string"
},
"description": "要艾特的多个群成员昵称或备注。"
},
"all": {
"type": "boolean",
"description": "是否 @所有人。用户明确要求 @所有人或通知全体成员时设为 true,不能与 mention/mentions 同时使用。"
},
"content": {
"type": "string",
"description": "要发送的文本内容。纯文本和引用回复必须提供非空正文;只艾特不附加正文时可以省略或为空字符串。"
},
"refer_message_id": {
"type": "integer",
"minimum": 1,
"maximum": 9223372036854775807,
"description": "可选,仅引用时传入被引用消息的数据库主键 messages.id。仅文本、仅艾特时省略;传入后 content 必须为非空白文本。"
},
"ended": {
"type": "boolean",
"description": "是否结束当前对话。当 Agent 已经完成消息发送、要说的话已说完、要做的事已做完时,设置为 true。"
}
},
"anyOf": [
{
"required": ["content"],
"properties": { "content": { "pattern": "\\S" } }
},
{
"required": ["mention"],
"properties": { "mention": { "pattern": "\\S" } }
},
{
"required": ["mentions"],
"properties": {
"mentions": { "contains": { "type": "string", "pattern": "\\S" } }
}
},
{
"required": ["all"],
"properties": { "all": { "const": true } }
}
],
"dependencies": {
"refer_message_id": {
"required": ["content"],
"properties": { "content": { "minLength": 1, "pattern": "\\S" } }
}
},
"additionalProperties": false
}
```
对应命令行参数:
- `--refer-message-id <messages.id>` 可选,传入后发送引用消息,必须同时提供非空 `--content`
- `--mention <昵称或备注>` 指定成员时使用,可重复传入
- `--mentions <JSON数组>` 指定成员时可选,用于一次传入多个昵称或备注
- `--all`(也支持 `--mention-all`)可选,用于真正 @所有人,不能与 mention 参数同用
- `--content <文本内容>` 纯文本和引用消息必填,单独艾特时可省略或为空
- `--ended` 可选标志。当 Agent 已完成消息发送、要说的话已说完时传入。
## 引用消息的选择
- 仅在用户要求引用时选择消息,传给脚本的引用 ID 只使用 `messages.id`。
- 引用当前用户触发本次对话的消息时,使用环境变量 `ROBOT_MESSAGE_ID` 中的消息主键。
- 用户要求回复他引用的原消息时,使用 `ROBOT_REF_MESSAGE_ID` 中的消息主键;该值为空或 `0` 表示没有引用目标,不能改为引用当前消息。
- 引用其他历史消息时,从当前机器人数据库 `messages` 表查找,限定 `from_wxid = ROBOT_FROM_WX_ID`,按用户描述确认原消息后取 `id`。不要使用 `msg_id`、`client_msg_id` 或 XML 中的 `svrid`。
- 引用目标不明确时先确认,不能猜测消息 ID。不要因为上下文存在引用消息就自动发送引用回复。
- 脚本接收明确的 `--refer-message-id`,不会自动选择最近一条消息;仅引用且不指定成员时不需要查询成员表。
## 成员匹配规则
仅指定成员时执行以下匹配;`--all` 不查询成员表:
1. 只在当前群聊 `ROBOT_FROM_WX_ID` 对应的 `chat_room_members` 记录中查找。
2. 只匹配 `is_leaved` 为空或 `0` 的成员,已经退群的成员不能被艾特。
3. 使用用户给出的昵称或备注做模糊查询,字段优先级为 `remark`,然后是 `nickname`。
4. 查询到候选成员后,优先选择 `remark` 完全等于输入值的成员。
5. 如果没有完全相等的 `remark`,选择 `nickname` 完全等于输入值的成员。
6. 如果没有完全相等结果,选择第一个 `remark` 包含输入值的成员。
7. 如果仍未命中,选择第一个 `nickname` 包含输入值的成员。
## 执行步骤
1. 判断用户需要纯文本、仅艾特、引用回复,还是引用时同时艾特。纯文本和引用回复必须准备非空正文 `content`;引用时按上面的规则确定 `refer_message_id`。
2. 如需指定成员,把用户原话中的昵称或备注写入 `mention`/`mentions`;@所有人时设置 `all: true` 并使用 `--all`。
3. 在该技能目录执行脚本,例如:
仅艾特某人,不附加正文:
```bash
python3 scripts/send_complex_message.py --mention '张三' --ended
```
仅发送文本,不艾特、不引用:
```bash
python3 scripts/send_complex_message.py --content '收到,我来处理' --ended
```
引用当前用户消息回复:
```bash
python3 scripts/send_complex_message.py --refer-message-id "$ROBOT_MESSAGE_ID" --content '收到,我来处理' --ended
```
回复用户引用的原消息(先确认 `ROBOT_REF_MESSAGE_ID` 大于 `0`):
```bash
python3 scripts/send_complex_message.py --refer-message-id "$ROBOT_REF_MESSAGE_ID" --content '同意这个安排'
```
引用已确认的历史消息并附带成员显示(当前客户端不产生原生艾特提醒):
```bash
python3 scripts/send_complex_message.py --refer-message-id 12345 --mention '张三' --content '请看一下这个'
```
艾特群成员并附加正文:
```bash
python3 scripts/send_complex_message.py --mention '张三' --content '看一下这个'
```
用户要求 @所有人时传 `--all`,不要把“所有人”当成员昵称查询:
```bash
python3 scripts/send_complex_message.py --all --content '请大家查看群公告'
```
当 Agent 认为任务已完成、对话可以结束时,加上 `--ended` 标志:
```bash
python3 scripts/send_complex_message.py --mention '张三' --content '看一下这个' --ended
```
4. 指定成员时,脚本查询数据库表 `chat_room_members` 并解析微信 ID;`--all` 时跳过数据库查询,使用 `at: ["notify@all"]`。如果指定成员未命中,可以查询记忆里是否记录了对方的别称。
5. 脚本通过 `X-Private-Token` 请求头传递环境变量 `ROBOT_CLIENT_PRIVATE_TOKEN`,统一调用 `POST http://127.0.0.1:{ROBOT_WECHAT_CLIENT_PORT}/api/v1/robot/message/send/refermessage`。请求体包含 `to_wxid`、`content`、`at`;只有引用时才添加 `refer_message_id`。不引用时,客户端直接转入普通文本发送方法;纯文本的 `at` 为空数组,仅艾特的 `content` 为空字符串。
`to_wxid` 固定取当前会话 `ROBOT_FROM_WX_ID`。技能在客户端内执行,无需携带管理后台的机器人实例 query `id`。
## 校验规则
- `ROBOT_FROM_WX_ID` 必须配置;带艾特时必须以 `@chatroom` 结尾。
- 没有艾特且正文为空或纯空白时不能发送;空的成员名称、空成员数组和 `all: false` 都不算有效艾特。
- 仅艾特时,`content` 可直接为空,由客户端根据 `at` 生成艾特正文,不添加额外话语。
- 传入 `refer_message_id` 时,必须是 int64 范围内的正整数,引用正文不能是空字符串或纯空白;不引用时直接省略 ID,不能为满足校验而猜测或自动填入消息 ID。
- `--all` 必须独占,不能再指定成员。
- 每个要艾特的人都必须能在当前群内匹配到未退群成员。
- 如果同一个微信 ID 被多个昵称命中,只会艾特一次。
## 依赖安装
- 指定成员、需要查询数据库时,脚本会自动创建虚拟环境并安装依赖;纯文本、仅引用或 `--all` 不需要安装数据库依赖。
- 如需手动重新安装,可执行:`python3 scripts/bootstrap.py`
## ended 行为
- 当 `--ended` 传入且客户端返回业务状态 `code: 200` 时,脚本在正常输出末尾追加打印独立一行 `ended`。
- `ended` 字符串必须位于输出的最末尾,前面不能跟其他字符。
- Agent 检测到输出以 `ended` 结尾时,会自动退出 Agent 循环。
## 回复要求
- 成功时,脚本输出「文本消息发送成功」「引用消息发送成功」「艾特消息发送成功」或「艾特所有人消息发送成功」,表示消息已通过客户端接口直接发送,不要再重复发送正文。
- HTTP 200 不等于业务成功,必须检查响应中的 `code`。当前引用接口成功时可能返回 `data: null`,不能因没有消息对象重发。
- 如果传入 `--ended`,输出末尾会追加 `ended`,Agent 会自动结束对话。
- 失败时,返回脚本输出的具体错误信息,不输出 `ended`。引用消息不存在、原消息内容不完整或原发送人不存在时,按客户端错误说明原因;请求超时或发送结果不确定时,不自动重试,以免重复发送。