@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.
@@ -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('fs');
349
- const cfg = JSON.parse(fs.readFileSync('$_STASH_CFG', 'utf8'));
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 = $_QQBOT_CHANNEL_STASH;
352
- fs.writeFileSync('$_STASH_CFG', JSON.stringify(cfg, null, 4) + '\n');
353
- " 2>/dev/null || true
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
- # 清理多余的 peerDependencies 传递依赖(兼容旧版 openclaw):
444
- # openclaw v2026.3.4 之前的 plugins install 缺少 --omit=peer,会把 peerDeps
445
- # (openclaw 平台及其 400+ 传递依赖)也安装到插件 node_modules 中。
446
- # 新版已修复,此处通过阈值判断:包数量 > 50 才触发清理,避免对新版做无用操作。
473
+ # ── 复制 bundledDependencies 到插件 node_modules ──
474
+ # 由于安装前移走了源码的 node_modules,extensions 中的插件目录没有依赖。
475
+ # 只需复制 bundledDependenciesws, 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
- [ -d "$_nm" ] && PLUGIN_NM="$_nm" && break
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 = $_QQBOT_CHANNEL_STASH;
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 + ':' + ch.clientSecret); }
598
- " 2>/dev/null || true)
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
- const fs = require('fs');
791
- const cfg = JSON.parse(fs.readFileSync('$_target_cfg', 'utf8'));
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 = '$_QQBOT_CHANNEL_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 = '$DESIRED_QQBOT_TOKEN';
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 = '$MARKDOWN_VALUE';
812
- if (md === 'true') cfg.channels.qqbot.markdownSupport = true;
813
- else if (md === 'false') cfg.channels.qqbot.markdownSupport = false;
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('$_target_cfg', JSON.stringify(cfg, null, 4) + '\n');
816
- " 2>/dev/null || true
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('fs');
888
- const cfg = JSON.parse(fs.readFileSync('$_STASH_CFG', 'utf8'));
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 = $_QQBOT_CHANNEL_STASH;
891
- fs.writeFileSync('$_STASH_CFG', JSON.stringify(cfg, null, 4) + '\n');
892
- " 2>/dev/null || true
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` 的值作为参数调用 `cron` 工具。
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
- | `payload.deliver` | `true` | 否则不投递 |
58
- | `payload.channel` | `"qqbot"` | QQ 通道标识 |
59
- | `payload.to` | 用户 openid | 从 `To` 字段获取 |
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
- "deliver": true,
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
- "deliver": true,
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 ?? DEFAULT_ACCOUNT_ID;
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
 
@@ -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
+ }