@ryantest/openclaw-qqbot 1.6.7-beta.15 → 1.6.7-beta.17
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/dist/src/slash-commands.js +146 -128
- package/package.json +1 -1
- package/src/slash-commands.ts +143 -120
|
@@ -694,131 +694,95 @@ function fireHotUpgrade(targetVersion, pkg, useLocal) {
|
|
|
694
694
|
// openclaw plugins install/update 启动时会校验整个配置文件,
|
|
695
695
|
// 如果 channels.qqbot 已存在但 qqbot 插件尚未加载,校验会报 "unknown channel id: qqbot"。
|
|
696
696
|
//
|
|
697
|
-
//
|
|
698
|
-
//
|
|
699
|
-
//
|
|
700
|
-
//
|
|
701
|
-
//
|
|
702
|
-
//
|
|
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)内部也有同样的临时配置机制作为双保险。
|
|
703
705
|
const homeDir = getHomeDir();
|
|
704
706
|
const realConfigPath = path.join(homeDir, ".openclaw", "openclaw.json");
|
|
705
|
-
const channelBackupPath = path.join(homeDir, ".openclaw", ".qqbot-upgrade-channel-stash.json");
|
|
706
|
-
let stashedChannelConfig = null;
|
|
707
|
-
let stashedPluginEntry = null;
|
|
708
|
-
let configModified = false;
|
|
709
707
|
let tempConfigPath = null;
|
|
710
708
|
const childEnv = { ...process.env };
|
|
711
709
|
try {
|
|
712
710
|
if (fs.existsSync(realConfigPath)) {
|
|
713
711
|
const cfg = JSON.parse(fs.readFileSync(realConfigPath, "utf8"));
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
stash.channelsQqbot = cfg.channels.qqbot;
|
|
724
|
-
stashedChannelConfig = cfg.channels.qqbot;
|
|
725
|
-
}
|
|
726
|
-
if (cfg.plugins?.entries?.["openclaw-qqbot"]) {
|
|
727
|
-
stash.pluginsEntry = cfg.plugins.entries["openclaw-qqbot"];
|
|
728
|
-
stashedPluginEntry = cfg.plugins.entries["openclaw-qqbot"];
|
|
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;
|
|
729
721
|
}
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
delete cfg.channels.qqbot;
|
|
735
|
-
if (Object.keys(cfg.channels).length === 0)
|
|
736
|
-
delete cfg.channels;
|
|
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;
|
|
737
726
|
}
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
configModified = true;
|
|
745
|
-
console.log(`[qqbot] fireHotUpgrade: temporarily removed channels.qqbot & plugins.entries from real config (stash=${channelBackupPath})`);
|
|
746
|
-
// 双保险:同时创建临时配置副本并通过 OPENCLAW_CONFIG_PATH 传递
|
|
747
|
-
try {
|
|
748
|
-
const tmpDir = path.join(homeDir, ".openclaw", ".qqbot-upgrade-tmp");
|
|
749
|
-
fs.mkdirSync(tmpDir, { recursive: true });
|
|
750
|
-
tempConfigPath = path.join(tmpDir, "openclaw-tmp.json");
|
|
751
|
-
fs.writeFileSync(tempConfigPath, JSON.stringify(cfg, null, 4) + "\n");
|
|
752
|
-
childEnv.OPENCLAW_CONFIG_PATH = tempConfigPath;
|
|
753
|
-
}
|
|
754
|
-
catch { /* 非关键,忽略 */ }
|
|
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`);
|
|
755
733
|
}
|
|
756
734
|
}
|
|
757
735
|
}
|
|
758
736
|
catch (e) {
|
|
759
|
-
console.warn(`[qqbot] fireHotUpgrade: failed to
|
|
760
|
-
|
|
761
|
-
stashedChannelConfig = null;
|
|
762
|
-
stashedPluginEntry = null;
|
|
737
|
+
console.warn(`[qqbot] fireHotUpgrade: failed to create temp config: ${e.message}, proceeding with original`);
|
|
738
|
+
tempConfigPath = null;
|
|
763
739
|
}
|
|
764
740
|
/**
|
|
765
|
-
*
|
|
766
|
-
*
|
|
741
|
+
* 将 openclaw plugins install 写入临时配置的 installs/entries 记录同步回真实配置,
|
|
742
|
+
* 然后清理临时文件。
|
|
743
|
+
*
|
|
744
|
+
* 注意:真实配置中的 channels.qqbot 从未被移除,无需恢复。
|
|
767
745
|
*/
|
|
768
|
-
function
|
|
746
|
+
function syncTempConfigAndCleanup() {
|
|
769
747
|
try {
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
if (configModified && fs.existsSync(realConfigPath)) {
|
|
781
|
-
const cfg = JSON.parse(fs.readFileSync(realConfigPath, "utf8"));
|
|
782
|
-
// 恢复 channels.qqbot
|
|
783
|
-
if (channelCfg) {
|
|
784
|
-
if (!cfg.channels)
|
|
785
|
-
cfg.channels = {};
|
|
786
|
-
cfg.channels.qqbot = channelCfg;
|
|
787
|
-
}
|
|
788
|
-
// 恢复 plugins.entries.openclaw-qqbot(仅当 openclaw plugins install 没有写入新 entry 时)
|
|
789
|
-
if (entryCfg) {
|
|
790
|
-
if (!cfg.plugins)
|
|
791
|
-
cfg.plugins = {};
|
|
792
|
-
if (!cfg.plugins.entries)
|
|
793
|
-
cfg.plugins.entries = {};
|
|
794
|
-
if (!cfg.plugins.entries["openclaw-qqbot"]) {
|
|
795
|
-
cfg.plugins.entries["openclaw-qqbot"] = entryCfg;
|
|
796
|
-
}
|
|
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;
|
|
797
758
|
}
|
|
798
|
-
//
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
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;
|
|
806
773
|
}
|
|
807
774
|
}
|
|
808
|
-
catch { /* 忽略 */ }
|
|
809
775
|
}
|
|
810
|
-
|
|
811
|
-
|
|
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
|
+
}
|
|
812
780
|
}
|
|
813
781
|
}
|
|
814
782
|
catch (e) {
|
|
815
|
-
console.warn(`[qqbot] fireHotUpgrade: failed to
|
|
783
|
+
console.warn(`[qqbot] fireHotUpgrade: failed to sync temp config: ${e.message}`);
|
|
816
784
|
}
|
|
817
|
-
//
|
|
818
|
-
try {
|
|
819
|
-
fs.unlinkSync(channelBackupPath);
|
|
820
|
-
}
|
|
821
|
-
catch { /* ignore */ }
|
|
785
|
+
// 清理临时文件
|
|
822
786
|
try {
|
|
823
787
|
if (tempConfigPath)
|
|
824
788
|
fs.unlinkSync(tempConfigPath);
|
|
@@ -841,7 +805,7 @@ function fireHotUpgrade(targetVersion, pkg, useLocal) {
|
|
|
841
805
|
console.error(`[qqbot] fireHotUpgrade: stdout: ${stdout.slice(0, 2000)}`);
|
|
842
806
|
if (_stderr)
|
|
843
807
|
console.error(`[qqbot] fireHotUpgrade: stderr: ${_stderr.slice(0, 2000)}`);
|
|
844
|
-
|
|
808
|
+
syncTempConfigAndCleanup();
|
|
845
809
|
cleanupTempScript();
|
|
846
810
|
_upgrading = false;
|
|
847
811
|
return;
|
|
@@ -852,14 +816,14 @@ function fireHotUpgrade(targetVersion, pkg, useLocal) {
|
|
|
852
816
|
const newVersion = versionMatch?.[1];
|
|
853
817
|
if (newVersion === "unknown") {
|
|
854
818
|
console.error(`[qqbot] fireHotUpgrade: script output QQBOT_NEW_VERSION=unknown, aborting restart`);
|
|
855
|
-
|
|
819
|
+
syncTempConfigAndCleanup();
|
|
856
820
|
cleanupTempScript();
|
|
857
821
|
_upgrading = false;
|
|
858
822
|
return;
|
|
859
823
|
}
|
|
860
824
|
console.log(`[qqbot] fireHotUpgrade: new version=${newVersion || "(not detected)"}, triggering restart...`);
|
|
861
|
-
//
|
|
862
|
-
|
|
825
|
+
// 脚本执行成功,同步临时配置中的 install 记录并清理
|
|
826
|
+
syncTempConfigAndCleanup();
|
|
863
827
|
cleanupTempScript();
|
|
864
828
|
// 文件替换成功,在 restart 之前把 source 从 path 切换为 npm,
|
|
865
829
|
// 确保新进程启动时读到的是 npm source,不会被本地源码覆盖。
|
|
@@ -935,12 +899,64 @@ CLI="${cliInvoke}"
|
|
|
935
899
|
CONFIG="${configPath}"
|
|
936
900
|
BACKUP="${qqbotChannelBackup}"
|
|
937
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
|
+
|
|
938
935
|
echo "[qqbot-upgrade] Stopping gateway..."
|
|
939
|
-
|
|
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
|
|
940
941
|
sleep 2
|
|
941
942
|
|
|
942
|
-
|
|
943
|
-
|
|
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..."
|
|
944
960
|
node -e "
|
|
945
961
|
const fs = require('fs');
|
|
946
962
|
const cfg = JSON.parse(fs.readFileSync(process.argv[1], 'utf8'));
|
|
@@ -959,42 +975,44 @@ if [ -f "$BACKUP" ]; then
|
|
|
959
975
|
fs.writeFileSync(process.argv[1], JSON.stringify(cfg, null, 4) + '\\n');
|
|
960
976
|
}
|
|
961
977
|
" "$CONFIG" 2>/dev/null
|
|
962
|
-
echo "[qqbot-upgrade] channels.qqbot temporarily removed"
|
|
963
|
-
fi
|
|
964
978
|
|
|
965
|
-
|
|
966
|
-
START_OK=
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
echo "[qqbot-upgrade] WARNING: gateway start failed, will still restore config"
|
|
972
|
-
fi
|
|
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
|
|
973
985
|
|
|
974
|
-
|
|
975
|
-
|
|
986
|
+
# 等待 gateway 进程启动并加载插件(插件注册 qqbot channel type)
|
|
987
|
+
echo "[qqbot-upgrade] Waiting for plugin to load (8s)..."
|
|
988
|
+
sleep 8
|
|
976
989
|
|
|
977
|
-
|
|
978
|
-
|
|
990
|
+
# 恢复 channels.qqbot 到真实配置
|
|
991
|
+
# gateway 的 config file watcher 会检测到变更并热加载
|
|
992
|
+
echo "[qqbot-upgrade] Restoring channels.qqbot to real config..."
|
|
979
993
|
node -e "
|
|
994
|
+
const fs = require('fs');
|
|
980
995
|
const fs = require('fs');
|
|
981
996
|
const cfg = JSON.parse(fs.readFileSync(process.argv[1], 'utf8'));
|
|
982
997
|
const qqbot = JSON.parse(fs.readFileSync(process.argv[2], 'utf8'));
|
|
983
998
|
if (!cfg.channels) cfg.channels = {};
|
|
984
999
|
cfg.channels.qqbot = qqbot;
|
|
985
|
-
//
|
|
986
|
-
|
|
987
|
-
if (!cfg.plugins.entries) cfg.plugins.entries = {};
|
|
988
|
-
if (!cfg.plugins.entries['openclaw-qqbot']) {
|
|
989
|
-
cfg.plugins.entries['openclaw-qqbot'] = { enabled: true };
|
|
990
|
-
}
|
|
1000
|
+
// 注意:不写入 plugins.entries.openclaw-qqbot,
|
|
1001
|
+
// 插件通过 auto-discover 加载,显式 entry 会导致 duplicate plugin id 警告。
|
|
991
1002
|
fs.writeFileSync(process.argv[1], JSON.stringify(cfg, null, 4) + '\\n');
|
|
992
1003
|
" "$CONFIG" "$BACKUP" 2>/dev/null
|
|
993
1004
|
rm -f "$BACKUP"
|
|
994
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"
|
|
995
1013
|
fi
|
|
996
1014
|
|
|
997
|
-
# 如果 start
|
|
1015
|
+
# 如果 start 失败,尝试再次启动
|
|
998
1016
|
if [ "$START_OK" != "true" ]; then
|
|
999
1017
|
echo "[qqbot-upgrade] Retrying gateway start..."
|
|
1000
1018
|
sleep 2
|
package/package.json
CHANGED
package/src/slash-commands.ts
CHANGED
|
@@ -796,125 +796,94 @@ function fireHotUpgrade(targetVersion?: string, pkg?: string, useLocal?: boolean
|
|
|
796
796
|
// openclaw plugins install/update 启动时会校验整个配置文件,
|
|
797
797
|
// 如果 channels.qqbot 已存在但 qqbot 插件尚未加载,校验会报 "unknown channel id: qqbot"。
|
|
798
798
|
//
|
|
799
|
-
//
|
|
800
|
-
//
|
|
801
|
-
//
|
|
802
|
-
//
|
|
803
|
-
//
|
|
804
|
-
//
|
|
799
|
+
// ⚠️ 关键:绝不能直接修改真实的 openclaw.json!
|
|
800
|
+
// gateway 的 config file watcher 会检测到变更并触发 SIGUSR1 重启,
|
|
801
|
+
// 导致当前进程被杀、execFile 回调(restoreConfigAndCleanup)永远不会执行,
|
|
802
|
+
// channels.qqbot 配置就此丢失。
|
|
803
|
+
//
|
|
804
|
+
// 策略:创建临时配置副本(不含 channels.qqbot),通过 OPENCLAW_CONFIG_PATH
|
|
805
|
+
// 环境变量传递给子进程,真实配置文件不受影响。
|
|
806
|
+
// shell 脚本(upgrade-via-npm.sh)内部也有同样的临时配置机制作为双保险。
|
|
805
807
|
const homeDir = getHomeDir();
|
|
806
808
|
const realConfigPath = path.join(homeDir, ".openclaw", "openclaw.json");
|
|
807
|
-
const channelBackupPath = path.join(homeDir, ".openclaw", ".qqbot-upgrade-channel-stash.json");
|
|
808
|
-
let stashedChannelConfig: unknown = null;
|
|
809
|
-
let stashedPluginEntry: unknown = null;
|
|
810
|
-
let configModified = false;
|
|
811
809
|
let tempConfigPath: string | null = null;
|
|
812
810
|
const childEnv: NodeJS.ProcessEnv = { ...process.env };
|
|
813
811
|
|
|
814
812
|
try {
|
|
815
813
|
if (fs.existsSync(realConfigPath)) {
|
|
816
814
|
const cfg = JSON.parse(fs.readFileSync(realConfigPath, "utf8"));
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
//
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
stashedChannelConfig = cfg.channels.qqbot;
|
|
815
|
+
const needsTempConfig =
|
|
816
|
+
!!(cfg.channels?.qqbot) ||
|
|
817
|
+
!!(cfg.plugins?.entries?.["openclaw-qqbot"]);
|
|
818
|
+
|
|
819
|
+
if (needsTempConfig) {
|
|
820
|
+
// 创建临时配置副本(移除 channels.qqbot 和 plugins.entries.openclaw-qqbot)
|
|
821
|
+
const cleanCfg = JSON.parse(JSON.stringify(cfg)); // deep clone
|
|
822
|
+
if (cleanCfg.channels?.qqbot) {
|
|
823
|
+
delete cleanCfg.channels.qqbot;
|
|
824
|
+
if (Object.keys(cleanCfg.channels).length === 0) delete cleanCfg.channels;
|
|
828
825
|
}
|
|
829
|
-
if (
|
|
830
|
-
|
|
831
|
-
|
|
826
|
+
if (cleanCfg.plugins?.entries?.["openclaw-qqbot"]) {
|
|
827
|
+
delete cleanCfg.plugins.entries["openclaw-qqbot"];
|
|
828
|
+
if (cleanCfg.plugins.entries && Object.keys(cleanCfg.plugins.entries).length === 0) delete cleanCfg.plugins.entries;
|
|
832
829
|
}
|
|
833
|
-
// 写入备份文件(防止进程异常退出时丢失暂存数据)
|
|
834
|
-
fs.writeFileSync(channelBackupPath, JSON.stringify(stash, null, 2), "utf8");
|
|
835
830
|
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
delete cfg.plugins.entries["openclaw-qqbot"];
|
|
843
|
-
if (Object.keys(cfg.plugins.entries).length === 0) delete cfg.plugins.entries;
|
|
844
|
-
}
|
|
845
|
-
fs.writeFileSync(realConfigPath, JSON.stringify(cfg, null, 4) + "\n");
|
|
846
|
-
configModified = true;
|
|
847
|
-
console.log(`[qqbot] fireHotUpgrade: temporarily removed channels.qqbot & plugins.entries from real config (stash=${channelBackupPath})`);
|
|
848
|
-
|
|
849
|
-
// 双保险:同时创建临时配置副本并通过 OPENCLAW_CONFIG_PATH 传递
|
|
850
|
-
try {
|
|
851
|
-
const tmpDir = path.join(homeDir, ".openclaw", ".qqbot-upgrade-tmp");
|
|
852
|
-
fs.mkdirSync(tmpDir, { recursive: true });
|
|
853
|
-
tempConfigPath = path.join(tmpDir, "openclaw-tmp.json");
|
|
854
|
-
fs.writeFileSync(tempConfigPath, JSON.stringify(cfg, null, 4) + "\n");
|
|
855
|
-
childEnv.OPENCLAW_CONFIG_PATH = tempConfigPath;
|
|
856
|
-
} catch { /* 非关键,忽略 */ }
|
|
831
|
+
const tmpDir = path.join(homeDir, ".openclaw", ".qqbot-upgrade-tmp");
|
|
832
|
+
fs.mkdirSync(tmpDir, { recursive: true });
|
|
833
|
+
tempConfigPath = path.join(tmpDir, "openclaw-tmp.json");
|
|
834
|
+
fs.writeFileSync(tempConfigPath, JSON.stringify(cleanCfg, null, 4) + "\n");
|
|
835
|
+
childEnv.OPENCLAW_CONFIG_PATH = tempConfigPath;
|
|
836
|
+
console.log(`[qqbot] fireHotUpgrade: created temp config without channels.qqbot (OPENCLAW_CONFIG_PATH=${tempConfigPath}), real config untouched`);
|
|
857
837
|
}
|
|
858
838
|
}
|
|
859
839
|
} catch (e: any) {
|
|
860
|
-
console.warn(`[qqbot] fireHotUpgrade: failed to
|
|
861
|
-
|
|
862
|
-
stashedChannelConfig = null;
|
|
863
|
-
stashedPluginEntry = null;
|
|
840
|
+
console.warn(`[qqbot] fireHotUpgrade: failed to create temp config: ${e.message}, proceeding with original`);
|
|
841
|
+
tempConfigPath = null;
|
|
864
842
|
}
|
|
865
843
|
|
|
866
844
|
/**
|
|
867
|
-
*
|
|
868
|
-
*
|
|
845
|
+
* 将 openclaw plugins install 写入临时配置的 installs/entries 记录同步回真实配置,
|
|
846
|
+
* 然后清理临时文件。
|
|
847
|
+
*
|
|
848
|
+
* 注意:真实配置中的 channels.qqbot 从未被移除,无需恢复。
|
|
869
849
|
*/
|
|
870
|
-
function
|
|
850
|
+
function syncTempConfigAndCleanup(): void {
|
|
871
851
|
try {
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
if (configModified && fs.existsSync(realConfigPath)) {
|
|
883
|
-
const cfg = JSON.parse(fs.readFileSync(realConfigPath, "utf8"));
|
|
884
|
-
|
|
885
|
-
// 恢复 channels.qqbot
|
|
886
|
-
if (channelCfg) {
|
|
887
|
-
if (!cfg.channels) cfg.channels = {};
|
|
888
|
-
cfg.channels.qqbot = channelCfg;
|
|
852
|
+
if (tempConfigPath && fs.existsSync(tempConfigPath) && fs.existsSync(realConfigPath)) {
|
|
853
|
+
const tmp = JSON.parse(fs.readFileSync(tempConfigPath, "utf8"));
|
|
854
|
+
const real = JSON.parse(fs.readFileSync(realConfigPath, "utf8"));
|
|
855
|
+
let changed = false;
|
|
856
|
+
|
|
857
|
+
// 同步 plugins.installs(openclaw plugins install 会写入安装记录)
|
|
858
|
+
if (tmp.plugins?.installs) {
|
|
859
|
+
if (!real.plugins) real.plugins = {};
|
|
860
|
+
real.plugins.installs = { ...(real.plugins.installs || {}), ...tmp.plugins.installs };
|
|
861
|
+
changed = true;
|
|
889
862
|
}
|
|
890
|
-
//
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
if (!
|
|
895
|
-
|
|
863
|
+
// 同步 plugins.entries(openclaw plugins install 会写入 entries)
|
|
864
|
+
// 注意:不同步 openclaw-qqbot 自身的 entry,因为插件通过 auto-discover 加载,
|
|
865
|
+
// 显式写入 entries 会导致 "duplicate plugin id" 警告刷屏。
|
|
866
|
+
if (tmp.plugins?.entries) {
|
|
867
|
+
if (!real.plugins) real.plugins = {};
|
|
868
|
+
if (!real.plugins.entries) real.plugins.entries = {};
|
|
869
|
+
for (const [k, v] of Object.entries(tmp.plugins.entries)) {
|
|
870
|
+
if (k === "openclaw-qqbot") continue; // 跳过自身,避免 duplicate
|
|
871
|
+
if (!real.plugins.entries[k]) {
|
|
872
|
+
real.plugins.entries[k] = v;
|
|
873
|
+
changed = true;
|
|
874
|
+
}
|
|
896
875
|
}
|
|
897
876
|
}
|
|
898
877
|
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
const tmp = JSON.parse(fs.readFileSync(tempConfigPath, "utf8"));
|
|
903
|
-
if (tmp.plugins?.installs) {
|
|
904
|
-
if (!cfg.plugins) cfg.plugins = {};
|
|
905
|
-
cfg.plugins.installs = { ...(cfg.plugins.installs || {}), ...tmp.plugins.installs };
|
|
906
|
-
}
|
|
907
|
-
} catch { /* 忽略 */ }
|
|
878
|
+
if (changed) {
|
|
879
|
+
fs.writeFileSync(realConfigPath, JSON.stringify(real, null, 4) + "\n");
|
|
880
|
+
console.log("[qqbot] fireHotUpgrade: synced install/entries records from temp config to real config");
|
|
908
881
|
}
|
|
909
|
-
|
|
910
|
-
fs.writeFileSync(realConfigPath, JSON.stringify(cfg, null, 4) + "\n");
|
|
911
|
-
console.log("[qqbot] fireHotUpgrade: restored channels.qqbot & plugins.entries to real config");
|
|
912
882
|
}
|
|
913
883
|
} catch (e: any) {
|
|
914
|
-
console.warn(`[qqbot] fireHotUpgrade: failed to
|
|
884
|
+
console.warn(`[qqbot] fireHotUpgrade: failed to sync temp config: ${e.message}`);
|
|
915
885
|
}
|
|
916
|
-
//
|
|
917
|
-
try { fs.unlinkSync(channelBackupPath); } catch { /* ignore */ }
|
|
886
|
+
// 清理临时文件
|
|
918
887
|
try { if (tempConfigPath) fs.unlinkSync(tempConfigPath); } catch { /* ignore */ }
|
|
919
888
|
}
|
|
920
889
|
|
|
@@ -932,7 +901,7 @@ function fireHotUpgrade(targetVersion?: string, pkg?: string, useLocal?: boolean
|
|
|
932
901
|
console.error(`[qqbot] fireHotUpgrade: script failed: ${error.message}`);
|
|
933
902
|
if (stdout) console.error(`[qqbot] fireHotUpgrade: stdout: ${stdout.slice(0, 2000)}`);
|
|
934
903
|
if (_stderr) console.error(`[qqbot] fireHotUpgrade: stderr: ${_stderr.slice(0, 2000)}`);
|
|
935
|
-
|
|
904
|
+
syncTempConfigAndCleanup();
|
|
936
905
|
cleanupTempScript();
|
|
937
906
|
_upgrading = false;
|
|
938
907
|
return;
|
|
@@ -945,7 +914,7 @@ function fireHotUpgrade(targetVersion?: string, pkg?: string, useLocal?: boolean
|
|
|
945
914
|
const newVersion = versionMatch?.[1];
|
|
946
915
|
if (newVersion === "unknown") {
|
|
947
916
|
console.error(`[qqbot] fireHotUpgrade: script output QQBOT_NEW_VERSION=unknown, aborting restart`);
|
|
948
|
-
|
|
917
|
+
syncTempConfigAndCleanup();
|
|
949
918
|
cleanupTempScript();
|
|
950
919
|
_upgrading = false;
|
|
951
920
|
return;
|
|
@@ -953,8 +922,8 @@ function fireHotUpgrade(targetVersion?: string, pkg?: string, useLocal?: boolean
|
|
|
953
922
|
|
|
954
923
|
console.log(`[qqbot] fireHotUpgrade: new version=${newVersion || "(not detected)"}, triggering restart...`);
|
|
955
924
|
|
|
956
|
-
//
|
|
957
|
-
|
|
925
|
+
// 脚本执行成功,同步临时配置中的 install 记录并清理
|
|
926
|
+
syncTempConfigAndCleanup();
|
|
958
927
|
cleanupTempScript();
|
|
959
928
|
|
|
960
929
|
// 文件替换成功,在 restart 之前把 source 从 path 切换为 npm,
|
|
@@ -1031,12 +1000,64 @@ CLI="${cliInvoke}"
|
|
|
1031
1000
|
CONFIG="${configPath}"
|
|
1032
1001
|
BACKUP="${qqbotChannelBackup}"
|
|
1033
1002
|
|
|
1003
|
+
# ── 兼容 openclaw 3.23+ 配置严格校验 ──
|
|
1004
|
+
# 所有 openclaw CLI 命令(包括 gateway stop/start)启动时都会 loadConfig 校验配置,
|
|
1005
|
+
# 如果 channels.qqbot 存在但 qqbot 插件尚未加载,校验会报 "unknown channel id: qqbot"。
|
|
1006
|
+
#
|
|
1007
|
+
# 策略:
|
|
1008
|
+
# 1. gateway stop:使用 OPENCLAW_CONFIG_PATH 临时配置(不含 channels.qqbot)
|
|
1009
|
+
# 2. gateway start:先尝试直接启动(真实配置),如果 CLI 校验失败,
|
|
1010
|
+
# 则临时修改真实配置(此时 gateway 已停止,无 config watcher),启动后恢复。
|
|
1011
|
+
# 这样 gateway 进程读取的是完整配置(含 channels.qqbot)。
|
|
1012
|
+
|
|
1013
|
+
# 为 gateway stop 创建临时配置
|
|
1014
|
+
TEMP_RESTART_CONFIG=""
|
|
1015
|
+
if [ -f "$BACKUP" ]; then
|
|
1016
|
+
TEMP_RESTART_CONFIG="\$(mktemp)"
|
|
1017
|
+
node -e "
|
|
1018
|
+
const fs = require('fs');
|
|
1019
|
+
const cfg = JSON.parse(fs.readFileSync(process.argv[1], 'utf8'));
|
|
1020
|
+
if (cfg.channels && cfg.channels.qqbot) {
|
|
1021
|
+
delete cfg.channels.qqbot;
|
|
1022
|
+
if (Object.keys(cfg.channels).length === 0) delete cfg.channels;
|
|
1023
|
+
}
|
|
1024
|
+
if (cfg.plugins && cfg.plugins.entries && cfg.plugins.entries['openclaw-qqbot']) {
|
|
1025
|
+
delete cfg.plugins.entries['openclaw-qqbot'];
|
|
1026
|
+
if (Object.keys(cfg.plugins.entries).length === 0) delete cfg.plugins.entries;
|
|
1027
|
+
}
|
|
1028
|
+
fs.writeFileSync(process.argv[2], JSON.stringify(cfg, null, 4) + '\\n');
|
|
1029
|
+
" "$CONFIG" "$TEMP_RESTART_CONFIG" 2>/dev/null
|
|
1030
|
+
if [ \$? -ne 0 ] || [ ! -s "$TEMP_RESTART_CONFIG" ]; then
|
|
1031
|
+
echo "[qqbot-upgrade] WARNING: failed to create temp config"
|
|
1032
|
+
TEMP_RESTART_CONFIG=""
|
|
1033
|
+
fi
|
|
1034
|
+
fi
|
|
1035
|
+
|
|
1034
1036
|
echo "[qqbot-upgrade] Stopping gateway..."
|
|
1035
|
-
|
|
1037
|
+
if [ -n "$TEMP_RESTART_CONFIG" ]; then
|
|
1038
|
+
OPENCLAW_CONFIG_PATH="$TEMP_RESTART_CONFIG" $CLI gateway stop 2>/dev/null || true
|
|
1039
|
+
else
|
|
1040
|
+
$CLI gateway stop 2>/dev/null || true
|
|
1041
|
+
fi
|
|
1036
1042
|
sleep 2
|
|
1037
1043
|
|
|
1038
|
-
|
|
1039
|
-
|
|
1044
|
+
# 清理临时配置(不再需要)
|
|
1045
|
+
if [ -n "$TEMP_RESTART_CONFIG" ] && [ -f "$TEMP_RESTART_CONFIG" ]; then
|
|
1046
|
+
rm -f "$TEMP_RESTART_CONFIG"
|
|
1047
|
+
fi
|
|
1048
|
+
|
|
1049
|
+
echo "[qqbot-upgrade] Starting gateway..."
|
|
1050
|
+
START_OK=false
|
|
1051
|
+
|
|
1052
|
+
# 先尝试直接启动(使用真实配置,含 channels.qqbot)
|
|
1053
|
+
# 如果 openclaw 版本不做严格校验,或者插件已注册,这会直接成功
|
|
1054
|
+
if $CLI gateway start 2>/dev/null; then
|
|
1055
|
+
START_OK=true
|
|
1056
|
+
echo "[qqbot-upgrade] Gateway started successfully (direct start)"
|
|
1057
|
+
elif [ -f "$BACKUP" ]; then
|
|
1058
|
+
# 直接启动失败(可能是 channels.qqbot 校验失败),
|
|
1059
|
+
# 临时修改真实配置(此时 gateway 已停止,无 config watcher,安全)
|
|
1060
|
+
echo "[qqbot-upgrade] Direct start failed, temporarily removing channels.qqbot from real config..."
|
|
1040
1061
|
node -e "
|
|
1041
1062
|
const fs = require('fs');
|
|
1042
1063
|
const cfg = JSON.parse(fs.readFileSync(process.argv[1], 'utf8'));
|
|
@@ -1055,42 +1076,44 @@ if [ -f "$BACKUP" ]; then
|
|
|
1055
1076
|
fs.writeFileSync(process.argv[1], JSON.stringify(cfg, null, 4) + '\\n');
|
|
1056
1077
|
}
|
|
1057
1078
|
" "$CONFIG" 2>/dev/null
|
|
1058
|
-
echo "[qqbot-upgrade] channels.qqbot temporarily removed"
|
|
1059
|
-
fi
|
|
1060
1079
|
|
|
1061
|
-
|
|
1062
|
-
START_OK=
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
echo "[qqbot-upgrade] WARNING: gateway start failed, will still restore config"
|
|
1068
|
-
fi
|
|
1080
|
+
if $CLI gateway start 2>/dev/null; then
|
|
1081
|
+
START_OK=true
|
|
1082
|
+
echo "[qqbot-upgrade] Gateway started successfully (after config fix)"
|
|
1083
|
+
else
|
|
1084
|
+
echo "[qqbot-upgrade] WARNING: gateway start still failed after config fix"
|
|
1085
|
+
fi
|
|
1069
1086
|
|
|
1070
|
-
|
|
1071
|
-
|
|
1087
|
+
# 等待 gateway 进程启动并加载插件(插件注册 qqbot channel type)
|
|
1088
|
+
echo "[qqbot-upgrade] Waiting for plugin to load (8s)..."
|
|
1089
|
+
sleep 8
|
|
1072
1090
|
|
|
1073
|
-
|
|
1074
|
-
|
|
1091
|
+
# 恢复 channels.qqbot 到真实配置
|
|
1092
|
+
# gateway 的 config file watcher 会检测到变更并热加载
|
|
1093
|
+
echo "[qqbot-upgrade] Restoring channels.qqbot to real config..."
|
|
1075
1094
|
node -e "
|
|
1095
|
+
const fs = require('fs');
|
|
1076
1096
|
const fs = require('fs');
|
|
1077
1097
|
const cfg = JSON.parse(fs.readFileSync(process.argv[1], 'utf8'));
|
|
1078
1098
|
const qqbot = JSON.parse(fs.readFileSync(process.argv[2], 'utf8'));
|
|
1079
1099
|
if (!cfg.channels) cfg.channels = {};
|
|
1080
1100
|
cfg.channels.qqbot = qqbot;
|
|
1081
|
-
//
|
|
1082
|
-
|
|
1083
|
-
if (!cfg.plugins.entries) cfg.plugins.entries = {};
|
|
1084
|
-
if (!cfg.plugins.entries['openclaw-qqbot']) {
|
|
1085
|
-
cfg.plugins.entries['openclaw-qqbot'] = { enabled: true };
|
|
1086
|
-
}
|
|
1101
|
+
// 注意:不写入 plugins.entries.openclaw-qqbot,
|
|
1102
|
+
// 插件通过 auto-discover 加载,显式 entry 会导致 duplicate plugin id 警告。
|
|
1087
1103
|
fs.writeFileSync(process.argv[1], JSON.stringify(cfg, null, 4) + '\\n');
|
|
1088
1104
|
" "$CONFIG" "$BACKUP" 2>/dev/null
|
|
1089
1105
|
rm -f "$BACKUP"
|
|
1090
1106
|
echo "[qqbot-upgrade] channels.qqbot restored"
|
|
1107
|
+
else
|
|
1108
|
+
echo "[qqbot-upgrade] WARNING: gateway start failed, no backup to restore"
|
|
1109
|
+
fi
|
|
1110
|
+
|
|
1111
|
+
# 直接启动成功的情况下,清理备份文件
|
|
1112
|
+
if [ "$START_OK" = "true" ] && [ -f "$BACKUP" ]; then
|
|
1113
|
+
rm -f "$BACKUP"
|
|
1091
1114
|
fi
|
|
1092
1115
|
|
|
1093
|
-
# 如果 start
|
|
1116
|
+
# 如果 start 失败,尝试再次启动
|
|
1094
1117
|
if [ "$START_OK" != "true" ]; then
|
|
1095
1118
|
echo "[qqbot-upgrade] Retrying gateway start..."
|
|
1096
1119
|
sleep 2
|