aicq-chat-plugin 3.8.1 → 3.9.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.
- package/README.md +80 -80
- package/SKILL.md +78 -78
- package/cli.cjs +356 -356
- package/index.js +417 -375
- package/lib/chat.js +854 -749
- package/lib/crypto.js +168 -168
- package/lib/database.js +455 -455
- package/lib/file-transfer.js +266 -266
- package/lib/handshake.js +147 -147
- package/lib/identity.js +165 -165
- package/lib/package.json +3 -3
- package/lib/server-client.js +380 -337
- package/openclaw.plugin.json +170 -168
- package/package.json +87 -87
- package/postinstall.cjs +27 -27
- package/public/favicon.ico +0 -0
- package/public/icon-16.png +0 -0
- package/public/icon-32.png +0 -0
- package/public/index.html +1468 -1468
- package/public/logo-512.png +0 -0
- package/setup-entry.js +14 -14
- package/src/channel.js +616 -613
- package/src/ui-routes.js +647 -594
package/index.js
CHANGED
|
@@ -1,375 +1,417 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AICQ Chat Plugin — Channel Plugin Entry Point
|
|
3
|
-
*
|
|
4
|
-
* Architecture: Channel (in-process, no independent port)
|
|
5
|
-
* - Runs inside the OpenClaw process
|
|
6
|
-
* - Uses defineChannelPluginEntry from the official Channel Plugin SDK
|
|
7
|
-
* - Provides Gateway RPC methods for the SPA UI and agent tools
|
|
8
|
-
* - No sidecar process needed
|
|
9
|
-
*
|
|
10
|
-
* ESM module — this file IS the openclaw extension entry.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { defineChannelPluginEntry } from "openclaw/plugin-sdk/channel-core";
|
|
14
|
-
import { aicqChatPlugin, runtime } from "./src/channel.js";
|
|
15
|
-
import { createRequire } from "module";
|
|
16
|
-
import path from "path";
|
|
17
|
-
import os from "os";
|
|
18
|
-
import fs from "fs";
|
|
19
|
-
|
|
20
|
-
// ── CJS interop — lib/ modules are CommonJS ──────────────────────────
|
|
21
|
-
const require = createRequire(import.meta.url);
|
|
22
|
-
|
|
23
|
-
// ── Configuration ────────────────────────────────────────────────────
|
|
24
|
-
const DATA_DIR = process.env.AICQ_DATA_DIR || path.join(os.homedir(), ".aicq-plugin");
|
|
25
|
-
const SERVER_URL = process.env.AICQ_SERVER_URL || "https://aicq.online";
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
let
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
runtime
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
runtime.
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
case "aicq.
|
|
152
|
-
return
|
|
153
|
-
case "aicq.
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
return {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
return
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
return
|
|
238
|
-
}
|
|
239
|
-
case "aicq.
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
if (!kwargs.
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
"aicq.
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
"aicq.
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
//
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
"
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
1
|
+
/**
|
|
2
|
+
* AICQ Chat Plugin — Channel Plugin Entry Point
|
|
3
|
+
*
|
|
4
|
+
* Architecture: Channel (in-process, no independent port)
|
|
5
|
+
* - Runs inside the OpenClaw process
|
|
6
|
+
* - Uses defineChannelPluginEntry from the official Channel Plugin SDK
|
|
7
|
+
* - Provides Gateway RPC methods for the SPA UI and agent tools
|
|
8
|
+
* - No sidecar process needed
|
|
9
|
+
*
|
|
10
|
+
* ESM module — this file IS the openclaw extension entry.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { defineChannelPluginEntry } from "openclaw/plugin-sdk/channel-core";
|
|
14
|
+
import { aicqChatPlugin, runtime } from "./src/channel.js";
|
|
15
|
+
import { createRequire } from "module";
|
|
16
|
+
import path from "path";
|
|
17
|
+
import os from "os";
|
|
18
|
+
import fs from "fs";
|
|
19
|
+
|
|
20
|
+
// ── CJS interop — lib/ modules are CommonJS ──────────────────────────
|
|
21
|
+
const require = createRequire(import.meta.url);
|
|
22
|
+
|
|
23
|
+
// ── Configuration ────────────────────────────────────────────────────
|
|
24
|
+
const DATA_DIR = process.env.AICQ_DATA_DIR || path.join(os.homedir(), ".aicq-plugin");
|
|
25
|
+
const SERVER_URL = process.env.AICQ_SERVER_URL || "https://aicq.online";
|
|
26
|
+
const AUTO_ADD_FRIENDS = process.env.AICQ_AUTO_ADD_FRIENDS
|
|
27
|
+
? process.env.AICQ_AUTO_ADD_FRIENDS.split(",").map(s => s.trim()).filter(Boolean)
|
|
28
|
+
: ["1000000"]; // Default: auto-add user 1000000
|
|
29
|
+
const AUTO_ACCEPT_FRIENDS = process.env.AICQ_AUTO_ACCEPT_FRIENDS !== "false"; // default true
|
|
30
|
+
|
|
31
|
+
fs.mkdirSync(DATA_DIR, { recursive: true });
|
|
32
|
+
|
|
33
|
+
// ── Lazy-loaded CJS modules (need async db init) ────────────────────
|
|
34
|
+
let _db = null;
|
|
35
|
+
let _identity = null;
|
|
36
|
+
let _serverClient = null;
|
|
37
|
+
let _handshake = null;
|
|
38
|
+
let _chat = null;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Initialize all plugin components (async, called once from registerFull).
|
|
42
|
+
*/
|
|
43
|
+
async function ensureInitialized() {
|
|
44
|
+
if (runtime._initialized) return;
|
|
45
|
+
|
|
46
|
+
const PluginDatabase = require("./lib/database");
|
|
47
|
+
const IdentityManager = require("./lib/identity");
|
|
48
|
+
const ServerClient = require("./lib/server-client");
|
|
49
|
+
const HandshakeManager = require("./lib/handshake");
|
|
50
|
+
const ChatManager = require("./lib/chat");
|
|
51
|
+
|
|
52
|
+
// Initialize database
|
|
53
|
+
_db = new PluginDatabase(DATA_DIR);
|
|
54
|
+
await _db.init();
|
|
55
|
+
console.log("[AICQ Channel] Database initialized");
|
|
56
|
+
|
|
57
|
+
// Initialize managers
|
|
58
|
+
_identity = new IdentityManager(_db);
|
|
59
|
+
_serverClient = new ServerClient(_identity, _db, SERVER_URL);
|
|
60
|
+
_handshake = new HandshakeManager(_identity, _serverClient, _db);
|
|
61
|
+
const uploadsDir = path.join(DATA_DIR, "uploads");
|
|
62
|
+
const userfilesDir = path.join(DATA_DIR, "userfiles");
|
|
63
|
+
fs.mkdirSync(uploadsDir, { recursive: true });
|
|
64
|
+
fs.mkdirSync(userfilesDir, { recursive: true });
|
|
65
|
+
|
|
66
|
+
_chat = new ChatManager(_identity, _serverClient, _db, uploadsDir, userfilesDir);
|
|
67
|
+
|
|
68
|
+
// Populate the shared runtime store so channel adapters can use it
|
|
69
|
+
runtime.db = _db;
|
|
70
|
+
runtime.identity = _identity;
|
|
71
|
+
runtime.serverClient = _serverClient;
|
|
72
|
+
runtime.handshake = _handshake;
|
|
73
|
+
runtime.chat = _chat;
|
|
74
|
+
runtime.dataDir = DATA_DIR;
|
|
75
|
+
runtime.userfilesDir = userfilesDir;
|
|
76
|
+
runtime.uploadsDir = uploadsDir;
|
|
77
|
+
runtime.serverUrl = SERVER_URL;
|
|
78
|
+
runtime.handleGateway = handleGatewayMethod;
|
|
79
|
+
runtime.ensureInitialized = ensureInitialized;
|
|
80
|
+
runtime.autoAddFriends = AUTO_ADD_FRIENDS;
|
|
81
|
+
runtime.autoAcceptFriends = AUTO_ACCEPT_FRIENDS;
|
|
82
|
+
|
|
83
|
+
// Periodic cleanup
|
|
84
|
+
setInterval(() => _db.cleanup(), 3600000);
|
|
85
|
+
|
|
86
|
+
runtime._initialized = true;
|
|
87
|
+
console.log("[AICQ Channel] Plugin runtime initialized");
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ── Sync helpers ─────────────────────────────────────────────────────
|
|
91
|
+
async function syncFriendsFromServer(agentId) {
|
|
92
|
+
try {
|
|
93
|
+
await _serverClient.ensureAuth(agentId);
|
|
94
|
+
const result = await _serverClient.listFriends();
|
|
95
|
+
if (result.friends) {
|
|
96
|
+
for (const f of result.friends) {
|
|
97
|
+
const existing = _db.getFriend(agentId, f.id);
|
|
98
|
+
if (!existing) {
|
|
99
|
+
_db.addFriend({
|
|
100
|
+
agent_id: agentId,
|
|
101
|
+
id: f.id,
|
|
102
|
+
public_key: f.public_key || f.publicKey || "",
|
|
103
|
+
fingerprint: f.fingerprint || "",
|
|
104
|
+
friend_type: f.type || f.friend_type || "ai",
|
|
105
|
+
ai_name: f.agent_name || f.ai_name || f.displayName || "",
|
|
106
|
+
});
|
|
107
|
+
} else {
|
|
108
|
+
_db.updateFriendOnline(agentId, f.id, f.is_online || f.isOnline || false);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
} catch (e) {
|
|
113
|
+
console.error("[AICQ Channel] Sync friends failed:", e.message);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function syncGroupsFromServer(agentId) {
|
|
118
|
+
try {
|
|
119
|
+
await _serverClient.ensureAuth(agentId);
|
|
120
|
+
const result = await _serverClient.listGroups();
|
|
121
|
+
if (result.groups) {
|
|
122
|
+
for (const g of result.groups) {
|
|
123
|
+
_db.addGroup({
|
|
124
|
+
agent_id: agentId,
|
|
125
|
+
id: g.id,
|
|
126
|
+
name: g.name,
|
|
127
|
+
owner_id: g.owner_id || g.ownerId || "",
|
|
128
|
+
members_json: g.members || g.members_json || "[]",
|
|
129
|
+
description: g.description || "",
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
} catch (e) {
|
|
134
|
+
console.error("[AICQ Channel] Sync groups failed:", e.message);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ── Gateway method handler ───────────────────────────────────────────
|
|
139
|
+
async function handleGatewayMethod(method, kwargs = {}) {
|
|
140
|
+
const agents = _identity.listAgents();
|
|
141
|
+
const currentAgentId = agents.length > 0 ? agents[0].agent_id : null;
|
|
142
|
+
|
|
143
|
+
switch (method) {
|
|
144
|
+
case "aicq.status":
|
|
145
|
+
return {
|
|
146
|
+
state: _serverClient.connected ? "connected" : "disconnected",
|
|
147
|
+
agent_id: currentAgentId,
|
|
148
|
+
version: "3.6.0",
|
|
149
|
+
architecture: "channel",
|
|
150
|
+
};
|
|
151
|
+
case "aicq.friends.list":
|
|
152
|
+
return { friends: _db.listFriends(currentAgentId) };
|
|
153
|
+
case "aicq.friends.add":
|
|
154
|
+
return await _handshake.addFriendByCode(currentAgentId, kwargs.temp_number);
|
|
155
|
+
case "aicq.friends.addByNumber": {
|
|
156
|
+
// Add friend by AICQ number directly (e.g., "1000000")
|
|
157
|
+
if (!kwargs.number && !kwargs.aicq_number)
|
|
158
|
+
return { error: "number or aicq_number is required" };
|
|
159
|
+
try {
|
|
160
|
+
await _serverClient.ensureAuth(currentAgentId);
|
|
161
|
+
const aicqNumber = kwargs.number || kwargs.aicq_number;
|
|
162
|
+
const result = await _serverClient.sendFriendRequest(aicqNumber, kwargs.message || 'Hi, I\'d like to add you as a friend!');
|
|
163
|
+
// If the request was accepted immediately, also add locally
|
|
164
|
+
if (result.status === 'accepted' && result.to_id) {
|
|
165
|
+
_db.addFriend({
|
|
166
|
+
agent_id: currentAgentId,
|
|
167
|
+
id: result.to_id,
|
|
168
|
+
public_key: '',
|
|
169
|
+
fingerprint: '',
|
|
170
|
+
friend_type: 'human',
|
|
171
|
+
ai_name: kwargs.nickname || '',
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
return { success: true, request_id: result.id, status: result.status, to_id: result.to_id };
|
|
175
|
+
} catch (e) {
|
|
176
|
+
return { error: e.message };
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
case "aicq.friends.remove":
|
|
180
|
+
_db.removeFriend(currentAgentId, kwargs.friend_id);
|
|
181
|
+
return { success: true };
|
|
182
|
+
case "aicq.friends.requests":
|
|
183
|
+
return { requests: _db.getPendingRequests(currentAgentId) };
|
|
184
|
+
case "aicq.friends.acceptRequest":
|
|
185
|
+
return await _handshake.acceptRequest(currentAgentId, kwargs.request_id);
|
|
186
|
+
case "aicq.friends.rejectRequest":
|
|
187
|
+
return await _handshake.rejectRequest(currentAgentId, kwargs.request_id);
|
|
188
|
+
case "aicq.identity.info":
|
|
189
|
+
return _identity.getInfo(currentAgentId) || {};
|
|
190
|
+
case "aicq.agent.create":
|
|
191
|
+
_identity.createAgent(kwargs.agent_id, kwargs.nickname);
|
|
192
|
+
return { success: true };
|
|
193
|
+
case "aicq.agent.delete":
|
|
194
|
+
_identity.deleteAgent(kwargs.agent_id);
|
|
195
|
+
return { success: true };
|
|
196
|
+
case "aicq.chat.send":
|
|
197
|
+
return await _chat.sendMessage(currentAgentId, kwargs.targetId, kwargs.content, {
|
|
198
|
+
isGroup: kwargs.isGroup,
|
|
199
|
+
});
|
|
200
|
+
case "aicq.chat.history":
|
|
201
|
+
return {
|
|
202
|
+
messages: _db.getChatHistory(currentAgentId, kwargs.targetId, {
|
|
203
|
+
limit: kwargs.limit || 50,
|
|
204
|
+
}),
|
|
205
|
+
};
|
|
206
|
+
case "aicq.chat.delete":
|
|
207
|
+
_db.deleteMessage(currentAgentId, kwargs.message_id);
|
|
208
|
+
return { success: true };
|
|
209
|
+
case "aicq.chat.userUpload": {
|
|
210
|
+
// Save a file from a user to the userfiles directory and notify the AI agent
|
|
211
|
+
if (!kwargs.file_data && !kwargs.file_path)
|
|
212
|
+
return { error: "file_data (base64) or file_path is required" };
|
|
213
|
+
if (!kwargs.from_id && !kwargs.targetId)
|
|
214
|
+
return { error: "from_id or targetId is required" };
|
|
215
|
+
const uploadFromId = kwargs.from_id || kwargs.targetId;
|
|
216
|
+
const isGroupUpload = !!kwargs.isGroup;
|
|
217
|
+
let uploadResult;
|
|
218
|
+
if (kwargs.file_data) {
|
|
219
|
+
// Base64 file data
|
|
220
|
+
const fileBuffer = Buffer.from(kwargs.file_data, 'base64');
|
|
221
|
+
uploadResult = await _chat.handleUserFileUpload(currentAgentId, uploadFromId, {
|
|
222
|
+
buffer: fileBuffer,
|
|
223
|
+
originalname: kwargs.file_name || kwargs.fileName || 'file.bin',
|
|
224
|
+
size: fileBuffer.length,
|
|
225
|
+
}, isGroupUpload);
|
|
226
|
+
} else {
|
|
227
|
+
// File path — copy to userfiles
|
|
228
|
+
const srcPath = kwargs.file_path;
|
|
229
|
+
if (!fs.existsSync(srcPath)) return { error: "File not found: " + srcPath };
|
|
230
|
+
const fileBuffer = fs.readFileSync(srcPath);
|
|
231
|
+
uploadResult = await _chat.handleUserFileUpload(currentAgentId, uploadFromId, {
|
|
232
|
+
buffer: fileBuffer,
|
|
233
|
+
originalname: kwargs.file_name || path.basename(srcPath),
|
|
234
|
+
size: fileBuffer.length,
|
|
235
|
+
}, isGroupUpload);
|
|
236
|
+
}
|
|
237
|
+
return { success: true, localPath: uploadResult.localPath, originalName: uploadResult.originalName };
|
|
238
|
+
}
|
|
239
|
+
case "aicq.chat.userfiles": {
|
|
240
|
+
// List user files
|
|
241
|
+
const userfilesDir = runtime.userfilesDir;
|
|
242
|
+
if (!userfilesDir || !fs.existsSync(userfilesDir)) return { files: [] };
|
|
243
|
+
const userFiles = fs.readdirSync(userfilesDir)
|
|
244
|
+
.filter(f => fs.statSync(path.join(userfilesDir, f)).isFile())
|
|
245
|
+
.map(f => {
|
|
246
|
+
const fp = path.join(userfilesDir, f);
|
|
247
|
+
const stat = fs.statSync(fp);
|
|
248
|
+
return { name: f, path: fp, size: stat.size, modified: stat.mtime.toISOString() };
|
|
249
|
+
})
|
|
250
|
+
.sort((a, b) => b.modified.localeCompare(a.modified));
|
|
251
|
+
return { files: userFiles };
|
|
252
|
+
}
|
|
253
|
+
case "aicq.chat.streamChunk": {
|
|
254
|
+
if (!kwargs.friend_id && !kwargs.targetId)
|
|
255
|
+
return { error: "friend_id or targetId is required" };
|
|
256
|
+
if (!kwargs.data) return { error: "data is required" };
|
|
257
|
+
const chunkType = kwargs.chunk_type || kwargs.chunkType || "text";
|
|
258
|
+
const ALLOWED_CHUNK_TYPES = [
|
|
259
|
+
"text",
|
|
260
|
+
"reasoning",
|
|
261
|
+
"thinking",
|
|
262
|
+
"clear_text",
|
|
263
|
+
"tool_call",
|
|
264
|
+
"tool_result",
|
|
265
|
+
];
|
|
266
|
+
if (!ALLOWED_CHUNK_TYPES.includes(chunkType))
|
|
267
|
+
return {
|
|
268
|
+
error: `Invalid chunk_type: ${chunkType}. Allowed: ${ALLOWED_CHUNK_TYPES.join(", ")}`,
|
|
269
|
+
};
|
|
270
|
+
const streamTarget = kwargs.friend_id || kwargs.targetId;
|
|
271
|
+
const sent = _serverClient.sendWS({
|
|
272
|
+
type: "stream_chunk",
|
|
273
|
+
to: streamTarget,
|
|
274
|
+
chunkType,
|
|
275
|
+
data: kwargs.data,
|
|
276
|
+
});
|
|
277
|
+
if (!sent) return { error: "Not connected to server", success: false };
|
|
278
|
+
return { success: true };
|
|
279
|
+
}
|
|
280
|
+
case "aicq.chat.streamEnd": {
|
|
281
|
+
if (!kwargs.friend_id && !kwargs.targetId)
|
|
282
|
+
return { error: "friend_id or targetId is required" };
|
|
283
|
+
const endTarget = kwargs.friend_id || kwargs.targetId;
|
|
284
|
+
const msgId =
|
|
285
|
+
kwargs.message_id ||
|
|
286
|
+
kwargs.messageId ||
|
|
287
|
+
"msg_" + Date.now() + "_" + Math.random().toString(36).substr(2, 6);
|
|
288
|
+
const endSent = _serverClient.sendWS({
|
|
289
|
+
type: "stream_end",
|
|
290
|
+
to: endTarget,
|
|
291
|
+
messageId: msgId,
|
|
292
|
+
});
|
|
293
|
+
if (!endSent) return { error: "Not connected to server", success: false };
|
|
294
|
+
return { success: true, messageId: msgId };
|
|
295
|
+
}
|
|
296
|
+
case "aicq.groups.list":
|
|
297
|
+
return { groups: _db.listGroups(currentAgentId) };
|
|
298
|
+
case "aicq.groups.create": {
|
|
299
|
+
await _serverClient.ensureAuth(currentAgentId);
|
|
300
|
+
const result = await _serverClient.createGroup(kwargs.name, kwargs.description);
|
|
301
|
+
if (result.id) {
|
|
302
|
+
_db.addGroup({
|
|
303
|
+
agent_id: currentAgentId,
|
|
304
|
+
id: result.id,
|
|
305
|
+
name: kwargs.name,
|
|
306
|
+
owner_id: currentAgentId,
|
|
307
|
+
members_json: result.members || "[]",
|
|
308
|
+
description: kwargs.description || "",
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
return { success: true, group: result };
|
|
312
|
+
}
|
|
313
|
+
case "aicq.groups.join":
|
|
314
|
+
await _serverClient.ensureAuth(currentAgentId);
|
|
315
|
+
return await _serverClient.inviteGroupMember(kwargs.group_id, currentAgentId);
|
|
316
|
+
case "aicq.groups.messages": {
|
|
317
|
+
await _serverClient.ensureAuth(currentAgentId);
|
|
318
|
+
return await _serverClient.getGroupMessages(kwargs.group_id, kwargs.limit || 50);
|
|
319
|
+
}
|
|
320
|
+
case "aicq.groups.silent":
|
|
321
|
+
_db.setGroupSilentMode(currentAgentId, kwargs.group_id, !!kwargs.silent);
|
|
322
|
+
return { success: true, silent: !!kwargs.silent };
|
|
323
|
+
case "aicq.sessions.list":
|
|
324
|
+
return { sessions: [] };
|
|
325
|
+
default:
|
|
326
|
+
return { error: `Unknown method: ${method}` };
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// ── CLI metadata registration (lightweight, no runtime init) ─────────
|
|
331
|
+
function registerCliMetadata(api) {
|
|
332
|
+
api.registerCli(
|
|
333
|
+
({ program }) => {
|
|
334
|
+
program
|
|
335
|
+
.command("aicq-chat")
|
|
336
|
+
.description("AICQ Encrypted Chat management");
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
descriptors: [
|
|
340
|
+
{
|
|
341
|
+
name: "aicq-chat",
|
|
342
|
+
description: "AICQ Encrypted Chat management",
|
|
343
|
+
hasSubcommands: false,
|
|
344
|
+
},
|
|
345
|
+
],
|
|
346
|
+
}
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// ── Full runtime registration ────────────────────────────────────────
|
|
351
|
+
async function registerFull(api) {
|
|
352
|
+
// Expose ensureInitialized on the runtime store immediately so that
|
|
353
|
+
// startAccount (called by the channel loader) can trigger init even
|
|
354
|
+
// if no gateway method has been invoked yet.
|
|
355
|
+
runtime.ensureInitialized = ensureInitialized;
|
|
356
|
+
|
|
357
|
+
// Register gateway RPC methods — each wraps handleGatewayMethod
|
|
358
|
+
const GATEWAY_METHODS = [
|
|
359
|
+
"aicq.status",
|
|
360
|
+
"aicq.friends.list",
|
|
361
|
+
"aicq.friends.add",
|
|
362
|
+
"aicq.friends.addByNumber",
|
|
363
|
+
"aicq.friends.remove",
|
|
364
|
+
"aicq.friends.requests",
|
|
365
|
+
"aicq.friends.acceptRequest",
|
|
366
|
+
"aicq.friends.rejectRequest",
|
|
367
|
+
"aicq.identity.info",
|
|
368
|
+
"aicq.agent.create",
|
|
369
|
+
"aicq.agent.delete",
|
|
370
|
+
"aicq.chat.send",
|
|
371
|
+
"aicq.chat.history",
|
|
372
|
+
"aicq.chat.delete",
|
|
373
|
+
"aicq.chat.userUpload",
|
|
374
|
+
"aicq.chat.userfiles",
|
|
375
|
+
"aicq.chat.streamChunk",
|
|
376
|
+
"aicq.chat.streamEnd",
|
|
377
|
+
"aicq.groups.list",
|
|
378
|
+
"aicq.groups.create",
|
|
379
|
+
"aicq.groups.join",
|
|
380
|
+
"aicq.groups.messages",
|
|
381
|
+
"aicq.groups.silent",
|
|
382
|
+
"aicq.sessions.list",
|
|
383
|
+
];
|
|
384
|
+
|
|
385
|
+
for (const method of GATEWAY_METHODS) {
|
|
386
|
+
api.registerGatewayMethod(method, async (opts) => {
|
|
387
|
+
try {
|
|
388
|
+
await ensureInitialized();
|
|
389
|
+
const result = await handleGatewayMethod(method, opts.params || {});
|
|
390
|
+
opts.respond(true, result);
|
|
391
|
+
} catch (e) {
|
|
392
|
+
opts.respond(false, undefined, { message: e.message, code: "AICQ_ERROR" });
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Register HTTP routes for the SPA UI and REST API.
|
|
398
|
+
// Lazy-loaded to keep the entry narrow — the ui-routes module pulls in
|
|
399
|
+
// qrcode and multer which are not needed during setup-only registration.
|
|
400
|
+
try {
|
|
401
|
+
const { registerHttpRoutes } = await import("./src/ui-routes.js");
|
|
402
|
+
registerHttpRoutes(api, { ensureInitialized, runtime, DATA_DIR, SERVER_URL });
|
|
403
|
+
} catch (e) {
|
|
404
|
+
console.error("[AICQ Channel] Failed to register HTTP routes:", e.message);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// ── Export the entry point ───────────────────────────────────────────
|
|
409
|
+
export default defineChannelPluginEntry({
|
|
410
|
+
id: "aicq-chat",
|
|
411
|
+
name: "AICQ Encrypted Chat",
|
|
412
|
+
description:
|
|
413
|
+
"End-to-end encrypted chat channel plugin for OpenClaw agents — NaCl (X25519 + XSalsa20-Poly1305)",
|
|
414
|
+
plugin: aicqChatPlugin,
|
|
415
|
+
registerCliMetadata,
|
|
416
|
+
registerFull,
|
|
417
|
+
});
|