aicq-chat-plugin 2.1.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/README.md +74 -0
- package/bin/aicq-plugin.js +118 -0
- package/bin/postinstall.js +27 -0
- package/index.js +499 -0
- package/lib/chat.js +244 -0
- package/lib/crypto.js +156 -0
- package/lib/database.js +319 -0
- package/lib/file-transfer.js +266 -0
- package/lib/handshake.js +147 -0
- package/lib/identity.js +154 -0
- package/lib/server-client.js +322 -0
- package/openclaw.plugin.json +45 -0
- package/package.json +58 -0
- package/public/index.html +921 -0
package/index.js
ADDED
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AICQ Chat Plugin — Main Entry Point
|
|
3
|
+
* OpenClaw sidecar plugin providing E2EE chat UI
|
|
4
|
+
*/
|
|
5
|
+
const express = require('express');
|
|
6
|
+
const multer = require('multer');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const os = require('os');
|
|
10
|
+
const QRCode = require('qrcode');
|
|
11
|
+
const PluginDatabase = require('./lib/database');
|
|
12
|
+
const IdentityManager = require('./lib/identity');
|
|
13
|
+
const ServerClient = require('./lib/server-client');
|
|
14
|
+
const HandshakeManager = require('./lib/handshake');
|
|
15
|
+
const ChatManager = require('./lib/chat');
|
|
16
|
+
|
|
17
|
+
// ─── Configuration ──────────────────────────────────────────────────
|
|
18
|
+
const PORT = parseInt(process.env.AICQ_PORT || '6109', 10);
|
|
19
|
+
const SERVER_URL = process.env.AICQ_SERVER_URL || 'http://aicq.online:61018';
|
|
20
|
+
const DATA_DIR = process.env.AICQ_DATA_DIR || path.join(os.homedir(), '.aicq-plugin');
|
|
21
|
+
const UPLOADS_DIR = path.join(DATA_DIR, 'uploads');
|
|
22
|
+
|
|
23
|
+
fs.mkdirSync(DATA_DIR, { recursive: true });
|
|
24
|
+
fs.mkdirSync(UPLOADS_DIR, { recursive: true });
|
|
25
|
+
|
|
26
|
+
// ─── Initialize Components ──────────────────────────────────────────
|
|
27
|
+
const db = new PluginDatabase(DATA_DIR);
|
|
28
|
+
const identity = new IdentityManager(db);
|
|
29
|
+
const serverClient = new ServerClient(identity, db, SERVER_URL);
|
|
30
|
+
const handshake = new HandshakeManager(identity, serverClient, db);
|
|
31
|
+
const chat = new ChatManager(identity, serverClient, db, UPLOADS_DIR);
|
|
32
|
+
|
|
33
|
+
// Auto-create a default agent if none exists
|
|
34
|
+
const agents = identity.listAgents();
|
|
35
|
+
let currentAgentId = null;
|
|
36
|
+
if (agents.length === 0) {
|
|
37
|
+
const defaultAgent = identity.createAgent('agent-' + Date.now(), '默认Agent');
|
|
38
|
+
currentAgentId = defaultAgent.agent_id;
|
|
39
|
+
console.log('[AICQ] Created default agent:', currentAgentId);
|
|
40
|
+
} else {
|
|
41
|
+
currentAgentId = agents[0].agent_id;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Connect to server in background
|
|
45
|
+
(async () => {
|
|
46
|
+
try {
|
|
47
|
+
await serverClient.start(currentAgentId);
|
|
48
|
+
// Sync friends from server
|
|
49
|
+
await syncFriendsFromServer(currentAgentId);
|
|
50
|
+
await syncGroupsFromServer(currentAgentId);
|
|
51
|
+
} catch (e) {
|
|
52
|
+
console.error('[AICQ] Initial server connection failed:', e.message);
|
|
53
|
+
}
|
|
54
|
+
})();
|
|
55
|
+
|
|
56
|
+
// Periodic cleanup
|
|
57
|
+
setInterval(() => db.cleanup(), 3600000);
|
|
58
|
+
|
|
59
|
+
// ─── Helper: get current agent ID ──────────────────────────────────
|
|
60
|
+
function getAgentId(req) {
|
|
61
|
+
return req.query.agent_id || req.body?.agent_id || currentAgentId;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ─── Sync friends/groups from server ────────────────────────────────
|
|
65
|
+
async function syncFriendsFromServer(agentId) {
|
|
66
|
+
try {
|
|
67
|
+
await serverClient.ensureAuth(agentId);
|
|
68
|
+
const result = await serverClient.listFriends();
|
|
69
|
+
if (result.friends) {
|
|
70
|
+
for (const f of result.friends) {
|
|
71
|
+
const existing = db.getFriend(agentId, f.id);
|
|
72
|
+
if (!existing) {
|
|
73
|
+
db.addFriend({
|
|
74
|
+
agent_id: agentId,
|
|
75
|
+
id: f.id,
|
|
76
|
+
public_key: f.public_key || f.publicKey || '',
|
|
77
|
+
fingerprint: f.fingerprint || '',
|
|
78
|
+
friend_type: f.type || f.friend_type || 'ai',
|
|
79
|
+
ai_name: f.agent_name || f.ai_name || f.displayName || '',
|
|
80
|
+
});
|
|
81
|
+
} else {
|
|
82
|
+
db.updateFriendOnline(agentId, f.id, f.is_online || f.isOnline || false);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
} catch (e) {
|
|
87
|
+
console.error('[AICQ] Sync friends failed:', e.message);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function syncGroupsFromServer(agentId) {
|
|
92
|
+
try {
|
|
93
|
+
await serverClient.ensureAuth(agentId);
|
|
94
|
+
const result = await serverClient.listGroups();
|
|
95
|
+
if (result.groups) {
|
|
96
|
+
for (const g of result.groups) {
|
|
97
|
+
db.addGroup({
|
|
98
|
+
agent_id: agentId,
|
|
99
|
+
id: g.id,
|
|
100
|
+
name: g.name,
|
|
101
|
+
owner_id: g.owner_id || g.ownerId || '',
|
|
102
|
+
members_json: g.members || g.members_json || '[]',
|
|
103
|
+
description: g.description || '',
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
} catch (e) {
|
|
108
|
+
console.error('[AICQ] Sync groups failed:', e.message);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ─── Express App ────────────────────────────────────────────────────
|
|
113
|
+
const app = express();
|
|
114
|
+
app.use(express.json());
|
|
115
|
+
app.use(express.urlencoded({ extended: true }));
|
|
116
|
+
|
|
117
|
+
const upload = multer({
|
|
118
|
+
storage: multer.memoryStorage(),
|
|
119
|
+
limits: { fileSize: 50 * 1024 * 1024 }, // 50MB
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// ─── Serve SPA ──────────────────────────────────────────────────────
|
|
123
|
+
app.use(express.static(path.join(__dirname, 'public')));
|
|
124
|
+
|
|
125
|
+
// ─── API Routes ─────────────────────────────────────────────────────
|
|
126
|
+
|
|
127
|
+
// Status
|
|
128
|
+
app.get('/api/status', (req, res) => {
|
|
129
|
+
res.json({
|
|
130
|
+
status: 'running',
|
|
131
|
+
version: '2.1.0',
|
|
132
|
+
connected: serverClient.connected,
|
|
133
|
+
currentAgent: currentAgentId,
|
|
134
|
+
serverUrl: SERVER_URL,
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// Agents
|
|
139
|
+
app.get('/api/agents', (req, res) => {
|
|
140
|
+
res.json({ agents: identity.listAgents() });
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
app.post('/api/agents', async (req, res) => {
|
|
144
|
+
try {
|
|
145
|
+
const { agent_id, nickname } = req.body;
|
|
146
|
+
if (!agent_id) return res.status(400).json({ error: 'agent_id is required' });
|
|
147
|
+
const agent = identity.createAgent(agent_id, nickname);
|
|
148
|
+
currentAgentId = agent_id;
|
|
149
|
+
// Register on server
|
|
150
|
+
try {
|
|
151
|
+
await serverClient.start(agent_id);
|
|
152
|
+
} catch (e) {
|
|
153
|
+
console.error('Server registration failed:', e.message);
|
|
154
|
+
}
|
|
155
|
+
res.json({ success: true, agent });
|
|
156
|
+
} catch (e) {
|
|
157
|
+
res.status(500).json({ error: e.message });
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
app.delete('/api/agents/:id', (req, res) => {
|
|
162
|
+
identity.deleteAgent(req.params.id);
|
|
163
|
+
if (currentAgentId === req.params.id) {
|
|
164
|
+
const remaining = identity.listAgents();
|
|
165
|
+
currentAgentId = remaining.length > 0 ? remaining[0].agent_id : null;
|
|
166
|
+
}
|
|
167
|
+
res.json({ success: true });
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
app.post('/api/agents/switch', async (req, res) => {
|
|
171
|
+
try {
|
|
172
|
+
const { agent_id } = req.body;
|
|
173
|
+
if (!agent_id) return res.status(400).json({ error: 'agent_id is required' });
|
|
174
|
+
currentAgentId = agent_id;
|
|
175
|
+
await serverClient.switchAgent(agent_id);
|
|
176
|
+
await syncFriendsFromServer(agent_id);
|
|
177
|
+
await syncGroupsFromServer(agent_id);
|
|
178
|
+
res.json({ success: true, agent_id });
|
|
179
|
+
} catch (e) {
|
|
180
|
+
res.status(500).json({ error: e.message });
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// Friends
|
|
185
|
+
app.get('/api/friends', (req, res) => {
|
|
186
|
+
const agentId = getAgentId(req);
|
|
187
|
+
res.json({ friends: db.listFriends(agentId) });
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
app.post('/api/friends/add', async (req, res) => {
|
|
191
|
+
try {
|
|
192
|
+
const { temp_number, friend_code, agent_id } = req.body;
|
|
193
|
+
const agentId = agent_id || currentAgentId;
|
|
194
|
+
const code = temp_number || friend_code;
|
|
195
|
+
if (!code) return res.status(400).json({ error: 'temp_number or friend_code is required' });
|
|
196
|
+
const result = await handshake.addFriendByCode(agentId, code);
|
|
197
|
+
res.json({ success: true, result });
|
|
198
|
+
} catch (e) {
|
|
199
|
+
res.status(500).json({ error: e.message });
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
app.delete('/api/friends/:id', async (req, res) => {
|
|
204
|
+
try {
|
|
205
|
+
const agentId = getAgentId(req);
|
|
206
|
+
db.removeFriend(agentId, req.params.id);
|
|
207
|
+
try { await serverClient.removeFriend(req.params.id); } catch (e) {}
|
|
208
|
+
res.json({ success: true });
|
|
209
|
+
} catch (e) {
|
|
210
|
+
res.status(500).json({ error: e.message });
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
app.get('/api/friends/requests', async (req, res) => {
|
|
215
|
+
try {
|
|
216
|
+
const agentId = getAgentId(req);
|
|
217
|
+
// Get from server
|
|
218
|
+
let serverRequests = [];
|
|
219
|
+
try {
|
|
220
|
+
await serverClient.ensureAuth(agentId);
|
|
221
|
+
const result = await serverClient.listFriendRequests();
|
|
222
|
+
serverRequests = result.sent || [];
|
|
223
|
+
serverRequests = serverRequests.concat(result.received || []);
|
|
224
|
+
} catch (e) {}
|
|
225
|
+
const localRequests = db.getPendingRequests(agentId);
|
|
226
|
+
res.json({ requests: [...localRequests, ...serverRequests] });
|
|
227
|
+
} catch (e) {
|
|
228
|
+
res.status(500).json({ error: e.message });
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
app.post('/api/friends/requests/:id/accept', async (req, res) => {
|
|
233
|
+
try {
|
|
234
|
+
const agentId = getAgentId(req);
|
|
235
|
+
const result = await handshake.acceptRequest(agentId, req.params.id);
|
|
236
|
+
res.json(result);
|
|
237
|
+
} catch (e) {
|
|
238
|
+
res.status(500).json({ error: e.message });
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
app.post('/api/friends/requests/:id/reject', async (req, res) => {
|
|
243
|
+
try {
|
|
244
|
+
const agentId = getAgentId(req);
|
|
245
|
+
const result = await handshake.rejectRequest(agentId, req.params.id);
|
|
246
|
+
res.json(result);
|
|
247
|
+
} catch (e) {
|
|
248
|
+
res.status(500).json({ error: e.message });
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
// Groups
|
|
253
|
+
app.get('/api/groups', (req, res) => {
|
|
254
|
+
const agentId = getAgentId(req);
|
|
255
|
+
res.json({ groups: db.listGroups(agentId) });
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
app.post('/api/groups', async (req, res) => {
|
|
259
|
+
try {
|
|
260
|
+
const agentId = getAgentId(req);
|
|
261
|
+
const { name, description } = req.body;
|
|
262
|
+
if (!name) return res.status(400).json({ error: 'name is required' });
|
|
263
|
+
await serverClient.ensureAuth(agentId);
|
|
264
|
+
const result = await serverClient.createGroup(name, description);
|
|
265
|
+
if (result.id) {
|
|
266
|
+
db.addGroup({
|
|
267
|
+
agent_id: agentId,
|
|
268
|
+
id: result.id,
|
|
269
|
+
name,
|
|
270
|
+
owner_id: agentId,
|
|
271
|
+
members_json: result.members || '[]',
|
|
272
|
+
description: description || '',
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
res.json({ success: true, group: result });
|
|
276
|
+
} catch (e) {
|
|
277
|
+
res.status(500).json({ error: e.message });
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
app.post('/api/groups/:id/join', async (req, res) => {
|
|
282
|
+
try {
|
|
283
|
+
const agentId = getAgentId(req);
|
|
284
|
+
await serverClient.ensureAuth(agentId);
|
|
285
|
+
const result = await serverClient.inviteGroupMember(req.params.id, agentId);
|
|
286
|
+
res.json({ success: true, result });
|
|
287
|
+
} catch (e) {
|
|
288
|
+
res.status(500).json({ error: e.message });
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
app.get('/api/groups/:id/messages', async (req, res) => {
|
|
293
|
+
try {
|
|
294
|
+
const agentId = getAgentId(req);
|
|
295
|
+
const limit = parseInt(req.query.limit || '50', 10);
|
|
296
|
+
const before = req.query.before || null;
|
|
297
|
+
// Try server first
|
|
298
|
+
try {
|
|
299
|
+
await serverClient.ensureAuth(agentId);
|
|
300
|
+
const result = await serverClient.getGroupMessages(req.params.id, limit, before);
|
|
301
|
+
if (result.messages && result.messages.length > 0) {
|
|
302
|
+
return res.json({ messages: result.messages });
|
|
303
|
+
}
|
|
304
|
+
} catch (e) {}
|
|
305
|
+
// Fallback to local
|
|
306
|
+
const messages = db.getChatHistory(agentId, req.params.id, { limit, before });
|
|
307
|
+
res.json({ messages });
|
|
308
|
+
} catch (e) {
|
|
309
|
+
res.status(500).json({ error: e.message });
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
app.put('/api/groups/:id/silent', (req, res) => {
|
|
314
|
+
const agentId = getAgentId(req);
|
|
315
|
+
const { silent } = req.body;
|
|
316
|
+
db.setGroupSilentMode(agentId, req.params.id, !!silent);
|
|
317
|
+
res.json({ success: true, silent: !!silent });
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
// Chat
|
|
321
|
+
app.get('/api/chat/:targetId', (req, res) => {
|
|
322
|
+
const agentId = getAgentId(req);
|
|
323
|
+
const limit = parseInt(req.query.limit || '50', 10);
|
|
324
|
+
const before = req.query.before || null;
|
|
325
|
+
const messages = db.getChatHistory(agentId, req.params.targetId, { limit, before });
|
|
326
|
+
res.json({ messages });
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
app.post('/api/chat/send', async (req, res) => {
|
|
330
|
+
try {
|
|
331
|
+
const { agent_id, targetId, content, type, isGroup, mentions, file_url, file_name } = req.body;
|
|
332
|
+
const agentId = agent_id || currentAgentId;
|
|
333
|
+
if (!targetId || !content) return res.status(400).json({ error: 'targetId and content are required' });
|
|
334
|
+
const msg = await chat.sendMessage(agentId, targetId, content, {
|
|
335
|
+
type: type || 'text',
|
|
336
|
+
isGroup: !!isGroup,
|
|
337
|
+
mentions: mentions || [],
|
|
338
|
+
file_url,
|
|
339
|
+
file_name,
|
|
340
|
+
});
|
|
341
|
+
res.json({ success: true, message: msg });
|
|
342
|
+
} catch (e) {
|
|
343
|
+
res.status(500).json({ error: e.message });
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
app.delete('/api/chat/:messageId', (req, res) => {
|
|
348
|
+
const agentId = getAgentId(req);
|
|
349
|
+
db.deleteMessage(agentId, req.params.messageId);
|
|
350
|
+
res.json({ success: true });
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
// File upload
|
|
354
|
+
app.post('/api/upload', upload.single('file'), async (req, res) => {
|
|
355
|
+
try {
|
|
356
|
+
if (!req.file) return res.status(400).json({ error: 'No file uploaded' });
|
|
357
|
+
const agentId = getAgentId(req);
|
|
358
|
+
const targetId = req.body.targetId;
|
|
359
|
+
const isGroup = req.body.isGroup === 'true' || req.body.isGroup === '1';
|
|
360
|
+
const msg = await chat.handleFileUpload(agentId, targetId, req.file, isGroup);
|
|
361
|
+
res.json({ success: true, message: msg });
|
|
362
|
+
} catch (e) {
|
|
363
|
+
res.status(500).json({ error: e.message });
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
app.get('/api/files/:fileId', (req, res) => {
|
|
368
|
+
const filePath = path.join(UPLOADS_DIR, req.params.fileId);
|
|
369
|
+
if (fs.existsSync(filePath)) {
|
|
370
|
+
res.sendFile(filePath);
|
|
371
|
+
} else {
|
|
372
|
+
res.status(404).json({ error: 'File not found' });
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
// Identity
|
|
377
|
+
app.get('/api/identity', (req, res) => {
|
|
378
|
+
const agentId = getAgentId(req);
|
|
379
|
+
res.json(identity.getInfo(agentId) || {});
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
app.post('/api/identity/nickname', (req, res) => {
|
|
383
|
+
const { agent_id, nickname } = req.body;
|
|
384
|
+
const agentId = agent_id || currentAgentId;
|
|
385
|
+
identity.updateNickname(agentId, nickname);
|
|
386
|
+
res.json({ success: true });
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
app.post('/api/identity/friend-code', async (req, res) => {
|
|
390
|
+
try {
|
|
391
|
+
const agentId = req.body.agent_id || currentAgentId;
|
|
392
|
+
await serverClient.ensureAuth(agentId);
|
|
393
|
+
const result = await handshake.generateFriendCode(agentId);
|
|
394
|
+
res.json({ success: true, code: result.number, expires_at: result.expiresAt || result.expires_at });
|
|
395
|
+
} catch (e) {
|
|
396
|
+
res.status(500).json({ error: e.message });
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
app.get('/api/identity/qr', async (req, res) => {
|
|
401
|
+
try {
|
|
402
|
+
const agentId = getAgentId(req);
|
|
403
|
+
const info = identity.getInfo(agentId);
|
|
404
|
+
if (!info) return res.status(404).json({ error: 'Agent not found' });
|
|
405
|
+
const qrData = JSON.stringify({
|
|
406
|
+
type: 'aicq-friend',
|
|
407
|
+
agent_id: info.agent_id,
|
|
408
|
+
public_key: info.signing_public_key,
|
|
409
|
+
exchange_public_key: info.exchange_public_key,
|
|
410
|
+
fingerprint: info.fingerprint,
|
|
411
|
+
});
|
|
412
|
+
const qrImage = await QRCode.toDataURL(qrData);
|
|
413
|
+
res.json({ qr: qrImage, data: qrData, info });
|
|
414
|
+
} catch (e) {
|
|
415
|
+
res.status(500).json({ error: e.message });
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
app.post('/api/identity/rotate-keys', (req, res) => {
|
|
420
|
+
try {
|
|
421
|
+
const agentId = req.body.agent_id || currentAgentId;
|
|
422
|
+
const newInfo = identity.rotateKeys(agentId);
|
|
423
|
+
res.json({ success: true, info: identity.getInfo(agentId) });
|
|
424
|
+
} catch (e) {
|
|
425
|
+
res.status(500).json({ error: e.message });
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
app.get('/api/identity/keys', (req, res) => {
|
|
430
|
+
const agentId = getAgentId(req);
|
|
431
|
+
const info = identity.loadAgent(agentId);
|
|
432
|
+
if (!info) return res.status(404).json({ error: 'Agent not found' });
|
|
433
|
+
res.json({
|
|
434
|
+
agent_id: info.agent_id,
|
|
435
|
+
nickname: info.nickname,
|
|
436
|
+
signing_public_key: info.signing_public_key,
|
|
437
|
+
exchange_public_key: info.exchange_public_key,
|
|
438
|
+
signing_secret_key: info.signing_secret_key,
|
|
439
|
+
exchange_secret_key: info.exchange_secret_key,
|
|
440
|
+
fingerprint: info.fingerprint,
|
|
441
|
+
});
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
// Sync endpoint
|
|
445
|
+
app.post('/api/sync', async (req, res) => {
|
|
446
|
+
try {
|
|
447
|
+
const agentId = req.body.agent_id || currentAgentId;
|
|
448
|
+
await syncFriendsFromServer(agentId);
|
|
449
|
+
await syncGroupsFromServer(agentId);
|
|
450
|
+
res.json({ success: true });
|
|
451
|
+
} catch (e) {
|
|
452
|
+
res.status(500).json({ error: e.message });
|
|
453
|
+
}
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
// ─── Start Server ───────────────────────────────────────────────────
|
|
457
|
+
app.listen(PORT, '0.0.0.0', () => {
|
|
458
|
+
console.log(`[AICQ Plugin] Running on http://0.0.0.0:${PORT}`);
|
|
459
|
+
console.log(`[AICQ Plugin] Server: ${SERVER_URL}`);
|
|
460
|
+
console.log(`[AICQ Plugin] Data dir: ${DATA_DIR}`);
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
// ─── OpenClaw Gateway Integration ───────────────────────────────────
|
|
464
|
+
process.on('message', (msg) => {
|
|
465
|
+
if (msg.type === 'gateway_call') {
|
|
466
|
+
handleGatewayCall(msg.method, msg.kwargs).then(result => {
|
|
467
|
+
process.send({ type: 'gateway_response', id: msg.id, result });
|
|
468
|
+
}).catch(err => {
|
|
469
|
+
process.send({ type: 'gateway_response', id: msg.id, error: err.message });
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
async function handleGatewayCall(method, kwargs = {}) {
|
|
475
|
+
switch (method) {
|
|
476
|
+
case 'aicq.status':
|
|
477
|
+
return { state: serverClient.connected ? 'connected' : 'disconnected', agent_id: currentAgentId, version: '2.1.0' };
|
|
478
|
+
case 'aicq.friends.list':
|
|
479
|
+
return { friends: db.listFriends(currentAgentId) };
|
|
480
|
+
case 'aicq.friends.add':
|
|
481
|
+
return await handshake.addFriendByCode(currentAgentId, kwargs.temp_number);
|
|
482
|
+
case 'aicq.friends.remove':
|
|
483
|
+
db.removeFriend(currentAgentId, kwargs.friend_id);
|
|
484
|
+
return { success: true };
|
|
485
|
+
case 'aicq.friends.requests':
|
|
486
|
+
return { requests: db.getPendingRequests(currentAgentId) };
|
|
487
|
+
case 'aicq.identity.info':
|
|
488
|
+
return identity.getInfo(currentAgentId) || {};
|
|
489
|
+
case 'aicq.agent.create':
|
|
490
|
+
identity.createAgent(kwargs.agent_id, kwargs.nickname);
|
|
491
|
+
return { success: true };
|
|
492
|
+
case 'aicq.chat.send':
|
|
493
|
+
return await chat.sendMessage(currentAgentId, kwargs.targetId, kwargs.content, { isGroup: kwargs.isGroup });
|
|
494
|
+
case 'aicq.chat.history':
|
|
495
|
+
return { messages: db.getChatHistory(currentAgentId, kwargs.targetId, { limit: kwargs.limit || 50 }) };
|
|
496
|
+
default:
|
|
497
|
+
return { error: `Unknown method: ${method}` };
|
|
498
|
+
}
|
|
499
|
+
}
|