aicq-chat-plugin 2.5.8 → 2.6.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/extension.js +20 -8
- package/index.js +573 -568
- package/lib/database.js +205 -85
- package/openclaw.plugin.json +2 -2
- package/package.json +9 -9
- package/postinstall.js +1 -1
package/lib/database.js
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* AICQ Plugin Database — SQLite via
|
|
2
|
+
* AICQ Plugin Database — SQLite via sql.js (pure WASM, no native compilation)
|
|
3
|
+
*
|
|
4
|
+
* This replaces better-sqlite3 with sql.js to avoid C++ native binding issues
|
|
5
|
+
* when installed via `openclaw plugins install`.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* const db = new PluginDatabase(dataDir);
|
|
9
|
+
* await db.init(); // MUST call init() before using any methods
|
|
10
|
+
* ... use db methods (all synchronous after init) ...
|
|
11
|
+
* db.close();
|
|
3
12
|
*/
|
|
4
|
-
const
|
|
13
|
+
const initSqlJs = require('sql.js');
|
|
5
14
|
const path = require('path');
|
|
6
15
|
const fs = require('fs');
|
|
7
16
|
const crypto = require('crypto');
|
|
@@ -9,18 +18,57 @@ const crypto = require('crypto');
|
|
|
9
18
|
class PluginDatabase {
|
|
10
19
|
constructor(dataDir) {
|
|
11
20
|
this.dataDir = dataDir;
|
|
21
|
+
this.db = null;
|
|
22
|
+
this.dbPath = path.join(dataDir, 'aicq-plugin.db');
|
|
23
|
+
this._dirty = false;
|
|
24
|
+
this._saveTimer = null;
|
|
12
25
|
fs.mkdirSync(dataDir, { recursive: true });
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ── Async initialization ────────────────────────────────────────────
|
|
29
|
+
async init() {
|
|
30
|
+
const SQL = await initSqlJs();
|
|
31
|
+
|
|
32
|
+
// Load existing database or create new
|
|
33
|
+
if (fs.existsSync(this.dbPath)) {
|
|
34
|
+
try {
|
|
35
|
+
const buffer = fs.readFileSync(this.dbPath);
|
|
36
|
+
this.db = new SQL.Database(buffer);
|
|
37
|
+
} catch (e) {
|
|
38
|
+
console.error('[AICQ DB] Failed to load database, creating new one:', e.message);
|
|
39
|
+
this.db = new SQL.Database();
|
|
40
|
+
}
|
|
41
|
+
} else {
|
|
42
|
+
this.db = new SQL.Database();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
this.db.run('PRAGMA foreign_keys = ON');
|
|
19
46
|
this._initSchema();
|
|
47
|
+
this._save(); // Persist initial schema
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ── Persist database to disk ────────────────────────────────────────
|
|
51
|
+
_save() {
|
|
52
|
+
try {
|
|
53
|
+
const data = this.db.export();
|
|
54
|
+
const buffer = Buffer.from(data);
|
|
55
|
+
fs.writeFileSync(this.dbPath, buffer);
|
|
56
|
+
this._dirty = false;
|
|
57
|
+
} catch (e) {
|
|
58
|
+
console.error('[AICQ DB] Save failed:', e.message);
|
|
59
|
+
}
|
|
20
60
|
}
|
|
21
61
|
|
|
62
|
+
// Debounced save — coalesces multiple writes within 500ms
|
|
63
|
+
_scheduleSave() {
|
|
64
|
+
this._dirty = true;
|
|
65
|
+
if (this._saveTimer) clearTimeout(this._saveTimer);
|
|
66
|
+
this._saveTimer = setTimeout(() => this._save(), 500);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ── Schema ──────────────────────────────────────────────────────────
|
|
22
70
|
_initSchema() {
|
|
23
|
-
this.
|
|
71
|
+
this._execScript(`
|
|
24
72
|
CREATE TABLE IF NOT EXISTS identity (
|
|
25
73
|
agent_id TEXT PRIMARY KEY,
|
|
26
74
|
nickname TEXT NOT NULL DEFAULT '',
|
|
@@ -46,7 +94,6 @@ class PluginDatabase {
|
|
|
46
94
|
ai_name TEXT NOT NULL DEFAULT '',
|
|
47
95
|
ai_avatar TEXT NOT NULL DEFAULT ''
|
|
48
96
|
);
|
|
49
|
-
CREATE INDEX IF NOT EXISTS idx_friends_agent ON friends(agent_id);
|
|
50
97
|
|
|
51
98
|
CREATE TABLE IF NOT EXISTS groups (
|
|
52
99
|
id TEXT PRIMARY KEY,
|
|
@@ -59,7 +106,6 @@ class PluginDatabase {
|
|
|
59
106
|
created_at TEXT NOT NULL,
|
|
60
107
|
updated_at TEXT
|
|
61
108
|
);
|
|
62
|
-
CREATE INDEX IF NOT EXISTS idx_groups_agent ON groups(agent_id);
|
|
63
109
|
|
|
64
110
|
CREATE TABLE IF NOT EXISTS sessions (
|
|
65
111
|
peer_id TEXT PRIMARY KEY,
|
|
@@ -85,8 +131,6 @@ class PluginDatabase {
|
|
|
85
131
|
timestamp TEXT NOT NULL,
|
|
86
132
|
status TEXT NOT NULL DEFAULT 'pending'
|
|
87
133
|
);
|
|
88
|
-
CREATE INDEX IF NOT EXISTS idx_chat_agent_target ON chat_history(agent_id, target_id);
|
|
89
|
-
CREATE INDEX IF NOT EXISTS idx_chat_timestamp ON chat_history(agent_id, target_id, timestamp);
|
|
90
134
|
|
|
91
135
|
CREATE TABLE IF NOT EXISTS pending_requests (
|
|
92
136
|
session_id TEXT PRIMARY KEY,
|
|
@@ -110,7 +154,6 @@ class PluginDatabase {
|
|
|
110
154
|
data TEXT NOT NULL,
|
|
111
155
|
created_at TEXT NOT NULL
|
|
112
156
|
);
|
|
113
|
-
CREATE INDEX IF NOT EXISTS idx_offline_target ON offline_queue(agent_id, target_id);
|
|
114
157
|
|
|
115
158
|
CREATE TABLE IF NOT EXISTS group_settings (
|
|
116
159
|
group_id TEXT PRIMARY KEY,
|
|
@@ -118,48 +161,104 @@ class PluginDatabase {
|
|
|
118
161
|
silent_mode INTEGER NOT NULL DEFAULT 0
|
|
119
162
|
);
|
|
120
163
|
`);
|
|
164
|
+
|
|
165
|
+
// Create indexes (must be separate statements)
|
|
166
|
+
this.db.run('CREATE INDEX IF NOT EXISTS idx_friends_agent ON friends(agent_id)');
|
|
167
|
+
this.db.run('CREATE INDEX IF NOT EXISTS idx_groups_agent ON groups(agent_id)');
|
|
168
|
+
this.db.run('CREATE INDEX IF NOT EXISTS idx_chat_agent_target ON chat_history(agent_id, target_id)');
|
|
169
|
+
this.db.run('CREATE INDEX IF NOT EXISTS idx_chat_timestamp ON chat_history(agent_id, target_id, timestamp)');
|
|
170
|
+
this.db.run('CREATE INDEX IF NOT EXISTS idx_offline_target ON offline_queue(agent_id, target_id)');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Execute multiple SQL statements (like db.exec in better-sqlite3)
|
|
174
|
+
_execScript(sql) {
|
|
175
|
+
// sql.js db.exec() can handle multiple statements but returns results.
|
|
176
|
+
// For DDL statements we just use run() for each.
|
|
177
|
+
const statements = sql.split(';').map(s => s.trim()).filter(s => s.length > 0);
|
|
178
|
+
for (const stmt of statements) {
|
|
179
|
+
try {
|
|
180
|
+
this.db.run(stmt);
|
|
181
|
+
} catch (e) {
|
|
182
|
+
// Ignore "already exists" errors for CREATE TABLE/INDEX
|
|
183
|
+
if (!e.message.includes('already exists')) {
|
|
184
|
+
console.error('[AICQ DB] Schema error:', e.message, 'SQL:', stmt.substring(0, 80));
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// ── Query helpers ───────────────────────────────────────────────────
|
|
191
|
+
|
|
192
|
+
// Run a parameterized write query, then schedule save
|
|
193
|
+
_run(sql, params) {
|
|
194
|
+
this.db.run(sql, params || []);
|
|
195
|
+
this._scheduleSave();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Get a single row as an object
|
|
199
|
+
_get(sql, params) {
|
|
200
|
+
const stmt = this.db.prepare(sql);
|
|
201
|
+
stmt.bind(params || []);
|
|
202
|
+
let row = null;
|
|
203
|
+
if (stmt.step()) {
|
|
204
|
+
row = stmt.getAsObject();
|
|
205
|
+
}
|
|
206
|
+
stmt.free();
|
|
207
|
+
return row;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Get all rows as objects
|
|
211
|
+
_all(sql, params) {
|
|
212
|
+
const stmt = this.db.prepare(sql);
|
|
213
|
+
stmt.bind(params || []);
|
|
214
|
+
const rows = [];
|
|
215
|
+
while (stmt.step()) {
|
|
216
|
+
rows.push(stmt.getAsObject());
|
|
217
|
+
}
|
|
218
|
+
stmt.free();
|
|
219
|
+
return rows;
|
|
121
220
|
}
|
|
122
221
|
|
|
123
222
|
// ─── Identity ──────────────────────────────────────────────────────
|
|
124
223
|
|
|
125
224
|
saveIdentity({ agent_id, nickname, signing_public_key, signing_secret_key, exchange_public_key, exchange_secret_key }) {
|
|
126
225
|
const now = new Date().toISOString();
|
|
127
|
-
this.
|
|
128
|
-
INSERT OR REPLACE INTO identity (agent_id, nickname, signing_public_key, signing_secret_key, exchange_public_key, exchange_secret_key, created_at, updated_at)
|
|
129
|
-
|
|
130
|
-
|
|
226
|
+
this._run(
|
|
227
|
+
`INSERT OR REPLACE INTO identity (agent_id, nickname, signing_public_key, signing_secret_key, exchange_public_key, exchange_secret_key, created_at, updated_at)
|
|
228
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
229
|
+
[agent_id, nickname || '', signing_public_key, signing_secret_key, exchange_public_key, exchange_secret_key, now, now]
|
|
230
|
+
);
|
|
131
231
|
}
|
|
132
232
|
|
|
133
233
|
loadIdentity(agentId) {
|
|
134
|
-
return this.
|
|
234
|
+
return this._get('SELECT * FROM identity WHERE agent_id = ?', [agentId]);
|
|
135
235
|
}
|
|
136
236
|
|
|
137
237
|
listIdentities() {
|
|
138
|
-
return this.
|
|
238
|
+
return this._all('SELECT agent_id, nickname, signing_public_key, exchange_public_key, created_at FROM identity');
|
|
139
239
|
}
|
|
140
240
|
|
|
141
241
|
deleteIdentity(agentId) {
|
|
142
|
-
this.
|
|
143
|
-
this.
|
|
144
|
-
this.
|
|
145
|
-
this.
|
|
146
|
-
this.
|
|
242
|
+
this._run('DELETE FROM identity WHERE agent_id = ?', [agentId]);
|
|
243
|
+
this._run('DELETE FROM friends WHERE agent_id = ?', [agentId]);
|
|
244
|
+
this._run('DELETE FROM groups WHERE agent_id = ?', [agentId]);
|
|
245
|
+
this._run('DELETE FROM chat_history WHERE agent_id = ?', [agentId]);
|
|
246
|
+
this._run('DELETE FROM sessions WHERE agent_id = ?', [agentId]);
|
|
147
247
|
}
|
|
148
248
|
|
|
149
249
|
updateNickname(agentId, nickname) {
|
|
150
250
|
const now = new Date().toISOString();
|
|
151
|
-
this.
|
|
251
|
+
this._run('UPDATE identity SET nickname = ?, updated_at = ? WHERE agent_id = ?', [nickname, now, agentId]);
|
|
152
252
|
}
|
|
153
253
|
|
|
154
254
|
updateAvatar(agentId, avatarUrl) {
|
|
155
255
|
const now = new Date().toISOString();
|
|
156
|
-
// Ensure column exists (migration for existing databases)
|
|
157
256
|
try {
|
|
158
|
-
this.
|
|
257
|
+
this._run('UPDATE identity SET avatar = ?, updated_at = ? WHERE agent_id = ?', [avatarUrl, now, agentId]);
|
|
159
258
|
} catch (e) {
|
|
160
|
-
if (e.message.includes('no column named avatar')) {
|
|
161
|
-
this.db.
|
|
162
|
-
this.
|
|
259
|
+
if (e.message && e.message.includes('no column named avatar')) {
|
|
260
|
+
this.db.run('ALTER TABLE identity ADD COLUMN avatar TEXT NOT NULL DEFAULT ""');
|
|
261
|
+
this._run('UPDATE identity SET avatar = ?, updated_at = ? WHERE agent_id = ?', [avatarUrl, now, agentId]);
|
|
163
262
|
} else {
|
|
164
263
|
throw e;
|
|
165
264
|
}
|
|
@@ -170,57 +269,63 @@ class PluginDatabase {
|
|
|
170
269
|
|
|
171
270
|
addFriend({ agent_id, id, public_key, fingerprint, friend_type = 'ai', ai_name = '', permissions = ['chat'] }) {
|
|
172
271
|
const now = new Date().toISOString();
|
|
173
|
-
this.
|
|
174
|
-
INSERT OR REPLACE INTO friends (id, agent_id, public_key, fingerprint, added_at, is_online, permissions, friend_type, ai_name, ai_avatar)
|
|
175
|
-
|
|
176
|
-
|
|
272
|
+
this._run(
|
|
273
|
+
`INSERT OR REPLACE INTO friends (id, agent_id, public_key, fingerprint, added_at, is_online, permissions, friend_type, ai_name, ai_avatar)
|
|
274
|
+
VALUES (?, ?, ?, ?, ?, 0, ?, ?, ?, '')`,
|
|
275
|
+
[id, agent_id, public_key, fingerprint, now, JSON.stringify(permissions), friend_type, ai_name]
|
|
276
|
+
);
|
|
177
277
|
}
|
|
178
278
|
|
|
179
279
|
removeFriend(agentId, friendId) {
|
|
180
|
-
this.
|
|
181
|
-
this.
|
|
280
|
+
this._run('DELETE FROM friends WHERE agent_id = ? AND id = ?', [agentId, friendId]);
|
|
281
|
+
this._run('DELETE FROM sessions WHERE agent_id = ? AND peer_id = ?', [agentId, friendId]);
|
|
182
282
|
}
|
|
183
283
|
|
|
184
284
|
getFriend(agentId, friendId) {
|
|
185
|
-
return this.
|
|
285
|
+
return this._get('SELECT * FROM friends WHERE agent_id = ? AND id = ?', [agentId, friendId]);
|
|
186
286
|
}
|
|
187
287
|
|
|
188
288
|
listFriends(agentId) {
|
|
189
|
-
return this.
|
|
289
|
+
return this._all('SELECT * FROM friends WHERE agent_id = ? ORDER BY added_at DESC', [agentId]);
|
|
190
290
|
}
|
|
191
291
|
|
|
192
292
|
updateFriendOnline(agentId, friendId, isOnline) {
|
|
193
293
|
const now = isOnline ? new Date().toISOString() : null;
|
|
194
|
-
|
|
195
|
-
|
|
294
|
+
// COALESCE equivalent: if now is null, keep existing last_seen
|
|
295
|
+
this._run(
|
|
296
|
+
'UPDATE friends SET is_online = ?, last_seen = COALESCE(?, last_seen) WHERE agent_id = ? AND id = ?',
|
|
297
|
+
[isOnline ? 1 : 0, now, agentId, friendId]
|
|
298
|
+
);
|
|
196
299
|
}
|
|
197
300
|
|
|
198
301
|
// ─── Groups ────────────────────────────────────────────────────────
|
|
199
302
|
|
|
200
303
|
addGroup({ agent_id, id, name, owner_id, members_json = '[]', description = '' }) {
|
|
201
304
|
const now = new Date().toISOString();
|
|
202
|
-
this.
|
|
203
|
-
INSERT OR REPLACE INTO groups (id, agent_id, name, owner_id, members_json, description, silent_mode, created_at, updated_at)
|
|
204
|
-
|
|
205
|
-
|
|
305
|
+
this._run(
|
|
306
|
+
`INSERT OR REPLACE INTO groups (id, agent_id, name, owner_id, members_json, description, silent_mode, created_at, updated_at)
|
|
307
|
+
VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?)`,
|
|
308
|
+
[id, agent_id, name, owner_id, typeof members_json === 'string' ? members_json : JSON.stringify(members_json), description, now, now]
|
|
309
|
+
);
|
|
206
310
|
}
|
|
207
311
|
|
|
208
312
|
listGroups(agentId) {
|
|
209
|
-
return this.
|
|
313
|
+
return this._all('SELECT * FROM groups WHERE agent_id = ? ORDER BY created_at DESC', [agentId]);
|
|
210
314
|
}
|
|
211
315
|
|
|
212
316
|
getGroup(agentId, groupId) {
|
|
213
|
-
return this.
|
|
317
|
+
return this._get('SELECT * FROM groups WHERE agent_id = ? AND id = ?', [agentId, groupId]);
|
|
214
318
|
}
|
|
215
319
|
|
|
216
320
|
setGroupSilentMode(agentId, groupId, silent) {
|
|
217
|
-
this.
|
|
218
|
-
INSERT OR REPLACE INTO group_settings (group_id, agent_id, silent_mode) VALUES (?, ?, ?)
|
|
219
|
-
|
|
321
|
+
this._run(
|
|
322
|
+
'INSERT OR REPLACE INTO group_settings (group_id, agent_id, silent_mode) VALUES (?, ?, ?)',
|
|
323
|
+
[groupId, agentId, silent ? 1 : 0]
|
|
324
|
+
);
|
|
220
325
|
}
|
|
221
326
|
|
|
222
327
|
getGroupSilentMode(agentId, groupId) {
|
|
223
|
-
const row = this.
|
|
328
|
+
const row = this._get('SELECT silent_mode FROM group_settings WHERE group_id = ? AND agent_id = ?', [groupId, agentId]);
|
|
224
329
|
return row ? !!row.silent_mode : false;
|
|
225
330
|
}
|
|
226
331
|
|
|
@@ -228,18 +333,19 @@ class PluginDatabase {
|
|
|
228
333
|
|
|
229
334
|
saveSession({ agent_id, peer_id, session_key }) {
|
|
230
335
|
const now = new Date().toISOString();
|
|
231
|
-
this.
|
|
232
|
-
INSERT OR REPLACE INTO sessions (peer_id, agent_id, session_key, created_at, message_count, last_rotation)
|
|
233
|
-
|
|
234
|
-
|
|
336
|
+
this._run(
|
|
337
|
+
`INSERT OR REPLACE INTO sessions (peer_id, agent_id, session_key, created_at, message_count, last_rotation)
|
|
338
|
+
VALUES (?, ?, ?, ?, 0, ?)`,
|
|
339
|
+
[peer_id, agent_id, session_key, now, now]
|
|
340
|
+
);
|
|
235
341
|
}
|
|
236
342
|
|
|
237
343
|
loadSession(agentId, peerId) {
|
|
238
|
-
return this.
|
|
344
|
+
return this._get('SELECT * FROM sessions WHERE agent_id = ? AND peer_id = ?', [agentId, peerId]);
|
|
239
345
|
}
|
|
240
346
|
|
|
241
347
|
incrementSessionMessageCount(agentId, peerId) {
|
|
242
|
-
this.
|
|
348
|
+
this._run('UPDATE sessions SET message_count = message_count + 1 WHERE agent_id = ? AND peer_id = ?', [agentId, peerId]);
|
|
243
349
|
}
|
|
244
350
|
|
|
245
351
|
// ─── Chat History ──────────────────────────────────────────────────
|
|
@@ -247,73 +353,83 @@ class PluginDatabase {
|
|
|
247
353
|
saveMessage({ agent_id, target_id, from_id, to_id, type = 'text', content = '', file_url = null, file_name = null, is_group = 0, mentions = [], status = 'pending' }) {
|
|
248
354
|
const id = crypto.randomUUID();
|
|
249
355
|
const now = new Date().toISOString();
|
|
250
|
-
this.
|
|
251
|
-
INSERT INTO chat_history (id, agent_id, target_id, from_id, to_id, type, content, file_url, file_name, is_group, mentions, timestamp, status)
|
|
252
|
-
|
|
253
|
-
|
|
356
|
+
this._run(
|
|
357
|
+
`INSERT INTO chat_history (id, agent_id, target_id, from_id, to_id, type, content, file_url, file_name, is_group, mentions, timestamp, status)
|
|
358
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
359
|
+
[id, agent_id, target_id, from_id, to_id, type, content, file_url, file_name, is_group, JSON.stringify(mentions), now, status]
|
|
360
|
+
);
|
|
254
361
|
return { id, timestamp: now };
|
|
255
362
|
}
|
|
256
363
|
|
|
257
364
|
getChatHistory(agentId, targetId, { limit = 50, before = null } = {}) {
|
|
258
365
|
if (before) {
|
|
259
|
-
return this.
|
|
260
|
-
'SELECT * FROM chat_history WHERE agent_id = ? AND target_id = ? AND timestamp < ? ORDER BY timestamp DESC LIMIT ?'
|
|
261
|
-
|
|
366
|
+
return this._all(
|
|
367
|
+
'SELECT * FROM chat_history WHERE agent_id = ? AND target_id = ? AND timestamp < ? ORDER BY timestamp DESC LIMIT ?',
|
|
368
|
+
[agentId, targetId, before, limit]
|
|
369
|
+
);
|
|
262
370
|
}
|
|
263
|
-
return this.
|
|
264
|
-
'SELECT * FROM chat_history WHERE agent_id = ? AND target_id = ? ORDER BY timestamp DESC LIMIT ?'
|
|
265
|
-
|
|
371
|
+
return this._all(
|
|
372
|
+
'SELECT * FROM chat_history WHERE agent_id = ? AND target_id = ? ORDER BY timestamp DESC LIMIT ?',
|
|
373
|
+
[agentId, targetId, limit]
|
|
374
|
+
);
|
|
266
375
|
}
|
|
267
376
|
|
|
268
377
|
deleteMessage(agentId, messageId) {
|
|
269
|
-
this.
|
|
378
|
+
this._run('DELETE FROM chat_history WHERE agent_id = ? AND id = ?', [agentId, messageId]);
|
|
270
379
|
}
|
|
271
380
|
|
|
272
381
|
updateMessageStatus(agentId, messageId, status) {
|
|
273
|
-
this.
|
|
382
|
+
this._run('UPDATE chat_history SET status = ? WHERE agent_id = ? AND id = ?', [status, agentId, messageId]);
|
|
274
383
|
}
|
|
275
384
|
|
|
276
385
|
// ─── Pending Requests ──────────────────────────────────────────────
|
|
277
386
|
|
|
278
387
|
savePendingRequest({ agent_id, session_id, requester_id, requester_public_key }) {
|
|
279
388
|
const now = new Date().toISOString();
|
|
280
|
-
this.
|
|
281
|
-
INSERT OR REPLACE INTO pending_requests (session_id, agent_id, requester_id, requester_public_key, timestamp)
|
|
282
|
-
|
|
283
|
-
|
|
389
|
+
this._run(
|
|
390
|
+
`INSERT OR REPLACE INTO pending_requests (session_id, agent_id, requester_id, requester_public_key, timestamp)
|
|
391
|
+
VALUES (?, ?, ?, ?, ?)`,
|
|
392
|
+
[session_id, agent_id, requester_id, requester_public_key, now]
|
|
393
|
+
);
|
|
284
394
|
}
|
|
285
395
|
|
|
286
396
|
getPendingRequests(agentId) {
|
|
287
|
-
return this.
|
|
397
|
+
return this._all('SELECT * FROM pending_requests WHERE agent_id = ? ORDER BY timestamp DESC', [agentId]);
|
|
288
398
|
}
|
|
289
399
|
|
|
290
400
|
removePendingRequest(agentId, sessionId) {
|
|
291
|
-
this.
|
|
401
|
+
this._run('DELETE FROM pending_requests WHERE agent_id = ? AND session_id = ?', [agentId, sessionId]);
|
|
292
402
|
}
|
|
293
403
|
|
|
294
404
|
// ─── Temp Numbers ──────────────────────────────────────────────────
|
|
295
405
|
|
|
296
406
|
saveTempNumber({ agent_id, number, expires_at }) {
|
|
297
407
|
const now = new Date().toISOString();
|
|
298
|
-
this.
|
|
299
|
-
|
|
408
|
+
this._run(
|
|
409
|
+
'INSERT OR REPLACE INTO temp_numbers (number, agent_id, expires_at, created_at) VALUES (?, ?, ?, ?)',
|
|
410
|
+
[number, agent_id, expires_at, now]
|
|
411
|
+
);
|
|
300
412
|
}
|
|
301
413
|
|
|
302
414
|
// ─── Offline Queue ─────────────────────────────────────────────────
|
|
303
415
|
|
|
304
416
|
enqueueOffline({ agent_id, target_id, data }) {
|
|
305
417
|
const now = new Date().toISOString();
|
|
306
|
-
this.
|
|
307
|
-
|
|
418
|
+
this._run(
|
|
419
|
+
'INSERT INTO offline_queue (agent_id, target_id, data, created_at) VALUES (?, ?, ?, ?)',
|
|
420
|
+
[agent_id, target_id, data, now]
|
|
421
|
+
);
|
|
308
422
|
}
|
|
309
423
|
|
|
310
424
|
dequeueOffline(agentId, targetId, limit = 100) {
|
|
311
|
-
const rows = this.
|
|
312
|
-
|
|
425
|
+
const rows = this._all(
|
|
426
|
+
'SELECT * FROM offline_queue WHERE agent_id = ? AND target_id = ? ORDER BY created_at ASC LIMIT ?',
|
|
427
|
+
[agentId, targetId, limit]
|
|
428
|
+
);
|
|
313
429
|
if (rows.length > 0) {
|
|
314
430
|
const ids = rows.map(r => r.id);
|
|
315
431
|
const placeholders = ids.map(() => '?').join(',');
|
|
316
|
-
this.
|
|
432
|
+
this._run(`DELETE FROM offline_queue WHERE id IN (${placeholders})`, ids);
|
|
317
433
|
}
|
|
318
434
|
return rows;
|
|
319
435
|
}
|
|
@@ -322,12 +438,16 @@ class PluginDatabase {
|
|
|
322
438
|
|
|
323
439
|
cleanup() {
|
|
324
440
|
const now = new Date().toISOString();
|
|
325
|
-
this.
|
|
326
|
-
this.
|
|
327
|
-
this.
|
|
441
|
+
this._run("DELETE FROM temp_numbers WHERE expires_at < ?", [now]);
|
|
442
|
+
this._run("DELETE FROM pending_requests WHERE timestamp < datetime(?, '-48 hours')", [now]);
|
|
443
|
+
this._run("DELETE FROM offline_queue WHERE created_at < datetime(?, '-7 days')", [now]);
|
|
328
444
|
}
|
|
329
445
|
|
|
330
446
|
close() {
|
|
447
|
+
if (this._saveTimer) {
|
|
448
|
+
clearTimeout(this._saveTimer);
|
|
449
|
+
}
|
|
450
|
+
this._save(); // Final save before closing
|
|
331
451
|
this.db.close();
|
|
332
452
|
}
|
|
333
453
|
}
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "aicq-chat",
|
|
3
3
|
"name": "AICQ Encrypted Chat",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.6.0",
|
|
5
5
|
"description": "End-to-end encrypted chat plugin for OpenClaw agents — Node.js implementation with full UI",
|
|
6
6
|
"entry": "index.js",
|
|
7
7
|
"activation": {
|
|
@@ -96,7 +96,7 @@
|
|
|
96
96
|
"requires": {
|
|
97
97
|
"node": ">=18.0.0",
|
|
98
98
|
"packages": [
|
|
99
|
-
"
|
|
99
|
+
"sql.js",
|
|
100
100
|
"tweetnacl",
|
|
101
101
|
"ws",
|
|
102
102
|
"qrcode",
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aicq-chat-plugin",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "AICQ End-to-end Encrypted Chat Plugin for OpenClaw
|
|
3
|
+
"version": "2.6.0",
|
|
4
|
+
"description": "AICQ End-to-end Encrypted Chat Plugin for OpenClaw — Full UI with friend management, group chat, file transfer, and AI agent communication",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"aicq-plugin": "cli.js"
|
|
@@ -45,15 +45,15 @@
|
|
|
45
45
|
},
|
|
46
46
|
"homepage": "https://aicq.online",
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"better-sqlite3": "^11.0.0",
|
|
49
|
-
"tweetnacl": "^1.0.3",
|
|
50
|
-
"tweetnacl-util": "^0.15.1",
|
|
51
|
-
"ws": "^8.16.0",
|
|
52
|
-
"qrcode": "^1.5.3",
|
|
53
48
|
"express": "^4.18.2",
|
|
54
|
-
"multer": "^1.4.5-lts.1",
|
|
55
49
|
"marked": "^12.0.0",
|
|
56
|
-
"
|
|
50
|
+
"multer": "^1.4.5-lts.1",
|
|
51
|
+
"node-fetch": "^2.7.0",
|
|
52
|
+
"qrcode": "^1.5.3",
|
|
53
|
+
"sql.js": "^1.14.1",
|
|
54
|
+
"tweetnacl": "^1.0.3",
|
|
55
|
+
"tweetnacl-util": "^0.15.1",
|
|
56
|
+
"ws": "^8.16.0"
|
|
57
57
|
},
|
|
58
58
|
"engines": {
|
|
59
59
|
"node": ">=18.0.0"
|
package/postinstall.js
CHANGED
|
@@ -270,7 +270,7 @@ console.log(' ╚════════════════════
|
|
|
270
270
|
console.log('');
|
|
271
271
|
|
|
272
272
|
// Read version from package.json
|
|
273
|
-
let version = '2.
|
|
273
|
+
let version = '2.6.0';
|
|
274
274
|
try {
|
|
275
275
|
const pkg = JSON.parse(fs.readFileSync(path.join(PLUGIN_DIR, 'package.json'), 'utf8'));
|
|
276
276
|
version = pkg.version;
|