@tencent-connect/openclaw-qqbot 1.6.6-alpha.4 → 1.6.7-beta.3

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/README.md CHANGED
@@ -10,7 +10,7 @@
10
10
 
11
11
  **Connect your AI assistant to QQ — private chat, group chat, and rich media, all in one plugin.**
12
12
 
13
- ### 🚀 Current Version: `v1.6.5`
13
+ ### 🚀 Current Version: `v1.6.6`
14
14
 
15
15
  [![License](https://img.shields.io/badge/license-MIT-green)](./LICENSE)
16
16
  [![QQ Bot](https://img.shields.io/badge/QQ_Bot-API_v2-red)](https://bot.q.qq.com/wiki/)
@@ -25,7 +25,7 @@
25
25
 
26
26
  Scan to join the QQ group chat
27
27
 
28
- <img width="400" alt="QQ QR Code" src="./docs/images/developer_group.png" />
28
+ <img width="400" alt="QQ QR Code" src="./docs/images/developer-group.png" />
29
29
 
30
30
 
31
31
  </div>
@@ -46,6 +46,7 @@ Scan to join the QQ group chat
46
46
  | 📝 **Markdown** | Full Markdown formatting support |
47
47
  | 🛠️ **Commands** | Native OpenClaw command integration |
48
48
  | 💬 **Quoted Context** | Resolve QQ `REFIDX_*` quoted messages and inject quote body into AI context |
49
+ | 📦 **Large File Support** | Auto chunked upload for large files (parallel upload with retry), up to 100 MB |
49
50
 
50
51
  ---
51
52
 
@@ -61,7 +62,7 @@ QQ quote events carry index keys (e.g. `REFIDX_xxx`) instead of full original me
61
62
  - Store path: `~/.openclaw/qqbot/data/ref-index.jsonl` (survives gateway restart).
62
63
  - Quote body may include text + media summary (image/voice/video/file).
63
64
 
64
- <img width="360" src="docs/images/ref_msg.png" alt="Quoted Message Context Demo" />
65
+ <img width="360" src="docs/images/ref-msg.png" alt="Quoted Message Context Demo" />
65
66
 
66
67
  ### 🎙️ Voice Messages (STT)
67
68
 
@@ -71,7 +72,7 @@ With STT configured, the plugin automatically transcribes voice messages to text
71
72
  >
72
73
  > **QQBot**: Tomorrow (March 7, Saturday) Shenzhen weather forecast 🌤️ ...
73
74
 
74
- <img width="360" src="docs/images/fc7b2236896cfba3a37c94be5d59ce3e_720.jpg" alt="Voice STT Demo" />
75
+ <img width="360" src="docs/images/voice-stt.jpg" alt="Voice STT Demo" />
75
76
 
76
77
  ### 📄 File Understanding
77
78
 
@@ -81,7 +82,7 @@ Send any file to the bot — novels, reports, spreadsheets — AI automatically
81
82
  >
82
83
  > **QQBot**: Got it! You uploaded the Chinese version of "War and Peace" by Leo Tolstoy. This appears to be the opening of Chapter 1...
83
84
 
84
- <img width="360" src="docs/images/07bff56ab68e03173d2af586eeb3bcee_720.jpg" alt="File Understanding Demo" />
85
+ <img width="360" src="docs/images/file-understand.jpg" alt="File Understanding Demo" />
85
86
 
86
87
  ### 🖼️ Image Understanding
87
88
 
@@ -91,7 +92,7 @@ If your main model supports vision (e.g. Tencent Hunyuan `hunyuan-vision`), AI c
91
92
  >
92
93
  > **QQBot**: Haha, so cute! Is that a QQ penguin in a lobster costume? 🦞🐧 ...
93
94
 
94
- <img width="360" src="docs/images/59d421891f813b0d3c0cbe12574b6a72_720.jpg" alt="Image Understanding Demo" />
95
+ <img width="360" src="docs/images/image-understand.jpg" alt="Image Understanding Demo" />
95
96
 
96
97
  ### 🎨 Image Sending
97
98
 
@@ -101,7 +102,7 @@ If your main model supports vision (e.g. Tencent Hunyuan `hunyuan-vision`), AI c
101
102
 
102
103
  AI can send images directly. Supports local paths and URLs. Formats: jpg/png/gif/webp/bmp.
103
104
 
104
- <img width="360" src="docs/images/4645f2b3a20822b7f8d6664a708529eb_720.jpg" alt="Image Generation Demo" />
105
+ <img width="360" src="docs/images/image-send.jpg" alt="Image Generation Demo" />
105
106
 
106
107
  ### 🔊 Voice Sending
107
108
 
@@ -111,7 +112,7 @@ AI can send images directly. Supports local paths and URLs. Formats: jpg/png/gif
111
112
 
112
113
  AI can send voice messages directly. Formats: mp3/wav/silk/ogg. No ffmpeg required.
113
114
 
114
- <img width="360" src="docs/images/21dce8bfc553ce23d1bd1b270e9c516c.jpg" alt="TTS Voice Demo" />
115
+ <img width="360" src="docs/images/voice-send.jpg" alt="TTS Voice Demo" />
115
116
 
116
117
  ### ⏰ Scheduled Reminder (Proactive Message)
117
118
 
@@ -129,9 +130,13 @@ This capability depends on OpenClaw cron scheduling and proactive messaging. If
129
130
  >
130
131
  > **QQBot**: *(sends a .txt file)*
131
132
 
132
- AI can send files directly. Any format, up to 20MB.
133
+ AI can send files directly, in any format.
133
134
 
134
- <img width="360" src="docs/images/17cada70df90185d45a2d6dd36e92f2f_720.jpg" alt="File Sending Demo" />
135
+ <img width="360" src="docs/images/file-send.jpg" alt="File Sending Demo" />
136
+
137
+ Since v1.6.6, large file transfer is supported: images up to 20MB, videos up to 30MB, attachments up to 100MB, with a daily transfer limit of 2GB.
138
+
139
+ <img width="360" src="docs/images/large-file-transfer.jpg" alt="Large File Transfer Demo" />
135
140
 
136
141
  ### 🎬 Video Sending
137
142
 
@@ -141,7 +146,7 @@ AI can send files directly. Any format, up to 20MB.
141
146
 
142
147
  AI can send videos directly. Supports local files and URLs.
143
148
 
144
- <img width="360" src="docs/images/85d03b8a216f267ab7b2aee248a18a41_720.jpg" alt="Video Sending Demo" />
149
+ <img width="360" src="docs/images/video-send.jpg" alt="Video Sending Demo" />
145
150
 
146
151
  > **Under the hood:** Upload dedup caching, ordered queue delivery, and multi-layer audio format fallback.
147
152
 
@@ -207,6 +212,10 @@ All commands support a `?` suffix to show usage:
207
212
  >
208
213
  > **QQBot**: 📖 /bot-upgrade usage: …
209
214
 
215
+ #### `/bot-clear-storage` — Clear files generated through QQBot conversations and downloaded resources (stored on the host running OpenClaw)
216
+
217
+ `/bot-clear-storage` lists files generated by the conversation and files in the downloaded resources directory. Use `/bot-clear-storage --force` to confirm deletion.
218
+
210
219
  ---
211
220
 
212
221
  ## 🚀 Getting Started
@@ -220,15 +229,15 @@ All commands support a `?` suffix to show usage:
220
229
  2. After scanning, tap **Agree** on your phone — you'll land on the bot configuration page.
221
230
  3. Click **Create Bot** to create a new QQ bot.
222
231
 
223
- <img width="720" alt="Create Bot" src="docs/images/create_robot.png" />
232
+ <img width="720" alt="Create Bot" src="docs/images/create-robot.png" />
224
233
 
225
234
  > ⚠️ The bot will automatically appear in your QQ message list and send a first message. However, it will reply "The bot has gone to Mars" until you complete the configuration steps below.
226
235
 
227
- <img width="400" alt="Bot Say Hello" src="docs/images/bot_say_hello.jpg" />
236
+ <img width="400" alt="Bot Say Hello" src="docs/images/bot-say-hello.jpg" />
228
237
 
229
238
  4. Find **AppID** and **AppSecret** on the bot's page, click **Copy** for each, and save them somewhere safe (e.g., a notepad). **AppSecret is not stored in plaintext — if you leave the page without saving it, you'll have to regenerate a new one.**
230
239
 
231
- <img width="720" alt="Find AppID and AppSecret" src="docs/images/find_appid_secret.png" />
240
+ <img width="720" alt="Find AppID and AppSecret" src="docs/images/find-appid-secret.png" />
232
241
 
233
242
  > For a step-by-step walkthrough with screenshots, see the [official guide](https://cloud.tencent.com/developer/article/2626045).
234
243
 
@@ -458,7 +467,7 @@ Special thanks to [@sliverp](https://github.com/sliverp) for outstanding contrib
458
467
  Thanks to [Tencent Cloud Lighthouse](https://cloud.tencent.com/product/lighthouse) for the deep collaboration. For raising crawfish, choose Tencent Cloud Lighthouse!
459
468
 
460
469
  <a href="https://cloud.tencent.com/product/lighthouse">
461
- <img alt="Tencent Cloud Lighthouse" src="./docs/images/lighthouse_head.png" height="500" style="max-width:80%; height:auto;"/>
470
+ <img alt="Tencent Cloud Lighthouse" src="./docs/images/lighthouse-head.png" height="500" style="max-width:80%; height:auto;"/>
462
471
  </a>
463
472
 
464
473
  ## ⭐ Star History
package/README.zh.md CHANGED
@@ -9,7 +9,7 @@
9
9
 
10
10
  **让你的 AI 助手接入 QQ — 私聊、群聊、富媒体,一个插件全搞定。**
11
11
 
12
- ### 🚀 当前版本: `v1.6.5`
12
+ ### 🚀 当前版本: `v1.6.6`
13
13
 
14
14
  [![License](https://img.shields.io/badge/license-MIT-green)](./LICENSE)
15
15
  [![QQ Bot](https://img.shields.io/badge/QQ_Bot-API_v2-red)](https://bot.q.qq.com/wiki/)
@@ -21,7 +21,7 @@
21
21
 
22
22
  扫描二维码加入群聊,一起交流
23
23
 
24
- <img width="400" alt="QQ 群二维码" src="./docs/images/developer_group.png" />
24
+ <img width="400" alt="QQ 群二维码" src="./docs/images/developer-group.png" />
25
25
 
26
26
  </div>
27
27
 
@@ -41,6 +41,7 @@
41
41
  | 📝 **Markdown** | 完整支持 Markdown 格式消息 |
42
42
  | 🛠️ **原生命令** | 支持 OpenClaw 原生命令 |
43
43
  | 💬 **引用上下文** | 解析 QQ `REFIDX_*` 引用消息,并将引用内容注入 AI 上下文 |
44
+ | 📦 **大文件支持** | 大文件自动分片并行上传,最大支持 100 MB |
44
45
 
45
46
  ---
46
47
 
@@ -56,7 +57,7 @@ QQ 的引用事件通常只携带索引键(如 `REFIDX_xxx`),不直接返
56
57
  - 存储位置:`~/.openclaw/qqbot/data/ref-index.jsonl`(网关重启后仍可恢复)。
57
58
  - 引用内容支持文本 + 媒体摘要(图片/语音/视频/文件)。
58
59
 
59
- <img width="360" src="docs/images/ref_msg.png" alt="引用消息上下文演示" />
60
+ <img width="360" src="docs/images/ref-msg.png" alt="引用消息上下文演示" />
60
61
 
61
62
  ### 🎙️ 语音消息(STT)
62
63
 
@@ -66,7 +67,7 @@ QQ 的引用事件通常只携带索引键(如 `REFIDX_xxx`),不直接返
66
67
  >
67
68
  > **QQBot**:明天(3月7日 周六)深圳的天气预报 🌤️ ...
68
69
 
69
- <img width="360" src="docs/images/fc7b2236896cfba3a37c94be5d59ce3e_720.jpg" alt="听语音演示" />
70
+ <img width="360" src="docs/images/voice-stt.jpg" alt="听语音演示" />
70
71
 
71
72
  ### 📄 文件理解
72
73
 
@@ -76,7 +77,7 @@ QQ 的引用事件通常只携带索引键(如 `REFIDX_xxx`),不直接返
76
77
  >
77
78
  > **QQBot**:收到!你上传了列夫·托尔斯泰的《战争与和平》中文版文本。从内容来看,这是第一章的开头……你想让我做什么?
78
79
 
79
- <img width="360" src="docs/images/07bff56ab68e03173d2af586eeb3bcee_720.jpg" alt="AI理解用户发送的文件" />
80
+ <img width="360" src="docs/images/file-understand.jpg" alt="AI理解用户发送的文件" />
80
81
 
81
82
  ### 🖼️ 图片理解
82
83
 
@@ -86,7 +87,7 @@ QQ 的引用事件通常只携带索引键(如 `REFIDX_xxx`),不直接返
86
87
  >
87
88
  > **QQBot**:哈哈,好可爱!这是QQ企鹅穿上小龙虾套装吗?🦞🐧 ...
88
89
 
89
- <img width="360" src="docs/images/59d421891f813b0d3c0cbe12574b6a72_720.jpg" alt="图片理解演示" />
90
+ <img width="360" src="docs/images/image-understand.jpg" alt="图片理解演示" />
90
91
 
91
92
  ### 🎨 图片发送
92
93
 
@@ -96,7 +97,7 @@ QQ 的引用事件通常只携带索引键(如 `REFIDX_xxx`),不直接返
96
97
 
97
98
  AI 可直接发送图片,支持本地文件路径和网络 URL。格式:jpg/png/gif/webp/bmp。
98
99
 
99
- <img width="360" src="docs/images/4645f2b3a20822b7f8d6664a708529eb_720.jpg" alt="发图片演示" />
100
+ <img width="360" src="docs/images/image-send.jpg" alt="发图片演示" />
100
101
 
101
102
  ### 🔊 语音发送
102
103
 
@@ -106,7 +107,7 @@ AI 可直接发送图片,支持本地文件路径和网络 URL。格式:jpg/
106
107
 
107
108
  AI 可直接发送语音消息。格式:mp3/wav/silk/ogg,无需安装 ffmpeg。
108
109
 
109
- <img width="360" src="docs/images/21dce8bfc553ce23d1bd1b270e9c516c.jpg" alt="发语音演示" />
110
+ <img width="360" src="docs/images/voice-send.jpg" alt="发语音演示" />
110
111
 
111
112
  ### ⏰ 定时提醒(主动消息)
112
113
 
@@ -124,9 +125,13 @@ AI 可直接发送语音消息。格式:mp3/wav/silk/ogg,无需安装 ffmpeg
124
125
  >
125
126
  > **QQBot**:*(发送 .txt 文件)*
126
127
 
127
- AI 可直接发送文件。任意格式,最大 20MB。
128
+ AI 可直接发送文件,任意格式均可。
128
129
 
129
- <img width="360" src="docs/images/17cada70df90185d45a2d6dd36e92f2f_720.jpg" alt="发文件演示" />
130
+ <img width="360" src="docs/images/file-send.jpg" alt="发文件演示" />
131
+
132
+ v1.6.6 起支持大文件传输:图片最大 20MB,视频最大 30MB,附件最大 100MB,每日累计传输上限 2GB。
133
+
134
+ <img width="360" src="docs/images/large-file-transfer.jpg" alt="大文件传输演示" />
130
135
 
131
136
  ### 🎬 视频发送
132
137
 
@@ -136,7 +141,7 @@ AI 可直接发送文件。任意格式,最大 20MB。
136
141
 
137
142
  AI 可直接发送视频,支持本地文件和公网 URL。
138
143
 
139
- <img width="360" src="docs/images/85d03b8a216f267ab7b2aee248a18a41_720.jpg" alt="发视频演示" />
144
+ <img width="360" src="docs/images/video-send.jpg" alt="发视频演示" />
140
145
 
141
146
  > **底层细节:** 上传去重缓存、有序队列发送、音频格式多层降级。
142
147
 
@@ -202,6 +207,10 @@ AI 可直接发送视频,支持本地文件和公网 URL。
202
207
  >
203
208
  > **QQBot**:📖 /bot-upgrade 用法:…
204
209
 
210
+ #### `/bot-clear-storage` — 清理通过 QQBot 对话产生的文件以及下载的资源(保存在 OpenClaw 运行环境的主机上)
211
+
212
+ `/bot-clear-storage` 列出对话产生的文件以及下载的资源目录里的文件,使用`/bot-clear-storage -- force`确定删除。
213
+
205
214
  ---
206
215
 
207
216
  ## 🚀 快速开始
@@ -215,15 +224,15 @@ AI 可直接发送视频,支持本地文件和公网 URL。
215
224
  2. 手机 QQ 扫码后选择**同意**,即完成注册,进入 QQ 机器人配置页。
216
225
  3. 点击**创建机器人**,即可直接新建一个 QQ 机器人。
217
226
 
218
- <img width="720" alt="创建机器人" src="docs/images/create_robot.png" />
227
+ <img width="720" alt="创建机器人" src="docs/images/create-robot.png" />
219
228
 
220
229
  > ⚠️ 机器人创建后会自动出现在你的 QQ 消息列表中,并发送第一条消息。但在完成下面的配置之前,发消息会提示"该机器人去火星了",属于正常现象。
221
230
 
222
- <img width="400" alt="机器人打招呼" src="docs/images/bot_say_hello.jpg" />
231
+ <img width="400" alt="机器人打招呼" src="docs/images/bot-say-hello.jpg" />
223
232
 
224
233
  4. 在机器人页面中找到 **AppID** 和 **AppSecret**,分别点击右侧**复制**按钮,保存到记事本或备忘录中。**AppSecret 不支持明文保存,离开页面后再查看会强制重置,请务必妥善保存。**
225
234
 
226
- <img width="720" alt="找到 AppID 和 AppSecret" src="docs/images/find_appid_secret.png" />
235
+ <img width="720" alt="找到 AppID 和 AppSecret" src="docs/images/find-appid-secret.png" />
227
236
 
228
237
  > 详细图文教程请参阅 [官方指南](https://cloud.tencent.com/developer/article/2626045)。
229
238
 
@@ -453,7 +462,7 @@ STT 支持两级配置,按优先级查找:
453
462
  感谢[腾讯云Lighthouse](https://cloud.tencent.com/product/lighthouse)的深度合作,养小龙虾,首选腾讯云Lighthouse!
454
463
 
455
464
  <a href="https://cloud.tencent.com/product/lighthouse">
456
- <img alt="腾讯云 Lighthouse" src="./docs/images/lighthouse_head.png" height="500" style="max-width:80%; height:auto;"/>
465
+ <img alt="腾讯云 Lighthouse" src="./docs/images/lighthouse-head.png" height="500" style="max-width:80%; height:auto;"/>
457
466
  </a>
458
467
 
459
468
  ## ⭐ Star History
package/dist/src/api.js CHANGED
@@ -106,7 +106,7 @@ async function doFetchToken(appId, clientSecret) {
106
106
  const requestBody = { appId, clientSecret };
107
107
  const requestHeaders = { "Content-Type": "application/json", "User-Agent": PLUGIN_USER_AGENT };
108
108
  // 打印请求信息(隐藏敏感信息)
109
- console.log(`[qqbot-api:${appId}] >>> POST ${TOKEN_URL}`);
109
+ console.log(`[qqbot-api:${appId}] >>> POST ${TOKEN_URL} [secret: ${clientSecret.slice(0, 6)}...len=${clientSecret.length}]`);
110
110
  let response;
111
111
  try {
112
112
  response = await fetch(TOKEN_URL, {
@@ -168,7 +168,7 @@ export function resolveDefaultQQBotAccountId(cfg) {
168
168
  * 解析 QQBot 账户配置
169
169
  */
170
170
  export function resolveQQBotAccount(cfg, accountId) {
171
- const resolvedAccountId = accountId ?? DEFAULT_ACCOUNT_ID;
171
+ const resolvedAccountId = accountId ?? resolveDefaultQQBotAccountId(cfg);
172
172
  const qqbot = cfg.channels?.qqbot;
173
173
  // 基础配置
174
174
  let accountConfig = {};
@@ -1131,7 +1131,7 @@ export async function startGateway(ctx) {
1131
1131
  const sendErrorMessage = (errorText) => sendErrorToTarget(replyCtx, errorText);
1132
1132
  // 使用 AsyncLocalStorage 建立请求级上下文,作用域内所有异步代码
1133
1133
  // (包括 AI agent 调用、tool execute)都能安全获取当前会话信息,无并发竞态。
1134
- await runWithRequestContext({ target: qualifiedTarget }, async () => {
1134
+ await runWithRequestContext({ target: qualifiedTarget, accountId: account.accountId }, async () => {
1135
1135
  try {
1136
1136
  const messagesConfig = pluginRuntime.channel.reply.resolveEffectiveMessagesConfig(cfg, route.agentId);
1137
1137
  // 追踪是否有响应
@@ -1,6 +1,8 @@
1
1
  export interface RequestContext {
2
2
  /** 投递目标地址,如 qqbot:c2c:xxx 或 qqbot:group:xxx */
3
3
  target: string;
4
+ /** 当前请求的 QQBot 账户 ID(多账户场景) */
5
+ accountId?: string;
4
6
  }
5
7
  /**
6
8
  * 在请求级作用域中执行回调。
@@ -16,3 +18,8 @@ export declare function getRequestContext(): RequestContext | undefined;
16
18
  * 便捷方法,等价于 getRequestContext()?.target。
17
19
  */
18
20
  export declare function getRequestTarget(): string | undefined;
21
+ /**
22
+ * 获取当前请求的账户 ID。
23
+ * 便捷方法,等价于 getRequestContext()?.accountId。
24
+ */
25
+ export declare function getRequestAccountId(): string | undefined;
@@ -28,3 +28,10 @@ export function getRequestContext() {
28
28
  export function getRequestTarget() {
29
29
  return asyncLocalStorage.getStore()?.target;
30
30
  }
31
+ /**
32
+ * 获取当前请求的账户 ID。
33
+ * 便捷方法,等价于 getRequestContext()?.accountId。
34
+ */
35
+ export function getRequestAccountId() {
36
+ return asyncLocalStorage.getStore()?.accountId;
37
+ }
@@ -152,7 +152,7 @@ function checkUpgradeCompatibility() {
152
152
  // 3. 检查 Node.js 版本
153
153
  const nodeVer = process.version.replace(/^v/, "");
154
154
  if (compareSemver(nodeVer, req.minNodeVersion) < 0) {
155
- errors.push(`❌ Node.js 版本过低:当前 **v${nodeVer}**,热更新要求最低 **v${req.minNodeVersion}**`);
155
+ errors.push(`❌ NoVBNde.js 版本过低:当前 **v${nodeVer}**,热更新要求最低 **v${req.minNodeVersion}**`);
156
156
  }
157
157
  // 4. 检查系统架构(arm 等特殊架构提示)
158
158
  const arch = process.arch;
@@ -642,15 +642,24 @@ function cleanupTempScript() {
642
642
  *
643
643
  * 安全机制:脚本会被复制到临时目录再执行,避免升级过程中插件目录被操作导致脚本自身丢失。
644
644
  */
645
- function fireHotUpgrade(targetVersion) {
646
- // 优先从远端下载升级脚本,避免使用本地可能过时的版本
647
- const scriptPath = downloadRemoteUpgradeScript() || (() => {
648
- const local = getUpgradeScriptPath();
649
- if (!local)
650
- return null;
651
- console.log(`[qqbot] fireHotUpgrade: remote download failed, falling back to local script: ${local}`);
652
- return copyScriptToTemp(local) || local;
653
- })();
645
+ function fireHotUpgrade(targetVersion, pkg, useLocal) {
646
+ // --local: 直接使用本地脚本,跳过远端下载
647
+ // 默认: 优先从远端下载升级脚本,避免使用本地可能过时的版本
648
+ const scriptPath = useLocal
649
+ ? (() => {
650
+ const local = getUpgradeScriptPath();
651
+ if (!local)
652
+ return null;
653
+ console.log(`[qqbot] fireHotUpgrade: --local specified, using local script: ${local}`);
654
+ return copyScriptToTemp(local) || local;
655
+ })()
656
+ : downloadRemoteUpgradeScript() || (() => {
657
+ const local = getUpgradeScriptPath();
658
+ if (!local)
659
+ return null;
660
+ console.log(`[qqbot] fireHotUpgrade: remote download failed, falling back to local script: ${local}`);
661
+ return copyScriptToTemp(local) || local;
662
+ })();
654
663
  if (!scriptPath)
655
664
  return { ok: false, reason: "no-script" };
656
665
  const cli = findCli();
@@ -669,6 +678,7 @@ function fireHotUpgrade(targetVersion) {
669
678
  "-File", scriptPath,
670
679
  "-NoRestart",
671
680
  ...(targetVersion ? ["-Version", targetVersion] : []),
681
+ ...(pkg ? ["-Pkg", pkg] : []),
672
682
  ];
673
683
  }
674
684
  else {
@@ -677,9 +687,9 @@ function fireHotUpgrade(targetVersion) {
677
687
  if (!bash)
678
688
  return { ok: false, reason: "no-bash" };
679
689
  shell = bash;
680
- shellArgs = [scriptPath, "--no-restart", ...(targetVersion ? ["--version", targetVersion] : [])];
690
+ shellArgs = [scriptPath, "--no-restart", ...(targetVersion ? ["--version", targetVersion] : []), ...(pkg ? ["--pkg", pkg] : [])];
681
691
  }
682
- console.log(`[qqbot] fireHotUpgrade: shell=${shell}, script=${scriptPath}, cli=${cli}, target=${targetVersion || "latest"}`);
692
+ console.log(`[qqbot] fireHotUpgrade: shell=${shell}, script=${scriptPath}, cli=${cli}, target=${targetVersion || "latest"}, pkg=${pkg || "default"}`);
683
693
  // 异步执行升级脚本
684
694
  execFile(shell, shellArgs, {
685
695
  timeout: 120_000,
@@ -786,11 +796,13 @@ registerCommand({
786
796
  `/bot-upgrade 检查是否有新版本`,
787
797
  `/bot-upgrade --latest 确认升级到最新版本(需 upgradeMode=hot-reload)`,
788
798
  `/bot-upgrade --version X 升级到指定版本(需 upgradeMode=hot-reload)`,
799
+ `/bot-upgrade --pkg scope/name 指定 npm 包(如 ryantest/openclaw-qqbot)`,
789
800
  `/bot-upgrade --force 强制重新安装当前版本(需 upgradeMode=hot-reload)`,
801
+ `/bot-upgrade --local 使用本地升级脚本(跳过远端下载)`,
790
802
  ].join("\n"),
791
803
  handler: async (ctx) => {
792
804
  const url = ctx.accountConfig?.upgradeUrl || DEFAULT_UPGRADE_URL;
793
- const upgradeMode = ctx.accountConfig?.upgradeMode || "doc";
805
+ const upgradeMode = ctx.accountConfig?.upgradeMode || "hot-reload";
794
806
  const args = ctx.args.trim();
795
807
  const info = await getUpdateInfo();
796
808
  const GITHUB_URL = "https://github.com/tencent-connect/openclaw-qqbot/";
@@ -834,7 +846,9 @@ registerCommand({
834
846
  }
835
847
  let isForce = false;
836
848
  let isLatest = false;
849
+ let isLocal = false;
837
850
  let versionArg;
851
+ let pkgArg;
838
852
  const tokens = args ? args.split(/\s+/).filter(Boolean) : [];
839
853
  for (let i = 0; i < tokens.length; i += 1) {
840
854
  const t = tokens[i];
@@ -846,6 +860,27 @@ registerCommand({
846
860
  isLatest = true;
847
861
  continue;
848
862
  }
863
+ if (t === "--local") {
864
+ isLocal = true;
865
+ continue;
866
+ }
867
+ if (t === "--pkg") {
868
+ const next = tokens[i + 1];
869
+ if (!next || next.startsWith("--")) {
870
+ return `❌ 参数错误:--pkg 需要包名\n\n示例:/bot-upgrade --pkg ryantest/openclaw-qqbot`;
871
+ }
872
+ pkgArg = next;
873
+ i += 1;
874
+ continue;
875
+ }
876
+ if (t.startsWith("--pkg=")) {
877
+ const v = t.slice("--pkg=".length).trim();
878
+ if (!v) {
879
+ return `❌ 参数错误:--pkg 需要包名\n\n示例:/bot-upgrade --pkg ryantest/openclaw-qqbot`;
880
+ }
881
+ pkgArg = v;
882
+ continue;
883
+ }
849
884
  if (t === "--version") {
850
885
  const next = tokens[i + 1];
851
886
  if (!next || next.startsWith("--")) {
@@ -904,9 +939,17 @@ registerCommand({
904
939
  `🌟官方 GitHub 仓库:[点击前往](${GITHUB_URL})`,
905
940
  ].join("\n");
906
941
  }
942
+ // 解析 npm 包名:--pkg 参数 > 配置项 upgradePkg > 默认
943
+ // 支持 "scope/name"(自动补 @)和 "@scope/name" 两种格式
944
+ let upgradePkg = pkgArg || ctx.accountConfig?.upgradePkg;
945
+ if (upgradePkg) {
946
+ upgradePkg = upgradePkg.trim();
947
+ if (!upgradePkg.startsWith("@"))
948
+ upgradePkg = `@${upgradePkg}`;
949
+ }
907
950
  // ── --version 指定版本:先校验版本号是否存在 ──
908
951
  if (versionArg) {
909
- const exists = await checkVersionExists(versionArg);
952
+ const exists = await checkVersionExists(versionArg, upgradePkg);
910
953
  if (!exists) {
911
954
  return `❌ 版本 ${versionArg} 不存在,请检查版本号`;
912
955
  }
@@ -951,7 +994,7 @@ registerCommand({
951
994
  // 热更新前保存凭证快照,防止更新过程被打断导致 appId/secret 丢失
952
995
  preUpgradeCredentialBackup(ctx.accountId, ctx.appId);
953
996
  // 异步执行升级
954
- const startResult = fireHotUpgrade(targetVersion);
997
+ const startResult = fireHotUpgrade(targetVersion, upgradePkg, isLocal);
955
998
  if (!startResult.ok) {
956
999
  _upgrading = false;
957
1000
  if (startResult.reason === "no-script") {
@@ -1,4 +1,4 @@
1
- import { getRequestTarget } from "../request-context.js";
1
+ import { getRequestTarget, getRequestAccountId } from "../request-context.js";
2
2
  // ========== JSON Schema ==========
3
3
  const RemindSchema = {
4
4
  type: "object",
@@ -100,7 +100,7 @@ function generateJobName(content) {
100
100
  /**
101
101
  * 构建一次性提醒的 cron 工具参数
102
102
  */
103
- function buildOnceJob(params, delayMs, to) {
103
+ function buildOnceJob(params, delayMs, to, accountId) {
104
104
  const atMs = Date.now() + delayMs;
105
105
  const content = params.content;
106
106
  const name = params.name || generateJobName(content);
@@ -115,9 +115,12 @@ function buildOnceJob(params, delayMs, to) {
115
115
  payload: {
116
116
  kind: "agentTurn",
117
117
  message: buildReminderPrompt(content),
118
- deliver: true,
118
+ },
119
+ delivery: {
120
+ mode: "announce",
119
121
  channel: "qqbot",
120
122
  to,
123
+ accountId,
121
124
  },
122
125
  },
123
126
  };
@@ -125,7 +128,7 @@ function buildOnceJob(params, delayMs, to) {
125
128
  /**
126
129
  * 构建周期提醒的 cron 工具参数
127
130
  */
128
- function buildCronJob(params, to) {
131
+ function buildCronJob(params, to, accountId) {
129
132
  const content = params.content;
130
133
  const name = params.name || generateJobName(content);
131
134
  const tz = params.timezone || "Asia/Shanghai";
@@ -139,9 +142,12 @@ function buildCronJob(params, to) {
139
142
  payload: {
140
143
  kind: "agentTurn",
141
144
  message: buildReminderPrompt(content),
142
- deliver: true,
145
+ },
146
+ delivery: {
147
+ mode: "announce",
143
148
  channel: "qqbot",
144
149
  to,
150
+ accountId,
145
151
  },
146
152
  },
147
153
  };
@@ -211,15 +217,17 @@ export function registerRemindTool(api) {
211
217
  if (!resolvedTo) {
212
218
  return json({ error: "action=add 时无法确定投递目标地址。请稍后重试。" });
213
219
  }
220
+ // 从请求上下文获取当前账户 ID(多账户场景),fallback 到 "default"
221
+ const resolvedAccountId = getRequestAccountId() || "default";
214
222
  if (!p.time) {
215
223
  return json({ error: "action=add 时 time(时间)为必填参数。示例:\"5m\"、\"1h30m\"、\"0 8 * * *\"" });
216
224
  }
217
225
  // 判断是 cron 表达式还是相对时间
218
226
  if (isCronExpression(p.time)) {
219
227
  // 周期提醒
220
- const cronJob = buildCronJob(p, resolvedTo);
228
+ const cronJob = buildCronJob(p, resolvedTo, resolvedAccountId);
221
229
  return json({
222
- _instruction: "请立即使用 cron 工具执行以下参数,然后回复用户提醒已设置",
230
+ _instruction: "请立即使用 cron 工具,将 cronParams 的值原样作为参数传入(不要修改或省略任何字段,特别是 delivery.accountId),然后回复用户提醒已设置",
223
231
  cronParams: cronJob,
224
232
  summary: `⏰ 周期提醒: "${p.content}" (${p.time}, tz=${p.timezone || "Asia/Shanghai"})`,
225
233
  });
@@ -236,9 +244,9 @@ export function registerRemindTool(api) {
236
244
  if (delayMs < 30_000) {
237
245
  return json({ error: "提醒时间不能少于 30 秒" });
238
246
  }
239
- const onceJob = buildOnceJob(p, delayMs, resolvedTo);
247
+ const onceJob = buildOnceJob(p, delayMs, resolvedTo, resolvedAccountId);
240
248
  return json({
241
- _instruction: "请立即使用 cron 工具执行以下参数,然后回复用户提醒已设置",
249
+ _instruction: "请立即使用 cron 工具,将 cronParams 的值原样作为参数传入(不要修改或省略任何字段,特别是 delivery.accountId),然后回复用户提醒已设置",
242
250
  cronParams: onceJob,
243
251
  summary: `⏰ ${formatDelay(delayMs)}后提醒: "${p.content}"`,
244
252
  });
@@ -92,10 +92,17 @@ export interface QQBotAccountConfig {
92
92
  upgradeUrl?: string;
93
93
  /**
94
94
  * /bot-upgrade 指令的行为模式
95
- * - "doc":展示升级文档链接(默认,安全模式)
96
- * - "hot-reload":检测到新版本时直接执行 npm 升级脚本进行热更新
95
+ * - "doc":展示升级文档链接(安全模式)
96
+ * - "hot-reload":检测到新版本时直接执行 npm 升级脚本进行热更新(默认)
97
97
  */
98
98
  upgradeMode?: "doc" | "hot-reload";
99
+ /**
100
+ * /bot-upgrade 热更新时使用的 npm 包名
101
+ * 支持 "scope/name"(自动补 @)或 "@scope/name" 格式
102
+ * 默认: "@tencent-connect/openclaw-qqbot"
103
+ * 示例: "ryantest/openclaw-qqbot"
104
+ */
105
+ upgradePkg?: string;
99
106
  /**
100
107
  * 出站消息合并回复(debounce)配置
101
108
  * 当短时间内收到多次 deliver 时,将文本合并为一条消息发送,避免消息轰炸
@@ -30,5 +30,7 @@ export declare function getUpdateInfo(): Promise<UpdateInfo>;
30
30
  /**
31
31
  * 检查指定版本是否存在于 npm registry
32
32
  * 用于 /bot-upgrade --version 的前置校验
33
+ * @param version 要检查的版本号
34
+ * @param pkgName 可选的包名(如 "@ryantest/openclaw-qqbot"),默认使用内置包名
33
35
  */
34
- export declare function checkVersionExists(version: string): Promise<boolean>;
36
+ export declare function checkVersionExists(version: string, pkgName?: string): Promise<boolean>;
@@ -99,9 +99,12 @@ export async function getUpdateInfo() {
99
99
  /**
100
100
  * 检查指定版本是否存在于 npm registry
101
101
  * 用于 /bot-upgrade --version 的前置校验
102
+ * @param version 要检查的版本号
103
+ * @param pkgName 可选的包名(如 "@ryantest/openclaw-qqbot"),默认使用内置包名
102
104
  */
103
- export async function checkVersionExists(version) {
104
- for (const baseUrl of REGISTRIES) {
105
+ export async function checkVersionExists(version, pkgName) {
106
+ const registries = pkgName ? buildRegistries(pkgName) : REGISTRIES;
107
+ for (const baseUrl of registries) {
105
108
  try {
106
109
  const url = `${baseUrl}/${version}`;
107
110
  const json = await fetchJson(url, 10_000);
@@ -114,6 +117,14 @@ export async function checkVersionExists(version) {
114
117
  }
115
118
  return false;
116
119
  }
120
+ /** 根据自定义包名构建 registry URL 列表 */
121
+ function buildRegistries(pkgName) {
122
+ const encoded = encodeURIComponent(pkgName);
123
+ return [
124
+ `https://registry.npmjs.org/${encoded}`,
125
+ `https://registry.npmmirror.com/${encoded}`,
126
+ ];
127
+ }
117
128
  function compareVersions(a, b) {
118
129
  const parse = (v) => {
119
130
  const clean = v.replace(/^v/, "");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tencent-connect/openclaw-qqbot",
3
- "version": "1.6.6-alpha.4",
3
+ "version": "1.6.7-beta.3",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",