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