@tencent-connect/openclaw-qqbot 1.5.7 → 1.6.0-alpha.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 +9 -2
- package/README.zh.md +7 -2
- package/package.json +1 -1
- package/scripts/upgrade-via-npm.sh +85 -115
- package/scripts/upgrade-via-source.sh +203 -35
- package/skills/qqbot-cron/SKILL.md +46 -423
- package/skills/qqbot-media/SKILL.md +29 -182
- package/src/api.ts +16 -5
- package/src/channel.ts +6 -7
- package/src/gateway.ts +510 -525
- package/src/image-server.ts +72 -10
- package/src/openclaw-plugin-sdk.d.ts +1 -1
- package/src/outbound.ts +571 -611
- package/src/ref-index-store.ts +1 -1
- package/src/slash-commands.ts +425 -0
- package/src/types.ts +18 -1
- package/src/update-checker.ts +102 -0
- package/src/user-messages.ts +73 -0
- package/src/utils/audio-convert.ts +69 -4
- package/src/utils/media-tags.ts +46 -4
- package/dist/AI/345/210/233/346/226/260/345/272/224/347/224/250/345/245/226_/347/224/263/346/212/245/344/271/246.md +0 -211
- package/dist/index.d.ts +0 -17
- package/dist/index.js +0 -22
- package/dist/src/api.d.ts +0 -138
- package/dist/src/api.js +0 -525
- package/dist/src/channel.d.ts +0 -3
- package/dist/src/channel.js +0 -337
- package/dist/src/config.d.ts +0 -25
- package/dist/src/config.js +0 -161
- package/dist/src/gateway.d.ts +0 -18
- package/dist/src/gateway.js +0 -2468
- package/dist/src/image-server.d.ts +0 -62
- package/dist/src/image-server.js +0 -401
- package/dist/src/known-users.d.ts +0 -100
- package/dist/src/known-users.js +0 -263
- package/dist/src/onboarding.d.ts +0 -10
- package/dist/src/onboarding.js +0 -203
- package/dist/src/outbound.d.ts +0 -150
- package/dist/src/outbound.js +0 -1175
- package/dist/src/proactive.d.ts +0 -170
- package/dist/src/proactive.js +0 -399
- package/dist/src/runtime.d.ts +0 -3
- package/dist/src/runtime.js +0 -10
- package/dist/src/session-store.d.ts +0 -52
- package/dist/src/session-store.js +0 -254
- package/dist/src/slash-commands.d.ts +0 -48
- package/dist/src/slash-commands.js +0 -212
- package/dist/src/types.d.ts +0 -146
- package/dist/src/types.js +0 -1
- package/dist/src/utils/audio-convert.d.ts +0 -73
- package/dist/src/utils/audio-convert.js +0 -645
- package/dist/src/utils/file-utils.d.ts +0 -46
- package/dist/src/utils/file-utils.js +0 -107
- package/dist/src/utils/image-size.d.ts +0 -51
- package/dist/src/utils/image-size.js +0 -234
- package/dist/src/utils/media-tags.d.ts +0 -14
- package/dist/src/utils/media-tags.js +0 -120
- package/dist/src/utils/payload.d.ts +0 -112
- package/dist/src/utils/payload.js +0 -186
- package/dist/src/utils/platform.d.ts +0 -126
- package/dist/src/utils/platform.js +0 -358
- package/dist/src/utils/upload-cache.d.ts +0 -34
- package/dist/src/utils/upload-cache.js +0 -93
package/README.md
CHANGED
|
@@ -6,14 +6,19 @@
|
|
|
6
6
|
|
|
7
7
|
# QQ Bot Channel Plugin for OpenClaw
|
|
8
8
|
|
|
9
|
+
|
|
10
|
+
|
|
9
11
|
**Connect your AI assistant to QQ — private chat, group chat, and rich media, all in one plugin.**
|
|
10
12
|
|
|
13
|
+
### 🚀 Current Version: `v1.5.7`
|
|
14
|
+
|
|
11
15
|
[](./LICENSE)
|
|
12
16
|
[](https://bot.q.qq.com/wiki/)
|
|
13
17
|
[](https://github.com/tencent-connect/openclaw-qqbot)
|
|
14
18
|
[](https://nodejs.org/)
|
|
15
19
|
[](https://www.typescriptlang.org/)
|
|
16
20
|
|
|
21
|
+
|
|
17
22
|
<br/>
|
|
18
23
|
|
|
19
24
|
**[简体中文](README.zh.md) | English**
|
|
@@ -56,6 +61,8 @@ QQ quote events carry index keys (e.g. `REFIDX_xxx`) instead of full original me
|
|
|
56
61
|
- Store path: `~/.openclaw/qqbot/data/ref-index.jsonl` (survives gateway restart).
|
|
57
62
|
- Quote body may include text + media summary (image/voice/video/file).
|
|
58
63
|
|
|
64
|
+
<img width="360" src="docs/images/ref_msg.png" alt="Quoted Message Context Demo" />
|
|
65
|
+
|
|
59
66
|
### 🎙️ Voice Messages (STT)
|
|
60
67
|
|
|
61
68
|
With STT configured, the plugin automatically transcribes voice messages to text before passing them to AI. The whole process is transparent to the user — sending voice feels as natural as sending text.
|
|
@@ -86,7 +93,7 @@ If your main model supports vision (e.g. Tencent Hunyuan `hunyuan-vision`), AI c
|
|
|
86
93
|
|
|
87
94
|
<img width="360" src="docs/images/59d421891f813b0d3c0cbe12574b6a72_720.jpg" alt="Image Understanding Demo" />
|
|
88
95
|
|
|
89
|
-
### 🎨 Image
|
|
96
|
+
### 🎨 Image Sending
|
|
90
97
|
|
|
91
98
|
> **You**: Draw me a cat
|
|
92
99
|
>
|
|
@@ -96,7 +103,7 @@ AI sends images via `<qqimg>path</qqimg>`. Supports local paths and URLs. Format
|
|
|
96
103
|
|
|
97
104
|
<img width="360" src="docs/images/4645f2b3a20822b7f8d6664a708529eb_720.jpg" alt="Image Generation Demo" />
|
|
98
105
|
|
|
99
|
-
### 🔊 Voice
|
|
106
|
+
### 🔊 Voice Sending
|
|
100
107
|
|
|
101
108
|
> **You**: Tell me a joke in voice
|
|
102
109
|
>
|
package/README.zh.md
CHANGED
|
@@ -6,8 +6,11 @@
|
|
|
6
6
|
|
|
7
7
|
# QQ Bot — OpenClaw 渠道插件
|
|
8
8
|
|
|
9
|
+
|
|
9
10
|
**让你的 AI 助手接入 QQ — 私聊、群聊、富媒体,一个插件全搞定。**
|
|
10
11
|
|
|
12
|
+
### 🚀 当前版本: `v1.5.7`
|
|
13
|
+
|
|
11
14
|
[](./LICENSE)
|
|
12
15
|
[](https://bot.q.qq.com/wiki/)
|
|
13
16
|
[](https://github.com/tencent-connect/openclaw-qqbot)
|
|
@@ -53,6 +56,8 @@ QQ 的引用事件通常只携带索引键(如 `REFIDX_xxx`),不直接返
|
|
|
53
56
|
- 存储位置:`~/.openclaw/qqbot/data/ref-index.jsonl`(网关重启后仍可恢复)。
|
|
54
57
|
- 引用内容支持文本 + 媒体摘要(图片/语音/视频/文件)。
|
|
55
58
|
|
|
59
|
+
<img width="360" src="docs/images/ref_msg.png" alt="引用消息上下文演示" />
|
|
60
|
+
|
|
56
61
|
### 🎙️ 语音消息(STT)
|
|
57
62
|
|
|
58
63
|
配置 STT 后,插件会自动将语音转录为文字再交给 AI 处理。整个过程对用户完全透明——发语音就像发文字一样自然,AI 听得懂你在说什么。
|
|
@@ -83,7 +88,7 @@ QQ 的引用事件通常只携带索引键(如 `REFIDX_xxx`),不直接返
|
|
|
83
88
|
|
|
84
89
|
<img width="360" src="docs/images/59d421891f813b0d3c0cbe12574b6a72_720.jpg" alt="图片理解演示" />
|
|
85
90
|
|
|
86
|
-
### 🎨
|
|
91
|
+
### 🎨 图片发送
|
|
87
92
|
|
|
88
93
|
> **你**:画一只猫咪
|
|
89
94
|
>
|
|
@@ -93,7 +98,7 @@ AI 通过 `<qqimg>路径</qqimg>` 发送图片,支持本地文件路径和网
|
|
|
93
98
|
|
|
94
99
|
<img width="360" src="docs/images/4645f2b3a20822b7f8d6664a708529eb_720.jpg" alt="发图片演示" />
|
|
95
100
|
|
|
96
|
-
### 🔊
|
|
101
|
+
### 🔊 语音发送
|
|
97
102
|
|
|
98
103
|
> **你**:给我讲一个笑话
|
|
99
104
|
>
|
package/package.json
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
|
|
3
|
-
# qqbot 通过 npm
|
|
3
|
+
# qqbot 通过 npm 包升级(纯文件操作版本)
|
|
4
|
+
#
|
|
5
|
+
# 重要:此脚本不修改 openclaw.json 配置文件!
|
|
6
|
+
# 配置更新由调用方(TS handler)在脚本完成后统一处理,
|
|
7
|
+
# 避免 gateway config watcher 在安装过程中触发 SIGUSR1 重启导致竞态。
|
|
4
8
|
#
|
|
5
9
|
# 用法:
|
|
6
10
|
# upgrade-via-npm.sh # 升级到 latest(默认)
|
|
@@ -11,8 +15,6 @@ set -eo pipefail
|
|
|
11
15
|
|
|
12
16
|
PKG_NAME="@tencent-connect/openclaw-qqbot"
|
|
13
17
|
INSTALL_SRC=""
|
|
14
|
-
APPID=""
|
|
15
|
-
SECRET=""
|
|
16
18
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
17
19
|
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
18
20
|
|
|
@@ -30,16 +32,11 @@ print_usage() {
|
|
|
30
32
|
echo "用法:"
|
|
31
33
|
echo " upgrade-via-npm.sh # 升级到 latest(默认)"
|
|
32
34
|
echo " upgrade-via-npm.sh --version <版本号> # 升级到指定版本"
|
|
33
|
-
echo " upgrade-via-npm.sh --appid <appid> --secret <secret> # 配置通道并启动"
|
|
34
35
|
if [ -n "$LOCAL_VERSION" ]; then
|
|
35
36
|
echo " upgrade-via-npm.sh --self-version # 升级到当前仓库版本($LOCAL_VERSION)"
|
|
36
37
|
else
|
|
37
38
|
echo " upgrade-via-npm.sh --self-version # 升级到当前仓库版本"
|
|
38
39
|
fi
|
|
39
|
-
echo ""
|
|
40
|
-
echo "也可以通过环境变量设置:"
|
|
41
|
-
echo " QQBOT_APPID QQ机器人 appid"
|
|
42
|
-
echo " QQBOT_SECRET QQ机器人 secret"
|
|
43
40
|
}
|
|
44
41
|
|
|
45
42
|
while [[ $# -gt 0 ]]; do
|
|
@@ -59,16 +56,6 @@ while [[ $# -gt 0 ]]; do
|
|
|
59
56
|
INSTALL_SRC="${PKG_NAME}@${LOCAL_VERSION}"
|
|
60
57
|
shift 1
|
|
61
58
|
;;
|
|
62
|
-
--appid)
|
|
63
|
-
[ -z "$2" ] && echo "❌ --appid 需要参数" && exit 1
|
|
64
|
-
APPID="$2"
|
|
65
|
-
shift 2
|
|
66
|
-
;;
|
|
67
|
-
--secret)
|
|
68
|
-
[ -z "$2" ] && echo "❌ --secret 需要参数" && exit 1
|
|
69
|
-
SECRET="$2"
|
|
70
|
-
shift 2
|
|
71
|
-
;;
|
|
72
59
|
-h|--help)
|
|
73
60
|
print_usage
|
|
74
61
|
exit 0
|
|
@@ -78,18 +65,13 @@ while [[ $# -gt 0 ]]; do
|
|
|
78
65
|
done
|
|
79
66
|
INSTALL_SRC="${INSTALL_SRC:-${PKG_NAME}@latest}"
|
|
80
67
|
|
|
81
|
-
#
|
|
82
|
-
APPID="${APPID:-$QQBOT_APPID}"
|
|
83
|
-
SECRET="${SECRET:-$QQBOT_SECRET}"
|
|
84
|
-
|
|
85
|
-
# 检测 CLI
|
|
68
|
+
# 检测 CLI(仅用于确定 extensions 目录路径)
|
|
86
69
|
CMD=""
|
|
87
70
|
for name in openclaw clawdbot moltbot; do
|
|
88
71
|
command -v "$name" &>/dev/null && CMD="$name" && break
|
|
89
72
|
done
|
|
90
73
|
[ -z "$CMD" ] && echo "❌ 未找到 openclaw / clawdbot / moltbot" && exit 1
|
|
91
74
|
|
|
92
|
-
APP_CONFIG="$HOME/.$CMD/$CMD.json"
|
|
93
75
|
EXTENSIONS_DIR="$HOME/.$CMD/extensions"
|
|
94
76
|
|
|
95
77
|
echo "==========================================="
|
|
@@ -97,108 +79,96 @@ echo " qqbot npm 升级: $INSTALL_SRC"
|
|
|
97
79
|
echo "==========================================="
|
|
98
80
|
echo ""
|
|
99
81
|
|
|
100
|
-
# [1/
|
|
101
|
-
echo "[1/
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
82
|
+
# [1/3] 下载并安装新版本到临时目录
|
|
83
|
+
echo "[1/3] 下载新版本..."
|
|
84
|
+
TMPDIR_PACK=$(mktemp -d)
|
|
85
|
+
EXTRACT_DIR=$(mktemp -d)
|
|
86
|
+
trap "rm -rf '$TMPDIR_PACK' '$EXTRACT_DIR'" EXIT
|
|
87
|
+
|
|
88
|
+
cd "$TMPDIR_PACK"
|
|
89
|
+
npm pack "$INSTALL_SRC" --quiet 2>&1 || { echo "❌ npm pack 失败"; exit 1; }
|
|
90
|
+
TGZ_FILE=$(ls -1 *.tgz 2>/dev/null | head -1)
|
|
91
|
+
[ -z "$TGZ_FILE" ] && echo "❌ 未找到下载的 tgz 文件" && exit 1
|
|
92
|
+
echo " 已下载: $TGZ_FILE"
|
|
93
|
+
|
|
94
|
+
tar xzf "$TGZ_FILE" -C "$EXTRACT_DIR"
|
|
95
|
+
PACKAGE_DIR="$EXTRACT_DIR/package"
|
|
96
|
+
[ ! -d "$PACKAGE_DIR" ] && echo "❌ 解压失败,未找到 package 目录" && exit 1
|
|
97
|
+
|
|
98
|
+
# 准备 staging 目录:放在 ~/.openclaw/ 下(extensions 的父目录),
|
|
99
|
+
# 同一文件系统保证 mv 原子操作,同时避免 OpenClaw 扫描 extensions/ 时发现它。
|
|
100
|
+
STAGING_DIR="$(dirname "$EXTENSIONS_DIR")/.qqbot-upgrade-staging"
|
|
101
|
+
rm -rf "$STAGING_DIR"
|
|
102
|
+
mkdir -p "$STAGING_DIR"
|
|
103
|
+
cp -R "$PACKAGE_DIR/"* "$STAGING_DIR/"
|
|
104
|
+
|
|
105
|
+
# 依赖处理:所有 production dependencies 都声明为 bundledDependencies,
|
|
106
|
+
# npm pack 时已打包进 tgz,解压后 node_modules/ 已包含全部依赖,无需 npm install。
|
|
107
|
+
# 注意:不能执行 npm install,否则会安装 peerDependencies(openclaw 平台及其 400+ 传递依赖),
|
|
108
|
+
# 导致插件目录膨胀到 900MB+,而这些依赖在运行时由宿主 openclaw 提供。
|
|
109
|
+
if [ -d "$STAGING_DIR/node_modules" ]; then
|
|
110
|
+
BUNDLED_COUNT=$(ls -d "$STAGING_DIR/node_modules"/*/ "$STAGING_DIR/node_modules"/@*/*/ 2>/dev/null | wc -l | tr -d ' ')
|
|
111
|
+
echo " bundled 依赖已就绪(${BUNDLED_COUNT} 个包)"
|
|
124
112
|
else
|
|
125
|
-
echo "
|
|
113
|
+
echo " ⚠️ 未找到 bundled node_modules,尝试安装依赖..."
|
|
114
|
+
NPM_TMP_CACHE=$(mktemp -d)
|
|
115
|
+
(cd "$STAGING_DIR" && npm install --omit=dev --omit=peer --ignore-scripts --cache="$NPM_TMP_CACHE" --quiet 2>&1) || echo " ⚠️ 依赖安装失败"
|
|
116
|
+
rm -rf "$NPM_TMP_CACHE"
|
|
126
117
|
fi
|
|
127
118
|
|
|
128
|
-
#
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
for old_plugin in openclaw-qqbot qqbot openclaw-qq @sliverp/qqbot @tencent-connect/qqbot @tencent-connect/openclaw-qq @tencent-connect/openclaw-qqbot; do
|
|
132
|
-
$CMD plugins uninstall "$old_plugin" 2>/dev/null && echo " 已卸载: $old_plugin" || true
|
|
133
|
-
done
|
|
134
|
-
for dir_name in openclaw-qqbot qqbot openclaw-qq; do
|
|
135
|
-
if [ -d "$EXTENSIONS_DIR/$dir_name" ]; then
|
|
136
|
-
rm -rf "$EXTENSIONS_DIR/$dir_name"
|
|
137
|
-
echo " 已清理残留目录: $dir_name"
|
|
138
|
-
fi
|
|
139
|
-
done
|
|
119
|
+
# 清理下载临时文件
|
|
120
|
+
rm -rf "$TMPDIR_PACK" "$EXTRACT_DIR"
|
|
121
|
+
cd "$HOME"
|
|
140
122
|
|
|
141
|
-
# [3
|
|
123
|
+
# [2/3] 原子替换:先移走旧目录,再把 staging 目录 rename 过去
|
|
124
|
+
# 这样 extensions/openclaw-qqbot 只有极短的不存在时间窗口
|
|
142
125
|
echo ""
|
|
143
|
-
echo "[3
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
if [ -
|
|
149
|
-
|
|
150
|
-
const fs = require('fs');
|
|
151
|
-
const cfg = JSON.parse(fs.readFileSync('$APP_CONFIG', 'utf8'));
|
|
152
|
-
const saved = JSON.parse(fs.readFileSync('$BACKUP_FILE', 'utf8'));
|
|
153
|
-
cfg.channels = cfg.channels || {};
|
|
154
|
-
cfg.channels.qqbot = saved;
|
|
155
|
-
fs.writeFileSync('$APP_CONFIG', JSON.stringify(cfg, null, 4) + '\n');
|
|
156
|
-
fs.unlinkSync('$BACKUP_FILE');
|
|
157
|
-
console.log(' ✅ 通道配置已恢复');
|
|
158
|
-
" 2>/dev/null || echo " ⚠️ 通道配置恢复失败,请手动检查: $APP_CONFIG"
|
|
126
|
+
echo "[2/3] 原子替换插件目录..."
|
|
127
|
+
TARGET_DIR="$EXTENSIONS_DIR/openclaw-qqbot"
|
|
128
|
+
OLD_DIR="$(dirname "$EXTENSIONS_DIR")/.qqbot-upgrade-old"
|
|
129
|
+
|
|
130
|
+
rm -rf "$OLD_DIR"
|
|
131
|
+
if [ -d "$TARGET_DIR" ]; then
|
|
132
|
+
mv "$TARGET_DIR" "$OLD_DIR"
|
|
159
133
|
fi
|
|
134
|
+
mv "$STAGING_DIR" "$TARGET_DIR"
|
|
135
|
+
rm -rf "$OLD_DIR"
|
|
160
136
|
|
|
161
|
-
#
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
DESIRED_TOKEN="${APPID}:${SECRET}"
|
|
166
|
-
|
|
167
|
-
# 读取当前配置中的 token
|
|
168
|
-
CURRENT_TOKEN=""
|
|
169
|
-
if [ -f "$APP_CONFIG" ]; then
|
|
170
|
-
CURRENT_TOKEN=$(node -e "
|
|
171
|
-
const cfg = JSON.parse(require('fs').readFileSync('$APP_CONFIG', 'utf8'));
|
|
172
|
-
const keys = ['qqbot', 'openclaw-qqbot', 'openclaw-qq'];
|
|
173
|
-
for (const key of keys) {
|
|
174
|
-
const ch = cfg.channels && cfg.channels[key];
|
|
175
|
-
if (!ch) continue;
|
|
176
|
-
if (ch.token) { process.stdout.write(ch.token); process.exit(0); }
|
|
177
|
-
if (ch.appId && ch.clientSecret) { process.stdout.write(ch.appId + ':' + ch.clientSecret); process.exit(0); }
|
|
178
|
-
}
|
|
179
|
-
" 2>/dev/null || true)
|
|
180
|
-
fi
|
|
137
|
+
# 清理可能残留的旧版 staging 目录(extensions 内外都清理)
|
|
138
|
+
rm -rf "$EXTENSIONS_DIR/openclaw-qqbot.staging"
|
|
139
|
+
rm -rf "$EXTENSIONS_DIR/.qqbot-upgrade-staging"
|
|
140
|
+
rm -rf "$EXTENSIONS_DIR/.qqbot-upgrade-old"
|
|
181
141
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
echo " ⚠️ 通道配置失败,请手动执行: $CMD channels add --channel qqbot --token \"$DESIRED_TOKEN\""
|
|
188
|
-
fi
|
|
189
|
-
fi
|
|
142
|
+
# 同时清理历史遗留的其他目录名
|
|
143
|
+
for dir_name in qqbot openclaw-qq; do
|
|
144
|
+
[ -d "$EXTENSIONS_DIR/$dir_name" ] && rm -rf "$EXTENSIONS_DIR/$dir_name"
|
|
145
|
+
done
|
|
146
|
+
echo " 已安装到: $TARGET_DIR"
|
|
190
147
|
|
|
191
|
-
# [
|
|
148
|
+
# [3/3] 输出新版本号和升级报告(供调用方解析)
|
|
192
149
|
echo ""
|
|
193
|
-
echo "[
|
|
194
|
-
$
|
|
150
|
+
echo "[3/3] 验证安装..."
|
|
151
|
+
NEW_VERSION="$(node -e "
|
|
152
|
+
try {
|
|
153
|
+
const fs = require('fs');
|
|
154
|
+
const path = require('path');
|
|
155
|
+
const p = path.join('$EXTENSIONS_DIR', 'openclaw-qqbot', 'package.json');
|
|
156
|
+
if (fs.existsSync(p)) {
|
|
157
|
+
const v = JSON.parse(fs.readFileSync(p, 'utf8')).version;
|
|
158
|
+
if (v) { process.stdout.write(v); process.exit(0); }
|
|
159
|
+
}
|
|
160
|
+
} catch {}
|
|
161
|
+
" 2>/dev/null || true)"
|
|
162
|
+
echo "QQBOT_NEW_VERSION=${NEW_VERSION:-unknown}"
|
|
163
|
+
|
|
164
|
+
# 输出结构化升级报告(QQBOT_REPORT=...),供 TS handler 解析后直接回复用户
|
|
165
|
+
if [ -n "$NEW_VERSION" ] && [ "$NEW_VERSION" != "unknown" ]; then
|
|
166
|
+
echo "QQBOT_REPORT=✅ QQBot 升级完成: v${NEW_VERSION}"
|
|
167
|
+
else
|
|
168
|
+
echo "QQBOT_REPORT=⚠️ QQBot 升级异常,无法确认新版本"
|
|
169
|
+
fi
|
|
195
170
|
|
|
196
171
|
echo ""
|
|
197
172
|
echo "==========================================="
|
|
198
|
-
echo " ✅
|
|
173
|
+
echo " ✅ 文件安装完成"
|
|
199
174
|
echo "==========================================="
|
|
200
|
-
echo ""
|
|
201
|
-
echo "常用命令:"
|
|
202
|
-
echo " $CMD logs --follow # 跟踪日志"
|
|
203
|
-
echo " $CMD gateway restart # 重启服务"
|
|
204
|
-
echo " $CMD plugins list # 查看插件列表"
|
|
@@ -186,6 +186,19 @@ INSTALL_LOG="/tmp/openclaw-install-$(date +%s).log"
|
|
|
186
186
|
echo "安装日志文件: $INSTALL_LOG"
|
|
187
187
|
echo "详细信息将记录到日志文件中..."
|
|
188
188
|
|
|
189
|
+
# 安装前先 stop gateway,防止 chokidar 在 plugins install 写入配置的中间状态
|
|
190
|
+
# 触发 restart,导致 "unknown channel id: qqbot" 等错误
|
|
191
|
+
_gw_was_running=0
|
|
192
|
+
if lsof -i :18789 -sTCP:LISTEN >/dev/null 2>&1; then
|
|
193
|
+
_gw_was_running=1
|
|
194
|
+
echo " 暂停 gateway 服务(避免安装过程中中间状态 restart)..."
|
|
195
|
+
openclaw gateway stop 2>/dev/null || true
|
|
196
|
+
sleep 1
|
|
197
|
+
fi
|
|
198
|
+
|
|
199
|
+
# 清理之前可能残留的 staging 目录
|
|
200
|
+
find "$HOME/.openclaw/extensions/" -maxdepth 1 -name ".openclaw-install-stage-*" -exec rm -rf {} + 2>/dev/null || true
|
|
201
|
+
|
|
189
202
|
# 尝试安装并捕获详细输出
|
|
190
203
|
if ! openclaw plugins install . 2>&1 | tee "$INSTALL_LOG"; then
|
|
191
204
|
echo ""
|
|
@@ -243,15 +256,136 @@ if ! openclaw plugins install . 2>&1 | tee "$INSTALL_LOG"; then
|
|
|
243
256
|
esac
|
|
244
257
|
else
|
|
245
258
|
echo ""
|
|
246
|
-
echo "✅
|
|
259
|
+
echo "✅ 插件安装命令执行完成"
|
|
247
260
|
echo "安装日志已保存到: $INSTALL_LOG"
|
|
248
261
|
|
|
249
|
-
#
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
262
|
+
# 验证插件目录是否真正创建(防止 "安装成功" 但目录缺失的情况)
|
|
263
|
+
_plugin_dir_ok=0
|
|
264
|
+
for _candidate_name in openclaw-qqbot qqbot openclaw-qq; do
|
|
265
|
+
if [ -d "$HOME/.openclaw/extensions/$_candidate_name" ] && \
|
|
266
|
+
[ -f "$HOME/.openclaw/extensions/$_candidate_name/package.json" ]; then
|
|
267
|
+
_plugin_dir_ok=1
|
|
268
|
+
echo " ✅ 插件目录验证通过: ~/.openclaw/extensions/$_candidate_name/"
|
|
269
|
+
break
|
|
270
|
+
fi
|
|
271
|
+
done
|
|
272
|
+
if [ "$_plugin_dir_ok" -eq 0 ]; then
|
|
273
|
+
echo ""
|
|
274
|
+
echo "⚠️ 警告: 插件目录不存在!安装命令返回成功但目录未创建"
|
|
275
|
+
echo " 可能原因: staging 目录未正确 rename(参考 openclaw/issues)"
|
|
276
|
+
echo " 检查 staging 残留:"
|
|
277
|
+
ls -la "$HOME/.openclaw/extensions/" 2>/dev/null
|
|
278
|
+
echo ""
|
|
279
|
+
echo " 尝试自动修复: 清理残留并重试安装..."
|
|
280
|
+
find "$HOME/.openclaw/extensions/" -maxdepth 1 -name ".openclaw-install-stage-*" -exec rm -rf {} + 2>/dev/null || true
|
|
281
|
+
if openclaw plugins install . 2>&1 | tee -a "$INSTALL_LOG"; then
|
|
282
|
+
for _candidate_name in openclaw-qqbot qqbot openclaw-qq; do
|
|
283
|
+
if [ -d "$HOME/.openclaw/extensions/$_candidate_name" ]; then
|
|
284
|
+
_plugin_dir_ok=1
|
|
285
|
+
echo " ✅ 重试安装成功: ~/.openclaw/extensions/$_candidate_name/"
|
|
286
|
+
break
|
|
287
|
+
fi
|
|
288
|
+
done
|
|
289
|
+
fi
|
|
290
|
+
if [ "$_plugin_dir_ok" -eq 0 ]; then
|
|
291
|
+
echo " ❌ 重试安装仍失败,插件目录不存在"
|
|
292
|
+
echo " 请手动排查: ls -la ~/.openclaw/extensions/"
|
|
293
|
+
# 清理无效的配置条目,防止 gateway 启动时报错
|
|
294
|
+
echo " 清理无效的配置条目..."
|
|
295
|
+
node -e "
|
|
296
|
+
const fs = require('fs');
|
|
297
|
+
const path = require('path');
|
|
298
|
+
for (const app of ['openclaw', 'clawdbot', 'moltbot']) {
|
|
299
|
+
const f = path.join(process.env.HOME, '.' + app, app + '.json');
|
|
300
|
+
if (!fs.existsSync(f)) continue;
|
|
301
|
+
const cfg = JSON.parse(fs.readFileSync(f, 'utf8'));
|
|
302
|
+
let changed = false;
|
|
303
|
+
// 移除 channels.qqbot(插件未加载时此配置会导致 unknown channel id 错误)
|
|
304
|
+
if (cfg.channels && cfg.channels.qqbot) { delete cfg.channels.qqbot; changed = true; }
|
|
305
|
+
// 清理 plugins.allow 中的 openclaw-qqbot
|
|
306
|
+
if (cfg.plugins && Array.isArray(cfg.plugins.allow)) {
|
|
307
|
+
cfg.plugins.allow = cfg.plugins.allow.filter(x => x !== 'openclaw-qqbot');
|
|
308
|
+
changed = true;
|
|
309
|
+
}
|
|
310
|
+
if (changed) fs.writeFileSync(f, JSON.stringify(cfg, null, 4) + '\n');
|
|
311
|
+
break;
|
|
312
|
+
}
|
|
313
|
+
" 2>/dev/null || true
|
|
314
|
+
echo " 已清理无效配置,gateway 可正常启动(无 qqbot 插件状态)"
|
|
315
|
+
read -t 10 -p "是否继续? (y/N): " _cont || _cont="N"
|
|
316
|
+
case "$_cont" in
|
|
317
|
+
[Yy]* ) echo "继续..." ;;
|
|
318
|
+
* ) exit 1 ;;
|
|
319
|
+
esac
|
|
320
|
+
fi
|
|
321
|
+
fi
|
|
322
|
+
|
|
323
|
+
# 清理多余的 peerDependencies 传递依赖(兼容旧版 openclaw):
|
|
324
|
+
# openclaw v2026.3.4 之前的 plugins install 缺少 --omit=peer,会把 peerDeps
|
|
325
|
+
# (openclaw 平台及其 400+ 传递依赖)也安装到插件 node_modules 中。
|
|
326
|
+
# 新版已修复,此处通过阈值判断:包数量 > 50 才触发清理,避免对新版做无用操作。
|
|
327
|
+
PLUGIN_NM=""
|
|
328
|
+
for _candidate in openclaw-qqbot qqbot openclaw-qq; do
|
|
329
|
+
_nm="$HOME/.openclaw/extensions/$_candidate/node_modules"
|
|
330
|
+
[ -d "$_nm" ] && PLUGIN_NM="$_nm" && break
|
|
331
|
+
done
|
|
332
|
+
if [ -n "$PLUGIN_NM" ]; then
|
|
333
|
+
_before=$(ls -d "$PLUGIN_NM"/*/ "$PLUGIN_NM"/@*/*/ 2>/dev/null | wc -l | tr -d ' ')
|
|
334
|
+
if [ "$_before" -gt 50 ]; then
|
|
335
|
+
# 读取 bundledDependencies 列表,只保留这些包及其子依赖
|
|
336
|
+
_bundled_deps=$(node -e "
|
|
337
|
+
const fs = require('fs');
|
|
338
|
+
const path = require('path');
|
|
339
|
+
const pkgPath = path.join('$PLUGIN_NM', '..', 'package.json');
|
|
340
|
+
if (!fs.existsSync(pkgPath)) process.exit(0);
|
|
341
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
342
|
+
const bundled = pkg.bundledDependencies || pkg.bundleDependencies || [];
|
|
343
|
+
const keep = new Set();
|
|
344
|
+
const resolve = (name) => {
|
|
345
|
+
if (keep.has(name)) return;
|
|
346
|
+
keep.add(name);
|
|
347
|
+
const depPkg = path.join('$PLUGIN_NM', name, 'package.json');
|
|
348
|
+
if (!fs.existsSync(depPkg)) return;
|
|
349
|
+
const dep = JSON.parse(fs.readFileSync(depPkg, 'utf8'));
|
|
350
|
+
for (const d of Object.keys(dep.dependencies || {})) resolve(d);
|
|
351
|
+
};
|
|
352
|
+
bundled.forEach(resolve);
|
|
353
|
+
const installed = fs.readdirSync('$PLUGIN_NM').filter(n => !n.startsWith('.'));
|
|
354
|
+
const toRemove = [];
|
|
355
|
+
for (const item of installed) {
|
|
356
|
+
if (item.startsWith('@')) {
|
|
357
|
+
const scopeDir = path.join('$PLUGIN_NM', item);
|
|
358
|
+
const subs = fs.readdirSync(scopeDir);
|
|
359
|
+
const keepSubs = subs.filter(s => keep.has(item + '/' + s));
|
|
360
|
+
if (keepSubs.length === 0) toRemove.push(item);
|
|
361
|
+
else {
|
|
362
|
+
for (const s of subs) {
|
|
363
|
+
if (!keep.has(item + '/' + s)) toRemove.push(item + '/' + s);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
} else {
|
|
367
|
+
if (!keep.has(item)) toRemove.push(item);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
process.stdout.write(toRemove.join('\n'));
|
|
371
|
+
" 2>/dev/null || true)
|
|
372
|
+
if [ -n "$_bundled_deps" ]; then
|
|
373
|
+
echo ""
|
|
374
|
+
echo "检测到 ${_before} 个包(超过阈值 50),清理多余的 peerDep 传递依赖..."
|
|
375
|
+
echo "$_bundled_deps" | while IFS= read -r _pkg; do
|
|
376
|
+
rm -rf "$PLUGIN_NM/$_pkg"
|
|
377
|
+
done
|
|
378
|
+
find "$PLUGIN_NM" -maxdepth 1 -type d -name '@*' -empty -delete 2>/dev/null || true
|
|
379
|
+
_after=$(ls -d "$PLUGIN_NM"/*/ "$PLUGIN_NM"/@*/*/ 2>/dev/null | wc -l | tr -d ' ')
|
|
380
|
+
echo " 已清理: ${_before} → ${_after} 个包"
|
|
381
|
+
fi
|
|
382
|
+
else
|
|
383
|
+
echo " node_modules 包数量正常(${_before} 个),无需清理"
|
|
384
|
+
fi
|
|
385
|
+
fi
|
|
386
|
+
|
|
387
|
+
# gateway 已在安装前 stop,此时不会有自动 restart 的问题
|
|
388
|
+
# 所有配置写入完成后,在 Step 6 统一启动
|
|
255
389
|
|
|
256
390
|
# 记录更新后的 qqbot 插件版本
|
|
257
391
|
NEW_QQBOT_VERSION=$(node -e '
|
|
@@ -318,10 +452,9 @@ if [ -n "$DESIRED_QQBOT_TOKEN" ]; then
|
|
|
318
452
|
else
|
|
319
453
|
echo "✅ 机器人通道配置成功"
|
|
320
454
|
_config_changed=1
|
|
321
|
-
#
|
|
322
|
-
#
|
|
323
|
-
|
|
324
|
-
sleep 5
|
|
455
|
+
# channels 配置变更在 reload plan 中匹配为 hot reload(非 restart),
|
|
456
|
+
# 由 channel 插件热重载处理,通常 <1 秒完成,无需长时间等待。
|
|
457
|
+
sleep 1
|
|
325
458
|
fi
|
|
326
459
|
else
|
|
327
460
|
# 未提供任何可用 token 时,检查是否已有可用配置
|
|
@@ -446,19 +579,37 @@ start_choice=$(printf '%s' "$start_choice" | tr '[:upper:]' '[:lower:]')
|
|
|
446
579
|
case "$start_choice" in
|
|
447
580
|
y|yes)
|
|
448
581
|
echo ""
|
|
449
|
-
#
|
|
450
|
-
# (plugins install 触发的自动重启链已在第 3 步等待完成)
|
|
582
|
+
# gateway 在 Step 3 安装前已 stop,此处统一 restart
|
|
451
583
|
echo "正在后台重启 openclaw 网关服务..."
|
|
452
|
-
|
|
584
|
+
_restart_output=$(openclaw gateway restart 2>&1) || true
|
|
585
|
+
echo "$_restart_output"
|
|
586
|
+
|
|
587
|
+
if echo "$_restart_output" | grep -qi "not loaded\|not found\|not running\|not installed"; then
|
|
453
588
|
echo ""
|
|
454
|
-
echo "⚠️
|
|
455
|
-
echo "
|
|
589
|
+
echo "⚠️ gateway 服务未加载,尝试自动恢复..."
|
|
590
|
+
echo ""
|
|
591
|
+
echo " [1/2] 注册 gateway 服务..."
|
|
592
|
+
_install_out=$(openclaw gateway install 2>&1) || true
|
|
593
|
+
echo " $_install_out"
|
|
594
|
+
echo ""
|
|
595
|
+
echo " [2/2] 启动 gateway 服务..."
|
|
596
|
+
_start_out=$(openclaw gateway start 2>&1) || true
|
|
597
|
+
echo " $_start_out"
|
|
598
|
+
if echo "$_start_out" | grep -qi "restart\|started\|bootstrap"; then
|
|
599
|
+
echo ""
|
|
600
|
+
echo "✅ gateway 服务恢复成功"
|
|
601
|
+
else
|
|
602
|
+
echo ""
|
|
603
|
+
echo "⚠️ 自动恢复可能失败,请手动执行:"
|
|
604
|
+
echo " openclaw gateway install && openclaw gateway start"
|
|
605
|
+
fi
|
|
606
|
+
else
|
|
607
|
+
echo ""
|
|
608
|
+
echo "✅ openclaw 网关已在后台重启"
|
|
456
609
|
fi
|
|
457
610
|
echo ""
|
|
458
|
-
|
|
459
|
-
echo ""
|
|
460
|
-
# 等待 gateway 端口就绪(插件安装+自动重启可能需要 30-60 秒)
|
|
461
|
-
echo "等待 gateway 就绪(插件安装中,可能需要 30-60 秒)..."
|
|
611
|
+
# 等待 gateway 端口就绪
|
|
612
|
+
echo "等待 gateway 就绪..."
|
|
462
613
|
echo "========================================="
|
|
463
614
|
_port_ready=0
|
|
464
615
|
for i in $(seq 1 30); do
|
|
@@ -472,32 +623,49 @@ case "$start_choice" in
|
|
|
472
623
|
echo ""
|
|
473
624
|
|
|
474
625
|
if [ "$_port_ready" -eq 0 ]; then
|
|
475
|
-
echo "⚠️
|
|
476
|
-
|
|
477
|
-
echo "
|
|
478
|
-
|
|
626
|
+
echo "⚠️ 等待超时,尝试 openclaw doctor --fix 自动修复..."
|
|
627
|
+
_doctor_output=$(openclaw doctor --fix 2>&1) || true
|
|
628
|
+
echo "$_doctor_output"
|
|
629
|
+
# doctor --fix 后再尝试 restart 一次
|
|
630
|
+
echo ""
|
|
631
|
+
echo "doctor 修复后重试 gateway restart..."
|
|
632
|
+
openclaw gateway restart 2>&1 || true
|
|
633
|
+
sleep 5
|
|
634
|
+
if lsof -i :18789 -sTCP:LISTEN >/dev/null 2>&1; then
|
|
635
|
+
echo "✅ doctor --fix 后 gateway 启动成功"
|
|
636
|
+
_port_ready=1
|
|
637
|
+
else
|
|
638
|
+
echo "❌ 仍然无法启动,请手动排查:"
|
|
639
|
+
echo " openclaw doctor"
|
|
640
|
+
echo " tail -f /tmp/openclaw/openclaw-$(date +%Y-%m-%d).log"
|
|
641
|
+
fi
|
|
642
|
+
fi
|
|
643
|
+
|
|
644
|
+
# 端口就绪后:检查 qqbot 连接 + 跟踪日志
|
|
645
|
+
if [ "$_port_ready" -eq 1 ]; then
|
|
479
646
|
echo "✅ Gateway 端口已就绪"
|
|
480
647
|
echo ""
|
|
481
|
-
# 检查 qqbot WS 是否连接成功(最多等
|
|
648
|
+
# 检查 qqbot WS 是否连接成功(最多等 20 秒)
|
|
482
649
|
echo "检查 qqbot 插件连接状态..."
|
|
483
650
|
_LOG_FILE="/tmp/openclaw/openclaw-$(date +%Y-%m-%d).log"
|
|
651
|
+
_restart_ts=$(date +%s)
|
|
484
652
|
_qqbot_ready=0
|
|
485
|
-
for _j in $(seq 1
|
|
486
|
-
if
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
653
|
+
for _j in $(seq 1 10); do
|
|
654
|
+
if [ -f "$_LOG_FILE" ]; then
|
|
655
|
+
_last_line=$(grep "Gateway ready" "$_LOG_FILE" 2>/dev/null | tail -1 || true)
|
|
656
|
+
if [ -n "$_last_line" ]; then
|
|
657
|
+
_qqbot_ready=1
|
|
658
|
+
break
|
|
659
|
+
fi
|
|
491
660
|
fi
|
|
492
|
-
printf "\r 等待 qqbot WS 连接... (%d/
|
|
661
|
+
printf "\r 等待 qqbot WS 连接... (%d/10)" "$_j"
|
|
493
662
|
sleep 2
|
|
494
663
|
done
|
|
495
664
|
echo ""
|
|
496
665
|
|
|
497
666
|
if [ "$_qqbot_ready" -eq 0 ]; then
|
|
498
|
-
echo "⚠️ qqbot
|
|
499
|
-
|
|
500
|
-
sleep 10
|
|
667
|
+
echo "⚠️ qqbot 插件可能未正确加载"
|
|
668
|
+
echo "请检查: openclaw doctor"
|
|
501
669
|
else
|
|
502
670
|
echo "✅ qqbot 插件已连接"
|
|
503
671
|
fi
|