@tencent-connect/openclaw-qqbot 1.7.0 → 1.7.2
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 +216 -49
- package/README.zh.md +216 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/src/api.d.ts +6 -0
- package/dist/src/api.js +33 -4
- package/dist/src/approval-handler.d.ts +47 -0
- package/dist/src/approval-handler.js +372 -0
- package/dist/src/channel.js +72 -0
- package/dist/src/config.d.ts +5 -1
- package/dist/src/config.js +12 -2
- package/dist/src/gateway.js +175 -170
- package/dist/src/slash-commands.d.ts +7 -2
- package/dist/src/slash-commands.js +354 -3
- package/dist/src/tools/channel.js +1 -4
- package/dist/src/tools/remind.js +0 -1
- package/dist/src/transport/index.d.ts +10 -0
- package/dist/src/transport/index.js +9 -0
- package/dist/src/transport/webhook-transport.d.ts +67 -0
- package/dist/src/transport/webhook-transport.js +245 -0
- package/dist/src/transport/webhook-verify.d.ts +48 -0
- package/dist/src/transport/webhook-verify.js +98 -0
- package/dist/src/types.d.ts +85 -0
- package/dist/src/utils/audio-convert.js +37 -9
- package/index.ts +1 -0
- package/package.json +1 -1
- package/scripts/postinstall-link-sdk.js +44 -0
- package/scripts/upgrade-via-npm.sh +358 -62
- package/scripts/upgrade-via-source.sh +122 -85
- package/src/api.ts +50 -5
- package/src/approval-handler.ts +505 -0
- package/src/channel.ts +76 -0
- package/src/config.ts +15 -2
- package/src/gateway.ts +181 -169
- package/src/onboarding.ts +8 -0
- package/src/openclaw-plugin-sdk.d.ts +127 -2
- package/src/slash-commands.ts +390 -5
- package/src/tools/channel.ts +1 -7
- package/src/tools/remind.ts +0 -2
- package/src/transport/index.ts +11 -0
- package/src/transport/webhook-transport.ts +332 -0
- package/src/transport/webhook-verify.ts +119 -0
- package/src/types.ts +100 -1
- package/src/typings/openclaw-webhook-ingress.d.ts +66 -0
- package/src/utils/audio-convert.ts +37 -9
|
@@ -17,6 +17,9 @@
|
|
|
17
17
|
|
|
18
18
|
set -eo pipefail
|
|
19
19
|
|
|
20
|
+
# 忽略 SIGTERM:gateway restart 时可能向进程组发送 SIGTERM,不能让它中断升级
|
|
21
|
+
trap 'echo " ⚠️ 收到 SIGTERM,已忽略(升级进行中)"' SIGTERM
|
|
22
|
+
|
|
20
23
|
# ============================================================================
|
|
21
24
|
# 进程隔离 — 脱离 gateway 进程组
|
|
22
25
|
# ============================================================================
|
|
@@ -28,6 +31,8 @@ fi
|
|
|
28
31
|
# ============================================================================
|
|
29
32
|
# 环境准备
|
|
30
33
|
# ============================================================================
|
|
34
|
+
_SCRIPT_START_MS="$(node -e "process.stdout.write(String(Date.now()))" 2>/dev/null || echo "$(date +%s)000")"
|
|
35
|
+
|
|
31
36
|
SCRIPT_DIR="$(cd "$(dirname "$0")" 2>/dev/null && pwd)" || SCRIPT_DIR=""
|
|
32
37
|
PROJECT_DIR=""
|
|
33
38
|
[ -n "$SCRIPT_DIR" ] && PROJECT_DIR="$(cd "$SCRIPT_DIR/.." 2>/dev/null && pwd)" || true
|
|
@@ -53,6 +58,132 @@ done
|
|
|
53
58
|
|
|
54
59
|
NPM_REGISTRIES="https://registry.npmjs.org/ https://mirrors.cloud.tencent.com/npm/"
|
|
55
60
|
|
|
61
|
+
# ============================================================================
|
|
62
|
+
# CLS 日志上报(腾讯云日志服务)
|
|
63
|
+
# ============================================================================
|
|
64
|
+
CLS_HOST="ap-guangzhou.cls.tencentcs.com"
|
|
65
|
+
CLS_TOPIC_ID="${CLS_TOPIC_ID:-845a0802-ec56-49a0-afa0-fe686b0a16f2}"
|
|
66
|
+
CLS_ENABLED="${CLS_ENABLED:-true}"
|
|
67
|
+
|
|
68
|
+
# 静态字段(全局变量,启动时采集一次)
|
|
69
|
+
_STATIC_FIELDS=""
|
|
70
|
+
_SESSION_ID=""
|
|
71
|
+
|
|
72
|
+
# 采集静态字段(只执行一次)
|
|
73
|
+
init_track_log() {
|
|
74
|
+
[ "$CLS_ENABLED" != "true" ] && return 0
|
|
75
|
+
[ -n "$_STATIC_FIELDS" ] && return 0 # 已初始化
|
|
76
|
+
|
|
77
|
+
_SESSION_ID="$(node -e "try{process.stdout.write(require('crypto').randomUUID())}catch{process.stdout.write(Date.now().toString())}" 2>/dev/null || echo "$$-$(date +%s)")"
|
|
78
|
+
|
|
79
|
+
local node_ver="${NODE_VERSION:-$(node --version 2>/dev/null || echo "")}"
|
|
80
|
+
local os_type="$(uname -s 2>/dev/null || echo "unknown")"
|
|
81
|
+
|
|
82
|
+
# 构造静态字段 JSON(转义处理)
|
|
83
|
+
_STATIC_FIELDS=$(node -e "
|
|
84
|
+
const fields = {
|
|
85
|
+
session_id: '$_SESSION_ID',
|
|
86
|
+
script_version: 'v4',
|
|
87
|
+
node_version: '$node_ver',
|
|
88
|
+
os: '$os_type'
|
|
89
|
+
};
|
|
90
|
+
process.stdout.write(JSON.stringify(fields));
|
|
91
|
+
" 2>/dev/null || echo "{}")
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
# 上报日志到 CLS
|
|
95
|
+
# 用法: track_log <event> <result> <log_message> [extra_key1=val1] [extra_key2=val2] ...
|
|
96
|
+
track_log() {
|
|
97
|
+
[ "$CLS_ENABLED" != "true" ] && return 0
|
|
98
|
+
[ -z "$CLS_TOPIC_ID" ] && return 0
|
|
99
|
+
|
|
100
|
+
local event="$1"
|
|
101
|
+
local result="$2"
|
|
102
|
+
local log_msg="$3"
|
|
103
|
+
shift 3
|
|
104
|
+
|
|
105
|
+
# 确保静态字段已初始化
|
|
106
|
+
[ -z "$_STATIC_FIELDS" ] && init_track_log
|
|
107
|
+
|
|
108
|
+
# 计算耗时(毫秒)
|
|
109
|
+
local _now_ms="$(node -e "process.stdout.write(String(Date.now()))" 2>/dev/null || echo "$(date +%s)000")"
|
|
110
|
+
local _elapsed_ms="$(( _now_ms - _SCRIPT_START_MS ))"
|
|
111
|
+
|
|
112
|
+
# 构造额外字段
|
|
113
|
+
local extra_fields=",\"elapsed_ms\":\"$_elapsed_ms\""
|
|
114
|
+
for arg in "$@"; do
|
|
115
|
+
if [[ "$arg" == *=* ]]; then
|
|
116
|
+
local key="${arg%%=*}"
|
|
117
|
+
local val="${arg#*=}"
|
|
118
|
+
extra_fields="$extra_fields,\"$key\":\"$val\""
|
|
119
|
+
fi
|
|
120
|
+
done
|
|
121
|
+
|
|
122
|
+
# 后台异步上报(不阻塞主流程)
|
|
123
|
+
(
|
|
124
|
+
node -e "
|
|
125
|
+
(() => {
|
|
126
|
+
try {
|
|
127
|
+
const https = require('https');
|
|
128
|
+
const staticFields = $_STATIC_FIELDS;
|
|
129
|
+
const contents = {
|
|
130
|
+
...staticFields,
|
|
131
|
+
event: '$event',
|
|
132
|
+
result: '$result',
|
|
133
|
+
log: $(node -e "process.stdout.write(JSON.stringify('$log_msg'))" 2>/dev/null || echo "'$log_msg'"),
|
|
134
|
+
openclaw_version: '${OPENCLAW_VERSION:-}',
|
|
135
|
+
old_version: '${OLD_VERSION:-}',
|
|
136
|
+
new_version: '${NEW_VERSION:-}',
|
|
137
|
+
target_version: '${TARGET_VERSION:-}'$extra_fields
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const body = JSON.stringify({
|
|
141
|
+
logs: [{
|
|
142
|
+
contents: contents,
|
|
143
|
+
time: Math.floor(Date.now() / 1000)
|
|
144
|
+
}],
|
|
145
|
+
source: 'qqbot-upgrade-script'
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
const req = https.request({
|
|
149
|
+
hostname: '$CLS_HOST',
|
|
150
|
+
path: '/tracklog?topic_id=$CLS_TOPIC_ID',
|
|
151
|
+
method: 'POST',
|
|
152
|
+
headers: {
|
|
153
|
+
'Content-Type': 'application/json',
|
|
154
|
+
'Content-Length': Buffer.byteLength(body)
|
|
155
|
+
},
|
|
156
|
+
timeout: 5000
|
|
157
|
+
}, (res) => { res.resume(); });
|
|
158
|
+
|
|
159
|
+
req.on('error', () => {});
|
|
160
|
+
req.on('timeout', () => req.destroy());
|
|
161
|
+
req.end(body);
|
|
162
|
+
} catch {}
|
|
163
|
+
})();
|
|
164
|
+
" 2>/dev/null
|
|
165
|
+
) &
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
# 封装 echo:同时输出到终端 + 上报 CLS
|
|
169
|
+
# 用法: log <message> → event=log, result=info
|
|
170
|
+
# log <event> <result> <message> → 自定义 event/result
|
|
171
|
+
# log <event> <result> <message> k=v → 带额外字段
|
|
172
|
+
log() {
|
|
173
|
+
if [ $# -eq 1 ]; then
|
|
174
|
+
echo "$1"
|
|
175
|
+
track_log "log" "info" "$1"
|
|
176
|
+
elif [ $# -ge 3 ]; then
|
|
177
|
+
local _event="$1" _result="$2" _msg="$3"
|
|
178
|
+
shift 3
|
|
179
|
+
echo "$_msg"
|
|
180
|
+
track_log "$_event" "$_result" "$_msg" "$@"
|
|
181
|
+
else
|
|
182
|
+
# 2 个参数:当普通 echo,不上报
|
|
183
|
+
echo "$@"
|
|
184
|
+
fi
|
|
185
|
+
}
|
|
186
|
+
|
|
56
187
|
# ============================================================================
|
|
57
188
|
# 超时执行包装器(兼容 macOS 无 GNU timeout)
|
|
58
189
|
# ============================================================================
|
|
@@ -62,7 +193,11 @@ run_with_timeout() {
|
|
|
62
193
|
if command -v timeout &>/dev/null; then
|
|
63
194
|
timeout --kill-after=10 "$timeout_secs" "$@" && return 0
|
|
64
195
|
local rc=$?
|
|
65
|
-
|
|
196
|
+
# GNU coreutils timeout 超时返回 124;uutils coreutils 返回 125
|
|
197
|
+
if [ $rc -eq 124 ] || [ $rc -eq 125 ]; then
|
|
198
|
+
echo " ⏰ ${description} 超时 (${timeout_secs}s)"
|
|
199
|
+
return 124 # 统一返回 124 表示超时
|
|
200
|
+
fi
|
|
66
201
|
return $rc
|
|
67
202
|
fi
|
|
68
203
|
|
|
@@ -117,15 +252,21 @@ restore_reload_mode() {
|
|
|
117
252
|
|
|
118
253
|
rollback_plugin_dir() {
|
|
119
254
|
local reason="${1:-未知原因}"
|
|
255
|
+
log "rollback" "start" " 开始回滚..." "reason=$reason"
|
|
120
256
|
if [ -n "$BACKUP_DIR" ] && [ -d "$BACKUP_DIR/$PLUGIN_ID" ]; then
|
|
121
257
|
rm -rf "$EXTENSIONS_DIR/$PLUGIN_ID" 2>/dev/null || true
|
|
122
258
|
mv "$BACKUP_DIR/$PLUGIN_ID" "$EXTENSIONS_DIR/$PLUGIN_ID" 2>/dev/null || \
|
|
123
259
|
cp -a "$BACKUP_DIR/$PLUGIN_ID" "$EXTENSIONS_DIR/$PLUGIN_ID" 2>/dev/null || true
|
|
124
|
-
[ -f "$EXTENSIONS_DIR/$PLUGIN_ID/package.json" ]
|
|
125
|
-
|
|
126
|
-
|
|
260
|
+
if [ -f "$EXTENSIONS_DIR/$PLUGIN_ID/package.json" ]; then
|
|
261
|
+
local rollback_ver="$(read_pkg_version "$EXTENSIONS_DIR/$PLUGIN_ID/package.json")"
|
|
262
|
+
log "rollback" "success" " ↩️ 已回滚到旧版本 v${rollback_ver}(原因: ${reason})" "rollback_version=$rollback_ver"
|
|
263
|
+
return 0
|
|
264
|
+
fi
|
|
265
|
+
log "rollback" "fail" " ❌ 回滚后插件目录仍不完整!"
|
|
266
|
+
return 1
|
|
127
267
|
fi
|
|
128
|
-
|
|
268
|
+
log "rollback" "fail" " ⚠️ 无备份可回滚(原因: ${reason})" "reason=$reason"
|
|
269
|
+
return 1
|
|
129
270
|
}
|
|
130
271
|
|
|
131
272
|
# ============================================================================
|
|
@@ -138,7 +279,7 @@ acquire_upgrade_lock() {
|
|
|
138
279
|
if [ -f "$UPGRADE_LOCK_FILE" ]; then
|
|
139
280
|
local lock_pid="$(cat "$UPGRADE_LOCK_FILE" 2>/dev/null || true)"
|
|
140
281
|
if [ -n "$lock_pid" ] && kill -0 "$lock_pid" 2>/dev/null; then
|
|
141
|
-
|
|
282
|
+
log "lock_conflict" "fail" "❌ 另一个升级进程正在运行 (PID: $lock_pid)" "lock_pid=$lock_pid"; exit 1
|
|
142
283
|
fi
|
|
143
284
|
rm -f "$UPGRADE_LOCK_FILE" 2>/dev/null || true
|
|
144
285
|
fi
|
|
@@ -165,7 +306,9 @@ setup_temp_config() {
|
|
|
165
306
|
" 2>/dev/null || true)"
|
|
166
307
|
[ "$need_temp" != "1" ] && return 0
|
|
167
308
|
|
|
168
|
-
|
|
309
|
+
# 临时配置必须放在 $OPENCLAW_HOME 下,避免 openclaw ≥2026.4.9 将其父目录当作 CONFIG_DIR
|
|
310
|
+
# (2026-04-07 新增逻辑:OPENCLAW_CONFIG_PATH 的 dirname 会被用作 CONFIG_DIR)
|
|
311
|
+
TEMP_CONFIG_FILE="$(mktemp "$OPENCLAW_HOME/.qqbot-temp-config-XXXXXX")"
|
|
169
312
|
if node -e "
|
|
170
313
|
const fs = require('fs');
|
|
171
314
|
const cfg = JSON.parse(fs.readFileSync('$CONFIG_FILE', 'utf8'));
|
|
@@ -179,10 +322,10 @@ setup_temp_config() {
|
|
|
179
322
|
cfg.plugins?.entries && Object.keys(cfg.plugins.entries).length === 0 && delete cfg.plugins.entries;
|
|
180
323
|
fs.writeFileSync('$TEMP_CONFIG_FILE', JSON.stringify(cfg, null, 4) + '\n');
|
|
181
324
|
" 2>/dev/null; then
|
|
182
|
-
|
|
325
|
+
log "temp_config" "success" " [兼容] 创建临时配置副本以通过 3.23+ 配置校验"
|
|
183
326
|
export OPENCLAW_CONFIG_PATH="$TEMP_CONFIG_FILE"
|
|
184
327
|
else
|
|
185
|
-
|
|
328
|
+
log "temp_config" "fail" " ⚠️ 创建临时配置失败,继续使用原配置"
|
|
186
329
|
rm -f "$TEMP_CONFIG_FILE" 2>/dev/null || true; TEMP_CONFIG_FILE=""
|
|
187
330
|
fi
|
|
188
331
|
}
|
|
@@ -235,15 +378,15 @@ npm_pack_download() {
|
|
|
235
378
|
fi
|
|
236
379
|
done
|
|
237
380
|
if [ "$ok" != "true" ]; then
|
|
238
|
-
|
|
381
|
+
log "npm_pack" "fail" " ❌ npm pack 失败(所有 registry 均不可用)"
|
|
239
382
|
rm -rf "$PACK_TMP_DIR" 2>/dev/null; PACK_TMP_DIR=""; return 1
|
|
240
383
|
fi
|
|
241
384
|
PACK_TGZ_FILE="$(find "$PACK_TMP_DIR" -maxdepth 1 -name '*.tgz' -type f | head -1)"
|
|
242
385
|
if [ -z "$PACK_TGZ_FILE" ]; then
|
|
243
|
-
|
|
386
|
+
log "npm_pack" "fail" " ❌ 未找到 tgz 文件"
|
|
244
387
|
rm -rf "$PACK_TMP_DIR" 2>/dev/null; PACK_TMP_DIR=""; return 1
|
|
245
388
|
fi
|
|
246
|
-
|
|
389
|
+
log "npm_pack" "success" " 已下载: $(basename "$PACK_TGZ_FILE")"
|
|
247
390
|
return 0
|
|
248
391
|
}
|
|
249
392
|
|
|
@@ -264,25 +407,47 @@ npm_pack_native_install() {
|
|
|
264
407
|
echo " [Level 2] npm pack + openclaw install 本地目录"
|
|
265
408
|
echo " ============================================"
|
|
266
409
|
|
|
267
|
-
echo " [L2 1/
|
|
410
|
+
echo " [L2 1/4] 下载 tarball..."
|
|
268
411
|
npm_pack_download || return 1
|
|
269
412
|
|
|
270
413
|
# 先解压再传目录路径给 openclaw,而非直接传 tarball 路径
|
|
271
414
|
# 原因:openclaw installPluginFromArchive 漏传 --dangerously-force-unsafe-install,
|
|
272
415
|
# installPluginFromDir 正确传递,传目录可绕过此 bug
|
|
273
|
-
echo " [L2 2/
|
|
416
|
+
echo " [L2 2/4] 解压 tarball..."
|
|
274
417
|
local extract_dir
|
|
275
418
|
extract_dir="$(mktemp -d "${TMPDIR:-/tmp}/.qqbot-extract-XXXXXX")"
|
|
276
419
|
if ! tar xzf "$PACK_TGZ_FILE" -C "$extract_dir" 2>&1; then
|
|
277
|
-
|
|
420
|
+
log "l2_extract" "fail" " ❌ 解压失败"; cleanup_pack; rm -rf "$extract_dir"; return 1
|
|
278
421
|
fi
|
|
279
422
|
cleanup_pack
|
|
280
423
|
local package_dir="$extract_dir/package"
|
|
281
424
|
if [ ! -f "$package_dir/package.json" ]; then
|
|
282
|
-
|
|
425
|
+
log "l2_extract" "fail" " ❌ 解压后未找到 package.json"; rm -rf "$extract_dir"; return 1
|
|
283
426
|
fi
|
|
284
427
|
|
|
285
|
-
|
|
428
|
+
# L1 失败可能留下残缺目录或 stage,L2 安装前再次清理
|
|
429
|
+
echo " [L2 3/4] 清理残留..."
|
|
430
|
+
[ -d "$EXTENSIONS_DIR/$PLUGIN_ID" ] && rm -rf "$EXTENSIONS_DIR/$PLUGIN_ID" 2>/dev/null || true
|
|
431
|
+
find "${EXTENSIONS_DIR:-/dev/null}" "${TMPDIR:-/tmp}" -maxdepth 1 -name ".openclaw-install-stage-*" \
|
|
432
|
+
-exec rm -rf {} + 2>/dev/null || true
|
|
433
|
+
# 从配置中移除插件记录,防止 openclaw CLI 报 "already exists"
|
|
434
|
+
local _l2_cfg="${TEMP_CONFIG_FILE:-$CONFIG_FILE}"
|
|
435
|
+
[ -f "$_l2_cfg" ] && node -e "
|
|
436
|
+
try {
|
|
437
|
+
const fs = require('fs');
|
|
438
|
+
const cfg = JSON.parse(fs.readFileSync('$_l2_cfg', 'utf8'));
|
|
439
|
+
let c = false;
|
|
440
|
+
if (cfg.plugins?.installs?.['$PLUGIN_ID']) { delete cfg.plugins.installs['$PLUGIN_ID']; c = true; }
|
|
441
|
+
if (cfg.plugins?.entries?.['$PLUGIN_ID']) { delete cfg.plugins.entries['$PLUGIN_ID']; c = true; }
|
|
442
|
+
if (Array.isArray(cfg.plugins?.allow)) {
|
|
443
|
+
const i = cfg.plugins.allow.indexOf('$PLUGIN_ID');
|
|
444
|
+
if (i >= 0) { cfg.plugins.allow.splice(i, 1); c = true; }
|
|
445
|
+
}
|
|
446
|
+
if (c) fs.writeFileSync('$_l2_cfg', JSON.stringify(cfg, null, 4) + '\n');
|
|
447
|
+
} catch {}
|
|
448
|
+
" 2>/dev/null || true
|
|
449
|
+
|
|
450
|
+
echo " [L2 4/4] 用 openclaw 安装本地目录..."
|
|
286
451
|
ensure_valid_cwd
|
|
287
452
|
local rc=0
|
|
288
453
|
run_with_timeout "$INSTALL_TIMEOUT" "plugins install (local dir)" \
|
|
@@ -291,10 +456,10 @@ npm_pack_native_install() {
|
|
|
291
456
|
rm -rf "$extract_dir" 2>/dev/null || true
|
|
292
457
|
|
|
293
458
|
if [ $rc -eq 0 ] && [ -f "$EXTENSIONS_DIR/$PLUGIN_ID/package.json" ]; then
|
|
294
|
-
|
|
459
|
+
log "l2_install" "success" " ✅ Level 2 安装成功"
|
|
295
460
|
return 0
|
|
296
461
|
fi
|
|
297
|
-
|
|
462
|
+
log "l2_install" "fail" " Level 2 失败 (exit=$rc)" "exit_code=$rc"
|
|
298
463
|
[ -d "$EXTENSIONS_DIR/$PLUGIN_ID" ] && [ ! -f "$EXTENSIONS_DIR/$PLUGIN_ID/package.json" ] && \
|
|
299
464
|
rm -rf "$EXTENSIONS_DIR/$PLUGIN_ID" 2>/dev/null || true
|
|
300
465
|
find "${EXTENSIONS_DIR:-/dev/null}" "${TMPDIR:-/tmp}" -maxdepth 1 -name ".openclaw-install-stage-*" \
|
|
@@ -321,8 +486,8 @@ cleanup_on_exit() {
|
|
|
321
486
|
|
|
322
487
|
if [ "$INSTALL_COMPLETED" != "true" ] && [ $exit_code -ne 0 ]; then
|
|
323
488
|
local reason="异常退出 (code=$exit_code)"
|
|
324
|
-
case $exit_code in 124) reason="安装超时";; 130) reason="用户中断";; 143) reason="SIGTERM";; 129) reason="SIGHUP";; esac
|
|
325
|
-
|
|
489
|
+
case $exit_code in 124|125) reason="安装超时";; 130) reason="用户中断";; 143) reason="SIGTERM";; 129) reason="SIGHUP";; esac
|
|
490
|
+
log "abnormal_exit" "fail" " ⚠️ ${reason}" "reason=$reason" "exit_code=$exit_code"
|
|
326
491
|
restore_config_snapshot
|
|
327
492
|
rollback_plugin_dir "$reason"
|
|
328
493
|
fi
|
|
@@ -335,17 +500,18 @@ cleanup_on_exit() {
|
|
|
335
500
|
find "${EXTENSIONS_DIR:-/dev/null}" -maxdepth 1 -name ".openclaw-install-stage-*" -exec rm -rf {} + 2>/dev/null || true
|
|
336
501
|
find "${TMPDIR:-/tmp}" -maxdepth 1 \( -name ".openclaw-install-stage-*" -o -name ".qqbot-pack-*" \
|
|
337
502
|
-o -name ".qqbot-extract-*" -o -name ".qqbot-upgrade-backup-*" \) -exec rm -rf {} + 2>/dev/null || true
|
|
503
|
+
find "${OPENCLAW_HOME:-/dev/null}" -maxdepth 1 -name ".qqbot-temp-config-*" -exec rm -f {} + 2>/dev/null || true
|
|
338
504
|
release_upgrade_lock
|
|
339
505
|
exit $exit_code
|
|
340
506
|
}
|
|
341
507
|
trap cleanup_on_exit EXIT
|
|
342
|
-
trap 'exit 143' TERM
|
|
343
508
|
trap 'echo " 中断"; exit 130' INT
|
|
344
509
|
trap 'exit 129' HUP
|
|
345
510
|
|
|
346
511
|
# 清理上次升级遗留(>60min)
|
|
347
512
|
find "${TMPDIR:-/tmp}" -maxdepth 1 \( -name ".qqbot-upgrade-backup-*" -o -name ".qqbot-pack-*" \
|
|
348
513
|
-o -name ".qqbot-extract-*" \) -mmin +60 -exec rm -rf {} + 2>/dev/null || true
|
|
514
|
+
find "${OPENCLAW_HOME:-/dev/null}" -maxdepth 1 -name ".qqbot-temp-config-*" -mmin +60 -exec rm -f {} + 2>/dev/null || true
|
|
349
515
|
|
|
350
516
|
# ============================================================================
|
|
351
517
|
# 参数解析
|
|
@@ -425,7 +591,7 @@ if [ -n "$OPENCLAW_VERSION" ] && version_gte "$OPENCLAW_VERSION" "2026.3.30"; th
|
|
|
425
591
|
FORCE_UNSAFE_FLAG="--dangerously-force-unsafe-install"
|
|
426
592
|
fi
|
|
427
593
|
|
|
428
|
-
|
|
594
|
+
log "upgrade_start" "start" "==========================================="
|
|
429
595
|
echo " qqbot 升级: $INSTALL_SRC"
|
|
430
596
|
echo " openclaw: v${OPENCLAW_VERSION:-unknown}"
|
|
431
597
|
echo " 隔离: ${_UPGRADE_ISOLATED:+✓ setsid}${_UPGRADE_ISOLATED:-✗} 超时: ${INSTALL_TIMEOUT}s"
|
|
@@ -437,20 +603,60 @@ OLD_PKG="$EXTENSIONS_DIR/$PLUGIN_ID/package.json"
|
|
|
437
603
|
[ -f "$OLD_PKG" ] && OLD_VERSION="$(read_pkg_version "$OLD_PKG")"
|
|
438
604
|
[ -n "$OLD_VERSION" ] && echo " 当前版本: $OLD_VERSION"
|
|
439
605
|
|
|
606
|
+
# 初始化日志上报
|
|
607
|
+
init_track_log
|
|
608
|
+
|
|
440
609
|
# ============================================================================
|
|
441
|
-
# 禁用内置冲突插件(配置禁用 +
|
|
610
|
+
# 禁用内置冲突插件(配置禁用 + 验证)
|
|
442
611
|
# ============================================================================
|
|
612
|
+
# 记录已确认存在的内置冲突插件 ID,供 verify_builtin_disabled 复用
|
|
613
|
+
CONFIRMED_BUILTIN_IDS=""
|
|
614
|
+
|
|
443
615
|
disable_builtin_plugins() {
|
|
444
616
|
local found_any=false
|
|
617
|
+
CONFIRMED_BUILTIN_IDS=""
|
|
618
|
+
|
|
619
|
+
# 一次性获取 openclaw 已知的所有插件 ID(stock + global)
|
|
620
|
+
local _known_ids=""
|
|
621
|
+
ensure_valid_cwd
|
|
622
|
+
_known_ids="$(run_with_timeout 15 "plugins list" openclaw plugins list 2>/dev/null \
|
|
623
|
+
| sed -n 's/^│[^│]*│[[:space:]]*\([a-zA-Z0-9_-]*\)[[:space:]]*│.*/\1/p' || true)"
|
|
624
|
+
|
|
445
625
|
for bid in $BUILTIN_CONFLICT_IDS; do
|
|
446
626
|
[ "$bid" = "$PLUGIN_ID" ] && continue
|
|
627
|
+
|
|
628
|
+
# 判断该内置插件是否存在:plugins list 中有 / 配置中有记录 / user extensions 目录有
|
|
629
|
+
local _bid_exists=false
|
|
630
|
+
echo "$_known_ids" | grep -qx "$bid" 2>/dev/null && _bid_exists=true
|
|
631
|
+
[ "$_bid_exists" != "true" ] && [ -d "$EXTENSIONS_DIR/$bid" ] && _bid_exists=true
|
|
632
|
+
[ "$_bid_exists" != "true" ] && node -e "
|
|
633
|
+
try {
|
|
634
|
+
const c = JSON.parse(require('fs').readFileSync('$CONFIG_FILE', 'utf8'));
|
|
635
|
+
if (c.plugins?.entries?.['$bid'] || c.plugins?.installs?.['$bid'] ||
|
|
636
|
+
(Array.isArray(c.plugins?.allow) && c.plugins.allow.includes('$bid')))
|
|
637
|
+
process.stdout.write('1');
|
|
638
|
+
} catch {}
|
|
639
|
+
" 2>/dev/null | grep -q '1' && _bid_exists=true
|
|
640
|
+
|
|
641
|
+
if [ "$_bid_exists" != "true" ]; then
|
|
642
|
+
echo " [禁用内置] $bid: 未检测到,跳过"
|
|
643
|
+
continue
|
|
644
|
+
fi
|
|
645
|
+
|
|
646
|
+
CONFIRMED_BUILTIN_IDS="$CONFIRMED_BUILTIN_IDS $bid"
|
|
647
|
+
|
|
447
648
|
local _changed=""
|
|
448
649
|
_changed="$(node -e "
|
|
449
650
|
try {
|
|
450
651
|
const fs = require('fs');
|
|
451
652
|
const cfg = JSON.parse(fs.readFileSync('$CONFIG_FILE', 'utf8'));
|
|
452
653
|
let c = [];
|
|
453
|
-
|
|
654
|
+
// 显式写入 enabled:false,内置插件即使没有 entries 记录也会自动加载
|
|
655
|
+
(cfg.plugins ??= {}).entries ??= {};
|
|
656
|
+
if (!cfg.plugins.entries['$bid'] || cfg.plugins.entries['$bid'].enabled !== false) {
|
|
657
|
+
cfg.plugins.entries['$bid'] = { ...cfg.plugins.entries['$bid'], enabled: false };
|
|
658
|
+
c.push('entries');
|
|
659
|
+
}
|
|
454
660
|
if (Array.isArray(cfg.plugins?.allow) && cfg.plugins.allow.includes('$bid')) {
|
|
455
661
|
cfg.plugins.allow = cfg.plugins.allow.filter(p => p !== '$bid'); c.push('allow');
|
|
456
662
|
}
|
|
@@ -460,20 +666,36 @@ disable_builtin_plugins() {
|
|
|
460
666
|
} catch {}
|
|
461
667
|
" 2>/dev/null || true)"
|
|
462
668
|
[ -n "$_changed" ] && echo " [禁用内置] $bid: 已修改 $_changed" && found_any=true
|
|
463
|
-
if [ -d "$EXTENSIONS_DIR/$bid" ]; then
|
|
464
|
-
rm -rf "$EXTENSIONS_DIR/$bid"; echo " [禁用内置] 已删除 extensions/$bid"; found_any=true
|
|
465
|
-
fi
|
|
466
669
|
done
|
|
467
|
-
[ "$found_any" = "true" ]
|
|
670
|
+
if [ "$found_any" = "true" ]; then
|
|
671
|
+
log "disable_builtin" "success" " ✅ 内置冲突插件已禁用" "confirmed_ids=$CONFIRMED_BUILTIN_IDS"
|
|
672
|
+
else
|
|
673
|
+
log "disable_builtin" "skip" " ℹ️ 未发现需要禁用的内置冲突插件"
|
|
674
|
+
fi
|
|
468
675
|
}
|
|
469
676
|
|
|
470
677
|
verify_builtin_disabled() {
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
local _e="$(node -e "
|
|
678
|
+
[ -z "$CONFIRMED_BUILTIN_IDS" ] && return 0
|
|
679
|
+
for bid in $CONFIRMED_BUILTIN_IDS; do
|
|
680
|
+
local _e="$(node -e "
|
|
681
|
+
try {
|
|
682
|
+
const c = JSON.parse(require('fs').readFileSync('$CONFIG_FILE', 'utf8'));
|
|
683
|
+
const e = c.plugins?.entries?.['$bid'];
|
|
684
|
+
// 不存在或 enabled 不为 false 都需要修复
|
|
685
|
+
if (!e || e.enabled !== false) process.stdout.write('1');
|
|
686
|
+
} catch {}
|
|
687
|
+
" 2>/dev/null || true)"
|
|
474
688
|
if [ "$_e" = "1" ]; then
|
|
475
|
-
|
|
476
|
-
node -e "
|
|
689
|
+
log "verify_builtin" "fix" " ⚠️ 内置插件 $bid 未禁用,写入 entries..." "bid=$bid"
|
|
690
|
+
node -e "
|
|
691
|
+
try {
|
|
692
|
+
const f = require('fs');
|
|
693
|
+
const c = JSON.parse(f.readFileSync('$CONFIG_FILE', 'utf8'));
|
|
694
|
+
(c.plugins ??= {}).entries ??= {};
|
|
695
|
+
c.plugins.entries['$bid'] = { ...c.plugins.entries['$bid'], enabled: false };
|
|
696
|
+
f.writeFileSync('$CONFIG_FILE', JSON.stringify(c, null, 4) + '\n');
|
|
697
|
+
} catch {}
|
|
698
|
+
" 2>/dev/null || true
|
|
477
699
|
fi
|
|
478
700
|
done
|
|
479
701
|
}
|
|
@@ -502,13 +724,16 @@ setup_temp_config
|
|
|
502
724
|
|
|
503
725
|
# 清理历史遗留 ID 的配置记录(qqbot/openclaw-qq 是旧版本使用的 ID,
|
|
504
726
|
# entries 中残留会导致 gateway 重复加载同一插件报 tool name conflict)
|
|
727
|
+
# 注意:跳过 enabled===false 的 entries,那是 disable_builtin_plugins 写入的禁用记录
|
|
505
728
|
[ -f "$CONFIG_FILE" ] && node -e "
|
|
506
729
|
try {
|
|
507
730
|
const fs = require('fs');
|
|
508
731
|
const cfg = JSON.parse(fs.readFileSync('$CONFIG_FILE', 'utf8'));
|
|
509
732
|
let c = false;
|
|
510
733
|
for (const old of ['qqbot', 'openclaw-qq']) {
|
|
511
|
-
if (cfg.plugins?.entries?.[old]
|
|
734
|
+
if (cfg.plugins?.entries?.[old] && cfg.plugins.entries[old].enabled !== false) {
|
|
735
|
+
delete cfg.plugins.entries[old]; c = true;
|
|
736
|
+
}
|
|
512
737
|
if (cfg.plugins?.installs?.[old]) { delete cfg.plugins.installs[old]; c = true; }
|
|
513
738
|
if (Array.isArray(cfg.plugins?.allow)) {
|
|
514
739
|
const i = cfg.plugins.allow.indexOf(old);
|
|
@@ -539,10 +764,10 @@ HAS_PLUGIN_DIR=false
|
|
|
539
764
|
USE_UPDATE=false
|
|
540
765
|
if [ "$HAS_INSTALL_RECORD" = "yes" ] && [ "$HAS_PLUGIN_DIR" = "true" ] && [ -z "$TARGET_VERSION" ]; then
|
|
541
766
|
if [ -n "$FORCE_UNSAFE_FLAG" ]; then
|
|
542
|
-
|
|
767
|
+
log "install_decision" "info" " [检测] 配置 ✓ | 目录 ✓ | openclaw ≥3.30 → 跳过 update,直接 install(安全扫描兼容)" "decision=install" "reason=force_unsafe"
|
|
543
768
|
else
|
|
544
769
|
USE_UPDATE=true
|
|
545
|
-
|
|
770
|
+
log "install_decision" "info" " [检测] 配置 ✓ | 目录 ✓ | 未指定版本 → update" "decision=update"
|
|
546
771
|
# spec 解锁
|
|
547
772
|
if [ -n "$INSTALL_SPEC" ]; then
|
|
548
773
|
SPEC_SUFFIX="${INSTALL_SPEC##*@}"
|
|
@@ -562,9 +787,9 @@ if [ "$HAS_INSTALL_RECORD" = "yes" ] && [ "$HAS_PLUGIN_DIR" = "true" ] && [ -z "
|
|
|
562
787
|
fi
|
|
563
788
|
fi
|
|
564
789
|
elif [ "$HAS_PLUGIN_DIR" = "true" ]; then
|
|
565
|
-
|
|
790
|
+
log "install_decision" "info" " [检测] 目录 ✓ | 指定版本或无配置记录 → reinstall" "decision=reinstall"
|
|
566
791
|
else
|
|
567
|
-
|
|
792
|
+
log "install_decision" "info" " [检测] 目录 ✗ → 全新安装" "decision=fresh_install"
|
|
568
793
|
fi
|
|
569
794
|
|
|
570
795
|
mark_success() {
|
|
@@ -585,29 +810,39 @@ if [ "$USE_UPDATE" = "true" ]; then
|
|
|
585
810
|
if [ $UPDATE_RC -eq 0 ]; then
|
|
586
811
|
POST_VER=""; [ -f "$OLD_PKG" ] && POST_VER="$(read_pkg_version "$OLD_PKG")"
|
|
587
812
|
if [ -n "$POST_VER" ] && [ "$POST_VER" != "$OLD_VERSION" ]; then
|
|
588
|
-
mark_success;
|
|
813
|
+
mark_success; log "level1_update" "success" " ✅ update 成功 ($OLD_VERSION → $POST_VER)" "method=update" "post_version=$POST_VER"
|
|
589
814
|
elif [ -z "$OLD_VERSION" ]; then
|
|
590
|
-
mark_success;
|
|
815
|
+
mark_success; log "level1_update" "success" " ✅ update 成功" "method=update"
|
|
591
816
|
else
|
|
592
817
|
echo " ℹ️ 版本未变 ($POST_VER),查询 npm latest..."
|
|
593
818
|
NPM_LATEST="$(npm view "$PKG_NAME" version 2>/dev/null || true)"
|
|
594
819
|
if [ -n "$NPM_LATEST" ] && [ "$NPM_LATEST" = "$POST_VER" ]; then
|
|
595
|
-
mark_success;
|
|
820
|
+
mark_success; log "level1_update" "success" " ✅ 已是最新版本 $POST_VER" "method=update" "version=$POST_VER"
|
|
596
821
|
else
|
|
597
|
-
|
|
822
|
+
log "level1_update" "fail" " npm latest=${NPM_LATEST:-unknown},当前=$POST_VER" "npm_latest=$NPM_LATEST" "current=$POST_VER"
|
|
598
823
|
fi
|
|
599
824
|
fi
|
|
600
825
|
else
|
|
601
|
-
[ $UPDATE_RC -eq 124 ]
|
|
826
|
+
if [ $UPDATE_RC -eq 124 ]; then
|
|
827
|
+
log "level1_update" "fail" " ⏰ update 超时" "method=update" "exit_code=$UPDATE_RC"
|
|
828
|
+
else
|
|
829
|
+
log "level1_update" "fail" " update 失败 (exit=$UPDATE_RC)" "method=update" "exit_code=$UPDATE_RC"
|
|
830
|
+
fi
|
|
602
831
|
fi
|
|
603
832
|
|
|
604
833
|
# Level 1 失败 → Level 2 降级
|
|
605
834
|
if [ "$UPGRADE_OK" != "true" ]; then
|
|
835
|
+
log "level2_fallback" "start" " 尝试 Level 2 降级..." "reason=level1_failed"
|
|
606
836
|
if [ -z "$BACKUP_DIR" ] && [ -d "$EXTENSIONS_DIR/$PLUGIN_ID" ]; then
|
|
607
837
|
BACKUP_DIR="$(mktemp -d "${TMPDIR:-/tmp}/.qqbot-upgrade-backup-XXXXXX")"
|
|
608
838
|
cp -a "$EXTENSIONS_DIR/$PLUGIN_ID" "$BACKUP_DIR/$PLUGIN_ID"
|
|
609
839
|
fi
|
|
610
|
-
run_fallback
|
|
840
|
+
if run_fallback; then
|
|
841
|
+
mark_success
|
|
842
|
+
log "level2_fallback" "success" " ✅ Level 2 降级成功"
|
|
843
|
+
else
|
|
844
|
+
log "level2_fallback" "fail" " ❌ Level 2 降级失败"
|
|
845
|
+
fi
|
|
611
846
|
fi
|
|
612
847
|
fi
|
|
613
848
|
|
|
@@ -644,7 +879,7 @@ if [ "$UPGRADE_OK" != "true" ]; then
|
|
|
644
879
|
" 2>/dev/null || true
|
|
645
880
|
|
|
646
881
|
# Level 1: 原生 install(单次尝试,失败后由 Level 2 的 npm pack 多源重试接管)
|
|
647
|
-
|
|
882
|
+
log "level1_install" "start" " [Level 1] 尝试 openclaw plugins install..." "method=install"
|
|
648
883
|
ensure_valid_cwd
|
|
649
884
|
RC=0
|
|
650
885
|
run_with_timeout "$INSTALL_TIMEOUT" \
|
|
@@ -652,24 +887,29 @@ if [ "$UPGRADE_OK" != "true" ]; then
|
|
|
652
887
|
$FORCE_UNSAFE_FLAG 2>&1 || RC=$?
|
|
653
888
|
|
|
654
889
|
if [ $RC -eq 0 ] && [ -f "$EXTENSIONS_DIR/$PLUGIN_ID/package.json" ]; then
|
|
655
|
-
mark_success;
|
|
890
|
+
mark_success; log "level1_install" "success" " ✅ Level 1 install 成功" "method=install"
|
|
656
891
|
else
|
|
657
|
-
|
|
892
|
+
log "level1_install" "fail" " Level 1 install 失败 (exit=$RC)" "method=install" "exit_code=$RC"
|
|
658
893
|
# 清理不完整的目录和 stage
|
|
659
894
|
[ -d "$EXTENSIONS_DIR/$PLUGIN_ID" ] && [ ! -f "$EXTENSIONS_DIR/$PLUGIN_ID/package.json" ] && \
|
|
660
895
|
rm -rf "$EXTENSIONS_DIR/$PLUGIN_ID" 2>/dev/null || true
|
|
661
896
|
find "${EXTENSIONS_DIR:-/dev/null}" "${TMPDIR:-/tmp}" -maxdepth 1 -name ".openclaw-install-stage-*" \
|
|
662
897
|
-exec rm -rf {} + 2>/dev/null || true
|
|
663
898
|
|
|
664
|
-
|
|
665
|
-
run_fallback
|
|
899
|
+
log "level2_fallback" "start" " Level 1 失败,尝试 Level 2 降级..." "reason=level1_install_failed"
|
|
900
|
+
if run_fallback; then
|
|
901
|
+
mark_success
|
|
902
|
+
log "level2_fallback" "success" " ✅ Level 2 降级成功"
|
|
903
|
+
else
|
|
904
|
+
log "level2_fallback" "fail" " ❌ Level 2 降级失败"
|
|
905
|
+
log "upgrade_complete" "fail" " 升级完全失败,已回滚"
|
|
666
906
|
rollback_plugin_dir "安装失败"; restore_config_snapshot
|
|
667
907
|
[ -n "$TEMP_CONFIG_FILE" ] && rm -f "$TEMP_CONFIG_FILE" 2>/dev/null || true
|
|
668
908
|
unset OPENCLAW_CONFIG_PATH 2>/dev/null || true
|
|
669
909
|
echo "QQBOT_NEW_VERSION=unknown"
|
|
670
910
|
echo "QQBOT_REPORT=❌ QQBot 安装失败(已回滚),请检查网络"
|
|
671
911
|
exit 1
|
|
672
|
-
|
|
912
|
+
fi
|
|
673
913
|
fi
|
|
674
914
|
fi
|
|
675
915
|
|
|
@@ -713,11 +953,12 @@ if [ -d "$TARGET_DIR/node_modules" ]; then
|
|
|
713
953
|
fi
|
|
714
954
|
|
|
715
955
|
if [ "$PREFLIGHT_OK" != "true" ]; then
|
|
716
|
-
echo ""
|
|
956
|
+
echo ""
|
|
957
|
+
log "validation" "fail" "❌ 验证未通过" "missing=$MISS"
|
|
717
958
|
echo "QQBOT_NEW_VERSION=unknown"; echo "QQBOT_REPORT=⚠️ 验证未通过"
|
|
718
959
|
exit 1
|
|
719
960
|
fi
|
|
720
|
-
|
|
961
|
+
log "validation" "success" " ✅ 验证全部通过"
|
|
721
962
|
|
|
722
963
|
# 轻量健康检查
|
|
723
964
|
echo ""
|
|
@@ -749,7 +990,7 @@ echo "QQBOT_NEW_VERSION=${NEW_VERSION:-unknown}"
|
|
|
749
990
|
echo "QQBOT_REPORT=⚠️ 无法确认新版本"
|
|
750
991
|
|
|
751
992
|
echo ""
|
|
752
|
-
|
|
993
|
+
log "upgrade_complete" "success" "===========================================" "new_version=${NEW_VERSION:-unknown}"
|
|
753
994
|
echo " ✅ 安装完成"
|
|
754
995
|
echo "==========================================="
|
|
755
996
|
|
|
@@ -805,16 +1046,71 @@ fi
|
|
|
805
1046
|
|
|
806
1047
|
echo "[4/4] 重启 gateway..."
|
|
807
1048
|
ensure_valid_cwd
|
|
808
|
-
GW_RC=0; run_with_timeout 90 "gateway restart" openclaw gateway restart 2>&1 || GW_RC=$?
|
|
1049
|
+
GW_RC=0; GW_OUTPUT="$(run_with_timeout 90 "gateway restart" openclaw gateway restart 2>&1)" || GW_RC=$?
|
|
1050
|
+
echo "$GW_OUTPUT"
|
|
809
1051
|
|
|
810
1052
|
if [ $GW_RC -eq 0 ]; then
|
|
811
|
-
|
|
1053
|
+
log "gateway_restart" "success" " ✅ gateway 已重启"
|
|
812
1054
|
[ -n "$NEW_VERSION" ] && echo "" && echo "🎉 QQBot 插件已更新至 v${NEW_VERSION},在线等候你的吩咐。"
|
|
813
1055
|
else
|
|
814
|
-
[ $GW_RC -eq 124 ]
|
|
815
|
-
|
|
816
|
-
|
|
1056
|
+
if [ $GW_RC -eq 124 ]; then
|
|
1057
|
+
log "gateway_restart" "fail" " ⏰ gateway restart 超时" "exit_code=$GW_RC"
|
|
1058
|
+
else
|
|
1059
|
+
log "gateway_restart" "fail" " ⚠️ 重启失败" "exit_code=$GW_RC"
|
|
1060
|
+
fi
|
|
1061
|
+
|
|
1062
|
+
# 检测是否是 qqbot 通道配置的 additional properties 校验错误
|
|
1063
|
+
if echo "$GW_OUTPUT" | grep -q "additional properties"; then
|
|
1064
|
+
echo ""
|
|
1065
|
+
log "config_fix" "start" " [配置修复] 检测到 QQBot 通道配置包含不支持的字段"
|
|
1066
|
+
|
|
1067
|
+
# 备份当前配置
|
|
1068
|
+
_cfg_backup="${CONFIG_FILE}.pre-fix.$(date +%s)"
|
|
1069
|
+
cp -a "$CONFIG_FILE" "$_cfg_backup" 2>/dev/null && \
|
|
1070
|
+
echo " [配置修复] 已备份当前配置到: $_cfg_backup"
|
|
817
1071
|
|
|
1072
|
+
# 只保留合法字段: enabled/appId/clientSecret/allowFrom/accounts
|
|
1073
|
+
# accounts 内每个条目也只保留: enabled/appId/clientSecret/allowFrom
|
|
1074
|
+
node -e "
|
|
1075
|
+
try {
|
|
1076
|
+
const fs = require('fs');
|
|
1077
|
+
const cfg = JSON.parse(fs.readFileSync('$CONFIG_FILE', 'utf8'));
|
|
1078
|
+
const ch = cfg.channels?.qqbot;
|
|
1079
|
+
if (!ch) process.exit(0);
|
|
1080
|
+
|
|
1081
|
+
const ALLOWED_ROOT = new Set(['enabled', 'appId', 'clientSecret', 'allowFrom', 'accounts']);
|
|
1082
|
+
const ALLOWED_ACCOUNT = new Set(['enabled', 'appId', 'clientSecret', 'allowFrom']);
|
|
1083
|
+
|
|
1084
|
+
const cleaned = {};
|
|
1085
|
+
for (const k of Object.keys(ch)) {
|
|
1086
|
+
if (!ALLOWED_ROOT.has(k)) continue;
|
|
1087
|
+
if (k === 'accounts' && typeof ch.accounts === 'object' && ch.accounts !== null) {
|
|
1088
|
+
cleaned.accounts = {};
|
|
1089
|
+
for (const [accId, acc] of Object.entries(ch.accounts)) {
|
|
1090
|
+
if (typeof acc !== 'object' || acc === null) continue;
|
|
1091
|
+
const cleanedAcc = {};
|
|
1092
|
+
for (const ak of Object.keys(acc)) {
|
|
1093
|
+
if (ALLOWED_ACCOUNT.has(ak)) cleanedAcc[ak] = acc[ak];
|
|
1094
|
+
}
|
|
1095
|
+
if (Object.keys(cleanedAcc).length > 0) cleaned.accounts[accId] = cleanedAcc;
|
|
1096
|
+
}
|
|
1097
|
+
if (Object.keys(cleaned.accounts).length === 0) delete cleaned.accounts;
|
|
1098
|
+
} else {
|
|
1099
|
+
cleaned[k] = ch[k];
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
cfg.channels.qqbot = cleaned;
|
|
1104
|
+
fs.writeFileSync('$CONFIG_FILE', JSON.stringify(cfg, null, 4) + '\n');
|
|
1105
|
+
process.stdout.write('fixed');
|
|
1106
|
+
} catch (e) { process.stderr.write(String(e)); }
|
|
1107
|
+
" 2>/dev/null && log "config_fix" "success" " [配置修复] ✅ 已清理 channels.qqbot 中不支持的字段" || \
|
|
1108
|
+
log "config_fix" "fail" " [配置修复] ⚠️ 自动修复失败,请手动编辑 $CONFIG_FILE"
|
|
1109
|
+
|
|
1110
|
+
echo " [配置修复] 如需恢复原配置: cp $_cfg_backup $CONFIG_FILE"
|
|
1111
|
+
fi
|
|
1112
|
+
|
|
1113
|
+
ensure_valid_cwd
|
|
818
1114
|
_bak=""; [ -f "$CONFIG_FILE" ] && _bak="$(mktemp "${TMPDIR:-/tmp}/.qqbot-pre-doctor-XXXXXX")" && cp -a "$CONFIG_FILE" "$_bak"
|
|
819
1115
|
run_with_timeout 30 "doctor --fix" openclaw doctor --fix 2>&1 | head -20 | sed 's/^/ /' || true
|
|
820
1116
|
|
|
@@ -828,7 +1124,7 @@ else
|
|
|
828
1124
|
else if (b.plugins?.entries?.['$PLUGIN_ID'] && !a.plugins?.entries?.['$PLUGIN_ID']) process.stdout.write('entries');
|
|
829
1125
|
} catch {}
|
|
830
1126
|
" 2>/dev/null || true)
|
|
831
|
-
[ -n "$_damaged" ] &&
|
|
1127
|
+
[ -n "$_damaged" ] && log "doctor_fix" "warn" " ⚠️ doctor 误删 $_damaged,恢复中..." "damaged=$_damaged" && cp -a "$_bak" "$CONFIG_FILE" && echo " ✅ 已恢复"
|
|
832
1128
|
rm -f "$_bak" 2>/dev/null || true
|
|
833
1129
|
fi
|
|
834
1130
|
|
|
@@ -836,10 +1132,10 @@ else
|
|
|
836
1132
|
ensure_valid_cwd
|
|
837
1133
|
RR=0; run_with_timeout 90 "gateway restart (重试)" openclaw gateway restart 2>&1 || RR=$?
|
|
838
1134
|
if [ $RR -eq 0 ]; then
|
|
839
|
-
|
|
1135
|
+
log "gateway_retry" "success" " ✅ 重启成功"
|
|
840
1136
|
[ -n "$NEW_VERSION" ] && echo "" && echo "🎉 QQBot 插件已更新至 v${NEW_VERSION},在线等候你的吩咐。"
|
|
841
1137
|
else
|
|
842
|
-
|
|
1138
|
+
log "gateway_retry" "fail" " ❌ 仍无法重启,请手动排查:" "exit_code=$RR"
|
|
843
1139
|
echo " openclaw doctor && openclaw gateway restart"
|
|
844
1140
|
fi
|
|
845
1141
|
fi
|