@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,378 @@
1
+ /**
2
+ * Agent Worker - 每个 Agent 的独立 WuKongIM 连接进程
3
+ *
4
+ * 通过 fork 启动,每个 worker 有独立的 V8 实例和 WKSDK.shared() 实例
5
+ * 用法: node agent-worker.js <agentId> <configJson>
6
+ */
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+
11
+ // Worker 专用错误日志(独立于主进程日志,避免编码问题)
12
+ // 优先使用主进程传入的 WORKER_LOG_PATH 环境变量,兼容旧版硬编码路径
13
+ const workerLogPath = process.env.WORKER_LOG_PATH || path.join(require('os').homedir(), 'AppData', 'Roaming', 'voko', 'agent-worker.log');
14
+ function formatTime() {
15
+ const now = new Date();
16
+ const y = now.getFullYear();
17
+ const m = String(now.getMonth() + 1).padStart(2, '0');
18
+ const d = String(now.getDate()).padStart(2, '0');
19
+ const h = String(now.getHours()).padStart(2, '0');
20
+ const min = String(now.getMinutes()).padStart(2, '0');
21
+ const s = String(now.getSeconds()).padStart(2, '0');
22
+ return `${y}-${m}-${d} ${h}:${min}:${s}`;
23
+ }
24
+ function workerLog(...args) {
25
+ const line = `[${formatTime()}] ` + args.map(a => typeof a === 'object' ? JSON.stringify(a) : String(a)).join(' ') + '\n';
26
+ try { fs.appendFileSync(workerLogPath, line); } catch (e) { /* ignore */ }
27
+ console.log(...args);
28
+ }
29
+ function workerErr(...args) {
30
+ const line = `[${formatTime()}] [ERR] ` + args.map(a => typeof a === 'object' ? JSON.stringify(a) : String(a)).join(' ') + '\n';
31
+ try { fs.appendFileSync(workerLogPath, line); } catch (e) { /* ignore */ }
32
+ console.error(...args);
33
+ }
34
+
35
+ // 获取命令行参数
36
+ const agentId = process.argv[2];
37
+ let config = {};
38
+ try {
39
+ config = JSON.parse(process.argv[3] || '{}');
40
+ } catch (e) {
41
+ workerErr(`[${agentId || 'unknown'}] 解析 config JSON 失败:`, e.message, '原始参数:', process.argv[3]);
42
+ process.exit(1);
43
+ }
44
+
45
+ const { uid, token, serverUrl } = config;
46
+
47
+
48
+ if (!agentId || !uid || !token || !serverUrl) {
49
+ workerErr(`[${agentId || 'unknown'}] Worker 启动参数不足`, { agentId, uid, token, serverUrl });
50
+ process.exit(1);
51
+ }
52
+
53
+ // Node.js 环境没有全局 WebSocket,用 ws 模块 polyfill
54
+ if (typeof WebSocket === 'undefined') {
55
+ try {
56
+ const WS = require('ws');
57
+ global.WebSocket = WS;
58
+ } catch (err) {
59
+ workerErr(`[${agentId}] 加载 ws 模块失败:`, err.message, err.stack);
60
+ process.exit(1);
61
+ }
62
+ } else {
63
+ }
64
+
65
+ // 加载 SDK
66
+ let WKSDK, MessageText, MessageImage, Channel, ChannelTypePerson;
67
+ let MessageFile;
68
+ try {
69
+ const wukongim = require('wukongimjssdk');
70
+ WKSDK = wukongim.WKSDK;
71
+ MessageText = wukongim.MessageText;
72
+ MessageImage = wukongim.MessageImage;
73
+ Channel = wukongim.Channel;
74
+ ChannelTypePerson = wukongim.ChannelTypePerson;
75
+ try {
76
+ MessageFile = require('./message-content').MessageFile;
77
+ } catch (err) {
78
+ workerErr(`[${agentId}] 加载 MessageFile 失败:`, err.message);
79
+ }
80
+ } catch (err) {
81
+ workerErr(`[${agentId}] 加载 wukongimjssdk 失败:`, err.message, err.stack);
82
+ process.exit(1);
83
+ }
84
+
85
+ // 初始化 SDK
86
+ let sdk;
87
+ try {
88
+ sdk = WKSDK.shared();
89
+ sdk.config.uid = uid;
90
+ sdk.config.token = token;
91
+ sdk.config.addr = serverUrl;
92
+ sdk.config.autoReconnect = true; // SDK 内置自动重连,退出手动管理避免冲突
93
+
94
+ // 注册自定义文件消息类型(contentType=4),确保收发都能正确编解码
95
+ if (MessageFile) {
96
+ try {
97
+ WKSDK.shared().messageContentManager.register(4, MessageFile);
98
+ } catch (err) {
99
+ workerErr(`[${agentId}] 注册 MessageFile 失败:`, err.message);
100
+ }
101
+ }
102
+ } catch (err) {
103
+ workerErr(`[${agentId}] WKSDK 初始化失败:`, err.message, err.stack);
104
+ process.exit(1);
105
+ }
106
+
107
+ // 状态映射(与 SDK ConnectStatus 枚举一致)
108
+ // ConnectStatus: 0=Disconnect 1=Connected 2=Connecting 3=ConnectFail 4=ConnectKick
109
+ const statusMap = {
110
+ 0: 'disconnected',
111
+ 1: 'connected',
112
+ 2: 'connecting',
113
+ 3: 'connectFail',
114
+ 4: 'kicked'
115
+ };
116
+
117
+ // 连接状态监听(仅上报,重连由 SDK 内置的 autoReconnect 处理)
118
+ sdk.connectManager.addConnectStatusListener((status) => {
119
+ const statusText = statusMap[status] || 'unknown';
120
+ workerLog(`[${agentId}] 连接状态: ${statusText}`);
121
+ process.send({
122
+ type: 'status',
123
+ agentId,
124
+ status: statusText,
125
+ statusCode: status
126
+ });
127
+ });
128
+
129
+ // 消息监听
130
+ sdk.chatManager.addMessageListener((rawMsg) => {
131
+ // 如果是自己发的消息,跳过(避免回声)
132
+ if (rawMsg.fromUID === uid) {
133
+ return;
134
+ }
135
+
136
+ // contentType 99 是命令消息,跳过不处理
137
+ if (rawMsg.contentType === 99) {
138
+ return;
139
+ }
140
+
141
+
142
+ // 提取消息字段
143
+ const fromUid = rawMsg.fromUID;
144
+ const toUid = rawMsg.toUID || uid;
145
+ const channelId = rawMsg.channel?.channelID || rawMsg.channelID;
146
+ const channelType = rawMsg.channelType ?? 1;
147
+ const msgId = rawMsg.messageID || 'unknown';
148
+ const timestamp = rawMsg.timestamp || Date.now() / 1000;
149
+
150
+ // 根据 contentType 提取内容
151
+ let content = '';
152
+ let contentObj = rawMsg.content;
153
+ // 如果 content 是 { contentObj: { url, name, size, type } } 结构,提取出来
154
+ if (rawMsg.content?.contentObj) {
155
+ contentObj = rawMsg.content.contentObj;
156
+ }
157
+ const contentType = rawMsg.contentType ?? 1;
158
+
159
+ // 检测是否是文件(有 name, url, size 字段的对象)
160
+ const isFileObject = contentObj?.name && contentObj?.url;
161
+
162
+ if (contentType === 4 || isFileObject) {
163
+ // 文件消息:统一提取 name, url, size, type 用于图形化显示
164
+ content = JSON.stringify({
165
+ name: contentObj?.name || contentObj?.fileName || '',
166
+ url: contentObj?.url || '',
167
+ size: contentObj?.size || 0,
168
+ type: contentObj?.type || ''
169
+ });
170
+ } else if (contentType === 1) {
171
+ // 文本消息 - 支持 text 和 content 字段
172
+ content = contentObj?.text || contentObj?.content || '';
173
+ } else if (contentType === 2) {
174
+ // 图片消息
175
+ content = contentObj?.url || rawMsg.content?.url || '';
176
+ } else if (contentType === 3) {
177
+ // 视频消息
178
+ content = contentObj?.url || '[视频]';
179
+ } else {
180
+ // 其他类型,尝试提取有意义的字段
181
+ const c = rawMsg.content;
182
+ if (typeof c === 'object' && c !== null) {
183
+ content = c.fileName || c.url || c.text || JSON.stringify(c);
184
+ } else {
185
+ content = String(c || '');
186
+ }
187
+ }
188
+ // 确保 content 是字符串
189
+ if (typeof content !== 'string') {
190
+ content = JSON.stringify(content);
191
+ }
192
+
193
+ process.send({
194
+ type: 'message',
195
+ agentId,
196
+ data: {
197
+ fromUid,
198
+ toUid,
199
+ channelId,
200
+ channelType,
201
+ content: typeof content === 'string' ? content : String(content),
202
+ contentType,
203
+ messageId: msgId,
204
+ timestamp,
205
+ messageSeq: rawMsg.messageSeq,
206
+ clientMsgNo: rawMsg.clientMsgNo,
207
+ noPersist: rawMsg.header?.noPersist ? 1 : 0,
208
+ redDot: rawMsg.header?.reddot ? 1 : 0,
209
+ syncOnce: rawMsg.header?.syncOnce ? 1 : 0
210
+ }
211
+ });
212
+ });
213
+
214
+ // 响应主进程的主动探测
215
+ process.on('message', (msg) => {
216
+ if (msg && msg.type === 'ping') {
217
+ const st = sdk.connectManager.status;
218
+ const connected = st === 1;
219
+ process.send({ type: 'pong', agentId, connected, statusCode: st });
220
+ }
221
+ });
222
+
223
+ // 连接
224
+ try {
225
+ sdk.connect();
226
+ } catch (err) {
227
+ workerErr(`[${agentId}] sdk.connect() 失败:`, err.message, err.stack);
228
+ process.exit(1);
229
+ }
230
+
231
+ // SDK bug: connectManager 连接成功时不触发 notifyConnectStatusListeners(1)
232
+ // 持续轮询 SDK 内部状态,状态变化时实时上报给主进程
233
+ // 不设超时:SDK autoReconnect 自动重连后仍能检测到
234
+ let lastStatus = -1;
235
+ const connCheckTimer = setInterval(() => {
236
+ const st = sdk.connectManager.status;
237
+ if (st === lastStatus) return; // 状态未变,跳过
238
+ lastStatus = st;
239
+
240
+ if (st === 1) { // ConnectStatus.Connected
241
+ workerLog(`[${agentId}] 连接状态: connected`);
242
+ process.send({ type: 'status', agentId, status: 'connected', statusCode: 1 });
243
+ } else if (st === 0) { // Disconnect
244
+ workerLog(`[${agentId}] 连接状态: disconnected`);
245
+ process.send({ type: 'status', agentId, status: 'disconnected', statusCode: 0 });
246
+ } else if (st === 3) { // ConnectFail
247
+ workerLog(`[${agentId}] 连接状态: connect_fail`);
248
+ process.send({ type: 'status', agentId, status: 'connect_fail', statusCode: 3 });
249
+ } else if (st === 4) { // ConnectKick — SDK 不会自动重连,停止轮询
250
+ workerLog(`[${agentId}] 连接状态: kicked`);
251
+ process.send({ type: 'status', agentId, status: 'kicked', statusCode: 4 });
252
+ clearInterval(connCheckTimer);
253
+ }
254
+ }, 500);
255
+
256
+ // 等待服务端回执的发送队列(clientSeq → 消息元数据)
257
+ const pendingSends = new Map();
258
+
259
+ // 监听发送回执(服务端确认后提供真实 messageSeq)
260
+ sdk.chatManager.addMessageStatusListener((sendack) => {
261
+ const pending = pendingSends.get(sendack.clientSeq);
262
+ if (pending) {
263
+ process.send({
264
+ type: 'sent',
265
+ agentId: pending.agentId,
266
+ channelId: pending.channelId,
267
+ localMsgId: pending.localMsgId,
268
+ messageId: sendack.messageID,
269
+ messageSeq: sendack.messageSeq,
270
+ clientMsgNo: pending.clientMsgNo,
271
+ success: true
272
+ });
273
+ pendingSends.delete(sendack.clientSeq);
274
+ }
275
+ });
276
+
277
+ // 接收主进程指令
278
+ process.on('message', (msg) => {
279
+ console.log(`[${agentId}] 收到指令:`, JSON.stringify(msg).substring(0, 100));
280
+
281
+ if (msg.type === 'send') {
282
+ // 发送消息
283
+ const { channelId, content, messageType, localMsgId } = msg;
284
+ (async () => {
285
+ try {
286
+ const channel = new Channel(channelId, ChannelTypePerson);
287
+ let message;
288
+ if (messageType === 'image') {
289
+ // 发送图片(远程 URL,不上传)
290
+ message = new MessageImage();
291
+ message.url = content;
292
+ } else if (messageType === 'file') {
293
+ // 发送文件消息:content 为 JSON 字符串 {url, name, size, type}
294
+ if (MessageFile) {
295
+ let filePayload = {};
296
+ try {
297
+ filePayload = JSON.parse(content || '{}');
298
+ } catch (_) { /* 非 JSON 则视为 url */ }
299
+ message = new MessageFile();
300
+ message.url = filePayload.url || content;
301
+ message.name = filePayload.name || '';
302
+ message.size = filePayload.size || 0;
303
+ message.type = filePayload.type || '';
304
+ } else {
305
+ // 兜底:按文本发送 JSON(兼容旧版本)
306
+ message = new MessageText(content);
307
+ }
308
+ } else {
309
+ // 发送文本
310
+ message = new MessageText(content);
311
+ }
312
+ const result = await sdk.chatManager.send(message, channel);
313
+
314
+ // SDK 在 send() 内部已生成 clientMsgNo 和 clientSeq
315
+ const clientSeq = result.clientSeq;
316
+ const clientMsgNo = result.clientMsgNo || null;
317
+
318
+ // 加入确认队列(等待服务端回执后更新 messageSeq)
319
+ pendingSends.set(clientSeq, { agentId, channelId, localMsgId: localMsgId || result.messageID, clientMsgNo });
320
+
321
+ // 立即返回(clientMsgNo 已知,messageSeq 待回执)
322
+ process.send({
323
+ type: 'sent',
324
+ agentId,
325
+ channelId,
326
+ localMsgId: localMsgId || result.messageID,
327
+ messageId: result.messageID,
328
+ messageSeq: null,
329
+ clientMsgNo,
330
+ success: true
331
+ });
332
+ } catch (e) {
333
+ workerErr(`[${agentId}] 消息发送失败:`, e);
334
+ process.send({
335
+ type: 'sent',
336
+ agentId,
337
+ channelId,
338
+ success: false,
339
+ error: e.message
340
+ });
341
+ }
342
+ })();
343
+ }
344
+
345
+ if (msg.type === 'disconnect') {
346
+ sdk.disconnect();
347
+ }
348
+ });
349
+
350
+ // 进程退出时断开连接
351
+ process.on('exit', () => {
352
+ sdk.disconnect();
353
+ });
354
+
355
+ process.on('uncaughtException', (err) => {
356
+ workerErr(`[${agentId}] 未捕获异常:`, err.message, err.stack);
357
+ });
358
+
359
+ process.on('unhandledRejection', (reason, promise) => {
360
+ workerErr(`[${agentId}] 未处理的 Promise 拒绝:`, reason);
361
+ });
362
+
363
+ // 定期检查父进程是否还活着(Windows 上 kill 不触发 SIGTERM,需主动检测)
364
+ if (process.platform === 'win32') {
365
+ const parentCheck = setInterval(() => {
366
+ try {
367
+ // 信号 0 = 仅探测进程是否存在,不发送实际信号
368
+ process.kill(process.ppid, 0);
369
+ } catch (_) {
370
+ workerLog(`[${agentId}] 主进程已退出,worker 自动终止`);
371
+ clearInterval(parentCheck);
372
+ sdk.disconnect();
373
+ process.exit(0);
374
+ }
375
+ }, 3000);
376
+ }
377
+
378
+ workerLog(`[${agentId}] Worker 已启动,等待连接...`);
@@ -0,0 +1,51 @@
1
+ /**
2
+ * 自定义 IM 消息内容类
3
+ *
4
+ * WuKongIM JS SDK 内置:MessageText(1)、MessageImage(2)、MessageVideo(3) 等。
5
+ * 文件消息(contentType=4)没有现成类,因此参考 chatroom 项目实现自定义 MessageFile。
6
+ */
7
+
8
+ const { MessageContent } = require('wukongimjssdk');
9
+
10
+ // WuKongIM SDK 当前版本未导出 file 常量,文件消息统一使用 contentType = 4
11
+ const MESSAGE_CONTENT_TYPE_FILE = 4;
12
+
13
+ /**
14
+ * 文件消息内容类
15
+ * encodeJSON / decodeJSON 会被 SDK 用于序列化/反序列化 payload
16
+ */
17
+ class MessageFile extends MessageContent {
18
+ constructor() {
19
+ super();
20
+ this.url = '';
21
+ this.name = '';
22
+ this.size = 0;
23
+ this.type = '';
24
+ }
25
+
26
+ get contentType() {
27
+ return MESSAGE_CONTENT_TYPE_FILE;
28
+ }
29
+
30
+ encodeJSON() {
31
+ return {
32
+ url: this.url,
33
+ name: this.name,
34
+ size: this.size,
35
+ type: this.type
36
+ };
37
+ }
38
+
39
+ decodeJSON(json) {
40
+ this.url = json.url || '';
41
+ this.name = json.name || '';
42
+ this.size = json.size || 0;
43
+ this.type = json.type || '';
44
+ }
45
+
46
+ get conversationDigest() {
47
+ return `[文件] ${this.name || ''}`;
48
+ }
49
+ }
50
+
51
+ module.exports = { MessageFile };