aicq-chat-plugin 2.5.9 → 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/index.js CHANGED
@@ -1,6 +1,9 @@
1
1
  /**
2
2
  * AICQ Chat Plugin — Main Entry Point
3
3
  * OpenClaw sidecar plugin providing E2EE chat UI
4
+ *
5
+ * Uses sql.js (pure WASM SQLite) instead of better-sqlite3
6
+ * to avoid native C++ compilation issues.
4
7
  */
5
8
  const express = require('express');
6
9
  const multer = require('multer');
@@ -9,10 +12,6 @@ const fs = require('fs');
9
12
  const os = require('os');
10
13
  const QRCode = require('qrcode');
11
14
  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
15
 
17
16
  // ─── Configuration ──────────────────────────────────────────────────
18
17
  const PORT = parseInt(process.env.AICQ_PORT || '6109', 10);
@@ -23,640 +22,646 @@ const UPLOADS_DIR = path.join(DATA_DIR, 'uploads');
23
22
  fs.mkdirSync(DATA_DIR, { recursive: true });
24
23
  fs.mkdirSync(UPLOADS_DIR, { recursive: true });
25
24
 
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
25
+ // ─── Async bootstrap ────────────────────────────────────────────────
26
+ // sql.js requires async init, so we wrap the entire app setup
45
27
  (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);
28
+ // Initialize database (async — loads WASM + opens/creates DB file)
29
+ const db = new PluginDatabase(DATA_DIR);
30
+ await db.init();
31
+ console.log('[AICQ] Database initialized');
32
+
33
+ // Lazy-load modules that depend on db
34
+ const IdentityManager = require('./lib/identity');
35
+ const ServerClient = require('./lib/server-client');
36
+ const HandshakeManager = require('./lib/handshake');
37
+ const ChatManager = require('./lib/chat');
38
+
39
+ const identity = new IdentityManager(db);
40
+ const serverClient = new ServerClient(identity, db, SERVER_URL);
41
+ const handshake = new HandshakeManager(identity, serverClient, db);
42
+ const chat = new ChatManager(identity, serverClient, db, UPLOADS_DIR);
43
+
44
+ // Auto-create a default agent if none exists
45
+ const agents = identity.listAgents();
46
+ let currentAgentId = null;
47
+ if (agents.length === 0) {
48
+ const defaultAgent = identity.createAgent('agent-' + Date.now(), '默认Agent');
49
+ currentAgentId = defaultAgent.agent_id;
50
+ console.log('[AICQ] Created default agent:', currentAgentId);
51
+ } else {
52
+ currentAgentId = agents[0].agent_id;
53
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);
54
+
55
+ // Connect to server in background
56
+ (async () => {
57
+ try {
58
+ await serverClient.start(currentAgentId);
59
+ await syncFriendsFromServer(currentAgentId);
60
+ await syncGroupsFromServer(currentAgentId);
61
+ } catch (e) {
62
+ console.error('[AICQ] Initial server connection failed:', e.message);
63
+ }
64
+ })();
65
+
66
+ // Periodic cleanup + save
67
+ setInterval(() => db.cleanup(), 3600000);
68
+
69
+ // ─── Helper: get current agent ID ──────────────────────────────────
70
+ function getAgentId(req) {
71
+ return req.query.agent_id || req.body?.agent_id || currentAgentId;
72
+ }
73
+
74
+ // ─── Sync friends/groups from server ────────────────────────────────
75
+ async function syncFriendsFromServer(agentId) {
76
+ try {
77
+ await serverClient.ensureAuth(agentId);
78
+ const result = await serverClient.listFriends();
79
+ if (result.friends) {
80
+ for (const f of result.friends) {
81
+ const existing = db.getFriend(agentId, f.id);
82
+ if (!existing) {
83
+ db.addFriend({
84
+ agent_id: agentId,
85
+ id: f.id,
86
+ public_key: f.public_key || f.publicKey || '',
87
+ fingerprint: f.fingerprint || '',
88
+ friend_type: f.type || f.friend_type || 'ai',
89
+ ai_name: f.agent_name || f.ai_name || f.displayName || '',
90
+ });
91
+ } else {
92
+ db.updateFriendOnline(agentId, f.id, f.is_online || f.isOnline || false);
93
+ }
83
94
  }
84
95
  }
96
+ } catch (e) {
97
+ console.error('[AICQ] Sync friends failed:', e.message);
85
98
  }
86
- } catch (e) {
87
- console.error('[AICQ] Sync friends failed:', e.message);
88
99
  }
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
- });
100
+
101
+ async function syncGroupsFromServer(agentId) {
102
+ try {
103
+ await serverClient.ensureAuth(agentId);
104
+ const result = await serverClient.listGroups();
105
+ if (result.groups) {
106
+ for (const g of result.groups) {
107
+ db.addGroup({
108
+ agent_id: agentId,
109
+ id: g.id,
110
+ name: g.name,
111
+ owner_id: g.owner_id || g.ownerId || '',
112
+ members_json: g.members || g.members_json || '[]',
113
+ description: g.description || '',
114
+ });
115
+ }
105
116
  }
117
+ } catch (e) {
118
+ console.error('[AICQ] Sync groups failed:', e.message);
106
119
  }
107
- } catch (e) {
108
- console.error('[AICQ] Sync groups failed:', e.message);
109
120
  }
110
- }
111
121
 
112
- // ─── Express App ────────────────────────────────────────────────────
113
- const app = express();
114
- app.use(express.json());
115
- app.use(express.urlencoded({ extended: true }));
122
+ // ─── Express App ────────────────────────────────────────────────────
123
+ const app = express();
124
+ app.use(express.json());
125
+ app.use(express.urlencoded({ extended: true }));
116
126
 
117
- const upload = multer({
118
- storage: multer.memoryStorage(),
119
- limits: { fileSize: 50 * 1024 * 1024 }, // 50MB
120
- });
127
+ const upload = multer({
128
+ storage: multer.memoryStorage(),
129
+ limits: { fileSize: 50 * 1024 * 1024 }, // 50MB
130
+ });
121
131
 
122
- // ─── Serve SPA ──────────────────────────────────────────────────────
123
- app.use(express.static(path.join(__dirname, 'public')));
132
+ // ─── Serve SPA ──────────────────────────────────────────────────────
133
+ app.use(express.static(path.join(__dirname, 'public')));
124
134
 
125
- // ─── API Routes ─────────────────────────────────────────────────────
135
+ // ─── API Routes ─────────────────────────────────────────────────────
126
136
 
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,
137
+ // Status
138
+ app.get('/api/status', (req, res) => {
139
+ res.json({
140
+ status: 'running',
141
+ version: '2.6.0',
142
+ connected: serverClient.connected,
143
+ currentAgent: currentAgentId,
144
+ serverUrl: SERVER_URL,
145
+ });
135
146
  });
136
- });
137
147
 
138
- // Agents
139
- app.get('/api/agents', (req, res) => {
140
- res.json({ agents: identity.listAgents() });
141
- });
148
+ // Agents
149
+ app.get('/api/agents', (req, res) => {
150
+ res.json({ agents: identity.listAgents() });
151
+ });
142
152
 
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
153
+ app.post('/api/agents', async (req, res) => {
150
154
  try {
151
- await serverClient.start(agent_id);
155
+ const { agent_id, nickname } = req.body;
156
+ if (!agent_id) return res.status(400).json({ error: 'agent_id is required' });
157
+ const agent = identity.createAgent(agent_id, nickname);
158
+ currentAgentId = agent_id;
159
+ try {
160
+ await serverClient.start(agent_id);
161
+ } catch (e) {
162
+ console.error('Server registration failed:', e.message);
163
+ }
164
+ res.json({ success: true, agent });
152
165
  } catch (e) {
153
- console.error('Server registration failed:', e.message);
166
+ res.status(500).json({ error: e.message });
154
167
  }
155
- res.json({ success: true, agent });
156
- } catch (e) {
157
- res.status(500).json({ error: e.message });
158
- }
159
- });
168
+ });
160
169
 
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
- });
170
+ app.delete('/api/agents/:id', (req, res) => {
171
+ identity.deleteAgent(req.params.id);
172
+ if (currentAgentId === req.params.id) {
173
+ const remaining = identity.listAgents();
174
+ currentAgentId = remaining.length > 0 ? remaining[0].agent_id : null;
175
+ }
176
+ res.json({ success: true });
177
+ });
169
178
 
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
- });
179
+ app.post('/api/agents/switch', async (req, res) => {
180
+ try {
181
+ const { agent_id } = req.body;
182
+ if (!agent_id) return res.status(400).json({ error: 'agent_id is required' });
183
+ currentAgentId = agent_id;
184
+ await serverClient.switchAgent(agent_id);
185
+ await syncFriendsFromServer(agent_id);
186
+ await syncGroupsFromServer(agent_id);
187
+ res.json({ success: true, agent_id });
188
+ } catch (e) {
189
+ res.status(500).json({ error: e.message });
190
+ }
191
+ });
183
192
 
184
- // Friends
185
- app.get('/api/friends', (req, res) => {
186
- const agentId = getAgentId(req);
187
- res.json({ friends: db.listFriends(agentId) });
188
- });
193
+ // Friends
194
+ app.get('/api/friends', (req, res) => {
195
+ const agentId = getAgentId(req);
196
+ res.json({ friends: db.listFriends(agentId) });
197
+ });
189
198
 
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
- });
199
+ app.post('/api/friends/add', async (req, res) => {
200
+ try {
201
+ const { temp_number, friend_code, agent_id } = req.body;
202
+ const agentId = agent_id || currentAgentId;
203
+ const code = temp_number || friend_code;
204
+ if (!code) return res.status(400).json({ error: 'temp_number or friend_code is required' });
205
+ const result = await handshake.addFriendByCode(agentId, code);
206
+ res.json({ success: true, result });
207
+ } catch (e) {
208
+ res.status(500).json({ error: e.message });
209
+ }
210
+ });
202
211
 
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
- });
212
+ app.delete('/api/friends/:id', async (req, res) => {
213
+ try {
214
+ const agentId = getAgentId(req);
215
+ db.removeFriend(agentId, req.params.id);
216
+ try { await serverClient.removeFriend(req.params.id); } catch (e) {}
217
+ res.json({ success: true });
218
+ } catch (e) {
219
+ res.status(500).json({ error: e.message });
220
+ }
221
+ });
213
222
 
214
- app.get('/api/friends/requests', async (req, res) => {
215
- try {
216
- const agentId = getAgentId(req);
217
- // Get from server
218
- let serverRequests = [];
223
+ app.get('/api/friends/requests', async (req, res) => {
219
224
  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
- });
225
+ const agentId = getAgentId(req);
226
+ let serverRequests = [];
227
+ try {
228
+ await serverClient.ensureAuth(agentId);
229
+ const result = await serverClient.listFriendRequests();
230
+ serverRequests = result.sent || [];
231
+ serverRequests = serverRequests.concat(result.received || []);
232
+ } catch (e) {}
233
+ const localRequests = db.getPendingRequests(agentId);
234
+ res.json({ requests: [...localRequests, ...serverRequests] });
235
+ } catch (e) {
236
+ res.status(500).json({ error: e.message });
237
+ }
238
+ });
231
239
 
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
- });
240
+ app.post('/api/friends/requests/:id/accept', async (req, res) => {
241
+ try {
242
+ const agentId = getAgentId(req);
243
+ const result = await handshake.acceptRequest(agentId, req.params.id);
244
+ res.json(result);
245
+ } catch (e) {
246
+ res.status(500).json({ error: e.message });
247
+ }
248
+ });
249
+
250
+ app.post('/api/friends/requests/:id/reject', async (req, res) => {
251
+ try {
252
+ const agentId = getAgentId(req);
253
+ const result = await handshake.rejectRequest(agentId, req.params.id);
254
+ res.json(result);
255
+ } catch (e) {
256
+ res.status(500).json({ error: e.message });
257
+ }
258
+ });
241
259
 
242
- app.post('/api/friends/requests/:id/reject', async (req, res) => {
243
- try {
260
+ // Groups
261
+ app.get('/api/groups', (req, res) => {
244
262
  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
- });
263
+ res.json({ groups: db.listGroups(agentId) });
264
+ });
251
265
 
252
- // Groups
253
- app.get('/api/groups', (req, res) => {
254
- const agentId = getAgentId(req);
255
- res.json({ groups: db.listGroups(agentId) });
256
- });
266
+ app.post('/api/groups', async (req, res) => {
267
+ try {
268
+ const agentId = getAgentId(req);
269
+ const { name, description } = req.body;
270
+ if (!name) return res.status(400).json({ error: 'name is required' });
271
+ await serverClient.ensureAuth(agentId);
272
+ const result = await serverClient.createGroup(name, description);
273
+ if (result.id) {
274
+ db.addGroup({
275
+ agent_id: agentId,
276
+ id: result.id,
277
+ name,
278
+ owner_id: agentId,
279
+ members_json: result.members || '[]',
280
+ description: description || '',
281
+ });
282
+ }
283
+ res.json({ success: true, group: result });
284
+ } catch (e) {
285
+ res.status(500).json({ error: e.message });
286
+ }
287
+ });
257
288
 
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
- });
289
+ app.post('/api/groups/:id/join', async (req, res) => {
290
+ try {
291
+ const agentId = getAgentId(req);
292
+ await serverClient.ensureAuth(agentId);
293
+ const result = await serverClient.inviteGroupMember(req.params.id, agentId);
294
+ res.json({ success: true, result });
295
+ } catch (e) {
296
+ res.status(500).json({ error: e.message });
274
297
  }
275
- res.json({ success: true, group: result });
276
- } catch (e) {
277
- res.status(500).json({ error: e.message });
278
- }
279
- });
298
+ });
280
299
 
281
- app.post('/api/groups/:id/join', async (req, res) => {
282
- try {
300
+ app.get('/api/groups/:id/messages', async (req, res) => {
301
+ try {
302
+ const agentId = getAgentId(req);
303
+ const limit = parseInt(req.query.limit || '50', 10);
304
+ const before = req.query.before || null;
305
+ try {
306
+ await serverClient.ensureAuth(agentId);
307
+ const result = await serverClient.getGroupMessages(req.params.id, limit, before);
308
+ if (result.messages && result.messages.length > 0) {
309
+ return res.json({ messages: result.messages });
310
+ }
311
+ } catch (e) {}
312
+ const messages = db.getChatHistory(agentId, req.params.id, { limit, before });
313
+ res.json({ messages });
314
+ } catch (e) {
315
+ res.status(500).json({ error: e.message });
316
+ }
317
+ });
318
+
319
+ app.put('/api/groups/:id/silent', (req, res) => {
283
320
  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
- });
321
+ const { silent } = req.body;
322
+ db.setGroupSilentMode(agentId, req.params.id, !!silent);
323
+ res.json({ success: true, silent: !!silent });
324
+ });
291
325
 
292
- app.get('/api/groups/:id/messages', async (req, res) => {
293
- try {
326
+ // Chat
327
+ app.get('/api/chat/:targetId', (req, res) => {
294
328
  const agentId = getAgentId(req);
295
329
  const limit = parseInt(req.query.limit || '50', 10);
296
330
  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 });
331
+ const messages = db.getChatHistory(agentId, req.params.targetId, { limit, before });
307
332
  res.json({ messages });
308
- } catch (e) {
309
- res.status(500).json({ error: e.message });
310
- }
311
- });
333
+ });
312
334
 
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
- });
335
+ app.post('/api/chat/send', async (req, res) => {
336
+ try {
337
+ const { agent_id, targetId, content, type, isGroup, mentions, file_url, file_name } = req.body;
338
+ const agentId = agent_id || currentAgentId;
339
+ if (!targetId || !content) return res.status(400).json({ error: 'targetId and content are required' });
340
+ const msg = await chat.sendMessage(agentId, targetId, content, {
341
+ type: type || 'text',
342
+ isGroup: !!isGroup,
343
+ mentions: mentions || [],
344
+ file_url,
345
+ file_name,
346
+ });
347
+ res.json({ success: true, message: msg });
348
+ } catch (e) {
349
+ res.status(500).json({ error: e.message });
350
+ }
351
+ });
319
352
 
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
- });
353
+ app.delete('/api/chat/:messageId', (req, res) => {
354
+ const agentId = getAgentId(req);
355
+ db.deleteMessage(agentId, req.params.messageId);
356
+ res.json({ success: true });
357
+ });
328
358
 
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
- });
359
+ // Streaming endpoints
360
+ app.post('/api/chat/stream-chunk', (req, res) => {
361
+ try {
362
+ const { targetId, friend_id, chunk_type, chunkType, data } = req.body;
363
+ const streamTarget = targetId || friend_id;
364
+ if (!streamTarget) return res.status(400).json({ error: 'targetId or friend_id is required' });
365
+ if (!data) return res.status(400).json({ error: 'data is required' });
366
+ const type = chunk_type || chunkType || 'text';
367
+ if (!['text', 'reasoning', 'tool_call', 'tool_result'].includes(type)) {
368
+ return res.status(400).json({ error: `Invalid chunk_type: ${type}` });
369
+ }
370
+ const sent = serverClient.sendWS({
371
+ type: 'stream_chunk',
372
+ to: streamTarget,
373
+ chunkType: type,
374
+ data: data,
375
+ });
376
+ if (!sent) return res.status(503).json({ error: 'Not connected to server', success: false });
377
+ res.json({ success: true });
378
+ } catch (e) {
379
+ res.status(500).json({ error: e.message });
380
+ }
381
+ });
346
382
 
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
- });
383
+ app.post('/api/chat/stream-end', (req, res) => {
384
+ try {
385
+ const { targetId, friend_id, message_id, messageId } = req.body;
386
+ const streamTarget = targetId || friend_id;
387
+ if (!streamTarget) return res.status(400).json({ error: 'targetId or friend_id is required' });
388
+ const msgId = message_id || messageId || ('msg_' + Date.now() + '_' + Math.random().toString(36).substr(2, 6));
389
+ const sent = serverClient.sendWS({
390
+ type: 'stream_end',
391
+ to: streamTarget,
392
+ messageId: msgId,
393
+ });
394
+ if (!sent) return res.status(503).json({ error: 'Not connected to server', success: false });
395
+ res.json({ success: true, messageId: msgId });
396
+ } catch (e) {
397
+ res.status(500).json({ error: e.message });
398
+ }
399
+ });
352
400
 
353
- // Streaming endpoints — for external systems / OpenClaw agent output
354
- app.post('/api/chat/stream-chunk', (req, res) => {
355
- try {
356
- const { targetId, friend_id, chunk_type, chunkType, data } = req.body;
357
- const streamTarget = targetId || friend_id;
358
- if (!streamTarget) return res.status(400).json({ error: 'targetId or friend_id is required' });
359
- if (!data) return res.status(400).json({ error: 'data is required' });
360
- const type = chunk_type || chunkType || 'text';
361
- if (!['text', 'reasoning', 'tool_call', 'tool_result'].includes(type)) {
362
- return res.status(400).json({ error: `Invalid chunk_type: ${type}` });
401
+ // File upload
402
+ app.post('/api/upload', upload.single('file'), async (req, res) => {
403
+ try {
404
+ if (!req.file) return res.status(400).json({ error: 'No file uploaded' });
405
+ const agentId = getAgentId(req);
406
+ const targetId = req.body.targetId;
407
+ const isGroup = req.body.isGroup === 'true' || req.body.isGroup === '1';
408
+ const msg = await chat.handleFileUpload(agentId, targetId, req.file, isGroup);
409
+ res.json({ success: true, message: msg });
410
+ } catch (e) {
411
+ res.status(500).json({ error: e.message });
363
412
  }
364
- const sent = serverClient.sendWS({
365
- type: 'stream_chunk',
366
- to: streamTarget,
367
- chunkType: type,
368
- data: data,
369
- });
370
- if (!sent) return res.status(503).json({ error: 'Not connected to server', success: false });
371
- res.json({ success: true });
372
- } catch (e) {
373
- res.status(500).json({ error: e.message });
374
- }
375
- });
413
+ });
376
414
 
377
- app.post('/api/chat/stream-end', (req, res) => {
378
- try {
379
- const { targetId, friend_id, message_id, messageId } = req.body;
380
- const streamTarget = targetId || friend_id;
381
- if (!streamTarget) return res.status(400).json({ error: 'targetId or friend_id is required' });
382
- const msgId = message_id || messageId || ('msg_' + Date.now() + '_' + Math.random().toString(36).substr(2, 6));
383
- const sent = serverClient.sendWS({
384
- type: 'stream_end',
385
- to: streamTarget,
386
- messageId: msgId,
387
- });
388
- if (!sent) return res.status(503).json({ error: 'Not connected to server', success: false });
389
- res.json({ success: true, messageId: msgId });
390
- } catch (e) {
391
- res.status(500).json({ error: e.message });
392
- }
393
- });
415
+ app.get('/api/files/:fileId', (req, res) => {
416
+ const filePath = path.join(UPLOADS_DIR, req.params.fileId);
417
+ if (fs.existsSync(filePath)) {
418
+ res.sendFile(filePath);
419
+ } else {
420
+ res.status(404).json({ error: 'File not found' });
421
+ }
422
+ });
394
423
 
395
- // File upload
396
- app.post('/api/upload', upload.single('file'), async (req, res) => {
397
- try {
398
- if (!req.file) return res.status(400).json({ error: 'No file uploaded' });
424
+ // Identity
425
+ app.get('/api/identity', (req, res) => {
399
426
  const agentId = getAgentId(req);
400
- const targetId = req.body.targetId;
401
- const isGroup = req.body.isGroup === 'true' || req.body.isGroup === '1';
402
- const msg = await chat.handleFileUpload(agentId, targetId, req.file, isGroup);
403
- res.json({ success: true, message: msg });
404
- } catch (e) {
405
- res.status(500).json({ error: e.message });
406
- }
407
- });
427
+ res.json(identity.getInfo(agentId) || {});
428
+ });
408
429
 
409
- app.get('/api/files/:fileId', (req, res) => {
410
- const filePath = path.join(UPLOADS_DIR, req.params.fileId);
411
- if (fs.existsSync(filePath)) {
412
- res.sendFile(filePath);
413
- } else {
414
- res.status(404).json({ error: 'File not found' });
415
- }
416
- });
430
+ app.post('/api/identity/nickname', (req, res) => {
431
+ const { agent_id, nickname } = req.body;
432
+ const agentId = agent_id || currentAgentId;
433
+ identity.updateNickname(agentId, nickname);
434
+ res.json({ success: true });
435
+ });
417
436
 
418
- // Identity
419
- app.get('/api/identity', (req, res) => {
420
- const agentId = getAgentId(req);
421
- res.json(identity.getInfo(agentId) || {});
422
- });
437
+ app.post('/api/identity/friend-code', async (req, res) => {
438
+ try {
439
+ const agentId = req.body.agent_id || currentAgentId;
440
+ await serverClient.ensureAuth(agentId);
441
+ const result = await handshake.generateFriendCode(agentId);
442
+ res.json({ success: true, code: result.number, expires_at: result.expiresAt || result.expires_at });
443
+ } catch (e) {
444
+ res.status(500).json({ error: e.message });
445
+ }
446
+ });
423
447
 
424
- app.post('/api/identity/nickname', (req, res) => {
425
- const { agent_id, nickname } = req.body;
426
- const agentId = agent_id || currentAgentId;
427
- identity.updateNickname(agentId, nickname);
428
- res.json({ success: true });
429
- });
448
+ app.get('/api/identity/qr', async (req, res) => {
449
+ try {
450
+ const agentId = getAgentId(req);
451
+ const info = identity.getInfo(agentId);
452
+ if (!info) return res.status(404).json({ error: 'Agent not found' });
453
+ const qrData = JSON.stringify({
454
+ type: 'aicq-friend',
455
+ agent_id: info.agent_id,
456
+ public_key: info.signing_public_key,
457
+ exchange_public_key: info.exchange_public_key,
458
+ fingerprint: info.fingerprint,
459
+ });
460
+ const qrImage = await QRCode.toDataURL(qrData);
461
+ res.json({ qr: qrImage, data: qrData, info });
462
+ } catch (e) {
463
+ res.status(500).json({ error: e.message });
464
+ }
465
+ });
430
466
 
431
- app.post('/api/identity/friend-code', async (req, res) => {
432
- try {
433
- const agentId = req.body.agent_id || currentAgentId;
434
- await serverClient.ensureAuth(agentId);
435
- const result = await handshake.generateFriendCode(agentId);
436
- res.json({ success: true, code: result.number, expires_at: result.expiresAt || result.expires_at });
437
- } catch (e) {
438
- res.status(500).json({ error: e.message });
439
- }
440
- });
467
+ app.post('/api/identity/rotate-keys', (req, res) => {
468
+ try {
469
+ const agentId = req.body.agent_id || currentAgentId;
470
+ const newInfo = identity.rotateKeys(agentId);
471
+ res.json({ success: true, info: identity.getInfo(agentId) });
472
+ } catch (e) {
473
+ res.status(500).json({ error: e.message });
474
+ }
475
+ });
476
+
477
+ // Avatar upload
478
+ const avatarUpload = multer({
479
+ storage: multer.memoryStorage(),
480
+ limits: { fileSize: 2 * 1024 * 1024 }, // 2MB max
481
+ fileFilter: (req, file, cb) => {
482
+ if (file.mimetype && file.mimetype.startsWith('image/')) {
483
+ cb(null, true);
484
+ } else {
485
+ cb(new Error('Only image files are allowed'));
486
+ }
487
+ },
488
+ });
441
489
 
442
- app.get('/api/identity/qr', async (req, res) => {
443
- try {
490
+ app.post('/api/identity/avatar', avatarUpload.single('avatar'), async (req, res) => {
491
+ try {
492
+ if (!req.file) return res.status(400).json({ error: 'No file uploaded' });
493
+ const agentId = req.body.agent_id || currentAgentId;
494
+
495
+ const avatarsDir = path.join(DATA_DIR, 'avatars');
496
+ fs.mkdirSync(avatarsDir, { recursive: true });
497
+ const ext = req.file.mimetype.split('/')[1] || 'png';
498
+ const avatarId = Date.now() + '-' + Math.random().toString(36).slice(2, 8);
499
+ const filename = `${avatarId}.${ext}`;
500
+ const filePath = path.join(avatarsDir, filename);
501
+ fs.writeFileSync(filePath, req.file.buffer);
502
+
503
+ const avatarUrl = `/api/identity/avatars/${filename}`;
504
+ identity.updateAvatar(agentId, avatarUrl);
505
+
506
+ try {
507
+ await serverClient.ensureAuth(agentId);
508
+ const FormData = (await import('form-data')).default;
509
+ const form = new FormData();
510
+ form.append('avatar', req.file.buffer, {
511
+ filename: req.file.originalname || 'avatar.' + ext,
512
+ contentType: req.file.mimetype,
513
+ });
514
+ const fetch = (await import('node-fetch')).default;
515
+ const serverUrl = SERVER_URL + '/api/v1/accounts/avatar';
516
+ const serverResp = await fetch(serverUrl, {
517
+ method: 'POST',
518
+ body: form,
519
+ headers: {
520
+ ...form.getHeaders(),
521
+ 'Authorization': 'Bearer ' + serverClient.getAccessToken(agentId),
522
+ },
523
+ });
524
+ if (serverResp.ok) {
525
+ const serverData = await serverResp.json();
526
+ if (serverData.avatar) {
527
+ identity.updateAvatar(agentId, serverData.avatar);
528
+ return res.json({ success: true, avatar: serverData.avatar });
529
+ }
530
+ }
531
+ } catch (e) {
532
+ console.error('[AICQ] Server avatar upload failed:', e.message);
533
+ }
534
+
535
+ res.json({ success: true, avatar: avatarUrl });
536
+ } catch (e) {
537
+ res.status(500).json({ error: e.message });
538
+ }
539
+ });
540
+
541
+ app.get('/api/identity/avatars/:filename', (req, res) => {
542
+ const filename = req.params.filename;
543
+ if (filename.includes('/') || filename.includes('\\') || filename.includes('..')) {
544
+ return res.status(400).json({ error: 'Invalid filename' });
545
+ }
546
+ const filePath = path.join(DATA_DIR, 'avatars', filename);
547
+ if (fs.existsSync(filePath)) {
548
+ res.sendFile(filePath);
549
+ } else {
550
+ res.status(404).json({ error: 'Avatar not found' });
551
+ }
552
+ });
553
+
554
+ app.get('/api/identity/keys', (req, res) => {
444
555
  const agentId = getAgentId(req);
445
- const info = identity.getInfo(agentId);
556
+ const info = identity.loadAgent(agentId);
446
557
  if (!info) return res.status(404).json({ error: 'Agent not found' });
447
- const qrData = JSON.stringify({
448
- type: 'aicq-friend',
558
+ res.json({
449
559
  agent_id: info.agent_id,
450
- public_key: info.signing_public_key,
560
+ nickname: info.nickname,
561
+ signing_public_key: info.signing_public_key,
451
562
  exchange_public_key: info.exchange_public_key,
563
+ signing_secret_key: info.signing_secret_key,
564
+ exchange_secret_key: info.exchange_secret_key,
452
565
  fingerprint: info.fingerprint,
453
566
  });
454
- const qrImage = await QRCode.toDataURL(qrData);
455
- res.json({ qr: qrImage, data: qrData, info });
456
- } catch (e) {
457
- res.status(500).json({ error: e.message });
458
- }
459
- });
460
-
461
- app.post('/api/identity/rotate-keys', (req, res) => {
462
- try {
463
- const agentId = req.body.agent_id || currentAgentId;
464
- const newInfo = identity.rotateKeys(agentId);
465
- res.json({ success: true, info: identity.getInfo(agentId) });
466
- } catch (e) {
467
- res.status(500).json({ error: e.message });
468
- }
469
- });
567
+ });
470
568
 
471
- // Avatar upload
472
- const avatarUpload = multer({
473
- storage: multer.memoryStorage(),
474
- limits: { fileSize: 2 * 1024 * 1024 }, // 2MB max
475
- fileFilter: (req, file, cb) => {
476
- if (file.mimetype && file.mimetype.startsWith('image/')) {
477
- cb(null, true);
478
- } else {
479
- cb(new Error('Only image files are allowed'));
569
+ // Sync endpoint
570
+ app.post('/api/sync', async (req, res) => {
571
+ try {
572
+ const agentId = req.body.agent_id || currentAgentId;
573
+ await syncFriendsFromServer(agentId);
574
+ await syncGroupsFromServer(agentId);
575
+ res.json({ success: true });
576
+ } catch (e) {
577
+ res.status(500).json({ error: e.message });
480
578
  }
481
- },
482
- });
579
+ });
483
580
 
484
- app.post('/api/identity/avatar', avatarUpload.single('avatar'), async (req, res) => {
485
- try {
486
- if (!req.file) return res.status(400).json({ error: 'No file uploaded' });
487
- const agentId = req.body.agent_id || currentAgentId;
488
-
489
- // Save avatar locally
490
- const avatarsDir = path.join(DATA_DIR, 'avatars');
491
- fs.mkdirSync(avatarsDir, { recursive: true });
492
- const ext = req.file.mimetype.split('/')[1] || 'png';
493
- const avatarId = Date.now() + '-' + Math.random().toString(36).slice(2, 8);
494
- const filename = `${avatarId}.${ext}`;
495
- const filePath = path.join(avatarsDir, filename);
496
- fs.writeFileSync(filePath, req.file.buffer);
497
-
498
- // Update local identity
499
- const avatarUrl = `/api/identity/avatars/${filename}`;
500
- identity.updateAvatar(agentId, avatarUrl);
501
-
502
- // Try to upload to server too
581
+ // ─── Gateway Proxy Endpoint (for extension.js) ──────────────────────
582
+ app.post('/api/gateway', async (req, res) => {
503
583
  try {
504
- await serverClient.ensureAuth(agentId);
505
- const FormData = (await import('form-data')).default;
506
- const form = new FormData();
507
- form.append('avatar', req.file.buffer, {
508
- filename: req.file.originalname || 'avatar.' + ext,
509
- contentType: req.file.mimetype,
510
- });
511
- // Use node-fetch to upload to server
512
- const fetch = (await import('node-fetch')).default;
513
- const serverUrl = SERVER_URL + '/api/v1/accounts/avatar';
514
- const serverResp = await fetch(serverUrl, {
515
- method: 'POST',
516
- body: form,
517
- headers: {
518
- ...form.getHeaders(),
519
- 'Authorization': 'Bearer ' + serverClient.getAccessToken(agentId),
520
- },
521
- });
522
- if (serverResp.ok) {
523
- const serverData = await serverResp.json();
524
- if (serverData.avatar) {
525
- identity.updateAvatar(agentId, serverData.avatar);
526
- return res.json({ success: true, avatar: serverData.avatar });
527
- }
528
- }
584
+ const { method, kwargs } = req.body;
585
+ if (!method) return res.status(400).json({ error: 'method is required' });
586
+ const result = await handleGatewayCall(method, kwargs);
587
+ res.json(result);
529
588
  } catch (e) {
530
- console.error('[AICQ] Server avatar upload failed:', e.message);
589
+ res.status(500).json({ error: e.message });
531
590
  }
532
-
533
- res.json({ success: true, avatar: avatarUrl });
534
- } catch (e) {
535
- res.status(500).json({ error: e.message });
536
- }
537
- });
538
-
539
- app.get('/api/identity/avatars/:filename', (req, res) => {
540
- const filename = req.params.filename;
541
- // Security: prevent directory traversal
542
- if (filename.includes('/') || filename.includes('\\') || filename.includes('..')) {
543
- return res.status(400).json({ error: 'Invalid filename' });
544
- }
545
- const filePath = path.join(DATA_DIR, 'avatars', filename);
546
- if (fs.existsSync(filePath)) {
547
- res.sendFile(filePath);
548
- } else {
549
- res.status(404).json({ error: 'Avatar not found' });
550
- }
551
- });
552
-
553
- app.get('/api/identity/keys', (req, res) => {
554
- const agentId = getAgentId(req);
555
- const info = identity.loadAgent(agentId);
556
- if (!info) return res.status(404).json({ error: 'Agent not found' });
557
- res.json({
558
- agent_id: info.agent_id,
559
- nickname: info.nickname,
560
- signing_public_key: info.signing_public_key,
561
- exchange_public_key: info.exchange_public_key,
562
- signing_secret_key: info.signing_secret_key,
563
- exchange_secret_key: info.exchange_secret_key,
564
- fingerprint: info.fingerprint,
565
591
  });
566
- });
567
-
568
- // Sync endpoint
569
- app.post('/api/sync', async (req, res) => {
570
- try {
571
- const agentId = req.body.agent_id || currentAgentId;
572
- await syncFriendsFromServer(agentId);
573
- await syncGroupsFromServer(agentId);
574
- res.json({ success: true });
575
- } catch (e) {
576
- res.status(500).json({ error: e.message });
577
- }
578
- });
579
-
580
- // ─── Gateway Proxy Endpoint (for extension.js) ──────────────────────
581
- app.post('/api/gateway', async (req, res) => {
582
- try {
583
- const { method, kwargs } = req.body;
584
- if (!method) return res.status(400).json({ error: 'method is required' });
585
- const result = await handleGatewayCall(method, kwargs);
586
- res.json(result);
587
- } catch (e) {
588
- res.status(500).json({ error: e.message });
589
- }
590
- });
591
-
592
- // ─── Start Server ───────────────────────────────────────────────────
593
- app.listen(PORT, '0.0.0.0', () => {
594
- console.log(`[AICQ Plugin] Running on http://0.0.0.0:${PORT}`);
595
- console.log(`[AICQ Plugin] Server: ${SERVER_URL}`);
596
- console.log(`[AICQ Plugin] Data dir: ${DATA_DIR}`);
597
- });
598
592
 
599
- // ─── OpenClaw Gateway Integration ───────────────────────────────────
600
- process.on('message', (msg) => {
601
- if (msg.type === 'gateway_call') {
602
- handleGatewayCall(msg.method, msg.kwargs).then(result => {
603
- process.send({ type: 'gateway_response', id: msg.id, result });
604
- }).catch(err => {
605
- process.send({ type: 'gateway_response', id: msg.id, error: err.message });
606
- });
607
- }
608
- });
593
+ // ─── Start Server ───────────────────────────────────────────────────
594
+ app.listen(PORT, '0.0.0.0', () => {
595
+ console.log(`[AICQ Plugin] Running on http://0.0.0.0:${PORT}`);
596
+ console.log(`[AICQ Plugin] Server: ${SERVER_URL}`);
597
+ console.log(`[AICQ Plugin] Data dir: ${DATA_DIR}`);
598
+ });
609
599
 
610
- async function handleGatewayCall(method, kwargs = {}) {
611
- switch (method) {
612
- case 'aicq.status':
613
- return { state: serverClient.connected ? 'connected' : 'disconnected', agent_id: currentAgentId, version: '2.1.0' };
614
- case 'aicq.friends.list':
615
- return { friends: db.listFriends(currentAgentId) };
616
- case 'aicq.friends.add':
617
- return await handshake.addFriendByCode(currentAgentId, kwargs.temp_number);
618
- case 'aicq.friends.remove':
619
- db.removeFriend(currentAgentId, kwargs.friend_id);
620
- return { success: true };
621
- case 'aicq.friends.requests':
622
- return { requests: db.getPendingRequests(currentAgentId) };
623
- case 'aicq.identity.info':
624
- return identity.getInfo(currentAgentId) || {};
625
- case 'aicq.agent.create':
626
- identity.createAgent(kwargs.agent_id, kwargs.nickname);
627
- return { success: true };
628
- case 'aicq.chat.send':
629
- return await chat.sendMessage(currentAgentId, kwargs.targetId, kwargs.content, { isGroup: kwargs.isGroup });
630
- case 'aicq.chat.history':
631
- return { messages: db.getChatHistory(currentAgentId, kwargs.targetId, { limit: kwargs.limit || 50 }) };
632
- case 'aicq.chat.streamChunk': {
633
- if (!kwargs.friend_id && !kwargs.targetId) return { error: 'friend_id or targetId is required' };
634
- if (!kwargs.data) return { error: 'data is required' };
635
- const chunkType = kwargs.chunk_type || kwargs.chunkType || 'text';
636
- if (!['text', 'reasoning', 'tool_call', 'tool_result'].includes(chunkType)) return { error: `Invalid chunk_type: ${chunkType}` };
637
- const streamTarget = kwargs.friend_id || kwargs.targetId;
638
- const sent = serverClient.sendWS({
639
- type: 'stream_chunk',
640
- to: streamTarget,
641
- chunkType: chunkType,
642
- data: kwargs.data,
600
+ // ─── OpenClaw Gateway Integration ───────────────────────────────────
601
+ process.on('message', (msg) => {
602
+ if (msg.type === 'gateway_call') {
603
+ handleGatewayCall(msg.method, msg.kwargs).then(result => {
604
+ process.send({ type: 'gateway_response', id: msg.id, result });
605
+ }).catch(err => {
606
+ process.send({ type: 'gateway_response', id: msg.id, error: err.message });
643
607
  });
644
- if (!sent) return { error: 'Not connected to server', success: false };
645
- return { success: true };
646
608
  }
647
- case 'aicq.chat.streamEnd': {
648
- if (!kwargs.friend_id && !kwargs.targetId) return { error: 'friend_id or targetId is required' };
649
- const endTarget = kwargs.friend_id || kwargs.targetId;
650
- const msgId = kwargs.message_id || kwargs.messageId || ('msg_' + Date.now() + '_' + Math.random().toString(36).substr(2, 6));
651
- const endSent = serverClient.sendWS({
652
- type: 'stream_end',
653
- to: endTarget,
654
- messageId: msgId,
655
- });
656
- if (!endSent) return { error: 'Not connected to server', success: false };
657
- return { success: true, messageId: msgId };
609
+ });
610
+
611
+ async function handleGatewayCall(method, kwargs = {}) {
612
+ switch (method) {
613
+ case 'aicq.status':
614
+ return { state: serverClient.connected ? 'connected' : 'disconnected', agent_id: currentAgentId, version: '2.6.0' };
615
+ case 'aicq.friends.list':
616
+ return { friends: db.listFriends(currentAgentId) };
617
+ case 'aicq.friends.add':
618
+ return await handshake.addFriendByCode(currentAgentId, kwargs.temp_number);
619
+ case 'aicq.friends.remove':
620
+ db.removeFriend(currentAgentId, kwargs.friend_id);
621
+ return { success: true };
622
+ case 'aicq.friends.requests':
623
+ return { requests: db.getPendingRequests(currentAgentId) };
624
+ case 'aicq.identity.info':
625
+ return identity.getInfo(currentAgentId) || {};
626
+ case 'aicq.agent.create':
627
+ identity.createAgent(kwargs.agent_id, kwargs.nickname);
628
+ return { success: true };
629
+ case 'aicq.chat.send':
630
+ return await chat.sendMessage(currentAgentId, kwargs.targetId, kwargs.content, { isGroup: kwargs.isGroup });
631
+ case 'aicq.chat.history':
632
+ return { messages: db.getChatHistory(currentAgentId, kwargs.targetId, { limit: kwargs.limit || 50 }) };
633
+ case 'aicq.chat.streamChunk': {
634
+ if (!kwargs.friend_id && !kwargs.targetId) return { error: 'friend_id or targetId is required' };
635
+ if (!kwargs.data) return { error: 'data is required' };
636
+ const chunkType = kwargs.chunk_type || kwargs.chunkType || 'text';
637
+ if (!['text', 'reasoning', 'tool_call', 'tool_result'].includes(chunkType)) return { error: `Invalid chunk_type: ${chunkType}` };
638
+ const streamTarget = kwargs.friend_id || kwargs.targetId;
639
+ const sent = serverClient.sendWS({
640
+ type: 'stream_chunk',
641
+ to: streamTarget,
642
+ chunkType: chunkType,
643
+ data: kwargs.data,
644
+ });
645
+ if (!sent) return { error: 'Not connected to server', success: false };
646
+ return { success: true };
647
+ }
648
+ case 'aicq.chat.streamEnd': {
649
+ if (!kwargs.friend_id && !kwargs.targetId) return { error: 'friend_id or targetId is required' };
650
+ const endTarget = kwargs.friend_id || kwargs.targetId;
651
+ const msgId = kwargs.message_id || kwargs.messageId || ('msg_' + Date.now() + '_' + Math.random().toString(36).substr(2, 6));
652
+ const endSent = serverClient.sendWS({
653
+ type: 'stream_end',
654
+ to: endTarget,
655
+ messageId: msgId,
656
+ });
657
+ if (!endSent) return { error: 'Not connected to server', success: false };
658
+ return { success: true, messageId: msgId };
659
+ }
660
+ default:
661
+ return { error: `Unknown method: ${method}` };
658
662
  }
659
- default:
660
- return { error: `Unknown method: ${method}` };
661
663
  }
662
- }
664
+ })().catch(err => {
665
+ console.error('[AICQ] Fatal startup error:', err);
666
+ process.exit(1);
667
+ });