@tencent-connect/openclaw-qqbot 1.7.1 → 2.0.0
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 +206 -4
- package/README.zh.md +207 -4
- package/bin/qqbot-cli.js +5 -2
- package/dist/index.cjs +15549 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +890 -0
- package/index.ts +56 -18
- package/openclaw.plugin.json +19 -3
- package/package.json +14 -23
- package/preload.cjs +17 -14
- package/scripts/link-sdk-core.cjs +133 -50
- package/scripts/upgrade-via-npm.sh +400 -73
- package/scripts/upgrade-via-source.sh +126 -89
- package/skills/qqbot-channel/SKILL.md +34 -12
- package/src/adapter/contract.ts +69 -0
- package/src/adapter/gateway.ts +61 -0
- package/src/adapter/index.ts +15 -0
- package/src/adapter/lint.ts +144 -0
- package/src/adapter/media.ts +112 -0
- package/src/adapter/pairing.ts +99 -0
- package/src/adapter/resolve.ts +333 -0
- package/src/adapter/setup.ts +55 -0
- package/src/adapter/webhook.ts +248 -0
- package/src/adapter/workspace.ts +29 -0
- package/src/bot-instance.ts +60 -0
- package/src/channel.ts +202 -413
- package/src/commands/bot-approve.ts +143 -0
- package/src/commands/bot-clear-storage.ts +114 -0
- package/src/commands/bot-group-always.ts +62 -0
- package/src/commands/bot-help.ts +40 -0
- package/src/commands/bot-logs.ts +248 -0
- package/src/commands/bot-me.ts +18 -0
- package/src/commands/bot-pairing.ts +57 -0
- package/src/commands/bot-ping.ts +33 -0
- package/src/commands/bot-streaming.ts +55 -0
- package/src/commands/bot-upgrade.ts +56 -0
- package/src/commands/bot-version.ts +41 -0
- package/src/commands/config-util.ts +96 -0
- package/src/commands/index.ts +49 -0
- package/src/config.ts +59 -19
- package/src/dispatch/body-assembler.ts +356 -0
- package/src/dispatch/ctx-builder.ts +110 -0
- package/src/dispatch/dispatch.ts +352 -0
- package/src/dispatch/envelope-builder.ts +112 -0
- package/src/dispatch/index.ts +2 -0
- package/src/{approval-handler.ts → features/approval-handler.ts} +40 -94
- package/src/features/approval-utils.ts +41 -0
- package/src/features/credential-backup.ts +82 -0
- package/src/features/history-store.ts +19 -0
- package/src/features/msgid-cache.ts +55 -0
- package/src/features/onboarding.ts +38 -0
- package/src/{proactive.ts → features/proactive.ts} +68 -49
- package/src/features/ref-index-store.ts +282 -0
- package/src/{update-checker.ts → features/update-checker.ts} +15 -35
- package/src/gateway/event-handlers.ts +241 -0
- package/src/gateway/index.ts +3 -0
- package/src/gateway/lifecycle.ts +221 -0
- package/src/gateway/middleware-setup.ts +139 -0
- package/src/gateway/qqbot-gateway.ts +342 -0
- package/src/middleware/access-control.ts +130 -0
- package/src/middleware/attachment.ts +314 -0
- package/src/middleware/policy-injector.ts +66 -0
- package/src/openclaw-plugin-sdk.d.ts +81 -10
- package/src/outbound/cron-scheduler.ts +130 -0
- package/src/outbound/debounce.ts +102 -0
- package/src/outbound/deliver-pipeline.ts +235 -0
- package/src/outbound/image-size.ts +123 -0
- package/src/outbound/index.ts +3 -0
- package/src/outbound/local-file-router.ts +145 -0
- package/src/outbound/media-send.ts +377 -0
- package/src/outbound/outbound-service.ts +222 -0
- package/src/outbound/reply-limiter.ts +128 -0
- package/src/outbound/sanitize.ts +32 -0
- package/src/outbound/streaming-controller.ts +228 -0
- package/src/outbound/target.ts +63 -0
- package/src/outbound/tts-provider.ts +84 -0
- package/src/request-context.ts +9 -8
- package/src/runtime.ts +31 -6
- package/src/setup/account-key.ts +41 -0
- package/src/setup/finalize.ts +110 -0
- package/src/setup/login.ts +197 -0
- package/src/setup/surface.ts +40 -0
- package/src/tools/platform.ts +149 -0
- package/src/tools/remind.ts +1 -1
- package/src/types-augment.d.ts +54 -0
- package/src/types.ts +71 -8
- package/src/typings/openclaw-webhook-ingress.d.ts +66 -0
- package/src/utils/mention.ts +52 -0
- package/src/utils/pkg-version.ts +84 -47
- package/src/utils/platform.ts +31 -7
- package/src/utils/plugin-logger.ts +104 -0
- package/src/utils/ssrf-guard.ts +32 -2
- package/src/utils/stt.ts +137 -0
- package/src/utils/voice-text.ts +61 -0
- package/tsconfig.json +2 -1
- package/tsup.config.ts +57 -0
- package/dist/index.d.ts +0 -17
- package/dist/index.js +0 -26
- package/dist/src/admin-resolver.d.ts +0 -33
- package/dist/src/admin-resolver.js +0 -157
- package/dist/src/api.d.ts +0 -307
- package/dist/src/api.js +0 -910
- package/dist/src/approval-handler.d.ts +0 -47
- package/dist/src/approval-handler.js +0 -372
- package/dist/src/channel.d.ts +0 -29
- package/dist/src/channel.js +0 -528
- package/dist/src/config.d.ts +0 -56
- package/dist/src/config.js +0 -278
- package/dist/src/credential-backup.d.ts +0 -31
- package/dist/src/credential-backup.js +0 -66
- package/dist/src/deliver-debounce.d.ts +0 -74
- package/dist/src/deliver-debounce.js +0 -174
- package/dist/src/gateway.d.ts +0 -18
- package/dist/src/gateway.js +0 -2063
- package/dist/src/group-history.d.ts +0 -136
- package/dist/src/group-history.js +0 -226
- package/dist/src/image-server.d.ts +0 -87
- package/dist/src/image-server.js +0 -570
- package/dist/src/inbound-attachments.d.ts +0 -60
- package/dist/src/inbound-attachments.js +0 -248
- package/dist/src/known-users.d.ts +0 -100
- package/dist/src/known-users.js +0 -263
- package/dist/src/message-gating.d.ts +0 -53
- package/dist/src/message-gating.js +0 -107
- package/dist/src/message-queue.d.ts +0 -91
- package/dist/src/message-queue.js +0 -257
- package/dist/src/onboarding.d.ts +0 -10
- package/dist/src/onboarding.js +0 -203
- package/dist/src/outbound-deliver.d.ts +0 -48
- package/dist/src/outbound-deliver.js +0 -392
- package/dist/src/outbound.d.ts +0 -220
- package/dist/src/outbound.js +0 -975
- package/dist/src/proactive.d.ts +0 -170
- package/dist/src/proactive.js +0 -399
- package/dist/src/ref-index-store.d.ts +0 -101
- package/dist/src/ref-index-store.js +0 -298
- package/dist/src/reply-dispatcher.d.ts +0 -35
- package/dist/src/reply-dispatcher.js +0 -311
- package/dist/src/request-context.d.ts +0 -25
- package/dist/src/request-context.js +0 -37
- package/dist/src/runtime.d.ts +0 -3
- package/dist/src/runtime.js +0 -13
- 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 -82
- package/dist/src/slash-commands.js +0 -2117
- package/dist/src/startup-greeting.d.ts +0 -30
- package/dist/src/startup-greeting.js +0 -97
- package/dist/src/streaming.d.ts +0 -247
- package/dist/src/streaming.js +0 -899
- package/dist/src/stt.d.ts +0 -21
- package/dist/src/stt.js +0 -70
- package/dist/src/tools/channel.d.ts +0 -16
- package/dist/src/tools/channel.js +0 -234
- package/dist/src/tools/remind.d.ts +0 -2
- package/dist/src/tools/remind.js +0 -256
- package/dist/src/types.d.ts +0 -443
- package/dist/src/types.js +0 -22
- package/dist/src/typing-keepalive.d.ts +0 -27
- package/dist/src/typing-keepalive.js +0 -64
- package/dist/src/update-checker.d.ts +0 -36
- package/dist/src/update-checker.js +0 -171
- package/dist/src/utils/audio-convert.d.ts +0 -98
- package/dist/src/utils/audio-convert.js +0 -755
- package/dist/src/utils/chunked-upload.d.ts +0 -68
- package/dist/src/utils/chunked-upload.js +0 -341
- package/dist/src/utils/file-utils.d.ts +0 -61
- package/dist/src/utils/file-utils.js +0 -172
- package/dist/src/utils/image-size.d.ts +0 -51
- package/dist/src/utils/image-size.js +0 -234
- package/dist/src/utils/media-send.d.ts +0 -158
- package/dist/src/utils/media-send.js +0 -502
- package/dist/src/utils/media-tags.d.ts +0 -14
- package/dist/src/utils/media-tags.js +0 -165
- package/dist/src/utils/payload.d.ts +0 -112
- package/dist/src/utils/payload.js +0 -186
- package/dist/src/utils/pkg-version.d.ts +0 -5
- package/dist/src/utils/pkg-version.js +0 -61
- package/dist/src/utils/platform.d.ts +0 -137
- package/dist/src/utils/platform.js +0 -390
- package/dist/src/utils/ssrf-guard.d.ts +0 -25
- package/dist/src/utils/ssrf-guard.js +0 -91
- package/dist/src/utils/text-parsing.d.ts +0 -31
- package/dist/src/utils/text-parsing.js +0 -74
- package/dist/src/utils/upload-cache.d.ts +0 -34
- package/dist/src/utils/upload-cache.js +0 -93
- package/node_modules/@eshaz/web-worker/LICENSE +0 -201
- package/node_modules/@eshaz/web-worker/README.md +0 -134
- package/node_modules/@eshaz/web-worker/browser.js +0 -17
- package/node_modules/@eshaz/web-worker/cjs/browser.js +0 -16
- package/node_modules/@eshaz/web-worker/cjs/node.js +0 -219
- package/node_modules/@eshaz/web-worker/index.d.ts +0 -4
- package/node_modules/@eshaz/web-worker/node.js +0 -223
- package/node_modules/@eshaz/web-worker/package.json +0 -54
- package/node_modules/@wasm-audio-decoders/common/index.js +0 -5
- package/node_modules/@wasm-audio-decoders/common/package.json +0 -36
- package/node_modules/@wasm-audio-decoders/common/src/WASMAudioDecoderCommon.js +0 -231
- package/node_modules/@wasm-audio-decoders/common/src/WASMAudioDecoderWorker.js +0 -129
- package/node_modules/@wasm-audio-decoders/common/src/puff/README +0 -67
- package/node_modules/@wasm-audio-decoders/common/src/puff/build_puff.js +0 -31
- package/node_modules/@wasm-audio-decoders/common/src/puff/puff.c +0 -863
- package/node_modules/@wasm-audio-decoders/common/src/puff/puff.h +0 -35
- package/node_modules/@wasm-audio-decoders/common/src/utilities.js +0 -3
- package/node_modules/@wasm-audio-decoders/common/types.d.ts +0 -7
- package/node_modules/mpg123-decoder/README.md +0 -265
- package/node_modules/mpg123-decoder/dist/mpg123-decoder.min.js +0 -185
- package/node_modules/mpg123-decoder/dist/mpg123-decoder.min.js.map +0 -1
- package/node_modules/mpg123-decoder/index.js +0 -8
- package/node_modules/mpg123-decoder/package.json +0 -58
- package/node_modules/mpg123-decoder/src/EmscriptenWasm.js +0 -464
- package/node_modules/mpg123-decoder/src/MPEGDecoder.js +0 -200
- package/node_modules/mpg123-decoder/src/MPEGDecoderWebWorker.js +0 -21
- package/node_modules/mpg123-decoder/types.d.ts +0 -30
- package/node_modules/silk-wasm/LICENSE +0 -21
- package/node_modules/silk-wasm/README.md +0 -85
- package/node_modules/silk-wasm/lib/index.cjs +0 -16
- package/node_modules/silk-wasm/lib/index.d.ts +0 -70
- package/node_modules/silk-wasm/lib/index.mjs +0 -16
- package/node_modules/silk-wasm/lib/silk.wasm +0 -0
- package/node_modules/silk-wasm/lib/utils.d.ts +0 -4
- package/node_modules/silk-wasm/package.json +0 -39
- package/node_modules/simple-yenc/.github/FUNDING.yml +0 -1
- package/node_modules/simple-yenc/.prettierignore +0 -1
- package/node_modules/simple-yenc/LICENSE +0 -7
- package/node_modules/simple-yenc/README.md +0 -163
- package/node_modules/simple-yenc/dist/esm.js +0 -1
- package/node_modules/simple-yenc/dist/index.js +0 -1
- package/node_modules/simple-yenc/package.json +0 -50
- package/node_modules/simple-yenc/rollup.config.js +0 -27
- package/node_modules/simple-yenc/src/simple-yenc.js +0 -302
- package/node_modules/ws/LICENSE +0 -20
- package/node_modules/ws/README.md +0 -548
- package/node_modules/ws/browser.js +0 -8
- package/node_modules/ws/index.js +0 -13
- package/node_modules/ws/lib/buffer-util.js +0 -131
- package/node_modules/ws/lib/constants.js +0 -19
- package/node_modules/ws/lib/event-target.js +0 -292
- package/node_modules/ws/lib/extension.js +0 -203
- package/node_modules/ws/lib/limiter.js +0 -55
- package/node_modules/ws/lib/permessage-deflate.js +0 -528
- package/node_modules/ws/lib/receiver.js +0 -706
- package/node_modules/ws/lib/sender.js +0 -602
- package/node_modules/ws/lib/stream.js +0 -161
- package/node_modules/ws/lib/subprotocol.js +0 -62
- package/node_modules/ws/lib/validation.js +0 -152
- package/node_modules/ws/lib/websocket-server.js +0 -554
- package/node_modules/ws/lib/websocket.js +0 -1393
- package/node_modules/ws/package.json +0 -69
- package/node_modules/ws/wrapper.mjs +0 -8
- package/scripts/postinstall-link-sdk.js +0 -126
- package/skills/qqbot-media/SKILL.md +0 -60
- package/src/admin-resolver.ts +0 -181
- package/src/api.ts +0 -1319
- package/src/credential-backup.ts +0 -72
- package/src/deliver-debounce.ts +0 -229
- package/src/gateway.ts +0 -2304
- package/src/group-history.ts +0 -328
- package/src/image-server.ts +0 -675
- package/src/inbound-attachments.ts +0 -321
- package/src/known-users.ts +0 -353
- package/src/message-gating.ts +0 -190
- package/src/message-queue.ts +0 -354
- package/src/onboarding.ts +0 -274
- package/src/outbound-deliver.ts +0 -486
- package/src/outbound.ts +0 -1179
- package/src/ref-index-store.ts +0 -412
- package/src/reply-dispatcher.ts +0 -334
- package/src/session-store.ts +0 -303
- package/src/slash-commands.ts +0 -2297
- package/src/startup-greeting.ts +0 -120
- package/src/streaming.ts +0 -1077
- package/src/stt.ts +0 -86
- package/src/tools/channel.ts +0 -281
- package/src/typing-keepalive.ts +0 -59
- package/src/utils/audio-convert.ts +0 -859
- package/src/utils/chunked-upload.ts +0 -483
- package/src/utils/file-utils.ts +0 -193
- package/src/utils/image-size.ts +0 -266
- package/src/utils/media-send.ts +0 -636
- package/src/utils/media-tags.ts +0 -183
- package/src/utils/payload.ts +0 -265
- package/src/utils/text-parsing.ts +0 -85
- package/src/utils/upload-cache.ts +0 -128
|
@@ -252,10 +252,53 @@ for _app in openclaw clawdbot moltbot; do
|
|
|
252
252
|
fi
|
|
253
253
|
done
|
|
254
254
|
|
|
255
|
+
# ── 安全网:注册 trap 确保脚本退出时恢复暂存的 channels.qqbot 配置 ──
|
|
256
|
+
_restore_stash_on_exit() {
|
|
257
|
+
if [ -n "$_QQBOT_CHANNEL_STASH" ] && [ -n "$_STASH_CFG" ] && [ -f "$_STASH_CFG" ]; then
|
|
258
|
+
# 检查配置是否已恢复(避免重复写入)
|
|
259
|
+
local _already_restored
|
|
260
|
+
_already_restored=$(node -e "
|
|
261
|
+
const fs = require('fs');
|
|
262
|
+
const cfg = JSON.parse(fs.readFileSync('$_STASH_CFG', 'utf8'));
|
|
263
|
+
if (cfg.channels && cfg.channels.qqbot) process.stdout.write('yes');
|
|
264
|
+
else process.stdout.write('no');
|
|
265
|
+
" 2>/dev/null || echo "no")
|
|
266
|
+
if [ "$_already_restored" = "no" ]; then
|
|
267
|
+
_STASH="$_QQBOT_CHANNEL_STASH" _CFG="$_STASH_CFG" node -e '
|
|
268
|
+
const fs = require("fs");
|
|
269
|
+
const cfg = JSON.parse(fs.readFileSync(process.env._CFG, "utf8"));
|
|
270
|
+
if (!cfg.channels) cfg.channels = {};
|
|
271
|
+
cfg.channels.qqbot = JSON.parse(process.env._STASH);
|
|
272
|
+
fs.writeFileSync(process.env._CFG, JSON.stringify(cfg, null, 4) + "\n");
|
|
273
|
+
' 2>/dev/null || true
|
|
274
|
+
echo ""
|
|
275
|
+
echo " ⚠️ [trap] 已恢复暂存的 channels.qqbot 配置(脚本提前退出)"
|
|
276
|
+
fi
|
|
277
|
+
fi
|
|
278
|
+
}
|
|
279
|
+
trap _restore_stash_on_exit EXIT
|
|
280
|
+
|
|
255
281
|
# 安装前先 stop gateway,防止 chokidar 在 plugins install 写入配置的中间状态
|
|
256
282
|
# 触发 restart,导致 "unknown channel id: qqbot" 等错误
|
|
283
|
+
|
|
284
|
+
# 动态读取 gateway 端口(从 openclaw.json 配置中获取,默认 18789)
|
|
285
|
+
_GW_PORT=$(node -e "
|
|
286
|
+
const fs = require('fs');
|
|
287
|
+
const path = require('path');
|
|
288
|
+
for (const app of ['openclaw', 'clawdbot', 'moltbot']) {
|
|
289
|
+
const f = path.join(process.env.HOME, '.' + app, app + '.json');
|
|
290
|
+
if (!fs.existsSync(f)) continue;
|
|
291
|
+
const cfg = JSON.parse(fs.readFileSync(f, 'utf8'));
|
|
292
|
+
if (cfg.gateway && cfg.gateway.port) {
|
|
293
|
+
process.stdout.write(String(cfg.gateway.port));
|
|
294
|
+
process.exit(0);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
process.stdout.write('18789');
|
|
298
|
+
" 2>/dev/null || echo "18789")
|
|
299
|
+
|
|
257
300
|
_gw_was_running=0
|
|
258
|
-
if lsof -i :
|
|
301
|
+
if lsof -i :"$_GW_PORT" -sTCP:LISTEN >/dev/null 2>&1; then
|
|
259
302
|
_gw_was_running=1
|
|
260
303
|
echo " 暂停 gateway 服务(避免安装过程中中间状态 restart)..."
|
|
261
304
|
openclaw gateway stop 2>/dev/null || true
|
|
@@ -265,6 +308,10 @@ fi
|
|
|
265
308
|
# 清理之前可能残留的 staging 目录(extensions 和 /tmp 中都可能存在)
|
|
266
309
|
find "$HOME/.openclaw/extensions/" -maxdepth 1 -name ".openclaw-install-stage-*" -exec rm -rf {} + 2>/dev/null || true
|
|
267
310
|
find "${TMPDIR:-/tmp}" -maxdepth 1 -name ".openclaw-install-stage-*" -exec rm -rf {} + 2>/dev/null || true
|
|
311
|
+
# 清理可能残留的 node_modules 临时备份(上次脚本中断时留下的)
|
|
312
|
+
find "${TMPDIR:-/tmp}" -maxdepth 1 -name ".openclaw-qqbot-nm-bak-*" -exec rm -rf {} + 2>/dev/null || true
|
|
313
|
+
# 清理旧版脚本遗留的项目内备份
|
|
314
|
+
[ -d "$PROJ_DIR/.node_modules_install_bak" ] && mv "$PROJ_DIR/.node_modules_install_bak" "$PROJ_DIR/node_modules" 2>/dev/null || true
|
|
268
315
|
|
|
269
316
|
# ── 清空并重新构建 dist/ ──
|
|
270
317
|
# openclaw plugins install . 只做文件复制,不执行 npm lifecycle scripts。
|
|
@@ -272,14 +319,14 @@ find "${TMPDIR:-/tmp}" -maxdepth 1 -name ".openclaw-install-stage-*" -exec rm -r
|
|
|
272
319
|
# 导致新增的模块(如 streaming.js)缺失。
|
|
273
320
|
echo " 清空 dist/ 确保全量重新构建..."
|
|
274
321
|
rm -rf "$PROJ_DIR/dist"
|
|
275
|
-
if [ ! -f "$PROJ_DIR/dist/index.js" ]; then
|
|
322
|
+
if [ ! -f "$PROJ_DIR/dist/index.cjs" ] && [ ! -f "$PROJ_DIR/dist/index.js" ]; then
|
|
276
323
|
echo " dist/ 不存在,先执行构建..."
|
|
277
324
|
if [ ! -d "$PROJ_DIR/node_modules" ]; then
|
|
278
325
|
npm install --prefix "$PROJ_DIR" 2>&1 || true
|
|
279
326
|
fi
|
|
280
327
|
npm run --prefix "$PROJ_DIR" build 2>&1 || true
|
|
281
|
-
if [ ! -f "$PROJ_DIR/dist/index.js" ]; then
|
|
282
|
-
echo " ❌ 构建失败:dist/index.
|
|
328
|
+
if [ ! -f "$PROJ_DIR/dist/index.cjs" ] && [ ! -f "$PROJ_DIR/dist/index.js" ]; then
|
|
329
|
+
echo " ❌ 构建失败:dist/index.cjs 未生成"
|
|
283
330
|
echo " 请手动执行: cd $PROJ_DIR && npm install && npm run build"
|
|
284
331
|
exit 1
|
|
285
332
|
fi
|
|
@@ -296,21 +343,24 @@ _INSTALL_DIR="$HOME/.openclaw/extensions/openclaw-qqbot"
|
|
|
296
343
|
# openclaw plugins install . 会把整个项目目录(含 node_modules/)复制到 extensions,
|
|
297
344
|
# 但运行时只需要 bundledDependencies 中的 3 个包 + openclaw symlink。
|
|
298
345
|
# 移走 node_modules 可避免复制数百个不必要的包,大幅加速安装。
|
|
346
|
+
# 同时临时移走 devDependencies 声明,避免 manifest dependency scan 递归解析
|
|
347
|
+
# 超出 10000 目录上限导致 "code safety scan failed"。
|
|
299
348
|
_NM_BACKUP=""
|
|
300
349
|
if [ -d "$PROJ_DIR/node_modules" ]; then
|
|
301
|
-
echo " 临时移走 node_modules
|
|
302
|
-
|
|
350
|
+
echo " 临时移走 node_modules(避免安全扫描超出目录上限)..."
|
|
351
|
+
# 备份到项目目录外部(/tmp),否则安全扫描仍会递归遍历到重命名后的目录
|
|
352
|
+
_NM_BACKUP="${TMPDIR:-/tmp}/.openclaw-qqbot-nm-bak-$$"
|
|
303
353
|
mv "$PROJ_DIR/node_modules" "$_NM_BACKUP"
|
|
304
354
|
fi
|
|
305
355
|
|
|
306
|
-
openclaw plugins install . 2>&1 | tee "$INSTALL_LOG" || true
|
|
356
|
+
openclaw plugins install . --dangerously-force-unsafe-install 2>&1 | tee "$INSTALL_LOG" || true
|
|
307
357
|
|
|
308
358
|
# ── 恢复 node_modules ──
|
|
309
359
|
if [ -n "$_NM_BACKUP" ] && [ -d "$_NM_BACKUP" ]; then
|
|
310
360
|
mv "$_NM_BACKUP" "$PROJ_DIR/node_modules"
|
|
311
361
|
echo " 已恢复源码目录 node_modules"
|
|
312
362
|
fi
|
|
313
|
-
if [ ! -f "$_INSTALL_DIR/dist/index.js" ] || [ ! -f "$_INSTALL_DIR/preload.cjs" ]; then
|
|
363
|
+
if { [ ! -f "$_INSTALL_DIR/dist/index.cjs" ] && [ ! -f "$_INSTALL_DIR/dist/index.js" ]; } || [ ! -f "$_INSTALL_DIR/preload.cjs" ]; then
|
|
314
364
|
echo ""
|
|
315
365
|
echo "❌ 插件安装失败!"
|
|
316
366
|
echo "========================================="
|
|
@@ -418,13 +468,13 @@ else
|
|
|
418
468
|
echo " 尝试自动修复: 清理残留并重试安装..."
|
|
419
469
|
find "$HOME/.openclaw/extensions/" -maxdepth 1 -name ".openclaw-install-stage-*" -exec rm -rf {} + 2>/dev/null || true
|
|
420
470
|
find "${TMPDIR:-/tmp}" -maxdepth 1 -name ".openclaw-install-stage-*" -exec rm -rf {} + 2>/dev/null || true
|
|
421
|
-
# 重试时同样移走 node_modules
|
|
471
|
+
# 重试时同样移走 node_modules 避免安全扫描超出目录上限
|
|
422
472
|
_NM_BACKUP=""
|
|
423
473
|
if [ -d "$PROJ_DIR/node_modules" ]; then
|
|
424
|
-
_NM_BACKUP="$
|
|
474
|
+
_NM_BACKUP="${TMPDIR:-/tmp}/.openclaw-qqbot-nm-bak-$$"
|
|
425
475
|
mv "$PROJ_DIR/node_modules" "$_NM_BACKUP"
|
|
426
476
|
fi
|
|
427
|
-
if openclaw plugins install . 2>&1 | tee -a "$INSTALL_LOG"; then
|
|
477
|
+
if openclaw plugins install . --dangerously-force-unsafe-install 2>&1 | tee -a "$INSTALL_LOG"; then
|
|
428
478
|
for _candidate_name in openclaw-qqbot qqbot openclaw-qq; do
|
|
429
479
|
if [ -d "$HOME/.openclaw/extensions/$_candidate_name" ]; then
|
|
430
480
|
_plugin_dir_ok=1
|
|
@@ -481,16 +531,23 @@ else
|
|
|
481
531
|
done
|
|
482
532
|
if [ -n "$PLUGIN_NM" ] && [ -d "$PROJ_DIR/node_modules" ]; then
|
|
483
533
|
# 如果 extensions 中已有大量包(旧版 openclaw 安装的 peerDeps),先清理
|
|
534
|
+
# 但保留 openclaw symlink
|
|
484
535
|
if [ -d "$PLUGIN_NM" ]; then
|
|
485
|
-
_before=$(ls -d "$PLUGIN_NM"/*/ "$PLUGIN_NM"/@*/*/ 2>/dev/null | wc -l | tr -d ' ')
|
|
536
|
+
_before=$({ ls -d "$PLUGIN_NM"/*/ "$PLUGIN_NM"/@*/*/ 2>/dev/null || true; } | wc -l | tr -d ' ')
|
|
486
537
|
if [ "$_before" -gt 50 ]; then
|
|
487
538
|
echo ""
|
|
488
539
|
echo "检测到 ${_before} 个包(超过阈值 50),清理多余的 peerDep 传递依赖..."
|
|
540
|
+
_has_openclaw_link=""
|
|
541
|
+
[ -L "$PLUGIN_NM/openclaw" ] && _has_openclaw_link="$(readlink "$PLUGIN_NM/openclaw")"
|
|
489
542
|
rm -rf "$PLUGIN_NM"
|
|
543
|
+
if [ -n "$_has_openclaw_link" ]; then
|
|
544
|
+
mkdir -p "$PLUGIN_NM"
|
|
545
|
+
ln -sf "$_has_openclaw_link" "$PLUGIN_NM/openclaw"
|
|
546
|
+
fi
|
|
490
547
|
fi
|
|
491
548
|
fi
|
|
492
549
|
|
|
493
|
-
# 读取 bundledDependencies
|
|
550
|
+
# 读取 bundledDependencies 及其传递依赖列表,复制到插件目录
|
|
494
551
|
_deps_to_copy=$(node -e "
|
|
495
552
|
const fs = require('fs');
|
|
496
553
|
const path = require('path');
|
|
@@ -514,23 +571,34 @@ else
|
|
|
514
571
|
if [ -n "$_deps_to_copy" ]; then
|
|
515
572
|
mkdir -p "$PLUGIN_NM"
|
|
516
573
|
_copied=0
|
|
517
|
-
|
|
574
|
+
while IFS= read -r _dep; do
|
|
575
|
+
[ -z "$_dep" ] && continue
|
|
518
576
|
_src="$PROJ_DIR/node_modules/$_dep"
|
|
519
577
|
_dst="$PLUGIN_NM/$_dep"
|
|
520
|
-
if [ -d "$_src" ]
|
|
521
|
-
# 处理 scoped 包(如 @scope/pkg)
|
|
578
|
+
if [ -d "$_src" ]; then
|
|
522
579
|
mkdir -p "$(dirname "$_dst")"
|
|
580
|
+
rm -rf "$_dst"
|
|
523
581
|
cp -r "$_src" "$_dst"
|
|
582
|
+
_copied=$((_copied + 1))
|
|
524
583
|
fi
|
|
525
|
-
done
|
|
526
|
-
|
|
527
|
-
echo " ✅ 已复制 bundled 依赖到插件目录(${_after} 个包)"
|
|
584
|
+
done <<< "$_deps_to_copy"
|
|
585
|
+
echo " ✅ 已复制 ${_copied} 个 bundled 依赖到插件目录"
|
|
528
586
|
else
|
|
529
587
|
echo " ⚠️ 未读取到 bundledDependencies,跳过依赖复制"
|
|
588
|
+
echo " 回退: 使用 npm install --omit=dev 安装运行时依赖..."
|
|
589
|
+
npm install --omit=dev --prefix "$_ext_dir" 2>&1 || true
|
|
590
|
+
fi
|
|
591
|
+
# 验证关键依赖是否就绪
|
|
592
|
+
if [ ! -d "$PLUGIN_NM/ws" ]; then
|
|
593
|
+
echo " ⚠️ ws 包仍缺失,回退: 使用 npm install --omit=dev 安装..."
|
|
594
|
+
npm install --omit=dev --prefix "$_ext_dir" 2>&1 || true
|
|
530
595
|
fi
|
|
596
|
+
elif [ -n "$PLUGIN_NM" ] && [ ! -d "$PROJ_DIR/node_modules" ]; then
|
|
597
|
+
echo " ⚠️ 源码 node_modules 不存在,使用 npm install --omit=dev 安装运行时依赖..."
|
|
598
|
+
npm install --omit=dev --prefix "$_ext_dir" 2>&1 || true
|
|
531
599
|
elif [ -n "$PLUGIN_NM" ] && [ -d "$PLUGIN_NM" ]; then
|
|
532
600
|
# node_modules 已存在(可能是旧版 openclaw 安装的),检查是否需要清理
|
|
533
|
-
_before=$(ls -d "$PLUGIN_NM"/*/ "$PLUGIN_NM"/@*/*/ 2>/dev/null | wc -l | tr -d ' ')
|
|
601
|
+
_before=$({ ls -d "$PLUGIN_NM"/*/ "$PLUGIN_NM"/@*/*/ 2>/dev/null || true; } | wc -l | tr -d ' ')
|
|
534
602
|
if [ "$_before" -gt 50 ]; then
|
|
535
603
|
echo ""
|
|
536
604
|
echo "检测到 ${_before} 个包(超过阈值 50),清理多余的 peerDep 传递依赖..."
|
|
@@ -576,7 +644,7 @@ else
|
|
|
576
644
|
rm -rf "$PLUGIN_NM/$_pkg"
|
|
577
645
|
done
|
|
578
646
|
find "$PLUGIN_NM" -maxdepth 1 -type d -name '@*' -empty -delete 2>/dev/null || true
|
|
579
|
-
_after=$(ls -d "$PLUGIN_NM"/*/ "$PLUGIN_NM"/@*/*/ 2>/dev/null | wc -l | tr -d ' ')
|
|
647
|
+
_after=$({ ls -d "$PLUGIN_NM"/*/ "$PLUGIN_NM"/@*/*/ 2>/dev/null || true; } | wc -l | tr -d ' ')
|
|
580
648
|
echo " 已清理: ${_before} → ${_after} 个包"
|
|
581
649
|
fi
|
|
582
650
|
else
|
|
@@ -609,54 +677,6 @@ else
|
|
|
609
677
|
find "$HOME/.openclaw/extensions/" -maxdepth 1 -name ".openclaw-qqbot-backup-*" -exec rm -rf {} + 2>/dev/null || true
|
|
610
678
|
find "${TMPDIR:-/tmp}" -maxdepth 1 -name ".qqbot-upgrade-backup-*" -exec rm -rf {} + 2>/dev/null || true
|
|
611
679
|
|
|
612
|
-
# 恢复 channels.qqbot 完整配置(防止 plugins install 意外覆盖)
|
|
613
|
-
# 策略:直接用备份完整覆盖回去,确保 groups / env / prompts / allowFrom 等所有用户配置不丢失。
|
|
614
|
-
if [ -n "$SAVED_QQBOT_CHANNEL_JSON" ]; then
|
|
615
|
-
node -e "
|
|
616
|
-
const fs = require('fs');
|
|
617
|
-
const path = require('path');
|
|
618
|
-
const saved = JSON.parse(process.argv[1]);
|
|
619
|
-
for (const app of ['openclaw', 'clawdbot', 'moltbot']) {
|
|
620
|
-
const f = path.join(process.env.HOME, '.' + app, app + '.json');
|
|
621
|
-
if (!fs.existsSync(f)) continue;
|
|
622
|
-
const cfg = JSON.parse(fs.readFileSync(f, 'utf8'));
|
|
623
|
-
if (!cfg.channels) { cfg.channels = {}; }
|
|
624
|
-
if (JSON.stringify(cfg.channels.qqbot) === JSON.stringify(saved)) {
|
|
625
|
-
process.stderr.write(' channels.qqbot 配置无变化,无需恢复\\n');
|
|
626
|
-
} else {
|
|
627
|
-
cfg.channels.qqbot = saved;
|
|
628
|
-
fs.writeFileSync(f, JSON.stringify(cfg, null, 4) + '\\n');
|
|
629
|
-
process.stderr.write(' 已完整恢复 channels.qqbot 配置(' + Object.keys(saved).join(', ') + ')\\n');
|
|
630
|
-
}
|
|
631
|
-
break;
|
|
632
|
-
}
|
|
633
|
-
" "$SAVED_QQBOT_CHANNEL_JSON" 2>&1 || true
|
|
634
|
-
fi
|
|
635
|
-
|
|
636
|
-
# 恢复 channels.qqbot 完整配置(防止 plugins install 意外覆盖)
|
|
637
|
-
# 策略:直接用备份完整覆盖回去,确保 groups / env / prompts / allowFrom 等所有用户配置不丢失。
|
|
638
|
-
if [ -n "$SAVED_QQBOT_CHANNEL_JSON" ]; then
|
|
639
|
-
node -e "
|
|
640
|
-
const fs = require('fs');
|
|
641
|
-
const path = require('path');
|
|
642
|
-
const saved = JSON.parse(process.argv[1]);
|
|
643
|
-
for (const app of ['openclaw', 'clawdbot', 'moltbot']) {
|
|
644
|
-
const f = path.join(process.env.HOME, '.' + app, app + '.json');
|
|
645
|
-
if (!fs.existsSync(f)) continue;
|
|
646
|
-
const cfg = JSON.parse(fs.readFileSync(f, 'utf8'));
|
|
647
|
-
if (!cfg.channels) { cfg.channels = {}; }
|
|
648
|
-
if (JSON.stringify(cfg.channels.qqbot) === JSON.stringify(saved)) {
|
|
649
|
-
process.stderr.write(' channels.qqbot 配置无变化,无需恢复\\n');
|
|
650
|
-
} else {
|
|
651
|
-
cfg.channels.qqbot = saved;
|
|
652
|
-
fs.writeFileSync(f, JSON.stringify(cfg, null, 4) + '\\n');
|
|
653
|
-
process.stderr.write(' 已完整恢复 channels.qqbot 配置(' + Object.keys(saved).join(', ') + ')\\n');
|
|
654
|
-
}
|
|
655
|
-
break;
|
|
656
|
-
}
|
|
657
|
-
" "$SAVED_QQBOT_CHANNEL_JSON" 2>&1 || true
|
|
658
|
-
fi
|
|
659
|
-
|
|
660
680
|
# 记录更新后的 qqbot 插件版本
|
|
661
681
|
NEW_QQBOT_VERSION=$(node -e '
|
|
662
682
|
try {
|
|
@@ -821,12 +841,17 @@ case "$start_choice" in
|
|
|
821
841
|
_start_output=$(_openclaw_with_timeout 30 openclaw gateway start)
|
|
822
842
|
echo "$_start_output"
|
|
823
843
|
|
|
824
|
-
|
|
844
|
+
# 判断是否真正启动失败:
|
|
845
|
+
# 1. 过滤掉 Config warnings 区块中的 "plugin not installed"(其他插件的警告,非错误)
|
|
846
|
+
# 2. 过滤掉 "not loaded; re-bootstrapped"(自动恢复的正常行为)
|
|
847
|
+
# 3. 只检查 gateway 自身的启动/加载状态
|
|
848
|
+
_start_output_filtered=$(echo "$_start_output" | grep -v "^│" | grep -vi "re-bootstrapped\|re-bootstrap")
|
|
849
|
+
if echo "$_start_output_filtered" | grep -qi "not loaded\|not found\|not installed\|error\|fail"; then
|
|
825
850
|
echo ""
|
|
826
851
|
echo "⚠️ 启动异常,尝试 restart 恢复..."
|
|
827
852
|
_restart_output=$(_openclaw_with_timeout 30 openclaw gateway restart)
|
|
828
853
|
echo "$_restart_output"
|
|
829
|
-
if echo "$_restart_output" | grep -qi "not loaded\|not found\|not installed"; then
|
|
854
|
+
if echo "$_restart_output" | grep -v "^│" | grep -vi "re-bootstrapped\|re-bootstrap" | grep -qi "not loaded\|not found\|not installed"; then
|
|
830
855
|
echo ""
|
|
831
856
|
echo "⚠️ 自动恢复失败,请手动执行:"
|
|
832
857
|
echo " openclaw gateway install && openclaw gateway start"
|
|
@@ -844,11 +869,11 @@ case "$start_choice" in
|
|
|
844
869
|
echo "========================================="
|
|
845
870
|
_port_ready=0
|
|
846
871
|
for i in $(seq 1 30); do
|
|
847
|
-
if lsof -i :
|
|
872
|
+
if lsof -i :"$_GW_PORT" -sTCP:LISTEN >/dev/null 2>&1; then
|
|
848
873
|
_port_ready=1
|
|
849
874
|
break
|
|
850
875
|
fi
|
|
851
|
-
printf "\r 等待端口
|
|
876
|
+
printf "\r 等待端口 %s 就绪... (%d/30)" "$_GW_PORT" "$i"
|
|
852
877
|
sleep 2
|
|
853
878
|
done
|
|
854
879
|
echo ""
|
|
@@ -862,7 +887,7 @@ case "$start_choice" in
|
|
|
862
887
|
echo "doctor 修复后重试 gateway restart..."
|
|
863
888
|
_openclaw_with_timeout 30 openclaw gateway restart
|
|
864
889
|
sleep 5
|
|
865
|
-
if lsof -i :
|
|
890
|
+
if lsof -i :"$_GW_PORT" -sTCP:LISTEN >/dev/null 2>&1; then
|
|
866
891
|
echo "✅ doctor --fix 后 gateway 启动成功"
|
|
867
892
|
_port_ready=1
|
|
868
893
|
else
|
|
@@ -926,24 +951,35 @@ case "$start_choice" in
|
|
|
926
951
|
' 2>&1 || echo " ⚠️ 配置写入失败"
|
|
927
952
|
echo " ✅ 已恢复 channels.qqbot 配置(含 token/markdown)"
|
|
928
953
|
_need_reload=1
|
|
954
|
+
# 清空暂存变量,防止 EXIT trap 重复恢复
|
|
955
|
+
_QQBOT_CHANNEL_STASH=""
|
|
929
956
|
fi
|
|
930
957
|
|
|
931
|
-
#
|
|
958
|
+
# 配置写回后让 gateway 重载配置使其生效
|
|
959
|
+
# gateway 有 chokidar 文件监听,写入 openclaw.json 后通常会自动 reload。
|
|
960
|
+
# 这里发送 SIGHUP 通知进程立即重载,无需完整 restart(避免冷启动 10-20s)。
|
|
932
961
|
if [ "$_need_reload" -eq 1 ]; then
|
|
933
|
-
echo " 重载配置..."
|
|
934
|
-
sleep
|
|
935
|
-
#
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
962
|
+
echo " 通知 gateway 重载配置..."
|
|
963
|
+
sleep 2
|
|
964
|
+
# 尝试用 SIGHUP 触发热重载;若失败则回退到 restart
|
|
965
|
+
_gw_pid=$(lsof -i :"$_GW_PORT" -sTCP:LISTEN -t 2>/dev/null | head -1)
|
|
966
|
+
if [ -n "$_gw_pid" ]; then
|
|
967
|
+
kill -HUP "$_gw_pid" 2>/dev/null || true
|
|
968
|
+
echo " 已发送 SIGHUP 到 gateway (pid: $_gw_pid)"
|
|
969
|
+
sleep 3
|
|
970
|
+
else
|
|
971
|
+
# gateway 端口还在但 pid 拿不到,回退到 restart
|
|
972
|
+
_openclaw_with_timeout 30 openclaw gateway restart
|
|
973
|
+
# 等待重启后端口重新就绪
|
|
974
|
+
for _k in $(seq 1 30); do
|
|
975
|
+
if lsof -i :"$_GW_PORT" -sTCP:LISTEN >/dev/null 2>&1; then
|
|
976
|
+
break
|
|
977
|
+
fi
|
|
978
|
+
printf "\r 等待端口 %s 重新就绪... (%d/30)" "$_GW_PORT" "$_k"
|
|
979
|
+
sleep 2
|
|
980
|
+
done
|
|
981
|
+
echo ""
|
|
982
|
+
fi
|
|
947
983
|
fi
|
|
948
984
|
# 检查 qqbot WS 是否连接成功(最多等 20 秒)
|
|
949
985
|
echo "检查 qqbot 插件连接状态..."
|
|
@@ -1001,6 +1037,7 @@ case "$start_choice" in
|
|
|
1001
1037
|
fs.writeFileSync(process.env._CFG, JSON.stringify(cfg, null, 4) + "\n");
|
|
1002
1038
|
' 2>/dev/null || true
|
|
1003
1039
|
echo " 已恢复 channels.qqbot 配置"
|
|
1040
|
+
_QQBOT_CHANNEL_STASH=""
|
|
1004
1041
|
fi
|
|
1005
1042
|
echo ""
|
|
1006
1043
|
echo "后续启动方法(兼容 openclaw 3.23+):"
|
|
@@ -1,12 +1,33 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: qqbot-channel
|
|
3
|
-
description: QQ
|
|
3
|
+
description: QQ 频道管理技能(非QQ群)。查询频道列表、子频道、成员、发帖、公告、日程等操作。使用 qqbot_platform_api 工具代理 QQ 开放平台 HTTP 接口,自动处理 Token 鉴权。仅用于 QQ 频道(Guild/Channel)。触发条件:用户提到「频道」「子频道」「guild」「channel」「帖子」「日程」「公告」。
|
|
4
4
|
metadata: {"openclaw":{"emoji":"📡","requires":{"config":["channels.qqbot"]}}}
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# QQ 频道 API 请求指导
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
> **本 Skill 仅处理 QQ 频道(Guild/Channel)操作。** 频道 ID 为纯数字,API 路径为 `/guilds/...` 和 `/channels/...`。
|
|
10
|
+
|
|
11
|
+
`qqbot_platform_api` 是 QQ 开放平台统一 HTTP API 网关,**自动填充鉴权 Token**。你只需要指定 HTTP 方法、API 路径、请求体和查询参数即可调用频道相关接口。
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 🔀 适用范围(必读)
|
|
16
|
+
|
|
17
|
+
QQ 频道是类似 Discord 的社区产品,层级结构为 **频道(Guild) → 子频道(Channel)**:
|
|
18
|
+
|
|
19
|
+
| 层级 | 中文名 | 英文 | API 路径 | ID 格式 |
|
|
20
|
+
|------|--------|------|----------|---------|
|
|
21
|
+
| 顶层 | 频道 | Guild | `/guilds/{guild_id}` | 纯数字(如 `123456`) |
|
|
22
|
+
| 次级 | 子频道 | Channel | `/channels/{channel_id}` | 纯数字(如 `789012`) |
|
|
23
|
+
|
|
24
|
+
**核心特征**:
|
|
25
|
+
- 频道内可以创建多个子频道(文字、语音、论坛、直播等)
|
|
26
|
+
- 支持发帖、公告、日程等功能
|
|
27
|
+
- 频道成员通过 `/guilds/{guild_id}/members` 管理
|
|
28
|
+
- ID 为纯数字格式,API 路径以 `/guilds/` 或 `/channels/` 开头
|
|
29
|
+
|
|
30
|
+
**触发关键词**:频道、子频道、guild、channel、帖子、日程、公告、论坛、频道成员
|
|
10
31
|
|
|
11
32
|
## 📚 详细参考文档
|
|
12
33
|
|
|
@@ -251,13 +272,14 @@ metadata: {"openclaw":{"emoji":"📡","requires":{"config":["channels.qqbot"]}}}
|
|
|
251
272
|
|
|
252
273
|
## ⚠️ 注意事项
|
|
253
274
|
|
|
254
|
-
1.
|
|
255
|
-
2.
|
|
256
|
-
3.
|
|
257
|
-
4.
|
|
258
|
-
5.
|
|
259
|
-
6.
|
|
260
|
-
7.
|
|
261
|
-
8.
|
|
262
|
-
9.
|
|
263
|
-
10.
|
|
275
|
+
1. **适用范围**:本 Skill 仅处理 QQ 频道(Guild/Channel)操作,API 路径以 `/guilds/` 或 `/channels/` 开头,ID 为纯数字
|
|
276
|
+
2. **路径中的占位符**(如 `{guild_id}`、`{channel_id}`)必须替换为实际的纯数字 ID
|
|
277
|
+
3. **query 参数的值必须为字符串类型**,如 `{ "limit": "100" }` 而非 `{ "limit": 100 }`
|
|
278
|
+
4. **成员列表翻页**时可能返回重复成员,需按 `user.id` 去重
|
|
279
|
+
5. **公告**的两种类型(消息公告和推荐子频道公告)会互相顶替
|
|
280
|
+
6. **日程**的时间戳为毫秒级字符串
|
|
281
|
+
7. **删除操作不可逆**,请谨慎使用
|
|
282
|
+
8. **论坛操作**仅私域机器人可用
|
|
283
|
+
9. **子频道分组**(type=4)的 `position` 必须 >= 2
|
|
284
|
+
10. **日程操作**有频率限制:单个管理员每天 10 次,单个频道每天 100 次
|
|
285
|
+
11. **头像/图标展示**:成员 `user.avatar` 和频道 `icon` 等图片 URL 必须使用 Markdown 图片语法 `` 展示,禁止作为纯文本或超链接展示
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime Contract Check — 启动时校验必需 API 可用性。
|
|
3
|
+
*
|
|
4
|
+
* 在 plugin.register() 中立刻调用,console 直接输出(register 阶段
|
|
5
|
+
* runtime.logging 的 getChildLogger 存在但输出管道未接通)。
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { PluginRuntime } from 'openclaw/plugin-sdk';
|
|
9
|
+
import { createPluginLogger } from '../utils/plugin-logger.js';
|
|
10
|
+
|
|
11
|
+
const log = createPluginLogger({ prefix: '[contract]', forceConsole: false });
|
|
12
|
+
|
|
13
|
+
interface ApiProbe {
|
|
14
|
+
name: string;
|
|
15
|
+
probe: (rt: PluginRuntime) => boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const REQUIRED: ApiProbe[] = [
|
|
19
|
+
{
|
|
20
|
+
name: 'channel.reply.dispatchReplyWithBufferedBlockDispatcher',
|
|
21
|
+
probe: (rt) => typeof (rt as any).channel?.reply?.dispatchReplyWithBufferedBlockDispatcher === 'function',
|
|
22
|
+
},
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
const OPTIONAL: ApiProbe[] = [
|
|
26
|
+
{ name: 'channel.inbound.run (degraded)', probe: (rt) => {
|
|
27
|
+
const c = (rt as any).channel;
|
|
28
|
+
return typeof c?.inbound?.run === 'function' || typeof c?.turn?.run === 'function';
|
|
29
|
+
}},
|
|
30
|
+
{ name: 'channel.inbound.buildContext', probe: (rt) => typeof (rt as any).channel?.inbound?.buildContext === 'function' },
|
|
31
|
+
{ name: 'channel.reply.formatAgentEnvelope', probe: (rt) => typeof (rt as any).channel?.reply?.formatAgentEnvelope === 'function' },
|
|
32
|
+
{ name: 'channel.text.chunkMarkdownText', probe: (rt) => typeof (rt as any).channel?.text?.chunkMarkdownText === 'function' },
|
|
33
|
+
{ name: 'channel.routing.resolveAgentRoute', probe: (rt) => typeof (rt as any).channel?.routing?.resolveAgentRoute === 'function' },
|
|
34
|
+
{ name: 'channel.session.resolveStorePath (deprecated)', probe: (rt) => typeof (rt as any).channel?.session?.resolveStorePath === 'function' },
|
|
35
|
+
{ name: 'channel.session.recordInboundSession (deprecated)', probe: (rt) => typeof (rt as any).channel?.session?.recordInboundSession === 'function' },
|
|
36
|
+
{ name: 'channel.reply.finalizeInboundContext (deprecated)', probe: (rt) => typeof (rt as any).channel?.reply?.finalizeInboundContext === 'function' },
|
|
37
|
+
{ name: 'channel.reply.formatInboundEnvelope (deprecated)', probe: (rt) => typeof (rt as any).channel?.reply?.formatInboundEnvelope === 'function' },
|
|
38
|
+
{ name: 'config.current', probe: (rt) => typeof (rt as any).config?.current === 'function' },
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
export interface ContractResult {
|
|
42
|
+
ok: boolean;
|
|
43
|
+
version: string;
|
|
44
|
+
missing: string[];
|
|
45
|
+
degraded: string[];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function verifyRuntimeContract(rt: PluginRuntime): ContractResult {
|
|
49
|
+
const version = (rt as any).version ?? 'unknown';
|
|
50
|
+
const missing: string[] = [];
|
|
51
|
+
const degraded: string[] = [];
|
|
52
|
+
|
|
53
|
+
for (const r of REQUIRED) {
|
|
54
|
+
if (!r.probe(rt)) missing.push(r.name);
|
|
55
|
+
}
|
|
56
|
+
for (const r of OPTIONAL) {
|
|
57
|
+
if (!r.probe(rt)) degraded.push(r.name);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
log.debug(`openclaw=${version} required=${REQUIRED.length - missing.length}/${REQUIRED.length} degraded=${degraded.length}/${OPTIONAL.length}`);
|
|
61
|
+
if (missing.length) {
|
|
62
|
+
log.error(`BROKEN — missing: ${missing.join(', ')}`);
|
|
63
|
+
}
|
|
64
|
+
if (degraded.length) {
|
|
65
|
+
log.debug(`degraded: ${degraded.join(', ')}`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return { ok: missing.length === 0, version, missing, degraded };
|
|
69
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gateway Runtime 动态加载(兼容旧版框架)
|
|
3
|
+
*
|
|
4
|
+
* openclaw < 3.22 上 gateway-runtime.js 不存在,此函数返回 null。
|
|
5
|
+
* 审批功能降级为不可用,不影响消息收发等核心能力。
|
|
6
|
+
*/
|
|
7
|
+
import { createRequire } from 'node:module';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
|
|
10
|
+
export interface ApprovalGatewayClient {
|
|
11
|
+
start: () => void | Promise<void>;
|
|
12
|
+
stop: () => void | Promise<void>;
|
|
13
|
+
request: (method: string, params: unknown) => Promise<unknown>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** 动态加载 gateway-runtime 模块,旧版框架不可用时返回 null */
|
|
17
|
+
export function loadApprovalGatewayRuntime(): {
|
|
18
|
+
createOperatorApprovalsGatewayClient: (...args: any[]) => Promise<ApprovalGatewayClient>;
|
|
19
|
+
} | null {
|
|
20
|
+
const req = createRequire(__filename);
|
|
21
|
+
const pluginRoot = path.resolve(path.dirname(__filename), '..', '..');
|
|
22
|
+
const fs = req('node:fs') as typeof import('node:fs');
|
|
23
|
+
|
|
24
|
+
const tryLoadFromRoot = (root: string) => {
|
|
25
|
+
for (const rel of ['dist/plugin-sdk/gateway-runtime.js', 'plugin-sdk/gateway-runtime.js']) {
|
|
26
|
+
const p = path.join(root, rel);
|
|
27
|
+
try {
|
|
28
|
+
if (fs.existsSync(p)) return req(p);
|
|
29
|
+
} catch { /* try next */ }
|
|
30
|
+
}
|
|
31
|
+
return null;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const { findOpenclawRoot } = req(path.join(pluginRoot, 'scripts', 'link-sdk-core.cjs')) as {
|
|
36
|
+
findOpenclawRoot: (root: string) => string | null;
|
|
37
|
+
};
|
|
38
|
+
const root = findOpenclawRoot(pluginRoot);
|
|
39
|
+
if (root) {
|
|
40
|
+
const mod = tryLoadFromRoot(root);
|
|
41
|
+
if (mod) return mod;
|
|
42
|
+
}
|
|
43
|
+
} catch { /* fallback */ }
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
const entry = process.argv[1];
|
|
47
|
+
if (entry) {
|
|
48
|
+
const realEntry = fs.realpathSync(entry);
|
|
49
|
+
let dir = path.dirname(realEntry);
|
|
50
|
+
for (let i = 0; i < 6; i++) {
|
|
51
|
+
const mod = tryLoadFromRoot(dir);
|
|
52
|
+
if (mod) return mod;
|
|
53
|
+
const parent = path.dirname(dir);
|
|
54
|
+
if (parent === dir) break;
|
|
55
|
+
dir = parent;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
} catch { /* fallback */ }
|
|
59
|
+
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime Adapter — 隔离 OpenClaw 框架 API 变更对插件核心逻辑的冲击。
|
|
3
|
+
*
|
|
4
|
+
* 所有 `runtime.channel.*` / `runtime.config.*` 访问 **必须** 经由本模块导出的函数。
|
|
5
|
+
* 禁止在 adapter/ 外直接使用 `(runtime as any).channel.*`。
|
|
6
|
+
*
|
|
7
|
+
* 设计原则:
|
|
8
|
+
* 1. Capability Probe — 按候选 API 路径探测,兼容多版本框架
|
|
9
|
+
* 2. Contract Check — 启动时校验必需 API 可用性,fail-fast 暴露问题
|
|
10
|
+
* 3. 单一 resolve 入口 — 一次 resolve,全程复用缓存
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export { verifyRuntimeContract, type ContractResult } from './contract.js';
|
|
14
|
+
export { resolveRuntimeAdapters, getAdapters, type RuntimeAdapters } from './resolve.js';
|
|
15
|
+
export { loadApprovalGatewayRuntime, type ApprovalGatewayClient } from './gateway.js';
|