aicq-chat-plugin 3.4.1 → 3.5.3
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 +1 -0
- package/openclaw.plugin.json +13 -1
- package/package.json +1 -1
- package/setup-entry.js +0 -0
- package/src/channel.js +237 -5
- 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);
|
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.5.3",
|
|
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.5.3",
|
|
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
|
@@ -32,6 +32,37 @@ export const runtime = {
|
|
|
32
32
|
_initialized: false,
|
|
33
33
|
};
|
|
34
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
|
+
|
|
35
66
|
// ── Resolved account type ────────────────────────────────────────────
|
|
36
67
|
// This is the object returned by resolveAccount() and consumed by
|
|
37
68
|
// security / pairing / outbound adapters.
|
|
@@ -43,21 +74,30 @@ export const runtime = {
|
|
|
43
74
|
*/
|
|
44
75
|
function resolveAccount(cfg, accountId) {
|
|
45
76
|
const section = (cfg.channels || {})["aicq-chat"] || {};
|
|
46
|
-
const
|
|
77
|
+
const rawAccountId = accountId || section.accountId || null;
|
|
47
78
|
|
|
48
|
-
if (!
|
|
79
|
+
if (!rawAccountId) {
|
|
49
80
|
throw new Error(
|
|
50
81
|
"aicq-chat: accountId is required (set channels.aicq-chat.accountId)"
|
|
51
82
|
);
|
|
52
83
|
}
|
|
53
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
|
+
|
|
54
94
|
return {
|
|
55
95
|
accountId: resolvedAccountId,
|
|
56
96
|
serverUrl: section.serverUrl || "https://aicq.online",
|
|
57
97
|
autoAcceptFriends: section.autoAcceptFriends ?? true,
|
|
58
98
|
enabled: section.enabled ?? true,
|
|
59
99
|
dmPolicy: section.dmPolicy || "allowlist",
|
|
60
|
-
allowFrom:
|
|
100
|
+
allowFrom: resolvedAllowFrom,
|
|
61
101
|
};
|
|
62
102
|
}
|
|
63
103
|
|
|
@@ -184,18 +224,197 @@ const _plugin = createChatChannelPlugin({
|
|
|
184
224
|
},
|
|
185
225
|
});
|
|
186
226
|
|
|
227
|
+
// ── Gateway adapter: startAccount / stopAccount ───────────────────────
|
|
228
|
+
// OpenClaw calls startAccount when the channel is activated (on startup
|
|
229
|
+
// or when re-enabled). This is where we initialise the runtime, connect
|
|
230
|
+
// to the AICQ signalling server, and wire up inbound message delivery
|
|
231
|
+
// via the channelRuntime helpers.
|
|
232
|
+
|
|
233
|
+
_plugin.gateway = {
|
|
234
|
+
/**
|
|
235
|
+
* Start the channel account — connect to the AICQ server and begin
|
|
236
|
+
* listening for inbound messages.
|
|
237
|
+
*/
|
|
238
|
+
async startAccount(ctx) {
|
|
239
|
+
const { cfg, accountId, account, setStatus, log } = ctx;
|
|
240
|
+
|
|
241
|
+
const logger = log || console;
|
|
242
|
+
logger.info?.(`[AICQ Channel] startAccount called for ${accountId}`) || console.log(`[AICQ Channel] startAccount called for ${accountId}`);
|
|
243
|
+
|
|
244
|
+
// Ensure the runtime (DB, identity, transport) is initialised.
|
|
245
|
+
// The runtime is populated by registerFull() in index.js, but startAccount
|
|
246
|
+
// may be called before any gateway method is invoked, so we must ensure
|
|
247
|
+
// initialization here too.
|
|
248
|
+
if (!runtime._initialized && typeof runtime.ensureInitialized === "function") {
|
|
249
|
+
try {
|
|
250
|
+
await runtime.ensureInitialized();
|
|
251
|
+
logger.info?.("[AICQ Channel] Runtime initialized via startAccount") || console.log("[AICQ Channel] Runtime initialized via startAccount");
|
|
252
|
+
} catch (e) {
|
|
253
|
+
console.error("[AICQ Channel] Runtime initialization failed:", e.message);
|
|
254
|
+
setStatus({
|
|
255
|
+
accountId,
|
|
256
|
+
enabled: true,
|
|
257
|
+
configured: true,
|
|
258
|
+
running: false,
|
|
259
|
+
lastError: `Initialization failed: ${e.message}`,
|
|
260
|
+
});
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Resolve the agent ID: prefer the resolved accountId from
|
|
266
|
+
// resolveAccount (which already handles {{agent.id}}), then
|
|
267
|
+
// fall back to the OpenClaw default agent ID.
|
|
268
|
+
const agents = cfg.agents?.list;
|
|
269
|
+
const agentId = account?.accountId || accountId || OPENCLAW_DEFAULT_AGENT_ID;
|
|
270
|
+
|
|
271
|
+
// Ensure we have an identity in the plugin DB
|
|
272
|
+
if (runtime.identity) {
|
|
273
|
+
const existing = runtime.identity.listAgents();
|
|
274
|
+
if (existing.length === 0) {
|
|
275
|
+
const agentName = (Array.isArray(agents) && agents.length > 0 && agents[0]?.name)
|
|
276
|
+
? agents[0].name
|
|
277
|
+
: "AICQ Agent";
|
|
278
|
+
runtime.identity.createAgent(agentId, agentName);
|
|
279
|
+
console.log(`[AICQ Channel] Created agent identity: ${agentId}`);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Connect to the AICQ server
|
|
284
|
+
if (runtime.serverClient) {
|
|
285
|
+
try {
|
|
286
|
+
await runtime.serverClient.ensureAuth(agentId);
|
|
287
|
+
console.log(`[AICQ Channel] Authenticated as ${agentId}`);
|
|
288
|
+
|
|
289
|
+
// Connect WebSocket for real-time messages
|
|
290
|
+
// ServerClient.start() does ensureAuth + connectWS
|
|
291
|
+
if (typeof runtime.serverClient.start === "function") {
|
|
292
|
+
await runtime.serverClient.start(agentId);
|
|
293
|
+
console.log("[AICQ Channel] WebSocket connected");
|
|
294
|
+
} else if (typeof runtime.serverClient.connectWS === "function") {
|
|
295
|
+
runtime.serverClient.connectWS();
|
|
296
|
+
console.log("[AICQ Channel] WebSocket connecting");
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Sync friends and groups from server
|
|
300
|
+
if (runtime.handleGateway) {
|
|
301
|
+
try {
|
|
302
|
+
await runtime.handleGateway("aicq.friends.list", {});
|
|
303
|
+
await runtime.handleGateway("aicq.groups.list", {});
|
|
304
|
+
} catch (e) {
|
|
305
|
+
console.warn("[AICQ Channel] Initial sync failed:", e.message);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
} catch (e) {
|
|
309
|
+
console.error("[AICQ Channel] Failed to connect:", e.message);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Wire up inbound message handling via channelRuntime if available
|
|
314
|
+
if (ctx.channelRuntime) {
|
|
315
|
+
const { reply, routing } = ctx.channelRuntime;
|
|
316
|
+
if (reply && routing) {
|
|
317
|
+
console.log("[AICQ Channel] channelRuntime available — AI dispatch enabled");
|
|
318
|
+
|
|
319
|
+
// Register inbound message handler on the serverClient
|
|
320
|
+
if (runtime.serverClient && typeof runtime.serverClient.onMessage === "function") {
|
|
321
|
+
runtime.serverClient.onMessage(async (msg) => {
|
|
322
|
+
try {
|
|
323
|
+
const resolvedAgentId = agentId;
|
|
324
|
+
const routeResult = await routing.resolveAgentRoute({
|
|
325
|
+
channelId: "aicq-chat",
|
|
326
|
+
accountId,
|
|
327
|
+
fromId: msg.from || msg.sender_id,
|
|
328
|
+
chatType: msg.isGroup ? "group" : "dm",
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
if (routeResult?.agentId) {
|
|
332
|
+
await reply.dispatchReplyWithBufferedBlockDispatcher({
|
|
333
|
+
ctx: {
|
|
334
|
+
channelId: "aicq-chat",
|
|
335
|
+
accountId,
|
|
336
|
+
fromId: msg.from || msg.sender_id,
|
|
337
|
+
text: msg.content || msg.text || "",
|
|
338
|
+
chatType: msg.isGroup ? "group" : "dm",
|
|
339
|
+
},
|
|
340
|
+
cfg,
|
|
341
|
+
dispatcherOptions: {
|
|
342
|
+
deliver: async (payload) => {
|
|
343
|
+
// Send AI reply back through AICQ
|
|
344
|
+
if (runtime.chat && payload.text) {
|
|
345
|
+
await runtime.chat.sendMessage(
|
|
346
|
+
resolvedAgentId,
|
|
347
|
+
msg.from || msg.sender_id,
|
|
348
|
+
payload.text,
|
|
349
|
+
{ isGroup: !!msg.isGroup }
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
},
|
|
353
|
+
},
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
} catch (e) {
|
|
357
|
+
console.error("[AICQ Channel] Inbound message handling error:", e.message);
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
} else {
|
|
363
|
+
console.log("[AICQ Channel] channelRuntime not available — running in standalone mode");
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Update health status
|
|
367
|
+
setStatus({
|
|
368
|
+
accountId,
|
|
369
|
+
enabled: true,
|
|
370
|
+
configured: true,
|
|
371
|
+
running: true,
|
|
372
|
+
lastStartAt: Date.now(),
|
|
373
|
+
lastError: null,
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
console.log(`[AICQ Channel] Account ${accountId} started successfully`);
|
|
377
|
+
},
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Stop the channel account — disconnect and clean up.
|
|
381
|
+
*/
|
|
382
|
+
async stopAccount(ctx) {
|
|
383
|
+
const { accountId } = ctx;
|
|
384
|
+
console.log(`[AICQ Channel] stopAccount called for ${accountId}`);
|
|
385
|
+
|
|
386
|
+
if (runtime.serverClient) {
|
|
387
|
+
try {
|
|
388
|
+
if (typeof runtime.serverClient.stop === "function") {
|
|
389
|
+
runtime.serverClient.stop();
|
|
390
|
+
} else if (typeof runtime.serverClient.disconnect === "function") {
|
|
391
|
+
runtime.serverClient.disconnect();
|
|
392
|
+
}
|
|
393
|
+
console.log("[AICQ Channel] WebSocket disconnected");
|
|
394
|
+
} catch (e) {
|
|
395
|
+
console.warn("[AICQ Channel] Disconnect error:", e.message);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
},
|
|
399
|
+
};
|
|
400
|
+
|
|
187
401
|
// ── Add config helpers (required by OpenClaw channel loader) ──────────
|
|
188
402
|
// createChatChannelPlugin does not auto-attach config helpers,
|
|
189
403
|
// but the OpenClaw loader requires plugin.config.listAccountIds
|
|
190
404
|
// and plugin.config.resolveAccount for channel registration.
|
|
405
|
+
|
|
406
|
+
// resolveTemplateVar is defined at the top of this file.
|
|
407
|
+
|
|
191
408
|
_plugin.config = {
|
|
192
409
|
/**
|
|
193
410
|
* List all account IDs configured for this channel.
|
|
411
|
+
* Resolves template variables like {{agent.id}}.
|
|
194
412
|
*/
|
|
195
413
|
listAccountIds(cfg) {
|
|
196
414
|
const section = (cfg.channels || {})["aicq-chat"] || {};
|
|
197
415
|
if (section.accountId) {
|
|
198
|
-
|
|
416
|
+
const resolved = resolveTemplateVar(cfg, section.accountId);
|
|
417
|
+
return [resolved];
|
|
199
418
|
}
|
|
200
419
|
return [];
|
|
201
420
|
},
|
|
@@ -210,6 +429,18 @@ _plugin.config = {
|
|
|
210
429
|
*/
|
|
211
430
|
inspectAccount,
|
|
212
431
|
|
|
432
|
+
/**
|
|
433
|
+
* Default account ID for this channel.
|
|
434
|
+
* Resolves {{agent.id}} to the actual agent ID.
|
|
435
|
+
*/
|
|
436
|
+
defaultAccountId(cfg) {
|
|
437
|
+
const section = (cfg.channels || {})["aicq-chat"] || {};
|
|
438
|
+
if (section.accountId) {
|
|
439
|
+
return resolveTemplateVar(cfg, section.accountId);
|
|
440
|
+
}
|
|
441
|
+
return OPENCLAW_DEFAULT_AGENT_ID;
|
|
442
|
+
},
|
|
443
|
+
|
|
213
444
|
/**
|
|
214
445
|
* Check if the channel is configured.
|
|
215
446
|
*/
|
|
@@ -234,8 +465,9 @@ _plugin.config = {
|
|
|
234
465
|
*/
|
|
235
466
|
describeAccount(cfg, accountId) {
|
|
236
467
|
const section = (cfg.channels || {})["aicq-chat"] || {};
|
|
468
|
+
const rawId = accountId || section.accountId || null;
|
|
237
469
|
return {
|
|
238
|
-
accountId:
|
|
470
|
+
accountId: rawId ? resolveTemplateVar(cfg, rawId) : null,
|
|
239
471
|
label: "AICQ Encrypted Chat",
|
|
240
472
|
enabled: section.enabled !== false,
|
|
241
473
|
};
|
package/src/ui-routes.js
CHANGED
|
File without changes
|