@zxh126/pi-qqbot 1.4.0 → 2.0.0
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.
- package/CHANGELOG.md +171 -0
- package/README.md +41 -10
- package/config.ts +158 -21
- package/constants.ts +5 -0
- package/docs/README.zh-CN.md +36 -9
- package/docs/accounts.md +88 -0
- package/index.ts +318 -54
- package/lock.ts +204 -0
- package/logger.ts +23 -2
- package/package.json +3 -2
- package/qq/auth.ts +10 -2
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
本文件记录 pi-qqbot 的版本变更。格式遵循 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),
|
|
4
|
+
版本号遵循 [SemVer](https://semver.org/lang/zh-CN/)。
|
|
5
|
+
|
|
6
|
+
## [2.0.0] - 2026-10-25
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- **多 QQ bot(一份配置、一台机器、多个 pi 进程)**:配置里用 `qqbots: [...]` 写多组
|
|
11
|
+
`appId` / `appSecret`,支持 `name`、以及逐账号覆盖 `sandbox` / `allowlist` / `queue` /
|
|
12
|
+
`progress` / `approve` / `commands`(未写的继承全局默认值,逐键合并)。
|
|
13
|
+
- **自动抢占**:每个 pi 进程启动时(`session_start`)按配置顺序找第一个**未上线**的 bot 接入;
|
|
14
|
+
抢不到的进程不接入 QQ、但每 5 秒重扫,别人退出后接管。一进程一把锁,绝不并发多接。
|
|
15
|
+
- **「不抢别人 reload 中临时释放的 bot」**:会话重建(`/reload`、`/new`、`/fork`、`/resume`)时,
|
|
16
|
+
释放锁**之前**写 20 秒预约窗口,其他进程跳过该 bot;抢锁后还会二次读预约记录,
|
|
17
|
+
撞上就让出(关掉竞态窗口)。新 runtime 用同一 pid 认回自己的 bot,静默不打扰。
|
|
18
|
+
- **身份可见**:接入日志带 bot 名称 / appId / cwd / pid;QQ 首条回执前加「(已接入 qqbot「work」)」;
|
|
19
|
+
`/status` 首行显示 `bot: work (…0143) · cwd: …`。
|
|
20
|
+
- **换进程接管提示**:`~/.pi/agent/pi-qqbot-<appId>.owner.json` 记录上一位持有者的 pid 与工作目录,
|
|
21
|
+
换进程时日志写新旧 pid/cwd,QQ 首条回执带「(qqbot「work」已由新实例接管 · 当前工作目录 …)」。
|
|
22
|
+
- `PI_QQBOT_ACCOUNT=<name|appId>` 钉选账号(调试/固定目录用),名字不存在则不接入并写日志。
|
|
23
|
+
- **抢占后先验凭据**:抢到锁立即换一次 `access_token`,失败就**放锁**并跳过该 bot(继续试下一个),
|
|
24
|
+
避免「凭据写错的 bot 占着锁位但永远连不上」;全部不可用时不空转重扫。
|
|
25
|
+
- **修复** `AuthManager`:QQ 平台对无效凭据返回 HTTP 200 + `code 100016`,以前只拿空 token 去连网关,
|
|
26
|
+
现在直接抛错(带 code/message),并在抢占阶段被发现。
|
|
27
|
+
- 新增文档 [docs/accounts.md](docs/accounts.md);新增测试 `test/config.test.mjs`(9)、
|
|
28
|
+
`test/lock.test.mjs`(8,真子进程 + 真 lockfile,含「同一工作目录也能各接一个」)、
|
|
29
|
+
`test/accounts.test.mjs`(9,端到端抢占/提示/凭据无效降级)、`test/auth.test.mjs`(3)。
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
|
|
33
|
+
- 日志按账号分文件:`~/.pi/agent/pi-qqbot-<name>.log`(同机多进程不再交错);
|
|
34
|
+
单 bot 旧格式仍写 `pi-qqbot.log`。
|
|
35
|
+
- `/status` 增加 bot 身份与工作目录行;`/help` 增加多 bot 说明。
|
|
36
|
+
- 配置加载改为 `loadAccounts()`(返回按顺序排好的账号列表),`loadConfig()` 保留为兼容包装。
|
|
37
|
+
|
|
38
|
+
### Compatibility
|
|
39
|
+
|
|
40
|
+
- **旧的单 bot 配置零改动可用**(等价于名为 `default` 的账号,行为与 1.5.0 一致);
|
|
41
|
+
`QQ_BOT_APPID` / `QQ_BOT_APPSECRET` 仍只用于单 bot 场景。
|
|
42
|
+
- 同时写顶层 `appId` 与 `qqbots` → 报错不接入(歧义不猜);单个账号凭据缺失/`appId` 重复/`name`
|
|
43
|
+
重复 → 跳过该条并写日志,其余照常。
|
|
44
|
+
- **bot 与工作目录无绑定**:分配只看配置顺序与在线状态,与 cwd 无关——同一目录开多个 pi
|
|
45
|
+
也照样一人接一个 bot。cwd 仅用于日志、接管提示与 `/status` 展示。
|
|
46
|
+
- 升级后建议重启(或 `/reload`)一次:新版本启动时才会按账号分文件写日志。
|
|
47
|
+
|
|
48
|
+
## [1.5.0] - 2026-10-25
|
|
49
|
+
|
|
50
|
+
### Added
|
|
51
|
+
|
|
52
|
+
- **暂停 / 恢复自动进度汇报(会话内有效)**:`/progress off`(别名 `/p pause`、`暂停`、`stop`、
|
|
53
|
+
「暂停汇报」「关闭自动汇报」)停掉定时推送,`/progress on`(别名 `resume`、`start`、`恢复`、
|
|
54
|
+
「恢复汇报」「继续汇报」)重新打开。
|
|
55
|
+
- 手动暂停跨 run 保留(`agent_start` 不再顺手复位),只在 `session_start`(`/new`、`/reload`、重启)复位为默认开启。
|
|
56
|
+
- 手动暂停期间,`/progress`、「进度」、「当前进度」、「现在进度」、「进展」**只回一次当前摘要**,
|
|
57
|
+
不会偷偷把 15 s 定时器打开,回复末尾附「(自动汇报已暂停,发 /progress on 恢复)」提示。
|
|
58
|
+
- 与原有的「配额静默」严格区分:配额静默是发送失败后的自动暂停,问一次进度即恢复;
|
|
59
|
+
手动暂停只有 `on`/「恢复汇报」能解除。
|
|
60
|
+
- `/progress <看不懂的参数>` 回用法提示,不再静默忽略。
|
|
61
|
+
- `/status` 新增 `自动汇报:` 一行,区分四种模式:`开启(每 Ns)` / `已暂停(本会话…)` /
|
|
62
|
+
`推送失败已静默` / `未启用(配置 progress.enabled=false)`。
|
|
63
|
+
- 裸文本口令「当前进度」「现在进度」「目前进展」(可带子命令,如「进度 暂停」)也走进度查询。
|
|
64
|
+
- 测试:`test/progress.test.mjs` 新增 7 个用例(暂停/恢复即时生效、跨 run 保留、暂停后手动查看不解除、
|
|
65
|
+
中文口令且不误伤任务句、空闲时问「当前进度」、`/status` 文案、未知子命令)。
|
|
66
|
+
|
|
67
|
+
### Changed
|
|
68
|
+
|
|
69
|
+
- `/progress`(无参数)在**手动暂停**状态下不再解除暂停(此前任何一次问询都会清掉暂停标志)。
|
|
70
|
+
- 「推送失败已静默」与「用户手动暂停」拆成两个独立状态(`progressPaused` / `progressMuted`),
|
|
71
|
+
互不覆盖。
|
|
72
|
+
- README(英文)与 `docs/README.zh-CN.md` 的指令表、进度小节、`progress.enabled` 说明同步更新。
|
|
73
|
+
|
|
74
|
+
## [1.4.0] - 2026-10-24
|
|
75
|
+
|
|
76
|
+
### Added
|
|
77
|
+
|
|
78
|
+
- 任务运行期间按 `progress.intervalMs`(默认 15 s)推送进度摘要:轮次、工具调用次数与前三名、
|
|
79
|
+
失败次数、最近完成的工具(含耗时)、正在跑的工具、token 用量。全部由本地事件拼装,
|
|
80
|
+
不调用模型、不消耗 token。
|
|
81
|
+
- 全链路敏感信息脱敏(`redact.ts`):进度摘要、审批提示、队列预览、日志落盘统一过一遍,
|
|
82
|
+
覆盖 `Authorization` / QQ bot token / `sk-…` / `ghp_…` / `npm_…` / `AKIA…` / JWT / PEM /
|
|
83
|
+
`token=` / `password=` / `--api-key` / URL `user:pass@` / 高熵串。
|
|
84
|
+
- `/progress`(别名 `/p`、「进度」)与忙碌时的「到哪了」「怎么样了」问询。
|
|
85
|
+
- 最终答案因配额发不出去时缓存,下条消息以「(上一条回复发送失败,补发如下)」补发。
|
|
86
|
+
- 配置 `progress.enabled` / `progress.intervalMs`(环境变量 `PI_QQBOT_PROGRESS_MS` 可覆盖)。
|
|
87
|
+
|
|
88
|
+
## [1.3.3] - 2026-10-24
|
|
89
|
+
|
|
90
|
+
### Fixed
|
|
91
|
+
|
|
92
|
+
- `/usage` 报告读回改为轮询等待:`appendEntry` 晚于命令 `await` 返回,导致偶发读不到刚写入的报告。
|
|
93
|
+
|
|
94
|
+
## [1.3.2] - 2026-10-24
|
|
95
|
+
|
|
96
|
+
### Fixed
|
|
97
|
+
|
|
98
|
+
- `/usage` 读回兜底:`getEntries` 尚未刷新时直接读会话文件尾部;`generatedAt` 兼容毫秒时间戳。
|
|
99
|
+
|
|
100
|
+
## [1.3.1] - 2026-10-24
|
|
101
|
+
|
|
102
|
+
### Fixed
|
|
103
|
+
|
|
104
|
+
- `/usage rate` 真正执行并读回配置文件确认,不再「口头成功」。
|
|
105
|
+
|
|
106
|
+
## [1.3.0] - 2026-10-24
|
|
107
|
+
|
|
108
|
+
### Added
|
|
109
|
+
|
|
110
|
+
- `/usage`、`/cost`(只往 `ctx.ui.*` 输出的扩展命令)桥接回 QQ,不再沉默。
|
|
111
|
+
- `/mcp`、`/llama` 这类需要主机 TUI 选择框/登录的命令改为明确回执说明。
|
|
112
|
+
|
|
113
|
+
## [1.2.1] - 2026-10-24
|
|
114
|
+
|
|
115
|
+
### Changed
|
|
116
|
+
|
|
117
|
+
- 日志时间戳改为本地时间并带 UTC 偏移(如 `[2026-10-24T07:39:09.762+08:00]`),与文件 mtime、
|
|
118
|
+
进程启动时间直接对齐,不再需要心算时区。
|
|
119
|
+
|
|
120
|
+
## [1.2.0] - 2026-10-24
|
|
121
|
+
|
|
122
|
+
### Added
|
|
123
|
+
|
|
124
|
+
- 忙碌时收到的消息进入队列,当前任务 settle 后自动续跑;`/cancel` 永远不入队、立即生效。
|
|
125
|
+
- `/queue`(别名 `/q`)查看队列,`/queue clear`、`/queue drop <编号>` 管理队列。
|
|
126
|
+
- 配置 `queue.max`(0 = 关闭排队)、`queue.ack`。
|
|
127
|
+
|
|
128
|
+
## [1.1.0] - 2026-10-24
|
|
129
|
+
|
|
130
|
+
### Added
|
|
131
|
+
|
|
132
|
+
- `/tree`、`/fork`、`/resume` 的编号选择器接管:回一个最多 10 项的列表,只对发出者有效、10 分钟过期;
|
|
133
|
+
- `/clone` 支持。
|
|
134
|
+
|
|
135
|
+
## [1.0.0] - 2026-10-24
|
|
136
|
+
|
|
137
|
+
### Added
|
|
138
|
+
|
|
139
|
+
- 接管 pi 的斜杠命令(白名单 `commands.passthrough` / `allowReload` / `allowQuit` / `allowSession`):
|
|
140
|
+
`/reload`、`/new`、`/compact`、`/session`、`/name`、`/model`、`/thinking`、`/quit`,
|
|
141
|
+
以及扩展命令、prompt 模板、`/skill:name` 的透传。
|
|
142
|
+
- 有任务在跑时,`/reload`、`/new`、`/compact`、`/quit`、`/tree`、`/fork`、`/clone`、`/resume` 拒绝执行。
|
|
143
|
+
|
|
144
|
+
## [0.3.0] - 2026-10-24
|
|
145
|
+
|
|
146
|
+
### Added
|
|
147
|
+
|
|
148
|
+
- `/cancel`(别名 `/stop`、`/abort`、「取消」「停止」「中止」「终止」「算了」)中断正在运行的任务:
|
|
149
|
+
先终止待审批、清空队列,再 `ctx.abort()`,丢弃半截回复;8 秒看门狗兜底释放忙碌标记。
|
|
150
|
+
|
|
151
|
+
## [0.2.1] - 2026-10-24
|
|
152
|
+
|
|
153
|
+
### Changed
|
|
154
|
+
|
|
155
|
+
- 中文 README 移入 `docs/README.zh-CN.md`,修复 npm 页面默认 readme 被中文版顶掉。
|
|
156
|
+
|
|
157
|
+
## [0.2.0] - 2026-10-24
|
|
158
|
+
|
|
159
|
+
### Added
|
|
160
|
+
|
|
161
|
+
- 会话级「以后都同意」自动放行(`approve.remember`),含已在等待的那一条;
|
|
162
|
+
「取消自动同意」「恢复审批」等**长得像取消**的说法仍走审批分支。
|
|
163
|
+
- 中英文双语 README。
|
|
164
|
+
|
|
165
|
+
## [0.1.0] - 2026-10-16
|
|
166
|
+
|
|
167
|
+
### Added
|
|
168
|
+
|
|
169
|
+
- 初始版本:把 Pi coding agent 接入 QQ 单聊(C2C),单会话 + `proper-lockfile` 单实例锁(宿主接管)、
|
|
170
|
+
危险工具(`bash` / `powershell` / `write` / `edit` 及 `destructiveHint` 工具)执行前远程审批、
|
|
171
|
+
长回复自动分段。
|
package/README.md
CHANGED
|
@@ -17,9 +17,10 @@ Bridge the **[Pi](https://pi.dev) coding agent** into QQ private (C2C) chats: se
|
|
|
17
17
|
| **Single-instance lock** | Only one pi process hosts the QQ gateway; the others stand by and take over automatically when the host exits. |
|
|
18
18
|
| **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. |
|
|
19
19
|
| **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. |
|
|
20
|
-
| **Progress reports** | While a run is in progress the bot posts a short progress summary (turns, tool calls, the tool currently running, tokens) every `progress.intervalMs` (default **15 s**). Ask at any time with `/progress` (aliases `/p`, 「进度」). If a report cannot be delivered the automatic reports stay quiet until you ask again. |
|
|
20
|
+
| **Progress reports** | While a run is in progress the bot posts a short progress summary (turns, tool calls, the tool currently running, tokens) every `progress.intervalMs` (default **15 s**). Ask at any time with `/progress` (aliases `/p`, 「进度」). `/progress off` pauses the automatic reports for the rest of the session, `/progress on` brings them back. If a report cannot be delivered the automatic reports stay quiet until you ask again. |
|
|
21
21
|
| **Secret redaction** | Anything leaving the machine is scrubbed first — progress reports, approval prompts, queue previews and the log file: `Authorization` headers (incl. QQ bot tokens), `sk-…` / `ghp_…` / `npm_…` / `AKIA…` / JWTs / PEM blocks, `token=` / `password=` / `--api-key` values, URL `user:pass@`, and long high-entropy strings. |
|
|
22
|
-
| **
|
|
22
|
+
| **Multi-bot on one machine** | Put several QQ bots in one config (`qqbots: [...]`, see [docs/accounts.md](./docs/accounts.md)). Each pi process, in config order, claims the first bot that is not online yet and then verifies its credentials (a wrong secret releases the lock instead of squatting the slot); a process that finds every bot taken stays idle and rescans every 5 s, taking over as soon as one goes offline. A bot whose owner is merely reloading is never snatched (a 20 s reservation is left behind). The bot name is logged, shown in `/status` and prepended to the first QQ reply; taking over from another process also tells you the new working directory. |
|
|
23
|
+
| **Built-in commands** | `/status`, `/queue`, `/progress` (+ `/progress off` / `on`), `/reset`, `/cancel`, `/help`. |
|
|
23
24
|
|
|
24
25
|
## Install
|
|
25
26
|
|
|
@@ -63,12 +64,29 @@ Then create `~/.pi/agent/pi-qqbot.json`:
|
|
|
63
64
|
| `approve.tools` | `["bash","powershell","write","edit"]` | Tools that require approval; tools annotated `destructiveHint` are always gated too |
|
|
64
65
|
| `approve.remember` | `true` | Remember the decision per tool inside the session (no more prompts for that tool) |
|
|
65
66
|
| `commands` | see below | Slash-command routing gates: `passthrough`, `allowReload`, `allowQuit`, `allowSession` |
|
|
66
|
-
| `progress.enabled` | `true` | Post periodic progress reports while a task is running |
|
|
67
|
+
| `progress.enabled` | `true` | Post periodic progress reports while a task is running (read at session start — restart or `/new` after changing; at runtime use `/progress off`) |
|
|
67
68
|
| `progress.intervalMs` | `15000` | Interval of those reports (env override: `PI_QQBOT_PROGRESS_MS`) |
|
|
68
69
|
| `queue.max` | `20` | How many messages may wait while a task runs; `0` disables queueing (busy messages are refused as before) |
|
|
69
70
|
| `queue.ack` | `true` | Reply to QQ when a queued [message](#queueing-while-busy) starts (the "queued" ack is always sent) |
|
|
70
71
|
|
|
71
|
-
Credentials can also come from the `QQ_BOT_APPID` / `QQ_BOT_APPSECRET` environment variables.
|
|
72
|
+
Credentials can also come from the `QQ_BOT_APPID` / `QQ_BOT_APPSECRET` environment variables (single-bot setup only).
|
|
73
|
+
|
|
74
|
+
### Several bots (2.0)
|
|
75
|
+
|
|
76
|
+
```jsonc
|
|
77
|
+
{
|
|
78
|
+
"qqbots": [
|
|
79
|
+
{ "name": "work", "appId": "1903340143", "appSecret": "…", "sandbox": true },
|
|
80
|
+
{ "name": "home", "appId": "1020123456", "appSecret": "…" }
|
|
81
|
+
],
|
|
82
|
+
"queue": { "max": 20, "ack": true } // still the global defaults, per-bot overrides allowed
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Every pi process claims the first bot (in config order) that is not online; spare processes stand by and rescan
|
|
87
|
+
every 5 s. After claiming, the credentials are verified by fetching an `access_token`: a bot with a wrong
|
|
88
|
+
`appId`/`appSecret` releases its lock and is skipped instead of squatting the slot. `PI_QQBOT_ACCOUNT=<name|appId>`
|
|
89
|
+
pins one bot instead of auto-claiming. Full details: [docs/accounts.md](./docs/accounts.md).
|
|
72
90
|
|
|
73
91
|
## Usage
|
|
74
92
|
|
|
@@ -77,9 +95,11 @@ Start pi, then message the bot in QQ; pi handles it and replies.
|
|
|
77
95
|
| Command | Description |
|
|
78
96
|
|---|---|
|
|
79
97
|
| `/reset` | Clear the reply buffer, approval memory and session-wide auto-approve (**does not** switch sessions — send `/new` for that) |
|
|
80
|
-
| `/status` | Show busy/idle state, queue length, auto-approve state
|
|
98
|
+
| `/status` | Show the bot this process serves (name + working directory), busy/idle state, queue length, auto-approve state, pending approval count and the progress-report mode |
|
|
81
99
|
| `/queue` | Show the queue (aliases: `/q`); `/queue clear` empties it, `/queue drop <id>` removes one entry |
|
|
82
|
-
| `/progress` | **Show the current progress summary immediately**
|
|
100
|
+
| `/progress` | **Show the current progress summary immediately** (aliases: `/p`, 「进度」, 「当前进度」, 「进展」; plain-text questions like 「到哪了」「怎么样了」 work while busy). While you paused reports yourself it only replies once — use `/progress on` to resume; while reports are merely quota-quiet, asking resumes them |
|
|
101
|
+
| `/progress off` | **Pause automatic progress reports for the rest of the session** (aliases: `pause`, `stop`, `暂停`, 「暂停汇报」, 「关闭汇报」). Manual `/progress` still works, it just no longer resumes the timer |
|
|
102
|
+
| `/progress on` | **Resume automatic progress reports** (aliases: `resume`, `start`, `恢复`, 「恢复汇报」, 「继续汇报」) |
|
|
83
103
|
| `/cancel` | **Interrupt the task that is running right now** and empty the queue (aliases: `/stop`, `/abort`, 「取消」, 「停止」, 「中止」, 「终止」, 「算了」) |
|
|
84
104
|
| `/help` | Show help |
|
|
85
105
|
|
|
@@ -197,10 +217,14 @@ While a run is in progress, a summary is pushed to QQ every `progress.intervalMs
|
|
|
197
217
|
- **Quota-aware**: one QQ message buys only 4 passive replies, so a long run will eventually run out.
|
|
198
218
|
The first failed send stops automatic reports for that run (no retry spam); send `/progress`
|
|
199
219
|
(or 「进度」) and they resume.
|
|
220
|
+
- **Manual pause / resume**: `/progress off` (or 「暂停汇报」) silences the reports for the rest of the
|
|
221
|
+
session — it survives `agent_settled` and the next run, and asking for `/progress` no longer turns it
|
|
222
|
+
back on. `/progress on` (or 「恢复汇报」) resumes. A new session (`/new`, restart) starts enabled again.
|
|
223
|
+
`/status` shows which of the four modes you are in (on / paused-by-you / quota-quiet / disabled).
|
|
200
224
|
- **The final answer is protected**: if it cannot be delivered (quota), it is cached and re-sent with a
|
|
201
225
|
`(上一条回复发送失败,补发如下)` prefix as soon as you send the next message.
|
|
202
226
|
- Timer is cleared on `agent_settled` / `/cancel` / session shutdown.
|
|
203
|
-
- Set `progress.enabled: false` to turn the whole thing off.
|
|
227
|
+
- Set `progress.enabled: false` to turn the whole thing off (across sessions; read at session start).
|
|
204
228
|
|
|
205
229
|
### Cancelling a run
|
|
206
230
|
|
|
@@ -253,8 +277,10 @@ Semantics:
|
|
|
253
277
|
The QQ gateway keeps only one active connection per bot. The extension uses a `proper-lockfile` lock at `~/.pi/agent/pi-qqbot-<appId>.lock`:
|
|
254
278
|
|
|
255
279
|
- the process that acquires the lock becomes the host and connects to QQ;
|
|
256
|
-
- other processes stand by
|
|
257
|
-
- when the host exits or crashes (30 second heartbeat timeout), a standby process takes over
|
|
280
|
+
- other processes stand by, retry every 5 seconds, and claim **any** bot of the list that is free;
|
|
281
|
+
- when the host exits or crashes (30 second heartbeat timeout), a standby process takes over;
|
|
282
|
+
- a host that is only rebuilding its session (`/reload`, `/new`, `/fork`, `/resume`) writes a 20 second reservation next to the lock, so nobody else grabs its bot in the gap;
|
|
283
|
+
- the last owner (pid + working directory) is kept in `~/.pi/agent/pi-qqbot-<appId>.owner.json` and is used to tell you that a **new process** took over, and from which directory.
|
|
258
284
|
|
|
259
285
|
## Prerequisites
|
|
260
286
|
|
|
@@ -264,7 +290,12 @@ The QQ gateway keeps only one active connection per bot. The extension uses a `p
|
|
|
264
290
|
|
|
265
291
|
## Logs
|
|
266
292
|
|
|
267
|
-
Debug logs are written to `~/.pi/agent/pi-qqbot.log`. Each line starts with a **local** timestamp that carries the UTC offset, e.g. `[2026-10-24T07:39:09.762+08:00]`, so it lines up with file mtimes and process start times without mental arithmetic.
|
|
293
|
+
Debug logs are written to `~/.pi/agent/pi-qqbot-<name>.log` (one file per bot, so several pi processes on the same machine never interleave). A single-bot v1 config keeps writing `pi-qqbot.log`. Each line starts with a **local** timestamp that carries the UTC offset, e.g. `[2026-10-24T07:39:09.762+08:00]`, so it lines up with file mtimes and process start times without mental arithmetic.
|
|
294
|
+
|
|
295
|
+
## Changelog
|
|
296
|
+
|
|
297
|
+
See [CHANGELOG.md](./CHANGELOG.md) — every release with what changed and why. The progress-report
|
|
298
|
+
pause/resume (`/progress off` / `on`) landed in **1.5.0**; multi-bot auto-claiming in **2.0.0**.
|
|
268
299
|
|
|
269
300
|
## License
|
|
270
301
|
|
package/config.ts
CHANGED
|
@@ -41,41 +41,178 @@ export interface QQBotConfig {
|
|
|
41
41
|
progress: ProgressConfig;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
/**
|
|
45
|
+
* 配置里的一个 QQ bot。
|
|
46
|
+
*
|
|
47
|
+
* 除凭据外,所有字段都是「对全局默认值的逐键覆盖」——没写的就继承顶层
|
|
48
|
+
* `queue` / `progress` / `approve` / `commands` / `sandbox` / `allowlist`。
|
|
49
|
+
*/
|
|
50
|
+
export interface QQBotsEntry {
|
|
51
|
+
/** 展示名(日志、/status、抢占提示都用它);缺省 = `bot-<appId 后 6 位>` */
|
|
52
|
+
name?: string;
|
|
53
|
+
appId: string;
|
|
54
|
+
appSecret: string;
|
|
55
|
+
sandbox?: boolean;
|
|
56
|
+
allowlist?: string[];
|
|
57
|
+
markdownReply?: boolean;
|
|
58
|
+
streaming?: boolean;
|
|
59
|
+
toolProgress?: boolean;
|
|
60
|
+
approve?: Partial<ApproveConfig>;
|
|
61
|
+
commands?: Partial<CommandConfig>;
|
|
62
|
+
queue?: Partial<QueueConfig>;
|
|
63
|
+
progress?: Partial<ProgressConfig>;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** 校验并归一化后的账号(凭据 + 合并好的完整配置) */
|
|
67
|
+
export interface QQBotAccount {
|
|
68
|
+
name: string;
|
|
69
|
+
appId: string;
|
|
70
|
+
/** 该账号最终生效的配置:全局 defaults + 本账号覆盖 */
|
|
71
|
+
config: QQBotConfig;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface LoadAccountsResult {
|
|
75
|
+
accounts: QQBotAccount[];
|
|
76
|
+
/** 配置错误:有错误时**不启动网关**,避免「机器人没反应又不知道为什么」 */
|
|
77
|
+
errors: string[];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const DEFAULT_PROGRESS_MS = 15000;
|
|
81
|
+
|
|
82
|
+
const asObject = (value: unknown): any =>
|
|
83
|
+
value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
84
|
+
|
|
85
|
+
function readFile(): any {
|
|
46
86
|
try {
|
|
47
|
-
|
|
87
|
+
return asObject(JSON.parse(fs.readFileSync(CONFIG_PATH(), "utf8")));
|
|
48
88
|
} catch {
|
|
49
89
|
/* 文件不存在或非法不致命,凭据可完全来自环境变量 */
|
|
90
|
+
return {};
|
|
50
91
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** 缺省名字:`bot-<appId 后 6 位>`,方便在多 bot 场景里一眼分辨 */
|
|
95
|
+
export function defaultAccountName(appId: string): string {
|
|
96
|
+
const tail = appId.replace(/[^0-9a-zA-Z]/g, "").slice(-6);
|
|
97
|
+
return tail ? `bot-${tail}` : "default";
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** 全局 defaults(顶层字段)与账号覆盖合并成一份可直接用的配置 */
|
|
101
|
+
export function resolveConfig(
|
|
102
|
+
file: any,
|
|
103
|
+
entry: QQBotsEntry,
|
|
104
|
+
fallback?: { appId: string; appSecret: string },
|
|
105
|
+
): QQBotConfig {
|
|
106
|
+
const appId = entry.appId ?? fallback?.appId ?? "";
|
|
107
|
+
const appSecret = entry.appSecret ?? fallback?.appSecret ?? "";
|
|
108
|
+
const envInterval = process.env.PI_QQBOT_PROGRESS_MS;
|
|
54
109
|
return {
|
|
55
110
|
appId,
|
|
56
111
|
appSecret,
|
|
57
|
-
sandbox: file.sandbox ?? false,
|
|
58
|
-
allowlist: file.allowlist ?? [],
|
|
59
|
-
markdownReply: file.markdownReply ?? false,
|
|
60
|
-
streaming: file.streaming ?? false,
|
|
61
|
-
toolProgress: file.toolProgress ?? false,
|
|
112
|
+
sandbox: entry.sandbox ?? file.sandbox ?? false,
|
|
113
|
+
allowlist: entry.allowlist ?? file.allowlist ?? [],
|
|
114
|
+
markdownReply: entry.markdownReply ?? file.markdownReply ?? false,
|
|
115
|
+
streaming: entry.streaming ?? file.streaming ?? false,
|
|
116
|
+
toolProgress: entry.toolProgress ?? file.toolProgress ?? false,
|
|
62
117
|
approve: {
|
|
63
|
-
tools: file.approve?.tools ?? ["bash", "powershell", "write", "edit"],
|
|
64
|
-
remember: file.approve?.remember ?? true,
|
|
118
|
+
tools: entry.approve?.tools ?? file.approve?.tools ?? ["bash", "powershell", "write", "edit"],
|
|
119
|
+
remember: entry.approve?.remember ?? file.approve?.remember ?? true,
|
|
65
120
|
},
|
|
66
121
|
commands: {
|
|
67
|
-
passthrough: file.commands?.passthrough ?? DEFAULT_COMMANDS.passthrough,
|
|
68
|
-
allowReload: file.commands?.allowReload ?? DEFAULT_COMMANDS.allowReload,
|
|
69
|
-
allowQuit: file.commands?.allowQuit ?? DEFAULT_COMMANDS.allowQuit,
|
|
70
|
-
allowSession: file.commands?.allowSession ?? DEFAULT_COMMANDS.allowSession,
|
|
122
|
+
passthrough: entry.commands?.passthrough ?? file.commands?.passthrough ?? DEFAULT_COMMANDS.passthrough,
|
|
123
|
+
allowReload: entry.commands?.allowReload ?? file.commands?.allowReload ?? DEFAULT_COMMANDS.allowReload,
|
|
124
|
+
allowQuit: entry.commands?.allowQuit ?? file.commands?.allowQuit ?? DEFAULT_COMMANDS.allowQuit,
|
|
125
|
+
allowSession: entry.commands?.allowSession ?? file.commands?.allowSession ?? DEFAULT_COMMANDS.allowSession,
|
|
71
126
|
},
|
|
72
127
|
queue: {
|
|
73
|
-
max: file.queue?.max ?? DEFAULT_QUEUE_MAX,
|
|
74
|
-
ack: file.queue?.ack ?? true,
|
|
128
|
+
max: entry.queue?.max ?? file.queue?.max ?? DEFAULT_QUEUE_MAX,
|
|
129
|
+
ack: entry.queue?.ack ?? file.queue?.ack ?? true,
|
|
75
130
|
},
|
|
76
131
|
progress: {
|
|
77
|
-
enabled: file.progress?.enabled ?? true,
|
|
78
|
-
intervalMs: Number(
|
|
132
|
+
enabled: entry.progress?.enabled ?? file.progress?.enabled ?? true,
|
|
133
|
+
intervalMs: Number(
|
|
134
|
+
envInterval ?? entry.progress?.intervalMs ?? file.progress?.intervalMs ?? DEFAULT_PROGRESS_MS,
|
|
135
|
+
),
|
|
79
136
|
},
|
|
80
137
|
};
|
|
81
138
|
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* 读配置,返回**按配置顺序**排好的账号列表。
|
|
142
|
+
*
|
|
143
|
+
* 支持两种格式:
|
|
144
|
+
* - v2 多 bot:`{ "qqbots": [ { "name": "work", "appId": … }, … ], "queue": {…}, … }`
|
|
145
|
+
* - v1 单 bot:`{ "appId": …, "appSecret": …, … }`(或全部来自 `QQ_BOT_APPID/SECRET`),
|
|
146
|
+
* 等价于只有一个名叫 `default` 的账号,行为与 1.x 完全一致。
|
|
147
|
+
*/
|
|
148
|
+
export function loadAccounts(): LoadAccountsResult {
|
|
149
|
+
const file = readFile();
|
|
150
|
+
const errors: string[] = [];
|
|
151
|
+
const envAppId = process.env.QQ_BOT_APPID;
|
|
152
|
+
const envAppSecret = process.env.QQ_BOT_APPSECRET;
|
|
153
|
+
|
|
154
|
+
let entries: QQBotsEntry[];
|
|
155
|
+
if (Array.isArray(file.qqbots)) {
|
|
156
|
+
if (file.appId) {
|
|
157
|
+
errors.push("配置里同时出现顶层 appId 和 qqbots 数组:请只保留一种格式(多 bot 请写进 qqbots)");
|
|
158
|
+
return { accounts: [], errors };
|
|
159
|
+
}
|
|
160
|
+
entries = file.qqbots.map((item: any) => asObject(item));
|
|
161
|
+
if (entries.length === 0) {
|
|
162
|
+
errors.push("qqbots 是空数组:至少配置一个 bot,否则插件不会接入 QQ");
|
|
163
|
+
return { accounts: [], errors };
|
|
164
|
+
}
|
|
165
|
+
} else if (file.appId || envAppId) {
|
|
166
|
+
entries = [
|
|
167
|
+
{
|
|
168
|
+
name: "default",
|
|
169
|
+
appId: envAppId ?? file.appId,
|
|
170
|
+
appSecret: envAppSecret ?? file.appSecret,
|
|
171
|
+
},
|
|
172
|
+
];
|
|
173
|
+
} else {
|
|
174
|
+
return { accounts: [], errors };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const accounts: QQBotAccount[] = [];
|
|
178
|
+
const seenNames = new Set<string>();
|
|
179
|
+
const seenAppIds = new Set<string>();
|
|
180
|
+
|
|
181
|
+
for (let index = 0; index < entries.length; index += 1) {
|
|
182
|
+
const raw = asObject(entries[index]);
|
|
183
|
+
const appId = String(raw.appId ?? "").trim();
|
|
184
|
+
const appSecret = String(raw.appSecret ?? "").trim();
|
|
185
|
+
const label = `qqbots[${index}]${raw.name ? `(name=${raw.name})` : ""}`;
|
|
186
|
+
if (!appId || !appSecret) {
|
|
187
|
+
// 单个账号缺凭据只跳过它,别让整个插件都起不来
|
|
188
|
+
errors.push(`${label} 缺少 appId 或 appSecret,已跳过`);
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
if (seenAppIds.has(appId)) {
|
|
192
|
+
errors.push(`${label} 的 appId ${appId} 重复,已跳过(否则会双连接被平台踢)`);
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
const name = String(raw.name ?? defaultAccountName(appId)).trim() || defaultAccountName(appId);
|
|
196
|
+
if (seenNames.has(name)) {
|
|
197
|
+
errors.push(`${label} 的 name "${name}" 与其他 bot 重复,已跳过(改名后重试)`);
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
seenAppIds.add(appId);
|
|
201
|
+
seenNames.add(name);
|
|
202
|
+
accounts.push({ name, appId, config: resolveConfig(file, raw) });
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return { accounts, errors };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** 兼容旧调用:只取第一个账号的配置(1.x 单 bot 语义) */
|
|
209
|
+
export function loadConfig(): QQBotConfig | null {
|
|
210
|
+
const { accounts } = loadAccounts();
|
|
211
|
+
return accounts[0]?.config ?? null;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** 环境变量指定的账号名(调试/测试用:钉住某个 bot,不参与自动抢占) */
|
|
215
|
+
export function pinnedAccountName(): string | null {
|
|
216
|
+
const name = process.env.PI_QQBOT_ACCOUNT?.trim();
|
|
217
|
+
return name ? name : null;
|
|
218
|
+
}
|
package/constants.ts
CHANGED
|
@@ -20,3 +20,8 @@ const PI_DIR = path.join(os.homedir(), ".pi", "agent");
|
|
|
20
20
|
export const PI_AGENT_DIR = PI_DIR;
|
|
21
21
|
export const CONFIG_PATH = () => path.join(PI_DIR, "pi-qqbot.json");
|
|
22
22
|
export const LOCK_PATH = (appId: string) => path.join(PI_DIR, `pi-qqbot-${appId}.lock`);
|
|
23
|
+
/** 记录「谁占着这个 bot」:pid/cwd/账号名 + 临时释放的预约窗口(换进程时用来生成提示) */
|
|
24
|
+
export const OWNER_PATH = (appId: string) => path.join(PI_DIR, `pi-qqbot-${appId}.owner.json`);
|
|
25
|
+
/** 日志文件(按账号分文件:多进程同机时不交错) */
|
|
26
|
+
export const LOG_PATH = (account: string, legacy = false) =>
|
|
27
|
+
path.join(PI_DIR, legacy ? "pi-qqbot.log" : `pi-qqbot-${account}.log`);
|
package/docs/README.zh-CN.md
CHANGED
|
@@ -17,8 +17,9 @@
|
|
|
17
17
|
| **单实例锁** | 只有一个 pi 进程作为宿主连接 QQ,其余待命,宿主退出后自动接管。 |
|
|
18
18
|
| **中断正在运行的任务** | 发 `/cancel`(或「取消」/「停止」),当前任务立刻中断 —— QQ 回复、待审批工具调用、写到一半的回答、排队中的消息一并清理。 |
|
|
19
19
|
| **斜杠命令路由** | 以 `/` 开头的 QQ 消息会先被路由:pi 的扩展命令、prompt 模板、`/skill:name` 交给 pi 派发;一部分 pi 内置命令(`/reload`、`/new`、`/compact`、`/session`、`/name`、`/model`、`/thinking`、`/quit`)由本扩展接管;只能在主机执行的内置命令直接回执,不会送给模型。 |
|
|
20
|
-
| **进度汇报** | 任务运行期间按 `progress.intervalMs`(默认 **15 秒**)推一条进度摘要(轮次、工具调用、正在跑的工具、token 用量)。随时发 `/progress`(别名 `/p
|
|
20
|
+
| **进度汇报** | 任务运行期间按 `progress.intervalMs`(默认 **15 秒**)推一条进度摘要(轮次、工具调用、正在跑的工具、token 用量)。随时发 `/progress`(别名 `/p`、「进度」)可立即获取;`/progress off` 在本会话内暂停自动汇报,`/progress on` 恢复。推送失败后自动推送静默,直到你再次问询。 |
|
|
21
21
|
| **敏感信息脱敏** | 离开本机的文本先脱敏——进度摘要、审批提示、队列预览、日志文件:`Authorization` 头(含 QQ bot token)、`sk-…`/`ghp_…`/`npm_…`/`AKIA…`/JWT/PEM、`token=`/`password=`/`--api-key` 的值、URL 里的 `user:pass@`,以及超长高熵串。 |
|
|
22
|
+
| **多 bot 同机** | 一份配置里写多个 QQ bot(`qqbots: [...]`,详见 [accounts.md](./accounts.md)),同一台机器上每个 pi 进程按配置顺序接入第一个未上线的 bot;全部在线则待命、每 5 秒重扫,有人退出就接管。别人只是 `/reload` 临时释放的 bot 不会被抢(留 20 秒预约窗口)。接入/接管时会告知当前 bot 名称与工作目录。 |
|
|
22
23
|
| **内置指令** | `/status`、`/queue`、`/progress`、`/reset`、`/cancel`、`/help`。 |
|
|
23
24
|
|
|
24
25
|
## 安装
|
|
@@ -65,10 +66,25 @@ pi install ./pi-qqbot
|
|
|
65
66
|
| `commands` | 见下 | 斜杠命令路由开关:`passthrough`、`allowReload`、`allowQuit`、`allowSession` |
|
|
66
67
|
| `queue.max` | `20` | 任务运行期间最多排队多少条;`0` = 关闭排队(忙碌时直接拒绍,回到旧行为) |
|
|
67
68
|
| `queue.ack` | `true` | 排队消息开始执行时是否回执 QQ(入队回执总是会发) |
|
|
68
|
-
| `progress.enabled` | `true` |
|
|
69
|
+
| `progress.enabled` | `true` | 任务运行期间是否定期推送进度摘要(会话启动时读取,改完需 `/new` 或重启;运行时用 `/progress off`) |
|
|
69
70
|
| `progress.intervalMs` | `15000` | 推送间隔(可用环境变量 `PI_QQBOT_PROGRESS_MS` 覆盖,便于测试) |
|
|
70
71
|
|
|
71
|
-
也可用环境变量 `QQ_BOT_APPID` / `QQ_BOT_APPSECRET`
|
|
72
|
+
也可用环境变量 `QQ_BOT_APPID` / `QQ_BOT_APPSECRET` 代替文件里的凭据(仅单 bot 场景)。
|
|
73
|
+
|
|
74
|
+
### 多个 bot(2.0)
|
|
75
|
+
|
|
76
|
+
```jsonc
|
|
77
|
+
{
|
|
78
|
+
"qqbots": [
|
|
79
|
+
{ "name": "work", "appId": "1903340143", "appSecret": "…", "sandbox": true },
|
|
80
|
+
{ "name": "home", "appId": "1020123456", "appSecret": "…" }
|
|
81
|
+
],
|
|
82
|
+
"queue": { "max": 20, "ack": true } // 这里仍是全局默认,qqbots 内可逐键覆盖
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
多个 pi 进程按配置顺序各自接入一个未上线的 bot,抢不到的待命并每 5 秒重扫;
|
|
87
|
+
`PI_QQBOT_ACCOUNT=<name|appId>` 可以钉住某个 bot,不走自动抢占。细节见 [accounts.md](./accounts.md)。
|
|
72
88
|
|
|
73
89
|
## 使用
|
|
74
90
|
|
|
@@ -77,9 +93,11 @@ pi install ./pi-qqbot
|
|
|
77
93
|
| 指令 | 说明 |
|
|
78
94
|
|---|---|
|
|
79
95
|
| `/reset` | 重置:清空回复缓冲、审批记忆与自动同意开关(**不换会话**,换会话发 `/new`) |
|
|
80
|
-
| `/status` |
|
|
96
|
+
| `/status` | 查看本进程服务的 bot(名称 + 工作目录)、忙闲状态、队列长度、自动同意状态、待审批数量、自动汇报模式 |
|
|
81
97
|
| `/queue` | 查看队列(别名 `/q`);`/queue clear` 清空,`/queue drop <编号>` 删一条 |
|
|
82
|
-
| `/progress` |
|
|
98
|
+
| `/progress` | **立即获取当前进度摘要**(别名 `/p`、「进度」「当前进度」「进展」;忙碌时「到哪了」「怎么样了」这类问法同样生效)。手动暂停期间**问一次只回一次**,要重新打开定时汇报需发 `/progress on` 或「恢复汇报」;若只是配额静默,问一次即恢复 |
|
|
99
|
+
| `/progress off` | **在本会话内暂停自动进度汇报**(别名:`pause`、`stop`、`暂停`、「暂停汇报」、「关闭汇报」)。手动 `/progress` 仍可看一次,但不再解除暂停 |
|
|
100
|
+
| `/progress on` | **恢复自动进度汇报**(别名:`resume`、`start`、`恢复`、「恢复汇报」、「继续汇报」) |
|
|
83
101
|
| `/cancel` | **中断当前正在运行的任务**并清空队列(别名:`/stop`、`/abort`、「取消」、「停止」、「中止」、「终止」、「算了」) |
|
|
84
102
|
| `/help` | 帮助 |
|
|
85
103
|
|
|
@@ -187,10 +205,13 @@ pi 的会话一次只跑一轮 agent run,所以串行调度由 bot 自己做
|
|
|
187
205
|
- 工具参数先脱敏再截断,带凭据的命令不会明文出现在 QQ 里。
|
|
188
206
|
- **配额感知**:一条用户消息只有 4 次被动回复额度,长任务迟早会用完。第一次发送失败就停掉本轮
|
|
189
207
|
的自动推送(不重试、不刷屏);你发 `/progress`(或「进度」)即恢复。
|
|
208
|
+
- **手动暂停/恢复**:`/progress off`(或「暂停汇报」)在本会话内停掉自动推送——跨 run 保留,
|
|
209
|
+
之后问 `/progress` 也不会把它打开;`/progress on`(或「恢复汇报」)恢复。开新会话(`/new`、重启)后
|
|
210
|
+
恢复默认开启。`/status` 会显示当前处于哪种模式(开启 / 手动暂停 / 配额静默 / 配置关闭)。
|
|
190
211
|
- **保护最终答案**:答案因配额发不出去时会被缓存,等你发下一条消息时以
|
|
191
212
|
`(上一条回复发送失败,补发如下)` 前缀补发。
|
|
192
213
|
- `agent_settled` / `/cancel` / 会话关闭时定时器都会清掉。
|
|
193
|
-
-
|
|
214
|
+
- 想彻底关掉(跨会话):`progress.enabled: false`(改完需 `/new` 或重启才生效)。
|
|
194
215
|
|
|
195
216
|
### 取消正在运行的任务
|
|
196
217
|
|
|
@@ -240,8 +261,10 @@ pi 的会话一次只跑一轮 agent run,所以串行调度由 bot 自己做
|
|
|
240
261
|
QQ 网关对同一 bot 只保留一个活跃连接。插件用 `proper-lockfile` 文件锁(`~/.pi/agent/pi-qqbot-<appId>.lock`):
|
|
241
262
|
|
|
242
263
|
- 抢到锁的进程 = 宿主,连接 QQ;
|
|
243
|
-
- 其余进程待命,每 5
|
|
244
|
-
- 宿主退出或崩溃(30
|
|
264
|
+
- 其余进程待命,每 5 秒重扫,**列表里任何一个空闲的 bot** 都能抢;
|
|
265
|
+
- 宿主退出或崩溃(30 秒心跳超时)后,待命进程自动接管;
|
|
266
|
+
- 只是重建会话(`/reload`、`/new`、`/fork`、`/resume`)的宿主会在锁旁留 20 秒预约,别人不会趁空抢走;
|
|
267
|
+
- 上一位持有者的 pid 与工作目录记在 `~/.pi/agent/pi-qqbot-<appId>.owner.json`,用来提示「换进程接管」以及新实例的工作目录。
|
|
245
268
|
|
|
246
269
|
## 前置条件
|
|
247
270
|
|
|
@@ -251,7 +274,11 @@ QQ 网关对同一 bot 只保留一个活跃连接。插件用 `proper-lockfile`
|
|
|
251
274
|
|
|
252
275
|
## 日志
|
|
253
276
|
|
|
254
|
-
调试日志写入 `~/.pi/agent/pi-qqbot.log`。每行开头是**本地时间 + 时区偏移**(如 `[2026-10-24T07:39:09.762+08:00]`),和文件 mtime、进程启动时间直接对得上,不需要再拿 UTC 换算。
|
|
277
|
+
调试日志写入 `~/.pi/agent/pi-qqbot-<name>.log`(按 bot 分文件,同机多进程不交错);单 bot 旧格式仍写 `pi-qqbot.log`。每行开头是**本地时间 + 时区偏移**(如 `[2026-10-24T07:39:09.762+08:00]`),和文件 mtime、进程启动时间直接对得上,不需要再拿 UTC 换算。
|
|
278
|
+
|
|
279
|
+
## 变更日志
|
|
280
|
+
|
|
281
|
+
见 [CHANGELOG.md](../CHANGELOG.md):每个版本改了什么、为什么改。进度汇报的暂停/恢复(`/progress off` / `on`)在 **1.5.0** 加入;多 bot 自动抢占在 **2.0.0**。
|
|
255
282
|
|
|
256
283
|
## License
|
|
257
284
|
|