aicq-chat-plugin 3.8.1 → 3.9.1
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 +80 -80
- package/SKILL.md +78 -78
- package/cli.cjs +356 -356
- package/index.js +417 -375
- package/lib/chat.js +854 -749
- package/lib/crypto.js +168 -168
- package/lib/database.js +455 -455
- package/lib/file-transfer.js +266 -266
- package/lib/handshake.js +147 -147
- package/lib/identity.js +165 -165
- package/lib/package.json +3 -3
- package/lib/server-client.js +380 -337
- package/openclaw.plugin.json +170 -168
- package/package.json +87 -87
- package/postinstall.cjs +27 -27
- package/public/favicon.ico +0 -0
- package/public/icon-16.png +0 -0
- package/public/icon-32.png +0 -0
- package/public/index.html +1468 -1468
- package/public/logo-512.png +0 -0
- package/setup-entry.js +14 -14
- package/src/channel.js +616 -613
- package/src/ui-routes.js +647 -594
package/src/channel.js
CHANGED
|
@@ -1,613 +1,616 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AICQ Channel Plugin — Core Channel Logic
|
|
3
|
-
*
|
|
4
|
-
* Uses the official OpenClaw Channel Plugin SDK:
|
|
5
|
-
* createChatChannelPlugin + createChannelPluginBase
|
|
6
|
-
*
|
|
7
|
-
* Architecture: In-process Channel (no sidecar, no independent port)
|
|
8
|
-
*
|
|
9
|
-
* The runtime store is a mutable object populated by registerFull() in
|
|
10
|
-
* index.js. This keeps the channel-plugin object safe to import during
|
|
11
|
-
* setup-only / discovery modes without pulling in transport clients or
|
|
12
|
-
* database handles.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import {
|
|
16
|
-
createChatChannelPlugin,
|
|
17
|
-
createChannelPluginBase,
|
|
18
|
-
} from "openclaw/plugin-sdk/channel-core";
|
|
19
|
-
|
|
20
|
-
// ── Mutable runtime store ────────────────────────────────────────────
|
|
21
|
-
// Populated lazily by the registerFull() callback in index.js.
|
|
22
|
-
// Adapters that need runtime state check these before acting.
|
|
23
|
-
export const runtime = {
|
|
24
|
-
db: null,
|
|
25
|
-
identity: null,
|
|
26
|
-
serverClient: null,
|
|
27
|
-
handshake: null,
|
|
28
|
-
chat: null,
|
|
29
|
-
dataDir: null,
|
|
30
|
-
serverUrl: null,
|
|
31
|
-
handleGateway: null,
|
|
32
|
-
_initialized: false,
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
// ── Template variable resolver ───────────────────────────────────────
|
|
36
|
-
// OpenClaw stores accountId as-is (e.g. "{{agent.id}}") in config.
|
|
37
|
-
// Plugins must resolve template variables at runtime.
|
|
38
|
-
//
|
|
39
|
-
// The default agent ID in OpenClaw is "main" (DEFAULT_AGENT_ID).
|
|
40
|
-
// When cfg.agents.list is empty/undefined (no explicit agent config),
|
|
41
|
-
// the implicit default agent "main" is used.
|
|
42
|
-
|
|
43
|
-
const OPENCLAW_DEFAULT_AGENT_ID = "main";
|
|
44
|
-
|
|
45
|
-
function resolveTemplateVar(cfg, value) {
|
|
46
|
-
if (typeof value !== "string") return value;
|
|
47
|
-
const match = value.match(/^\{\{(\w[\w.]*)\}\}$/);
|
|
48
|
-
if (!match) return value;
|
|
49
|
-
|
|
50
|
-
const tmplPath = match[1]; // e.g. "agent.id"
|
|
51
|
-
if (tmplPath === "agent.id") {
|
|
52
|
-
// Strategy: look for explicit agents in config first
|
|
53
|
-
const agents = cfg.agents?.list;
|
|
54
|
-
if (Array.isArray(agents) && agents.length > 0) {
|
|
55
|
-
// Use the default=true agent, or the first one
|
|
56
|
-
const defaultAgent = agents.find((a) => a.default) || agents[0];
|
|
57
|
-
if (defaultAgent?.id) return defaultAgent.id;
|
|
58
|
-
}
|
|
59
|
-
// Fallback: OpenClaw's implicit default agent ID
|
|
60
|
-
return OPENCLAW_DEFAULT_AGENT_ID;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return value; // unknown template — return as-is
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// ── Resolved account type ────────────────────────────────────────────
|
|
67
|
-
// This is the object returned by resolveAccount() and consumed by
|
|
68
|
-
// security / pairing / outbound adapters.
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Read the AICQ channel section from OpenClaw config and return a typed
|
|
72
|
-
* account object. This is the setup-safe resolver — no network or DB
|
|
73
|
-
* side effects.
|
|
74
|
-
*/
|
|
75
|
-
function resolveAccount(cfg, accountId) {
|
|
76
|
-
const section = (cfg.channels || {})["aicq-chat"] || {};
|
|
77
|
-
const rawAccountId = accountId || section.accountId || null;
|
|
78
|
-
|
|
79
|
-
if (!rawAccountId) {
|
|
80
|
-
throw new Error(
|
|
81
|
-
"aicq-chat: accountId is required (set channels.aicq-chat.accountId)"
|
|
82
|
-
);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Resolve template variables like {{agent.id}}
|
|
86
|
-
const resolvedAccountId = resolveTemplateVar(cfg, rawAccountId);
|
|
87
|
-
|
|
88
|
-
// Resolve allowFrom entries (may contain {{agent.id}} or friend IDs)
|
|
89
|
-
const rawAllowFrom = section.allowFrom || [];
|
|
90
|
-
const resolvedAllowFrom = Array.isArray(rawAllowFrom)
|
|
91
|
-
? rawAllowFrom.map((entry) => resolveTemplateVar(cfg, entry))
|
|
92
|
-
: rawAllowFrom;
|
|
93
|
-
|
|
94
|
-
return {
|
|
95
|
-
accountId: resolvedAccountId,
|
|
96
|
-
serverUrl: section.serverUrl || "https://aicq.online",
|
|
97
|
-
autoAcceptFriends: section.autoAcceptFriends ?? true,
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
*
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
*
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
*
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
*
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
//
|
|
169
|
-
//
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
"aicq.
|
|
173
|
-
"aicq.friends.
|
|
174
|
-
"aicq.friends.
|
|
175
|
-
"aicq.friends.
|
|
176
|
-
"aicq.friends.
|
|
177
|
-
"aicq.friends.
|
|
178
|
-
"aicq.
|
|
179
|
-
"aicq.
|
|
180
|
-
"aicq.
|
|
181
|
-
"aicq.
|
|
182
|
-
"aicq.
|
|
183
|
-
"aicq.chat.
|
|
184
|
-
"aicq.chat.
|
|
185
|
-
"aicq.chat.
|
|
186
|
-
"aicq.chat.
|
|
187
|
-
"aicq.chat.
|
|
188
|
-
"aicq.chat.
|
|
189
|
-
"aicq.
|
|
190
|
-
"aicq.groups.
|
|
191
|
-
"aicq.groups.
|
|
192
|
-
"aicq.groups.
|
|
193
|
-
"aicq.groups.
|
|
194
|
-
"aicq.
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
//
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
params.
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
params.
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
params.
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
params.
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
//
|
|
270
|
-
//
|
|
271
|
-
//
|
|
272
|
-
//
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
*
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
logger
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
//
|
|
287
|
-
//
|
|
288
|
-
//
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
//
|
|
308
|
-
//
|
|
309
|
-
|
|
310
|
-
const
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
await runtime.handleGateway("aicq.
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
//
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
abortSignal?.
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
//
|
|
511
|
-
|
|
512
|
-
//
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
*
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
*
|
|
562
|
-
*
|
|
563
|
-
*
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
*
|
|
574
|
-
*
|
|
575
|
-
*
|
|
576
|
-
*
|
|
577
|
-
*
|
|
578
|
-
*
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
*
|
|
601
|
-
*
|
|
602
|
-
*
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
1
|
+
/**
|
|
2
|
+
* AICQ Channel Plugin — Core Channel Logic
|
|
3
|
+
*
|
|
4
|
+
* Uses the official OpenClaw Channel Plugin SDK:
|
|
5
|
+
* createChatChannelPlugin + createChannelPluginBase
|
|
6
|
+
*
|
|
7
|
+
* Architecture: In-process Channel (no sidecar, no independent port)
|
|
8
|
+
*
|
|
9
|
+
* The runtime store is a mutable object populated by registerFull() in
|
|
10
|
+
* index.js. This keeps the channel-plugin object safe to import during
|
|
11
|
+
* setup-only / discovery modes without pulling in transport clients or
|
|
12
|
+
* database handles.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
createChatChannelPlugin,
|
|
17
|
+
createChannelPluginBase,
|
|
18
|
+
} from "openclaw/plugin-sdk/channel-core";
|
|
19
|
+
|
|
20
|
+
// ── Mutable runtime store ────────────────────────────────────────────
|
|
21
|
+
// Populated lazily by the registerFull() callback in index.js.
|
|
22
|
+
// Adapters that need runtime state check these before acting.
|
|
23
|
+
export const runtime = {
|
|
24
|
+
db: null,
|
|
25
|
+
identity: null,
|
|
26
|
+
serverClient: null,
|
|
27
|
+
handshake: null,
|
|
28
|
+
chat: null,
|
|
29
|
+
dataDir: null,
|
|
30
|
+
serverUrl: null,
|
|
31
|
+
handleGateway: null,
|
|
32
|
+
_initialized: false,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// ── Template variable resolver ───────────────────────────────────────
|
|
36
|
+
// OpenClaw stores accountId as-is (e.g. "{{agent.id}}") in config.
|
|
37
|
+
// Plugins must resolve template variables at runtime.
|
|
38
|
+
//
|
|
39
|
+
// The default agent ID in OpenClaw is "main" (DEFAULT_AGENT_ID).
|
|
40
|
+
// When cfg.agents.list is empty/undefined (no explicit agent config),
|
|
41
|
+
// the implicit default agent "main" is used.
|
|
42
|
+
|
|
43
|
+
const OPENCLAW_DEFAULT_AGENT_ID = "main";
|
|
44
|
+
|
|
45
|
+
function resolveTemplateVar(cfg, value) {
|
|
46
|
+
if (typeof value !== "string") return value;
|
|
47
|
+
const match = value.match(/^\{\{(\w[\w.]*)\}\}$/);
|
|
48
|
+
if (!match) return value;
|
|
49
|
+
|
|
50
|
+
const tmplPath = match[1]; // e.g. "agent.id"
|
|
51
|
+
if (tmplPath === "agent.id") {
|
|
52
|
+
// Strategy: look for explicit agents in config first
|
|
53
|
+
const agents = cfg.agents?.list;
|
|
54
|
+
if (Array.isArray(agents) && agents.length > 0) {
|
|
55
|
+
// Use the default=true agent, or the first one
|
|
56
|
+
const defaultAgent = agents.find((a) => a.default) || agents[0];
|
|
57
|
+
if (defaultAgent?.id) return defaultAgent.id;
|
|
58
|
+
}
|
|
59
|
+
// Fallback: OpenClaw's implicit default agent ID
|
|
60
|
+
return OPENCLAW_DEFAULT_AGENT_ID;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return value; // unknown template — return as-is
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ── Resolved account type ────────────────────────────────────────────
|
|
67
|
+
// This is the object returned by resolveAccount() and consumed by
|
|
68
|
+
// security / pairing / outbound adapters.
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Read the AICQ channel section from OpenClaw config and return a typed
|
|
72
|
+
* account object. This is the setup-safe resolver — no network or DB
|
|
73
|
+
* side effects.
|
|
74
|
+
*/
|
|
75
|
+
function resolveAccount(cfg, accountId) {
|
|
76
|
+
const section = (cfg.channels || {})["aicq-chat"] || {};
|
|
77
|
+
const rawAccountId = accountId || section.accountId || null;
|
|
78
|
+
|
|
79
|
+
if (!rawAccountId) {
|
|
80
|
+
throw new Error(
|
|
81
|
+
"aicq-chat: accountId is required (set channels.aicq-chat.accountId)"
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Resolve template variables like {{agent.id}}
|
|
86
|
+
const resolvedAccountId = resolveTemplateVar(cfg, rawAccountId);
|
|
87
|
+
|
|
88
|
+
// Resolve allowFrom entries (may contain {{agent.id}} or friend IDs)
|
|
89
|
+
const rawAllowFrom = section.allowFrom || [];
|
|
90
|
+
const resolvedAllowFrom = Array.isArray(rawAllowFrom)
|
|
91
|
+
? rawAllowFrom.map((entry) => resolveTemplateVar(cfg, entry))
|
|
92
|
+
: rawAllowFrom;
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
accountId: resolvedAccountId,
|
|
96
|
+
serverUrl: section.serverUrl || "https://aicq.online",
|
|
97
|
+
autoAcceptFriends: section.autoAcceptFriends ?? true,
|
|
98
|
+
autoAddFriends: section.autoAddFriends || [],
|
|
99
|
+
enabled: section.enabled ?? true,
|
|
100
|
+
dmPolicy: section.dmPolicy || "allowlist",
|
|
101
|
+
allowFrom: resolvedAllowFrom,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Lightweight account inspection for status / health / setup surfaces.
|
|
107
|
+
* Must not materialise secrets or start transports.
|
|
108
|
+
*/
|
|
109
|
+
function inspectAccount(cfg, accountId) {
|
|
110
|
+
const section = (cfg.channels || {})["aicq-chat"] || {};
|
|
111
|
+
const hasAccountId = Boolean(section.accountId || accountId);
|
|
112
|
+
return {
|
|
113
|
+
enabled: hasAccountId && section.enabled !== false,
|
|
114
|
+
configured: hasAccountId,
|
|
115
|
+
accountStatus: hasAccountId ? "available" : "missing",
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ── Build the channel plugin ─────────────────────────────────────────
|
|
120
|
+
|
|
121
|
+
const _plugin = createChatChannelPlugin({
|
|
122
|
+
base: createChannelPluginBase({
|
|
123
|
+
id: "aicq-chat",
|
|
124
|
+
|
|
125
|
+
setup: {
|
|
126
|
+
/**
|
|
127
|
+
* Resolve the account ID from setup input.
|
|
128
|
+
* Called by the setup wizard when a user configures the channel.
|
|
129
|
+
*/
|
|
130
|
+
resolveAccountId(params) {
|
|
131
|
+
const { cfg, accountId, input } = params;
|
|
132
|
+
return accountId || input?.accountId || resolveTemplateVar(cfg, "{{agent.id}}");
|
|
133
|
+
},
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Apply the account config after the setup wizard completes.
|
|
137
|
+
* Must return the updated OpenClawConfig.
|
|
138
|
+
*/
|
|
139
|
+
applyAccountConfig(params) {
|
|
140
|
+
const { cfg, accountId, input } = params;
|
|
141
|
+
const section = (cfg.channels || {})["aicq-chat"] || {};
|
|
142
|
+
return {
|
|
143
|
+
...cfg,
|
|
144
|
+
channels: {
|
|
145
|
+
...(cfg.channels || {}),
|
|
146
|
+
"aicq-chat": {
|
|
147
|
+
...section,
|
|
148
|
+
accountId: accountId || input?.accountId || "{{agent.id}}",
|
|
149
|
+
serverUrl: input?.serverUrl || section.serverUrl || "https://aicq.online",
|
|
150
|
+
autoAcceptFriends: input?.autoAcceptFriends ?? section.autoAcceptFriends ?? true,
|
|
151
|
+
enabled: true,
|
|
152
|
+
dmPolicy: input?.dmPolicy || section.dmPolicy || "allowlist",
|
|
153
|
+
allowFrom: input?.allowFrom || section.allowFrom || [],
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
},
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Validate setup input before applying.
|
|
161
|
+
* Return an error message string or null if valid.
|
|
162
|
+
*/
|
|
163
|
+
validateInput(params) {
|
|
164
|
+
return null;
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
|
|
168
|
+
// Gateway method descriptors — these are the method names the plugin
|
|
169
|
+
// will register via registerFull(). Declaring them here lets OpenClaw
|
|
170
|
+
// surface them in discovery / status surfaces before full activation.
|
|
171
|
+
gatewayMethodDescriptors: [
|
|
172
|
+
"aicq.status",
|
|
173
|
+
"aicq.friends.list",
|
|
174
|
+
"aicq.friends.add",
|
|
175
|
+
"aicq.friends.addByNumber",
|
|
176
|
+
"aicq.friends.remove",
|
|
177
|
+
"aicq.friends.requests",
|
|
178
|
+
"aicq.friends.acceptRequest",
|
|
179
|
+
"aicq.friends.rejectRequest",
|
|
180
|
+
"aicq.identity.info",
|
|
181
|
+
"aicq.agent.create",
|
|
182
|
+
"aicq.agent.delete",
|
|
183
|
+
"aicq.chat.send",
|
|
184
|
+
"aicq.chat.history",
|
|
185
|
+
"aicq.chat.delete",
|
|
186
|
+
"aicq.chat.userUpload",
|
|
187
|
+
"aicq.chat.userfiles",
|
|
188
|
+
"aicq.chat.streamChunk",
|
|
189
|
+
"aicq.chat.streamEnd",
|
|
190
|
+
"aicq.groups.list",
|
|
191
|
+
"aicq.groups.create",
|
|
192
|
+
"aicq.groups.join",
|
|
193
|
+
"aicq.groups.messages",
|
|
194
|
+
"aicq.groups.silent",
|
|
195
|
+
"aicq.sessions.list",
|
|
196
|
+
],
|
|
197
|
+
}),
|
|
198
|
+
|
|
199
|
+
// ── DM Security ──────────────────────────────────────────────────
|
|
200
|
+
security: {
|
|
201
|
+
dm: {
|
|
202
|
+
channelKey: "aicq-chat",
|
|
203
|
+
resolvePolicy: (account) => account.dmPolicy,
|
|
204
|
+
resolveAllowFrom: (account) => account.allowFrom,
|
|
205
|
+
defaultPolicy: "allowlist",
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
|
|
209
|
+
// ── Pairing ──────────────────────────────────────────────────────
|
|
210
|
+
pairing: {
|
|
211
|
+
text: {
|
|
212
|
+
idLabel: "AICQ Friend Code",
|
|
213
|
+
message: "Share this pairing code with the other party:",
|
|
214
|
+
notify: async ({ target, code }) => {
|
|
215
|
+
// AICQ pairing codes are shared out-of-band by the operator.
|
|
216
|
+
// No automatic notification is sent to the peer.
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
|
|
221
|
+
// ── Threading ────────────────────────────────────────────────────
|
|
222
|
+
threading: {
|
|
223
|
+
topLevelReplyToMode: "reply",
|
|
224
|
+
},
|
|
225
|
+
|
|
226
|
+
// ── Outbound ─────────────────────────────────────────────────────
|
|
227
|
+
outbound: {
|
|
228
|
+
attachedResults: {
|
|
229
|
+
channel: "aicq-chat",
|
|
230
|
+
|
|
231
|
+
sendText: async (params) => {
|
|
232
|
+
if (!runtime.chat) {
|
|
233
|
+
throw new Error("AICQ runtime not initialized — cannot send text");
|
|
234
|
+
}
|
|
235
|
+
const fromId =
|
|
236
|
+
params.from ||
|
|
237
|
+
params.accountId ||
|
|
238
|
+
(runtime.identity && runtime.identity.listAgents()[0]?.agent_id);
|
|
239
|
+
const result = await runtime.chat.sendMessage(
|
|
240
|
+
fromId,
|
|
241
|
+
params.to,
|
|
242
|
+
params.text,
|
|
243
|
+
{ isGroup: false }
|
|
244
|
+
);
|
|
245
|
+
return { messageId: result?.message_id || result?.id || "sent" };
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
|
|
249
|
+
base: {
|
|
250
|
+
sendMedia: async (params) => {
|
|
251
|
+
if (!runtime.chat) {
|
|
252
|
+
throw new Error("AICQ runtime not initialized — cannot send media");
|
|
253
|
+
}
|
|
254
|
+
const fromId =
|
|
255
|
+
params.from ||
|
|
256
|
+
params.accountId ||
|
|
257
|
+
(runtime.identity && runtime.identity.listAgents()[0]?.agent_id);
|
|
258
|
+
await runtime.chat.sendMessage(
|
|
259
|
+
fromId,
|
|
260
|
+
params.to,
|
|
261
|
+
params.mediaUrl || params.filePath,
|
|
262
|
+
{ type: params.mediaType || "file", isGroup: false }
|
|
263
|
+
);
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
// ── Gateway adapter: startAccount / stopAccount ───────────────────────
|
|
270
|
+
// OpenClaw calls startAccount when the channel is activated (on startup
|
|
271
|
+
// or when re-enabled). This is where we initialise the runtime, connect
|
|
272
|
+
// to the AICQ signalling server, and wire up inbound message delivery
|
|
273
|
+
// via the channelRuntime helpers.
|
|
274
|
+
|
|
275
|
+
_plugin.gateway = {
|
|
276
|
+
/**
|
|
277
|
+
* Start the channel account — connect to the AICQ server and begin
|
|
278
|
+
* listening for inbound messages.
|
|
279
|
+
*/
|
|
280
|
+
async startAccount(ctx) {
|
|
281
|
+
const { cfg, accountId, account, setStatus, log, abortSignal } = ctx;
|
|
282
|
+
|
|
283
|
+
const logger = log || console;
|
|
284
|
+
logger.info?.(`[AICQ Channel] startAccount called for ${accountId}`) || console.log(`[AICQ Channel] startAccount called for ${accountId}`);
|
|
285
|
+
|
|
286
|
+
// Ensure the runtime (DB, identity, transport) is initialised.
|
|
287
|
+
// The runtime is populated by registerFull() in index.js, but startAccount
|
|
288
|
+
// may be called before any gateway method is invoked, so we must ensure
|
|
289
|
+
// initialization here too.
|
|
290
|
+
if (!runtime._initialized && typeof runtime.ensureInitialized === "function") {
|
|
291
|
+
try {
|
|
292
|
+
await runtime.ensureInitialized();
|
|
293
|
+
logger.info?.("[AICQ Channel] Runtime initialized via startAccount") || console.log("[AICQ Channel] Runtime initialized via startAccount");
|
|
294
|
+
} catch (e) {
|
|
295
|
+
console.error("[AICQ Channel] Runtime initialization failed:", e.message);
|
|
296
|
+
setStatus({
|
|
297
|
+
accountId,
|
|
298
|
+
enabled: true,
|
|
299
|
+
configured: true,
|
|
300
|
+
running: false,
|
|
301
|
+
lastError: `Initialization failed: ${e.message}`,
|
|
302
|
+
});
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Resolve the agent ID: prefer the resolved accountId from
|
|
308
|
+
// resolveAccount (which already handles {{agent.id}}), then
|
|
309
|
+
// fall back to the OpenClaw default agent ID.
|
|
310
|
+
const agents = cfg.agents?.list;
|
|
311
|
+
const agentId = account?.accountId || accountId || OPENCLAW_DEFAULT_AGENT_ID;
|
|
312
|
+
|
|
313
|
+
// Ensure we have an identity in the plugin DB
|
|
314
|
+
if (runtime.identity) {
|
|
315
|
+
const existing = runtime.identity.listAgents();
|
|
316
|
+
if (existing.length === 0) {
|
|
317
|
+
const agentName = (Array.isArray(agents) && agents.length > 0 && agents[0]?.name)
|
|
318
|
+
? agents[0].name
|
|
319
|
+
: "AICQ Agent";
|
|
320
|
+
runtime.identity.createAgent(agentId, agentName);
|
|
321
|
+
console.log(`[AICQ Channel] Created agent identity: ${agentId}`);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Connect to the AICQ server
|
|
326
|
+
if (runtime.serverClient) {
|
|
327
|
+
try {
|
|
328
|
+
await runtime.serverClient.ensureAuth(agentId);
|
|
329
|
+
console.log(`[AICQ Channel] Authenticated as ${agentId}`);
|
|
330
|
+
|
|
331
|
+
// Connect WebSocket for real-time messages
|
|
332
|
+
if (typeof runtime.serverClient.start === "function") {
|
|
333
|
+
await runtime.serverClient.start(agentId);
|
|
334
|
+
console.log("[AICQ Channel] WebSocket connected");
|
|
335
|
+
} else if (typeof runtime.serverClient.connectWS === "function") {
|
|
336
|
+
runtime.serverClient.connectWS();
|
|
337
|
+
console.log("[AICQ Channel] WebSocket connecting");
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Sync friends and groups from server
|
|
341
|
+
if (runtime.handleGateway) {
|
|
342
|
+
try {
|
|
343
|
+
await runtime.handleGateway("aicq.friends.list", {});
|
|
344
|
+
await runtime.handleGateway("aicq.groups.list", {});
|
|
345
|
+
} catch (e) {
|
|
346
|
+
console.warn("[AICQ Channel] Initial sync failed:", e.message);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Auto-add friends from config (autoAddFriends list)
|
|
351
|
+
const autoAddFriends = account?.autoAddFriends || section?.autoAddFriends || [];
|
|
352
|
+
if (Array.isArray(autoAddFriends) && autoAddFriends.length > 0) {
|
|
353
|
+
console.log(`[AICQ Channel] Auto-adding ${autoAddFriends.length} friend(s) from config...`);
|
|
354
|
+
for (const friendEntry of autoAddFriends) {
|
|
355
|
+
try {
|
|
356
|
+
const aicqNumber = typeof friendEntry === 'string' ? friendEntry : friendEntry.number;
|
|
357
|
+
const friendMsg = typeof friendEntry === 'object' ? friendEntry.message : undefined;
|
|
358
|
+
if (!aicqNumber) continue;
|
|
359
|
+
const result = await runtime.handleGateway("aicq.friends.addByNumber", {
|
|
360
|
+
number: aicqNumber,
|
|
361
|
+
message: friendMsg || 'Hi, I\'d like to add you!',
|
|
362
|
+
});
|
|
363
|
+
if (result.error) {
|
|
364
|
+
console.warn(`[AICQ Channel] Auto-add friend ${aicqNumber} failed: ${result.error}`);
|
|
365
|
+
} else {
|
|
366
|
+
console.log(`[AICQ Channel] Auto-add friend ${aicqNumber}: ${result.status}`);
|
|
367
|
+
}
|
|
368
|
+
} catch (e) {
|
|
369
|
+
console.warn(`[AICQ Channel] Auto-add friend failed:`, e.message);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Auto-accept pending friend requests if autoAcceptFriends is true
|
|
375
|
+
if (account?.autoAcceptFriends && runtime.handleGateway) {
|
|
376
|
+
try {
|
|
377
|
+
const pendingResult = await runtime.handleGateway("aicq.friends.requests", {});
|
|
378
|
+
if (pendingResult.requests && pendingResult.requests.length > 0) {
|
|
379
|
+
for (const req of pendingResult.requests) {
|
|
380
|
+
try {
|
|
381
|
+
await runtime.handleGateway("aicq.friends.acceptRequest", { request_id: req.session_id });
|
|
382
|
+
console.log(`[AICQ Channel] Auto-accepted friend request from ${req.requester_id}`);
|
|
383
|
+
} catch (e) {
|
|
384
|
+
console.warn(`[AICQ Channel] Auto-accept failed:`, e.message);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
} catch (e) {
|
|
389
|
+
console.warn("[AICQ Channel] Auto-accept check failed:", e.message);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
} catch (e) {
|
|
393
|
+
console.error("[AICQ Channel] Failed to connect:", e.message);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Wire up inbound message handling via channelRuntime if available
|
|
398
|
+
if (ctx.channelRuntime) {
|
|
399
|
+
const { reply, routing } = ctx.channelRuntime;
|
|
400
|
+
if (reply && routing) {
|
|
401
|
+
console.log("[AICQ Channel] channelRuntime available — AI dispatch enabled");
|
|
402
|
+
|
|
403
|
+
// Set up the onNewMessage callback for the ChatManager
|
|
404
|
+
// This handles both regular text messages and synthetic file notifications
|
|
405
|
+
if (runtime.chat) {
|
|
406
|
+
runtime.chat.setOnNewMessage(async (msg) => {
|
|
407
|
+
try {
|
|
408
|
+
// Skip stream and presence events — not user messages
|
|
409
|
+
if (msg.type === 'stream_chunk' || msg.type === 'stream_end') return;
|
|
410
|
+
|
|
411
|
+
const resolvedAgentId = agentId;
|
|
412
|
+
const fromId = msg.from_id || msg.from || msg.sender_id;
|
|
413
|
+
const isGroup = !!(msg.is_group || msg.isGroup);
|
|
414
|
+
const isFileMsg = !!(msg.local_path || msg._synthetic);
|
|
415
|
+
let textContent = msg.content || msg.text || "";
|
|
416
|
+
|
|
417
|
+
// For file messages, include the local path info in the dispatch text
|
|
418
|
+
if (isFileMsg && msg.local_path) {
|
|
419
|
+
// The content already includes file info (from the synthetic message
|
|
420
|
+
// generated in chat.js), so we just pass it through to the AI
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const routeResult = await routing.resolveAgentRoute({
|
|
424
|
+
channelId: "aicq-chat",
|
|
425
|
+
accountId,
|
|
426
|
+
fromId,
|
|
427
|
+
chatType: isGroup ? "group" : "dm",
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
if (routeResult?.agentId) {
|
|
431
|
+
await reply.dispatchReplyWithBufferedBlockDispatcher({
|
|
432
|
+
ctx: {
|
|
433
|
+
channelId: "aicq-chat",
|
|
434
|
+
accountId,
|
|
435
|
+
fromId,
|
|
436
|
+
text: textContent,
|
|
437
|
+
chatType: isGroup ? "group" : "dm",
|
|
438
|
+
},
|
|
439
|
+
cfg,
|
|
440
|
+
dispatcherOptions: {
|
|
441
|
+
deliver: async (payload) => {
|
|
442
|
+
if (runtime.chat && payload.text) {
|
|
443
|
+
await runtime.chat.sendMessage(
|
|
444
|
+
resolvedAgentId,
|
|
445
|
+
fromId,
|
|
446
|
+
payload.text,
|
|
447
|
+
{ isGroup }
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
},
|
|
451
|
+
},
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
} catch (e) {
|
|
455
|
+
console.error("[AICQ Channel] Inbound message handling error:", e.message);
|
|
456
|
+
}
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
} else {
|
|
461
|
+
console.log("[AICQ Channel] channelRuntime not available — running in standalone mode");
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Update health status
|
|
465
|
+
setStatus({
|
|
466
|
+
accountId,
|
|
467
|
+
enabled: true,
|
|
468
|
+
configured: true,
|
|
469
|
+
running: true,
|
|
470
|
+
lastStartAt: Date.now(),
|
|
471
|
+
lastError: null,
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
console.log(`[AICQ Channel] Account ${accountId} started successfully`);
|
|
475
|
+
|
|
476
|
+
// ── Keep startAccount alive until abort signal ──────────────────
|
|
477
|
+
// OpenClaw expects startAccount to be a long-lived task. If it
|
|
478
|
+
// resolves immediately, the gateway treats it as an unexpected
|
|
479
|
+
// exit and enters a restart loop. We wait on the abort signal.
|
|
480
|
+
await new Promise((resolve) => {
|
|
481
|
+
if (abortSignal?.aborted) { resolve(); return; }
|
|
482
|
+
const onAbort = () => { cleanup(); resolve(); };
|
|
483
|
+
const cleanup = () => { abortSignal?.removeEventListener("abort", onAbort); };
|
|
484
|
+
abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
485
|
+
});
|
|
486
|
+
},
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Stop the channel account — disconnect and clean up.
|
|
490
|
+
*/
|
|
491
|
+
async stopAccount(ctx) {
|
|
492
|
+
const { accountId } = ctx;
|
|
493
|
+
console.log(`[AICQ Channel] stopAccount called for ${accountId}`);
|
|
494
|
+
|
|
495
|
+
if (runtime.serverClient) {
|
|
496
|
+
try {
|
|
497
|
+
if (typeof runtime.serverClient.stop === "function") {
|
|
498
|
+
runtime.serverClient.stop();
|
|
499
|
+
} else if (typeof runtime.serverClient.disconnect === "function") {
|
|
500
|
+
runtime.serverClient.disconnect();
|
|
501
|
+
}
|
|
502
|
+
console.log("[AICQ Channel] WebSocket disconnected");
|
|
503
|
+
} catch (e) {
|
|
504
|
+
console.warn("[AICQ Channel] Disconnect error:", e.message);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
},
|
|
508
|
+
};
|
|
509
|
+
|
|
510
|
+
// ── Add config helpers (required by OpenClaw channel loader) ──────────
|
|
511
|
+
// createChatChannelPlugin does not auto-attach config helpers,
|
|
512
|
+
// but the OpenClaw loader requires plugin.config.listAccountIds
|
|
513
|
+
// and plugin.config.resolveAccount for channel registration.
|
|
514
|
+
|
|
515
|
+
// resolveTemplateVar is defined at the top of this file.
|
|
516
|
+
|
|
517
|
+
_plugin.config = {
|
|
518
|
+
/**
|
|
519
|
+
* List all account IDs configured for this channel.
|
|
520
|
+
* Resolves template variables like {{agent.id}}.
|
|
521
|
+
*
|
|
522
|
+
* Signature: (cfg: OpenClawConfig) => string[]
|
|
523
|
+
*/
|
|
524
|
+
listAccountIds(cfg) {
|
|
525
|
+
const section = (cfg.channels || {})["aicq-chat"] || {};
|
|
526
|
+
if (section.accountId) {
|
|
527
|
+
const resolved = resolveTemplateVar(cfg, section.accountId);
|
|
528
|
+
return [resolved];
|
|
529
|
+
}
|
|
530
|
+
return [];
|
|
531
|
+
},
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Resolve an account from config. Reuses the setup resolver.
|
|
535
|
+
*
|
|
536
|
+
* Signature: (cfg: OpenClawConfig, accountId?: string | null) => ResolvedAccount
|
|
537
|
+
*/
|
|
538
|
+
resolveAccount,
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* Lightweight account inspection.
|
|
542
|
+
*
|
|
543
|
+
* Signature: (cfg: OpenClawConfig, accountId?: string | null) => unknown
|
|
544
|
+
*/
|
|
545
|
+
inspectAccount,
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* Default account ID for this channel.
|
|
549
|
+
*
|
|
550
|
+
* Signature: (cfg: OpenClawConfig) => string
|
|
551
|
+
*/
|
|
552
|
+
defaultAccountId(cfg) {
|
|
553
|
+
const section = (cfg.channels || {})["aicq-chat"] || {};
|
|
554
|
+
if (section.accountId) {
|
|
555
|
+
return resolveTemplateVar(cfg, section.accountId);
|
|
556
|
+
}
|
|
557
|
+
return OPENCLAW_DEFAULT_AGENT_ID;
|
|
558
|
+
},
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* Check if the account is enabled.
|
|
562
|
+
*
|
|
563
|
+
* IMPORTANT: OpenClaw calls this with (account, cfg) where `account`
|
|
564
|
+
* is the RESOLVED account object from resolveAccount(), not the config.
|
|
565
|
+
*
|
|
566
|
+
* Signature: (account: ResolvedAccount, cfg: OpenClawConfig) => boolean
|
|
567
|
+
*/
|
|
568
|
+
isEnabled(account, cfg) {
|
|
569
|
+
return account.enabled !== false;
|
|
570
|
+
},
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* Check if the channel account is configured.
|
|
574
|
+
*
|
|
575
|
+
* IMPORTANT: OpenClaw calls this with (account, cfg) where `account`
|
|
576
|
+
* is the RESOLVED account object from resolveAccount(), not the config.
|
|
577
|
+
* Our old code had isConfigured(cfg) which received the account object
|
|
578
|
+
* as `cfg`, causing it to always return false — this was the root cause
|
|
579
|
+
* of the "not-running" bug.
|
|
580
|
+
*
|
|
581
|
+
* Signature: (account: ResolvedAccount, cfg: OpenClawConfig) => boolean
|
|
582
|
+
*/
|
|
583
|
+
isConfigured(account, cfg) {
|
|
584
|
+
return Boolean(account && account.accountId);
|
|
585
|
+
},
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* Return the reason the channel is not configured.
|
|
589
|
+
*
|
|
590
|
+
* Signature: (account: ResolvedAccount, cfg: OpenClawConfig) => string
|
|
591
|
+
*/
|
|
592
|
+
unconfiguredReason(account, cfg) {
|
|
593
|
+
if (!account || !account.accountId) {
|
|
594
|
+
return "accountId is required — set channels.aicq-chat.accountId in openclaw.json";
|
|
595
|
+
}
|
|
596
|
+
return null;
|
|
597
|
+
},
|
|
598
|
+
|
|
599
|
+
/**
|
|
600
|
+
* Describe the account for status surfaces.
|
|
601
|
+
*
|
|
602
|
+
* IMPORTANT: OpenClaw calls this with (account, cfg) where `account`
|
|
603
|
+
* is the RESOLVED account object, not the config.
|
|
604
|
+
*
|
|
605
|
+
* Signature: (account: ResolvedAccount, cfg: OpenClawConfig) => ChannelAccountSnapshot
|
|
606
|
+
*/
|
|
607
|
+
describeAccount(account, cfg) {
|
|
608
|
+
return {
|
|
609
|
+
accountId: account?.accountId || null,
|
|
610
|
+
label: "AICQ Encrypted Chat",
|
|
611
|
+
enabled: account?.enabled !== false,
|
|
612
|
+
};
|
|
613
|
+
},
|
|
614
|
+
};
|
|
615
|
+
|
|
616
|
+
export const aicqChatPlugin = _plugin;
|