aicq-chat-plugin 3.5.1 → 3.6.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/index.js +7 -1
- package/openclaw.plugin.json +13 -1
- package/package.json +1 -1
- package/setup-entry.js +0 -0
- package/src/channel.js +162 -39
- package/src/ui-routes.js +0 -0
package/index.js
CHANGED
|
@@ -65,6 +65,7 @@ async function ensureInitialized() {
|
|
|
65
65
|
runtime.dataDir = DATA_DIR;
|
|
66
66
|
runtime.serverUrl = SERVER_URL;
|
|
67
67
|
runtime.handleGateway = handleGatewayMethod;
|
|
68
|
+
runtime.ensureInitialized = ensureInitialized;
|
|
68
69
|
|
|
69
70
|
// Periodic cleanup
|
|
70
71
|
setInterval(() => _db.cleanup(), 3600000);
|
|
@@ -131,7 +132,7 @@ async function handleGatewayMethod(method, kwargs = {}) {
|
|
|
131
132
|
return {
|
|
132
133
|
state: _serverClient.connected ? "connected" : "disconnected",
|
|
133
134
|
agent_id: currentAgentId,
|
|
134
|
-
version: "3.
|
|
135
|
+
version: "3.6.0",
|
|
135
136
|
architecture: "channel",
|
|
136
137
|
};
|
|
137
138
|
case "aicq.friends.list":
|
|
@@ -267,6 +268,11 @@ function registerCliMetadata(api) {
|
|
|
267
268
|
|
|
268
269
|
// ── Full runtime registration ────────────────────────────────────────
|
|
269
270
|
async function registerFull(api) {
|
|
271
|
+
// Expose ensureInitialized on the runtime store immediately so that
|
|
272
|
+
// startAccount (called by the channel loader) can trigger init even
|
|
273
|
+
// if no gateway method has been invoked yet.
|
|
274
|
+
runtime.ensureInitialized = ensureInitialized;
|
|
275
|
+
|
|
270
276
|
// Register gateway RPC methods — each wraps handleGatewayMethod
|
|
271
277
|
const GATEWAY_METHODS = [
|
|
272
278
|
"aicq.status",
|
package/openclaw.plugin.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"kind": "channel",
|
|
3
3
|
"id": "aicq-chat",
|
|
4
4
|
"name": "AICQ Encrypted Chat",
|
|
5
|
-
"version": "3.
|
|
5
|
+
"version": "3.6.1",
|
|
6
6
|
"description": "End-to-end encrypted chat channel via AICQ protocol — in-process Channel plugin using OpenClaw Channel SDK",
|
|
7
7
|
"entry": "index.js",
|
|
8
8
|
"activation": {
|
|
@@ -49,6 +49,14 @@
|
|
|
49
49
|
"open",
|
|
50
50
|
"disabled"
|
|
51
51
|
]
|
|
52
|
+
},
|
|
53
|
+
"allowFrom": {
|
|
54
|
+
"type": "array",
|
|
55
|
+
"items": {
|
|
56
|
+
"type": "string"
|
|
57
|
+
},
|
|
58
|
+
"default": [],
|
|
59
|
+
"description": "允许发消息的好友 ID 列表(dmPolicy 为 allowlist 时生效)"
|
|
52
60
|
}
|
|
53
61
|
},
|
|
54
62
|
"required": [
|
|
@@ -71,6 +79,10 @@
|
|
|
71
79
|
"dmPolicy": {
|
|
72
80
|
"label": "DM Policy",
|
|
73
81
|
"help": "Who can send direct messages: allowlist (friends only), open, or disabled"
|
|
82
|
+
},
|
|
83
|
+
"allowFrom": {
|
|
84
|
+
"label": "Allow From",
|
|
85
|
+
"help": "Friend IDs allowed to send DMs (used when dmPolicy is allowlist)"
|
|
74
86
|
}
|
|
75
87
|
}
|
|
76
88
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aicq-chat-plugin",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.6.1",
|
|
4
4
|
"description": "AICQ End-to-end Encrypted Chat Channel Plugin for OpenClaw — In-process Channel SDK architecture with friend management, group chat, file transfer, and AI agent communication",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
package/setup-entry.js
CHANGED
|
File without changes
|
package/src/channel.js
CHANGED
|
@@ -35,18 +35,29 @@ export const runtime = {
|
|
|
35
35
|
// ── Template variable resolver ───────────────────────────────────────
|
|
36
36
|
// OpenClaw stores accountId as-is (e.g. "{{agent.id}}") in config.
|
|
37
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";
|
|
38
44
|
|
|
39
45
|
function resolveTemplateVar(cfg, value) {
|
|
40
46
|
if (typeof value !== "string") return value;
|
|
41
47
|
const match = value.match(/^\{\{(\w[\w.]*)\}\}$/);
|
|
42
48
|
if (!match) return value;
|
|
43
49
|
|
|
44
|
-
const
|
|
45
|
-
if (
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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;
|
|
50
61
|
}
|
|
51
62
|
|
|
52
63
|
return value; // unknown template — return as-is
|
|
@@ -74,13 +85,19 @@ function resolveAccount(cfg, accountId) {
|
|
|
74
85
|
// Resolve template variables like {{agent.id}}
|
|
75
86
|
const resolvedAccountId = resolveTemplateVar(cfg, rawAccountId);
|
|
76
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
|
+
|
|
77
94
|
return {
|
|
78
95
|
accountId: resolvedAccountId,
|
|
79
96
|
serverUrl: section.serverUrl || "https://aicq.online",
|
|
80
97
|
autoAcceptFriends: section.autoAcceptFriends ?? true,
|
|
81
98
|
enabled: section.enabled ?? true,
|
|
82
99
|
dmPolicy: section.dmPolicy || "allowlist",
|
|
83
|
-
allowFrom:
|
|
100
|
+
allowFrom: resolvedAllowFrom,
|
|
84
101
|
};
|
|
85
102
|
}
|
|
86
103
|
|
|
@@ -105,8 +122,46 @@ const _plugin = createChatChannelPlugin({
|
|
|
105
122
|
id: "aicq-chat",
|
|
106
123
|
|
|
107
124
|
setup: {
|
|
108
|
-
|
|
109
|
-
|
|
125
|
+
/**
|
|
126
|
+
* Resolve the account ID from setup input.
|
|
127
|
+
* Called by the setup wizard when a user configures the channel.
|
|
128
|
+
*/
|
|
129
|
+
resolveAccountId(params) {
|
|
130
|
+
const { cfg, accountId, input } = params;
|
|
131
|
+
return accountId || input?.accountId || resolveTemplateVar(cfg, "{{agent.id}}");
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Apply the account config after the setup wizard completes.
|
|
136
|
+
* Must return the updated OpenClawConfig.
|
|
137
|
+
*/
|
|
138
|
+
applyAccountConfig(params) {
|
|
139
|
+
const { cfg, accountId, input } = params;
|
|
140
|
+
const section = (cfg.channels || {})["aicq-chat"] || {};
|
|
141
|
+
return {
|
|
142
|
+
...cfg,
|
|
143
|
+
channels: {
|
|
144
|
+
...(cfg.channels || {}),
|
|
145
|
+
"aicq-chat": {
|
|
146
|
+
...section,
|
|
147
|
+
accountId: accountId || input?.accountId || "{{agent.id}}",
|
|
148
|
+
serverUrl: input?.serverUrl || section.serverUrl || "https://aicq.online",
|
|
149
|
+
autoAcceptFriends: input?.autoAcceptFriends ?? section.autoAcceptFriends ?? true,
|
|
150
|
+
enabled: true,
|
|
151
|
+
dmPolicy: input?.dmPolicy || section.dmPolicy || "allowlist",
|
|
152
|
+
allowFrom: input?.allowFrom || section.allowFrom || [],
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
},
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Validate setup input before applying.
|
|
160
|
+
* Return an error message string or null if valid.
|
|
161
|
+
*/
|
|
162
|
+
validateInput(params) {
|
|
163
|
+
return null;
|
|
164
|
+
},
|
|
110
165
|
},
|
|
111
166
|
|
|
112
167
|
// Gateway method descriptors — these are the method names the plugin
|
|
@@ -219,24 +274,46 @@ _plugin.gateway = {
|
|
|
219
274
|
* listening for inbound messages.
|
|
220
275
|
*/
|
|
221
276
|
async startAccount(ctx) {
|
|
222
|
-
const { cfg, accountId, account, setStatus } = ctx;
|
|
277
|
+
const { cfg, accountId, account, setStatus, log, abortSignal } = ctx;
|
|
223
278
|
|
|
224
|
-
|
|
279
|
+
const logger = log || console;
|
|
280
|
+
logger.info?.(`[AICQ Channel] startAccount called for ${accountId}`) || console.log(`[AICQ Channel] startAccount called for ${accountId}`);
|
|
225
281
|
|
|
226
|
-
// Ensure the runtime (DB, identity, transport) is initialised
|
|
227
|
-
|
|
228
|
-
|
|
282
|
+
// Ensure the runtime (DB, identity, transport) is initialised.
|
|
283
|
+
// The runtime is populated by registerFull() in index.js, but startAccount
|
|
284
|
+
// may be called before any gateway method is invoked, so we must ensure
|
|
285
|
+
// initialization here too.
|
|
286
|
+
if (!runtime._initialized && typeof runtime.ensureInitialized === "function") {
|
|
287
|
+
try {
|
|
288
|
+
await runtime.ensureInitialized();
|
|
289
|
+
logger.info?.("[AICQ Channel] Runtime initialized via startAccount") || console.log("[AICQ Channel] Runtime initialized via startAccount");
|
|
290
|
+
} catch (e) {
|
|
291
|
+
console.error("[AICQ Channel] Runtime initialization failed:", e.message);
|
|
292
|
+
setStatus({
|
|
293
|
+
accountId,
|
|
294
|
+
enabled: true,
|
|
295
|
+
configured: true,
|
|
296
|
+
running: false,
|
|
297
|
+
lastError: `Initialization failed: ${e.message}`,
|
|
298
|
+
});
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
229
301
|
}
|
|
230
302
|
|
|
231
|
-
// Resolve the agent ID
|
|
232
|
-
|
|
233
|
-
|
|
303
|
+
// Resolve the agent ID: prefer the resolved accountId from
|
|
304
|
+
// resolveAccount (which already handles {{agent.id}}), then
|
|
305
|
+
// fall back to the OpenClaw default agent ID.
|
|
306
|
+
const agents = cfg.agents?.list;
|
|
307
|
+
const agentId = account?.accountId || accountId || OPENCLAW_DEFAULT_AGENT_ID;
|
|
234
308
|
|
|
235
309
|
// Ensure we have an identity in the plugin DB
|
|
236
310
|
if (runtime.identity) {
|
|
237
311
|
const existing = runtime.identity.listAgents();
|
|
238
312
|
if (existing.length === 0) {
|
|
239
|
-
|
|
313
|
+
const agentName = (Array.isArray(agents) && agents.length > 0 && agents[0]?.name)
|
|
314
|
+
? agents[0].name
|
|
315
|
+
: "AICQ Agent";
|
|
316
|
+
runtime.identity.createAgent(agentId, agentName);
|
|
240
317
|
console.log(`[AICQ Channel] Created agent identity: ${agentId}`);
|
|
241
318
|
}
|
|
242
319
|
}
|
|
@@ -248,9 +325,12 @@ _plugin.gateway = {
|
|
|
248
325
|
console.log(`[AICQ Channel] Authenticated as ${agentId}`);
|
|
249
326
|
|
|
250
327
|
// Connect WebSocket for real-time messages
|
|
251
|
-
if (typeof runtime.serverClient.
|
|
252
|
-
await runtime.serverClient.
|
|
328
|
+
if (typeof runtime.serverClient.start === "function") {
|
|
329
|
+
await runtime.serverClient.start(agentId);
|
|
253
330
|
console.log("[AICQ Channel] WebSocket connected");
|
|
331
|
+
} else if (typeof runtime.serverClient.connectWS === "function") {
|
|
332
|
+
runtime.serverClient.connectWS();
|
|
333
|
+
console.log("[AICQ Channel] WebSocket connecting");
|
|
254
334
|
}
|
|
255
335
|
|
|
256
336
|
// Sync friends and groups from server
|
|
@@ -273,11 +353,10 @@ _plugin.gateway = {
|
|
|
273
353
|
if (reply && routing) {
|
|
274
354
|
console.log("[AICQ Channel] channelRuntime available — AI dispatch enabled");
|
|
275
355
|
|
|
276
|
-
// Register inbound message handler on the serverClient
|
|
277
356
|
if (runtime.serverClient && typeof runtime.serverClient.onMessage === "function") {
|
|
278
357
|
runtime.serverClient.onMessage(async (msg) => {
|
|
279
358
|
try {
|
|
280
|
-
const resolvedAgentId =
|
|
359
|
+
const resolvedAgentId = agentId;
|
|
281
360
|
const routeResult = await routing.resolveAgentRoute({
|
|
282
361
|
channelId: "aicq-chat",
|
|
283
362
|
accountId,
|
|
@@ -297,7 +376,6 @@ _plugin.gateway = {
|
|
|
297
376
|
cfg,
|
|
298
377
|
dispatcherOptions: {
|
|
299
378
|
deliver: async (payload) => {
|
|
300
|
-
// Send AI reply back through AICQ
|
|
301
379
|
if (runtime.chat && payload.text) {
|
|
302
380
|
await runtime.chat.sendMessage(
|
|
303
381
|
resolvedAgentId,
|
|
@@ -331,6 +409,17 @@ _plugin.gateway = {
|
|
|
331
409
|
});
|
|
332
410
|
|
|
333
411
|
console.log(`[AICQ Channel] Account ${accountId} started successfully`);
|
|
412
|
+
|
|
413
|
+
// ── Keep startAccount alive until abort signal ──────────────────
|
|
414
|
+
// OpenClaw expects startAccount to be a long-lived task. If it
|
|
415
|
+
// resolves immediately, the gateway treats it as an unexpected
|
|
416
|
+
// exit and enters a restart loop. We wait on the abort signal.
|
|
417
|
+
await new Promise((resolve) => {
|
|
418
|
+
if (abortSignal?.aborted) { resolve(); return; }
|
|
419
|
+
const onAbort = () => { cleanup(); resolve(); };
|
|
420
|
+
const cleanup = () => { abortSignal?.removeEventListener("abort", onAbort); };
|
|
421
|
+
abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
422
|
+
});
|
|
334
423
|
},
|
|
335
424
|
|
|
336
425
|
/**
|
|
@@ -340,9 +429,13 @@ _plugin.gateway = {
|
|
|
340
429
|
const { accountId } = ctx;
|
|
341
430
|
console.log(`[AICQ Channel] stopAccount called for ${accountId}`);
|
|
342
431
|
|
|
343
|
-
if (runtime.serverClient
|
|
432
|
+
if (runtime.serverClient) {
|
|
344
433
|
try {
|
|
345
|
-
runtime.serverClient.
|
|
434
|
+
if (typeof runtime.serverClient.stop === "function") {
|
|
435
|
+
runtime.serverClient.stop();
|
|
436
|
+
} else if (typeof runtime.serverClient.disconnect === "function") {
|
|
437
|
+
runtime.serverClient.disconnect();
|
|
438
|
+
}
|
|
346
439
|
console.log("[AICQ Channel] WebSocket disconnected");
|
|
347
440
|
} catch (e) {
|
|
348
441
|
console.warn("[AICQ Channel] Disconnect error:", e.message);
|
|
@@ -362,6 +455,8 @@ _plugin.config = {
|
|
|
362
455
|
/**
|
|
363
456
|
* List all account IDs configured for this channel.
|
|
364
457
|
* Resolves template variables like {{agent.id}}.
|
|
458
|
+
*
|
|
459
|
+
* Signature: (cfg: OpenClawConfig) => string[]
|
|
365
460
|
*/
|
|
366
461
|
listAccountIds(cfg) {
|
|
367
462
|
const section = (cfg.channels || {})["aicq-chat"] || {};
|
|
@@ -374,40 +469,65 @@ _plugin.config = {
|
|
|
374
469
|
|
|
375
470
|
/**
|
|
376
471
|
* Resolve an account from config. Reuses the setup resolver.
|
|
472
|
+
*
|
|
473
|
+
* Signature: (cfg: OpenClawConfig, accountId?: string | null) => ResolvedAccount
|
|
377
474
|
*/
|
|
378
475
|
resolveAccount,
|
|
379
476
|
|
|
380
477
|
/**
|
|
381
478
|
* Lightweight account inspection.
|
|
479
|
+
*
|
|
480
|
+
* Signature: (cfg: OpenClawConfig, accountId?: string | null) => unknown
|
|
382
481
|
*/
|
|
383
482
|
inspectAccount,
|
|
384
483
|
|
|
385
484
|
/**
|
|
386
485
|
* Default account ID for this channel.
|
|
387
|
-
*
|
|
486
|
+
*
|
|
487
|
+
* Signature: (cfg: OpenClawConfig) => string
|
|
388
488
|
*/
|
|
389
489
|
defaultAccountId(cfg) {
|
|
390
490
|
const section = (cfg.channels || {})["aicq-chat"] || {};
|
|
391
491
|
if (section.accountId) {
|
|
392
492
|
return resolveTemplateVar(cfg, section.accountId);
|
|
393
493
|
}
|
|
394
|
-
return
|
|
494
|
+
return OPENCLAW_DEFAULT_AGENT_ID;
|
|
395
495
|
},
|
|
396
496
|
|
|
397
497
|
/**
|
|
398
|
-
* Check if the
|
|
498
|
+
* Check if the account is enabled.
|
|
499
|
+
*
|
|
500
|
+
* IMPORTANT: OpenClaw calls this with (account, cfg) where `account`
|
|
501
|
+
* is the RESOLVED account object from resolveAccount(), not the config.
|
|
502
|
+
*
|
|
503
|
+
* Signature: (account: ResolvedAccount, cfg: OpenClawConfig) => boolean
|
|
399
504
|
*/
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
505
|
+
isEnabled(account, cfg) {
|
|
506
|
+
return account.enabled !== false;
|
|
507
|
+
},
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Check if the channel account is configured.
|
|
511
|
+
*
|
|
512
|
+
* IMPORTANT: OpenClaw calls this with (account, cfg) where `account`
|
|
513
|
+
* is the RESOLVED account object from resolveAccount(), not the config.
|
|
514
|
+
* Our old code had isConfigured(cfg) which received the account object
|
|
515
|
+
* as `cfg`, causing it to always return false — this was the root cause
|
|
516
|
+
* of the "not-running" bug.
|
|
517
|
+
*
|
|
518
|
+
* Signature: (account: ResolvedAccount, cfg: OpenClawConfig) => boolean
|
|
519
|
+
*/
|
|
520
|
+
isConfigured(account, cfg) {
|
|
521
|
+
return Boolean(account && account.accountId);
|
|
403
522
|
},
|
|
404
523
|
|
|
405
524
|
/**
|
|
406
525
|
* Return the reason the channel is not configured.
|
|
526
|
+
*
|
|
527
|
+
* Signature: (account: ResolvedAccount, cfg: OpenClawConfig) => string
|
|
407
528
|
*/
|
|
408
|
-
unconfiguredReason(cfg) {
|
|
409
|
-
|
|
410
|
-
if (!section.accountId) {
|
|
529
|
+
unconfiguredReason(account, cfg) {
|
|
530
|
+
if (!account || !account.accountId) {
|
|
411
531
|
return "accountId is required — set channels.aicq-chat.accountId in openclaw.json";
|
|
412
532
|
}
|
|
413
533
|
return null;
|
|
@@ -415,14 +535,17 @@ _plugin.config = {
|
|
|
415
535
|
|
|
416
536
|
/**
|
|
417
537
|
* Describe the account for status surfaces.
|
|
538
|
+
*
|
|
539
|
+
* IMPORTANT: OpenClaw calls this with (account, cfg) where `account`
|
|
540
|
+
* is the RESOLVED account object, not the config.
|
|
541
|
+
*
|
|
542
|
+
* Signature: (account: ResolvedAccount, cfg: OpenClawConfig) => ChannelAccountSnapshot
|
|
418
543
|
*/
|
|
419
|
-
describeAccount(
|
|
420
|
-
const section = (cfg.channels || {})["aicq-chat"] || {};
|
|
421
|
-
const rawId = accountId || section.accountId || null;
|
|
544
|
+
describeAccount(account, cfg) {
|
|
422
545
|
return {
|
|
423
|
-
accountId:
|
|
546
|
+
accountId: account?.accountId || null,
|
|
424
547
|
label: "AICQ Encrypted Chat",
|
|
425
|
-
enabled:
|
|
548
|
+
enabled: account?.enabled !== false,
|
|
426
549
|
};
|
|
427
550
|
},
|
|
428
551
|
};
|
package/src/ui-routes.js
CHANGED
|
File without changes
|