# pi-qq

[English](./README.md) | 简体中文

将 [pi](https://github.com/earendil-works/pi) 接入 QQ 私聊。在 QQ 上发送文字、语音、图片或文件，pi 在你的电脑上工作，并把结果发回 QQ。

## 环境要求

- Node.js 22.19 或更高版本，与 pi 保持一致。
- 已启用 C2C 私聊能力的 QQ 机器人应用。
- 可用的 pi 环境。

## 安装

```bash
pi install npm:@chen_hu_hu/pi-qq
```

本地开发：

```bash
pi --extension /path/to/pi-qq
```

## 登录

1. 启动 pi。
2. 执行 `/qq-login`。
3. 用手机 QQ 扫描二维码。

登录凭据不会写入配置文件。重启 pi 后需要重新扫码。`/qq-logout` 会立即清除凭据。

## 使用

给机器人发送 QQ 私聊消息即可开始任务。

- **文字**：普通聊天消息。
- **语音**：直接使用 QQ 内置 ASR 文本；音频不会下载或保存。如果 ASR 没有返回文本，机器人会提示你重发或改用文字。
- **图片和文件**：保存到 `.pi/pi-qq/<sessionId>/uploads/`。如果消息中没有指令，pi 会等待你的下一条文字或语音消息。

QQ 客户端对混合消息的支持不同。手机 QQ 通常一次只发一种类型，桌面版 QQ 可以发送“文字 + 图片”。当客户端把文字/语音和附件放在同一条消息里时，pi-qq 会把它们合并到同一个提示词中处理。

### 附件

**只发送附件不会启动 pi。** 文件和图片会先保存，pi 等待下一条文字或语音指令后再处理。

- 先上传一个或多个文件或图片，再发送文字或语音说明要做什么。
- 下一条指令会包含保存后的附件路径。
- 指令到达前上传的多个附件会合并到同一个提示词中。
- 图片通过 pi 的 `read` 工具查看，因此当前模型必须支持图片输入才能看到内容。
- `/new` 会清空等待指令的附件。
- 附件按当前 pi 会话隔离。相同文件名且内容相同会复用已有文件；相同文件名但内容不同会保存为 `name-1.ext`；不同文件名但内容相同会分别保存。

pi 看到的提示词示例：

```text
# Attachments:
- .pi/pi-qq/<sessionId>/uploads/report.pdf

# User message:
Summarize this file.
```

### 语音消息

语音消息使用 QQ 内置 ASR（`asr_refer_text`）。转写文本会作为普通用户文本交给 pi；pi 不会收到音频文件。

- 语音附件不会下载或保存为 `.amr` / `.silk`。
- 不使用本地或外部语音识别服务。
- 如果 QQ ASR 没有返回文本，机器人会回复 `Could not transcribe this voice message; please resend or type it.`，并且不会启动 pi。
- 语音和文字是两条独立的 QQ 消息；需要时按顺序发送。
- 如果客户端允许语音消息同时携带文件或图片，它们会与转写文本一起处理。

将 `logLevel` 设为 `debug` 时，日志会包含原始 `asr_refer_text` 和附件元数据。

### 回复格式

- **思考**：独立气泡，纯文本，首行 `**💭 思考**`。长思考可能续到新气泡，并重复标题。
- **工具**：每次调用独立气泡，首行 `## <图标> <工具名>`；`bash` 命令使用 `bash` 代码块，其他参数使用行内代码；结果按工具/文件类型使用 `text`、`markdown`、`python`、`json` 等代码块。
- **普通消息**：原文，不加标题。
- **工具图标**：💻 bash、📖 read、📝 write、✏️ edit、🔎 grep、🔍 search、🗂️ find、📁 ls、📋 list、🌐 web、📡 fetch、🗑️ delete、❌ remove、✅ todo、🧪 test；未配置工具使用 🔧。
- **错误恢复**：模型错误后 60 秒内自动恢复的 run 会继续推送到原 QQ 会话；`/stop` 会等待当前工具和流收尾后再回复。

## 命令

在 pi 终端执行：

| 命令         | 说明                                   |
| ------------ | -------------------------------------- |
| `/qq-login`  | 扫码登录并绑定扫码的 QQ 用户           |
| `/qq-status` | 显示登录、连接、模型、上下文和绑定用户 |
| `/qq-logout` | 退出登录并清除凭据                     |
| `/qq-new`    | 重置当前会话（由 QQ 侧 `/new` 调用）   |

QQ 侧命令：

| 命令       | 说明                                         |
| ---------- | -------------------------------------------- |
| `/new`     | 开始新的 pi 会话                             |
| `/stop`    | 中止当前运行                                 |
| `/compact` | 压缩当前上下文                               |
| `/status`  | 显示会话和上下文状态                         |
| `/last`    | 以流式推送最近完成的回复，或重新推送当前输出 |
| `/help`    | 显示命令帮助                                 |

### 诊断

以下终端命令用于排查问题：

| 命令                 | 说明                                    |
| -------------------- | --------------------------------------- |
| `/qq-test-budget`    | 显示被动回复额度和流式气泡诊断信息      |
| `/qq-test-md [text]` | 发送 Markdown 测试消息                  |
| `/qq-test-stream`    | 运行流式时间线诊断（`resume` 验证续写） |

## 设置

创建 `~/.pi/agent/pi-qq.json` 覆盖默认值。该文件只保存设置；凭据不会写入其中。

```json
{
  "thinkingRelay": true,
  "language": "zh-cn",
  "logLevel": "silent"
}
```

| 配置项          | 默认值     | 说明                                                   |
| --------------- | ---------- | ------------------------------------------------------ |
| `thinkingRelay` | `true`     | 以本地化的 `💭` 标题纯文本回传思考；`false` 时隐藏     |
| `deliverAs`     | `"steer"`  | 忙碌时新消息的排队方式：`steer` 或 `followUp`          |
| `fileMaxSize`   | `52428800` | QQ 附件下载上限（字节，50MB）                          |
| `language`      | `"zh-cn"`  | UI 和日志语言：`zh-cn` 或 `en-us`                      |
| `logLevel`      | `"silent"` | 日志阈值：`silent`、`error`、`warn`、`info` 或 `debug` |

配置在 pi 会话启动时读取；修改文件后请重启 pi。未知配置项会被忽略，无效值会回退到默认值并给出提示。

启用日志后，日志写入 `<cwd>/.pi/pi-qq/logs/pi-qq-YYYY-MM-DD.log`。可用 `PI_QQ_LOG_DIR` 覆盖目录。`info` 只记录元数据；`debug` 记录完整事件、附件、提示词和工具参数/结果。

## 开发

```bash
npm ci
npm run lint
npm run typecheck
npm test
npm run format:check
```

扩展入口是 `index.ts`。源码按职责分组：

| 目录               | 职责                                  |
| ------------------ | ------------------------------------- |
| `src/qq/`          | QQ bot 生命周期、登录、入站处理和回复 |
| `src/relay/`       | 思考/工具/文本的流式回传              |
| `src/attachments/` | 附件下载、存储和语音转写              |
| `src/quota/`       | 被动回复额度                          |
| `src/infra/`       | 配置、日志和显示格式化                |

## 说明

- 仅支持 C2C（QQ 私聊）。
- QQ 入站消息触发的任务会回传 QQ；直接在 pi 中启动的无关任务不会推送。
- 图片会保存为文件，并通过 pi 的 `read` 工具查看；当前模型必须支持图片输入才能看到内容。
- 保存的附件位于工作区 `.pi/` 下；如有需要，请把 `.pi/` 加入你自己的项目 `.gitignore`。
- QQ 对每条入站消息的被动回复次数有限制。可用 `/qq-test-budget` 查看本地额度。
- 读取 `SKILL.md` 时，QQ 显示 `## 🧩 Skill` 和 skill 名称，不转发文件内容。

## 许可证

MIT
