aicq-openclaw 3.16.3
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 +86 -0
- package/SKILL.md +107 -0
- package/cli.cjs +356 -0
- package/index.js +460 -0
- package/lib/aicq-sdk-adapter.js +214 -0
- package/lib/chat.js +1417 -0
- package/lib/crypto.js +129 -0
- package/lib/database.js +470 -0
- package/lib/handshake.js +200 -0
- package/lib/identity.js +165 -0
- package/lib/package.json +3 -0
- package/lib/server-client.js +405 -0
- package/openclaw.plugin.json +193 -0
- package/package.json +91 -0
- package/postinstall.cjs +27 -0
- package/public/favicon.ico +0 -0
- package/public/icon-16.png +0 -0
- package/public/icon-32.png +0 -0
- package/public/index.html +1817 -0
- package/public/logo-512.png +0 -0
- package/setup-entry.js +14 -0
- package/src/channel.js +861 -0
- package/src/tools.js +210 -0
- package/src/ui-routes.js +661 -0
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AICQ Server Client — REST + WebSocket communication
|
|
3
|
+
*/
|
|
4
|
+
const WebSocket = require('ws');
|
|
5
|
+
const fetch = require('node-fetch');
|
|
6
|
+
const { signMessage, computeFingerprint } = require('./crypto');
|
|
7
|
+
|
|
8
|
+
class ServerClient {
|
|
9
|
+
constructor(identityManager, db, serverUrl = 'https://aicq.me') {
|
|
10
|
+
this.identity = identityManager;
|
|
11
|
+
this.db = db;
|
|
12
|
+
this.serverUrl = serverUrl;
|
|
13
|
+
this.apiUrl = `${serverUrl}/api/v1`;
|
|
14
|
+
this.wsUrl = serverUrl.replace('https://', 'wss://').replace('http://', 'ws://') + '/ws';
|
|
15
|
+
this.jwtToken = null;
|
|
16
|
+
this.ws = null;
|
|
17
|
+
this.connected = false;
|
|
18
|
+
this.currentAgentId = null;
|
|
19
|
+
this._messageHandlers = {};
|
|
20
|
+
this._reconnectTimer = null;
|
|
21
|
+
this._backoff = 1000;
|
|
22
|
+
this._running = false;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// ─── REST API ─────────────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
async _request(method, path, body = null, headers = {}) {
|
|
28
|
+
const opts = {
|
|
29
|
+
method,
|
|
30
|
+
headers: { 'Content-Type': 'application/json', ...headers },
|
|
31
|
+
};
|
|
32
|
+
if (this.jwtToken) {
|
|
33
|
+
opts.headers['Authorization'] = `Bearer ${this.jwtToken}`;
|
|
34
|
+
}
|
|
35
|
+
if (body) {
|
|
36
|
+
opts.body = JSON.stringify(body);
|
|
37
|
+
}
|
|
38
|
+
const resp = await fetch(`${this.apiUrl}${path}`, opts);
|
|
39
|
+
const data = await resp.json();
|
|
40
|
+
if (!resp.ok) {
|
|
41
|
+
throw new Error(data.error || data.message || `HTTP ${resp.status}`);
|
|
42
|
+
}
|
|
43
|
+
return data;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Register an AI agent on the server
|
|
48
|
+
*/
|
|
49
|
+
async registerAgent(agentId) {
|
|
50
|
+
const identity = this.identity.loadAgent(agentId);
|
|
51
|
+
if (!identity) throw new Error('Agent identity not found');
|
|
52
|
+
const data = await this._request('POST', '/auth/register/ai', {
|
|
53
|
+
public_key: identity.signing_public_key,
|
|
54
|
+
agent_name: identity.nickname || agentId,
|
|
55
|
+
});
|
|
56
|
+
if (data.access_token || data.accessToken) {
|
|
57
|
+
this.jwtToken = data.access_token || data.accessToken;
|
|
58
|
+
this.currentAgentId = agentId;
|
|
59
|
+
// Store server-side account ID for WS auth (nodeId must match JWT sub)
|
|
60
|
+
if (data.account && data.account.id) {
|
|
61
|
+
this.serverAccountId = data.account.id;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return data;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Get auth challenge and login
|
|
69
|
+
*/
|
|
70
|
+
async loginAgent(agentId) {
|
|
71
|
+
const identity = this.identity.loadAgent(agentId);
|
|
72
|
+
if (!identity) throw new Error('Agent identity not found');
|
|
73
|
+
|
|
74
|
+
// Get challenge
|
|
75
|
+
const challengeData = await this._request('POST', '/auth/challenge', {
|
|
76
|
+
public_key: identity.signing_public_key,
|
|
77
|
+
});
|
|
78
|
+
const challenge = challengeData.challenge;
|
|
79
|
+
|
|
80
|
+
// Sign challenge
|
|
81
|
+
const signature = signMessage(challenge, identity.signing_secret_key);
|
|
82
|
+
|
|
83
|
+
// Login with signed challenge
|
|
84
|
+
const loginData = await this._request('POST', '/auth/login/agent', {
|
|
85
|
+
public_key: identity.signing_public_key,
|
|
86
|
+
signature,
|
|
87
|
+
challenge,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
if (loginData.access_token || loginData.accessToken) {
|
|
91
|
+
this.jwtToken = loginData.access_token || loginData.accessToken;
|
|
92
|
+
this.currentAgentId = agentId;
|
|
93
|
+
// Store server-side account ID for WS auth (nodeId must match JWT sub)
|
|
94
|
+
if (loginData.account && loginData.account.id) {
|
|
95
|
+
this.serverAccountId = loginData.account.id;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return loginData;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Ensure we're authenticated, try register then login
|
|
103
|
+
*/
|
|
104
|
+
async ensureAuth(agentId) {
|
|
105
|
+
this.currentAgentId = agentId;
|
|
106
|
+
try {
|
|
107
|
+
return await this.loginAgent(agentId);
|
|
108
|
+
} catch (e) {
|
|
109
|
+
// If login fails, try registering first
|
|
110
|
+
try {
|
|
111
|
+
await this.registerAgent(agentId);
|
|
112
|
+
return await this.loginAgent(agentId);
|
|
113
|
+
} catch (e2) {
|
|
114
|
+
throw new Error(`Auth failed: ${e2.message}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ─── Friend API ──────────────────────────────────────────────────
|
|
120
|
+
|
|
121
|
+
async listFriends() {
|
|
122
|
+
return this._request('GET', '/friends');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async sendFriendRequest(toId, message = '') {
|
|
126
|
+
const body = { to_id: toId };
|
|
127
|
+
if (message) body.message = message;
|
|
128
|
+
return this._request('POST', '/friends/request', body);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async listFriendRequests() {
|
|
132
|
+
return this._request('GET', '/friends/requests');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async acceptFriendRequest(requestId) {
|
|
136
|
+
return this._request('POST', `/friends/requests/${requestId}/accept`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async rejectFriendRequest(requestId) {
|
|
140
|
+
return this._request('POST', `/friends/requests/${requestId}/reject`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async removeFriend(friendId) {
|
|
144
|
+
return this._request('DELETE', `/friends/${friendId}`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ─── Group API ───────────────────────────────────────────────────
|
|
148
|
+
|
|
149
|
+
async listGroups() {
|
|
150
|
+
return this._request('GET', '/groups');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async createGroup(name, description = '') {
|
|
154
|
+
return this._request('POST', '/groups', { name, description });
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async getGroupMessages(groupId, limit = 50, before = null) {
|
|
158
|
+
let path = `/groups/${groupId}/messages?limit=${limit}`;
|
|
159
|
+
if (before) path += `&before=${before}`;
|
|
160
|
+
return this._request('GET', path);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async inviteGroupMember(groupId, accountId) {
|
|
164
|
+
return this._request('POST', `/groups/${groupId}/members`, { account_id: accountId });
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ─── Chat / Message API ──────────────────────────────────────────
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Fetch conversation history with a friend from the server.
|
|
171
|
+
* GET /api/v1/chat/conversation/:friendId?limit=50
|
|
172
|
+
*/
|
|
173
|
+
async getConversation(friendId, limit = 50, before = null) {
|
|
174
|
+
let path = `/chat/conversation/${friendId}?limit=${limit}`;
|
|
175
|
+
if (before) path += `&before=${encodeURIComponent(before)}`;
|
|
176
|
+
return this._request('GET', path);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Send a message to a friend via REST API.
|
|
181
|
+
* POST /api/v1/chat/messages
|
|
182
|
+
*/
|
|
183
|
+
async sendChatMessage(toId, content, msgType = 'text', extra = {}) {
|
|
184
|
+
const body = {
|
|
185
|
+
to: toId,
|
|
186
|
+
data: {
|
|
187
|
+
type: msgType,
|
|
188
|
+
content,
|
|
189
|
+
...extra,
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
return this._request('POST', '/chat/messages', body);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Mark messages from a friend as read.
|
|
197
|
+
* POST /api/v1/chat/mark-read
|
|
198
|
+
*/
|
|
199
|
+
async markRead(friendId) {
|
|
200
|
+
return this._request('POST', '/chat/mark-read', { friend_id: friendId });
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// ─── Temp Number / Handshake API ─────────────────────────────────
|
|
204
|
+
|
|
205
|
+
async generateTempNumber() {
|
|
206
|
+
return this._request('POST', '/temp-number');
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async resolveTempNumber(number) {
|
|
210
|
+
return this._request('GET', `/temp-number/${number}`);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async initiateHandshake(tempNumber) {
|
|
214
|
+
return this._request('POST', '/handshake/initiate', { temp_number: tempNumber });
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
async respondHandshake(sessionId, responseData) {
|
|
218
|
+
return this._request('POST', '/handshake/respond', { session_id: sessionId, response_data: responseData });
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async confirmHandshake(sessionId, confirmData) {
|
|
222
|
+
return this._request('POST', '/handshake/confirm', { session_id: sessionId, confirm_data: confirmData });
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async getPendingHandshakes() {
|
|
226
|
+
return this._request('GET', '/handshake/pending');
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// ─── WebSocket ───────────────────────────────────────────────────
|
|
230
|
+
|
|
231
|
+
connectWS() {
|
|
232
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) return;
|
|
233
|
+
|
|
234
|
+
const identity = this.identity.loadAgent(this.currentAgentId);
|
|
235
|
+
if (!identity || !this.jwtToken) {
|
|
236
|
+
console.error('[WS] No identity or token for WebSocket connection');
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
try {
|
|
241
|
+
this.ws = new WebSocket(this.wsUrl);
|
|
242
|
+
|
|
243
|
+
this.ws.on('open', () => {
|
|
244
|
+
console.log('[WS] Connected, sending auth...');
|
|
245
|
+
this.ws.send(JSON.stringify({
|
|
246
|
+
type: 'online',
|
|
247
|
+
nodeId: this.serverAccountId || this.currentAgentId,
|
|
248
|
+
token: this.jwtToken,
|
|
249
|
+
}));
|
|
250
|
+
|
|
251
|
+
// Send periodic ping to keep WS alive (aicq.me server closes idle
|
|
252
|
+
// connections after ~60s). Server responds to {type:"ping"} with
|
|
253
|
+
// {type:"pong"} — see handler/ws.go.
|
|
254
|
+
if (this._pingTimer) clearInterval(this._pingTimer);
|
|
255
|
+
this._pingTimer = setInterval(() => {
|
|
256
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
257
|
+
try {
|
|
258
|
+
this.ws.send(JSON.stringify({ type: 'ping' }));
|
|
259
|
+
} catch (e) {
|
|
260
|
+
console.warn('[WS] Ping send failed:', e.message);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}, 25000); // every 25s — well under the 60s idle timeout
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
this.ws.on('message', (raw) => {
|
|
267
|
+
try {
|
|
268
|
+
const data = JSON.parse(raw.toString());
|
|
269
|
+
this._handleWSMessage(data);
|
|
270
|
+
} catch (e) {
|
|
271
|
+
console.error('[WS] Parse error:', e.message);
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
this.ws.on('close', () => {
|
|
276
|
+
console.log('[WS] Disconnected');
|
|
277
|
+
this.connected = false;
|
|
278
|
+
if (this._pingTimer) { clearInterval(this._pingTimer); this._pingTimer = null; }
|
|
279
|
+
this._scheduleReconnect();
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
this.ws.on('error', (err) => {
|
|
283
|
+
console.error('[WS] Error:', err.message);
|
|
284
|
+
this.connected = false;
|
|
285
|
+
});
|
|
286
|
+
} catch (e) {
|
|
287
|
+
console.error('[WS] Connect error:', e.message);
|
|
288
|
+
this._scheduleReconnect();
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
_handleWSMessage(data) {
|
|
293
|
+
const type = data.type;
|
|
294
|
+
|
|
295
|
+
if (type === 'online_ack') {
|
|
296
|
+
this.connected = true;
|
|
297
|
+
this._backoff = 1000;
|
|
298
|
+
console.log('[WS] Authenticated as', data.nodeId);
|
|
299
|
+
// Notify reconnect handlers so ChatManager can fetch missed messages
|
|
300
|
+
const reconnectHandlers = this._messageHandlers['_reconnected'] || [];
|
|
301
|
+
for (const handler of reconnectHandlers) {
|
|
302
|
+
try { handler(data); } catch (e) { console.error('[WS] Reconnect handler error:', e); }
|
|
303
|
+
}
|
|
304
|
+
// Don't return here — let handlers (e.g. unread_counts) process too
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (type === 'error') {
|
|
308
|
+
console.error('[WS] Server error:', data.message || data.code);
|
|
309
|
+
// Don't return — let handlers see the error too
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Dispatch to registered handlers
|
|
313
|
+
const handlers = this._messageHandlers[type] || [];
|
|
314
|
+
for (const handler of handlers) {
|
|
315
|
+
try {
|
|
316
|
+
const result = handler(data);
|
|
317
|
+
if (result && typeof result.catch === 'function') {
|
|
318
|
+
result.catch(e => console.error(`[WS] Async handler error for ${type}:`, e.message));
|
|
319
|
+
}
|
|
320
|
+
} catch (e) { console.error(`[WS] Handler error for ${type}:`, e); }
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Wildcard handlers
|
|
324
|
+
const wildcards = this._messageHandlers['*'] || [];
|
|
325
|
+
for (const handler of wildcards) {
|
|
326
|
+
try { handler(data); } catch (e) { console.error(`[WS] Wildcard handler error:`, e); }
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
onMessage(type, handler) {
|
|
331
|
+
if (!this._messageHandlers[type]) this._messageHandlers[type] = [];
|
|
332
|
+
this._messageHandlers[type].push(handler);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
sendWS(data) {
|
|
336
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return false;
|
|
337
|
+
this.ws.send(JSON.stringify(data));
|
|
338
|
+
return true;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
_scheduleReconnect() {
|
|
342
|
+
if (this._reconnectTimer) return;
|
|
343
|
+
this._reconnectTimer = setTimeout(() => {
|
|
344
|
+
this._reconnectTimer = null;
|
|
345
|
+
console.log(`[WS] Reconnecting (backoff ${this._backoff}ms)...`);
|
|
346
|
+
this._backoff = Math.min(this._backoff * 2, 60000);
|
|
347
|
+
this.connectWS();
|
|
348
|
+
}, this._backoff);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Start the server client: authenticate and connect WebSocket
|
|
353
|
+
*/
|
|
354
|
+
async start(agentId) {
|
|
355
|
+
try {
|
|
356
|
+
await this.ensureAuth(agentId);
|
|
357
|
+
this.connectWS();
|
|
358
|
+
this._running = true;
|
|
359
|
+
console.log('[ServerClient] Started for agent:', agentId);
|
|
360
|
+
} catch (e) {
|
|
361
|
+
console.error('[ServerClient] Start failed:', e.message);
|
|
362
|
+
this._scheduleReconnect();
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Switch to a different agent
|
|
368
|
+
*/
|
|
369
|
+
async switchAgent(agentId) {
|
|
370
|
+
this.disconnect();
|
|
371
|
+
await this.start(agentId);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
disconnect() {
|
|
375
|
+
if (this.ws) {
|
|
376
|
+
// SPEC 合规: offline 消息必须带 nodeId 字段
|
|
377
|
+
// 见 aicqSDK/SPEC.md 第 215-219 行
|
|
378
|
+
try {
|
|
379
|
+
const nodeId = this.currentAgentId || (this.identity && this.identity.currentAgentId) || '';
|
|
380
|
+
this.ws.send(JSON.stringify({ type: 'offline', nodeId }));
|
|
381
|
+
} catch (e) {}
|
|
382
|
+
this.ws.close();
|
|
383
|
+
this.ws = null;
|
|
384
|
+
}
|
|
385
|
+
this.connected = false;
|
|
386
|
+
if (this._reconnectTimer) {
|
|
387
|
+
clearTimeout(this._reconnectTimer);
|
|
388
|
+
this._reconnectTimer = null;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
stop() {
|
|
393
|
+
this._running = false;
|
|
394
|
+
this.disconnect();
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Get the current JWT access token for a given agent
|
|
399
|
+
*/
|
|
400
|
+
getAccessToken(agentId) {
|
|
401
|
+
return this.jwtToken || '';
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
module.exports = ServerClient;
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "aicq-chat",
|
|
3
|
+
"name": "AICQ Encrypted Chat",
|
|
4
|
+
"version": "3.16.0",
|
|
5
|
+
"description": "End-to-end encrypted chat channel via AICQ protocol — in-process Channel plugin using OpenClaw Channel SDK",
|
|
6
|
+
"entry": "index.js",
|
|
7
|
+
"activation": {
|
|
8
|
+
"onStartup": true
|
|
9
|
+
},
|
|
10
|
+
"channels": [
|
|
11
|
+
"aicq-chat"
|
|
12
|
+
],
|
|
13
|
+
"channelConfigs": {
|
|
14
|
+
"aicq-chat": {
|
|
15
|
+
"label": "AICQ Encrypted Chat",
|
|
16
|
+
"description": "End-to-end encrypted chat channel via AICQ protocol",
|
|
17
|
+
"schema": {
|
|
18
|
+
"type": "object",
|
|
19
|
+
"additionalProperties": false,
|
|
20
|
+
"properties": {
|
|
21
|
+
"accountId": {
|
|
22
|
+
"type": "string",
|
|
23
|
+
"description": "绑定到 OpenClaw 智能体的账户 ID",
|
|
24
|
+
"default": "{{agent.id}}"
|
|
25
|
+
},
|
|
26
|
+
"autoAcceptFriends": {
|
|
27
|
+
"type": "boolean",
|
|
28
|
+
"default": true,
|
|
29
|
+
"description": "是否自动接受好友请求"
|
|
30
|
+
},
|
|
31
|
+
"autoAddFriends": {
|
|
32
|
+
"type": "array",
|
|
33
|
+
"items": {
|
|
34
|
+
"type": "string"
|
|
35
|
+
},
|
|
36
|
+
"default": [
|
|
37
|
+
"1000000"
|
|
38
|
+
],
|
|
39
|
+
"description": "启动后自动添加为好友的 AICQ 号码列表(如 1000000)"
|
|
40
|
+
},
|
|
41
|
+
"serverUrl": {
|
|
42
|
+
"type": "string",
|
|
43
|
+
"description": "AICQ 服务器地址",
|
|
44
|
+
"default": "https://aicq.me"
|
|
45
|
+
},
|
|
46
|
+
"enabled": {
|
|
47
|
+
"type": "boolean",
|
|
48
|
+
"default": true,
|
|
49
|
+
"description": "Enable this channel account"
|
|
50
|
+
},
|
|
51
|
+
"dmPolicy": {
|
|
52
|
+
"type": "string",
|
|
53
|
+
"default": "allowlist",
|
|
54
|
+
"description": "DM access policy: allowlist (friends only), open, or disabled",
|
|
55
|
+
"enum": [
|
|
56
|
+
"allowlist",
|
|
57
|
+
"open",
|
|
58
|
+
"disabled"
|
|
59
|
+
]
|
|
60
|
+
},
|
|
61
|
+
"allowFrom": {
|
|
62
|
+
"type": "array",
|
|
63
|
+
"items": {
|
|
64
|
+
"type": "string"
|
|
65
|
+
},
|
|
66
|
+
"default": [],
|
|
67
|
+
"description": "允许发消息的好友 ID 列表(dmPolicy 为 allowlist 时生效)"
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
"required": [
|
|
71
|
+
"accountId"
|
|
72
|
+
]
|
|
73
|
+
},
|
|
74
|
+
"uiHints": {
|
|
75
|
+
"accountId": {
|
|
76
|
+
"label": "Account ID",
|
|
77
|
+
"help": "The OpenClaw agent ID to bind as AICQ account"
|
|
78
|
+
},
|
|
79
|
+
"autoAcceptFriends": {
|
|
80
|
+
"label": "Auto Accept Friends",
|
|
81
|
+
"help": "Automatically accept incoming friend requests"
|
|
82
|
+
},
|
|
83
|
+
"autoAddFriends": {
|
|
84
|
+
"label": "Auto Add Friends",
|
|
85
|
+
"help": "Comma-separated AICQ numbers to auto-add as friends on startup (e.g. 1000000)"
|
|
86
|
+
},
|
|
87
|
+
"serverUrl": {
|
|
88
|
+
"label": "AICQ Server URL",
|
|
89
|
+
"help": "The AICQ signaling server URL for WebSocket connections"
|
|
90
|
+
},
|
|
91
|
+
"dmPolicy": {
|
|
92
|
+
"label": "DM Policy",
|
|
93
|
+
"help": "Who can send direct messages: allowlist (friends only), open, or disabled"
|
|
94
|
+
},
|
|
95
|
+
"allowFrom": {
|
|
96
|
+
"label": "Allow From",
|
|
97
|
+
"help": "Friend IDs allowed to send DMs (used when dmPolicy is allowlist)"
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
"configSchema": {
|
|
103
|
+
"type": "object",
|
|
104
|
+
"additionalProperties": false,
|
|
105
|
+
"properties": {
|
|
106
|
+
"serverUrl": {
|
|
107
|
+
"type": "string",
|
|
108
|
+
"description": "AICQ 服务器地址",
|
|
109
|
+
"default": "https://aicq.me"
|
|
110
|
+
},
|
|
111
|
+
"autoAcceptFriends": {
|
|
112
|
+
"type": "boolean",
|
|
113
|
+
"description": "自动接受好友请求",
|
|
114
|
+
"default": true
|
|
115
|
+
},
|
|
116
|
+
"debug": {
|
|
117
|
+
"type": "boolean",
|
|
118
|
+
"description": "Enable verbose debug logging",
|
|
119
|
+
"default": false
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
"uiHints": {
|
|
124
|
+
"serverUrl": {
|
|
125
|
+
"label": "AICQ Server URL",
|
|
126
|
+
"help": "The AICQ signaling server URL for WebSocket connections."
|
|
127
|
+
},
|
|
128
|
+
"autoAcceptFriends": {
|
|
129
|
+
"label": "Auto Accept Friends",
|
|
130
|
+
"help": "Automatically accept incoming friend requests."
|
|
131
|
+
},
|
|
132
|
+
"debug": {
|
|
133
|
+
"label": "Debug Mode",
|
|
134
|
+
"help": "Enable verbose logging for troubleshooting.",
|
|
135
|
+
"advanced": true
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
"contracts": {
|
|
139
|
+
"tools": [
|
|
140
|
+
"chat-friend",
|
|
141
|
+
"chat-send",
|
|
142
|
+
"chat-export-key"
|
|
143
|
+
],
|
|
144
|
+
"gateway": [
|
|
145
|
+
"aicq.status",
|
|
146
|
+
"aicq.friends.list",
|
|
147
|
+
"aicq.friends.add",
|
|
148
|
+
"aicq.friends.remove",
|
|
149
|
+
"aicq.friends.requests",
|
|
150
|
+
"aicq.friends.acceptRequest",
|
|
151
|
+
"aicq.friends.rejectRequest",
|
|
152
|
+
"aicq.sessions.list",
|
|
153
|
+
"aicq.identity.info",
|
|
154
|
+
"aicq.agent.create",
|
|
155
|
+
"aicq.agent.delete",
|
|
156
|
+
"aicq.chat.history",
|
|
157
|
+
"aicq.chat.send",
|
|
158
|
+
"aicq.chat.delete",
|
|
159
|
+
"aicq.chat.userUpload",
|
|
160
|
+
"aicq.chat.userfiles",
|
|
161
|
+
"aicq.chat.streamChunk",
|
|
162
|
+
"aicq.chat.streamEnd",
|
|
163
|
+
"aicq.groups.list",
|
|
164
|
+
"aicq.groups.create",
|
|
165
|
+
"aicq.groups.join",
|
|
166
|
+
"aicq.groups.messages",
|
|
167
|
+
"aicq.groups.silent"
|
|
168
|
+
]
|
|
169
|
+
},
|
|
170
|
+
"runtime": "node",
|
|
171
|
+
"requires": {
|
|
172
|
+
"node": ">=22.0.0",
|
|
173
|
+
"packages": [
|
|
174
|
+
"sql.js",
|
|
175
|
+
"tweetnacl",
|
|
176
|
+
"tweetnacl-util",
|
|
177
|
+
"ws",
|
|
178
|
+
"qrcode",
|
|
179
|
+
"express"
|
|
180
|
+
]
|
|
181
|
+
},
|
|
182
|
+
"capabilities": {
|
|
183
|
+
"tts": {
|
|
184
|
+
"voice": false
|
|
185
|
+
},
|
|
186
|
+
"media": false,
|
|
187
|
+
"threads": false,
|
|
188
|
+
"reactions": false,
|
|
189
|
+
"editing": false,
|
|
190
|
+
"polls": false,
|
|
191
|
+
"location": false
|
|
192
|
+
}
|
|
193
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "aicq-openclaw",
|
|
3
|
+
"version": "3.16.3",
|
|
4
|
+
"description": "AICQ End-to-end Encrypted Chat Channel Plugin for OpenClaw — In-process Channel SDK architecture with friend management, group chat, file transfer, and AI agent communication",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"aicq-plugin": "cli.cjs"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"index.js",
|
|
12
|
+
"setup-entry.js",
|
|
13
|
+
"cli.cjs",
|
|
14
|
+
"postinstall.cjs",
|
|
15
|
+
"src/",
|
|
16
|
+
"lib/",
|
|
17
|
+
"public/",
|
|
18
|
+
"openclaw.plugin.json",
|
|
19
|
+
"SKILL.md",
|
|
20
|
+
"README.md"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"start": "node index.js",
|
|
24
|
+
"postinstall": "node postinstall.cjs",
|
|
25
|
+
"install-deps": "npm install"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"aicq",
|
|
29
|
+
"chat",
|
|
30
|
+
"encrypted",
|
|
31
|
+
"e2ee",
|
|
32
|
+
"openclaw",
|
|
33
|
+
"plugin",
|
|
34
|
+
"channel",
|
|
35
|
+
"ai-agent",
|
|
36
|
+
"messaging",
|
|
37
|
+
"p2p"
|
|
38
|
+
],
|
|
39
|
+
"author": "samaidev",
|
|
40
|
+
"license": "MIT",
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "git+https://github.com/samaidev/aicq.git",
|
|
44
|
+
"directory": "pluginAICQ/openclaw-plugin"
|
|
45
|
+
},
|
|
46
|
+
"bugs": {
|
|
47
|
+
"url": "https://github.com/samaidev/aicq/issues"
|
|
48
|
+
},
|
|
49
|
+
"homepage": "https://aicq.me",
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"express": "^5.1.0",
|
|
52
|
+
"marked": "^12.0.0",
|
|
53
|
+
"multer": "^1.4.5-lts.1",
|
|
54
|
+
"node-fetch": "^2.7.0",
|
|
55
|
+
"qrcode": "^1.5.3",
|
|
56
|
+
"sql.js": "^1.14.1",
|
|
57
|
+
"tweetnacl": "^1.0.3",
|
|
58
|
+
"tweetnacl-util": "^0.15.1",
|
|
59
|
+
"ws": "^8.16.0"
|
|
60
|
+
},
|
|
61
|
+
"peerDependencies": {
|
|
62
|
+
"openclaw": ">=2026.8.1"
|
|
63
|
+
},
|
|
64
|
+
"peerDependenciesMeta": {
|
|
65
|
+
"openclaw": {
|
|
66
|
+
"optional": true
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
"engines": {
|
|
70
|
+
"node": ">=22.0.0"
|
|
71
|
+
},
|
|
72
|
+
"openclaw": {
|
|
73
|
+
"extensions": [
|
|
74
|
+
"./index.js"
|
|
75
|
+
],
|
|
76
|
+
"setupEntry": "./setup-entry.js",
|
|
77
|
+
"compat": {
|
|
78
|
+
"pluginApi": ">=2026.8.1",
|
|
79
|
+
"minGatewayVersion": "2026.8.1"
|
|
80
|
+
},
|
|
81
|
+
"channel": {
|
|
82
|
+
"id": "aicq-chat",
|
|
83
|
+
"label": "AICQ Encrypted Chat",
|
|
84
|
+
"blurb": "端到端加密即时通讯频道,基于 NaCl (X25519 + XSalsa20-Poly1305)",
|
|
85
|
+
"icon": "🔐"
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
"optionalDependencies": {
|
|
89
|
+
"aicq-sdk": ">=1.0.0"
|
|
90
|
+
}
|
|
91
|
+
}
|