aicq-chat-plugin 3.4.1 → 3.5.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.
@@ -2,7 +2,7 @@
2
2
  "kind": "channel",
3
3
  "id": "aicq-chat",
4
4
  "name": "AICQ Encrypted Chat",
5
- "version": "3.4.1",
5
+ "version": "3.5.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": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aicq-chat-plugin",
3
- "version": "3.4.1",
3
+ "version": "3.5.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/src/channel.js CHANGED
@@ -32,6 +32,26 @@ 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
+ function resolveTemplateVar(cfg, value) {
40
+ if (typeof value !== "string") return value;
41
+ const match = value.match(/^\{\{(\w[\w.]*)\}\}$/);
42
+ if (!match) return value;
43
+
44
+ const path = match[1]; // e.g. "agent.id"
45
+ if (path === "agent.id") {
46
+ const agents = cfg.agents?.list || [];
47
+ if (agents.length > 0) return agents[0].id;
48
+ // Fallback: use the default agent ID from config
49
+ return cfg.agents?.defaultId || "default";
50
+ }
51
+
52
+ return value; // unknown template — return as-is
53
+ }
54
+
35
55
  // ── Resolved account type ────────────────────────────────────────────
36
56
  // This is the object returned by resolveAccount() and consumed by
37
57
  // security / pairing / outbound adapters.
@@ -43,14 +63,17 @@ export const runtime = {
43
63
  */
44
64
  function resolveAccount(cfg, accountId) {
45
65
  const section = (cfg.channels || {})["aicq-chat"] || {};
46
- const resolvedAccountId = accountId || section.accountId || null;
66
+ const rawAccountId = accountId || section.accountId || null;
47
67
 
48
- if (!resolvedAccountId) {
68
+ if (!rawAccountId) {
49
69
  throw new Error(
50
70
  "aicq-chat: accountId is required (set channels.aicq-chat.accountId)"
51
71
  );
52
72
  }
53
73
 
74
+ // Resolve template variables like {{agent.id}}
75
+ const resolvedAccountId = resolveTemplateVar(cfg, rawAccountId);
76
+
54
77
  return {
55
78
  accountId: resolvedAccountId,
56
79
  serverUrl: section.serverUrl || "https://aicq.online",
@@ -184,18 +207,167 @@ const _plugin = createChatChannelPlugin({
184
207
  },
185
208
  });
186
209
 
210
+ // ── Gateway adapter: startAccount / stopAccount ───────────────────────
211
+ // OpenClaw calls startAccount when the channel is activated (on startup
212
+ // or when re-enabled). This is where we initialise the runtime, connect
213
+ // to the AICQ signalling server, and wire up inbound message delivery
214
+ // via the channelRuntime helpers.
215
+
216
+ _plugin.gateway = {
217
+ /**
218
+ * Start the channel account — connect to the AICQ server and begin
219
+ * listening for inbound messages.
220
+ */
221
+ async startAccount(ctx) {
222
+ const { cfg, accountId, account, setStatus } = ctx;
223
+
224
+ console.log(`[AICQ Channel] startAccount called for ${accountId}`);
225
+
226
+ // Ensure the runtime (DB, identity, transport) is initialised
227
+ if (runtime.handleGateway) {
228
+ // Runtime already initialised via registerFull — just verify
229
+ }
230
+
231
+ // Resolve the agent ID from OpenClaw config
232
+ const agents = cfg.agents?.list || [];
233
+ const agentId = agents.length > 0 ? agents[0].id : accountId;
234
+
235
+ // Ensure we have an identity in the plugin DB
236
+ if (runtime.identity) {
237
+ const existing = runtime.identity.listAgents();
238
+ if (existing.length === 0) {
239
+ runtime.identity.createAgent(agentId, agents[0]?.name || "AICQ Agent");
240
+ console.log(`[AICQ Channel] Created agent identity: ${agentId}`);
241
+ }
242
+ }
243
+
244
+ // Connect to the AICQ server
245
+ if (runtime.serverClient) {
246
+ try {
247
+ await runtime.serverClient.ensureAuth(agentId);
248
+ console.log(`[AICQ Channel] Authenticated as ${agentId}`);
249
+
250
+ // Connect WebSocket for real-time messages
251
+ if (typeof runtime.serverClient.connect === "function") {
252
+ await runtime.serverClient.connect(agentId);
253
+ console.log("[AICQ Channel] WebSocket connected");
254
+ }
255
+
256
+ // Sync friends and groups from server
257
+ if (runtime.handleGateway) {
258
+ try {
259
+ await runtime.handleGateway("aicq.friends.list", {});
260
+ await runtime.handleGateway("aicq.groups.list", {});
261
+ } catch (e) {
262
+ console.warn("[AICQ Channel] Initial sync failed:", e.message);
263
+ }
264
+ }
265
+ } catch (e) {
266
+ console.error("[AICQ Channel] Failed to connect:", e.message);
267
+ }
268
+ }
269
+
270
+ // Wire up inbound message handling via channelRuntime if available
271
+ if (ctx.channelRuntime) {
272
+ const { reply, routing } = ctx.channelRuntime;
273
+ if (reply && routing) {
274
+ console.log("[AICQ Channel] channelRuntime available — AI dispatch enabled");
275
+
276
+ // Register inbound message handler on the serverClient
277
+ if (runtime.serverClient && typeof runtime.serverClient.onMessage === "function") {
278
+ runtime.serverClient.onMessage(async (msg) => {
279
+ try {
280
+ const resolvedAgentId = agents.length > 0 ? agents[0].id : accountId;
281
+ const routeResult = await routing.resolveAgentRoute({
282
+ channelId: "aicq-chat",
283
+ accountId,
284
+ fromId: msg.from || msg.sender_id,
285
+ chatType: msg.isGroup ? "group" : "dm",
286
+ });
287
+
288
+ if (routeResult?.agentId) {
289
+ await reply.dispatchReplyWithBufferedBlockDispatcher({
290
+ ctx: {
291
+ channelId: "aicq-chat",
292
+ accountId,
293
+ fromId: msg.from || msg.sender_id,
294
+ text: msg.content || msg.text || "",
295
+ chatType: msg.isGroup ? "group" : "dm",
296
+ },
297
+ cfg,
298
+ dispatcherOptions: {
299
+ deliver: async (payload) => {
300
+ // Send AI reply back through AICQ
301
+ if (runtime.chat && payload.text) {
302
+ await runtime.chat.sendMessage(
303
+ resolvedAgentId,
304
+ msg.from || msg.sender_id,
305
+ payload.text,
306
+ { isGroup: !!msg.isGroup }
307
+ );
308
+ }
309
+ },
310
+ },
311
+ });
312
+ }
313
+ } catch (e) {
314
+ console.error("[AICQ Channel] Inbound message handling error:", e.message);
315
+ }
316
+ });
317
+ }
318
+ }
319
+ } else {
320
+ console.log("[AICQ Channel] channelRuntime not available — running in standalone mode");
321
+ }
322
+
323
+ // Update health status
324
+ setStatus({
325
+ accountId,
326
+ enabled: true,
327
+ configured: true,
328
+ running: true,
329
+ lastStartAt: Date.now(),
330
+ lastError: null,
331
+ });
332
+
333
+ console.log(`[AICQ Channel] Account ${accountId} started successfully`);
334
+ },
335
+
336
+ /**
337
+ * Stop the channel account — disconnect and clean up.
338
+ */
339
+ async stopAccount(ctx) {
340
+ const { accountId } = ctx;
341
+ console.log(`[AICQ Channel] stopAccount called for ${accountId}`);
342
+
343
+ if (runtime.serverClient && typeof runtime.serverClient.disconnect === "function") {
344
+ try {
345
+ runtime.serverClient.disconnect();
346
+ console.log("[AICQ Channel] WebSocket disconnected");
347
+ } catch (e) {
348
+ console.warn("[AICQ Channel] Disconnect error:", e.message);
349
+ }
350
+ }
351
+ },
352
+ };
353
+
187
354
  // ── Add config helpers (required by OpenClaw channel loader) ──────────
188
355
  // createChatChannelPlugin does not auto-attach config helpers,
189
356
  // but the OpenClaw loader requires plugin.config.listAccountIds
190
357
  // and plugin.config.resolveAccount for channel registration.
358
+
359
+ // resolveTemplateVar is defined at the top of this file.
360
+
191
361
  _plugin.config = {
192
362
  /**
193
363
  * List all account IDs configured for this channel.
364
+ * Resolves template variables like {{agent.id}}.
194
365
  */
195
366
  listAccountIds(cfg) {
196
367
  const section = (cfg.channels || {})["aicq-chat"] || {};
197
368
  if (section.accountId) {
198
- return [section.accountId];
369
+ const resolved = resolveTemplateVar(cfg, section.accountId);
370
+ return [resolved];
199
371
  }
200
372
  return [];
201
373
  },
@@ -210,6 +382,18 @@ _plugin.config = {
210
382
  */
211
383
  inspectAccount,
212
384
 
385
+ /**
386
+ * Default account ID for this channel.
387
+ * Resolves {{agent.id}} to the actual agent ID.
388
+ */
389
+ defaultAccountId(cfg) {
390
+ const section = (cfg.channels || {})["aicq-chat"] || {};
391
+ if (section.accountId) {
392
+ return resolveTemplateVar(cfg, section.accountId);
393
+ }
394
+ return "default";
395
+ },
396
+
213
397
  /**
214
398
  * Check if the channel is configured.
215
399
  */
@@ -234,8 +418,9 @@ _plugin.config = {
234
418
  */
235
419
  describeAccount(cfg, accountId) {
236
420
  const section = (cfg.channels || {})["aicq-chat"] || {};
421
+ const rawId = accountId || section.accountId || null;
237
422
  return {
238
- accountId: accountId || section.accountId || null,
423
+ accountId: rawId ? resolveTemplateVar(cfg, rawId) : null,
239
424
  label: "AICQ Encrypted Chat",
240
425
  enabled: section.enabled !== false,
241
426
  };