aicq-chat-plugin 2.6.7 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -44
- package/SKILL.md +27 -19
- package/cli.js +77 -211
- package/index.js +343 -620
- package/openclaw.plugin.json +42 -33
- package/package.json +11 -7
- package/postinstall.js +17 -346
- 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,394 @@
|
|
|
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
|
-
|
|
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
|
+
},
|
|
43
97
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
+
// Auto-create default agent if none exists
|
|
165
|
+
const agents = _identity.listAgents();
|
|
166
|
+
let currentAgentId;
|
|
47
167
|
if (agents.length === 0) {
|
|
48
|
-
const defaultAgent =
|
|
168
|
+
const defaultAgent = _identity.createAgent('agent-' + Date.now(), '默认Agent');
|
|
49
169
|
currentAgentId = defaultAgent.agent_id;
|
|
50
|
-
console.log('[AICQ] Created default agent:', currentAgentId);
|
|
170
|
+
console.log('[AICQ Channel] Created default agent:', currentAgentId);
|
|
51
171
|
} else {
|
|
52
172
|
currentAgentId = agents[0].agent_id;
|
|
53
173
|
}
|
|
54
174
|
|
|
55
|
-
// Connect to server
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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;
|
|
175
|
+
// Connect to AICQ server
|
|
176
|
+
try {
|
|
177
|
+
await _serverClient.start(currentAgentId);
|
|
178
|
+
// Sync friends and groups from server
|
|
179
|
+
await syncFriendsFromServer(currentAgentId);
|
|
180
|
+
await syncGroupsFromServer(currentAgentId);
|
|
181
|
+
} catch (e) {
|
|
182
|
+
console.error('[AICQ Channel] Initial server connection failed:', e.message);
|
|
72
183
|
}
|
|
73
184
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
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({
|
|
185
|
+
return {
|
|
186
|
+
handleTool,
|
|
187
|
+
handleGateway,
|
|
188
|
+
channel: _channel,
|
|
189
|
+
gatewayRoutes: _uiRoutes,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// ── Sync helpers ────────────────────────────────────────────────────
|
|
194
|
+
async function syncFriendsFromServer(agentId) {
|
|
195
|
+
try {
|
|
196
|
+
await _serverClient.ensureAuth(agentId);
|
|
197
|
+
const result = await _serverClient.listFriends();
|
|
198
|
+
if (result.friends) {
|
|
199
|
+
for (const f of result.friends) {
|
|
200
|
+
const existing = _db.getFriend(agentId, f.id);
|
|
201
|
+
if (!existing) {
|
|
202
|
+
_db.addFriend({
|
|
108
203
|
agent_id: agentId,
|
|
109
|
-
id:
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
204
|
+
id: f.id,
|
|
205
|
+
public_key: f.public_key || f.publicKey || '',
|
|
206
|
+
fingerprint: f.fingerprint || '',
|
|
207
|
+
friend_type: f.type || f.friend_type || 'ai',
|
|
208
|
+
ai_name: f.agent_name || f.ai_name || f.displayName || '',
|
|
114
209
|
});
|
|
210
|
+
} else {
|
|
211
|
+
_db.updateFriendOnline(agentId, f.id, f.is_online || f.isOnline || false);
|
|
115
212
|
}
|
|
116
213
|
}
|
|
117
|
-
} catch (e) {
|
|
118
|
-
console.error('[AICQ] Sync groups failed:', e.message);
|
|
119
214
|
}
|
|
215
|
+
} catch (e) {
|
|
216
|
+
console.error('[AICQ Channel] Sync friends failed:', e.message);
|
|
120
217
|
}
|
|
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({
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
async function syncGroupsFromServer(agentId) {
|
|
221
|
+
try {
|
|
222
|
+
await _serverClient.ensureAuth(agentId);
|
|
223
|
+
const result = await _serverClient.listGroups();
|
|
224
|
+
if (result.groups) {
|
|
225
|
+
for (const g of result.groups) {
|
|
226
|
+
_db.addGroup({
|
|
275
227
|
agent_id: agentId,
|
|
276
|
-
id:
|
|
277
|
-
name,
|
|
278
|
-
owner_id:
|
|
279
|
-
members_json:
|
|
280
|
-
description: description || '',
|
|
228
|
+
id: g.id,
|
|
229
|
+
name: g.name,
|
|
230
|
+
owner_id: g.owner_id || g.ownerId || '',
|
|
231
|
+
members_json: g.members || g.members_json || '[]',
|
|
232
|
+
description: g.description || '',
|
|
281
233
|
});
|
|
282
234
|
}
|
|
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
235
|
}
|
|
298
|
-
})
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
const before = req.query.before || null;
|
|
331
|
-
const messages = db.getChatHistory(agentId, req.params.targetId, { limit, before });
|
|
332
|
-
res.json({ messages });
|
|
333
|
-
});
|
|
334
|
-
|
|
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 });
|
|
236
|
+
} catch (e) {
|
|
237
|
+
console.error('[AICQ Channel] Sync groups failed:', e.message);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// ── Tool handler ────────────────────────────────────────────────────
|
|
242
|
+
async function handleTool(toolName, params) {
|
|
243
|
+
await ensureInitialized();
|
|
244
|
+
const agents = _identity.listAgents();
|
|
245
|
+
const currentAgentId = agents.length > 0 ? agents[0].agent_id : null;
|
|
246
|
+
|
|
247
|
+
switch (toolName) {
|
|
248
|
+
case 'chat-friend': {
|
|
249
|
+
const { action, friend_code, friend_id } = params || {};
|
|
250
|
+
switch (action) {
|
|
251
|
+
case 'list':
|
|
252
|
+
return { friends: _db.listFriends(currentAgentId) };
|
|
253
|
+
case 'add':
|
|
254
|
+
return await _handshake.addFriendByCode(currentAgentId, friend_code);
|
|
255
|
+
case 'remove':
|
|
256
|
+
_db.removeFriend(currentAgentId, friend_id);
|
|
257
|
+
try { await _serverClient.removeFriend(friend_id); } catch (e) {}
|
|
258
|
+
return { success: true };
|
|
259
|
+
case 'requests':
|
|
260
|
+
return { requests: _db.getPendingRequests(currentAgentId) };
|
|
261
|
+
case 'accept':
|
|
262
|
+
return await _handshake.acceptRequest(currentAgentId, friend_id);
|
|
263
|
+
case 'reject':
|
|
264
|
+
return await _handshake.rejectRequest(currentAgentId, friend_id);
|
|
265
|
+
default:
|
|
266
|
+
return { error: `Unknown friend action: ${action}` };
|
|
267
|
+
}
|
|
350
268
|
}
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
269
|
+
case 'chat-send':
|
|
270
|
+
return await _chat.sendMessage(
|
|
271
|
+
currentAgentId,
|
|
272
|
+
params.targetId,
|
|
273
|
+
params.content,
|
|
274
|
+
{ isGroup: params.isGroup || false }
|
|
275
|
+
);
|
|
276
|
+
case 'chat-export-key':
|
|
277
|
+
return _identity.getInfo(currentAgentId) || {};
|
|
278
|
+
default:
|
|
279
|
+
return { error: `Unknown tool: ${toolName}` };
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// ── Gateway handler ─────────────────────────────────────────────────
|
|
284
|
+
async function handleGateway(method, kwargs = {}) {
|
|
285
|
+
await ensureInitialized();
|
|
286
|
+
const agents = _identity.listAgents();
|
|
287
|
+
const currentAgentId = agents.length > 0 ? agents[0].agent_id : null;
|
|
288
|
+
|
|
289
|
+
switch (method) {
|
|
290
|
+
case 'aicq.status':
|
|
291
|
+
return {
|
|
292
|
+
state: _serverClient.connected ? 'connected' : 'disconnected',
|
|
293
|
+
agent_id: currentAgentId,
|
|
294
|
+
version: '3.0.0',
|
|
295
|
+
architecture: 'channel',
|
|
296
|
+
};
|
|
297
|
+
case 'aicq.friends.list':
|
|
298
|
+
return { friends: _db.listFriends(currentAgentId) };
|
|
299
|
+
case 'aicq.friends.add':
|
|
300
|
+
return await _handshake.addFriendByCode(currentAgentId, kwargs.temp_number);
|
|
301
|
+
case 'aicq.friends.remove':
|
|
302
|
+
_db.removeFriend(currentAgentId, kwargs.friend_id);
|
|
303
|
+
return { success: true };
|
|
304
|
+
case 'aicq.friends.requests':
|
|
305
|
+
return { requests: _db.getPendingRequests(currentAgentId) };
|
|
306
|
+
case 'aicq.friends.acceptRequest':
|
|
307
|
+
return await _handshake.acceptRequest(currentAgentId, kwargs.request_id);
|
|
308
|
+
case 'aicq.friends.rejectRequest':
|
|
309
|
+
return await _handshake.rejectRequest(currentAgentId, kwargs.request_id);
|
|
310
|
+
case 'aicq.identity.info':
|
|
311
|
+
return _identity.getInfo(currentAgentId) || {};
|
|
312
|
+
case 'aicq.agent.create':
|
|
313
|
+
_identity.createAgent(kwargs.agent_id, kwargs.nickname);
|
|
314
|
+
return { success: true };
|
|
315
|
+
case 'aicq.agent.delete':
|
|
316
|
+
_identity.deleteAgent(kwargs.agent_id);
|
|
317
|
+
return { success: true };
|
|
318
|
+
case 'aicq.chat.send':
|
|
319
|
+
return await _chat.sendMessage(currentAgentId, kwargs.targetId, kwargs.content, { isGroup: kwargs.isGroup });
|
|
320
|
+
case 'aicq.chat.history':
|
|
321
|
+
return { messages: _db.getChatHistory(currentAgentId, kwargs.targetId, { limit: kwargs.limit || 50 }) };
|
|
322
|
+
case 'aicq.chat.delete':
|
|
323
|
+
_db.deleteMessage(currentAgentId, kwargs.message_id);
|
|
324
|
+
return { success: true };
|
|
325
|
+
case 'aicq.chat.streamChunk': {
|
|
326
|
+
if (!kwargs.friend_id && !kwargs.targetId) return { error: 'friend_id or targetId is required' };
|
|
327
|
+
if (!kwargs.data) return { error: 'data is required' };
|
|
328
|
+
const chunkType = kwargs.chunk_type || kwargs.chunkType || 'text';
|
|
368
329
|
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({
|
|
330
|
+
if (!ALLOWED_CHUNK_TYPES.includes(chunkType)) return { error: `Invalid chunk_type: ${chunkType}. Allowed: ${ALLOWED_CHUNK_TYPES.join(', ')}` };
|
|
331
|
+
const streamTarget = kwargs.friend_id || kwargs.targetId;
|
|
332
|
+
const sent = _serverClient.sendWS({
|
|
373
333
|
type: 'stream_chunk',
|
|
374
334
|
to: streamTarget,
|
|
375
|
-
chunkType:
|
|
376
|
-
data: data,
|
|
335
|
+
chunkType: chunkType,
|
|
336
|
+
data: kwargs.data,
|
|
377
337
|
});
|
|
378
|
-
if (!sent) return
|
|
379
|
-
|
|
380
|
-
} catch (e) {
|
|
381
|
-
res.status(500).json({ error: e.message });
|
|
338
|
+
if (!sent) return { error: 'Not connected to server', success: false };
|
|
339
|
+
return { success: true };
|
|
382
340
|
}
|
|
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({
|
|
341
|
+
case 'aicq.chat.streamEnd': {
|
|
342
|
+
if (!kwargs.friend_id && !kwargs.targetId) return { error: 'friend_id or targetId is required' };
|
|
343
|
+
const endTarget = kwargs.friend_id || kwargs.targetId;
|
|
344
|
+
const msgId = kwargs.message_id || kwargs.messageId || ('msg_' + Date.now() + '_' + Math.random().toString(36).substr(2, 6));
|
|
345
|
+
const endSent = _serverClient.sendWS({
|
|
392
346
|
type: 'stream_end',
|
|
393
|
-
to:
|
|
347
|
+
to: endTarget,
|
|
394
348
|
messageId: msgId,
|
|
395
349
|
});
|
|
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' });
|
|
350
|
+
if (!endSent) return { error: 'Not connected to server', success: false };
|
|
351
|
+
return { success: true, messageId: msgId };
|
|
423
352
|
}
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
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 });
|
|
476
|
-
}
|
|
477
|
-
});
|
|
478
|
-
|
|
479
|
-
// Avatar upload
|
|
480
|
-
const avatarUpload = multer({
|
|
481
|
-
storage: multer.memoryStorage(),
|
|
482
|
-
limits: { fileSize: 5 * 1024 * 1024 }, // 5MB max (client should resize before uploading)
|
|
483
|
-
fileFilter: (req, file, cb) => {
|
|
484
|
-
if (file.mimetype && file.mimetype.startsWith('image/')) {
|
|
485
|
-
cb(null, true);
|
|
486
|
-
} else {
|
|
487
|
-
cb(new Error('Only image files are allowed'));
|
|
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
|
-
},
|
|
353
|
+
case 'aicq.groups.list':
|
|
354
|
+
return { groups: _db.listGroups(currentAgentId) };
|
|
355
|
+
case 'aicq.groups.create': {
|
|
356
|
+
await _serverClient.ensureAuth(currentAgentId);
|
|
357
|
+
const result = await _serverClient.createGroup(kwargs.name, kwargs.description);
|
|
358
|
+
if (result.id) {
|
|
359
|
+
_db.addGroup({
|
|
360
|
+
agent_id: currentAgentId,
|
|
361
|
+
id: result.id,
|
|
362
|
+
name: kwargs.name,
|
|
363
|
+
owner_id: currentAgentId,
|
|
364
|
+
members_json: result.members || '[]',
|
|
365
|
+
description: kwargs.description || '',
|
|
525
366
|
});
|
|
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
367
|
}
|
|
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' });
|
|
553
|
-
}
|
|
554
|
-
});
|
|
555
|
-
|
|
556
|
-
app.get('/api/identity/keys', (req, res) => {
|
|
557
|
-
const agentId = getAgentId(req);
|
|
558
|
-
const info = identity.loadAgent(agentId);
|
|
559
|
-
if (!info) return res.status(404).json({ error: 'Agent not found' });
|
|
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
|
-
});
|
|
368
|
+
return { success: true, group: result };
|
|
610
369
|
}
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
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}` };
|
|
370
|
+
case 'aicq.groups.join':
|
|
371
|
+
await _serverClient.ensureAuth(currentAgentId);
|
|
372
|
+
return await _serverClient.inviteGroupMember(kwargs.group_id, currentAgentId);
|
|
373
|
+
case 'aicq.groups.messages': {
|
|
374
|
+
await _serverClient.ensureAuth(currentAgentId);
|
|
375
|
+
return await _serverClient.getGroupMessages(kwargs.group_id, kwargs.limit || 50);
|
|
666
376
|
}
|
|
377
|
+
case 'aicq.groups.silent':
|
|
378
|
+
_db.setGroupSilentMode(currentAgentId, kwargs.group_id, !!kwargs.silent);
|
|
379
|
+
return { success: true, silent: !!kwargs.silent };
|
|
380
|
+
case 'aicq.sessions.list':
|
|
381
|
+
return { sessions: [] };
|
|
382
|
+
default:
|
|
383
|
+
return { error: `Unknown method: ${method}` };
|
|
667
384
|
}
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// ── Exports ─────────────────────────────────────────────────────────
|
|
388
|
+
module.exports = {
|
|
389
|
+
register,
|
|
390
|
+
activate,
|
|
391
|
+
handleTool,
|
|
392
|
+
handleGateway,
|
|
393
|
+
ensureInitialized,
|
|
394
|
+
};
|