@ryantest/openclaw-qqbot 1.6.7-beta.2 → 1.6.7-beta.20

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.6`
13
+ ### 🚀 Current Version: `v1.6.7`
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/)
package/README.zh.md CHANGED
@@ -9,7 +9,7 @@
9
9
 
10
10
  **让你的 AI 助手接入 QQ — 私聊、群聊、富媒体,一个插件全搞定。**
11
11
 
12
- ### 🚀 当前版本: `v1.6.6`
12
+ ### 🚀 当前版本: `v1.6.7`
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/)
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, {
@@ -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,13 +687,116 @@ 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] : [])];
691
+ }
692
+ console.log(`[qqbot] fireHotUpgrade: shell=${shell}, script=${scriptPath}, cli=${cli}, target=${targetVersion || "latest"}, pkg=${pkg || "default"}`);
693
+ // ── 兼容 openclaw 3.23+ 配置严格校验 ──
694
+ // openclaw plugins install/update 启动时会校验整个配置文件,
695
+ // 如果 channels.qqbot 已存在但 qqbot 插件尚未加载,校验会报 "unknown channel id: qqbot"。
696
+ //
697
+ // ⚠️ 关键:绝不能直接修改真实的 openclaw.json!
698
+ // gateway 的 config file watcher 会检测到变更并触发 SIGUSR1 重启,
699
+ // 导致当前进程被杀、execFile 回调(restoreConfigAndCleanup)永远不会执行,
700
+ // channels.qqbot 配置就此丢失。
701
+ //
702
+ // 策略:创建临时配置副本(不含 channels.qqbot),通过 OPENCLAW_CONFIG_PATH
703
+ // 环境变量传递给子进程,真实配置文件不受影响。
704
+ // shell 脚本(upgrade-via-npm.sh)内部也有同样的临时配置机制作为双保险。
705
+ const homeDir = getHomeDir();
706
+ const realConfigPath = path.join(homeDir, ".openclaw", "openclaw.json");
707
+ let tempConfigPath = null;
708
+ const childEnv = { ...process.env };
709
+ try {
710
+ if (fs.existsSync(realConfigPath)) {
711
+ const cfg = JSON.parse(fs.readFileSync(realConfigPath, "utf8"));
712
+ const needsTempConfig = !!(cfg.channels?.qqbot) ||
713
+ !!(cfg.plugins?.entries?.["openclaw-qqbot"]);
714
+ if (needsTempConfig) {
715
+ // 创建临时配置副本(移除 channels.qqbot 和 plugins.entries.openclaw-qqbot)
716
+ const cleanCfg = JSON.parse(JSON.stringify(cfg)); // deep clone
717
+ if (cleanCfg.channels?.qqbot) {
718
+ delete cleanCfg.channels.qqbot;
719
+ if (Object.keys(cleanCfg.channels).length === 0)
720
+ delete cleanCfg.channels;
721
+ }
722
+ if (cleanCfg.plugins?.entries?.["openclaw-qqbot"]) {
723
+ delete cleanCfg.plugins.entries["openclaw-qqbot"];
724
+ if (cleanCfg.plugins.entries && Object.keys(cleanCfg.plugins.entries).length === 0)
725
+ delete cleanCfg.plugins.entries;
726
+ }
727
+ const tmpDir = path.join(homeDir, ".openclaw", ".qqbot-upgrade-tmp");
728
+ fs.mkdirSync(tmpDir, { recursive: true });
729
+ tempConfigPath = path.join(tmpDir, "openclaw-tmp.json");
730
+ fs.writeFileSync(tempConfigPath, JSON.stringify(cleanCfg, null, 4) + "\n");
731
+ childEnv.OPENCLAW_CONFIG_PATH = tempConfigPath;
732
+ console.log(`[qqbot] fireHotUpgrade: created temp config without channels.qqbot (OPENCLAW_CONFIG_PATH=${tempConfigPath}), real config untouched`);
733
+ }
734
+ }
735
+ }
736
+ catch (e) {
737
+ console.warn(`[qqbot] fireHotUpgrade: failed to create temp config: ${e.message}, proceeding with original`);
738
+ tempConfigPath = null;
739
+ }
740
+ /**
741
+ * 将 openclaw plugins install 写入临时配置的 installs/entries 记录同步回真实配置,
742
+ * 然后清理临时文件。
743
+ *
744
+ * 注意:真实配置中的 channels.qqbot 从未被移除,无需恢复。
745
+ */
746
+ function syncTempConfigAndCleanup() {
747
+ try {
748
+ if (tempConfigPath && fs.existsSync(tempConfigPath) && fs.existsSync(realConfigPath)) {
749
+ const tmp = JSON.parse(fs.readFileSync(tempConfigPath, "utf8"));
750
+ const real = JSON.parse(fs.readFileSync(realConfigPath, "utf8"));
751
+ let changed = false;
752
+ // 同步 plugins.installs(openclaw plugins install 会写入安装记录)
753
+ if (tmp.plugins?.installs) {
754
+ if (!real.plugins)
755
+ real.plugins = {};
756
+ real.plugins.installs = { ...(real.plugins.installs || {}), ...tmp.plugins.installs };
757
+ changed = true;
758
+ }
759
+ // 同步 plugins.entries(openclaw plugins install 会写入 entries)
760
+ // 注意:不同步 openclaw-qqbot 自身的 entry,因为插件通过 auto-discover 加载,
761
+ // 显式写入 entries 会导致 "duplicate plugin id" 警告刷屏。
762
+ if (tmp.plugins?.entries) {
763
+ if (!real.plugins)
764
+ real.plugins = {};
765
+ if (!real.plugins.entries)
766
+ real.plugins.entries = {};
767
+ for (const [k, v] of Object.entries(tmp.plugins.entries)) {
768
+ if (k === "openclaw-qqbot")
769
+ continue; // 跳过自身,避免 duplicate
770
+ if (!real.plugins.entries[k]) {
771
+ real.plugins.entries[k] = v;
772
+ changed = true;
773
+ }
774
+ }
775
+ }
776
+ if (changed) {
777
+ fs.writeFileSync(realConfigPath, JSON.stringify(real, null, 4) + "\n");
778
+ console.log("[qqbot] fireHotUpgrade: synced install/entries records from temp config to real config");
779
+ }
780
+ }
781
+ }
782
+ catch (e) {
783
+ console.warn(`[qqbot] fireHotUpgrade: failed to sync temp config: ${e.message}`);
784
+ }
785
+ // 清理临时文件
786
+ try {
787
+ if (tempConfigPath)
788
+ fs.unlinkSync(tempConfigPath);
789
+ }
790
+ catch { /* ignore */ }
681
791
  }
682
- console.log(`[qqbot] fireHotUpgrade: shell=${shell}, script=${scriptPath}, cli=${cli}, target=${targetVersion || "latest"}`);
683
792
  // 异步执行升级脚本
793
+ // 必须显式设置 cwd 为一个确定存在的目录(如 homeDir),
794
+ // 否则子进程继承 gateway 的 cwd,如果该目录在升级过程中被删除/移动,
795
+ // openclaw CLI 启动时 process.cwd() 会报 ENOENT: uv_cwd 错误。
684
796
  execFile(shell, shellArgs, {
685
797
  timeout: 120_000,
686
- env: { ...process.env },
798
+ cwd: homeDir,
799
+ env: childEnv,
687
800
  ...(isWindows() ? { windowsHide: true } : {}),
688
801
  }, (error, stdout, _stderr) => {
689
802
  if (error) {
@@ -692,6 +805,7 @@ function fireHotUpgrade(targetVersion) {
692
805
  console.error(`[qqbot] fireHotUpgrade: stdout: ${stdout.slice(0, 2000)}`);
693
806
  if (_stderr)
694
807
  console.error(`[qqbot] fireHotUpgrade: stderr: ${_stderr.slice(0, 2000)}`);
808
+ syncTempConfigAndCleanup();
695
809
  cleanupTempScript();
696
810
  _upgrading = false;
697
811
  return;
@@ -702,12 +816,14 @@ function fireHotUpgrade(targetVersion) {
702
816
  const newVersion = versionMatch?.[1];
703
817
  if (newVersion === "unknown") {
704
818
  console.error(`[qqbot] fireHotUpgrade: script output QQBOT_NEW_VERSION=unknown, aborting restart`);
819
+ syncTempConfigAndCleanup();
705
820
  cleanupTempScript();
706
821
  _upgrading = false;
707
822
  return;
708
823
  }
709
824
  console.log(`[qqbot] fireHotUpgrade: new version=${newVersion || "(not detected)"}, triggering restart...`);
710
- // 脚本执行成功,清理临时脚本副本
825
+ // 脚本执行成功,同步临时配置中的 install 记录并清理
826
+ syncTempConfigAndCleanup();
711
827
  cleanupTempScript();
712
828
  // 文件替换成功,在 restart 之前把 source 从 path 切换为 npm,
713
829
  // 确保新进程启动时读到的是 npm source,不会被本地源码覆盖。
@@ -750,17 +866,176 @@ function fireHotUpgrade(targetVersion) {
750
866
  }
751
867
  }
752
868
  else {
753
- // Mac/Linux: 直接 restart(框架通常以 daemon 模式运行)
754
- execCliAsync(cli, ["gateway", "restart"], { timeout: 30_000 }, (restartErr) => {
755
- if (restartErr) {
756
- console.error(`[qqbot] fireHotUpgrade: restart failed: ${restartErr.message}, trying stop+start fallback`);
757
- execCliAsync(cli, ["gateway", "stop"], { timeout: 10_000 }, () => {
758
- setTimeout(() => {
759
- execCliAsync(cli, ["gateway", "start"], { timeout: 30_000 }, () => { });
760
- }, 1000);
761
- });
869
+ // Mac/Linux: 使用 detached shell 脚本执行 stop+start
870
+ //
871
+ // 兼容 openclaw 2026.3.24+ 配置严格校验:
872
+ // gateway restart openclaw 先校验配置(loadConfig)再加载插件。
873
+ // 如果 channels.qqbot 存在但 qqbot channel type 尚未注册,校验会失败。
874
+ // 解决:stop 后临时移除 channels.qqbot → start(插件加载、qqbot type 注册)→ 恢复。
875
+ const cliInvoke = cli.endsWith(".mjs")
876
+ ? `"${process.execPath}" "${cli}"`
877
+ : `"${cli}"`;
878
+ const homeDir = getHomeDir();
879
+ const configPath = path.join(homeDir, ".openclaw", "openclaw.json");
880
+ const qqbotChannelBackup = path.join(homeDir, ".openclaw", ".qqbot-channel-backup.json");
881
+ const restartScript = path.join(homeDir, ".openclaw", ".qqbot-restart.sh");
882
+ // 先保存 channels.qqbot 到临时文件(在当前进程中,JSON 处理更安全)
883
+ let hasChannel = false;
884
+ try {
885
+ const cfgRaw = fs.readFileSync(configPath, "utf8");
886
+ const cfg = JSON.parse(cfgRaw);
887
+ const qqbot = cfg?.channels?.qqbot;
888
+ if (qqbot) {
889
+ fs.writeFileSync(qqbotChannelBackup, JSON.stringify(qqbot, null, 2), "utf8");
890
+ hasChannel = true;
762
891
  }
763
- });
892
+ }
893
+ catch {
894
+ // 配置文件不存在或 JSON 解析失败,不做处理
895
+ }
896
+ const shContent = `#!/bin/bash
897
+ # 注意:不使用 set -e,因为 gateway start 失败时仍需恢复 channels.qqbot
898
+ CLI="${cliInvoke}"
899
+ CONFIG="${configPath}"
900
+ BACKUP="${qqbotChannelBackup}"
901
+
902
+ # ── 兼容 openclaw 3.23+ 配置严格校验 ──
903
+ # 所有 openclaw CLI 命令(包括 gateway stop/start)启动时都会 loadConfig 校验配置,
904
+ # 如果 channels.qqbot 存在但 qqbot 插件尚未加载,校验会报 "unknown channel id: qqbot"。
905
+ #
906
+ # 策略:
907
+ # 1. gateway stop:使用 OPENCLAW_CONFIG_PATH 临时配置(不含 channels.qqbot)
908
+ # 2. gateway start:先尝试直接启动(真实配置),如果 CLI 校验失败,
909
+ # 则临时修改真实配置(此时 gateway 已停止,无 config watcher),启动后恢复。
910
+ # 这样 gateway 进程读取的是完整配置(含 channels.qqbot)。
911
+
912
+ # 为 gateway stop 创建临时配置
913
+ TEMP_RESTART_CONFIG=""
914
+ if [ -f "$BACKUP" ]; then
915
+ TEMP_RESTART_CONFIG="\$(mktemp)"
916
+ node -e "
917
+ const fs = require('fs');
918
+ const cfg = JSON.parse(fs.readFileSync(process.argv[1], 'utf8'));
919
+ if (cfg.channels && cfg.channels.qqbot) {
920
+ delete cfg.channels.qqbot;
921
+ if (Object.keys(cfg.channels).length === 0) delete cfg.channels;
922
+ }
923
+ if (cfg.plugins && cfg.plugins.entries && cfg.plugins.entries['openclaw-qqbot']) {
924
+ delete cfg.plugins.entries['openclaw-qqbot'];
925
+ if (Object.keys(cfg.plugins.entries).length === 0) delete cfg.plugins.entries;
926
+ }
927
+ fs.writeFileSync(process.argv[2], JSON.stringify(cfg, null, 4) + '\\n');
928
+ " "$CONFIG" "$TEMP_RESTART_CONFIG" 2>/dev/null
929
+ if [ \$? -ne 0 ] || [ ! -s "$TEMP_RESTART_CONFIG" ]; then
930
+ echo "[qqbot-upgrade] WARNING: failed to create temp config"
931
+ TEMP_RESTART_CONFIG=""
932
+ fi
933
+ fi
934
+
935
+ echo "[qqbot-upgrade] Stopping gateway..."
936
+ if [ -n "$TEMP_RESTART_CONFIG" ]; then
937
+ OPENCLAW_CONFIG_PATH="$TEMP_RESTART_CONFIG" $CLI gateway stop 2>/dev/null || true
938
+ else
939
+ $CLI gateway stop 2>/dev/null || true
940
+ fi
941
+ sleep 2
942
+
943
+ # 清理临时配置(不再需要)
944
+ if [ -n "$TEMP_RESTART_CONFIG" ] && [ -f "$TEMP_RESTART_CONFIG" ]; then
945
+ rm -f "$TEMP_RESTART_CONFIG"
946
+ fi
947
+
948
+ echo "[qqbot-upgrade] Starting gateway..."
949
+ START_OK=false
950
+
951
+ # 先尝试直接启动(使用真实配置,含 channels.qqbot)
952
+ # 如果 openclaw 版本不做严格校验,或者插件已注册,这会直接成功
953
+ if $CLI gateway start 2>/dev/null; then
954
+ START_OK=true
955
+ echo "[qqbot-upgrade] Gateway started successfully (direct start)"
956
+ elif [ -f "$BACKUP" ]; then
957
+ # 直接启动失败(可能是 channels.qqbot 校验失败),
958
+ # 临时修改真实配置(此时 gateway 已停止,无 config watcher,安全)
959
+ echo "[qqbot-upgrade] Direct start failed, temporarily removing channels.qqbot from real config..."
960
+ node -e "
961
+ const fs = require('fs');
962
+ const cfg = JSON.parse(fs.readFileSync(process.argv[1], 'utf8'));
963
+ let changed = false;
964
+ if (cfg.channels && cfg.channels.qqbot) {
965
+ delete cfg.channels.qqbot;
966
+ if (Object.keys(cfg.channels).length === 0) delete cfg.channels;
967
+ changed = true;
968
+ }
969
+ if (cfg.plugins && cfg.plugins.entries && cfg.plugins.entries['openclaw-qqbot']) {
970
+ delete cfg.plugins.entries['openclaw-qqbot'];
971
+ if (Object.keys(cfg.plugins.entries).length === 0) delete cfg.plugins.entries;
972
+ changed = true;
973
+ }
974
+ if (changed) {
975
+ fs.writeFileSync(process.argv[1], JSON.stringify(cfg, null, 4) + '\\n');
976
+ }
977
+ " "$CONFIG" 2>/dev/null
978
+
979
+ if $CLI gateway start 2>/dev/null; then
980
+ START_OK=true
981
+ echo "[qqbot-upgrade] Gateway started successfully (after config fix)"
982
+ else
983
+ echo "[qqbot-upgrade] WARNING: gateway start still failed after config fix"
984
+ fi
985
+
986
+ # 等待 gateway 进程启动并加载插件(插件注册 qqbot channel type)
987
+ echo "[qqbot-upgrade] Waiting for plugin to load (8s)..."
988
+ sleep 8
989
+
990
+ # 恢复 channels.qqbot 到真实配置
991
+ # gateway 的 config file watcher 会检测到变更并热加载
992
+ echo "[qqbot-upgrade] Restoring channels.qqbot to real config..."
993
+ node -e "
994
+ const fs = require('fs');
995
+ const fs = require('fs');
996
+ const cfg = JSON.parse(fs.readFileSync(process.argv[1], 'utf8'));
997
+ const qqbot = JSON.parse(fs.readFileSync(process.argv[2], 'utf8'));
998
+ if (!cfg.channels) cfg.channels = {};
999
+ cfg.channels.qqbot = qqbot;
1000
+ // 注意:不写入 plugins.entries.openclaw-qqbot,
1001
+ // 插件通过 auto-discover 加载,显式 entry 会导致 duplicate plugin id 警告。
1002
+ fs.writeFileSync(process.argv[1], JSON.stringify(cfg, null, 4) + '\\n');
1003
+ " "$CONFIG" "$BACKUP" 2>/dev/null
1004
+ rm -f "$BACKUP"
1005
+ echo "[qqbot-upgrade] channels.qqbot restored"
1006
+ else
1007
+ echo "[qqbot-upgrade] WARNING: gateway start failed, no backup to restore"
1008
+ fi
1009
+
1010
+ # 直接启动成功的情况下,清理备份文件
1011
+ if [ "$START_OK" = "true" ] && [ -f "$BACKUP" ]; then
1012
+ rm -f "$BACKUP"
1013
+ fi
1014
+
1015
+ # 如果 start 失败,尝试再次启动
1016
+ if [ "$START_OK" != "true" ]; then
1017
+ echo "[qqbot-upgrade] Retrying gateway start..."
1018
+ sleep 2
1019
+ $CLI gateway start 2>/dev/null || echo "[qqbot-upgrade] WARNING: retry also failed"
1020
+ fi
1021
+
1022
+ # 清理自身
1023
+ rm -f "$0"
1024
+ echo "[qqbot-upgrade] Done."
1025
+ `;
1026
+ try {
1027
+ fs.writeFileSync(restartScript, shContent, { mode: 0o755 });
1028
+ const child = spawn("bash", [restartScript], {
1029
+ detached: true,
1030
+ stdio: "ignore",
1031
+ });
1032
+ child.unref();
1033
+ console.log(`[qqbot] fireHotUpgrade: launched detached restart script (pid=${child.pid}), hasChannel=${hasChannel}`);
1034
+ }
1035
+ catch (shErr) {
1036
+ console.error(`[qqbot] fireHotUpgrade: failed to launch restart script: ${shErr.message}, falling back to direct restart`);
1037
+ execCliAsync(cli, ["gateway", "restart"], { timeout: 30_000 }, () => { });
1038
+ }
764
1039
  }
765
1040
  });
766
1041
  return { ok: true };
@@ -786,7 +1061,9 @@ registerCommand({
786
1061
  `/bot-upgrade 检查是否有新版本`,
787
1062
  `/bot-upgrade --latest 确认升级到最新版本(需 upgradeMode=hot-reload)`,
788
1063
  `/bot-upgrade --version X 升级到指定版本(需 upgradeMode=hot-reload)`,
1064
+ `/bot-upgrade --pkg scope/name 指定 npm 包(如 ryantest/openclaw-qqbot)`,
789
1065
  `/bot-upgrade --force 强制重新安装当前版本(需 upgradeMode=hot-reload)`,
1066
+ `/bot-upgrade --local 使用本地升级脚本(跳过远端下载)`,
790
1067
  ].join("\n"),
791
1068
  handler: async (ctx) => {
792
1069
  const url = ctx.accountConfig?.upgradeUrl || DEFAULT_UPGRADE_URL;
@@ -834,7 +1111,9 @@ registerCommand({
834
1111
  }
835
1112
  let isForce = false;
836
1113
  let isLatest = false;
1114
+ let isLocal = false;
837
1115
  let versionArg;
1116
+ let pkgArg;
838
1117
  const tokens = args ? args.split(/\s+/).filter(Boolean) : [];
839
1118
  for (let i = 0; i < tokens.length; i += 1) {
840
1119
  const t = tokens[i];
@@ -846,6 +1125,27 @@ registerCommand({
846
1125
  isLatest = true;
847
1126
  continue;
848
1127
  }
1128
+ if (t === "--local") {
1129
+ isLocal = true;
1130
+ continue;
1131
+ }
1132
+ if (t === "--pkg") {
1133
+ const next = tokens[i + 1];
1134
+ if (!next || next.startsWith("--")) {
1135
+ return `❌ 参数错误:--pkg 需要包名\n\n示例:/bot-upgrade --pkg ryantest/openclaw-qqbot`;
1136
+ }
1137
+ pkgArg = next;
1138
+ i += 1;
1139
+ continue;
1140
+ }
1141
+ if (t.startsWith("--pkg=")) {
1142
+ const v = t.slice("--pkg=".length).trim();
1143
+ if (!v) {
1144
+ return `❌ 参数错误:--pkg 需要包名\n\n示例:/bot-upgrade --pkg ryantest/openclaw-qqbot`;
1145
+ }
1146
+ pkgArg = v;
1147
+ continue;
1148
+ }
849
1149
  if (t === "--version") {
850
1150
  const next = tokens[i + 1];
851
1151
  if (!next || next.startsWith("--")) {
@@ -904,9 +1204,17 @@ registerCommand({
904
1204
  `🌟官方 GitHub 仓库:[点击前往](${GITHUB_URL})`,
905
1205
  ].join("\n");
906
1206
  }
1207
+ // 解析 npm 包名:--pkg 参数 > 配置项 upgradePkg > 默认
1208
+ // 支持 "scope/name"(自动补 @)和 "@scope/name" 两种格式
1209
+ let upgradePkg = pkgArg || ctx.accountConfig?.upgradePkg;
1210
+ if (upgradePkg) {
1211
+ upgradePkg = upgradePkg.trim();
1212
+ if (!upgradePkg.startsWith("@"))
1213
+ upgradePkg = `@${upgradePkg}`;
1214
+ }
907
1215
  // ── --version 指定版本:先校验版本号是否存在 ──
908
1216
  if (versionArg) {
909
- const exists = await checkVersionExists(versionArg);
1217
+ const exists = await checkVersionExists(versionArg, upgradePkg);
910
1218
  if (!exists) {
911
1219
  return `❌ 版本 ${versionArg} 不存在,请检查版本号`;
912
1220
  }
@@ -951,7 +1259,7 @@ registerCommand({
951
1259
  // 热更新前保存凭证快照,防止更新过程被打断导致 appId/secret 丢失
952
1260
  preUpgradeCredentialBackup(ctx.accountId, ctx.appId);
953
1261
  // 异步执行升级
954
- const startResult = fireHotUpgrade(targetVersion);
1262
+ const startResult = fireHotUpgrade(targetVersion, upgradePkg, isLocal);
955
1263
  if (!startResult.ok) {
956
1264
  _upgrading = false;
957
1265
  if (startResult.reason === "no-script") {
@@ -96,6 +96,13 @@ export interface QQBotAccountConfig {
96
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": "@ryantest/openclaw-qqbot",
3
- "version": "1.6.7-beta.2",
3
+ "version": "1.6.7-beta.20",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -17,6 +17,7 @@ param(
17
17
  [string]$Secret = "",
18
18
  [switch]$NoRestart,
19
19
  [string]$Tag = "",
20
+ [string]$Pkg = "",
20
21
  [switch]$Help
21
22
  )
22
23
 
@@ -25,6 +26,13 @@ $PKG_NAME = "@tencent-connect/openclaw-qqbot"
25
26
  $SCRIPT_DIR = Split-Path -Parent $MyInvocation.MyCommand.Definition
26
27
  $PROJECT_DIR = Split-Path -Parent $SCRIPT_DIR
27
28
 
29
+ # -Pkg 覆盖包名(支持 "scope/name" 自动补 @)
30
+ if ($Pkg) {
31
+ $Pkg = $Pkg.Trim()
32
+ if (-not $Pkg.StartsWith("@")) { $Pkg = "@$Pkg" }
33
+ $PKG_NAME = $Pkg
34
+ }
35
+
28
36
  # Read local version
29
37
  $LOCAL_VERSION = ""
30
38
  try {
@@ -41,6 +49,7 @@ if ($Help) {
41
49
  Write-Host " .\upgrade-via-npm.ps1 -Version [version] # upgrade to specific version"
42
50
  Write-Host " .\upgrade-via-npm.ps1 -SelfVersion # upgrade to repo version ($LOCAL_VERSION)"
43
51
  Write-Host ""
52
+ Write-Host " -Pkg [scope/name] Custom npm package (e.g. ryantest/openclaw-qqbot)"
44
53
  Write-Host " -AppId [appid] QQ bot appid (required on first install)"
45
54
  Write-Host " -Secret [secret] QQ bot secret (required on first install)"
46
55
  exit 0