@zxh126/pi-qqbot 2.4.1 → 2.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,6 +3,87 @@
3
3
  本文件记录 pi-qqbot 的版本变更。格式遵循 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),
4
4
  版本号遵循 [SemVer](https://semver.org/lang/zh-CN/)。
5
5
 
6
+ > 英文版 changelog:[CHANGELOG.en.md](CHANGELOG.en.md)
7
+
8
+ ## [2.6.2] - 2026-11-01
9
+
10
+ ### Fixed
11
+
12
+ - **`/qq …` 绕过命令门禁**:`/qq` 本身就是扩展命令,所以直接发 `/qq reload`、`/qq quit`、`/qq new`
13
+ 会落进 `routeSlash` 的 passthrough 分支,既不查 `commands.allowReload` / `allowQuit` / `allowUpdate` /
14
+ `allowSession`,也不查忙碌状态(`needsIdle`)——默认配置(`allowQuit: false`)下 QQ 侧发 `/qq quit`
15
+ 就能把主机 pi 关掉。现在 `/qq` 在 passthrough **之前**分流:`bridgeGate()` 把子命令翻回内置命令名
16
+ (多词 `list tree` / `list fork` / `list resume` 先整体匹配),复用同一套 `allowed()` / `needsIdle` 判定。
17
+ - **派发回执吞掉参数**:`/qq reload` 过去回「已派发 /qq 给 pi。」——`parseSlash` 只取首段当命令名,
18
+ 看起来像 `reload` 没被受理。现在回执与日志都显示完整命令(超 80 字截断加 `…`)。
19
+
20
+ ### Changed
21
+
22
+ - 自毁类桥接子命令(`reload` / `new` / `quit` / `clone`)改为**先回执再派发**,避免回执和
23
+ `ctx.reload()` / `newSession()` 抢时序。
24
+ - `/qq sendfile …`、`/qq usage …`、`/qq pick …` 以及未知子命令不再额外发一条通用回执(子命令自己会回),
25
+ 减少一次无意义的消息。
26
+ - 顺手清掉 `slash.ts` 里一处历史补丁残留的空行 `+`(`+reply(formatPicker(picker))`,语义上无害但脏)。
27
+
28
+ ### Compatibility
29
+
30
+ - 发 `/reload`、`/new`、`/quit`、`/compact` 等原命令的行为与门禁不变;只是 `/qq <sub>` 现在受同样约束。
31
+ - 无需改配置:原本 `allowReload: true` 的部署照旧可用;`allowQuit: false`(默认)下 `/qq quit` 变成回执拒绝。
32
+
33
+ ### Tests
34
+
35
+ - 新增 `test/qq-bridge-gate.test.mjs`(21 条断言):子命令映射、三种门禁拒绝、忙碌拒绝、
36
+ 先回执后派发顺序、多词 sub、回执截断、`/reload` 未回归。
37
+ - 全套 274 条断言 / 22 个测试文件通过;`tsc --noEmit --strict` 对改动文件 `slash.ts` 无报错。
38
+
39
+ ## [2.6.1] - 2026-11-01
40
+
41
+ ### Changed
42
+
43
+ - **changelog 分语种**:中文版更名 `CHANGELOG.zh-CN.md`,新增英文版 `CHANGELOG.en.md`(含全部历史版本);
44
+ README.md(英文)引用 `CHANGELOG.en.md`,docs/README.zh-CN.md 引用 `CHANGELOG.zh-CN.md`。
45
+
46
+ ## [2.6.0] - 2026-11-01
47
+
48
+ ### Added
49
+
50
+ - **审批与单选列表支持内联按钮**:审批卡片附带「✅ 允许一次 / ❌ 拒绝 / 🚀 以后都同意」按钮
51
+ (强制审批工具只有前两个);/tree、/fork、/resume 与模型选择列表附带选项按钮(2 列、最多 5 行,
52
+ label 超 10 字符截断,正文仍保留完整列表)。按钮只在 markdown 回复(`markdownReply=true`)时附带。
53
+ - **`INTERACTION_CREATE` 事件链路**:网关新增 `1<<26` 意图订阅与互动事件分发;`QQApi.acknowledgeInteraction`
54
+ 在 5 秒窗口内 `PUT /interactions/{id}` 回应(命中 ACK 0,过期/越权/越界 ACK 3 重复操作)。
55
+ - **`qq/keyboard.ts`**:键盘构建与 `button_data` 编解码(顶层 `t` 字段路由 approval / pick)。
56
+
57
+ ### Changed
58
+
59
+ - `QQApi.sendC2C` 的 `SendOptions.keyboard` 只随 markdown 消息发送;markdown 失败回退纯文本时自动丢弃键盘。
60
+ - `replyTo` / `bridgeReply` 增加可选 `keyboard` 透传;选择按钮点击复用「回复编号」的 `/qq pick` 派发路径,
61
+ 审批按钮点击复用 `Approver.confirm` / `allowAll`,文本回复(同意/拒绝 N、编号)全部保留为兜底。
62
+
63
+ ### Compatibility
64
+
65
+ - `markdownReply=false` 时行为与之前完全一致(键盘不附带、纯文本回复)。
66
+ - 自定义按钮需 QQ 开放平台「内邀开通」;未开通时按钮不渲染,编号/文本回复不受影响。
67
+
68
+ ## [2.5.0] - 2026-11-01
69
+
70
+ ### Added
71
+
72
+ - **`markdownReply` 配置生效**:置 `true` 后回复统一走 QQ markdown 消息(`msg_type=2`),GFM 表格、
73
+ 代码块等由 QQ 客户端渲染。此前该配置只被解析、从未被消费。发送失败(如无 markdown 权限)由
74
+ `qq/api.ts` 自动回退纯文本,行为安全。
75
+ - **`util/chunk.ts` 切分器防拆表格**:`splitText` 把 GFM 表格整块保留(表格行先入缓冲、遇到非表格行
76
+ 再整块提交),代码块边界不作切分点;长回复不再出现被拦腰截断的破表。
77
+
78
+ ### Changed
79
+
80
+ - 回复格式由 `state.cfg.markdownReply` 决定:`replyTo` 的 `format` 默认值改为 `auto`,最终答案、
81
+ 错误通知、审批提示与各回执统一生效;仍可显式传 `"text"` 强制纯文本。
82
+
83
+ ### Compatibility
84
+
85
+ - `markdownReply` 默认仍为 `false`,未开启时行为与之前完全一致。
86
+
6
87
  ## [2.4.1] - 2026-11-01
7
88
 
8
89
  ### Added
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 pi-qqbot contributors
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 pi-qqbot contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -12,9 +12,9 @@ Bridge the **[Pi](https://pi.dev) coding agent** into QQ private (C2C) chats: se
12
12
  |---|---|
13
13
  | **Chat-driven agent** | QQ private messages are fed into the current pi session; the assistant's answer is sent back to QQ (long replies are split automatically). |
14
14
  | **Message queue** | While a task is running, further QQ messages are **queued** instead of rejected; each one starts automatically as soon as the current run settles. `/cancel` is the exception — it acts immediately and clears the queue. |
15
- | **Remote approval** | Before pi runs `bash` / `powershell` / `write` / `edit` — or any tool annotated `destructiveHint` — the bot asks in QQ first. |
15
+ | **Remote approval** | Before pi runs `bash` / `powershell` / `write` / `edit` — or any tool annotated `destructiveHint` — the bot asks in QQ first. With `markdownReply: true` the card carries inline buttons; text replies work either way. |
16
16
  | **Remote-context notice** | The first QQ message of a session is really handed to pi (loading the extension or chatting locally in the TUI adds nothing), then the bot tells it the conversation comes from a remote QQ user and that the machine may be locked: desktop screenshots / screen OCR / UI automation / headed browsers are off by default, images must be rendered off-screen and sent with `qq_send_file`. Re-injected after context compaction. |
17
- | **Session-scoped "always allow"** | Reply 「以后都同意」 and every approval request in the **current session** is auto-approved, including the one already waiting. |
17
+ | **Session-scoped "always allow"** | Tap 🚀 以后都同意 on the approval card (or reply 「以后都同意」) and every approval request in the **current session** is auto-approved, including the one already waiting. |
18
18
  | **Single-instance lock** | Only one pi process hosts the QQ gateway; the others stand by and take over automatically when the host exits. |
19
19
  | **Cancel a running task** | Send `/cancel` (or 「取消」/「停止」) and the current run is aborted immediately — the QQ reply, the pending tool calls, the half-written answer and the pending queue are all cleaned up. |
20
20
  | **Slash-command routing** | QQ messages starting with `/` are routed: pi extension commands, prompt templates and `/skill:name` are dispatched to pi; a set of pi built-ins (`/reload`, `/new`, `/compact`, `/session`, `/name`, `/model`, `/thinking`, `/quit`) is taken over; host-only commands are answered instead of being fed to the model. |
@@ -90,7 +90,7 @@ Then create `~/.pi/agent/pi-qqbot.json`:
90
90
  | `appId` / `appSecret` | required | Credentials of your QQ Open Platform bot |
91
91
  | `sandbox` | `false` | Set `true` while the bot is not yet published (sandbox environment) |
92
92
  | `allowlist` | `[]` | openid allowlist; empty means no restriction |
93
- | `markdownReply` | `false` | QQ private chat does not support markdown, keep `false` |
93
+ | `markdownReply` | `false` | Reply with QQ markdown messages (msg_type=2) so GFM tables render; approval and selection lists then carry inline buttons (custom keyboards need platform enablement). Needs the bot's markdown permission; falls back to plain text on send failure |
94
94
  | `streaming` | `false` | Reserved, streaming is not enabled in v1 |
95
95
  | `approve.tools` | `["bash","powershell","write","edit"]` | Tools that require approval; tools annotated `destructiveHint` are always gated too |
96
96
  | `approve.remember` | `true` | Remember the decision per tool inside the session (no more prompts for that tool) |
@@ -255,10 +255,10 @@ Any message that starts with `/` is routed before it can become a chat prompt:
255
255
  | `/name [name]` | get or set the session name | `commands.allowSession` |
256
256
  | `/model [provider/model]` | list available models or switch | `commands.allowSession` |
257
257
  | `/thinking [level]` | get or set the thinking level | `commands.allowSession` |
258
- | `/tree` | list branch points (numbered), answer with a number to jump there | `commands.allowSession` |
259
- | `/fork` | list earlier user messages (numbered), answer with a number to fork from it | `commands.allowSession` |
258
+ | `/tree` | list branch points (numbered), answer with a number or tap the button to jump there | `commands.allowSession` |
259
+ | `/fork` | list earlier user messages (numbered), answer with a number or tap the button to fork from it | `commands.allowSession` |
260
260
  | `/clone` | duplicate the current session at its current leaf | `commands.allowSession` |
261
- | `/resume` | list the project's other sessions (numbered), answer with a number to switch | `commands.allowSession` |
261
+ | `/resume` | list the project's other sessions (numbered), answer with a number or tap the button to switch | `commands.allowSession` |
262
262
  | `/quit` | shuts the host pi down | `commands.allowQuit` (default **false**) |
263
263
  | `/update` | updates pi-qqbot from npm (`pi update`) and reloads itself — see [Self-update](#self-update-from-qq) | `commands.allowUpdate` (default **false**) |
264
264
 
@@ -297,11 +297,20 @@ Guards:
297
297
  - Prompt templates and `/skill:name` start an agent run, so while a task is in progress they are
298
298
  **queued** like ordinary messages (see [Queueing while busy](#queueing-while-busy)). Extension commands
299
299
  that manage their own LLM interaction still run immediately.
300
- - Selection commands (`/tree`, `/fork`, `/resume`) answer with a numbered list of at most 10 entries, scoped to
300
+ - Selection commands (`/tree`, `/fork`, `/resume`) answer with a numbered list of at most 10 entries (with tap-to-select buttons when `markdownReply` is on), scoped to
301
301
  the user who asked and valid for 10 minutes. The next bare number from that user executes the choice; anything
302
302
  else (or 「取消」) abandons it. A number without a pending list is ordinary chat.
303
303
  - `/reload` is off by default: it restarts the bot, and if the new code fails to load the bot stays
304
304
  down until someone fixes it on the host. Turn it on with `commands.allowReload: true`.
305
+ - Sending the bridge command yourself (`/qq reload`, `/qq quit`, `/qq new`, `/qq list fork`, …) hits the
306
+ **same** gates: the sub-command is mapped back to its built-in name first, so `commands.allowReload` /
307
+ `allowQuit` / `allowUpdate` / `allowSession` and the busy check all apply. Routing it as a plain
308
+ extension command would have bypassed every gate — with `allowQuit: false`, `/qq quit` could shut the
309
+ host pi down. Destructive sub-commands (`reload`, `new`, `quit`, `clone`) answer *before* they are
310
+ dispatched, because they replace the runtime they would answer through.
311
+ - Dispatch receipts echo the **whole** command (`已派发 /deploy prod 给 pi。`) instead of only the first
312
+ word, truncated at 80 characters with `…`. Previously `/qq reload` was answered with
313
+ `已派发 /qq 给 pi。`, which looked like the argument had been dropped.
305
314
  - Why the indirection: pi only dispatches **extension** commands from a message
306
315
  (`prompt()` → `_tryExecuteExtensionCommand`), and `reload()` / `newSession()` are command-context
307
316
  APIs. So this extension registers `/qq` and forwards `/reload` → `/qq reload`. Built-in names are
@@ -481,7 +490,11 @@ This is pi's own update path, not a re-implementation: `npm view @zxh126/pi-qqbo
481
490
  要本会话内全部放行,回复「以后都同意」(可用「取消自动同意」关闭)。
482
491
  ```
483
492
 
484
- Reply vocabulary (approval keywords are Chinese, with English aliases):
493
+ With `markdownReply: true` the card carries ✅ 允许一次 / ❌ 拒绝 / 🚀 以后都同意 buttons — tapping one
494
+ is the same as replying. Strict tools (`qq_send_file`) show only the first two buttons. Text replies keep
495
+ working either way.
496
+
497
+ Reply vocabulary (approval keywords are Chinese, with English aliases; the card buttons are the faster path when they are shown):
485
498
 
486
499
  | Intent | Reply |
487
500
  |---|---|
@@ -521,7 +534,7 @@ Debug logs are written to `~/.pi/agent/pi-qqbot-<name>.log` (one file per bot, s
521
534
 
522
535
  ## Changelog
523
536
 
524
- See [CHANGELOG.md](./CHANGELOG.md) — every release with what changed and why. The progress-report
537
+ See [CHANGELOG.en.md](./CHANGELOG.en.md) — every release with what changed and why. (中文:[CHANGELOG.zh-CN.md](./CHANGELOG.zh-CN.md)) The progress-report
525
538
  pause/resume (`/progress off` / `on`) landed in **1.5.0**; multi-bot auto-claiming in **2.0.0**;
526
539
  receiving QQ images/files (saved to temp files and handed to pi as `图片路径 <path>`) in **2.1.0**;
527
540
  sending local files to QQ (`qq_send_file` / `/sendfile`, chunked rich-media upload) in **2.2.0**.
package/approver.ts CHANGED
@@ -99,8 +99,8 @@ export class Approver {
99
99
  render(seq: number, opts: { strict?: boolean } = {}): string {
100
100
  const item = this.pending.get(seq);
101
101
  const hint = opts.strict
102
- ? `回「同意 ${seq}」或「拒绝 ${seq}」,需单独确认。`
103
- : `回「同意 ${seq}」或「拒绝 ${seq}」;想全部放行回「以后都同意」。`;
102
+ ? `点下方按钮,或回「同意 ${seq}」/「拒绝 ${seq}」,需单独确认。`
103
+ : `点下方按钮,或回「同意 ${seq}」/「拒绝 ${seq}」;想全部放行回「以后都同意」。`;
104
104
  return [`[审批 #${seq}] ${item?.summary ?? ""}`, hint].join("\n");
105
105
  }
106
106