@tencent-connect/openclaw-qqbot 1.6.6 → 1.6.7
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 +6 -1
- package/README.zh.md +6 -1
- package/dist/src/api.js +1 -1
- package/dist/src/config.js +1 -1
- package/dist/src/gateway.js +1 -1
- package/dist/src/request-context.d.ts +7 -0
- package/dist/src/request-context.js +7 -0
- package/dist/src/slash-commands.js +362 -38
- package/dist/src/tools/remind.js +17 -9
- package/dist/src/types.d.ts +9 -2
- package/dist/src/update-checker.d.ts +3 -1
- package/dist/src/update-checker.js +13 -2
- package/dist/src/utils/pkg-version.js +19 -9
- package/package.json +4 -1
- package/scripts/postinstall-link-sdk.js +22 -9
- package/scripts/upgrade-via-npm.ps1 +9 -0
- package/scripts/upgrade-via-npm.sh +154 -30
- package/scripts/upgrade-via-source.sh +124 -38
- package/skills/qqbot-remind/SKILL.md +21 -11
- package/src/api.ts +1 -1
- package/src/config.ts +1 -1
- package/src/gateway.ts +1 -1
- package/src/request-context.ts +10 -0
- package/src/slash-commands.ts +354 -36
- package/src/tools/remind.ts +17 -9
- package/src/types.ts +9 -2
- package/src/update-checker.ts +14 -2
- package/src/utils/pkg-version.ts +18 -8
|
@@ -262,8 +262,9 @@ if lsof -i :18789 -sTCP:LISTEN >/dev/null 2>&1; then
|
|
|
262
262
|
sleep 1
|
|
263
263
|
fi
|
|
264
264
|
|
|
265
|
-
# 清理之前可能残留的 staging
|
|
265
|
+
# 清理之前可能残留的 staging 目录(extensions 和 /tmp 中都可能存在)
|
|
266
266
|
find "$HOME/.openclaw/extensions/" -maxdepth 1 -name ".openclaw-install-stage-*" -exec rm -rf {} + 2>/dev/null || true
|
|
267
|
+
find "${TMPDIR:-/tmp}" -maxdepth 1 -name ".openclaw-install-stage-*" -exec rm -rf {} + 2>/dev/null || true
|
|
267
268
|
|
|
268
269
|
# ── 清空并重新构建 dist/ ──
|
|
269
270
|
# openclaw plugins install . 只做文件复制,不执行 npm lifecycle scripts。
|
|
@@ -290,7 +291,25 @@ fi
|
|
|
290
291
|
# 由于 CJS/ESM 混用问题(Node 22 ERR_INTERNAL_ASSERTION),验证阶段可能失败
|
|
291
292
|
# 但文件已成功拷贝。因此不依赖退出码,而是检查安装目录中关键文件是否存在。
|
|
292
293
|
_INSTALL_DIR="$HOME/.openclaw/extensions/openclaw-qqbot"
|
|
294
|
+
|
|
295
|
+
# ── 优化:安装前临时移走 node_modules ──
|
|
296
|
+
# openclaw plugins install . 会把整个项目目录(含 node_modules/)复制到 extensions,
|
|
297
|
+
# 但运行时只需要 bundledDependencies 中的 3 个包 + openclaw symlink。
|
|
298
|
+
# 移走 node_modules 可避免复制数百个不必要的包,大幅加速安装。
|
|
299
|
+
_NM_BACKUP=""
|
|
300
|
+
if [ -d "$PROJ_DIR/node_modules" ]; then
|
|
301
|
+
echo " 临时移走 node_modules(避免整体复制到 extensions)..."
|
|
302
|
+
_NM_BACKUP="$PROJ_DIR/.node_modules_install_bak"
|
|
303
|
+
mv "$PROJ_DIR/node_modules" "$_NM_BACKUP"
|
|
304
|
+
fi
|
|
305
|
+
|
|
293
306
|
openclaw plugins install . 2>&1 | tee "$INSTALL_LOG" || true
|
|
307
|
+
|
|
308
|
+
# ── 恢复 node_modules ──
|
|
309
|
+
if [ -n "$_NM_BACKUP" ] && [ -d "$_NM_BACKUP" ]; then
|
|
310
|
+
mv "$_NM_BACKUP" "$PROJ_DIR/node_modules"
|
|
311
|
+
echo " 已恢复源码目录 node_modules"
|
|
312
|
+
fi
|
|
294
313
|
if [ ! -f "$_INSTALL_DIR/dist/index.js" ] || [ ! -f "$_INSTALL_DIR/preload.cjs" ]; then
|
|
295
314
|
echo ""
|
|
296
315
|
echo "❌ 插件安装失败!"
|
|
@@ -344,13 +363,13 @@ if [ ! -f "$_INSTALL_DIR/dist/index.js" ] || [ ! -f "$_INSTALL_DIR/preload.cjs"
|
|
|
344
363
|
echo "请先解决安装问题后再运行此脚本。"
|
|
345
364
|
# 恢复 channels.qqbot 后再退出
|
|
346
365
|
if [ -n "$_QQBOT_CHANNEL_STASH" ] && [ -n "$_STASH_CFG" ] && [ -f "$_STASH_CFG" ]; then
|
|
347
|
-
node -e
|
|
348
|
-
const fs = require(
|
|
349
|
-
const cfg = JSON.parse(fs.readFileSync(
|
|
366
|
+
_STASH="$_QQBOT_CHANNEL_STASH" _CFG="$_STASH_CFG" node -e '
|
|
367
|
+
const fs = require("fs");
|
|
368
|
+
const cfg = JSON.parse(fs.readFileSync(process.env._CFG, "utf8"));
|
|
350
369
|
if (!cfg.channels) cfg.channels = {};
|
|
351
|
-
cfg.channels.qqbot =
|
|
352
|
-
fs.writeFileSync(
|
|
353
|
-
|
|
370
|
+
cfg.channels.qqbot = JSON.parse(process.env._STASH);
|
|
371
|
+
fs.writeFileSync(process.env._CFG, JSON.stringify(cfg, null, 4) + "\n");
|
|
372
|
+
' 2>/dev/null || true
|
|
354
373
|
fi
|
|
355
374
|
exit 1
|
|
356
375
|
;;
|
|
@@ -398,6 +417,13 @@ else
|
|
|
398
417
|
echo ""
|
|
399
418
|
echo " 尝试自动修复: 清理残留并重试安装..."
|
|
400
419
|
find "$HOME/.openclaw/extensions/" -maxdepth 1 -name ".openclaw-install-stage-*" -exec rm -rf {} + 2>/dev/null || true
|
|
420
|
+
find "${TMPDIR:-/tmp}" -maxdepth 1 -name ".openclaw-install-stage-*" -exec rm -rf {} + 2>/dev/null || true
|
|
421
|
+
# 重试时同样移走 node_modules 避免整体复制
|
|
422
|
+
_NM_BACKUP=""
|
|
423
|
+
if [ -d "$PROJ_DIR/node_modules" ]; then
|
|
424
|
+
_NM_BACKUP="$PROJ_DIR/.node_modules_install_bak"
|
|
425
|
+
mv "$PROJ_DIR/node_modules" "$_NM_BACKUP"
|
|
426
|
+
fi
|
|
401
427
|
if openclaw plugins install . 2>&1 | tee -a "$INSTALL_LOG"; then
|
|
402
428
|
for _candidate_name in openclaw-qqbot qqbot openclaw-qq; do
|
|
403
429
|
if [ -d "$HOME/.openclaw/extensions/$_candidate_name" ]; then
|
|
@@ -407,6 +433,10 @@ else
|
|
|
407
433
|
fi
|
|
408
434
|
done
|
|
409
435
|
fi
|
|
436
|
+
# 恢复 node_modules
|
|
437
|
+
if [ -n "$_NM_BACKUP" ] && [ -d "$_NM_BACKUP" ]; then
|
|
438
|
+
mv "$_NM_BACKUP" "$PROJ_DIR/node_modules"
|
|
439
|
+
fi
|
|
410
440
|
if [ "$_plugin_dir_ok" -eq 0 ]; then
|
|
411
441
|
echo " ❌ 重试安装仍失败,插件目录不存在"
|
|
412
442
|
echo " 请手动排查: ls -la ~/.openclaw/extensions/"
|
|
@@ -440,18 +470,70 @@ else
|
|
|
440
470
|
fi
|
|
441
471
|
fi
|
|
442
472
|
|
|
443
|
-
#
|
|
444
|
-
#
|
|
445
|
-
# (
|
|
446
|
-
# 新版已修复,此处通过阈值判断:包数量 > 50 才触发清理,避免对新版做无用操作。
|
|
473
|
+
# ── 复制 bundledDependencies 到插件 node_modules ──
|
|
474
|
+
# 由于安装前移走了源码的 node_modules,extensions 中的插件目录没有依赖。
|
|
475
|
+
# 只需复制 bundledDependencies(ws, silk-wasm, mpg123-decoder)及其传递依赖即可。
|
|
447
476
|
PLUGIN_NM=""
|
|
448
477
|
for _candidate in openclaw-qqbot qqbot openclaw-qq; do
|
|
449
478
|
_nm="$HOME/.openclaw/extensions/$_candidate/node_modules"
|
|
450
|
-
|
|
479
|
+
_ext_dir="$HOME/.openclaw/extensions/$_candidate"
|
|
480
|
+
[ -d "$_ext_dir" ] && PLUGIN_NM="$_nm" && break
|
|
451
481
|
done
|
|
452
|
-
if [ -n "$PLUGIN_NM" ]; then
|
|
482
|
+
if [ -n "$PLUGIN_NM" ] && [ -d "$PROJ_DIR/node_modules" ]; then
|
|
483
|
+
# 如果 extensions 中已有大量包(旧版 openclaw 安装的 peerDeps),先清理
|
|
484
|
+
if [ -d "$PLUGIN_NM" ]; then
|
|
485
|
+
_before=$(ls -d "$PLUGIN_NM"/*/ "$PLUGIN_NM"/@*/*/ 2>/dev/null | wc -l | tr -d ' ')
|
|
486
|
+
if [ "$_before" -gt 50 ]; then
|
|
487
|
+
echo ""
|
|
488
|
+
echo "检测到 ${_before} 个包(超过阈值 50),清理多余的 peerDep 传递依赖..."
|
|
489
|
+
rm -rf "$PLUGIN_NM"
|
|
490
|
+
fi
|
|
491
|
+
fi
|
|
492
|
+
|
|
493
|
+
# 读取 bundledDependencies 及其传递依赖列表,只复制这些包
|
|
494
|
+
_deps_to_copy=$(node -e "
|
|
495
|
+
const fs = require('fs');
|
|
496
|
+
const path = require('path');
|
|
497
|
+
const pkgPath = path.join('$PROJ_DIR', 'package.json');
|
|
498
|
+
if (!fs.existsSync(pkgPath)) process.exit(0);
|
|
499
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
500
|
+
const bundled = pkg.bundledDependencies || pkg.bundleDependencies || [];
|
|
501
|
+
const keep = new Set();
|
|
502
|
+
const resolve = (name) => {
|
|
503
|
+
if (keep.has(name)) return;
|
|
504
|
+
keep.add(name);
|
|
505
|
+
const depPkg = path.join('$PROJ_DIR', 'node_modules', name, 'package.json');
|
|
506
|
+
if (!fs.existsSync(depPkg)) return;
|
|
507
|
+
const dep = JSON.parse(fs.readFileSync(depPkg, 'utf8'));
|
|
508
|
+
for (const d of Object.keys(dep.dependencies || {})) resolve(d);
|
|
509
|
+
};
|
|
510
|
+
bundled.forEach(resolve);
|
|
511
|
+
process.stdout.write([...keep].join('\\n'));
|
|
512
|
+
" 2>/dev/null || true)
|
|
513
|
+
|
|
514
|
+
if [ -n "$_deps_to_copy" ]; then
|
|
515
|
+
mkdir -p "$PLUGIN_NM"
|
|
516
|
+
_copied=0
|
|
517
|
+
echo "$_deps_to_copy" | while IFS= read -r _dep; do
|
|
518
|
+
_src="$PROJ_DIR/node_modules/$_dep"
|
|
519
|
+
_dst="$PLUGIN_NM/$_dep"
|
|
520
|
+
if [ -d "$_src" ] && [ ! -d "$_dst" ]; then
|
|
521
|
+
# 处理 scoped 包(如 @scope/pkg)
|
|
522
|
+
mkdir -p "$(dirname "$_dst")"
|
|
523
|
+
cp -r "$_src" "$_dst"
|
|
524
|
+
fi
|
|
525
|
+
done
|
|
526
|
+
_after=$(ls -d "$PLUGIN_NM"/*/ "$PLUGIN_NM"/@*/*/ 2>/dev/null | wc -l | tr -d ' ')
|
|
527
|
+
echo " ✅ 已复制 bundled 依赖到插件目录(${_after} 个包)"
|
|
528
|
+
else
|
|
529
|
+
echo " ⚠️ 未读取到 bundledDependencies,跳过依赖复制"
|
|
530
|
+
fi
|
|
531
|
+
elif [ -n "$PLUGIN_NM" ] && [ -d "$PLUGIN_NM" ]; then
|
|
532
|
+
# node_modules 已存在(可能是旧版 openclaw 安装的),检查是否需要清理
|
|
453
533
|
_before=$(ls -d "$PLUGIN_NM"/*/ "$PLUGIN_NM"/@*/*/ 2>/dev/null | wc -l | tr -d ' ')
|
|
454
534
|
if [ "$_before" -gt 50 ]; then
|
|
535
|
+
echo ""
|
|
536
|
+
echo "检测到 ${_before} 个包(超过阈值 50),清理多余的 peerDep 传递依赖..."
|
|
455
537
|
# 读取 bundledDependencies 列表,只保留这些包及其子依赖
|
|
456
538
|
_bundled_deps=$(node -e "
|
|
457
539
|
const fs = require('fs');
|
|
@@ -490,8 +572,6 @@ else
|
|
|
490
572
|
process.stdout.write(toRemove.join('\n'));
|
|
491
573
|
" 2>/dev/null || true)
|
|
492
574
|
if [ -n "$_bundled_deps" ]; then
|
|
493
|
-
echo ""
|
|
494
|
-
echo "检测到 ${_before} 个包(超过阈值 50),清理多余的 peerDep 传递依赖..."
|
|
495
575
|
echo "$_bundled_deps" | while IFS= read -r _pkg; do
|
|
496
576
|
rm -rf "$PLUGIN_NM/$_pkg"
|
|
497
577
|
done
|
|
@@ -527,6 +607,7 @@ else
|
|
|
527
607
|
# 清理 openclaw CLI install 留下的 backup 目录,
|
|
528
608
|
# 避免 gateway 发现两个同 id 插件不断刷 duplicate plugin id 警告
|
|
529
609
|
find "$HOME/.openclaw/extensions/" -maxdepth 1 -name ".openclaw-qqbot-backup-*" -exec rm -rf {} + 2>/dev/null || true
|
|
610
|
+
find "${TMPDIR:-/tmp}" -maxdepth 1 -name ".qqbot-upgrade-backup-*" -exec rm -rf {} + 2>/dev/null || true
|
|
530
611
|
|
|
531
612
|
# 恢复 channels.qqbot 完整配置(防止 plugins install 意外覆盖)
|
|
532
613
|
# 策略:直接用备份完整覆盖回去,确保 groups / env / prompts / allowFrom 等所有用户配置不丢失。
|
|
@@ -591,11 +672,11 @@ echo "[4/6] 准备机器人通道配置..."
|
|
|
591
672
|
# 注意:channels.qqbot 已被暂存移除,所以从 _QQBOT_CHANNEL_STASH 读取
|
|
592
673
|
CURRENT_QQBOT_TOKEN=""
|
|
593
674
|
if [ -n "$_QQBOT_CHANNEL_STASH" ]; then
|
|
594
|
-
CURRENT_QQBOT_TOKEN=$(node -e
|
|
595
|
-
const ch =
|
|
675
|
+
CURRENT_QQBOT_TOKEN=$(_STASH="$_QQBOT_CHANNEL_STASH" node -e '
|
|
676
|
+
const ch = JSON.parse(process.env._STASH);
|
|
596
677
|
if (ch.token) { process.stdout.write(ch.token); }
|
|
597
|
-
else if (ch.appId && ch.clientSecret) { process.stdout.write(ch.appId +
|
|
598
|
-
|
|
678
|
+
else if (ch.appId && ch.clientSecret) { process.stdout.write(ch.appId + ":" + ch.clientSecret); }
|
|
679
|
+
' 2>/dev/null || true)
|
|
599
680
|
fi
|
|
600
681
|
|
|
601
682
|
DESIRED_QQBOT_TOKEN=""
|
|
@@ -786,34 +867,39 @@ case "$start_choice" in
|
|
|
786
867
|
|
|
787
868
|
if [ -n "$_target_cfg" ]; then
|
|
788
869
|
# 构建完整的 channels.qqbot 对象(合并暂存配置 + 新 token + markdown)
|
|
789
|
-
node -e "
|
|
790
|
-
|
|
791
|
-
|
|
870
|
+
# 通过环境变量传递,避免 JSON 双引号在 node -e "..." 中被 shell 错误解析
|
|
871
|
+
_STASH="$_QQBOT_CHANNEL_STASH" \
|
|
872
|
+
_DESIRED="$DESIRED_QQBOT_TOKEN" \
|
|
873
|
+
_MD="$MARKDOWN_VALUE" \
|
|
874
|
+
_CFG="$_target_cfg" \
|
|
875
|
+
node -e '
|
|
876
|
+
const fs = require("fs");
|
|
877
|
+
const cfg = JSON.parse(fs.readFileSync(process.env._CFG, "utf8"));
|
|
792
878
|
if (!cfg.channels) cfg.channels = {};
|
|
793
879
|
|
|
794
880
|
// 从暂存恢复基础配置
|
|
795
|
-
const stash =
|
|
881
|
+
const stash = process.env._STASH;
|
|
796
882
|
if (stash) {
|
|
797
883
|
try { cfg.channels.qqbot = JSON.parse(stash); } catch {}
|
|
798
884
|
}
|
|
799
885
|
if (!cfg.channels.qqbot) cfg.channels.qqbot = {};
|
|
800
886
|
|
|
801
887
|
// 覆盖 token(如果有新值)
|
|
802
|
-
const desired =
|
|
803
|
-
if (desired && desired.includes(
|
|
804
|
-
const [appId, ...rest] = desired.split(
|
|
888
|
+
const desired = process.env._DESIRED;
|
|
889
|
+
if (desired && desired.includes(":")) {
|
|
890
|
+
const [appId, ...rest] = desired.split(":");
|
|
805
891
|
cfg.channels.qqbot.appId = appId;
|
|
806
|
-
cfg.channels.qqbot.clientSecret = rest.join(
|
|
892
|
+
cfg.channels.qqbot.clientSecret = rest.join(":");
|
|
807
893
|
delete cfg.channels.qqbot.token;
|
|
808
894
|
}
|
|
809
895
|
|
|
810
896
|
// 覆盖 markdown(如果有指定)
|
|
811
|
-
const md =
|
|
812
|
-
if (md ===
|
|
813
|
-
else if (md ===
|
|
897
|
+
const md = process.env._MD;
|
|
898
|
+
if (md === "true") cfg.channels.qqbot.markdownSupport = true;
|
|
899
|
+
else if (md === "false") cfg.channels.qqbot.markdownSupport = false;
|
|
814
900
|
|
|
815
|
-
fs.writeFileSync(
|
|
816
|
-
|
|
901
|
+
fs.writeFileSync(process.env._CFG, JSON.stringify(cfg, null, 4) + "\n");
|
|
902
|
+
' 2>&1 || echo " ⚠️ 配置写入失败"
|
|
817
903
|
echo " ✅ 已恢复 channels.qqbot 配置(含 token/markdown)"
|
|
818
904
|
_need_reload=1
|
|
819
905
|
fi
|
|
@@ -883,13 +969,13 @@ case "$start_choice" in
|
|
|
883
969
|
# 注意:下次 gateway 启动可能因 "unknown channel id" 失败,
|
|
884
970
|
# 需要用户手动 stop → 移除 channels.qqbot → start → 恢复
|
|
885
971
|
if [ -n "$_QQBOT_CHANNEL_STASH" ] && [ -n "$_STASH_CFG" ] && [ -f "$_STASH_CFG" ]; then
|
|
886
|
-
node -e
|
|
887
|
-
const fs = require(
|
|
888
|
-
const cfg = JSON.parse(fs.readFileSync(
|
|
972
|
+
_STASH="$_QQBOT_CHANNEL_STASH" _CFG="$_STASH_CFG" node -e '
|
|
973
|
+
const fs = require("fs");
|
|
974
|
+
const cfg = JSON.parse(fs.readFileSync(process.env._CFG, "utf8"));
|
|
889
975
|
if (!cfg.channels) cfg.channels = {};
|
|
890
|
-
cfg.channels.qqbot =
|
|
891
|
-
fs.writeFileSync(
|
|
892
|
-
|
|
976
|
+
cfg.channels.qqbot = JSON.parse(process.env._STASH);
|
|
977
|
+
fs.writeFileSync(process.env._CFG, JSON.stringify(cfg, null, 4) + "\n");
|
|
978
|
+
' 2>/dev/null || true
|
|
893
979
|
echo " 已恢复 channels.qqbot 配置"
|
|
894
980
|
fi
|
|
895
981
|
echo ""
|
|
@@ -26,7 +26,7 @@ metadata: {"openclaw":{"emoji":"⏰","requires":{"config":["channels.qqbot"]}}}
|
|
|
26
26
|
| `time` | 时间(相对时间或 cron 表达式) | `"5m"` / `"1h30m"` / `"0 8 * * *"` |
|
|
27
27
|
| `jobId` | 任务 ID(仅 remove) | `"xxx"` |
|
|
28
28
|
|
|
29
|
-
**第二步**:`qqbot_remind` 会返回 `cronParams`,你必须**立即**将 `cronParams`
|
|
29
|
+
**第二步**:`qqbot_remind` 会返回 `cronParams`,你必须**立即**将 `cronParams` 的值**原样**作为参数调用 `cron` 工具。**禁止修改、省略或重组 `cronParams` 中的任何字段**(尤其是 `delivery` 对象中的 `accountId`)。
|
|
30
30
|
|
|
31
31
|
**第三步**:根据 `cron` 工具的返回结果,回复用户。
|
|
32
32
|
|
|
@@ -54,11 +54,13 @@ metadata: {"openclaw":{"emoji":"⏰","requires":{"config":["channels.qqbot"]}}}
|
|
|
54
54
|
| 字段 | 固定值 | 原因 |
|
|
55
55
|
|------|--------|------|
|
|
56
56
|
| `payload.kind` | `"agentTurn"` | `systemEvent` 不会发 QQ 消息 |
|
|
57
|
-
| `
|
|
58
|
-
| `
|
|
59
|
-
| `
|
|
57
|
+
| `delivery.mode` | `"announce"` | 投递模式 |
|
|
58
|
+
| `delivery.channel` | `"qqbot"` | QQ 通道标识 |
|
|
59
|
+
| `delivery.to` | 用户 openid | 从 `To` 字段获取 |
|
|
60
60
|
| `sessionTarget` | `"isolated"` | 隔离会话避免污染 |
|
|
61
61
|
|
|
62
|
+
> `delivery.accountId` 必须填写当前会话的账户 ID(如果已知),以确保多账户场景下消息通过正确的机器人账户发送。
|
|
63
|
+
|
|
62
64
|
> `schedule.atMs` 必须是**绝对毫秒时间戳**(如 `1770733800000`),不支持 `"5m"` 等相对字符串。
|
|
63
65
|
> 计算方式:`当前时间戳ms + 延迟毫秒`。
|
|
64
66
|
|
|
@@ -75,10 +77,13 @@ metadata: {"openclaw":{"emoji":"⏰","requires":{"config":["channels.qqbot"]}}}
|
|
|
75
77
|
"deleteAfterRun": true,
|
|
76
78
|
"payload": {
|
|
77
79
|
"kind": "agentTurn",
|
|
78
|
-
"message": "你是一个暖心的提醒助手。请用温暖、有趣的方式提醒用户:{提醒内容}。要求:(1) 不要回复HEARTBEAT_OK (2) 不要解释你是谁 (3) 直接输出一条暖心的提醒消息 (4) 可以加一句简短的鸡汤或关怀的话 (5) 控制在2-3句话以内 (6) 用emoji点缀"
|
|
79
|
-
|
|
80
|
+
"message": "你是一个暖心的提醒助手。请用温暖、有趣的方式提醒用户:{提醒内容}。要求:(1) 不要回复HEARTBEAT_OK (2) 不要解释你是谁 (3) 直接输出一条暖心的提醒消息 (4) 可以加一句简短的鸡汤或关怀的话 (5) 控制在2-3句话以内 (6) 用emoji点缀"
|
|
81
|
+
},
|
|
82
|
+
"delivery": {
|
|
83
|
+
"mode": "announce",
|
|
80
84
|
"channel": "qqbot",
|
|
81
|
-
"to": "{openid}"
|
|
85
|
+
"to": "{openid}",
|
|
86
|
+
"accountId": "{accountId}"
|
|
82
87
|
}
|
|
83
88
|
}
|
|
84
89
|
}
|
|
@@ -96,16 +101,19 @@ metadata: {"openclaw":{"emoji":"⏰","requires":{"config":["channels.qqbot"]}}}
|
|
|
96
101
|
"wakeMode": "now",
|
|
97
102
|
"payload": {
|
|
98
103
|
"kind": "agentTurn",
|
|
99
|
-
"message": "你是一个暖心的提醒助手。请用温暖、有趣的方式提醒用户:{提醒内容}。要求:(1) 不要回复HEARTBEAT_OK (2) 不要解释你是谁 (3) 直接输出一条暖心的提醒消息 (4) 可以加一句简短的鸡汤或关怀的话 (5) 控制在2-3句话以内 (6) 用emoji点缀"
|
|
100
|
-
|
|
104
|
+
"message": "你是一个暖心的提醒助手。请用温暖、有趣的方式提醒用户:{提醒内容}。要求:(1) 不要回复HEARTBEAT_OK (2) 不要解释你是谁 (3) 直接输出一条暖心的提醒消息 (4) 可以加一句简短的鸡汤或关怀的话 (5) 控制在2-3句话以内 (6) 用emoji点缀"
|
|
105
|
+
},
|
|
106
|
+
"delivery": {
|
|
107
|
+
"mode": "announce",
|
|
101
108
|
"channel": "qqbot",
|
|
102
|
-
"to": "{openid}"
|
|
109
|
+
"to": "{openid}",
|
|
110
|
+
"accountId": "{accountId}"
|
|
103
111
|
}
|
|
104
112
|
}
|
|
105
113
|
}
|
|
106
114
|
```
|
|
107
115
|
|
|
108
|
-
> 周期任务**不加** `deleteAfterRun`。群聊 `to` 格式为 `"group:{group_openid}"`。
|
|
116
|
+
> 周期任务**不加** `deleteAfterRun`。群聊 `to` 格式为 `"qqbot:group:{group_openid}"`。
|
|
109
117
|
|
|
110
118
|
---
|
|
111
119
|
|
|
@@ -147,3 +155,5 @@ metadata: {"openclaw":{"emoji":"⏰","requires":{"config":["channels.qqbot"]}}}
|
|
|
147
155
|
- 周期:`⏰ 收到,{周期}提醒你{内容}~`
|
|
148
156
|
- 查询无结果:`📋 目前没有提醒哦~ 说"5分钟后提醒我xxx"试试?`
|
|
149
157
|
- 删除成功:`✅ 已取消"{名称}"`
|
|
158
|
+
|
|
159
|
+
openclaw cron add \ --name "下班提醒" \ --at "2026-03-26T21:42:31+08:00" \ --message "test" \ --to …``
|
package/src/api.ts
CHANGED
|
@@ -137,7 +137,7 @@ async function doFetchToken(appId: string, clientSecret: string): Promise<string
|
|
|
137
137
|
const requestHeaders = { "Content-Type": "application/json", "User-Agent": PLUGIN_USER_AGENT };
|
|
138
138
|
|
|
139
139
|
// 打印请求信息(隐藏敏感信息)
|
|
140
|
-
console.log(`[qqbot-api:${appId}] >>> POST ${TOKEN_URL}`);
|
|
140
|
+
console.log(`[qqbot-api:${appId}] >>> POST ${TOKEN_URL} [secret: ${clientSecret.slice(0, 6)}...len=${clientSecret.length}]`);
|
|
141
141
|
|
|
142
142
|
let response: Response;
|
|
143
143
|
try {
|
package/src/config.ts
CHANGED
|
@@ -227,7 +227,7 @@ export function resolveQQBotAccount(
|
|
|
227
227
|
cfg: OpenClawConfig,
|
|
228
228
|
accountId?: string | null
|
|
229
229
|
): ResolvedQQBotAccount {
|
|
230
|
-
const resolvedAccountId = accountId ??
|
|
230
|
+
const resolvedAccountId = accountId ?? resolveDefaultQQBotAccountId(cfg);
|
|
231
231
|
const qqbot = cfg.channels?.qqbot as QQBotChannelConfig | undefined;
|
|
232
232
|
|
|
233
233
|
// 基础配置
|
package/src/gateway.ts
CHANGED
|
@@ -1333,7 +1333,7 @@ export async function startGateway(ctx: GatewayContext): Promise<void> {
|
|
|
1333
1333
|
|
|
1334
1334
|
// 使用 AsyncLocalStorage 建立请求级上下文,作用域内所有异步代码
|
|
1335
1335
|
// (包括 AI agent 调用、tool execute)都能安全获取当前会话信息,无并发竞态。
|
|
1336
|
-
await runWithRequestContext({ target: qualifiedTarget }, async () => {
|
|
1336
|
+
await runWithRequestContext({ target: qualifiedTarget, accountId: account.accountId }, async () => {
|
|
1337
1337
|
try {
|
|
1338
1338
|
const messagesConfig = pluginRuntime.channel.reply.resolveEffectiveMessagesConfig(cfg, route.agentId);
|
|
1339
1339
|
|
package/src/request-context.ts
CHANGED
|
@@ -11,6 +11,8 @@ import { AsyncLocalStorage } from "node:async_hooks";
|
|
|
11
11
|
export interface RequestContext {
|
|
12
12
|
/** 投递目标地址,如 qqbot:c2c:xxx 或 qqbot:group:xxx */
|
|
13
13
|
target: string;
|
|
14
|
+
/** 当前请求的 QQBot 账户 ID(多账户场景) */
|
|
15
|
+
accountId?: string;
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
const asyncLocalStorage = new AsyncLocalStorage<RequestContext>();
|
|
@@ -37,3 +39,11 @@ export function getRequestContext(): RequestContext | undefined {
|
|
|
37
39
|
export function getRequestTarget(): string | undefined {
|
|
38
40
|
return asyncLocalStorage.getStore()?.target;
|
|
39
41
|
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 获取当前请求的账户 ID。
|
|
45
|
+
* 便捷方法,等价于 getRequestContext()?.accountId。
|
|
46
|
+
*/
|
|
47
|
+
export function getRequestAccountId(): string | undefined {
|
|
48
|
+
return asyncLocalStorage.getStore()?.accountId;
|
|
49
|
+
}
|