@tencent-connect/openclaw-qqbot 1.6.5-alpha.6 → 1.6.5

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
  // 无论成功/失败/超时,都停止输入状态续期
@@ -726,7 +726,11 @@ function fireHotUpgrade(targetVersion) {
726
726
  /**
727
727
  * /bot-upgrade — 统一升级入口
728
728
  *
729
- * 产品流程:
729
+ * upgradeMode 开关:
730
+ * - "doc"(默认):只展示升级指引文档,不执行热更新
731
+ * - "hot-reload":执行 npm 升级脚本进行热更新
732
+ *
733
+ * 热更新模式下的产品流程:
730
734
  * /bot-upgrade — 展示版本信息+确认按钮(不直接升级)
731
735
  * /bot-upgrade --latest — 确认升级到最新版本
732
736
  * /bot-upgrade --version X — 升级到指定版本
@@ -735,21 +739,49 @@ function fireHotUpgrade(targetVersion) {
735
739
  let _upgrading = false; // 升级锁
736
740
  registerCommand({
737
741
  name: "bot-upgrade",
738
- description: "检查更新并自动热更",
742
+ description: "检查更新并查看升级指引",
739
743
  usage: [
740
- `/bot-upgrade 检查是否有新版本(展示信息+确认按钮)`,
741
- `/bot-upgrade --latest 确认升级到最新版本`,
742
- `/bot-upgrade --version X 升级到指定版本(如 1.6.5)`,
743
- `/bot-upgrade --force 强制重新安装当前版本`,
744
- ``,
745
- `⚠️ 仅在私聊中可用。升级过程约 30~60 秒,期间服务短暂不可用。`,
746
- ``,
747
- `环境要求:`,
748
- ` - 操作系统:macOS / Linux / Windows`,
749
- ` - OpenClaw 框架版本 ≥ ${UPGRADE_REQUIREMENTS.minFrameworkVersion}`,
750
- ` - 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)`,
751
748
  ].join("\n"),
752
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 模式:执行热更新 ──
753
785
  // 升级相关指令仅在私聊中可用
754
786
  if (ctx.type !== "c2c") {
755
787
  return `💡 请在私聊中使用此指令`;
@@ -758,9 +790,6 @@ registerCommand({
758
790
  if (_upgrading) {
759
791
  return `⏳ 正在升级中,请稍候...`;
760
792
  }
761
- const url = ctx.accountConfig?.upgradeUrl || DEFAULT_UPGRADE_URL;
762
- const args = ctx.args.trim();
763
- const info = await getUpdateInfo();
764
793
  let isForce = false;
765
794
  let isLatest = false;
766
795
  let versionArg;
@@ -797,7 +826,6 @@ registerCommand({
797
826
  continue;
798
827
  }
799
828
  }
800
- const GITHUB_URL = "https://github.com/tencent-connect/openclaw-qqbot/";
801
829
  // ── 无参数(也没有 --latest / --version / --force):只展示信息+确认按钮 ──
802
830
  if (!versionArg && !isLatest && !isForce) {
803
831
  if (info.checkedAt === 0) {
@@ -818,7 +846,7 @@ registerCommand({
818
846
  ];
819
847
  return lines.join("\n");
820
848
  }
821
- // 有新版本:展示信息 + 确认按钮(同通道:alpha 只展示 alpha,正式版只展示正式版)
849
+ // 有新版本:展示信息 + 确认按钮
822
850
  return [
823
851
  `🆕 发现新版本`,
824
852
  ``,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tencent-connect/openclaw-qqbot",
3
- "version": "1.6.5-alpha.6",
3
+ "version": "1.6.5",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -117,8 +117,12 @@ done
117
117
 
118
118
  EXTENSIONS_DIR="$HOME/.$CMD/extensions"
119
119
 
120
+ # 检测 openclaw 版本
121
+ OPENCLAW_VERSION="$($CMD --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+(\.[0-9]+)?' | head -1 || true)"
122
+
120
123
  echo "==========================================="
121
124
  echo " qqbot 升级: $INSTALL_SRC"
125
+ echo " openclaw 版本: ${OPENCLAW_VERSION:-unknown}"
122
126
  echo "==========================================="
123
127
  echo ""
124
128
 
@@ -483,40 +483,33 @@ else
483
483
  ' 2>/dev/null || echo "unknown")
484
484
  fi
485
485
 
486
- # ── 恢复 channels.qqbot 配置 ──
487
- # 安装完成(无论成功或失败),把之前暂存的 channels.qqbot 写回 openclaw.json
488
- if [ -n "$_QQBOT_CHANNEL_STASH" ] && [ -n "$_STASH_CFG" ] && [ -f "$_STASH_CFG" ]; then
489
- node -e "
490
- const fs = require('fs');
491
- const cfg = JSON.parse(fs.readFileSync('$_STASH_CFG', 'utf8'));
492
- if (!cfg.channels) cfg.channels = {};
493
- cfg.channels.qqbot = $_QQBOT_CHANNEL_STASH;
494
- fs.writeFileSync('$_STASH_CFG', JSON.stringify(cfg, null, 4) + '\n');
495
- " 2>/dev/null && echo " 已恢复 channels.qqbot 配置" || echo " ⚠️ 恢复 channels.qqbot 配置失败"
496
- fi
486
+ # ── 暂不恢复 channels.qqbot 配置 ──
487
+ # openclaw 3.23+ 启动时在插件加载前就校验 channels,
488
+ # 如果此时恢复 channels.qqbot,gateway 会因 "unknown channel id: qqbot" 拒绝启动。
489
+ # 延迟到 gateway 启动、插件加载完成后再恢复(见 Step 6)。
490
+ echo " [兼容] channels.qqbot 将在 gateway 启动后恢复(避免启动校验失败)"
491
+
492
+ # ── Step 4/5:不在此时写入 channels.qqbot 配置 ──
493
+ # openclaw 3.23+ 在插件加载前校验 channels,此时写入 channels.qqbot 会导致
494
+ # "unknown channel id: qqbot" 错误。所有 channels.qqbot 相关配置延迟到
495
+ # gateway 启动、插件加载完成后统一写入(见 Step 6 末尾)。
496
+ #
497
+ # 这里只计算出 DESIRED_QQBOT_TOKEN 和 MARKDOWN_VALUE,不实际写入。
497
498
 
498
- # 4. 配置机器人通道(仅在需要变更时写入配置,避免无意义覆盖)
499
+ # 4. 确定机器人通道配置
499
500
  echo ""
500
- echo "[4/6] 配置机器人通道..."
501
+ echo "[4/6] 准备机器人通道配置..."
501
502
 
502
- # 读取当前 qqbot token(兼容多 key)
503
+ # 读取当前 qqbot token(从暂存或配置文件)
504
+ # 注意:channels.qqbot 已被暂存移除,所以从 _QQBOT_CHANNEL_STASH 读取
503
505
  CURRENT_QQBOT_TOKEN=""
504
- for _app in openclaw clawdbot moltbot; do
505
- _cfg="$HOME/.$_app/$_app.json"
506
- if [ -f "$_cfg" ]; then
507
- CURRENT_QQBOT_TOKEN=$(node -e "
508
- const cfg = JSON.parse(require('fs').readFileSync('$_cfg', 'utf8'));
509
- const keys = ['qqbot', 'openclaw-qqbot', 'openclaw-qq'];
510
- for (const key of keys) {
511
- const ch = cfg.channels && cfg.channels[key];
512
- if (!ch) continue;
513
- if (ch.token) { process.stdout.write(ch.token); process.exit(0); }
514
- if (ch.appId && ch.clientSecret) { process.stdout.write(ch.appId + ':' + ch.clientSecret); process.exit(0); }
515
- }
516
- " 2>/dev/null || true)
517
- [ -n "$CURRENT_QQBOT_TOKEN" ] && break
518
- fi
519
- done
506
+ if [ -n "$_QQBOT_CHANNEL_STASH" ]; then
507
+ CURRENT_QQBOT_TOKEN=$(node -e "
508
+ const ch = $_QQBOT_CHANNEL_STASH;
509
+ if (ch.token) { process.stdout.write(ch.token); }
510
+ else if (ch.appId && ch.clientSecret) { process.stdout.write(ch.appId + ':' + ch.clientSecret); }
511
+ " 2>/dev/null || true)
512
+ fi
520
513
 
521
514
  DESIRED_QQBOT_TOKEN=""
522
515
  if [ -n "$APPID" ] && [ -n "$SECRET" ]; then
@@ -531,104 +524,40 @@ elif [ -n "$SAVED_QQBOT_TOKEN" ]; then
531
524
  fi
532
525
 
533
526
  if [ -n "$DESIRED_QQBOT_TOKEN" ]; then
534
- echo "配置机器人通道: qqbot"
535
- echo "目标Token: ${DESIRED_QQBOT_TOKEN:0:10}..."
536
- if [ "$CURRENT_QQBOT_TOKEN" = "$DESIRED_QQBOT_TOKEN" ]; then
537
- echo "✅ 当前配置已是目标值,跳过写入(避免配置覆盖提示)"
538
- _config_changed=0
539
- elif ! openclaw channels add --channel qqbot --token "$DESIRED_QQBOT_TOKEN" 2>&1; then
540
- echo "⚠️ 警告: 机器人通道配置失败,继续使用已有配置"
541
- _config_changed=0
542
- else
543
- echo "✅ 机器人通道配置成功"
544
- _config_changed=1
545
- # channels 配置变更在 reload plan 中匹配为 hot reload(非 restart),
546
- # channel 插件热重载处理,通常 <1 秒完成,无需长时间等待。
547
- sleep 1
548
- fi
527
+ echo "目标 Token: ${DESIRED_QQBOT_TOKEN:0:10}..."
528
+ echo " [兼容] 将在 gateway 启动后写入 channels.qqbot"
529
+ elif [ -z "$CURRENT_QQBOT_TOKEN" ] && [ -z "$_QQBOT_CHANNEL_STASH" ]; then
530
+ echo ""
531
+ echo "❌ 未检测到 qqbot 通道配置!"
532
+ echo ""
533
+ echo "首次运行请提供 appid 和 appsecret:"
534
+ echo ""
535
+ echo " bash $0 --appid <你的appid> --secret <你的appsecret>"
536
+ echo ""
537
+ echo "也可以通过环境变量:"
538
+ echo ""
539
+ echo " QQBOT_APPID=<appid> QQBOT_SECRET=<appsecret> bash $0"
540
+ echo ""
541
+ echo "appid 和 appsecret 可在 QQ 开放平台 (https://q.qq.com) 获取。"
542
+ exit 1
549
543
  else
550
- # 未提供任何可用 token 时,检查是否已有可用配置
551
- _has_channel=0
552
- if [ -n "$CURRENT_QQBOT_TOKEN" ]; then
553
- _has_channel=1
554
- fi
555
-
556
- if [ "$_has_channel" -eq 0 ]; then
557
- echo ""
558
- echo "❌ 未检测到 qqbot 通道配置!"
559
- echo ""
560
- echo "首次运行请提供 appid 和 appsecret:"
561
- echo ""
562
- echo " bash $0 --appid <你的appid> --secret <你的appsecret>"
563
- echo ""
564
- echo "也可以通过环境变量:"
565
- echo ""
566
- echo " QQBOT_APPID=<appid> QQBOT_SECRET=<appsecret> bash $0"
567
- echo ""
568
- echo "appid 和 appsecret 可在 QQ 开放平台 (https://q.qq.com) 获取。"
569
- exit 1
570
- else
571
- echo "使用已有配置"
572
- fi
544
+ echo "使用已有配置(暂存中)"
573
545
  fi
574
546
 
575
- # 5. 配置 markdown 选项(仅在明确指定时才配置)
547
+ # 5. 确定 markdown 选项
576
548
  echo ""
577
- echo "[5/6] 配置 markdown 选项..."
549
+ echo "[5/6] 准备 markdown 配置..."
578
550
 
551
+ MARKDOWN_VALUE=""
579
552
  if [ -n "$MARKDOWN" ]; then
580
- # 设置 markdown 配置
581
553
  if [ "$MARKDOWN" = "yes" ] || [ "$MARKDOWN" = "y" ] || [ "$MARKDOWN" = "true" ]; then
582
554
  MARKDOWN_VALUE="true"
583
- echo "启用 markdown 消息格式..."
555
+ echo "将启用 markdown 消息格式"
584
556
  else
585
557
  MARKDOWN_VALUE="false"
586
- echo "禁用 markdown 消息格式(使用纯文本)..."
587
- fi
588
-
589
- CURRENT_MARKDOWN_VALUE=$(node -e "
590
- const fs = require('fs');
591
- const path = require('path');
592
- const home = process.env.HOME;
593
- for (const app of ['openclaw', 'clawdbot', 'moltbot']) {
594
- const f = path.join(home, '.' + app, app + '.json');
595
- if (!fs.existsSync(f)) continue;
596
- try {
597
- const cfg = JSON.parse(fs.readFileSync(f, 'utf8'));
598
- const keys = ['qqbot', 'openclaw-qqbot', 'openclaw-qq'];
599
- for (const key of keys) {
600
- const ch = cfg.channels && cfg.channels[key];
601
- if (!ch) continue;
602
- if (typeof ch.markdownSupport === 'boolean') { process.stdout.write(String(ch.markdownSupport)); process.exit(0); }
603
- }
604
- } catch {}
605
- }
606
- " 2>/dev/null || true)
607
-
608
- if [ "$CURRENT_MARKDOWN_VALUE" = "$MARKDOWN_VALUE" ]; then
609
- echo "✅ markdown 配置已是目标值,跳过写入(避免配置覆盖提示)"
610
- elif openclaw config set channels.qqbot.markdownSupport "$MARKDOWN_VALUE" 2>&1; then
611
- echo "✅ markdown配置成功"
612
- _config_changed=1
613
- else
614
- echo "⚠️ openclaw config set 失败,尝试直接编辑配置文件..."
615
- OPENCLAW_CONFIG="$HOME/.openclaw/openclaw.json"
616
- if [ -f "$OPENCLAW_CONFIG" ] && node -e "
617
- const fs = require('fs');
618
- const cfg = JSON.parse(fs.readFileSync('$OPENCLAW_CONFIG', 'utf-8'));
619
- if (!cfg.channels) cfg.channels = {};
620
- if (!cfg.channels.qqbot) cfg.channels.qqbot = {};
621
- const target = $MARKDOWN_VALUE;
622
- if (cfg.channels.qqbot.markdownSupport === target) process.exit(0);
623
- cfg.channels.qqbot.markdownSupport = target;
624
- fs.writeFileSync('$OPENCLAW_CONFIG', JSON.stringify(cfg, null, 4) + '\n');
625
- " 2>&1; then
626
- echo "✅ markdown配置成功(直接编辑配置文件)"
627
- _config_changed=1
628
- else
629
- echo "⚠️ markdown配置设置失败,不影响后续运行"
630
- fi
558
+ echo "将禁用 markdown 消息格式(使用纯文本)"
631
559
  fi
560
+ echo " [兼容] 将在 gateway 启动后写入配置"
632
561
  else
633
562
  echo "未指定 markdown 选项,使用已有配置"
634
563
  fi
@@ -727,10 +656,70 @@ case "$start_choice" in
727
656
  fi
728
657
  fi
729
658
 
730
- # 端口就绪后:检查 qqbot 连接 + 跟踪日志
659
+ # 端口就绪后:恢复 channels.qqbot 配置 + 检查连接 + 跟踪日志
731
660
  if [ "$_port_ready" -eq 1 ]; then
732
- echo "✅ Gateway 端口已就绪"
661
+ echo "✅ Gateway 端口已就绪(插件已加载)"
733
662
  echo ""
663
+
664
+ # ── 恢复 channels.qqbot 配置 ──
665
+ # gateway 已启动、插件已注册 qqbot channel,现在可以安全写回配置
666
+ _need_reload=0
667
+ _target_cfg=""
668
+ for _app in openclaw clawdbot moltbot; do
669
+ _cfg="$HOME/.$_app/$_app.json"
670
+ if [ -f "$_cfg" ]; then
671
+ _target_cfg="$_cfg"
672
+ break
673
+ fi
674
+ done
675
+
676
+ if [ -n "$_target_cfg" ]; then
677
+ # 构建完整的 channels.qqbot 对象(合并暂存配置 + 新 token + markdown)
678
+ node -e "
679
+ const fs = require('fs');
680
+ const cfg = JSON.parse(fs.readFileSync('$_target_cfg', 'utf8'));
681
+ if (!cfg.channels) cfg.channels = {};
682
+
683
+ // 从暂存恢复基础配置
684
+ const stash = '$_QQBOT_CHANNEL_STASH';
685
+ if (stash) {
686
+ try { cfg.channels.qqbot = JSON.parse(stash); } catch {}
687
+ }
688
+ if (!cfg.channels.qqbot) cfg.channels.qqbot = {};
689
+
690
+ // 覆盖 token(如果有新值)
691
+ const desired = '$DESIRED_QQBOT_TOKEN';
692
+ if (desired && desired.includes(':')) {
693
+ const [appId, ...rest] = desired.split(':');
694
+ cfg.channels.qqbot.appId = appId;
695
+ cfg.channels.qqbot.clientSecret = rest.join(':');
696
+ delete cfg.channels.qqbot.token;
697
+ }
698
+
699
+ // 覆盖 markdown(如果有指定)
700
+ const md = '$MARKDOWN_VALUE';
701
+ if (md === 'true') cfg.channels.qqbot.markdownSupport = true;
702
+ else if (md === 'false') cfg.channels.qqbot.markdownSupport = false;
703
+
704
+ fs.writeFileSync('$_target_cfg', JSON.stringify(cfg, null, 4) + '\n');
705
+ " 2>/dev/null || true
706
+ echo " ✅ 已恢复 channels.qqbot 配置(含 token/markdown)"
707
+ _need_reload=1
708
+ fi
709
+
710
+ # 配置写回后 reload gateway 使其生效
711
+ if [ "$_need_reload" -eq 1 ]; then
712
+ echo " 重载配置..."
713
+ sleep 1
714
+ openclaw gateway restart 2>/dev/null || true
715
+ # 等待重启后端口重新就绪
716
+ for _k in $(seq 1 15); do
717
+ if lsof -i :18789 -sTCP:LISTEN >/dev/null 2>&1; then
718
+ break
719
+ fi
720
+ sleep 2
721
+ done
722
+ fi
734
723
  # 检查 qqbot WS 是否连接成功(最多等 20 秒)
735
724
  echo "检查 qqbot 插件连接状态..."
736
725
  _LOG_FILE="/tmp/openclaw/openclaw-$(date +%Y-%m-%d).log"
@@ -775,10 +764,27 @@ case "$start_choice" in
775
764
  n|no)
776
765
  echo ""
777
766
  echo "✅ 插件更新完毕,未启动服务"
767
+ # 不启动时也需要恢复 channels.qqbot,否则配置丢失
768
+ # 注意:下次 gateway 启动可能因 "unknown channel id" 失败,
769
+ # 需要用户手动 stop → 移除 channels.qqbot → start → 恢复
770
+ if [ -n "$_QQBOT_CHANNEL_STASH" ] && [ -n "$_STASH_CFG" ] && [ -f "$_STASH_CFG" ]; then
771
+ node -e "
772
+ const fs = require('fs');
773
+ const cfg = JSON.parse(fs.readFileSync('$_STASH_CFG', 'utf8'));
774
+ if (!cfg.channels) cfg.channels = {};
775
+ cfg.channels.qqbot = $_QQBOT_CHANNEL_STASH;
776
+ fs.writeFileSync('$_STASH_CFG', JSON.stringify(cfg, null, 4) + '\n');
777
+ " 2>/dev/null || true
778
+ echo " 已恢复 channels.qqbot 配置"
779
+ fi
778
780
  echo ""
779
- echo "后续可手动启动:"
780
- echo " openclaw gateway restart # 重启后台服务"
781
- echo " openclaw logs --follow # 跟踪日志"
781
+ echo "后续启动方法(兼容 openclaw 3.23+):"
782
+ echo " 1. 先临时移除 channels.qqbot 再启动:"
783
+ echo " openclaw gateway restart"
784
+ echo " 2. 或使用本脚本自动处理:"
785
+ echo " bash $0"
786
+ echo " 3. 跟踪日志:"
787
+ echo " openclaw logs --follow"
782
788
  ;;
783
789
  *)
784
790
  echo "无效选择,按默认值 y 执行后台重启"
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();
@@ -830,7 +830,11 @@ function fireHotUpgrade(targetVersion?: string): HotUpgradeStartResult {
830
830
  /**
831
831
  * /bot-upgrade — 统一升级入口
832
832
  *
833
- * 产品流程:
833
+ * upgradeMode 开关:
834
+ * - "doc"(默认):只展示升级指引文档,不执行热更新
835
+ * - "hot-reload":执行 npm 升级脚本进行热更新
836
+ *
837
+ * 热更新模式下的产品流程:
834
838
  * /bot-upgrade — 展示版本信息+确认按钮(不直接升级)
835
839
  * /bot-upgrade --latest — 确认升级到最新版本
836
840
  * /bot-upgrade --version X — 升级到指定版本
@@ -840,21 +844,54 @@ let _upgrading = false; // 升级锁
840
844
 
841
845
  registerCommand({
842
846
  name: "bot-upgrade",
843
- description: "检查更新并自动热更",
847
+ description: "检查更新并查看升级指引",
844
848
  usage: [
845
- `/bot-upgrade 检查是否有新版本(展示信息+确认按钮)`,
846
- `/bot-upgrade --latest 确认升级到最新版本`,
847
- `/bot-upgrade --version X 升级到指定版本(如 1.6.5)`,
848
- `/bot-upgrade --force 强制重新安装当前版本`,
849
- ``,
850
- `⚠️ 仅在私聊中可用。升级过程约 30~60 秒,期间服务短暂不可用。`,
851
- ``,
852
- `环境要求:`,
853
- ` - 操作系统:macOS / Linux / Windows`,
854
- ` - OpenClaw 框架版本 ≥ ${UPGRADE_REQUIREMENTS.minFrameworkVersion}`,
855
- ` - 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)`,
856
853
  ].join("\n"),
857
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
+
858
895
  // 升级相关指令仅在私聊中可用
859
896
  if (ctx.type !== "c2c") {
860
897
  return `💡 请在私聊中使用此指令`;
@@ -865,10 +902,6 @@ registerCommand({
865
902
  return `⏳ 正在升级中,请稍候...`;
866
903
  }
867
904
 
868
- const url = ctx.accountConfig?.upgradeUrl || DEFAULT_UPGRADE_URL;
869
- const args = ctx.args.trim();
870
- const info = await getUpdateInfo();
871
-
872
905
  let isForce = false;
873
906
  let isLatest = false;
874
907
  let versionArg: string | undefined;
@@ -906,8 +939,6 @@ registerCommand({
906
939
  }
907
940
  }
908
941
 
909
- const GITHUB_URL = "https://github.com/tencent-connect/openclaw-qqbot/";
910
-
911
942
  // ── 无参数(也没有 --latest / --version / --force):只展示信息+确认按钮 ──
912
943
  if (!versionArg && !isLatest && !isForce) {
913
944
  if (info.checkedAt === 0) {
@@ -929,7 +960,7 @@ registerCommand({
929
960
  return lines.join("\n");
930
961
  }
931
962
 
932
- // 有新版本:展示信息 + 确认按钮(同通道:alpha 只展示 alpha,正式版只展示正式版)
963
+ // 有新版本:展示信息 + 确认按钮
933
964
  return [
934
965
  `🆕 发现新版本`,
935
966
  ``,