aicq-chat-plugin 3.5.3 → 3.7.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/index.js +6 -1
- package/lib/package.json +0 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/src/channel.js +195 -62
package/index.js
CHANGED
|
@@ -132,7 +132,7 @@ async function handleGatewayMethod(method, kwargs = {}) {
|
|
|
132
132
|
return {
|
|
133
133
|
state: _serverClient.connected ? "connected" : "disconnected",
|
|
134
134
|
agent_id: currentAgentId,
|
|
135
|
-
version: "3.
|
|
135
|
+
version: "3.7.0",
|
|
136
136
|
architecture: "channel",
|
|
137
137
|
};
|
|
138
138
|
case "aicq.friends.list":
|
|
@@ -268,6 +268,11 @@ function registerCliMetadata(api) {
|
|
|
268
268
|
|
|
269
269
|
// ── Full runtime registration ────────────────────────────────────────
|
|
270
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
|
+
|
|
271
276
|
// Register gateway RPC methods — each wraps handleGatewayMethod
|
|
272
277
|
const GATEWAY_METHODS = [
|
|
273
278
|
"aicq.status",
|
package/lib/package.json
CHANGED
|
File without changes
|
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.7.0",
|
|
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": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aicq-chat-plugin",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.7.0",
|
|
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/src/channel.js
CHANGED
|
@@ -122,8 +122,46 @@ const _plugin = createChatChannelPlugin({
|
|
|
122
122
|
id: "aicq-chat",
|
|
123
123
|
|
|
124
124
|
setup: {
|
|
125
|
-
|
|
126
|
-
|
|
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
|
+
},
|
|
127
165
|
},
|
|
128
166
|
|
|
129
167
|
// Gateway method descriptors — these are the method names the plugin
|
|
@@ -236,7 +274,7 @@ _plugin.gateway = {
|
|
|
236
274
|
* listening for inbound messages.
|
|
237
275
|
*/
|
|
238
276
|
async startAccount(ctx) {
|
|
239
|
-
const { cfg, accountId, account, setStatus, log } = ctx;
|
|
277
|
+
const { cfg, accountId, account, setStatus, log, abortSignal } = ctx;
|
|
240
278
|
|
|
241
279
|
const logger = log || console;
|
|
242
280
|
logger.info?.(`[AICQ Channel] startAccount called for ${accountId}`) || console.log(`[AICQ Channel] startAccount called for ${accountId}`);
|
|
@@ -287,7 +325,6 @@ _plugin.gateway = {
|
|
|
287
325
|
console.log(`[AICQ Channel] Authenticated as ${agentId}`);
|
|
288
326
|
|
|
289
327
|
// Connect WebSocket for real-time messages
|
|
290
|
-
// ServerClient.start() does ensureAuth + connectWS
|
|
291
328
|
if (typeof runtime.serverClient.start === "function") {
|
|
292
329
|
await runtime.serverClient.start(agentId);
|
|
293
330
|
console.log("[AICQ Channel] WebSocket connected");
|
|
@@ -310,57 +347,112 @@ _plugin.gateway = {
|
|
|
310
347
|
}
|
|
311
348
|
}
|
|
312
349
|
|
|
313
|
-
// Wire up inbound message handling
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
350
|
+
// Wire up inbound message handling
|
|
351
|
+
// Register handlers for AICQ message types. When an inbound message arrives,
|
|
352
|
+
// we try the OpenClaw channelRuntime first; if that fails we fall back to
|
|
353
|
+
// calling z-ai CLI directly and sending the reply via chat.sendMessage.
|
|
354
|
+
if (runtime.serverClient && typeof runtime.serverClient.onMessage === "function") {
|
|
355
|
+
const inboundHandler = async (msg) => {
|
|
356
|
+
try {
|
|
357
|
+
// AICQ server wraps message content in msg.data for "message" type,
|
|
358
|
+
// but "relay" type may have fields at the top level.
|
|
359
|
+
const data = msg.data || msg;
|
|
360
|
+
const fromId = data.from || data.fromId || data.sender_id || msg.from || msg.fromId;
|
|
361
|
+
const isGroup = !!(data.isGroup || data.groupId || msg.isGroup || msg.groupId);
|
|
362
|
+
const text = data.content || data.text || data.payload || msg.content || msg.text || msg.payload || "";
|
|
363
|
+
|
|
364
|
+
console.log(`[AICQ Channel] Inbound message from=${fromId} isGroup=${isGroup} text=${(text || "").substring(0, 80)}`);
|
|
365
|
+
|
|
366
|
+
if (!fromId || !text) {
|
|
367
|
+
return; // Skip system messages (online_ack, presence, etc.)
|
|
368
|
+
}
|
|
330
369
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
resolvedAgentId,
|
|
347
|
-
msg.from || msg.sender_id,
|
|
348
|
-
payload.text,
|
|
349
|
-
{ isGroup: !!msg.isGroup }
|
|
350
|
-
);
|
|
351
|
-
}
|
|
352
|
-
},
|
|
353
|
-
},
|
|
370
|
+
// Skip our own messages
|
|
371
|
+
if (fromId === runtime.serverClient?.serverAccountId || fromId === agentId) {
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Try channelRuntime (OpenClaw's built-in agent dispatch) first
|
|
376
|
+
if (ctx.channelRuntime) {
|
|
377
|
+
const { reply, routing } = ctx.channelRuntime;
|
|
378
|
+
if (reply && routing) {
|
|
379
|
+
try {
|
|
380
|
+
const routeResult = await routing.resolveAgentRoute({
|
|
381
|
+
channelId: "aicq-chat",
|
|
382
|
+
accountId,
|
|
383
|
+
fromId,
|
|
384
|
+
chatType: isGroup ? "group" : "dm",
|
|
354
385
|
});
|
|
386
|
+
|
|
387
|
+
if (routeResult?.agentId) {
|
|
388
|
+
await reply.dispatchReplyWithBufferedBlockDispatcher({
|
|
389
|
+
ctx: {
|
|
390
|
+
channelId: "aicq-chat",
|
|
391
|
+
accountId,
|
|
392
|
+
fromId,
|
|
393
|
+
text,
|
|
394
|
+
chatType: isGroup ? "group" : "dm",
|
|
395
|
+
},
|
|
396
|
+
cfg,
|
|
397
|
+
dispatcherOptions: {
|
|
398
|
+
deliver: async (payload) => {
|
|
399
|
+
if (runtime.chat && payload.text) {
|
|
400
|
+
await runtime.chat.sendMessage(agentId, fromId, payload.text, { isGroup });
|
|
401
|
+
}
|
|
402
|
+
},
|
|
403
|
+
},
|
|
404
|
+
});
|
|
405
|
+
return; // Successfully dispatched via channelRuntime
|
|
406
|
+
}
|
|
407
|
+
} catch (e) {
|
|
408
|
+
console.error("[AICQ Channel] channelRuntime dispatch failed, using fallback:", e.message);
|
|
355
409
|
}
|
|
356
|
-
} catch (e) {
|
|
357
|
-
console.error("[AICQ Channel] Inbound message handling error:", e.message);
|
|
358
410
|
}
|
|
359
|
-
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// Fallback: call LLM via z-ai CLI and send reply directly
|
|
414
|
+
console.log("[AICQ Channel] Using fallback: direct z-ai CLI call");
|
|
415
|
+
try {
|
|
416
|
+
const { execFile } = await import('child_process');
|
|
417
|
+
const llmResult = await new Promise((resolve, reject) => {
|
|
418
|
+
execFile('z-ai', ['chat', '--prompt', text], { timeout: 60000 }, (err, stdout) => {
|
|
419
|
+
if (err) reject(err);
|
|
420
|
+
else resolve(stdout);
|
|
421
|
+
});
|
|
422
|
+
});
|
|
423
|
+
let replyText = '';
|
|
424
|
+
const jsonStart = llmResult.indexOf('{');
|
|
425
|
+
if (jsonStart >= 0) {
|
|
426
|
+
try {
|
|
427
|
+
const parsed = JSON.parse(llmResult.substring(jsonStart));
|
|
428
|
+
replyText = parsed.choices?.[0]?.message?.content || llmResult.trim();
|
|
429
|
+
} catch { replyText = llmResult.trim(); }
|
|
430
|
+
} else { replyText = llmResult.trim(); }
|
|
431
|
+
|
|
432
|
+
if (replyText && runtime.chat) {
|
|
433
|
+
await runtime.chat.sendMessage(agentId, fromId, replyText, { isGroup });
|
|
434
|
+
console.log(`[AICQ Channel] Fallback reply sent to ${fromId}: ${replyText.substring(0, 50)}`);
|
|
435
|
+
}
|
|
436
|
+
} catch (fallbackErr) {
|
|
437
|
+
console.error("[AICQ Channel] Fallback LLM also failed:", fallbackErr.message);
|
|
438
|
+
}
|
|
439
|
+
} catch (e) {
|
|
440
|
+
console.error("[AICQ Channel] Inbound message handling error:", e.message);
|
|
360
441
|
}
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
// Register handler for relevant AICQ message types.
|
|
445
|
+
// ServerClient.onMessage(type, handler) requires a type string.
|
|
446
|
+
runtime.serverClient.onMessage("relay", inboundHandler);
|
|
447
|
+
runtime.serverClient.onMessage("message", inboundHandler);
|
|
448
|
+
runtime.serverClient.onMessage("group_message", inboundHandler);
|
|
449
|
+
console.log("[AICQ Channel] Inbound message handlers registered (relay, message, group_message)");
|
|
450
|
+
|
|
451
|
+
if (ctx.channelRuntime) {
|
|
452
|
+
console.log("[AICQ Channel] channelRuntime available — AI dispatch enabled (with z-ai CLI fallback)");
|
|
453
|
+
} else {
|
|
454
|
+
console.log("[AICQ Channel] channelRuntime not available — using z-ai CLI fallback");
|
|
361
455
|
}
|
|
362
|
-
} else {
|
|
363
|
-
console.log("[AICQ Channel] channelRuntime not available — running in standalone mode");
|
|
364
456
|
}
|
|
365
457
|
|
|
366
458
|
// Update health status
|
|
@@ -374,6 +466,17 @@ _plugin.gateway = {
|
|
|
374
466
|
});
|
|
375
467
|
|
|
376
468
|
console.log(`[AICQ Channel] Account ${accountId} started successfully`);
|
|
469
|
+
|
|
470
|
+
// ── Keep startAccount alive until abort signal ──────────────────
|
|
471
|
+
// OpenClaw expects startAccount to be a long-lived task. If it
|
|
472
|
+
// resolves immediately, the gateway treats it as an unexpected
|
|
473
|
+
// exit and enters a restart loop. We wait on the abort signal.
|
|
474
|
+
await new Promise((resolve) => {
|
|
475
|
+
if (abortSignal?.aborted) { resolve(); return; }
|
|
476
|
+
const onAbort = () => { cleanup(); resolve(); };
|
|
477
|
+
const cleanup = () => { abortSignal?.removeEventListener("abort", onAbort); };
|
|
478
|
+
abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
479
|
+
});
|
|
377
480
|
},
|
|
378
481
|
|
|
379
482
|
/**
|
|
@@ -409,6 +512,8 @@ _plugin.config = {
|
|
|
409
512
|
/**
|
|
410
513
|
* List all account IDs configured for this channel.
|
|
411
514
|
* Resolves template variables like {{agent.id}}.
|
|
515
|
+
*
|
|
516
|
+
* Signature: (cfg: OpenClawConfig) => string[]
|
|
412
517
|
*/
|
|
413
518
|
listAccountIds(cfg) {
|
|
414
519
|
const section = (cfg.channels || {})["aicq-chat"] || {};
|
|
@@ -421,17 +526,22 @@ _plugin.config = {
|
|
|
421
526
|
|
|
422
527
|
/**
|
|
423
528
|
* Resolve an account from config. Reuses the setup resolver.
|
|
529
|
+
*
|
|
530
|
+
* Signature: (cfg: OpenClawConfig, accountId?: string | null) => ResolvedAccount
|
|
424
531
|
*/
|
|
425
532
|
resolveAccount,
|
|
426
533
|
|
|
427
534
|
/**
|
|
428
535
|
* Lightweight account inspection.
|
|
536
|
+
*
|
|
537
|
+
* Signature: (cfg: OpenClawConfig, accountId?: string | null) => unknown
|
|
429
538
|
*/
|
|
430
539
|
inspectAccount,
|
|
431
540
|
|
|
432
541
|
/**
|
|
433
542
|
* Default account ID for this channel.
|
|
434
|
-
*
|
|
543
|
+
*
|
|
544
|
+
* Signature: (cfg: OpenClawConfig) => string
|
|
435
545
|
*/
|
|
436
546
|
defaultAccountId(cfg) {
|
|
437
547
|
const section = (cfg.channels || {})["aicq-chat"] || {};
|
|
@@ -442,19 +552,39 @@ _plugin.config = {
|
|
|
442
552
|
},
|
|
443
553
|
|
|
444
554
|
/**
|
|
445
|
-
* Check if the
|
|
555
|
+
* Check if the account is enabled.
|
|
556
|
+
*
|
|
557
|
+
* IMPORTANT: OpenClaw calls this with (account, cfg) where `account`
|
|
558
|
+
* is the RESOLVED account object from resolveAccount(), not the config.
|
|
559
|
+
*
|
|
560
|
+
* Signature: (account: ResolvedAccount, cfg: OpenClawConfig) => boolean
|
|
446
561
|
*/
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
562
|
+
isEnabled(account, cfg) {
|
|
563
|
+
return account.enabled !== false;
|
|
564
|
+
},
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* Check if the channel account is configured.
|
|
568
|
+
*
|
|
569
|
+
* IMPORTANT: OpenClaw calls this with (account, cfg) where `account`
|
|
570
|
+
* is the RESOLVED account object from resolveAccount(), not the config.
|
|
571
|
+
* Our old code had isConfigured(cfg) which received the account object
|
|
572
|
+
* as `cfg`, causing it to always return false — this was the root cause
|
|
573
|
+
* of the "not-running" bug.
|
|
574
|
+
*
|
|
575
|
+
* Signature: (account: ResolvedAccount, cfg: OpenClawConfig) => boolean
|
|
576
|
+
*/
|
|
577
|
+
isConfigured(account, cfg) {
|
|
578
|
+
return Boolean(account && account.accountId);
|
|
450
579
|
},
|
|
451
580
|
|
|
452
581
|
/**
|
|
453
582
|
* Return the reason the channel is not configured.
|
|
583
|
+
*
|
|
584
|
+
* Signature: (account: ResolvedAccount, cfg: OpenClawConfig) => string
|
|
454
585
|
*/
|
|
455
|
-
unconfiguredReason(cfg) {
|
|
456
|
-
|
|
457
|
-
if (!section.accountId) {
|
|
586
|
+
unconfiguredReason(account, cfg) {
|
|
587
|
+
if (!account || !account.accountId) {
|
|
458
588
|
return "accountId is required — set channels.aicq-chat.accountId in openclaw.json";
|
|
459
589
|
}
|
|
460
590
|
return null;
|
|
@@ -462,14 +592,17 @@ _plugin.config = {
|
|
|
462
592
|
|
|
463
593
|
/**
|
|
464
594
|
* Describe the account for status surfaces.
|
|
595
|
+
*
|
|
596
|
+
* IMPORTANT: OpenClaw calls this with (account, cfg) where `account`
|
|
597
|
+
* is the RESOLVED account object, not the config.
|
|
598
|
+
*
|
|
599
|
+
* Signature: (account: ResolvedAccount, cfg: OpenClawConfig) => ChannelAccountSnapshot
|
|
465
600
|
*/
|
|
466
|
-
describeAccount(
|
|
467
|
-
const section = (cfg.channels || {})["aicq-chat"] || {};
|
|
468
|
-
const rawId = accountId || section.accountId || null;
|
|
601
|
+
describeAccount(account, cfg) {
|
|
469
602
|
return {
|
|
470
|
-
accountId:
|
|
603
|
+
accountId: account?.accountId || null,
|
|
471
604
|
label: "AICQ Encrypted Chat",
|
|
472
|
-
enabled:
|
|
605
|
+
enabled: account?.enabled !== false,
|
|
473
606
|
};
|
|
474
607
|
},
|
|
475
608
|
};
|