@ryantest/openclaw-qqbot 0.0.3 → 1.6.6-alpha.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.
Files changed (89) hide show
  1. package/README.md +2 -15
  2. package/README.zh.md +3 -16
  3. package/dist/src/admin-resolver.d.ts +12 -6
  4. package/dist/src/admin-resolver.js +69 -34
  5. package/dist/src/api.d.ts +105 -1
  6. package/dist/src/api.js +164 -15
  7. package/dist/src/channel.js +13 -0
  8. package/dist/src/config.js +3 -10
  9. package/dist/src/deliver-debounce.d.ts +74 -0
  10. package/dist/src/deliver-debounce.js +174 -0
  11. package/dist/src/gateway.js +450 -248
  12. package/dist/src/image-server.d.ts +27 -8
  13. package/dist/src/image-server.js +179 -71
  14. package/dist/src/inbound-attachments.d.ts +3 -1
  15. package/dist/src/inbound-attachments.js +28 -14
  16. package/dist/src/outbound-deliver.js +77 -148
  17. package/dist/src/outbound.d.ts +6 -4
  18. package/dist/src/outbound.js +266 -442
  19. package/dist/src/reply-dispatcher.js +4 -4
  20. package/dist/src/request-context.d.ts +18 -0
  21. package/dist/src/request-context.js +30 -0
  22. package/dist/src/slash-commands.js +277 -32
  23. package/dist/src/startup-greeting.d.ts +5 -5
  24. package/dist/src/startup-greeting.js +32 -13
  25. package/dist/src/streaming.d.ts +244 -0
  26. package/dist/src/streaming.js +907 -0
  27. package/dist/src/tools/remind.js +11 -10
  28. package/dist/src/types.d.ts +101 -0
  29. package/dist/src/types.js +17 -1
  30. package/dist/src/update-checker.js +2 -8
  31. package/dist/src/utils/audio-convert.d.ts +9 -0
  32. package/dist/src/utils/audio-convert.js +51 -0
  33. package/dist/src/utils/chunked-upload.d.ts +59 -0
  34. package/dist/src/utils/chunked-upload.js +289 -0
  35. package/dist/src/utils/file-utils.d.ts +7 -1
  36. package/dist/src/utils/file-utils.js +24 -2
  37. package/dist/src/utils/media-send.d.ts +147 -0
  38. package/dist/src/utils/media-send.js +434 -0
  39. package/dist/src/utils/pkg-version.d.ts +5 -0
  40. package/dist/src/utils/pkg-version.js +51 -0
  41. package/dist/src/utils/ssrf-guard.d.ts +25 -0
  42. package/dist/src/utils/ssrf-guard.js +91 -0
  43. package/node_modules/ws/index.js +15 -6
  44. package/node_modules/ws/lib/permessage-deflate.js +6 -6
  45. package/node_modules/ws/lib/websocket-server.js +5 -5
  46. package/node_modules/ws/lib/websocket.js +6 -6
  47. package/node_modules/ws/package.json +4 -3
  48. package/node_modules/ws/wrapper.mjs +14 -1
  49. package/openclaw.plugin.json +1 -0
  50. package/package.json +11 -22
  51. package/scripts/postinstall-link-sdk.js +113 -0
  52. package/scripts/upgrade-via-npm.ps1 +161 -6
  53. package/scripts/upgrade-via-npm.sh +311 -104
  54. package/scripts/upgrade-via-source.sh +117 -0
  55. package/skills/qqbot-media/SKILL.md +9 -5
  56. package/skills/qqbot-remind/SKILL.md +3 -3
  57. package/src/admin-resolver.ts +76 -35
  58. package/src/api.ts +284 -12
  59. package/src/channel.ts +12 -0
  60. package/src/config.ts +3 -10
  61. package/src/deliver-debounce.ts +229 -0
  62. package/src/gateway.ts +277 -67
  63. package/src/image-server.ts +213 -77
  64. package/src/inbound-attachments.ts +32 -15
  65. package/src/outbound-deliver.ts +77 -157
  66. package/src/outbound.ts +304 -451
  67. package/src/reply-dispatcher.ts +4 -4
  68. package/src/request-context.ts +39 -0
  69. package/src/slash-commands.ts +303 -33
  70. package/src/startup-greeting.ts +35 -13
  71. package/src/streaming.ts +1096 -0
  72. package/src/tools/remind.ts +15 -11
  73. package/src/types.ts +111 -0
  74. package/src/update-checker.ts +2 -7
  75. package/src/utils/audio-convert.ts +56 -0
  76. package/src/utils/chunked-upload.ts +419 -0
  77. package/src/utils/file-utils.ts +28 -2
  78. package/src/utils/media-send.ts +563 -0
  79. package/src/utils/pkg-version.ts +54 -0
  80. package/src/utils/ssrf-guard.ts +102 -0
  81. package/clawdbot.plugin.json +0 -16
  82. package/dist/src/user-messages.d.ts +0 -8
  83. package/dist/src/user-messages.js +0 -8
  84. package/moltbot.plugin.json +0 -16
  85. package/scripts/upgrade-via-alt-pkg.sh +0 -307
  86. package/src/bot-logs-2026-03-21T11-21-47(2).txt +0 -46
  87. package/src/gateway.log +0 -43
  88. package/src/openclaw-2026-03-21.log +0 -3729
  89. package/src/user-messages.ts +0 -7
@@ -1,24 +1,30 @@
1
1
  #!/bin/bash
2
2
 
3
- # qqbot 通过 npm 包升级(纯文件操作版本)
3
+ # qqbot 通过 openclaw 原生插件指令升级
4
4
  #
5
- # 默认只做文件替换,不修改 openclaw.json 配置文件。
6
- # 但如果提供了 --appid/--secret 参数(首次安装场景),
7
- # 则在文件安装完成后自动写入通道配置。
5
+ # 使用 openclaw plugins install/update 原生命令进行安装和升级,
6
+ # 保留 appid/secret 配置写入、热更新 (--no-restart)、结构化输出等功能。
7
+ #
8
+ # 升级策略:
9
+ # 1. 已安装(plugins.installs 有记录)→ openclaw plugins update
10
+ # 2. 未安装 / update 失败 → 删除旧目录 + openclaw plugins install
8
11
  #
9
12
  # 用法:
10
13
  # upgrade-via-npm.sh # 升级到 latest(默认)
11
14
  # upgrade-via-npm.sh --version <version> # 升级到指定版本
12
15
  # upgrade-via-npm.sh --self-version # 升级到当前仓库 package.json 版本
13
16
  # upgrade-via-npm.sh --appid <appid> --secret <secret> # 首次安装时配置 appid/secret
14
- # upgrade-via-npm.sh --no-restart # 只做文件替换,不重启 gateway(供热更指令使用)
17
+ # upgrade-via-npm.sh --no-restart # 只做文件替换,不重启 gateway(供热更指令使用)
15
18
 
16
19
  set -eo pipefail
17
20
 
18
21
  PKG_NAME="@tencent-connect/openclaw-qqbot"
22
+ PLUGIN_ID="openclaw-qqbot"
19
23
  INSTALL_SRC=""
24
+ TARGET_VERSION=""
20
25
  APPID=""
21
26
  SECRET=""
27
+ STREAMING=""
22
28
  NO_RESTART=false
23
29
  SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
24
30
  PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
@@ -45,6 +51,7 @@ print_usage() {
45
51
  echo ""
46
52
  echo " --appid <appid> QQ机器人 appid(首次安装时必填)"
47
53
  echo " --secret <secret> QQ机器人 secret(首次安装时必填)"
54
+ echo " --streaming <yes|no> 是否启用流式消息(默认: no,仅 C2C 私聊)"
48
55
  echo ""
49
56
  echo "也可以通过环境变量设置:"
50
57
  echo " QQBOT_APPID QQ机器人 appid"
@@ -56,16 +63,19 @@ while [[ $# -gt 0 ]]; do
56
63
  case "$1" in
57
64
  --tag)
58
65
  [ -z "$2" ] && echo "❌ --tag 需要参数" && exit 1
66
+ TARGET_VERSION="$2"
59
67
  INSTALL_SRC="${PKG_NAME}@$2"
60
68
  shift 2
61
69
  ;;
62
70
  --version)
63
71
  [ -z "$2" ] && echo "❌ --version 需要参数" && exit 1
72
+ TARGET_VERSION="$2"
64
73
  INSTALL_SRC="${PKG_NAME}@$2"
65
74
  shift 2
66
75
  ;;
67
76
  --self-version)
68
77
  [ -z "$LOCAL_VERSION" ] && echo "❌ 无法从 package.json 读取版本" && exit 1
78
+ TARGET_VERSION="$LOCAL_VERSION"
69
79
  INSTALL_SRC="${PKG_NAME}@${LOCAL_VERSION}"
70
80
  shift 1
71
81
  ;;
@@ -79,6 +89,11 @@ while [[ $# -gt 0 ]]; do
79
89
  SECRET="$2"
80
90
  shift 2
81
91
  ;;
92
+ --streaming)
93
+ [ -z "$2" ] && echo "❌ --streaming 需要参数" && exit 1
94
+ STREAMING="$2"
95
+ shift 2
96
+ ;;
82
97
  --no-restart)
83
98
  NO_RESTART=true
84
99
  shift 1
@@ -100,7 +115,7 @@ if [ -z "$APPID" ] && [ -z "$SECRET" ] && [ -n "$QQBOT_TOKEN" ]; then
100
115
  SECRET="${QQBOT_TOKEN#*:}"
101
116
  fi
102
117
 
103
- # 检测 CLI(仅用于确定 extensions 目录路径)
118
+ # 检测 CLI
104
119
  CMD=""
105
120
  for name in openclaw clawdbot moltbot; do
106
121
  command -v "$name" &>/dev/null && CMD="$name" && break
@@ -109,113 +124,267 @@ done
109
124
 
110
125
  EXTENSIONS_DIR="$HOME/.$CMD/extensions"
111
126
 
127
+ # 检测 openclaw 版本
128
+ OPENCLAW_VERSION="$($CMD --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+(\.[0-9]+)?' | head -1 || true)"
129
+
112
130
  echo "==========================================="
113
- echo " qqbot npm 升级: $INSTALL_SRC"
131
+ echo " qqbot 升级: $INSTALL_SRC"
132
+ echo " openclaw 版本: ${OPENCLAW_VERSION:-unknown}"
114
133
  echo "==========================================="
115
134
  echo ""
116
135
 
117
- # [1/3] 下载并安装新版本到临时目录
118
- echo "[1/3] 下载新版本..."
119
- TMPDIR_PACK=$(mktemp -d)
120
- EXTRACT_DIR=$(mktemp -d)
121
- trap "rm -rf '$TMPDIR_PACK' '$EXTRACT_DIR'" EXIT
122
-
123
- cd "$TMPDIR_PACK"
124
- # registry fallback:npmjs.org → npmmirror(国内镜像)→ 默认 registry
125
- PACK_OK=false
126
- for _registry in "https://registry.npmjs.org/" "https://registry.npmmirror.com/" ""; do
127
- if [ -n "$_registry" ]; then
128
- echo " 尝试 registry: $_registry"
129
- npm pack "$INSTALL_SRC" --registry "$_registry" --quiet 2>&1 && PACK_OK=true && break
130
- else
131
- echo " 尝试默认 registry..."
132
- npm pack "$INSTALL_SRC" --quiet 2>&1 && PACK_OK=true && break
136
+ # 记录升级前的版本
137
+ OLD_VERSION=""
138
+ OLD_PKG="$EXTENSIONS_DIR/$PLUGIN_ID/package.json"
139
+ if [ -f "$OLD_PKG" ]; then
140
+ OLD_VERSION="$(node -e "
141
+ try {
142
+ const v = JSON.parse(require('fs').readFileSync('$OLD_PKG', 'utf8')).version;
143
+ if (v) process.stdout.write(String(v));
144
+ } catch {}
145
+ " 2>/dev/null || true)"
146
+ echo " 当前版本: ${OLD_VERSION:-unknown}"
147
+ fi
148
+
149
+ # [1/4] 通过 openclaw 原生指令安装/升级
150
+ echo ""
151
+ echo "[1/4] 安装/升级插件..."
152
+
153
+ # ── 兼容 openclaw 3.23+ 配置严格校验 ──
154
+ # 3.23+ 在 plugins install/update 时会校验整个配置文件,
155
+ # 如果 channels.qqbot 已存在但 qqbot 插件尚未加载,校验会失败。
156
+ # 解决:install/update 前临时移除 channels.qqbot,成功后恢复。
157
+ CONFIG_FILE="$HOME/.$CMD/$CMD.json"
158
+ QQBOT_CHANNEL_BACKUP=""
159
+ if [ -f "$CONFIG_FILE" ]; then
160
+ QQBOT_CHANNEL_BACKUP="$(node -e "
161
+ try {
162
+ const fs = require('fs');
163
+ const cfg = JSON.parse(fs.readFileSync('$CONFIG_FILE', 'utf8'));
164
+ if (cfg.channels && cfg.channels.qqbot) {
165
+ process.stdout.write(JSON.stringify(cfg.channels.qqbot));
166
+ delete cfg.channels.qqbot;
167
+ if (Object.keys(cfg.channels).length === 0) delete cfg.channels;
168
+ fs.writeFileSync('$CONFIG_FILE', JSON.stringify(cfg, null, 4) + '\n');
169
+ }
170
+ } catch {}
171
+ " 2>/dev/null || true)"
172
+ if [ -n "$QQBOT_CHANNEL_BACKUP" ]; then
173
+ echo " [兼容] 临时移除 channels.qqbot 以通过配置校验"
133
174
  fi
134
- done
135
- $PACK_OK || { echo "❌ npm pack 失败(所有 registry 均不可用)"; exit 1; }
136
- TGZ_FILE=$(ls -1 *.tgz 2>/dev/null | head -1)
137
- [ -z "$TGZ_FILE" ] && echo "❌ 未找到下载的 tgz 文件" && exit 1
138
- echo " 已下载: $TGZ_FILE"
139
-
140
- tar xzf "$TGZ_FILE" -C "$EXTRACT_DIR"
141
- PACKAGE_DIR="$EXTRACT_DIR/package"
142
- [ ! -d "$PACKAGE_DIR" ] && echo "❌ 解压失败,未找到 package 目录" && exit 1
143
-
144
- # 准备 staging 目录:放在 ~/.openclaw/ 下(extensions 的父目录),
145
- # 同一文件系统保证 mv 原子操作,同时避免 OpenClaw 扫描 extensions/ 时发现它。
146
- STAGING_DIR="$(dirname "$EXTENSIONS_DIR")/.qqbot-upgrade-staging"
147
- rm -rf "$STAGING_DIR"
148
- mkdir -p "$STAGING_DIR"
149
- cp -R "$PACKAGE_DIR/"* "$STAGING_DIR/"
150
-
151
- # 依赖处理:所有 production dependencies 都声明为 bundledDependencies,
152
- # npm pack 时已打包进 tgz,解压后 node_modules/ 已包含全部依赖,无需 npm install。
153
- # 注意:不能执行 npm install,否则会安装 peerDependencies(openclaw 平台及其 400+ 传递依赖),
154
- # 导致插件目录膨胀到 900MB+,而这些依赖在运行时由宿主 openclaw 提供。
155
- if [ -d "$STAGING_DIR/node_modules" ]; then
156
- BUNDLED_COUNT=$(ls -d "$STAGING_DIR/node_modules"/*/ "$STAGING_DIR/node_modules"/@*/*/ 2>/dev/null | wc -l | tr -d ' ')
157
- echo " bundled 依赖已就绪(${BUNDLED_COUNT} 个包)"
175
+ fi
176
+
177
+ # 恢复 channels.qqbot 的函数(install/update 完成后调用)
178
+ restore_qqbot_channel() {
179
+ if [ -n "$QQBOT_CHANNEL_BACKUP" ] && [ -f "$CONFIG_FILE" ]; then
180
+ node -e "
181
+ try {
182
+ const fs = require('fs');
183
+ const cfg = JSON.parse(fs.readFileSync('$CONFIG_FILE', 'utf8'));
184
+ if (!cfg.channels) cfg.channels = {};
185
+ cfg.channels.qqbot = $QQBOT_CHANNEL_BACKUP;
186
+ fs.writeFileSync('$CONFIG_FILE', JSON.stringify(cfg, null, 4) + '\n');
187
+ } catch {}
188
+ " 2>/dev/null || true
189
+ echo " [兼容] 已恢复 channels.qqbot 配置"
190
+ fi
191
+ }
192
+
193
+ UPGRADE_OK=false
194
+
195
+ # 检测安装状态:同时检查配置记录和磁盘目录
196
+ HAS_INSTALL_RECORD="$(node -e "
197
+ try {
198
+ const fs = require('fs');
199
+ const p = '$HOME/.$CMD/$CMD.json';
200
+ const cfg = JSON.parse(fs.readFileSync(p, 'utf8'));
201
+ const inst = cfg.plugins && cfg.plugins.installs && cfg.plugins.installs['$PLUGIN_ID'];
202
+ if (inst) process.stdout.write('yes');
203
+ } catch {}
204
+ " 2>/dev/null || true)"
205
+ HAS_PLUGIN_DIR=false
206
+ [ -d "$EXTENSIONS_DIR/$PLUGIN_ID" ] && [ -f "$EXTENSIONS_DIR/$PLUGIN_ID/package.json" ] && HAS_PLUGIN_DIR=true
207
+
208
+ # 决策矩阵:
209
+ # 配置有记录 + 目录存在 → update(最佳路径)
210
+ # 配置有记录 + 目录不存在 → 清理残留记录,走 install
211
+ # 配置无记录 + 目录存在 → 删目录,走 install(配置与文件不一致)
212
+ # 配置无记录 + 目录不存在 → 走 install(全新安装)
213
+ #
214
+ # 指定了具体版本(--version/--tag/--self-version)时:
215
+ # update 不支持指定版本,直接走 删除 + install
216
+
217
+ USE_UPDATE=false
218
+
219
+ if [ "$HAS_INSTALL_RECORD" = "yes" ] && [ "$HAS_PLUGIN_DIR" = "true" ] && [ -z "$TARGET_VERSION" ]; then
220
+ # 配置和目录都齐全,且未指定版本 → 走 update
221
+ USE_UPDATE=true
222
+ echo " [检测] 配置记录 ✓ | 插件目录 ✓ | 未指定版本 → 使用 update"
223
+ elif [ "$HAS_INSTALL_RECORD" = "yes" ] && [ "$HAS_PLUGIN_DIR" = "true" ]; then
224
+ echo " [检测] 配置记录 ✓ | 插件目录 ✓ | 指定版本 $TARGET_VERSION → 使用 reinstall"
225
+ elif [ "$HAS_INSTALL_RECORD" = "yes" ]; then
226
+ echo " [检测] 配置记录 ✓ | 插件目录 ✗ → 配置与文件不一致,使用 install"
227
+ elif [ "$HAS_PLUGIN_DIR" = "true" ]; then
228
+ echo " [检测] 配置记录 ✗ | 插件目录 ✓ → 目录残留,清理后 install"
158
229
  else
159
- echo " ⚠️ 未找到 bundled node_modules,尝试安装依赖..."
160
- NPM_TMP_CACHE=$(mktemp -d)
161
- (cd "$STAGING_DIR" && npm install --omit=dev --omit=peer --ignore-scripts --cache="$NPM_TMP_CACHE" --quiet 2>&1) || echo " ⚠️ 依赖安装失败"
162
- rm -rf "$NPM_TMP_CACHE"
230
+ echo " [检测] 配置记录 ✗ | 插件目录 ✗ → 全新安装"
163
231
  fi
164
232
 
165
- # 清理下载临时文件
166
- rm -rf "$TMPDIR_PACK" "$EXTRACT_DIR"
167
- cd "$HOME"
233
+ if [ "$USE_UPDATE" = "true" ]; then
234
+ echo " 尝试 update..."
235
+ if $CMD plugins update "$PLUGIN_ID" 2>&1; then
236
+ UPGRADE_OK=true
237
+ echo " ✅ update 成功"
238
+ else
239
+ echo " ⚠️ update 失败,回退到 reinstall..."
240
+ fi
241
+ fi
242
+
243
+ if [ "$UPGRADE_OK" != "true" ]; then
244
+ # 备份旧目录(而非直接删除),install 失败时可回滚
245
+ BACKUP_DIR=""
246
+ if [ -d "$EXTENSIONS_DIR/$PLUGIN_ID" ]; then
247
+ BACKUP_DIR="$EXTENSIONS_DIR/.openclaw-qqbot-backup-$$"
248
+ mv "$EXTENSIONS_DIR/$PLUGIN_ID" "$BACKUP_DIR"
249
+ echo " 已备份旧目录: $BACKUP_DIR"
250
+ fi
168
251
 
169
- # [2/3] 原子替换:使用 mv -T/rename 确保目录切换尽可能原子
170
- # 策略:先把 staging 放到 extensions/ 同级的临时名,再做单次 mv 替换
252
+ # 清理历史遗留名称(这些不需要回滚)
253
+ for dir_name in qqbot openclaw-qq; do
254
+ [ -d "$EXTENSIONS_DIR/$dir_name" ] && rm -rf "$EXTENSIONS_DIR/$dir_name" && echo " 已清理历史目录: $EXTENSIONS_DIR/$dir_name"
255
+ done
256
+
257
+ echo " 执行 install: $INSTALL_SRC"
258
+ if $CMD plugins install "$INSTALL_SRC" --pin 2>&1; then
259
+ UPGRADE_OK=true
260
+ echo " ✅ install 成功"
261
+ # install 成功,清理备份
262
+ if [ -n "$BACKUP_DIR" ] && [ -d "$BACKUP_DIR" ]; then
263
+ rm -rf "$BACKUP_DIR"
264
+ echo " 已清理旧版备份"
265
+ fi
266
+ # 清理 openclaw CLI install 可能留下的额外 backup 目录
267
+ find "$EXTENSIONS_DIR" -maxdepth 1 -name ".openclaw-qqbot-backup-*" -exec rm -rf {} + 2>/dev/null || true
268
+ else
269
+ echo " ❌ install 失败"
270
+ # 回滚:恢复旧目录
271
+ if [ -n "$BACKUP_DIR" ] && [ -d "$BACKUP_DIR" ]; then
272
+ mv "$BACKUP_DIR" "$EXTENSIONS_DIR/$PLUGIN_ID"
273
+ echo " ↩️ 已回滚到旧版本"
274
+ fi
275
+ restore_qqbot_channel
276
+ echo "QQBOT_NEW_VERSION=unknown"
277
+ echo "QQBOT_REPORT=❌ QQBot 安装失败(已回滚到旧版本),请检查网络和 npm registry"
278
+ exit 1
279
+ fi
280
+ fi
281
+
282
+ # install/update 完成,恢复 channels.qqbot
283
+ restore_qqbot_channel
284
+
285
+ # [2/4] 验证安装
171
286
  echo ""
172
- echo "[2/3] 原子替换插件目录..."
173
- TARGET_DIR="$EXTENSIONS_DIR/openclaw-qqbot"
174
- OLD_DIR="$(dirname "$EXTENSIONS_DIR")/.qqbot-upgrade-old"
287
+ echo "[2/4] 验证安装..."
175
288
 
176
- rm -rf "$OLD_DIR"
289
+ PKG_JSON="$EXTENSIONS_DIR/$PLUGIN_ID/package.json"
290
+ if [ -f "$PKG_JSON" ]; then
291
+ NEW_VERSION="$(node -e "process.stdout.write(JSON.parse(require('fs').readFileSync(process.argv[1],'utf8')).version||'')" "$PKG_JSON" 2>/dev/null || true)"
292
+ fi
177
293
 
178
- # 先把 staging 目录移到 extensions/ 下的临时位置(同文件系统,确保 mv 是 rename 操作)
179
- STAGING_IN_EXT="$EXTENSIONS_DIR/.openclaw-qqbot-new"
180
- rm -rf "$STAGING_IN_EXT"
181
- mv "$STAGING_DIR" "$STAGING_IN_EXT"
294
+ # Preflight 检查
295
+ PREFLIGHT_OK=true
296
+ TARGET_DIR="$EXTENSIONS_DIR/$PLUGIN_ID"
182
297
 
183
- if [ -d "$TARGET_DIR" ]; then
184
- # 使用连续两个 mv 但中间零操作,最小化目录不存在的时间窗口
185
- mv "$TARGET_DIR" "$OLD_DIR" && mv "$STAGING_IN_EXT" "$TARGET_DIR"
298
+ if [ -z "$NEW_VERSION" ]; then
299
+ echo " ❌ 无法读取新版本号"
300
+ PREFLIGHT_OK=false
186
301
  else
187
- mv "$STAGING_IN_EXT" "$TARGET_DIR"
302
+ echo " 版本号: $NEW_VERSION"
188
303
  fi
189
- rm -rf "$OLD_DIR"
190
304
 
191
- # 清理可能残留的旧版 staging 目录(extensions 内外都清理)
192
- rm -rf "$EXTENSIONS_DIR/openclaw-qqbot.staging"
193
- rm -rf "$EXTENSIONS_DIR/.qqbot-upgrade-staging"
194
- rm -rf "$EXTENSIONS_DIR/.qqbot-upgrade-old"
305
+ # 入口文件
306
+ ENTRY_FILE=""
307
+ for candidate in "dist/index.js" "index.js"; do
308
+ if [ -f "$TARGET_DIR/$candidate" ]; then
309
+ ENTRY_FILE="$candidate"
310
+ break
311
+ fi
312
+ done
313
+ if [ -z "$ENTRY_FILE" ]; then
314
+ echo " ❌ 缺少入口文件(dist/index.js 或 index.js)"
315
+ PREFLIGHT_OK=false
316
+ else
317
+ echo " ✅ 入口文件: $ENTRY_FILE"
318
+ fi
195
319
 
196
- # 同时清理历史遗留的其他目录名
197
- for dir_name in qqbot openclaw-qq; do
198
- [ -d "$EXTENSIONS_DIR/$dir_name" ] && rm -rf "$EXTENSIONS_DIR/$dir_name"
320
+ # 核心目录
321
+ if [ -d "$TARGET_DIR/dist/src" ]; then
322
+ CORE_JS_COUNT=$(find "$TARGET_DIR/dist/src" -name "*.js" -type f 2>/dev/null | wc -l | tr -d ' ')
323
+ echo " ✅ dist/src/ 包含 ${CORE_JS_COUNT} 个 JS 文件"
324
+ if [ "$CORE_JS_COUNT" -lt 5 ]; then
325
+ echo " ❌ JS 文件数量异常偏少(预期 ≥ 5,实际 ${CORE_JS_COUNT})"
326
+ PREFLIGHT_OK=false
327
+ fi
328
+ else
329
+ echo " ❌ 缺少核心目录 dist/src/"
330
+ PREFLIGHT_OK=false
331
+ fi
332
+
333
+ # 关键模块
334
+ MISSING_MODULES=""
335
+ for module in "dist/src/gateway.js" "dist/src/api.js" "dist/src/admin-resolver.js"; do
336
+ if [ ! -f "$TARGET_DIR/$module" ]; then
337
+ MISSING_MODULES="$MISSING_MODULES $module"
338
+ fi
199
339
  done
200
- echo " 已安装到: $TARGET_DIR"
340
+ if [ -n "$MISSING_MODULES" ]; then
341
+ echo " ❌ 缺少关键模块:$MISSING_MODULES"
342
+ PREFLIGHT_OK=false
343
+ else
344
+ echo " ✅ 关键模块完整"
345
+ fi
346
+
347
+ # bundled 依赖
348
+ if [ -d "$TARGET_DIR/node_modules" ]; then
349
+ BUNDLED_OK=true
350
+ for dep in "ws" "silk-wasm"; do
351
+ if [ ! -d "$TARGET_DIR/node_modules/$dep" ]; then
352
+ echo " ⚠️ bundled 依赖缺失: $dep"
353
+ BUNDLED_OK=false
354
+ fi
355
+ done
356
+ if $BUNDLED_OK; then
357
+ echo " ✅ 核心 bundled 依赖完整"
358
+ fi
359
+ fi
360
+
361
+ if [ "$PREFLIGHT_OK" != "true" ]; then
362
+ echo ""
363
+ echo "❌ 验证未通过"
364
+ echo "QQBOT_NEW_VERSION=unknown"
365
+ echo "QQBOT_REPORT=⚠️ QQBot 升级异常,验证未通过"
366
+ exit 1
367
+ fi
368
+ echo " ✅ 验证全部通过"
369
+
370
+ # 确保 openclaw/plugin-sdk 可解析:
371
+ # openclaw plugins install 不会执行 npm lifecycle scripts,
372
+ # 需要手动调用 postinstall-link-sdk.js 创建 node_modules/openclaw → 全局 openclaw 的符号链接
373
+ POSTINSTALL_SCRIPT="$TARGET_DIR/scripts/postinstall-link-sdk.js"
374
+ if [ -f "$POSTINSTALL_SCRIPT" ]; then
375
+ echo " 执行 postinstall-link-sdk..."
376
+ if node "$POSTINSTALL_SCRIPT" 2>&1; then
377
+ echo " ✅ plugin-sdk 链接就绪"
378
+ else
379
+ echo " ⚠️ postinstall-link-sdk 失败,插件可能无法加载"
380
+ fi
381
+ fi
201
382
 
202
- # [3/3] 输出新版本号和升级报告(供调用方解析)
383
+ # [3/4] 输出结构化信息(供 TS handler 解析)
203
384
  echo ""
204
- echo "[3/3] 验证安装..."
205
- NEW_VERSION="$(node -e "
206
- try {
207
- const fs = require('fs');
208
- const path = require('path');
209
- const p = path.join('$EXTENSIONS_DIR', 'openclaw-qqbot', 'package.json');
210
- if (fs.existsSync(p)) {
211
- const v = JSON.parse(fs.readFileSync(p, 'utf8')).version;
212
- if (v) { process.stdout.write(v); process.exit(0); }
213
- }
214
- } catch {}
215
- " 2>/dev/null || true)"
385
+ echo "[3/4] 升级结果..."
216
386
  echo "QQBOT_NEW_VERSION=${NEW_VERSION:-unknown}"
217
387
 
218
- # 输出结构化升级报告(QQBOT_REPORT=...),供 TS handler 解析后直接回复用户
219
388
  if [ -n "$NEW_VERSION" ] && [ "$NEW_VERSION" != "unknown" ]; then
220
389
  echo "QQBOT_REPORT=✅ QQBot 升级完成: v${NEW_VERSION}"
221
390
  else
@@ -224,13 +393,10 @@ fi
224
393
 
225
394
  echo ""
226
395
  echo "==========================================="
227
- echo " ✅ 文件安装完成"
396
+ echo " ✅ 安装完成"
228
397
  echo "==========================================="
229
398
 
230
- # --no-restart 模式(热更新场景):文件替换完成后立即退出,
231
- # 让调用方尽快触发 gateway restart,避免 openclaw 配置轮询
232
- # 在旧进程中检测到插件变更产生 "plugin not found" warning 刷屏。
233
- # appid/secret 配置在热更新场景下已经存在,无需重新写入。
399
+ # --no-restart 模式(热更新场景):立即退出,让调用方触发 gateway restart
234
400
  if [ "$NO_RESTART" = "true" ]; then
235
401
  echo ""
236
402
  echo "[跳过重启] --no-restart 已指定,脚本立即退出以便调用方触发 gateway restart"
@@ -239,7 +405,7 @@ fi
239
405
 
240
406
  # 以下步骤仅在非热更新(手动执行)场景中执行
241
407
 
242
- # [4/4] 配置 appid/secret(仅在提供了参数时执行)
408
+ # [配置] appid/secret(仅在提供了参数时执行)
243
409
  if [ -n "$APPID" ] && [ -n "$SECRET" ]; then
244
410
  echo ""
245
411
  echo "[配置] 写入 qqbot 通道配置..."
@@ -266,10 +432,9 @@ if [ -n "$APPID" ] && [ -n "$SECRET" ]; then
266
432
 
267
433
  if [ "$CURRENT_TOKEN" = "$DESIRED_TOKEN" ]; then
268
434
  echo " ✅ 当前配置已是目标值,跳过写入"
269
- elif $CMD channels add --channel qqbot --token "$DESIRED_TOKEN" 2>&1; then
270
- echo " ✅ 通道配置写入成功"
271
435
  else
272
- echo " ⚠️ $CMD channels add 失败,尝试直接编辑配置文件..."
436
+ # qqbot 是插件自定义通道,openclaw channels add --channel 不支持,
437
+ # 直接编辑配置文件写入 channels.qqbot
273
438
  CONFIG_FILE="$HOME/.$CMD/$CMD.json"
274
439
  if [ -f "$CONFIG_FILE" ] && node -e "
275
440
  const fs = require('fs');
@@ -280,10 +445,10 @@ if [ -n "$APPID" ] && [ -n "$SECRET" ]; then
280
445
  cfg.channels.qqbot.clientSecret = '$SECRET';
281
446
  fs.writeFileSync('$CONFIG_FILE', JSON.stringify(cfg, null, 4) + '\n');
282
447
  " 2>&1; then
283
- echo " ✅ 通道配置写入成功(直接编辑配置文件)"
448
+ echo " ✅ 通道配置写入成功"
284
449
  else
285
- echo " ❌ 配置写入失败,请手动配置:"
286
- echo " $CMD channels add --channel qqbot --token \"${APPID}:${SECRET}\""
450
+ echo " ❌ 配置写入失败,请手动编辑 $CONFIG_FILE 添加 channels.qqbot:"
451
+ echo " { \"channels\": { \"qqbot\": { \"appId\": \"$APPID\", \"clientSecret\": \"...\" } } }"
287
452
  fi
288
453
  fi
289
454
  elif [ -n "$APPID" ] || [ -n "$SECRET" ]; then
@@ -291,11 +456,53 @@ elif [ -n "$APPID" ] || [ -n "$SECRET" ]; then
291
456
  echo "⚠️ --appid 和 --secret 必须同时提供"
292
457
  fi
293
458
 
294
- # [5/5] 重启 gateway 使新版本生效
459
+ # [配置] streaming(流式消息)
460
+ if [ -n "$STREAMING" ]; then
461
+ echo ""
462
+ echo "[配置] 写入 streaming(流式消息)配置..."
463
+ STREAMING_VALUE=""
464
+ if [ "$STREAMING" = "yes" ] || [ "$STREAMING" = "y" ] || [ "$STREAMING" = "true" ]; then
465
+ STREAMING_VALUE="true"
466
+ else
467
+ STREAMING_VALUE="false"
468
+ fi
469
+
470
+ CONFIG_FILE="$HOME/.$CMD/$CMD.json"
471
+ if [ -f "$CONFIG_FILE" ] && node -e "
472
+ const fs = require('fs');
473
+ const cfg = JSON.parse(fs.readFileSync('$CONFIG_FILE', 'utf8'));
474
+ if (!cfg.channels) cfg.channels = {};
475
+ if (!cfg.channels.qqbot) cfg.channels.qqbot = {};
476
+ const target = $STREAMING_VALUE;
477
+ if (cfg.channels.qqbot.streaming === target) process.exit(0);
478
+ cfg.channels.qqbot.streaming = target;
479
+ fs.writeFileSync('$CONFIG_FILE', JSON.stringify(cfg, null, 4) + '\n');
480
+ " 2>&1; then
481
+ echo " ✅ streaming 配置写入成功 (streaming=$STREAMING_VALUE)"
482
+ else
483
+ echo " ⚠️ streaming 配置写入失败,不影响后续运行"
484
+ fi
485
+ fi
486
+
487
+ # [4/4] 重启 gateway 使新版本生效
295
488
  echo ""
489
+
490
+ # 手动升级场景:提前写入 startup-marker,阻止重启后 bot 重复推送升级通知
491
+ if [ -n "$NEW_VERSION" ] && [ "$NEW_VERSION" != "unknown" ]; then
492
+ MARKER_DIR="$HOME/.openclaw/qqbot/data"
493
+ mkdir -p "$MARKER_DIR"
494
+ MARKER_FILE="$MARKER_DIR/startup-marker.json"
495
+ NOW="$(date -u +%Y-%m-%dT%H:%M:%S.000Z 2>/dev/null || date +%Y-%m-%dT%H:%M:%SZ)"
496
+ echo "{\"version\":\"$NEW_VERSION\",\"startedAt\":\"$NOW\",\"greetedAt\":\"$NOW\"}" > "$MARKER_FILE"
497
+ fi
498
+
296
499
  echo "[重启] 重启 gateway 使新版本生效..."
297
500
  if $CMD gateway restart 2>&1; then
298
501
  echo " ✅ gateway 已重启"
502
+ echo ""
503
+ if [ -n "$NEW_VERSION" ] && [ "$NEW_VERSION" != "unknown" ]; then
504
+ echo "🎉 QQBot 插件已更新至 v${NEW_VERSION},在线等候你的吩咐。"
505
+ fi
299
506
  else
300
507
  echo " ⚠️ gateway 重启失败,请手动执行: $CMD gateway restart"
301
508
  fi