@izhimu/qq 0.5.1 → 0.6.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.
Files changed (38) hide show
  1. package/README.md +12 -16
  2. package/dist/index.d.ts +5 -11
  3. package/dist/index.js +9 -18
  4. package/dist/src/adapters/message.d.ts +15 -4
  5. package/dist/src/adapters/message.js +179 -124
  6. package/dist/src/channel.d.ts +2 -7
  7. package/dist/src/channel.js +231 -312
  8. package/dist/src/core/auth.d.ts +67 -0
  9. package/dist/src/core/auth.js +154 -0
  10. package/dist/src/core/config.d.ts +5 -7
  11. package/dist/src/core/config.js +6 -8
  12. package/dist/src/core/connection.d.ts +6 -5
  13. package/dist/src/core/connection.js +17 -70
  14. package/dist/src/core/dispatch.d.ts +7 -54
  15. package/dist/src/core/dispatch.js +210 -398
  16. package/dist/src/core/event-handler.d.ts +42 -0
  17. package/dist/src/core/event-handler.js +171 -0
  18. package/dist/src/core/request.d.ts +3 -8
  19. package/dist/src/core/request.js +13 -126
  20. package/dist/src/core/runtime.d.ts +2 -11
  21. package/dist/src/core/runtime.js +0 -47
  22. package/dist/src/runtime.d.ts +3 -0
  23. package/dist/src/runtime.js +3 -0
  24. package/dist/src/setup-surface.d.ts +2 -0
  25. package/dist/src/setup-surface.js +59 -0
  26. package/dist/src/types/index.d.ts +69 -25
  27. package/dist/src/types/index.js +3 -4
  28. package/dist/src/utils/cqcode.d.ts +0 -9
  29. package/dist/src/utils/cqcode.js +0 -17
  30. package/dist/src/utils/index.d.ts +0 -17
  31. package/dist/src/utils/index.js +17 -154
  32. package/dist/src/utils/log.js +2 -2
  33. package/dist/src/utils/markdown.d.ts +5 -0
  34. package/dist/src/utils/markdown.js +57 -5
  35. package/openclaw.plugin.json +3 -2
  36. package/package.json +9 -11
  37. package/dist/src/onboarding.d.ts +0 -10
  38. package/dist/src/onboarding.js +0 -98
@@ -0,0 +1,154 @@
1
+ /**
2
+ * QQ 频道授权模块
3
+ *
4
+ * 使用三明治模式 (Sandwich Pattern) 集成 OpenClaw 原生授权系统:
5
+ *
6
+ * 1. 预处理层 (QQ 特有): denyFrom, policy='deny'
7
+ * 2. SDK 层 (OpenClaw 原生): commands.allowFrom 检查
8
+ * 3. 后处理层 (QQ 特有): allowFrom 覆盖, policy='allowlist'
9
+ *
10
+ * 授权优先级链 (从高到低):
11
+ * 1. denyFrom - 绝对拒绝
12
+ * 2. policy='deny' - 频道级全局拒绝
13
+ * 3. allowFrom - 频道级白名单 (最高优先授权)
14
+ * 4. commands.allowFrom.qq - 全局 QQ 专属授权
15
+ * 5. commands.allowFrom["*"] - 全局通配授权
16
+ * 6. policy='allow' - 频道级全局允许
17
+ * 7. policy='allowlist' 未匹配 - 拒绝
18
+ * 8. 默认 - 拒绝
19
+ */
20
+ import { Logger as log } from "../utils/index.js";
21
+ // ============================================================================
22
+ // Helper Functions
23
+ // ============================================================================
24
+ /**
25
+ * 检查发送者是否在 allowFrom 列表中
26
+ */
27
+ function isSenderInList(senderId, allowFrom) {
28
+ if (!allowFrom || allowFrom.length === 0)
29
+ return false;
30
+ return allowFrom.includes(senderId);
31
+ }
32
+ // ============================================================================
33
+ // Main Function
34
+ // ============================================================================
35
+ /**
36
+ * 解析 QQ 频道命令授权
37
+ *
38
+ * 使用三明治模式整合 QQ 特有配置与 OpenClaw 全局授权系统
39
+ */
40
+ export function resolveQQCommandAuthorization(params) {
41
+ const { senderId, qqConfig } = params;
42
+ // ============================================================
43
+ // 第一层: 预处理 (QQ 特有的"硬拒绝"规则)
44
+ // ============================================================
45
+ // Level 1: denyFrom 黑名单 (绝对拒绝,不可被任何规则覆盖)
46
+ if (isSenderInList(senderId, qqConfig.denyFrom)) {
47
+ log.info("auth", `Authorization denied for user ${senderId}: in denyFrom list`);
48
+ return {
49
+ providerId: "qq",
50
+ ownerList: [],
51
+ senderId,
52
+ senderIsOwner: false,
53
+ isAuthorizedSender: false,
54
+ denialReason: "denyFrom",
55
+ };
56
+ }
57
+ // Level 2: policy='deny' (频道级全局拒绝)
58
+ if (qqConfig.policy === "deny") {
59
+ log.info("auth", `Authorization denied for user ${senderId}: channel policy is deny`);
60
+ return {
61
+ providerId: "qq",
62
+ ownerList: [],
63
+ senderId,
64
+ senderIsOwner: false,
65
+ isAuthorizedSender: false,
66
+ denialReason: "policy_deny",
67
+ };
68
+ }
69
+ // ============================================================
70
+ // 第二层: 频道级 allowFrom (最高优先级授权)
71
+ // 这一层优先于 SDK,符合"最小权限原则"
72
+ // ============================================================
73
+ // Level 3: 频道级 allowFrom 白名单
74
+ if (qqConfig.allowFrom?.length && qqConfig.allowFrom.includes(senderId)) {
75
+ log.debug("auth", `Authorization granted for user ${senderId}: in channel allowFrom`);
76
+ return {
77
+ providerId: "qq",
78
+ ownerList: qqConfig.allowFrom,
79
+ senderId,
80
+ senderIsOwner: true,
81
+ isAuthorizedSender: true,
82
+ matchedBy: "channel_allowFrom",
83
+ };
84
+ }
85
+ // ============================================================
86
+ // 第三层: 后处理 (QQ 特有的覆盖规则)
87
+ // ============================================================
88
+ // Level 6: policy='allow' (频道级全局允许)
89
+ if (qqConfig.policy === "allow") {
90
+ log.debug("auth", `Authorization granted for user ${senderId}: channel policy is allow`);
91
+ return {
92
+ providerId: "qq",
93
+ ownerList: [],
94
+ senderId,
95
+ senderIsOwner: false,
96
+ isAuthorizedSender: true,
97
+ matchedBy: "policy_allow",
98
+ };
99
+ }
100
+ // Level 7: policy='allowlist' 但未匹配 allowFrom
101
+ if (qqConfig.policy === "allowlist") {
102
+ log.info("auth", `Authorization denied for user ${senderId}: not in allowlist`);
103
+ return {
104
+ providerId: "qq",
105
+ ownerList: [],
106
+ senderId,
107
+ senderIsOwner: false,
108
+ isAuthorizedSender: false,
109
+ denialReason: "not_in_allowlist",
110
+ };
111
+ }
112
+ // Level 8: 默认拒绝
113
+ log.info("auth", `Authorization denied for user ${senderId}: no matching authorization rule`);
114
+ return {
115
+ providerId: "qq",
116
+ ownerList: [],
117
+ senderId,
118
+ senderIsOwner: false,
119
+ isAuthorizedSender: false,
120
+ denialReason: "default_deny",
121
+ };
122
+ }
123
+ // ============================================================================
124
+ // Utility Functions for dispatch.ts
125
+ // ============================================================================
126
+ /**
127
+ * 默认群组配置(用于私聊场景)
128
+ */
129
+ const DEFAULT_GROUP_CONFIG = {
130
+ requireMention: false,
131
+ requirePoke: false,
132
+ historyLimit: 20,
133
+ };
134
+ /**
135
+ * 根据 chatType 获取对应的 QQ 配置
136
+ * 统一返回 QQGroupConfig 类型,私聊时使用默认值填充群组特有字段
137
+ */
138
+ export function getQQConfigByChatType(isGroup, groupId, config) {
139
+ // 私聊:返回 messageDirect + 默认群组配置
140
+ if (!isGroup) {
141
+ return {
142
+ ...DEFAULT_GROUP_CONFIG,
143
+ ...config.messageDirect,
144
+ };
145
+ }
146
+ // 群聊:检查是否有特定群组配置
147
+ if (groupId && config.messageGroupsCustom[groupId]) {
148
+ return {
149
+ ...config.messageGroup,
150
+ ...config.messageGroupsCustom[groupId],
151
+ };
152
+ }
153
+ return config.messageGroup;
154
+ }
@@ -1,10 +1,7 @@
1
- /**
2
- * QQ 配置管理
3
- */
4
- import type { OpenClawConfig } from "openclaw/plugin-sdk";
5
- import type { QQConfig } from "../types";
1
+ import type { OpenClawConfig } from "openclaw/plugin-sdk/core";
2
+ import type { QQAccount } from "../types";
6
3
  import { z } from "zod";
7
- export declare const CHANNEL_ID = "qq";
4
+ export declare const QQ_CHANNEL = "qq";
8
5
  export declare const DEBUG_MODE = false;
9
6
  /**
10
7
  * 列出所有 QQ 账户ID
@@ -15,7 +12,8 @@ export declare function listQQAccountIds(cfg: OpenClawConfig): string[];
15
12
  */
16
13
  export declare function resolveQQAccount(params: {
17
14
  cfg: OpenClawConfig;
18
- }): QQConfig;
15
+ accountId?: string | null;
16
+ }): QQAccount;
19
17
  export declare const QQDirectConfigSchema: z.ZodObject<{
20
18
  policy: z.ZodDefault<z.ZodEnum<{
21
19
  allow: "allow";
@@ -1,17 +1,14 @@
1
- /**
2
- * QQ 配置管理
3
- */
4
- import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk";
1
+ import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/core";
5
2
  import { z } from "zod";
6
- export const CHANNEL_ID = "qq";
3
+ export const QQ_CHANNEL = "qq";
7
4
  export const DEBUG_MODE = false;
8
5
  /**
9
6
  * 列出所有 QQ 账户ID
10
7
  */
11
8
  export function listQQAccountIds(cfg) {
12
- const config = cfg.channels?.[CHANNEL_ID];
9
+ const config = cfg.channels?.[QQ_CHANNEL];
13
10
  if (config?.wsUrl) {
14
- return [DEFAULT_ACCOUNT_ID];
11
+ return ["default"];
15
12
  }
16
13
  return [];
17
14
  }
@@ -19,8 +16,9 @@ export function listQQAccountIds(cfg) {
19
16
  * 解析 QQ 账户配置
20
17
  */
21
18
  export function resolveQQAccount(params) {
22
- const config = params.cfg.channels?.[CHANNEL_ID];
19
+ const config = params.cfg.channels?.[QQ_CHANNEL];
23
20
  return {
21
+ accountId: params.accountId ?? DEFAULT_ACCOUNT_ID,
24
22
  enabled: config?.enabled !== false,
25
23
  wsUrl: config?.wsUrl ?? "",
26
24
  token: config?.accessToken ?? "",
@@ -3,7 +3,7 @@
3
3
  * Handles per-account WebSocket connections with auto-reconnect and heartbeat
4
4
  */
5
5
  import EventEmitter from 'events';
6
- import type { NapCatResp, QQConfig, ConnectionStatus, NapCatAction } from '../types';
6
+ import type { NapCatResp, QQAccount, ConnectionStatus, NapCatAction } from '../types';
7
7
  /**
8
8
  * Connection Manager for a single NapCat account
9
9
  */
@@ -14,11 +14,9 @@ export declare class ConnectionManager extends EventEmitter {
14
14
  private lastHeartbeatTime;
15
15
  private heartbeatCheckTimer?;
16
16
  private reconnectTimer?;
17
- private totalReconnectAttempts;
18
17
  private shouldReconnect;
19
18
  private pendingRequests;
20
- private healthStatus;
21
- constructor(config: QQConfig);
19
+ constructor(config: QQAccount);
22
20
  /**
23
21
  * Start the connection
24
22
  */
@@ -57,7 +55,6 @@ export declare class ConnectionManager extends EventEmitter {
57
55
  private handleClose;
58
56
  private handleConnectionFailed;
59
57
  private handleResponse;
60
- private isNormalClosure;
61
58
  private scheduleReconnect;
62
59
  private clearReconnectTimer;
63
60
  /**
@@ -75,3 +72,7 @@ export declare class ConnectionManager extends EventEmitter {
75
72
  isConnected(): boolean;
76
73
  }
77
74
  export declare function failResp<T>(msg?: string): Promise<NapCatResp<T>>;
75
+ /**
76
+ * Send API request via current connection
77
+ */
78
+ export declare function sendRequest<T>(action: NapCatAction, params?: unknown): Promise<NapCatResp<T>>;
@@ -4,10 +4,11 @@
4
4
  */
5
5
  import WebSocket from 'ws';
6
6
  import EventEmitter from 'events';
7
- import { Logger as log, generateEchoId, calculateBackoff, getCloseCodeMessage, } from '../utils/index.js';
8
- const MAX_RECONNECT_ATTEMPTS = -1;
7
+ import { Logger as log, generateEchoId, getCloseCodeMessage, } from '../utils/index.js';
8
+ import { getConnection } from './runtime.js';
9
9
  const REQUEST_TIMEOUT = 30000; // 30 seconds
10
- const HEARTBEAT_TIMEOUT = 120000; // 120 seconds - time without heartbeat before reconnecting (increased for NapCat compatibility)
10
+ const RECONNECT_INTERVAL = 5000; // 5 seconds - delay between reconnection attempts
11
+ const HEARTBEAT_TIMEOUT = 120000; // 120 seconds - time without heartbeat before reconnecting
11
12
  const HEARTBEAT_CHECK_INTERVAL = 60000; // 60 seconds - how often to check for heartbeat timeout
12
13
  /**
13
14
  * Connection Manager for a single NapCat account
@@ -21,16 +22,9 @@ export class ConnectionManager extends EventEmitter {
21
22
  heartbeatCheckTimer;
22
23
  // Reconnection
23
24
  reconnectTimer;
24
- totalReconnectAttempts = 0;
25
25
  shouldReconnect = true;
26
26
  // Pending requests
27
27
  pendingRequests = new Map();
28
- // Health status
29
- healthStatus = {
30
- healthy: false,
31
- lastHeartbeatAt: 0,
32
- consecutiveFailures: 0,
33
- };
34
28
  constructor(config) {
35
29
  super();
36
30
  this.config = config;
@@ -43,7 +37,6 @@ export class ConnectionManager extends EventEmitter {
43
37
  */
44
38
  async start() {
45
39
  if (this.state === 'connected' || this.state === 'connecting') {
46
- log.debug('connection', `Already ${this.state}`);
47
40
  return;
48
41
  }
49
42
  this.shouldReconnect = true;
@@ -100,20 +93,6 @@ export class ConnectionManager extends EventEmitter {
100
93
  this.ws.on('message', this.handleMessage.bind(this));
101
94
  this.ws.on('error', this.handleError.bind(this));
102
95
  this.ws.on('close', this.handleClose.bind(this));
103
- // Wait for connection to be established or failed
104
- await new Promise((resolve, reject) => {
105
- const timeout = setTimeout(() => {
106
- reject(new Error('Connection timeout'));
107
- }, 30000);
108
- this.once('connected', () => {
109
- clearTimeout(timeout);
110
- resolve();
111
- });
112
- this.once('failed', (error) => {
113
- clearTimeout(timeout);
114
- reject(error);
115
- });
116
- });
117
96
  }
118
97
  catch (error) {
119
98
  log.error('connection', `Connection failed:`, error);
@@ -127,7 +106,6 @@ export class ConnectionManager extends EventEmitter {
127
106
  if (this.pendingRequests.size === 0) {
128
107
  return;
129
108
  }
130
- log.debug('connection', `Clearing ${this.pendingRequests.size} pending requests: ${reason}`);
131
109
  for (const [_echo, pending] of this.pendingRequests) {
132
110
  clearTimeout(pending.timeout);
133
111
  pending.reject(new Error(`Connection closed: ${reason}`));
@@ -146,23 +124,10 @@ export class ConnectionManager extends EventEmitter {
146
124
  const elapsed = Date.now() - this.lastHeartbeatTime;
147
125
  if (elapsed > HEARTBEAT_TIMEOUT && this.isConnected()) {
148
126
  log.warn('connection', `Heartbeat timeout (${elapsed}ms since last heartbeat), reconnecting...`);
149
- this.healthStatus = {
150
- healthy: false,
151
- lastHeartbeatAt: this.lastHeartbeatTime,
152
- consecutiveFailures: this.healthStatus.consecutiveFailures + 1,
153
- };
154
- this.emit('heartbeat', this.healthStatus);
155
127
  // Close connection and trigger immediate reconnect
156
128
  this.setState('disconnected');
157
129
  this.close('Heartbeat timeout').then(() => {
158
130
  if (this.shouldReconnect) {
159
- // Increment total reconnect attempts
160
- this.totalReconnectAttempts++;
161
- // Emit reconnecting event for external status updates
162
- this.emit('reconnecting', {
163
- reason: 'heartbeat-timeout',
164
- totalAttempts: this.totalReconnectAttempts,
165
- });
166
131
  this.connect().catch(error => {
167
132
  log.error('connection', `Reconnect failed:`, error);
168
133
  });
@@ -170,7 +135,6 @@ export class ConnectionManager extends EventEmitter {
170
135
  });
171
136
  }
172
137
  }, HEARTBEAT_CHECK_INTERVAL);
173
- log.debug('connection', 'Started heartbeat timeout detection');
174
138
  }
175
139
  /**
176
140
  * Stop heartbeat timeout detection
@@ -179,7 +143,6 @@ export class ConnectionManager extends EventEmitter {
179
143
  if (this.heartbeatCheckTimer) {
180
144
  clearInterval(this.heartbeatCheckTimer);
181
145
  this.heartbeatCheckTimer = undefined;
182
- log.debug('connection', 'Stopped heartbeat timeout detection');
183
146
  }
184
147
  }
185
148
  /**
@@ -226,9 +189,7 @@ export class ConnectionManager extends EventEmitter {
226
189
  // Handle event
227
190
  if ('post_type' in message) {
228
191
  this.emit('event', message);
229
- return;
230
192
  }
231
- log.debug('connection', `Received unsolicited response:`, message);
232
193
  }
233
194
  catch (error) {
234
195
  log.error('connection', `Failed to parse message:`, error);
@@ -239,19 +200,10 @@ export class ConnectionManager extends EventEmitter {
239
200
  */
240
201
  handleMetaEvent(event) {
241
202
  if (event.meta_event_type === 'heartbeat') {
242
- // NapCat sent us a heartbeat - update health status
243
203
  this.lastHeartbeatTime = Date.now();
244
- this.healthStatus = {
245
- healthy: true,
246
- lastHeartbeatAt: this.lastHeartbeatTime,
247
- consecutiveFailures: 0,
248
- };
249
- log.debug('connection', `Received heartbeat`);
250
- this.emit('heartbeat', this.healthStatus);
251
204
  }
252
205
  else if (event.meta_event_type === 'lifecycle') {
253
206
  log.info('connection', `Lifecycle event: ${event.sub_type}`);
254
- this.emit('lifecycle', event);
255
207
  }
256
208
  }
257
209
  handleError(error) {
@@ -266,7 +218,7 @@ export class ConnectionManager extends EventEmitter {
266
218
  if (this.ws === null) {
267
219
  return;
268
220
  }
269
- if (this.shouldReconnect && !this.isNormalClosure(code)) {
221
+ if (this.shouldReconnect) {
270
222
  this.scheduleReconnect();
271
223
  }
272
224
  else {
@@ -298,10 +250,6 @@ export class ConnectionManager extends EventEmitter {
298
250
  else {
299
251
  pending.reject(new Error(response.msg || 'Request failed'));
300
252
  }
301
- log.debug('connection', `Received response for echo: ${echo}`);
302
- }
303
- isNormalClosure(code) {
304
- return code === 1000 || code === 1001;
305
253
  }
306
254
  // ==========================================================================
307
255
  // Reconnection Logic
@@ -310,24 +258,16 @@ export class ConnectionManager extends EventEmitter {
310
258
  if (!this.shouldReconnect) {
311
259
  return;
312
260
  }
313
- if (MAX_RECONNECT_ATTEMPTS != -1 && this.totalReconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
314
- log.error('connection', `Max reconnect attempts reached`);
315
- this.setState('failed', 'Max reconnect attempts reached');
316
- this.emit('max-reconnect-attempts-reached');
317
- return;
318
- }
319
- const delayMs = calculateBackoff(this.totalReconnectAttempts);
320
- log.info('connection', `Scheduling reconnect in ${delayMs}ms (attempt ${this.totalReconnectAttempts + 1}/${MAX_RECONNECT_ATTEMPTS})`);
261
+ log.info('connection', `Scheduling reconnect in ${RECONNECT_INTERVAL}ms`);
321
262
  this.clearReconnectTimer();
322
263
  this.reconnectTimer = setTimeout(async () => {
323
- this.totalReconnectAttempts++;
324
264
  try {
325
265
  await this.connect();
326
266
  }
327
267
  catch (error) {
328
268
  log.error('connection', `Reconnect failed:`, error);
329
269
  }
330
- }, delayMs);
270
+ }, RECONNECT_INTERVAL);
331
271
  }
332
272
  clearReconnectTimer() {
333
273
  if (this.reconnectTimer) {
@@ -366,7 +306,6 @@ export class ConnectionManager extends EventEmitter {
366
306
  };
367
307
  try {
368
308
  this.ws?.send(JSON.stringify(request));
369
- log.debug('connection', `Sent request: ${action} (echo: ${echo})`);
370
309
  }
371
310
  catch (error) {
372
311
  this.pendingRequests.delete(echo);
@@ -394,9 +333,7 @@ export class ConnectionManager extends EventEmitter {
394
333
  return {
395
334
  state: this.state,
396
335
  lastConnected: this.lastHeartbeatTime || undefined,
397
- lastAttempted: this.totalReconnectAttempts > 0 ? Date.now() : undefined,
398
336
  error: this.state === 'failed' ? 'Connection failed' : undefined,
399
- reconnectAttempts: this.totalReconnectAttempts > 0 ? this.totalReconnectAttempts : undefined,
400
337
  };
401
338
  }
402
339
  // ==========================================================================
@@ -416,3 +353,13 @@ export async function failResp(msg = '') {
416
353
  msg
417
354
  });
418
355
  }
356
+ /**
357
+ * Send API request via current connection
358
+ */
359
+ export async function sendRequest(action, params) {
360
+ const connection = getConnection();
361
+ if (!connection) {
362
+ return failResp();
363
+ }
364
+ return connection.sendRequest(action, params);
365
+ }
@@ -1,54 +1,7 @@
1
- /**
2
- * Message Dispatch Module
3
- * Handles routing and dispatching incoming messages to the AI
4
- */
5
- import type { DispatchMessageParams } from '../types';
6
- /**
7
- * Dispatch an incoming message to the AI for processing
8
- */
9
- export declare function dispatchMessage(params: DispatchMessageParams): Promise<void>;
10
- /**
11
- * Handle group message event
12
- */
13
- export declare function handleGroupMessage(event: {
14
- time: number;
15
- self_id: number;
16
- message_id: number;
17
- group_id: number;
18
- user_id: number;
19
- message: Array<{
20
- type: string;
21
- data: Record<string, unknown>;
22
- }>;
23
- raw_message: string;
24
- sender?: {
25
- nickname?: string;
26
- card?: string;
27
- };
28
- }): Promise<void>;
29
- /**
30
- * Handle private message event
31
- */
32
- export declare function handlePrivateMessage(event: {
33
- time: number;
34
- self_id: number;
35
- message_id: number;
36
- user_id: number;
37
- message: Array<{
38
- type: string;
39
- data: Record<string, unknown>;
40
- }>;
41
- raw_message: string;
42
- sender?: {
43
- nickname?: string;
44
- };
45
- }): Promise<void>;
46
- export declare function handlePokeEvent(event: {
47
- user_id: number;
48
- target_id: number;
49
- group_id?: number;
50
- raw_info?: Array<{
51
- type: string;
52
- txt?: string;
53
- }>;
54
- }): Promise<void>;
1
+ import type { OpenClawConfig, PluginRuntime } from "openclaw/plugin-sdk/core";
2
+ import type { QQAccount, InboundMessage } from '../types';
3
+ export declare function createInboundHandler(params: {
4
+ cfg: OpenClawConfig;
5
+ account: QQAccount;
6
+ runtime: PluginRuntime;
7
+ }): (msg: InboundMessage) => Promise<void>;