@tencent-connect/openclaw-qqbot 1.6.5-alpha.5 → 1.6.5-alpha.7

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.
@@ -87,6 +87,22 @@ export async function startGateway(ctx) {
87
87
  log?.info(`[qqbot:${account.accountId}] ${w}`);
88
88
  }
89
89
  }
90
+ // 预检 openclaw runtime 模块是否可正常解析(兼容性诊断)
91
+ // openclaw 3.23+ 存在 plugin-sdk/root-alias.cjs 回归 bug,
92
+ // 内置插件(qwen-portal-auth 等)全部加载失败,导致 AI agent 调用返回
93
+ // "Unable to resolve plugin runtime module"。提前检测并告警。
94
+ try {
95
+ const pluginRuntime = getQQBotRuntime();
96
+ if (pluginRuntime?.channel?.reply?.dispatchReplyWithBufferedBlockDispatcher) {
97
+ log?.info(`[qqbot:${account.accountId}] Runtime module preflight: OK`);
98
+ }
99
+ else {
100
+ log?.error(`[qqbot:${account.accountId}] ⚠️ Runtime preflight: dispatchReply API 不可用,AI 消息处理可能失败。请检查 openclaw 版本兼容性`);
101
+ }
102
+ }
103
+ catch (preflightErr) {
104
+ log?.error(`[qqbot:${account.accountId}] ⚠️ Runtime preflight failed: ${preflightErr}. AI 消息处理可能失败`);
105
+ }
90
106
  // 后台版本检查(供 /bot-version、/bot-upgrade 指令被动查询)
91
107
  triggerUpdateCheck(log);
92
108
  // 初始化 API 配置(markdown 支持)
@@ -883,8 +899,13 @@ export async function startGateway(ctx) {
883
899
  clearTimeout(timeoutId);
884
900
  timeoutId = null;
885
901
  }
886
- // 发送错误提示给用户,显示完整错误信息
887
902
  const errMsg = String(err);
903
+ // 兼容 openclaw 3.23+ 的 plugin-sdk/root-alias.cjs 模块解析失败
904
+ if (errMsg.includes("Unable to resolve plugin runtime module") || errMsg.includes("root-alias.cjs")) {
905
+ log?.error(`[qqbot:${account.accountId}] ⚠️ openclaw 框架 runtime 模块解析失败,可能是 openclaw 版本与 plugin-sdk 不兼容。请尝试: npm install -g openclaw@latest && openclaw gateway restart`);
906
+ await sendErrorMessage("⚠️ AI 服务暂时不可用:openclaw 框架运行时模块加载失败。\n\n请管理员执行:\nnpm install -g openclaw@latest\nopenclaw gateway restart\n\n斜杠命令(如 /bot-ping)不受影响。");
907
+ return;
908
+ }
888
909
  if (errMsg.includes("401") || errMsg.includes("key") || errMsg.includes("auth")) {
889
910
  log?.error(`[qqbot:${account.accountId}] AI auth error: ${errMsg}`);
890
911
  }
@@ -929,7 +950,15 @@ export async function startGateway(ctx) {
929
950
  }
930
951
  }
931
952
  catch (err) {
953
+ const errStr = String(err);
932
954
  log?.error(`[qqbot:${account.accountId}] Message processing failed: ${err}`);
955
+ // 兼容 openclaw 3.23+ runtime 模块解析失败:给用户发可操作的提示
956
+ if (errStr.includes("Unable to resolve plugin runtime module") || errStr.includes("root-alias.cjs")) {
957
+ try {
958
+ await sendErrorMessage("⚠️ AI 服务暂时不可用:openclaw 框架运行时模块加载失败。\n\n请管理员执行:\nnpm install -g openclaw@latest\nopenclaw gateway restart\n\n斜杠命令(如 /bot-ping)不受影响。");
959
+ }
960
+ catch { /* best-effort */ }
961
+ }
933
962
  }
934
963
  finally {
935
964
  // 无论成功/失败/超时,都停止输入状态续期
@@ -251,7 +251,7 @@ registerCommand({
251
251
  return lines.join("\n");
252
252
  },
253
253
  });
254
- const DEFAULT_UPGRADE_URL = "https://doc.weixin.qq.com/doc/w3_AKEAGQaeACgCNHrh1CbHzTAKtT2gB?scode=AJEAIQdfAAozxFEnLZAKEAGQaeACg";
254
+ const DEFAULT_UPGRADE_URL = "https://docs.qq.com/doc/DSGxOZk1oVnVKVkpq";
255
255
  function saveUpgradeGreetingTarget(accountId, appId, openid) {
256
256
  const safeAccountId = accountId.replace(/[^a-zA-Z0-9._-]/g, "_");
257
257
  const safeAppId = appId.replace(/[^a-zA-Z0-9._-]/g, "_");
@@ -561,6 +561,38 @@ function findPowerShell() {
561
561
  }
562
562
  return null;
563
563
  }
564
+ /**
565
+ * 将升级脚本复制到临时位置,避免升级过程中插件目录被清理后脚本丢失。
566
+ * 返回临时脚本路径,失败返回 null。
567
+ */
568
+ function copyScriptToTemp(scriptPath) {
569
+ try {
570
+ const ext = path.extname(scriptPath);
571
+ const tmpDir = path.join(getHomeDir(), ".openclaw", ".qqbot-upgrade-tmp");
572
+ fs.mkdirSync(tmpDir, { recursive: true });
573
+ const tmpScript = path.join(tmpDir, `upgrade-via-npm${ext}`);
574
+ fs.copyFileSync(scriptPath, tmpScript);
575
+ if (!isWindows()) {
576
+ fs.chmodSync(tmpScript, 0o755);
577
+ }
578
+ return tmpScript;
579
+ }
580
+ catch {
581
+ return null;
582
+ }
583
+ }
584
+ /**
585
+ * 清理临时升级脚本目录
586
+ */
587
+ function cleanupTempScript() {
588
+ try {
589
+ const tmpDir = path.join(getHomeDir(), ".openclaw", ".qqbot-upgrade-tmp");
590
+ fs.rmSync(tmpDir, { recursive: true, force: true });
591
+ }
592
+ catch {
593
+ // 非关键,静默忽略
594
+ }
595
+ }
564
596
  /**
565
597
  * 执行热更新:执行脚本(--no-restart) → 立即触发 gateway restart
566
598
  *
@@ -570,11 +602,15 @@ function findPowerShell() {
570
602
  * - 新进程启动时 getStartupGreeting() 检测到版本变更,自动通知管理员
571
603
  *
572
604
  * Windows 使用 PowerShell 执行 .ps1 脚本,Mac/Linux 使用 bash 执行 .sh 脚本。
605
+ *
606
+ * 安全机制:脚本会被复制到临时目录再执行,避免升级过程中插件目录被操作导致脚本自身丢失。
573
607
  */
574
608
  function fireHotUpgrade(targetVersion) {
575
- const scriptPath = getUpgradeScriptPath();
576
- if (!scriptPath)
609
+ const originalScriptPath = getUpgradeScriptPath();
610
+ if (!originalScriptPath)
577
611
  return { ok: false, reason: "no-script" };
612
+ // 将脚本复制到临时位置,避免升级过程中脚本被删除
613
+ const scriptPath = copyScriptToTemp(originalScriptPath) || originalScriptPath;
578
614
  const cli = findCli();
579
615
  if (!cli)
580
616
  return { ok: false, reason: "no-cli" };
@@ -601,7 +637,7 @@ function fireHotUpgrade(targetVersion) {
601
637
  shell = bash;
602
638
  shellArgs = [scriptPath, "--no-restart", ...(targetVersion ? ["--version", targetVersion] : [])];
603
639
  }
604
- console.log(`[qqbot] fireHotUpgrade: shell=${shell}, script=${scriptPath}, cli=${cli}, target=${targetVersion || "latest"}`);
640
+ console.log(`[qqbot] fireHotUpgrade: shell=${shell}, script=${scriptPath} (original: ${originalScriptPath}), cli=${cli}, target=${targetVersion || "latest"}`);
605
641
  // 异步执行升级脚本
606
642
  execFile(shell, shellArgs, {
607
643
  timeout: 120_000,
@@ -614,6 +650,7 @@ function fireHotUpgrade(targetVersion) {
614
650
  console.error(`[qqbot] fireHotUpgrade: stdout: ${stdout.slice(0, 2000)}`);
615
651
  if (_stderr)
616
652
  console.error(`[qqbot] fireHotUpgrade: stderr: ${_stderr.slice(0, 2000)}`);
653
+ cleanupTempScript();
617
654
  _upgrading = false;
618
655
  return;
619
656
  }
@@ -623,10 +660,13 @@ function fireHotUpgrade(targetVersion) {
623
660
  const newVersion = versionMatch?.[1];
624
661
  if (newVersion === "unknown") {
625
662
  console.error(`[qqbot] fireHotUpgrade: script output QQBOT_NEW_VERSION=unknown, aborting restart`);
663
+ cleanupTempScript();
626
664
  _upgrading = false;
627
665
  return;
628
666
  }
629
667
  console.log(`[qqbot] fireHotUpgrade: new version=${newVersion || "(not detected)"}, triggering restart...`);
668
+ // 脚本执行成功,清理临时脚本副本
669
+ cleanupTempScript();
630
670
  // 文件替换成功,在 restart 之前把 source 从 path 切换为 npm,
631
671
  // 确保新进程启动时读到的是 npm source,不会被本地源码覆盖。
632
672
  switchPluginSourceToNpm();
@@ -686,7 +726,11 @@ function fireHotUpgrade(targetVersion) {
686
726
  /**
687
727
  * /bot-upgrade — 统一升级入口
688
728
  *
689
- * 产品流程:
729
+ * upgradeMode 开关:
730
+ * - "doc"(默认):只展示升级指引文档,不执行热更新
731
+ * - "hot-reload":执行 npm 升级脚本进行热更新
732
+ *
733
+ * 热更新模式下的产品流程:
690
734
  * /bot-upgrade — 展示版本信息+确认按钮(不直接升级)
691
735
  * /bot-upgrade --latest — 确认升级到最新版本
692
736
  * /bot-upgrade --version X — 升级到指定版本
@@ -695,21 +739,49 @@ function fireHotUpgrade(targetVersion) {
695
739
  let _upgrading = false; // 升级锁
696
740
  registerCommand({
697
741
  name: "bot-upgrade",
698
- description: "检查更新并自动热更",
742
+ description: "检查更新并查看升级指引",
699
743
  usage: [
700
- `/bot-upgrade 检查是否有新版本(展示信息+确认按钮)`,
701
- `/bot-upgrade --latest 确认升级到最新版本`,
702
- `/bot-upgrade --version X 升级到指定版本(如 1.6.5)`,
703
- `/bot-upgrade --force 强制重新安装当前版本`,
704
- ``,
705
- `⚠️ 仅在私聊中可用。升级过程约 30~60 秒,期间服务短暂不可用。`,
706
- ``,
707
- `环境要求:`,
708
- ` - 操作系统:macOS / Linux / Windows`,
709
- ` - OpenClaw 框架版本 ≥ ${UPGRADE_REQUIREMENTS.minFrameworkVersion}`,
710
- ` - Node.js ≥ v${UPGRADE_REQUIREMENTS.minNodeVersion}`,
744
+ `/bot-upgrade 检查是否有新版本`,
745
+ `/bot-upgrade --latest 确认升级到最新版本(需 upgradeMode=hot-reload)`,
746
+ `/bot-upgrade --version X 升级到指定版本(需 upgradeMode=hot-reload)`,
747
+ `/bot-upgrade --force 强制重新安装当前版本(需 upgradeMode=hot-reload)`,
711
748
  ].join("\n"),
712
749
  handler: async (ctx) => {
750
+ const url = ctx.accountConfig?.upgradeUrl || DEFAULT_UPGRADE_URL;
751
+ const upgradeMode = ctx.accountConfig?.upgradeMode || "doc";
752
+ const args = ctx.args.trim();
753
+ const info = await getUpdateInfo();
754
+ const GITHUB_URL = "https://github.com/tencent-connect/openclaw-qqbot/";
755
+ // ── doc 模式(默认):只展示升级指引,不执行热更新 ──
756
+ if (upgradeMode !== "hot-reload") {
757
+ if (info.checkedAt === 0) {
758
+ return `⏳ 版本检查中,请稍后再试`;
759
+ }
760
+ if (info.error) {
761
+ return [
762
+ `❌ 主机网络访问异常,无法检查更新`,
763
+ ``,
764
+ `查看升级指引:[点击查看](${url})`,
765
+ ].join("\n");
766
+ }
767
+ if (!info.hasUpdate) {
768
+ return [
769
+ `✅ 当前已是最新版本 v${PLUGIN_VERSION}`,
770
+ ``,
771
+ `项目地址:[GitHub](${GITHUB_URL})`,
772
+ ].join("\n");
773
+ }
774
+ return [
775
+ `🆕 发现新版本`,
776
+ ``,
777
+ `当前版本:**v${PLUGIN_VERSION}**`,
778
+ `最新版本:**v${info.latest}**`,
779
+ ``,
780
+ `📖 升级指引:[点击查看](${url})`,
781
+ `🌟 官方 GitHub 仓库:[点击前往](${GITHUB_URL})`,
782
+ ].join("\n");
783
+ }
784
+ // ── hot-reload 模式:执行热更新 ──
713
785
  // 升级相关指令仅在私聊中可用
714
786
  if (ctx.type !== "c2c") {
715
787
  return `💡 请在私聊中使用此指令`;
@@ -718,9 +790,6 @@ registerCommand({
718
790
  if (_upgrading) {
719
791
  return `⏳ 正在升级中,请稍候...`;
720
792
  }
721
- const url = ctx.accountConfig?.upgradeUrl || DEFAULT_UPGRADE_URL;
722
- const args = ctx.args.trim();
723
- const info = await getUpdateInfo();
724
793
  let isForce = false;
725
794
  let isLatest = false;
726
795
  let versionArg;
@@ -757,7 +826,6 @@ registerCommand({
757
826
  continue;
758
827
  }
759
828
  }
760
- const GITHUB_URL = "https://github.com/tencent-connect/openclaw-qqbot/";
761
829
  // ── 无参数(也没有 --latest / --version / --force):只展示信息+确认按钮 ──
762
830
  if (!versionArg && !isLatest && !isForce) {
763
831
  if (info.checkedAt === 0) {
@@ -778,7 +846,7 @@ registerCommand({
778
846
  ];
779
847
  return lines.join("\n");
780
848
  }
781
- // 有新版本:展示信息 + 确认按钮(同通道:alpha 只展示 alpha,正式版只展示正式版)
849
+ // 有新版本:展示信息 + 确认按钮
782
850
  return [
783
851
  `🆕 发现新版本`,
784
852
  ``,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tencent-connect/openclaw-qqbot",
3
- "version": "1.6.5-alpha.5",
3
+ "version": "1.6.5-alpha.7",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -230,20 +230,40 @@ if [ "$USE_UPDATE" = "true" ]; then
230
230
  fi
231
231
 
232
232
  if [ "$UPGRADE_OK" != "true" ]; then
233
- # 清理旧目录(包含当前插件和历史遗留名称)
234
- for dir_name in "$PLUGIN_ID" qqbot openclaw-qq; do
235
- [ -d "$EXTENSIONS_DIR/$dir_name" ] && rm -rf "$EXTENSIONS_DIR/$dir_name" && echo " 已清理: $EXTENSIONS_DIR/$dir_name"
233
+ # 备份旧目录(而非直接删除),install 失败时可回滚
234
+ BACKUP_DIR=""
235
+ if [ -d "$EXTENSIONS_DIR/$PLUGIN_ID" ]; then
236
+ BACKUP_DIR="$EXTENSIONS_DIR/.openclaw-qqbot-backup-$$"
237
+ mv "$EXTENSIONS_DIR/$PLUGIN_ID" "$BACKUP_DIR"
238
+ echo " 已备份旧目录: $BACKUP_DIR"
239
+ fi
240
+
241
+ # 清理历史遗留名称(这些不需要回滚)
242
+ for dir_name in qqbot openclaw-qq; do
243
+ [ -d "$EXTENSIONS_DIR/$dir_name" ] && rm -rf "$EXTENSIONS_DIR/$dir_name" && echo " 已清理历史目录: $EXTENSIONS_DIR/$dir_name"
236
244
  done
237
245
 
238
246
  echo " 执行 install: $INSTALL_SRC"
239
247
  if $CMD plugins install "$INSTALL_SRC" --pin 2>&1; then
240
248
  UPGRADE_OK=true
241
249
  echo " ✅ install 成功"
250
+ # install 成功,清理备份
251
+ if [ -n "$BACKUP_DIR" ] && [ -d "$BACKUP_DIR" ]; then
252
+ rm -rf "$BACKUP_DIR"
253
+ echo " 已清理旧版备份"
254
+ fi
255
+ # 清理 openclaw CLI install 可能留下的额外 backup 目录
256
+ find "$EXTENSIONS_DIR" -maxdepth 1 -name ".openclaw-qqbot-backup-*" -exec rm -rf {} + 2>/dev/null || true
242
257
  else
243
- echo "❌ install 失败"
258
+ echo " ❌ install 失败"
259
+ # 回滚:恢复旧目录
260
+ if [ -n "$BACKUP_DIR" ] && [ -d "$BACKUP_DIR" ]; then
261
+ mv "$BACKUP_DIR" "$EXTENSIONS_DIR/$PLUGIN_ID"
262
+ echo " ↩️ 已回滚到旧版本"
263
+ fi
244
264
  restore_qqbot_channel
245
265
  echo "QQBOT_NEW_VERSION=unknown"
246
- echo "QQBOT_REPORT=❌ QQBot 安装失败,请检查网络和 npm registry"
266
+ echo "QQBOT_REPORT=❌ QQBot 安装失败(已回滚到旧版本),请检查网络和 npm registry"
247
267
  exit 1
248
268
  fi
249
269
  fi
@@ -336,6 +356,19 @@ if [ "$PREFLIGHT_OK" != "true" ]; then
336
356
  fi
337
357
  echo " ✅ 验证全部通过"
338
358
 
359
+ # 确保 openclaw/plugin-sdk 可解析:
360
+ # openclaw plugins install 不会执行 npm lifecycle scripts,
361
+ # 需要手动调用 postinstall-link-sdk.js 创建 node_modules/openclaw → 全局 openclaw 的符号链接
362
+ POSTINSTALL_SCRIPT="$TARGET_DIR/scripts/postinstall-link-sdk.js"
363
+ if [ -f "$POSTINSTALL_SCRIPT" ]; then
364
+ echo " 执行 postinstall-link-sdk..."
365
+ if node "$POSTINSTALL_SCRIPT" 2>&1; then
366
+ echo " ✅ plugin-sdk 链接就绪"
367
+ else
368
+ echo " ⚠️ postinstall-link-sdk 失败,插件可能无法加载"
369
+ fi
370
+ fi
371
+
339
372
  # [3/4] 输出结构化信息(供 TS handler 解析)
340
373
  echo ""
341
374
  echo "[3/4] 升级结果..."
@@ -444,6 +444,27 @@ else
444
444
  # gateway 已在安装前 stop,此时不会有自动 restart 的问题
445
445
  # 所有配置写入完成后,在 Step 6 统一启动
446
446
 
447
+ # 确保 openclaw/plugin-sdk 可解析:
448
+ # openclaw plugins install 不会执行 npm lifecycle scripts,
449
+ # 需要手动调用 postinstall-link-sdk.js 创建 node_modules/openclaw → 全局 openclaw 的符号链接。
450
+ # 必须在 peerDeps 清理之后执行,否则 symlink 会被清理逻辑删除。
451
+ for _candidate_name in openclaw-qqbot qqbot openclaw-qq; do
452
+ _postinstall="$HOME/.openclaw/extensions/$_candidate_name/scripts/postinstall-link-sdk.js"
453
+ if [ -f "$_postinstall" ]; then
454
+ echo " 执行 postinstall-link-sdk..."
455
+ if node "$_postinstall" 2>&1; then
456
+ echo " ✅ plugin-sdk 链接就绪"
457
+ else
458
+ echo " ⚠️ postinstall-link-sdk 失败,插件可能无法加载"
459
+ fi
460
+ break
461
+ fi
462
+ done
463
+
464
+ # 清理 openclaw CLI install 留下的 backup 目录,
465
+ # 避免 gateway 发现两个同 id 插件不断刷 duplicate plugin id 警告
466
+ find "$HOME/.openclaw/extensions/" -maxdepth 1 -name ".openclaw-qqbot-backup-*" -exec rm -rf {} + 2>/dev/null || true
467
+
447
468
  # 记录更新后的 qqbot 插件版本
448
469
  NEW_QQBOT_VERSION=$(node -e '
449
470
  try {
package/src/gateway.ts CHANGED
@@ -112,6 +112,21 @@ export async function startGateway(ctx: GatewayContext): Promise<void> {
112
112
  }
113
113
  }
114
114
 
115
+ // 预检 openclaw runtime 模块是否可正常解析(兼容性诊断)
116
+ // openclaw 3.23+ 存在 plugin-sdk/root-alias.cjs 回归 bug,
117
+ // 内置插件(qwen-portal-auth 等)全部加载失败,导致 AI agent 调用返回
118
+ // "Unable to resolve plugin runtime module"。提前检测并告警。
119
+ try {
120
+ const pluginRuntime = getQQBotRuntime();
121
+ if (pluginRuntime?.channel?.reply?.dispatchReplyWithBufferedBlockDispatcher) {
122
+ log?.info(`[qqbot:${account.accountId}] Runtime module preflight: OK`);
123
+ } else {
124
+ log?.error(`[qqbot:${account.accountId}] ⚠️ Runtime preflight: dispatchReply API 不可用,AI 消息处理可能失败。请检查 openclaw 版本兼容性`);
125
+ }
126
+ } catch (preflightErr) {
127
+ log?.error(`[qqbot:${account.accountId}] ⚠️ Runtime preflight failed: ${preflightErr}. AI 消息处理可能失败`);
128
+ }
129
+
115
130
  // 后台版本检查(供 /bot-version、/bot-upgrade 指令被动查询)
116
131
  triggerUpdateCheck(log);
117
132
 
@@ -1019,8 +1034,15 @@ export async function startGateway(ctx: GatewayContext): Promise<void> {
1019
1034
  timeoutId = null;
1020
1035
  }
1021
1036
 
1022
- // 发送错误提示给用户,显示完整错误信息
1023
1037
  const errMsg = String(err);
1038
+
1039
+ // 兼容 openclaw 3.23+ 的 plugin-sdk/root-alias.cjs 模块解析失败
1040
+ if (errMsg.includes("Unable to resolve plugin runtime module") || errMsg.includes("root-alias.cjs")) {
1041
+ log?.error(`[qqbot:${account.accountId}] ⚠️ openclaw 框架 runtime 模块解析失败,可能是 openclaw 版本与 plugin-sdk 不兼容。请尝试: npm install -g openclaw@latest && openclaw gateway restart`);
1042
+ await sendErrorMessage("⚠️ AI 服务暂时不可用:openclaw 框架运行时模块加载失败。\n\n请管理员执行:\nnpm install -g openclaw@latest\nopenclaw gateway restart\n\n斜杠命令(如 /bot-ping)不受影响。");
1043
+ return;
1044
+ }
1045
+
1024
1046
  if (errMsg.includes("401") || errMsg.includes("key") || errMsg.includes("auth")) {
1025
1047
  log?.error(`[qqbot:${account.accountId}] AI auth error: ${errMsg}`);
1026
1048
  } else {
@@ -1062,7 +1084,14 @@ export async function startGateway(ctx: GatewayContext): Promise<void> {
1062
1084
  }
1063
1085
  }
1064
1086
  } catch (err) {
1087
+ const errStr = String(err);
1065
1088
  log?.error(`[qqbot:${account.accountId}] Message processing failed: ${err}`);
1089
+ // 兼容 openclaw 3.23+ runtime 模块解析失败:给用户发可操作的提示
1090
+ if (errStr.includes("Unable to resolve plugin runtime module") || errStr.includes("root-alias.cjs")) {
1091
+ try {
1092
+ await sendErrorMessage("⚠️ AI 服务暂时不可用:openclaw 框架运行时模块加载失败。\n\n请管理员执行:\nnpm install -g openclaw@latest\nopenclaw gateway restart\n\n斜杠命令(如 /bot-ping)不受影响。");
1093
+ } catch { /* best-effort */ }
1094
+ }
1066
1095
  } finally {
1067
1096
  // 无论成功/失败/超时,都停止输入状态续期
1068
1097
  typing.keepAlive?.stop();
@@ -337,7 +337,7 @@ registerCommand({
337
337
  },
338
338
  });
339
339
 
340
- const DEFAULT_UPGRADE_URL = "https://doc.weixin.qq.com/doc/w3_AKEAGQaeACgCNHrh1CbHzTAKtT2gB?scode=AJEAIQdfAAozxFEnLZAKEAGQaeACg";
340
+ const DEFAULT_UPGRADE_URL = "https://docs.qq.com/doc/DSGxOZk1oVnVKVkpq";
341
341
 
342
342
  function saveUpgradeGreetingTarget(accountId: string, appId: string, openid: string): void {
343
343
  const safeAccountId = accountId.replace(/[^a-zA-Z0-9._-]/g, "_");
@@ -660,6 +660,38 @@ function findPowerShell(): string | null {
660
660
  return null;
661
661
  }
662
662
 
663
+ /**
664
+ * 将升级脚本复制到临时位置,避免升级过程中插件目录被清理后脚本丢失。
665
+ * 返回临时脚本路径,失败返回 null。
666
+ */
667
+ function copyScriptToTemp(scriptPath: string): string | null {
668
+ try {
669
+ const ext = path.extname(scriptPath);
670
+ const tmpDir = path.join(getHomeDir(), ".openclaw", ".qqbot-upgrade-tmp");
671
+ fs.mkdirSync(tmpDir, { recursive: true });
672
+ const tmpScript = path.join(tmpDir, `upgrade-via-npm${ext}`);
673
+ fs.copyFileSync(scriptPath, tmpScript);
674
+ if (!isWindows()) {
675
+ fs.chmodSync(tmpScript, 0o755);
676
+ }
677
+ return tmpScript;
678
+ } catch {
679
+ return null;
680
+ }
681
+ }
682
+
683
+ /**
684
+ * 清理临时升级脚本目录
685
+ */
686
+ function cleanupTempScript(): void {
687
+ try {
688
+ const tmpDir = path.join(getHomeDir(), ".openclaw", ".qqbot-upgrade-tmp");
689
+ fs.rmSync(tmpDir, { recursive: true, force: true });
690
+ } catch {
691
+ // 非关键,静默忽略
692
+ }
693
+ }
694
+
663
695
  /**
664
696
  * 执行热更新:执行脚本(--no-restart) → 立即触发 gateway restart
665
697
  *
@@ -669,10 +701,15 @@ function findPowerShell(): string | null {
669
701
  * - 新进程启动时 getStartupGreeting() 检测到版本变更,自动通知管理员
670
702
  *
671
703
  * Windows 使用 PowerShell 执行 .ps1 脚本,Mac/Linux 使用 bash 执行 .sh 脚本。
704
+ *
705
+ * 安全机制:脚本会被复制到临时目录再执行,避免升级过程中插件目录被操作导致脚本自身丢失。
672
706
  */
673
707
  function fireHotUpgrade(targetVersion?: string): HotUpgradeStartResult {
674
- const scriptPath = getUpgradeScriptPath();
675
- if (!scriptPath) return { ok: false, reason: "no-script" };
708
+ const originalScriptPath = getUpgradeScriptPath();
709
+ if (!originalScriptPath) return { ok: false, reason: "no-script" };
710
+
711
+ // 将脚本复制到临时位置,避免升级过程中脚本被删除
712
+ const scriptPath = copyScriptToTemp(originalScriptPath) || originalScriptPath;
676
713
 
677
714
  const cli = findCli();
678
715
  if (!cli) return { ok: false, reason: "no-cli" };
@@ -699,7 +736,7 @@ function fireHotUpgrade(targetVersion?: string): HotUpgradeStartResult {
699
736
  shellArgs = [scriptPath, "--no-restart", ...(targetVersion ? ["--version", targetVersion] : [])];
700
737
  }
701
738
 
702
- console.log(`[qqbot] fireHotUpgrade: shell=${shell}, script=${scriptPath}, cli=${cli}, target=${targetVersion || "latest"}`);
739
+ console.log(`[qqbot] fireHotUpgrade: shell=${shell}, script=${scriptPath} (original: ${originalScriptPath}), cli=${cli}, target=${targetVersion || "latest"}`);
703
740
 
704
741
  // 异步执行升级脚本
705
742
  execFile(shell, shellArgs, {
@@ -711,6 +748,7 @@ function fireHotUpgrade(targetVersion?: string): HotUpgradeStartResult {
711
748
  console.error(`[qqbot] fireHotUpgrade: script failed: ${error.message}`);
712
749
  if (stdout) console.error(`[qqbot] fireHotUpgrade: stdout: ${stdout.slice(0, 2000)}`);
713
750
  if (_stderr) console.error(`[qqbot] fireHotUpgrade: stderr: ${_stderr.slice(0, 2000)}`);
751
+ cleanupTempScript();
714
752
  _upgrading = false;
715
753
  return;
716
754
  }
@@ -722,12 +760,16 @@ function fireHotUpgrade(targetVersion?: string): HotUpgradeStartResult {
722
760
  const newVersion = versionMatch?.[1];
723
761
  if (newVersion === "unknown") {
724
762
  console.error(`[qqbot] fireHotUpgrade: script output QQBOT_NEW_VERSION=unknown, aborting restart`);
763
+ cleanupTempScript();
725
764
  _upgrading = false;
726
765
  return;
727
766
  }
728
767
 
729
768
  console.log(`[qqbot] fireHotUpgrade: new version=${newVersion || "(not detected)"}, triggering restart...`);
730
769
 
770
+ // 脚本执行成功,清理临时脚本副本
771
+ cleanupTempScript();
772
+
731
773
  // 文件替换成功,在 restart 之前把 source 从 path 切换为 npm,
732
774
  // 确保新进程启动时读到的是 npm source,不会被本地源码覆盖。
733
775
  switchPluginSourceToNpm();
@@ -788,7 +830,11 @@ function fireHotUpgrade(targetVersion?: string): HotUpgradeStartResult {
788
830
  /**
789
831
  * /bot-upgrade — 统一升级入口
790
832
  *
791
- * 产品流程:
833
+ * upgradeMode 开关:
834
+ * - "doc"(默认):只展示升级指引文档,不执行热更新
835
+ * - "hot-reload":执行 npm 升级脚本进行热更新
836
+ *
837
+ * 热更新模式下的产品流程:
792
838
  * /bot-upgrade — 展示版本信息+确认按钮(不直接升级)
793
839
  * /bot-upgrade --latest — 确认升级到最新版本
794
840
  * /bot-upgrade --version X — 升级到指定版本
@@ -798,21 +844,54 @@ let _upgrading = false; // 升级锁
798
844
 
799
845
  registerCommand({
800
846
  name: "bot-upgrade",
801
- description: "检查更新并自动热更",
847
+ description: "检查更新并查看升级指引",
802
848
  usage: [
803
- `/bot-upgrade 检查是否有新版本(展示信息+确认按钮)`,
804
- `/bot-upgrade --latest 确认升级到最新版本`,
805
- `/bot-upgrade --version X 升级到指定版本(如 1.6.5)`,
806
- `/bot-upgrade --force 强制重新安装当前版本`,
807
- ``,
808
- `⚠️ 仅在私聊中可用。升级过程约 30~60 秒,期间服务短暂不可用。`,
809
- ``,
810
- `环境要求:`,
811
- ` - 操作系统:macOS / Linux / Windows`,
812
- ` - OpenClaw 框架版本 ≥ ${UPGRADE_REQUIREMENTS.minFrameworkVersion}`,
813
- ` - Node.js ≥ v${UPGRADE_REQUIREMENTS.minNodeVersion}`,
849
+ `/bot-upgrade 检查是否有新版本`,
850
+ `/bot-upgrade --latest 确认升级到最新版本(需 upgradeMode=hot-reload)`,
851
+ `/bot-upgrade --version X 升级到指定版本(需 upgradeMode=hot-reload)`,
852
+ `/bot-upgrade --force 强制重新安装当前版本(需 upgradeMode=hot-reload)`,
814
853
  ].join("\n"),
815
854
  handler: async (ctx) => {
855
+ const url = ctx.accountConfig?.upgradeUrl || DEFAULT_UPGRADE_URL;
856
+ const upgradeMode = ctx.accountConfig?.upgradeMode || "doc";
857
+ const args = ctx.args.trim();
858
+ const info = await getUpdateInfo();
859
+
860
+ const GITHUB_URL = "https://github.com/tencent-connect/openclaw-qqbot/";
861
+
862
+ // ── doc 模式(默认):只展示升级指引,不执行热更新 ──
863
+ if (upgradeMode !== "hot-reload") {
864
+ if (info.checkedAt === 0) {
865
+ return `⏳ 版本检查中,请稍后再试`;
866
+ }
867
+ if (info.error) {
868
+ return [
869
+ `❌ 主机网络访问异常,无法检查更新`,
870
+ ``,
871
+ `查看升级指引:[点击查看](${url})`,
872
+ ].join("\n");
873
+ }
874
+ if (!info.hasUpdate) {
875
+ return [
876
+ `✅ 当前已是最新版本 v${PLUGIN_VERSION}`,
877
+ ``,
878
+ `项目地址:[GitHub](${GITHUB_URL})`,
879
+ ].join("\n");
880
+ }
881
+
882
+ return [
883
+ `🆕 发现新版本`,
884
+ ``,
885
+ `当前版本:**v${PLUGIN_VERSION}**`,
886
+ `最新版本:**v${info.latest}**`,
887
+ ``,
888
+ `📖 升级指引:[点击查看](${url})`,
889
+ `🌟 官方 GitHub 仓库:[点击前往](${GITHUB_URL})`,
890
+ ].join("\n");
891
+ }
892
+
893
+ // ── hot-reload 模式:执行热更新 ──
894
+
816
895
  // 升级相关指令仅在私聊中可用
817
896
  if (ctx.type !== "c2c") {
818
897
  return `💡 请在私聊中使用此指令`;
@@ -823,10 +902,6 @@ registerCommand({
823
902
  return `⏳ 正在升级中,请稍候...`;
824
903
  }
825
904
 
826
- const url = ctx.accountConfig?.upgradeUrl || DEFAULT_UPGRADE_URL;
827
- const args = ctx.args.trim();
828
- const info = await getUpdateInfo();
829
-
830
905
  let isForce = false;
831
906
  let isLatest = false;
832
907
  let versionArg: string | undefined;
@@ -864,8 +939,6 @@ registerCommand({
864
939
  }
865
940
  }
866
941
 
867
- const GITHUB_URL = "https://github.com/tencent-connect/openclaw-qqbot/";
868
-
869
942
  // ── 无参数(也没有 --latest / --version / --force):只展示信息+确认按钮 ──
870
943
  if (!versionArg && !isLatest && !isForce) {
871
944
  if (info.checkedAt === 0) {
@@ -887,7 +960,7 @@ registerCommand({
887
960
  return lines.join("\n");
888
961
  }
889
962
 
890
- // 有新版本:展示信息 + 确认按钮(同通道:alpha 只展示 alpha,正式版只展示正式版)
963
+ // 有新版本:展示信息 + 确认按钮
891
964
  return [
892
965
  `🆕 发现新版本`,
893
966
  ``,