@tencent-connect/openclaw-qqbot 1.7.2 → 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 +25 -8
- package/README.zh.md +24 -8
- 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 -19
- 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 +42 -11
- package/scripts/upgrade-via-source.sh +4 -4
- 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 +46 -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 +2 -0
- package/src/types-augment.d.ts +54 -0
- package/src/types.ts +51 -9
- 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 -18
- package/dist/index.js +0 -27
- package/dist/src/admin-resolver.d.ts +0 -33
- package/dist/src/admin-resolver.js +0 -157
- package/dist/src/api.d.ts +0 -309
- package/dist/src/api.js +0 -923
- 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 -60
- package/dist/src/config.js +0 -288
- 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 -2025
- 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 -2233
- 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 -231
- package/dist/src/tools/remind.d.ts +0 -2
- package/dist/src/tools/remind.js +0 -255
- package/dist/src/transport/index.d.ts +0 -10
- package/dist/src/transport/index.js +0 -9
- package/dist/src/transport/webhook-transport.d.ts +0 -67
- package/dist/src/transport/webhook-transport.js +0 -245
- package/dist/src/transport/webhook-verify.d.ts +0 -48
- package/dist/src/transport/webhook-verify.js +0 -98
- package/dist/src/types.d.ts +0 -462
- 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 -783
- 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 -170
- package/skills/qqbot-media/SKILL.md +0 -60
- package/src/admin-resolver.ts +0 -181
- package/src/api.ts +0 -1333
- package/src/credential-backup.ts +0 -72
- package/src/deliver-debounce.ts +0 -229
- package/src/gateway.ts +0 -2272
- 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 -282
- 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 -2431
- 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 -275
- package/src/transport/index.ts +0 -11
- package/src/transport/webhook-transport.ts +0 -332
- package/src/transport/webhook-verify.ts +0 -119
- package/src/typing-keepalive.ts +0 -59
- package/src/utils/audio-convert.ts +0 -887
- 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
|
@@ -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';
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lint: 检测 adapter/ 外部直接访问 runtime.channel.* 的代码
|
|
3
|
+
*
|
|
4
|
+
* 运行方式:
|
|
5
|
+
* npx tsx src/adapter/lint-runtime-access.ts
|
|
6
|
+
*
|
|
7
|
+
* 退出码:
|
|
8
|
+
* 0 = 无违规
|
|
9
|
+
* 1 = 存在未加白的直接 runtime 访问
|
|
10
|
+
*
|
|
11
|
+
* 加白方式(行内注释):
|
|
12
|
+
* // @adapter-bypass: <理由>
|
|
13
|
+
*
|
|
14
|
+
* 示例:
|
|
15
|
+
* const tts = (runtime as any)?.tts; // @adapter-bypass: TTS 非 channel API,无需经过适配层
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import * as fs from 'node:fs';
|
|
19
|
+
import * as path from 'node:path';
|
|
20
|
+
|
|
21
|
+
// ── 配置 ──
|
|
22
|
+
|
|
23
|
+
const SRC_ROOT = path.resolve(import.meta.dirname, '..');
|
|
24
|
+
const ADAPTER_DIR = path.resolve(SRC_ROOT, 'adapter');
|
|
25
|
+
|
|
26
|
+
/** 匹配直接使用 runtime.channel / runtime.config / (runtime as any) 的模式 */
|
|
27
|
+
const VIOLATION_PATTERNS: RegExp[] = [
|
|
28
|
+
/runtime\.channel\b/,
|
|
29
|
+
/runtime\.config\b/,
|
|
30
|
+
/\(runtime\s+as\s+any\)/,
|
|
31
|
+
/channel\??\.(inbound|turn|reply|session|routing|text|media|runtimeContexts)\b/,
|
|
32
|
+
/config\s+as\s*\{.*writeConfigFile/,
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
/** 加白注释标记 */
|
|
36
|
+
const BYPASS_MARKER = '@adapter-bypass';
|
|
37
|
+
|
|
38
|
+
/** 排除的目录/文件 */
|
|
39
|
+
const EXCLUDE_DIRS = ['adapter', 'node_modules', 'dist', 'tests'];
|
|
40
|
+
const EXCLUDE_FILES = ['types.ts', 'types-augment.d.ts', 'openclaw-plugin-sdk.d.ts'];
|
|
41
|
+
|
|
42
|
+
// ── 扫描逻辑 ──
|
|
43
|
+
|
|
44
|
+
interface Violation {
|
|
45
|
+
file: string;
|
|
46
|
+
line: number;
|
|
47
|
+
content: string;
|
|
48
|
+
pattern: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function shouldExclude(filePath: string): boolean {
|
|
52
|
+
const relative = path.relative(SRC_ROOT, filePath);
|
|
53
|
+
// 排除 adapter 自身
|
|
54
|
+
if (filePath.startsWith(ADAPTER_DIR)) return true;
|
|
55
|
+
// 排除配置的目录
|
|
56
|
+
for (const dir of EXCLUDE_DIRS) {
|
|
57
|
+
if (relative.startsWith(dir + '/') || relative === dir) return true;
|
|
58
|
+
}
|
|
59
|
+
// 排除特定文件
|
|
60
|
+
const fileName = path.basename(filePath);
|
|
61
|
+
if (EXCLUDE_FILES.includes(fileName)) return true;
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function scanFile(filePath: string): Violation[] {
|
|
66
|
+
const violations: Violation[] = [];
|
|
67
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
68
|
+
const lines = content.split('\n');
|
|
69
|
+
|
|
70
|
+
for (let i = 0; i < lines.length; i++) {
|
|
71
|
+
const line = lines[i];
|
|
72
|
+
const lineNum = i + 1;
|
|
73
|
+
|
|
74
|
+
// 跳过注释行(单行注释、JSDoc 行、多行注释体)
|
|
75
|
+
const trimmed = line.trimStart();
|
|
76
|
+
if (trimmed.startsWith('//') || trimmed.startsWith('*') || trimmed.startsWith('/**') || trimmed.startsWith('*/')) continue;
|
|
77
|
+
|
|
78
|
+
// 检查加白标记
|
|
79
|
+
if (line.includes(BYPASS_MARKER)) continue;
|
|
80
|
+
|
|
81
|
+
// 检查违规模式
|
|
82
|
+
for (const pattern of VIOLATION_PATTERNS) {
|
|
83
|
+
if (pattern.test(line)) {
|
|
84
|
+
violations.push({
|
|
85
|
+
file: path.relative(SRC_ROOT, filePath),
|
|
86
|
+
line: lineNum,
|
|
87
|
+
content: line.trim(),
|
|
88
|
+
pattern: pattern.source,
|
|
89
|
+
});
|
|
90
|
+
break; // 同一行只报一次
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return violations;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function walkDir(dir: string): string[] {
|
|
99
|
+
const results: string[] = [];
|
|
100
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
101
|
+
for (const entry of entries) {
|
|
102
|
+
const fullPath = path.join(dir, entry.name);
|
|
103
|
+
if (entry.isDirectory()) {
|
|
104
|
+
if (!EXCLUDE_DIRS.includes(entry.name)) {
|
|
105
|
+
results.push(...walkDir(fullPath));
|
|
106
|
+
}
|
|
107
|
+
} else if (entry.isFile() && entry.name.endsWith('.ts') && !entry.name.endsWith('.d.ts')) {
|
|
108
|
+
results.push(fullPath);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return results;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ── 主流程 ──
|
|
115
|
+
|
|
116
|
+
function main(): void {
|
|
117
|
+
const files = walkDir(SRC_ROOT).filter((f) => !shouldExclude(f));
|
|
118
|
+
const allViolations: Violation[] = [];
|
|
119
|
+
|
|
120
|
+
for (const file of files) {
|
|
121
|
+
const violations = scanFile(file);
|
|
122
|
+
allViolations.push(...violations);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (allViolations.length === 0) {
|
|
126
|
+
console.log('✅ No direct runtime access found outside adapter/');
|
|
127
|
+
process.exit(0);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
console.error(`❌ Found ${allViolations.length} direct runtime access violation(s):\n`);
|
|
131
|
+
for (const v of allViolations) {
|
|
132
|
+
console.error(` ${v.file}:${v.line}`);
|
|
133
|
+
console.error(` ${v.content}`);
|
|
134
|
+
console.error(` → matched: ${v.pattern}`);
|
|
135
|
+
console.error(` → fix: use adapter or add "// @adapter-bypass: <reason>"\n`);
|
|
136
|
+
}
|
|
137
|
+
console.error(
|
|
138
|
+
`\nTo bypass a specific line, add an inline comment:\n` +
|
|
139
|
+
` // @adapter-bypass: <reason why this cannot go through the adapter>\n`,
|
|
140
|
+
);
|
|
141
|
+
process.exit(1);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
main();
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 远程媒体下载(动态加载 plugin-sdk/media-runtime,降级到 fetch)
|
|
3
|
+
*
|
|
4
|
+
* 新版本 openclaw 有 openclaw/plugin-sdk/media-runtime(含 SSRF 防护/重试/大小限制),
|
|
5
|
+
* 旧版本不可用时降级到原生 fetch 直连。
|
|
6
|
+
*/
|
|
7
|
+
import { createRequire } from 'node:module';
|
|
8
|
+
import * as dns from 'node:dns';
|
|
9
|
+
import * as path from 'node:path';
|
|
10
|
+
import * as fs from 'node:fs';
|
|
11
|
+
import * as crypto from 'node:crypto';
|
|
12
|
+
import { getQQBotMediaDir } from '../utils/platform.js';
|
|
13
|
+
|
|
14
|
+
const req = createRequire(__filename);
|
|
15
|
+
|
|
16
|
+
type SaveRemoteMedia = (opts: {
|
|
17
|
+
url: string;
|
|
18
|
+
subdir?: string;
|
|
19
|
+
originalFilename?: string;
|
|
20
|
+
maxBytes?: number;
|
|
21
|
+
timeoutMs?: number;
|
|
22
|
+
}) => Promise<{ path: string }>;
|
|
23
|
+
|
|
24
|
+
let _save: SaveRemoteMedia | null | undefined;
|
|
25
|
+
|
|
26
|
+
export function downloadRemoteMedia(opts: {
|
|
27
|
+
url: string;
|
|
28
|
+
subdir?: string;
|
|
29
|
+
originalFilename?: string;
|
|
30
|
+
maxBytes?: number;
|
|
31
|
+
timeoutMs?: number;
|
|
32
|
+
}): Promise<{ path: string }> {
|
|
33
|
+
if (_save === undefined) {
|
|
34
|
+
try {
|
|
35
|
+
const mod = req('openclaw/plugin-sdk/media-runtime') as { saveRemoteMedia: SaveRemoteMedia };
|
|
36
|
+
_save = mod.saveRemoteMedia;
|
|
37
|
+
} catch {
|
|
38
|
+
_save = null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return _save ? _save(opts) : downloadViaFetch(opts);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ── SSRF 防护 ──
|
|
45
|
+
|
|
46
|
+
const PRIVATE_RANGES: Array<[netmask: bigint, prefix: number]> = [
|
|
47
|
+
[0x0A000000n, 8], // 10.0.0.0/8
|
|
48
|
+
[0xAC100000n, 12], // 172.16.0.0/12
|
|
49
|
+
[0xC0A80000n, 16], // 192.168.0.0/16
|
|
50
|
+
[0x7F000000n, 8], // 127.0.0.0/8
|
|
51
|
+
[0xA9FE0000n, 16], // 169.254.0.0/16
|
|
52
|
+
[0xE0000000n, 4], // 224.0.0.0/4 (multicast)
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
function ipToBigInt(ip: string): bigint {
|
|
56
|
+
return ip.split('.').reduce((acc, octet) => (acc << 8n) | BigInt(Number(octet)), 0n);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function isPrivateIP(ip: string): boolean {
|
|
60
|
+
const val = ipToBigInt(ip);
|
|
61
|
+
return PRIVATE_RANGES.some(([mask, prefix]) => (val >> (32n - BigInt(prefix))) === (mask >> (32n - BigInt(prefix))));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function assertSafeHostname(hostname: string): Promise<void> {
|
|
65
|
+
const addresses = await dns.promises.resolve4(hostname).catch(() => []);
|
|
66
|
+
if (addresses.length === 0) throw new Error(`DNS resolution failed: ${hostname}`);
|
|
67
|
+
for (const addr of addresses) {
|
|
68
|
+
if (isPrivateIP(addr)) {
|
|
69
|
+
throw new Error(`SSRF blocked: ${hostname} resolves to private IP ${addr}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ── 降级 fetch ──
|
|
75
|
+
|
|
76
|
+
/** 降级:原生 fetch 直连(含 SSRF 防护、大小限制) */
|
|
77
|
+
async function downloadViaFetch(opts: {
|
|
78
|
+
url: string;
|
|
79
|
+
subdir?: string;
|
|
80
|
+
originalFilename?: string;
|
|
81
|
+
maxBytes?: number;
|
|
82
|
+
timeoutMs?: number;
|
|
83
|
+
}): Promise<{ path: string }> {
|
|
84
|
+
const parsed = new URL(opts.url);
|
|
85
|
+
if (parsed.protocol !== 'https:') {
|
|
86
|
+
throw new Error(`Only HTTPS allowed: ${parsed.protocol}`);
|
|
87
|
+
}
|
|
88
|
+
await assertSafeHostname(parsed.hostname);
|
|
89
|
+
|
|
90
|
+
const dir = getQQBotMediaDir(opts.subdir ?? 'downloads');
|
|
91
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
92
|
+
|
|
93
|
+
const resp = await fetch(opts.url, {
|
|
94
|
+
signal: AbortSignal.timeout(opts.timeoutMs ?? 120_000),
|
|
95
|
+
});
|
|
96
|
+
if (!resp.ok) throw new Error(`Download HTTP ${resp.status}`);
|
|
97
|
+
|
|
98
|
+
const maxBytes = opts.maxBytes ?? 500 * 1024 * 1024;
|
|
99
|
+
const buf = Buffer.from(await resp.arrayBuffer());
|
|
100
|
+
if (buf.length > maxBytes) throw new Error(`Download exceeds ${(maxBytes / 1024 / 1024).toFixed(0)}MB`);
|
|
101
|
+
|
|
102
|
+
const ext = opts.originalFilename
|
|
103
|
+
? path.extname(opts.originalFilename) || '.bin'
|
|
104
|
+
: '.bin';
|
|
105
|
+
const name = opts.originalFilename
|
|
106
|
+
? path.basename(opts.originalFilename, path.extname(opts.originalFilename))
|
|
107
|
+
: 'download';
|
|
108
|
+
const rand = crypto.randomBytes(4).toString('hex');
|
|
109
|
+
const filePath = path.join(dir, `${name}_${Date.now()}_${rand}${ext}`);
|
|
110
|
+
fs.writeFileSync(filePath, buf);
|
|
111
|
+
return { path: filePath };
|
|
112
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pairing Runtime 动态加载(兼容旧版框架)
|
|
3
|
+
*
|
|
4
|
+
* 从 OpenClaw 安装目录动态加载 conversation-runtime 模块中的配对函数。
|
|
5
|
+
* 不支持时返回 null,配对功能降级为不可用。
|
|
6
|
+
*/
|
|
7
|
+
import { createRequire } from 'node:module';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
|
|
10
|
+
export interface PairingApi {
|
|
11
|
+
readAllowFromStore: (params: { channel: string; accountId: string }) => Promise<string[]>;
|
|
12
|
+
issueChallenge: (params: { channel: string; id: string; accountId: string }) => Promise<{ code: string }>;
|
|
13
|
+
buildReply: (params: { code: string; channel: string }) => string;
|
|
14
|
+
approveCode: (params: { channel: string; code: string; accountId?: string }) => Promise<{ id: string } | null>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let _api: PairingApi | null | undefined;
|
|
18
|
+
|
|
19
|
+
/** 获取 Pairing API,首次调用触发加载并缓存 */
|
|
20
|
+
export function getPairingApi(): PairingApi | null {
|
|
21
|
+
if (_api !== undefined) return _api;
|
|
22
|
+
_api = loadPairingApi();
|
|
23
|
+
return _api;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function loadPairingApi(): PairingApi | null {
|
|
27
|
+
const currentFile = __filename;
|
|
28
|
+
const req = createRequire(currentFile);
|
|
29
|
+
const pluginRoot = path.resolve(path.dirname(currentFile), '..', '..');
|
|
30
|
+
const fs = req('node:fs') as typeof import('node:fs');
|
|
31
|
+
|
|
32
|
+
const tryLoad = (root: string) => {
|
|
33
|
+
for (const rel of ['dist/plugin-sdk/conversation-runtime.js', 'plugin-sdk/conversation-runtime.js']) {
|
|
34
|
+
const p = path.join(root, rel);
|
|
35
|
+
try {
|
|
36
|
+
if (fs.existsSync(p)) return req(p);
|
|
37
|
+
} catch { /* try next */ }
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
let mod: any = null;
|
|
43
|
+
try {
|
|
44
|
+
const { findOpenclawRoot } = req(path.join(pluginRoot, 'scripts', 'link-sdk-core.cjs')) as {
|
|
45
|
+
findOpenclawRoot: (root: string) => string | null;
|
|
46
|
+
};
|
|
47
|
+
const root = findOpenclawRoot(pluginRoot);
|
|
48
|
+
if (root) mod = tryLoad(root);
|
|
49
|
+
} catch { /* fallback */ }
|
|
50
|
+
|
|
51
|
+
if (!mod) {
|
|
52
|
+
try {
|
|
53
|
+
const entry = process.argv[1];
|
|
54
|
+
if (entry) {
|
|
55
|
+
const realEntry = fs.realpathSync(entry);
|
|
56
|
+
let dir = path.dirname(realEntry);
|
|
57
|
+
for (let i = 0; i < 6; i++) {
|
|
58
|
+
mod = tryLoad(dir);
|
|
59
|
+
if (mod) break;
|
|
60
|
+
const parent = path.dirname(dir);
|
|
61
|
+
if (parent === dir) break;
|
|
62
|
+
dir = parent;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
} catch { /* fallback */ }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (!mod?.readChannelAllowFromStore) return null;
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
// readChannelAllowFromStore(channel, env?, accountId?) — 位置参数
|
|
72
|
+
readAllowFromStore: (params) =>
|
|
73
|
+
mod.readChannelAllowFromStore(params.channel, undefined, params.accountId),
|
|
74
|
+
|
|
75
|
+
// upsertChannelPairingRequest({ channel, id, accountId }) — 对象参数
|
|
76
|
+
issueChallenge: (params) =>
|
|
77
|
+
mod.upsertChannelPairingRequest({
|
|
78
|
+
channel: params.channel,
|
|
79
|
+
id: params.id,
|
|
80
|
+
accountId: params.accountId,
|
|
81
|
+
}).then((r: any) => ({ code: r.code as string })),
|
|
82
|
+
|
|
83
|
+
// buildPairingReply({ channel, idLine, code }) — 对象参数
|
|
84
|
+
buildReply: (params) =>
|
|
85
|
+
mod.buildPairingReply({
|
|
86
|
+
channel: params.channel,
|
|
87
|
+
idLine: '', // qqbot 无额外 ID 信息,留空即可
|
|
88
|
+
code: params.code,
|
|
89
|
+
}),
|
|
90
|
+
|
|
91
|
+
// approveChannelPairingCode({ channel, code, accountId? }) — 对象参数
|
|
92
|
+
approveCode: (params) =>
|
|
93
|
+
mod.approveChannelPairingCode({
|
|
94
|
+
channel: params.channel,
|
|
95
|
+
code: params.code,
|
|
96
|
+
accountId: params.accountId,
|
|
97
|
+
}),
|
|
98
|
+
};
|
|
99
|
+
}
|