@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,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 动态访问控制中间件。
|
|
3
|
+
*
|
|
4
|
+
* 从 ctx.state.policy(由 policy-injector 注入)动态读取策略,支持配置热更新。
|
|
5
|
+
* 模式:disabled | open | allowlist | pairing(仅 c2c)
|
|
6
|
+
*/
|
|
7
|
+
import type { Middleware } from '@tencent-connect/qqbot-nodejs';
|
|
8
|
+
import { getPairingApi } from '../adapter/pairing.js';
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 创建动态访问控制中间件。
|
|
13
|
+
*
|
|
14
|
+
* 决策优先级:
|
|
15
|
+
* 1. disabled → 拒绝
|
|
16
|
+
* 2. open → 放行
|
|
17
|
+
* 3. allowlist → allowFrom 匹配(默认模式)
|
|
18
|
+
* 4. pairing → allowFrom + pairing store,未配对发起挑战
|
|
19
|
+
*/
|
|
20
|
+
export function dynamicAccessControl(params: {
|
|
21
|
+
accountId: string;
|
|
22
|
+
getRuntime: () => any;
|
|
23
|
+
}): Middleware {
|
|
24
|
+
const { accountId, getRuntime } = params;
|
|
25
|
+
|
|
26
|
+
return async (ctx, next) => {
|
|
27
|
+
const p = ctx.state.policy as Record<string, unknown> | undefined;
|
|
28
|
+
const isGroup = ctx.message.kind === 'group';
|
|
29
|
+
const mode: string = isGroup
|
|
30
|
+
? (p?.groupMode as string) ?? 'open'
|
|
31
|
+
: (p?.c2cMode as string) ?? 'allowlist';
|
|
32
|
+
|
|
33
|
+
if (mode === 'disabled') {
|
|
34
|
+
ctx.log?.info?.(`[access] blocked ${isGroup ? 'group' : 'c2c'} from ${ctx.message.senderId}: policy disabled`);
|
|
35
|
+
ctx.stop('access:policy_disabled');
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
if (mode === 'open') {
|
|
39
|
+
await next();
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// allowlist / pairing:c2c 用 allowFrom,group 用 groupAllowFrom
|
|
44
|
+
const allowList = isGroup
|
|
45
|
+
? ((p?.groupAllowFrom as string[]) ?? [])
|
|
46
|
+
: ((p?.allowFrom as string[]) ?? []);
|
|
47
|
+
if (!allowList.length || allowList.includes('*')) {
|
|
48
|
+
await next();
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const id = isGroup
|
|
53
|
+
? (ctx.message.groupOpenid ?? '')
|
|
54
|
+
: (ctx.message.senderId as string);
|
|
55
|
+
if (allowList.includes(id)) {
|
|
56
|
+
await next();
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// allowlist 未匹配 → pairing 模式尝试 pairing store(仅 c2c)
|
|
61
|
+
if (mode === 'pairing' && !isGroup) {
|
|
62
|
+
await checkPairingMode(ctx, next, {
|
|
63
|
+
accountId,
|
|
64
|
+
getRuntime,
|
|
65
|
+
senderId: ctx.message.senderId as string,
|
|
66
|
+
});
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// 未匹配 → 拒绝
|
|
71
|
+
const listLabel = isGroup ? 'groupAllowFrom' : 'allowFrom';
|
|
72
|
+
ctx.log?.info?.(
|
|
73
|
+
`[access] blocked ${isGroup ? 'group' : 'c2c'} from ${id}: not in ${listLabel}`,
|
|
74
|
+
);
|
|
75
|
+
ctx.stop('access:not_allowlisted');
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* pairing 模式:检查 pairing store + 发起配对挑战。
|
|
81
|
+
*/
|
|
82
|
+
async function checkPairingMode(
|
|
83
|
+
ctx: any,
|
|
84
|
+
next: () => Promise<void>,
|
|
85
|
+
opts: { accountId: string; getRuntime: () => any; senderId: string },
|
|
86
|
+
): Promise<void> {
|
|
87
|
+
const api = getPairingApi();
|
|
88
|
+
if (!api) {
|
|
89
|
+
ctx.log?.info?.(`[access] pairing unavailable for ${opts.senderId}`);
|
|
90
|
+
ctx.stop('access:pairing_unavailable');
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
const storeIds = await api.readAllowFromStore({
|
|
96
|
+
channel: 'qqbot',
|
|
97
|
+
accountId: opts.accountId,
|
|
98
|
+
});
|
|
99
|
+
if (storeIds.includes(opts.senderId) || storeIds.includes('*')) {
|
|
100
|
+
await next();
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const challenge = await api.issueChallenge({
|
|
105
|
+
channel: 'qqbot',
|
|
106
|
+
id: opts.senderId,
|
|
107
|
+
accountId: opts.accountId,
|
|
108
|
+
});
|
|
109
|
+
const frameworkReply = api.buildReply({
|
|
110
|
+
code: challenge.code,
|
|
111
|
+
channel: 'qqbot',
|
|
112
|
+
});
|
|
113
|
+
const reply = [
|
|
114
|
+
frameworkReply,
|
|
115
|
+
'',
|
|
116
|
+
'QQ 管理员可直接执行:',
|
|
117
|
+
'',
|
|
118
|
+
'```',
|
|
119
|
+
`/bot-pairing approve ${challenge.code}`,
|
|
120
|
+
'```',
|
|
121
|
+
].join('\n');
|
|
122
|
+
|
|
123
|
+
ctx.log?.info?.(`[access] pairing required for ${opts.senderId}`);
|
|
124
|
+
await ctx.bot.sendText(ctx.replyTarget, reply).catch(() => {/* ignore */});
|
|
125
|
+
ctx.stop('access:pairing_required');
|
|
126
|
+
} catch (err) {
|
|
127
|
+
ctx.log?.error?.(`[access] pairing error: ${(err as Error).message}`);
|
|
128
|
+
ctx.stop(`access:pairing_error: ${(err as Error).message}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 入站附件处理中间件
|
|
3
|
+
*
|
|
4
|
+
* 处理入站消息中的语音/图片/视频附件:
|
|
5
|
+
* - 语音:下载 → SILK转WAV → STT转文字 → 写入 ctx.state.processedAttachments
|
|
6
|
+
* - 图片:提取 URL 列表
|
|
7
|
+
* - 其他:标记为附件描述
|
|
8
|
+
*
|
|
9
|
+
* 插入位置:envelopeFormatter 之前
|
|
10
|
+
*/
|
|
11
|
+
import * as path from 'node:path';
|
|
12
|
+
import type { MiddlewareContext } from '@tencent-connect/qqbot-nodejs';
|
|
13
|
+
import {
|
|
14
|
+
convertSilkToWav,
|
|
15
|
+
isVoiceAttachment,
|
|
16
|
+
} from '@tencent-connect/qqbot-nodejs/protocol';
|
|
17
|
+
import type { MessageAttachment } from '../types.js';
|
|
18
|
+
import { transcribeAudio, resolveSTTConfig } from '../utils/stt.js';
|
|
19
|
+
import { formatVoiceText, formatDuration, type VoiceTranscript, type TranscriptSource } from '../utils/voice-text.js';
|
|
20
|
+
import { downloadRemoteMedia } from '../adapter/media.js';
|
|
21
|
+
import { getAdapters } from '../adapter/resolve.js';
|
|
22
|
+
|
|
23
|
+
export { formatVoiceText, formatDuration };
|
|
24
|
+
export type { VoiceTranscript, TranscriptSource };
|
|
25
|
+
|
|
26
|
+
/** 处理后的附件结果(写入 ctx.state.processedAttachments) */
|
|
27
|
+
export interface ProcessedAttachments {
|
|
28
|
+
voiceText: string;
|
|
29
|
+
imageUrls: string[];
|
|
30
|
+
otherInfo: string;
|
|
31
|
+
transcripts: VoiceTranscript[];
|
|
32
|
+
/** 下载到本地的媒体路径(图片 + 语音,供 AI 引用) */
|
|
33
|
+
localMediaPaths: string[];
|
|
34
|
+
/** 对应 localMediaPaths 的 MIME type */
|
|
35
|
+
localMediaTypes: string[];
|
|
36
|
+
/** 远端 URL 列表(下载失败时的回退) */
|
|
37
|
+
remoteMediaUrls: string[];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface AttachmentMiddlewareOptions {
|
|
41
|
+
/** 获取 runtime */
|
|
42
|
+
getRuntime: () => any;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 附件处理中间件
|
|
47
|
+
*
|
|
48
|
+
* SDK ctx 已提供 log 和 accountId,只需传入配置获取函数。
|
|
49
|
+
*/
|
|
50
|
+
export function attachmentProcessor(opts: AttachmentMiddlewareOptions) {
|
|
51
|
+
return async (ctx: MiddlewareContext, next: () => Promise<void>) => {
|
|
52
|
+
const msg = ctx.message;
|
|
53
|
+
const attachments = msg.attachments as MessageAttachment[] | undefined;
|
|
54
|
+
|
|
55
|
+
if (attachments?.length) {
|
|
56
|
+
const runtime = opts.getRuntime();
|
|
57
|
+
const adapters = getAdapters(runtime);
|
|
58
|
+
const cfg = (adapters.getConfig?.() ?? {}) as Record<string, unknown>;
|
|
59
|
+
const log = ctx.log;
|
|
60
|
+
const result = await processAttachments(attachments, cfg, log);
|
|
61
|
+
|
|
62
|
+
if (result.voiceText || result.imageUrls.length > 0 || result.otherInfo || result.localMediaPaths.length > 0) {
|
|
63
|
+
ctx.state.processedAttachments = result;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
await next();
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ── 核心处理逻辑 ──
|
|
72
|
+
|
|
73
|
+
type Log = { info: (m: string) => void; error: (m: string) => void; debug?: (m: string) => void };
|
|
74
|
+
|
|
75
|
+
async function processAttachments(
|
|
76
|
+
attachments: MessageAttachment[],
|
|
77
|
+
cfg: Record<string, unknown>,
|
|
78
|
+
log?: Log,
|
|
79
|
+
): Promise<ProcessedAttachments> {
|
|
80
|
+
const sttCfg = resolveSTTConfig(cfg);
|
|
81
|
+
const audioPolicy = resolveAudioPolicy(cfg);
|
|
82
|
+
|
|
83
|
+
const imageUrls: string[] = [];
|
|
84
|
+
const otherParts: string[] = [];
|
|
85
|
+
const transcripts: VoiceTranscript[] = [];
|
|
86
|
+
const localMediaPaths: string[] = [];
|
|
87
|
+
const localMediaTypes: string[] = [];
|
|
88
|
+
const remoteMediaUrls: string[] = [];
|
|
89
|
+
|
|
90
|
+
// 并行下载所有附件
|
|
91
|
+
const tasks = attachments.map(async (att) => {
|
|
92
|
+
const isVoice = isVoiceAttachment(att);
|
|
93
|
+
const isImage = att.content_type?.startsWith('image/');
|
|
94
|
+
const url = normalizeUrl(att.url);
|
|
95
|
+
|
|
96
|
+
if (isImage && url) {
|
|
97
|
+
const localPath = await downloadMediaFile(url, att.filename, log);
|
|
98
|
+
return { type: 'image' as const, localPath, url, contentType: att.content_type ?? 'image/png' };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (isVoice) {
|
|
102
|
+
const transcript = await processVoiceAttachment(att, sttCfg, audioPolicy, log);
|
|
103
|
+
return { type: 'voice' as const, transcript };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// other 类型也尝试下载
|
|
107
|
+
if (url) {
|
|
108
|
+
const localPath = await downloadMediaFile(url, att.filename, log);
|
|
109
|
+
return { type: 'other' as const, localPath, url, filename: att.filename ?? att.content_type };
|
|
110
|
+
}
|
|
111
|
+
return { type: 'other' as const, localPath: null, url: '', filename: att.filename ?? att.content_type };
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const results = await Promise.all(tasks);
|
|
115
|
+
|
|
116
|
+
// 按原始顺序收集结果
|
|
117
|
+
for (const result of results) {
|
|
118
|
+
if (result.type === 'image') {
|
|
119
|
+
if (result.localPath) {
|
|
120
|
+
imageUrls.push(result.localPath);
|
|
121
|
+
localMediaPaths.push(result.localPath);
|
|
122
|
+
localMediaTypes.push(result.contentType);
|
|
123
|
+
} else {
|
|
124
|
+
imageUrls.push(result.url);
|
|
125
|
+
remoteMediaUrls.push(result.url);
|
|
126
|
+
}
|
|
127
|
+
} else if (result.type === 'voice') {
|
|
128
|
+
transcripts.push(result.transcript);
|
|
129
|
+
if (result.transcript.localPath) {
|
|
130
|
+
localMediaPaths.push(result.transcript.localPath);
|
|
131
|
+
localMediaTypes.push('audio/wav');
|
|
132
|
+
} else if (result.transcript.remoteUrl) {
|
|
133
|
+
remoteMediaUrls.push(result.transcript.remoteUrl);
|
|
134
|
+
}
|
|
135
|
+
} else if (result.type === 'other') {
|
|
136
|
+
if (result.localPath) {
|
|
137
|
+
otherParts.push(`[Attachment: ${result.localPath}]`);
|
|
138
|
+
localMediaPaths.push(result.localPath);
|
|
139
|
+
localMediaTypes.push('application/octet-stream');
|
|
140
|
+
} else {
|
|
141
|
+
otherParts.push(`[Attachment: ${result.filename}]`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
voiceText: formatVoiceText(transcripts),
|
|
148
|
+
imageUrls,
|
|
149
|
+
otherInfo: otherParts.join('\n'),
|
|
150
|
+
transcripts,
|
|
151
|
+
localMediaPaths,
|
|
152
|
+
localMediaTypes,
|
|
153
|
+
remoteMediaUrls,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ── 语音处理 ──
|
|
158
|
+
|
|
159
|
+
async function processVoiceAttachment(
|
|
160
|
+
att: MessageAttachment,
|
|
161
|
+
sttCfg: ReturnType<typeof resolveSTTConfig>,
|
|
162
|
+
audioPolicy: AudioPolicyResolved,
|
|
163
|
+
log?: Log,
|
|
164
|
+
): Promise<VoiceTranscript> {
|
|
165
|
+
const asrReferText = att.asr_refer_text?.trim() || undefined;
|
|
166
|
+
// 远端 URL 兜底:优先 wav_url,其次原始 url
|
|
167
|
+
const remoteUrl = normalizeUrl(att.voice_wav_url) || normalizeUrl(att.url) || undefined;
|
|
168
|
+
|
|
169
|
+
// STT 未配置:直接走 ASR / fallback
|
|
170
|
+
if (!sttCfg) {
|
|
171
|
+
if (asrReferText) {
|
|
172
|
+
log?.debug?.(`Voice: using asr_refer_text (STT not configured)`);
|
|
173
|
+
return { text: asrReferText, source: 'asr', asrReferText, remoteUrl };
|
|
174
|
+
}
|
|
175
|
+
return {
|
|
176
|
+
text: '[Voice message - transcription unavailable]',
|
|
177
|
+
source: 'fallback',
|
|
178
|
+
asrReferText,
|
|
179
|
+
remoteUrl,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
let localPath: string | undefined;
|
|
184
|
+
let duration: number | undefined;
|
|
185
|
+
|
|
186
|
+
try {
|
|
187
|
+
const wavUrl = normalizeUrl(att.voice_wav_url);
|
|
188
|
+
if (wavUrl) {
|
|
189
|
+
const downloaded = await downloadMediaFile(wavUrl, undefined, log);
|
|
190
|
+
if (downloaded) {
|
|
191
|
+
localPath = downloaded;
|
|
192
|
+
log?.debug?.(`Voice: downloaded WAV from voice_wav_url`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (!localPath) {
|
|
197
|
+
const silkUrl = normalizeUrl(att.url);
|
|
198
|
+
if (silkUrl) {
|
|
199
|
+
const silkPath = await downloadMediaFile(silkUrl, att.filename, log);
|
|
200
|
+
if (silkPath) {
|
|
201
|
+
const ext = path.extname(silkPath).toLowerCase();
|
|
202
|
+
if (audioPolicy.sttDirectFormats.includes(ext)) {
|
|
203
|
+
localPath = silkPath;
|
|
204
|
+
} else {
|
|
205
|
+
const wavResult = await convertSilkToWav(silkPath);
|
|
206
|
+
if (wavResult) {
|
|
207
|
+
localPath = wavResult.wavPath;
|
|
208
|
+
duration = wavResult.duration / 1000;
|
|
209
|
+
log?.debug?.(`Voice: SILK→WAV (${formatDuration(duration)})`);
|
|
210
|
+
} else {
|
|
211
|
+
localPath = silkPath;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
} catch (err) {
|
|
218
|
+
log?.error(`Voice download/convert failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (localPath) {
|
|
222
|
+
try {
|
|
223
|
+
const transcript = await transcribeAudio(localPath, cfg2stt(sttCfg));
|
|
224
|
+
if (transcript) {
|
|
225
|
+
log?.debug?.(`Voice STT: ${transcript.slice(0, 80)}...`);
|
|
226
|
+
return { text: transcript, source: 'stt', duration, localPath, remoteUrl, asrReferText };
|
|
227
|
+
}
|
|
228
|
+
} catch (err) {
|
|
229
|
+
log?.error(`Voice STT failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (asrReferText) {
|
|
234
|
+
return { text: asrReferText, source: 'asr', duration, localPath, remoteUrl, asrReferText };
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return {
|
|
238
|
+
text: '[Voice message - transcription failed]',
|
|
239
|
+
source: 'fallback',
|
|
240
|
+
duration,
|
|
241
|
+
localPath,
|
|
242
|
+
remoteUrl,
|
|
243
|
+
asrReferText,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// ── 配置 ──
|
|
248
|
+
|
|
249
|
+
interface AudioPolicyResolved {
|
|
250
|
+
sttDirectFormats: string[];
|
|
251
|
+
uploadDirectFormats: string[];
|
|
252
|
+
transcodeEnabled: boolean;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function resolveAudioPolicy(cfg: Record<string, unknown>): AudioPolicyResolved {
|
|
256
|
+
const channels = cfg.channels as Record<string, unknown> | undefined;
|
|
257
|
+
const qqbot = channels?.qqbot as Record<string, unknown> | undefined;
|
|
258
|
+
const policy = qqbot?.audioFormatPolicy as Record<string, unknown> | undefined;
|
|
259
|
+
|
|
260
|
+
return {
|
|
261
|
+
sttDirectFormats: normalizeFormats((policy?.sttDirectFormats as string[]) ?? []),
|
|
262
|
+
uploadDirectFormats: normalizeFormats(
|
|
263
|
+
(policy?.uploadDirectFormats as string[]) ??
|
|
264
|
+
(qqbot?.voiceDirectUploadFormats as string[]) ??
|
|
265
|
+
['.wav', '.mp3', '.silk'],
|
|
266
|
+
),
|
|
267
|
+
transcodeEnabled: (policy?.transcodeEnabled as boolean) !== false,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function normalizeFormats(formats: string[]): string[] {
|
|
272
|
+
return formats.map((f) => {
|
|
273
|
+
const lower = f.toLowerCase().trim();
|
|
274
|
+
return lower.startsWith('.') ? lower : `.${lower}`;
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function cfg2stt(sttCfg: NonNullable<ReturnType<typeof resolveSTTConfig>>): Record<string, unknown> {
|
|
279
|
+
return { channels: { qqbot: { stt: sttCfg } } };
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// ── 文件工具 ──
|
|
283
|
+
|
|
284
|
+
function normalizeUrl(url: string | undefined): string {
|
|
285
|
+
if (!url) return '';
|
|
286
|
+
return url.startsWith('//') ? `https:${url}` : url;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
async function downloadMediaFile(
|
|
290
|
+
url: string,
|
|
291
|
+
filename?: string,
|
|
292
|
+
log?: Log,
|
|
293
|
+
): Promise<string | null> {
|
|
294
|
+
// 仅允许 HTTPS(安全策略)
|
|
295
|
+
if (!url.startsWith('https://')) {
|
|
296
|
+
log?.debug?.(`Skipping non-HTTPS URL: ${url.slice(0, 80)}`);
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
try {
|
|
301
|
+
const result = await downloadRemoteMedia({
|
|
302
|
+
url,
|
|
303
|
+
subdir: 'qqbot/downloads',
|
|
304
|
+
originalFilename: filename,
|
|
305
|
+
maxBytes: 500 * 1024 * 1024,
|
|
306
|
+
timeoutMs: 120_000,
|
|
307
|
+
});
|
|
308
|
+
log?.debug?.(`Downloaded: ${result.path}`);
|
|
309
|
+
return result.path;
|
|
310
|
+
} catch (err) {
|
|
311
|
+
log?.error(`Download failed: ${url.slice(0, 80)} — ${err instanceof Error ? err.message : String(err)}`);
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 动态策略注入中间件
|
|
3
|
+
*
|
|
4
|
+
* 每条消息到达时,按 groupId 解析群配置并注入 `ctx.state.policy`。
|
|
5
|
+
* SDK 内置中间件(mentionGate、historyBuffer 等)自动从 `ctx.state.policy`
|
|
6
|
+
* 读取动态策略作为 fallback,无需各自注册 `resolveConfig`。
|
|
7
|
+
*
|
|
8
|
+
* 优先级链(与旧版一致):
|
|
9
|
+
* 具体群配置 > 通配符 "*" > defaultRequireMention > 硬编码默认值
|
|
10
|
+
*/
|
|
11
|
+
import type { Middleware } from '@tencent-connect/qqbot-nodejs';
|
|
12
|
+
import type { ResolvedQQBotAccount } from '../types.js';
|
|
13
|
+
import { resolveGroupConfigFromAccount } from '../config.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 创建 policy injector 中间件。
|
|
17
|
+
*
|
|
18
|
+
* 注入的 `ctx.state.policy` 结构:
|
|
19
|
+
* ```
|
|
20
|
+
* {
|
|
21
|
+
* scope: "c2c" | "group",
|
|
22
|
+
* group: {
|
|
23
|
+
* requireMention: boolean,
|
|
24
|
+
* ignoreOtherMentions: boolean,
|
|
25
|
+
* historyLimit: number,
|
|
26
|
+
* // ... 可扩展任意字段
|
|
27
|
+
* }
|
|
28
|
+
* }
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* 使用者可在 `policy.group` 上添加自定义字段,如:
|
|
32
|
+
* ```
|
|
33
|
+
* ctx.state.policy.toolPolicy = "full";
|
|
34
|
+
* ```
|
|
35
|
+
* 自定义中间件通过 `ctx.state.policy` 读取即可。
|
|
36
|
+
*/
|
|
37
|
+
export function createPolicyInjector(account: ResolvedQQBotAccount): Middleware {
|
|
38
|
+
return async (ctx, next) => {
|
|
39
|
+
const msg = ctx.message as any;
|
|
40
|
+
const scope = msg.kind as 'c2c' | 'group' | 'dm' | 'channel';
|
|
41
|
+
|
|
42
|
+
const policy: Record<string, unknown> = {
|
|
43
|
+
scope,
|
|
44
|
+
accountId: account.accountId,
|
|
45
|
+
// 访问控制:dmPolicy(c2c) / groupPolicy(group),默认 allowlist
|
|
46
|
+
c2cMode: account.config?.dmPolicy ?? 'allowlist',
|
|
47
|
+
groupMode: account.config?.groupPolicy ?? 'allowlist',
|
|
48
|
+
allowFrom: account.config?.allowFrom ?? [],
|
|
49
|
+
groupAllowFrom: account.config?.groupAllowFrom ?? [],
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
if (scope === 'group') {
|
|
53
|
+
const groupOpenid = msg.groupOpenid ?? '';
|
|
54
|
+
const groupCfg = resolveGroupConfigFromAccount(account, groupOpenid);
|
|
55
|
+
policy.group = {
|
|
56
|
+
requireMention: groupCfg.requireMention,
|
|
57
|
+
ignoreOtherMentions: groupCfg.ignoreOtherMentions,
|
|
58
|
+
historyLimit: groupCfg.historyLimit,
|
|
59
|
+
prompt: groupCfg.prompt,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
ctx.state.policy = policy;
|
|
64
|
+
await next();
|
|
65
|
+
};
|
|
66
|
+
}
|
|
@@ -78,26 +78,41 @@ declare module "openclaw/plugin-sdk" {
|
|
|
78
78
|
export interface PluginRuntime {
|
|
79
79
|
/** OpenClaw 框架版本号,如 "2026.3.31" */
|
|
80
80
|
version: string;
|
|
81
|
-
/**
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
/** Channel 接口 - 使用 any 类型以兼容 SDK 内部复杂类型 */
|
|
81
|
+
/** 配置访问(运行时 config 在 config.current(),非顶层 getConfig()) */
|
|
82
|
+
config?: {
|
|
83
|
+
/** 获取当前运行时配置快照(只读) */
|
|
84
|
+
current(): OpenClawConfig;
|
|
85
|
+
};
|
|
86
|
+
/** Channel 接口 */
|
|
88
87
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
89
88
|
channel?: any;
|
|
90
|
-
/**
|
|
89
|
+
/** 日志函数(旧版,建议使用 logging.getChildLogger) */
|
|
91
90
|
log: {
|
|
92
91
|
info: (message: string, ...args: unknown[]) => void;
|
|
93
92
|
warn: (message: string, ...args: unknown[]) => void;
|
|
94
93
|
error: (message: string, ...args: unknown[]) => void;
|
|
95
94
|
debug: (message: string, ...args: unknown[]) => void;
|
|
96
95
|
};
|
|
97
|
-
/**
|
|
96
|
+
/** 结构化日志(框架自动追加 channel 前缀) */
|
|
97
|
+
logging: {
|
|
98
|
+
shouldLogVerbose: () => boolean;
|
|
99
|
+
getChildLogger: (
|
|
100
|
+
bindings?: Record<string, unknown>,
|
|
101
|
+
opts?: { level?: string },
|
|
102
|
+
) => RuntimeLogger;
|
|
103
|
+
};
|
|
104
|
+
/** 其他运行时方法(不要假设 getConfig/getDataDir/setConfig 可用) */
|
|
98
105
|
[key: string]: unknown;
|
|
99
106
|
}
|
|
100
107
|
|
|
108
|
+
/** 结构化日志器 */
|
|
109
|
+
export interface RuntimeLogger {
|
|
110
|
+
debug?: (message: string, meta?: Record<string, unknown>) => void;
|
|
111
|
+
info: (message: string, meta?: Record<string, unknown>) => void;
|
|
112
|
+
warn: (message: string, meta?: Record<string, unknown>) => void;
|
|
113
|
+
error: (message: string, meta?: Record<string, unknown>) => void;
|
|
114
|
+
}
|
|
115
|
+
|
|
101
116
|
// ============ 插件 API ============
|
|
102
117
|
|
|
103
118
|
/**
|
|
@@ -219,7 +234,6 @@ declare module "openclaw/plugin-sdk" {
|
|
|
219
234
|
tokenFile?: string;
|
|
220
235
|
useEnv?: boolean;
|
|
221
236
|
name?: string;
|
|
222
|
-
imageServerBaseUrl?: string;
|
|
223
237
|
[key: string]: unknown;
|
|
224
238
|
}
|
|
225
239
|
|
|
@@ -661,6 +675,63 @@ declare module "openclaw/plugin-sdk" {
|
|
|
661
675
|
export function normalizeAccountId(accountId: string | undefined | null): string;
|
|
662
676
|
}
|
|
663
677
|
|
|
678
|
+
declare module "openclaw/plugin-sdk/setup" {
|
|
679
|
+
export interface ChannelSetupWizardStatus {
|
|
680
|
+
channelLabel: string;
|
|
681
|
+
configuredLabel: string;
|
|
682
|
+
unconfiguredLabel: string;
|
|
683
|
+
configuredHint?: string;
|
|
684
|
+
unconfiguredHint?: string;
|
|
685
|
+
configuredScore?: number;
|
|
686
|
+
unconfiguredScore?: number;
|
|
687
|
+
includeStatusLine?: boolean;
|
|
688
|
+
resolveConfigured: (params: { cfg: unknown; accountId: string }) => boolean;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
export interface ChannelSetupWizard {
|
|
692
|
+
channel: string;
|
|
693
|
+
status: ChannelSetupWizardStatus;
|
|
694
|
+
/** 解析配置时使用哪个账户 ID:无账户时默认 'default' */
|
|
695
|
+
resolveAccountIdForConfigure?: (params: {
|
|
696
|
+
cfg: unknown;
|
|
697
|
+
prompter: unknown;
|
|
698
|
+
options?: unknown;
|
|
699
|
+
accountOverride?: string;
|
|
700
|
+
shouldPromptAccountIds: boolean;
|
|
701
|
+
listAccountIds: (cfg: unknown) => string[];
|
|
702
|
+
defaultAccountId: string;
|
|
703
|
+
}) => string | Promise<string>;
|
|
704
|
+
resolveShouldPromptAccountIds?: (params: {
|
|
705
|
+
cfg: unknown;
|
|
706
|
+
options?: unknown;
|
|
707
|
+
shouldPromptAccountIds: boolean;
|
|
708
|
+
}) => boolean;
|
|
709
|
+
credentials?: Array<{ id: string; label: string }>;
|
|
710
|
+
textInputs?: Array<{ id: string; label: string; placeholder?: string }>;
|
|
711
|
+
onStatusChange?: (params: { cfg: unknown }) => void | Promise<void>;
|
|
712
|
+
configure?: (params: { cfg: unknown; accountId: string; prompter: unknown; runtime: unknown }) => Promise<unknown>;
|
|
713
|
+
finalize: (params: { cfg: unknown; accountId: string; prompter: unknown; runtime: unknown }) => Promise<unknown>;
|
|
714
|
+
enable?: (cfg: unknown) => unknown;
|
|
715
|
+
disable?: (cfg: unknown) => unknown;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
export function createStandardChannelSetupStatus(
|
|
719
|
+
opts: ChannelSetupWizardStatus,
|
|
720
|
+
): ChannelSetupWizardStatus;
|
|
721
|
+
|
|
722
|
+
export function setSetupChannelEnabled(
|
|
723
|
+
cfg: unknown,
|
|
724
|
+
channel: string,
|
|
725
|
+
enabled: boolean,
|
|
726
|
+
): void;
|
|
727
|
+
|
|
728
|
+
export const DEFAULT_ACCOUNT_ID: string;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
declare module "openclaw/plugin-sdk/setup-tools" {
|
|
732
|
+
export function formatDocsLink(href: string, text?: string): string;
|
|
733
|
+
}
|
|
734
|
+
|
|
664
735
|
declare module "openclaw/plugin-sdk/approval-runtime" {
|
|
665
736
|
export interface ExecApprovalReplyMetadata {
|
|
666
737
|
approvalId: string;
|