@voko/lite 0.3.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.
Files changed (62) hide show
  1. package/package.json +32 -0
  2. package/scripts/build-native.js +72 -0
  3. package/src/bankHeadOffices.js +20543 -0
  4. package/src/channels/email.js +35 -0
  5. package/src/channels/feishu.js +31 -0
  6. package/src/channels/qq-email.js +30 -0
  7. package/src/channels/registry.js +279 -0
  8. package/src/channels/telegram.js +28 -0
  9. package/src/channels/voko-email.js +7 -0
  10. package/src/channels/wechat.js +35 -0
  11. package/src/cli.js +120 -0
  12. package/src/context.js +164 -0
  13. package/src/core/access-control-api.js +150 -0
  14. package/src/core/access-control.js +56 -0
  15. package/src/core/agent-registration.js +319 -0
  16. package/src/core/api-signature.js +33 -0
  17. package/src/core/audit.js +133 -0
  18. package/src/core/database.js +1409 -0
  19. package/src/core/did-auth.js +54 -0
  20. package/src/core/hermes-paths.js +57 -0
  21. package/src/core/invitation.js +49 -0
  22. package/src/core/lite-bus.js +16 -0
  23. package/src/core/llm-client.js +1032 -0
  24. package/src/core/messenger.js +456 -0
  25. package/src/core/notifier.js +99 -0
  26. package/src/core/offline-sync.js +150 -0
  27. package/src/core/payment.js +285 -0
  28. package/src/core/publish-agent.js +166 -0
  29. package/src/core/register-capabilities.js +119 -0
  30. package/src/core/search-capabilities.js +136 -0
  31. package/src/core/send-message.js +85 -0
  32. package/src/core/set-agent-status.js +65 -0
  33. package/src/core/update-agent-profile.js +102 -0
  34. package/src/core/worker-manager.js +332 -0
  35. package/src/endpoints.json +21 -0
  36. package/src/index.js +712 -0
  37. package/src/mcp/CLAUDE_TEST.md +82 -0
  38. package/src/mcp/FULL_TEST.md +139 -0
  39. package/src/mcp/TEST.md +124 -0
  40. package/src/mcp/TEST_STEPS.md +75 -0
  41. package/src/mcp/server.js +612 -0
  42. package/src/mcp/tools.js +1367 -0
  43. package/src/mcp/transport/http.js +95 -0
  44. package/src/mcp/transport/stdio.js +20 -0
  45. package/src/preload.js +27 -0
  46. package/src/server/agent-email-api.js +120 -0
  47. package/src/server/agent-manager.js +580 -0
  48. package/src/server/email-handler.js +329 -0
  49. package/src/server/feishu-handler.js +249 -0
  50. package/src/server/hermes-api-client.js +166 -0
  51. package/src/server/hermes-discovery.js +80 -0
  52. package/src/server/hermes-handler.js +287 -0
  53. package/src/server/openclaw-handler-cli.js +131 -0
  54. package/src/server/openclaw-websocket-handler.js +1290 -0
  55. package/src/server/oss.js +186 -0
  56. package/src/server/owner-intervention-notifier.js +320 -0
  57. package/src/server/release-page.html +204 -0
  58. package/src/server/telegram-handler.js +208 -0
  59. package/src/server/voko-email-handler.js +68 -0
  60. package/src/server/wechat-handler.js +439 -0
  61. package/src/workers/agent-worker.js +378 -0
  62. package/src/workers/message-content.js +51 -0
@@ -0,0 +1,439 @@
1
+ /**
2
+ * VOKO Desktop - WeChat (iLink) 消息处理器
3
+ * 通过长轮询接收 WeChat 消息事件
4
+ *
5
+ * 用途:
6
+ * - 当 agent 需要主人介入时,通过微信通知主人
7
+ * - 主人回复后,将消息转发给对应 agent 的 session
8
+ */
9
+
10
+ const https = require('https');
11
+
12
+ class WechatHandler {
13
+ constructor(config, options = {}) {
14
+ // iLink 配置
15
+ this.baseUrl = config.baseUrl || 'https://ilinkai.weixin.qq.com';
16
+ this.botToken = config.botToken;
17
+ this.ownerUserId = config.ownerUserId; // 主人微信 user_id
18
+ this.pollInterval = config.pollInterval || 35000; // 长轮询超时 35秒
19
+
20
+ // 回调函数
21
+ this.onOwnerReply = options.onOwnerReply || null;
22
+ this.getInterventionByParentMsgId = options.getInterventionByParentMsgId || null;
23
+ this.getLatestPendingIntervention = options.getLatestPendingIntervention || null;
24
+ this.getPendingWechatCount = options.getPendingWechatCount || null;
25
+ this.isEnabled = options.isEnabled || (() => true);
26
+ this.onSessionExpired = options.onSessionExpired || null;
27
+
28
+ // 轮询状态
29
+ this.getUpdatesBuf = '';
30
+ this.pollTimer = null;
31
+ this.isPolling = false;
32
+ this.enabled = false;
33
+ this.pollIntervalMs = config.pollIntervalMs || 1000; // 默认1秒
34
+
35
+ // 扫码登录状态
36
+ this.qrCode = null;
37
+ this.qrCodeToken = null; // 保存 token 用于轮询
38
+ this.qrCodeStatus = null;
39
+ this.accountId = null;
40
+ }
41
+
42
+ async start() {
43
+ if (this.enabled) {
44
+ console.log('[Wechat] 已经启动');
45
+ return;
46
+ }
47
+
48
+ if (!this.botToken || !this.ownerUserId) {
49
+ console.log('[Wechat] 缺少 botToken 或 ownerUserId,跳过启动');
50
+ return;
51
+ }
52
+
53
+ this.enabled = true;
54
+ console.log('[Wechat] 启动长轮询,超时', this.pollInterval, 'ms');
55
+ this.scheduleNextPoll();
56
+ }
57
+
58
+ scheduleNextPoll() {
59
+ if (!this.enabled) return;
60
+
61
+ this.pollTimer = setTimeout(async () => {
62
+ await this.pollOnce();
63
+ this.scheduleNextPoll();
64
+ }, this.pollIntervalMs); // 使用配置的时间间隔
65
+ }
66
+
67
+ async pollOnce() {
68
+ if (!this.isEnabled()) {
69
+ return;
70
+ }
71
+
72
+ const pendingCount = this.getPendingWechatCount ? this.getPendingWechatCount() : 1;
73
+ if (pendingCount === 0) {
74
+ return;
75
+ }
76
+
77
+ try {
78
+ this.isPolling = true;
79
+ await this.fetchUpdates();
80
+ } catch (err) {
81
+ console.error('[Wechat] 长轮询错误:', err.message);
82
+ } finally {
83
+ this.isPolling = false;
84
+ }
85
+ }
86
+
87
+ async fetchUpdates() {
88
+ return new Promise((resolve, reject) => {
89
+ const postData = JSON.stringify({
90
+ get_updates_buf: this.getUpdatesBuf || '',
91
+ base_info: {
92
+ channel_version: '1.0.2'
93
+ }
94
+ });
95
+
96
+ const url = new URL(`${this.baseUrl}/ilink/bot/getupdates`);
97
+ const options = {
98
+ hostname: url.hostname,
99
+ port: 443,
100
+ path: url.pathname,
101
+ method: 'POST',
102
+ headers: {
103
+ 'Content-Type': 'application/json',
104
+ 'Authorization': `Bearer ${this.botToken}`,
105
+ 'AuthorizationType': 'ilink_bot_token',
106
+ 'Content-Length': Buffer.byteLength(postData)
107
+ }
108
+ };
109
+
110
+ const req = https.request(options, (res) => {
111
+ let data = '';
112
+ res.on('data', chunk => data += chunk);
113
+ res.on('end', () => {
114
+ console.log('[Wechat] getupdates response length:', data.length, 'content:', data.substring(0, 300));
115
+ try {
116
+ const json = JSON.parse(data);
117
+ // 如果没有 ret 字段,检查是否有 msgs 或 sync_buf(iLink 成功响应)
118
+ if (json.ret === 0 || (json.ret === undefined && (json.msgs || json.sync_buf))) {
119
+ if (json.msgs && json.msgs.length > 0) {
120
+ console.log('[Wechat] 收到消息, msgs count:', json.msgs.length);
121
+ this.getUpdatesBuf = json.get_updates_buf || '';
122
+ this.processUpdates(json.msgs);
123
+ } else {
124
+ // 更新游标,即使没有消息
125
+ this.getUpdatesBuf = json.get_updates_buf || this.getUpdatesBuf;
126
+ }
127
+ resolve();
128
+ } else if (json.ret == null) {
129
+ // ret 为空、undefined、null 都视为心跳/空响应,继续轮询
130
+ console.log('[Wechat] 长轮询空响应或心跳,继续轮询');
131
+ resolve();
132
+ } else if (json.ret === -14 || json.ret === -2) {
133
+ console.log('[Wechat] 会话过期或无效,准备重新登录, ret:', json.ret, 'errmsg:', json.errmsg);
134
+ if (this.onSessionExpired) {
135
+ this.onSessionExpired();
136
+ }
137
+ resolve(); // 不拒绝,继续下一次轮询
138
+ } else {
139
+ console.error('[Wechat] 长轮询 API 错误, ret:', json.ret, 'errmsg:', json.errmsg);
140
+ resolve(); // 其他错误也继续轮询
141
+ }
142
+ } catch (e) {
143
+ reject(e);
144
+ }
145
+ });
146
+ });
147
+
148
+ req.on('error', (err) => {
149
+ console.error('[Wechat] 长轮询请求失败:', err.message);
150
+ reject(err);
151
+ });
152
+
153
+ // 设置超时,避免长时间挂起
154
+ req.setTimeout(40000, () => {
155
+ req.destroy();
156
+ console.log('[Wechat] 长轮询超时,40秒');
157
+ resolve(); // 超时不算错误,继续下一次轮询
158
+ });
159
+
160
+ req.write(postData);
161
+ req.end();
162
+ });
163
+ }
164
+
165
+ // 检查是否是会话过期错误(需要重新登录)
166
+ isSessionExpiredError(ret, errmsg) {
167
+ // -14 = session expired, -2 = 也可能是会话问题
168
+ return ret === -14 || (ret === -2 && errmsg === 'unknown error');
169
+ }
170
+
171
+ async processUpdates(msgs) {
172
+ for (const msg of msgs) {
173
+ if (msg.message_type === 1) { // USER 消息
174
+ await this.handleMessage(msg);
175
+ }
176
+ }
177
+ }
178
+
179
+ async handleMessage(msg) {
180
+ const fromUserId = msg.from_user_id;
181
+ const messageId = msg.message_id?.toString();
182
+ const text = this.extractText(msg);
183
+ const contextToken = msg.context_token;
184
+
185
+ console.log('[Wechat] 收到消息, context_token:', contextToken, 'messageId:', messageId, 'fromUserId:', fromUserId, 'text:', text);
186
+
187
+ // 只处理来自主人的消息
188
+ if (fromUserId !== this.ownerUserId) {
189
+ return;
190
+ }
191
+
192
+ if (contextToken) {
193
+ await this.handleReply(contextToken, text, messageId);
194
+ } else {
195
+ console.log('[Wechat] 消息无 context_token,跳过处理');
196
+ }
197
+ }
198
+
199
+ extractText(msg) {
200
+ if (!msg.item_list) return '';
201
+ for (const item of msg.item_list) {
202
+ if (item.type === 1 && item.text_item) {
203
+ return item.text_item.text || '';
204
+ }
205
+ }
206
+ return '';
207
+ }
208
+
209
+ async handleReply(contextToken, content, replyMessageId) {
210
+ if (this.getInterventionByParentMsgId) {
211
+ // 1. 优先尝试 context_token 精确匹配
212
+ let intervention = this.getInterventionByParentMsgId(contextToken);
213
+ if (intervention) {
214
+ console.log('[Wechat] context_token 精确匹配到干预记录:', intervention.id);
215
+ if (this.onOwnerReply) {
216
+ this.onOwnerReply(intervention, content, replyMessageId);
217
+ }
218
+ return;
219
+ }
220
+
221
+ // 2. Fallback: ownerUserId + 最新 pending 记录匹配
222
+ // iLink 的 context_token 是服务器生成的,与我们的 client_id 不同
223
+ // 因此无法精确匹配时,使用 fallback 机制
224
+ console.log('[Wechat] context_token 未匹配,尝试 fallback 匹配');
225
+ if (this.getLatestPendingIntervention) {
226
+ intervention = this.getLatestPendingIntervention();
227
+ if (intervention) {
228
+ console.log('[Wechat] fallback 匹配到最新 pending 记录:', intervention.id);
229
+ if (this.onOwnerReply) {
230
+ this.onOwnerReply(intervention, content, replyMessageId);
231
+ }
232
+ return;
233
+ }
234
+ console.log('[Wechat] 无 pending 记录,跳过');
235
+ } else {
236
+ console.log('[Wechat] getLatestPendingIntervention 未提供,跳过');
237
+ }
238
+ }
239
+ }
240
+
241
+ /**
242
+ * 发送消息给微信用户(带记录,用于后续匹配)
243
+ */
244
+ async sendMessageToOwnerWithTracking(content, visitorId, sessionKey) {
245
+ return new Promise((resolve, reject) => {
246
+ const clientId = `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
247
+
248
+ const postData = JSON.stringify({
249
+ msg: {
250
+ from_user_id: '',
251
+ to_user_id: this.ownerUserId,
252
+ client_id: clientId,
253
+ message_type: 2, // BOT
254
+ message_state: 2, // FINISH
255
+ context_token: '', // 主动推送时为空
256
+ item_list: [
257
+ { type: 1, text_item: { text: content } }
258
+ ]
259
+ },
260
+ base_info: {
261
+ channel_version: '1.0.2'
262
+ }
263
+ });
264
+
265
+ const url = new URL(`${this.baseUrl}/ilink/bot/sendmessage`);
266
+ const options = {
267
+ hostname: url.hostname,
268
+ port: 443,
269
+ path: url.pathname,
270
+ method: 'POST',
271
+ headers: {
272
+ 'Content-Type': 'application/json',
273
+ 'Authorization': `Bearer ${this.botToken}`,
274
+ 'AuthorizationType': 'ilink_bot_token',
275
+ 'Content-Length': Buffer.byteLength(postData)
276
+ }
277
+ };
278
+
279
+ const req = https.request(options, (res) => {
280
+ let data = '';
281
+ res.on('data', chunk => data += chunk);
282
+ res.on('end', () => {
283
+ try {
284
+ const json = JSON.parse(data);
285
+ if (json.ret === 0) {
286
+ console.log('[Wechat] 发送消息给主人成功, clientId:', clientId);
287
+ resolve({ messageId: clientId, sentMessageId: clientId });
288
+ } else if (json.ret === -2) {
289
+ // 会话过期/无效,标记为永久失败,不重试
290
+ console.error('[Wechat] 发送消息失败, ret:', json.ret, 'errmsg:', json.errmsg, '(会话过期,不重试)');
291
+ reject(new Error('SESSION_EXPIRED: ' + (json.errmsg || 'session expired')));
292
+ } else {
293
+ console.error('[Wechat] 发送消息失败, ret:', json.ret, 'errmsg:', json.errmsg);
294
+ reject(new Error(json.errmsg || 'Wechat API error'));
295
+ }
296
+ } catch (e) {
297
+ reject(e);
298
+ }
299
+ });
300
+ });
301
+
302
+ req.on('error', (err) => {
303
+ console.error('[Wechat] 发送请求失败:', err.message);
304
+ reject(new Error('请求失败: ' + err.message));
305
+ });
306
+ req.write(postData);
307
+ req.end();
308
+ });
309
+ }
310
+
311
+ /**
312
+ * 获取登录二维码
313
+ */
314
+ async fetchQrCode() {
315
+ return new Promise((resolve, reject) => {
316
+ const url = new URL(`${this.baseUrl}/ilink/bot/get_bot_qrcode`);
317
+ url.searchParams.set('bot_type', '3');
318
+
319
+ const postData = JSON.stringify({
320
+ local_token_list: this.botToken ? [this.botToken] : []
321
+ });
322
+
323
+ const options = {
324
+ hostname: url.hostname,
325
+ port: 443,
326
+ path: url.pathname + url.search,
327
+ method: 'POST',
328
+ headers: {
329
+ 'Content-Type': 'application/json',
330
+ 'Authorization': `Bearer ${this.botToken || ''}`,
331
+ 'AuthorizationType': 'ilink_bot_token',
332
+ 'Content-Length': Buffer.byteLength(postData)
333
+ }
334
+ };
335
+
336
+ const req = https.request(options, (res) => {
337
+ let data = '';
338
+ res.on('data', chunk => data += chunk);
339
+ res.on('end', () => {
340
+ try {
341
+ const json = JSON.parse(data);
342
+ // qrcode_img_content 是实际的图片 URL 或 base64 数据
343
+ // qrcode 只是 token,不是图片
344
+ if (json.ret === 0 && (json.qrcode_img_content || json.qrcode)) {
345
+ this.qrCodeToken = json.qrcode; // 保存 token
346
+ this.qrCode = json.qrcode_img_content || json.qrcode; // 返回 URL 或 token
347
+ resolve(this.qrCode);
348
+ } else {
349
+ reject(new Error(json.errmsg || '获取二维码失败'));
350
+ }
351
+ } catch (e) {
352
+ reject(e);
353
+ }
354
+ });
355
+ });
356
+
357
+ req.on('error', reject);
358
+ req.write(postData);
359
+ req.end();
360
+ });
361
+ }
362
+
363
+ /**
364
+ * 轮询扫码状态
365
+ */
366
+ async pollQrCodeStatus() {
367
+ if (!this.qrCodeToken) {
368
+ return { status: 'expired' };
369
+ }
370
+
371
+ return new Promise((resolve, reject) => {
372
+ const url = new URL(`${this.baseUrl}/ilink/bot/get_qrcode_status`);
373
+ url.searchParams.set('qrcode', this.qrCodeToken);
374
+
375
+ const options = {
376
+ hostname: url.hostname,
377
+ port: 443,
378
+ path: url.pathname + url.search,
379
+ method: 'GET',
380
+ headers: {
381
+ 'Authorization': `Bearer ${this.botToken || ''}`,
382
+ 'AuthorizationType': 'ilink_bot_token'
383
+ }
384
+ };
385
+
386
+ const req = https.request(options, (res) => {
387
+ let data = '';
388
+ res.on('data', chunk => data += chunk);
389
+ res.on('end', () => {
390
+ try {
391
+ const json = JSON.parse(data);
392
+ if (json.ret === 0) {
393
+ this.qrCodeStatus = json.status;
394
+ if (json.status === 'confirmed' && json.bot_token) {
395
+ this.botToken = json.bot_token;
396
+ this.accountId = json.ilink_bot_id || this.accountId;
397
+ this.ownerUserId = json.ilink_user_id || this.ownerUserId;
398
+ resolve({
399
+ status: 'confirmed',
400
+ bot_token: json.bot_token,
401
+ owner_user_id: json.ilink_user_id,
402
+ account_id: json.ilink_bot_id
403
+ });
404
+ } else {
405
+ resolve(json);
406
+ }
407
+ } else {
408
+ resolve({ status: 'expired' });
409
+ }
410
+ } catch (e) {
411
+ reject(e);
412
+ }
413
+ });
414
+ });
415
+
416
+ req.on('error', reject);
417
+ req.end();
418
+ });
419
+ }
420
+
421
+ stop() {
422
+ this.enabled = false;
423
+ if (this.pollTimer) {
424
+ clearTimeout(this.pollTimer);
425
+ this.pollTimer = null;
426
+ }
427
+ console.log('[Wechat] 轮询已停止');
428
+ }
429
+
430
+ updateConfig(config) {
431
+ if (config.baseUrl) this.baseUrl = config.baseUrl;
432
+ if (config.botToken) this.botToken = config.botToken;
433
+ if (config.ownerUserId) this.ownerUserId = config.ownerUserId;
434
+ if (config.pollInterval) this.pollInterval = config.pollInterval;
435
+ console.log('[Wechat] 配置已更新');
436
+ }
437
+ }
438
+
439
+ module.exports = WechatHandler;