aicq-openclaw 3.16.3 → 3.16.5

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/lib/database.js CHANGED
@@ -462,8 +462,14 @@ class PluginDatabase {
462
462
  if (this._saveTimer) {
463
463
  clearTimeout(this._saveTimer);
464
464
  }
465
- this._save(); // Final save before closing
466
- this.db.close();
465
+ // [fix 2026-08-29] init() may still be awaiting initSqlJs() when close()
466
+ // is called (plugin dispose during startup): this.db is null then, and
467
+ // the unconditional _save()/db.close() crashed with
468
+ // "Cannot read properties of null (reading 'export')".
469
+ if (this.db) {
470
+ this._save(); // Final save before closing
471
+ this.db.close();
472
+ }
467
473
  }
468
474
  }
469
475
 
@@ -36,9 +36,25 @@ class ServerClient {
36
36
  opts.body = JSON.stringify(body);
37
37
  }
38
38
  const resp = await fetch(`${this.apiUrl}${path}`, opts);
39
- const data = await resp.json();
39
+ const data = await resp.json().catch(() => ({}));
40
40
  if (!resp.ok) {
41
- throw new Error(data.error || data.message || `HTTP ${resp.status}`);
41
+ // [fix 2026-08-29] the AICQ server errors come back as
42
+ // {"error": {"code": "...", "message": "..."}} — a nested OBJECT.
43
+ // The old `new Error(data.error || ...)` interpolated the object and
44
+ // every failure surfaced to users/models as "[object Object]".
45
+ const err = data.error;
46
+ let msg;
47
+ if (err && typeof err === 'object') {
48
+ msg = err.message || err.code;
49
+ } else if (typeof err === 'string' && err) {
50
+ msg = err;
51
+ } else {
52
+ msg = data.message;
53
+ }
54
+ const e = new Error(msg || `HTTP ${resp.status}`);
55
+ e.status = resp.status;
56
+ e.code = (err && typeof err === 'object' && err.code) || undefined;
57
+ throw e;
42
58
  }
43
59
  return data;
44
60
  }
@@ -230,6 +246,9 @@ class ServerClient {
230
246
 
231
247
  connectWS() {
232
248
  if (this.ws && this.ws.readyState === WebSocket.OPEN) return;
249
+ // [fix 2026-08-29] a direct connectWS() call (non-start() path) also
250
+ // implies the client wants to stay connected.
251
+ this._running = true;
233
252
 
234
253
  const identity = this.identity.loadAgent(this.currentAgentId);
235
254
  if (!identity || !this.jwtToken) {
@@ -339,6 +358,11 @@ class ServerClient {
339
358
  }
340
359
 
341
360
  _scheduleReconnect() {
361
+ // [fix 2026-08-29] never reschedule after an explicit stop()/disconnect():
362
+ // the WS 'close' handler calls this even for intentional teardown, which
363
+ // left a reconnect timer (+ ping timer + open socket) keeping the host
364
+ // process alive forever after plugin dispose.
365
+ if (!this._running) return;
342
366
  if (this._reconnectTimer) return;
343
367
  this._reconnectTimer = setTimeout(() => {
344
368
  this._reconnectTimer = null;
@@ -354,8 +378,10 @@ class ServerClient {
354
378
  async start(agentId) {
355
379
  try {
356
380
  await this.ensureAuth(agentId);
357
- this.connectWS();
381
+ // [fix 2026-08-29] set _running BEFORE connecting so a fast 'close'
382
+ // event (e.g. server hiccup) is allowed to schedule a reconnect.
358
383
  this._running = true;
384
+ this.connectWS();
359
385
  console.log('[ServerClient] Started for agent:', agentId);
360
386
  } catch (e) {
361
387
  console.error('[ServerClient] Start failed:', e.message);
@@ -372,6 +398,11 @@ class ServerClient {
372
398
  }
373
399
 
374
400
  disconnect() {
401
+ // [fix 2026-08-29] mark stopped FIRST so the ws 'close' handler cannot
402
+ // schedule a zombie reconnect, and clear the ping interval here (it was
403
+ // only cleared in the 'close' event handler, which never fires if the
404
+ // socket never opened).
405
+ this._running = false;
375
406
  if (this.ws) {
376
407
  // SPEC 合规: offline 消息必须带 nodeId 字段
377
408
  // 见 aicqSDK/SPEC.md 第 215-219 行
@@ -383,6 +414,7 @@ class ServerClient {
383
414
  this.ws = null;
384
415
  }
385
416
  this.connected = false;
417
+ if (this._pingTimer) { clearInterval(this._pingTimer); this._pingTimer = null; }
386
418
  if (this._reconnectTimer) {
387
419
  clearTimeout(this._reconnectTimer);
388
420
  this._reconnectTimer = null;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "aicq-chat",
3
3
  "name": "AICQ Encrypted Chat",
4
- "version": "3.16.0",
4
+ "version": "3.16.4",
5
5
  "description": "End-to-end encrypted chat channel via AICQ protocol — in-process Channel plugin using OpenClaw Channel SDK",
6
6
  "entry": "index.js",
7
7
  "activation": {
@@ -33,10 +33,8 @@
33
33
  "items": {
34
34
  "type": "string"
35
35
  },
36
- "default": [
37
- "1000000"
38
- ],
39
- "description": "启动后自动添加为好友的 AICQ 号码列表(如 1000000)"
36
+ "default": [],
37
+ "description": "启动后自动添加为好友的 AICQ 号码列表;也可用环境变量 AICQ_AUTO_ADD_FRIENDS(逗号分隔)配置"
40
38
  },
41
39
  "serverUrl": {
42
40
  "type": "string",
@@ -82,7 +80,7 @@
82
80
  },
83
81
  "autoAddFriends": {
84
82
  "label": "Auto Add Friends",
85
- "help": "Comma-separated AICQ numbers to auto-add as friends on startup (e.g. 1000000)"
83
+ "help": "Comma-separated AICQ numbers to auto-add as friends on startup (or AICQ_AUTO_ADD_FRIENDS env)"
86
84
  },
87
85
  "serverUrl": {
88
86
  "label": "AICQ Server URL",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "aicq-openclaw",
3
- "version": "3.16.3",
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",
3
+ "version": "3.16.5",
4
+ "description": "AICQ End-to-end Encrypted Chat Channel Plugin for OpenClaw \u2014 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",
7
7
  "bin": {
@@ -81,8 +81,8 @@
81
81
  "channel": {
82
82
  "id": "aicq-chat",
83
83
  "label": "AICQ Encrypted Chat",
84
- "blurb": "端到端加密即时通讯频道,基于 NaCl (X25519 + XSalsa20-Poly1305)",
85
- "icon": "🔐"
84
+ "blurb": "\u7aef\u5230\u7aef\u52a0\u5bc6\u5373\u65f6\u901a\u8baf\u9891\u9053\uff0c\u57fa\u4e8e NaCl (X25519 + XSalsa20-Poly1305)",
85
+ "icon": "\ud83d\udd10"
86
86
  }
87
87
  },
88
88
  "optionalDependencies": {
package/src/channel.js CHANGED
@@ -347,8 +347,17 @@ _plugin.gateway = {
347
347
  }
348
348
  }
349
349
 
350
- // Auto-add friends from config (autoAddFriends list)
351
- const autoAddFriends = account?.autoAddFriends || cfg?.channels?.["aicq-chat"]?.autoAddFriends || [];
350
+ // Auto-add friends from config (autoAddFriends list).
351
+ // [fix v3.16.4] precedence: explicit channel config > AICQ_AUTO_ADD_FRIENDS env
352
+ // (runtime.autoAddFriends, set by index.js) > schema-resolved account default.
353
+ // The manifest default is now [] — it previously shipped a hard-coded author
354
+ // number, so every fresh install tried to befriend a stranger at startup and
355
+ // the env override was silently masked by the schema default.
356
+ const cfgAutoAdd = cfg?.channels?.["aicq-chat"]?.autoAddFriends;
357
+ const envAutoAdd = Array.isArray(runtime.autoAddFriends) ? runtime.autoAddFriends : [];
358
+ const autoAddFriends = (Array.isArray(cfgAutoAdd) && cfgAutoAdd.length > 0)
359
+ ? cfgAutoAdd
360
+ : (envAutoAdd.length > 0 ? envAutoAdd : (account?.autoAddFriends || []));
352
361
  if (Array.isArray(autoAddFriends) && autoAddFriends.length > 0) {
353
362
  console.log(`[AICQ Channel] Auto-adding ${autoAddFriends.length} friend(s) from config...`);
354
363
  for (const friendEntry of autoAddFriends) {