@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.
- package/package.json +32 -0
- package/scripts/build-native.js +72 -0
- package/src/bankHeadOffices.js +20543 -0
- package/src/channels/email.js +35 -0
- package/src/channels/feishu.js +31 -0
- package/src/channels/qq-email.js +30 -0
- package/src/channels/registry.js +279 -0
- package/src/channels/telegram.js +28 -0
- package/src/channels/voko-email.js +7 -0
- package/src/channels/wechat.js +35 -0
- package/src/cli.js +120 -0
- package/src/context.js +164 -0
- package/src/core/access-control-api.js +150 -0
- package/src/core/access-control.js +56 -0
- package/src/core/agent-registration.js +319 -0
- package/src/core/api-signature.js +33 -0
- package/src/core/audit.js +133 -0
- package/src/core/database.js +1409 -0
- package/src/core/did-auth.js +54 -0
- package/src/core/hermes-paths.js +57 -0
- package/src/core/invitation.js +49 -0
- package/src/core/lite-bus.js +16 -0
- package/src/core/llm-client.js +1032 -0
- package/src/core/messenger.js +456 -0
- package/src/core/notifier.js +99 -0
- package/src/core/offline-sync.js +150 -0
- package/src/core/payment.js +285 -0
- package/src/core/publish-agent.js +166 -0
- package/src/core/register-capabilities.js +119 -0
- package/src/core/search-capabilities.js +136 -0
- package/src/core/send-message.js +85 -0
- package/src/core/set-agent-status.js +65 -0
- package/src/core/update-agent-profile.js +102 -0
- package/src/core/worker-manager.js +332 -0
- package/src/endpoints.json +21 -0
- package/src/index.js +712 -0
- package/src/mcp/CLAUDE_TEST.md +82 -0
- package/src/mcp/FULL_TEST.md +139 -0
- package/src/mcp/TEST.md +124 -0
- package/src/mcp/TEST_STEPS.md +75 -0
- package/src/mcp/server.js +612 -0
- package/src/mcp/tools.js +1367 -0
- package/src/mcp/transport/http.js +95 -0
- package/src/mcp/transport/stdio.js +20 -0
- package/src/preload.js +27 -0
- package/src/server/agent-email-api.js +120 -0
- package/src/server/agent-manager.js +580 -0
- package/src/server/email-handler.js +329 -0
- package/src/server/feishu-handler.js +249 -0
- package/src/server/hermes-api-client.js +166 -0
- package/src/server/hermes-discovery.js +80 -0
- package/src/server/hermes-handler.js +287 -0
- package/src/server/openclaw-handler-cli.js +131 -0
- package/src/server/openclaw-websocket-handler.js +1290 -0
- package/src/server/oss.js +186 -0
- package/src/server/owner-intervention-notifier.js +320 -0
- package/src/server/release-page.html +204 -0
- package/src/server/telegram-handler.js +208 -0
- package/src/server/voko-email-handler.js +68 -0
- package/src/server/wechat-handler.js +439 -0
- package/src/workers/agent-worker.js +378 -0
- package/src/workers/message-content.js +51 -0
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* worker-manager.js — Agent Worker 进程管理
|
|
3
|
+
*
|
|
4
|
+
* 管理 WuKongIM 子进程(fork agent-worker.js)的全生命周期:
|
|
5
|
+
* 启动、停止、重启、状态追踪、系统消息发送。
|
|
6
|
+
* 纯 Node.js,无 Electron 依赖。
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const { fork, execSync } = require('child_process');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const EventEmitter = require('events');
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
|
|
14
|
+
class AgentWorkerManager extends EventEmitter {
|
|
15
|
+
/**
|
|
16
|
+
* @param {object} db - better-sqlite3 实例
|
|
17
|
+
* @param {object} [options]
|
|
18
|
+
* @param {object} [options.mainWindow] - Electron BrowserWindow(可选,用于 UI 通知)
|
|
19
|
+
*/
|
|
20
|
+
constructor(db, options = {}) {
|
|
21
|
+
super();
|
|
22
|
+
this.db = db;
|
|
23
|
+
this.mainWindow = options.mainWindow || null;
|
|
24
|
+
|
|
25
|
+
// WuKongIM 多连接管理: agentId → { sdk, config }
|
|
26
|
+
this.wukongimConnections = new Map();
|
|
27
|
+
|
|
28
|
+
// Agent Worker 进程管理: agentId → { worker, config }
|
|
29
|
+
this.workers = new Map();
|
|
30
|
+
|
|
31
|
+
// Worker 上报的真实 IM 连接状态
|
|
32
|
+
this.connectionStatus = new Map(); // agentId → 'connected' | 'disconnected' | 'connecting' | 'kicked'
|
|
33
|
+
|
|
34
|
+
// 已连接 agent 追踪
|
|
35
|
+
this.connectedAgents = new Set();
|
|
36
|
+
this.publishedAgentIds = new Set();
|
|
37
|
+
|
|
38
|
+
// 主动停止的 agent(Map: agentId → timestamp,防止 auto-restart 重新拉起)
|
|
39
|
+
this._stoppedAgents = new Map();
|
|
40
|
+
|
|
41
|
+
// 所有 worker 进程引用(包括已从 workers Map 删除的,供 exit 兜底清理)
|
|
42
|
+
this._allWorkers = new Map();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** 断开某个 agent 的 WuKongIM 连接 */
|
|
46
|
+
disconnect(agentId) {
|
|
47
|
+
const conn = this.wukongimConnections.get(agentId);
|
|
48
|
+
if (conn) {
|
|
49
|
+
conn.sdk.disconnect();
|
|
50
|
+
this.wukongimConnections.delete(agentId);
|
|
51
|
+
console.log(`[${agentId}] WuKongIM 已断开`);
|
|
52
|
+
}
|
|
53
|
+
return { success: true };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** 获取某个 agent 的连接状态 */
|
|
57
|
+
getStatus(agentId) {
|
|
58
|
+
const entry = this.workers.get(agentId);
|
|
59
|
+
if (!entry) return { connected: false, uid: null };
|
|
60
|
+
const status = this.connectionStatus.get(agentId);
|
|
61
|
+
const isConnected = status === 'connected';
|
|
62
|
+
return { connected: isConnected, uid: entry.config.uid, status: status || 'unknown' };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** 某个 agent 是否已在运行 */
|
|
66
|
+
isRunning(agentId) {
|
|
67
|
+
return this.workers.has(agentId);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** 获取 worker 条目 */
|
|
71
|
+
getWorker(agentId) {
|
|
72
|
+
return this.workers.get(agentId);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** 第一个已连接的 agent */
|
|
76
|
+
getFirstAgentId() {
|
|
77
|
+
const firstEntry = this.workers.values().next().value;
|
|
78
|
+
return firstEntry?.config?.agentId || Array.from(this.workers.keys())[0] || null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* 启动 Agent Worker 进程
|
|
83
|
+
* @param {string} agentId
|
|
84
|
+
* @param {object} config - { uid, token, serverUrl }
|
|
85
|
+
* @param {object} [appPaths] - { isPackaged, resourcesPath, userDataPath }
|
|
86
|
+
*/
|
|
87
|
+
start(agentId, config, appPaths) {
|
|
88
|
+
if (this.workers.has(agentId)) {
|
|
89
|
+
console.log(`[Agent Worker] ${agentId} 已经运行,跳过启动`);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
// 清除旧停止标记,允许启动
|
|
93
|
+
this._stoppedAgents.delete(agentId);
|
|
94
|
+
|
|
95
|
+
const workerPath = appPaths?.isPackaged
|
|
96
|
+
? path.join(appPaths.resourcesPath, 'app.asar.unpacked', 'src', 'workers', 'agent-worker.js')
|
|
97
|
+
: path.join(__dirname, '..', 'workers', 'agent-worker.js');
|
|
98
|
+
|
|
99
|
+
let worker;
|
|
100
|
+
try {
|
|
101
|
+
const env = { ...process.env };
|
|
102
|
+
if (appPaths?.userDataPath) {
|
|
103
|
+
env.WORKER_LOG_PATH = path.join(appPaths.userDataPath, 'agent-worker.log');
|
|
104
|
+
}
|
|
105
|
+
worker = fork(workerPath, [agentId, JSON.stringify(config)], { env });
|
|
106
|
+
} catch (err) {
|
|
107
|
+
console.error(`[Agent Worker] ${agentId} fork 失败:`, err.message);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
this.workers.set(agentId, { worker, config });
|
|
111
|
+
this._allWorkers.set(agentId, { worker, config });
|
|
112
|
+
|
|
113
|
+
worker.on('message', (msg) => {
|
|
114
|
+
if (msg.type === 'message') {
|
|
115
|
+
// 收到消息 → 由外部处理(handleAgentMessage)
|
|
116
|
+
this.emit('message', msg);
|
|
117
|
+
} else if (msg.type === 'status') {
|
|
118
|
+
this.connectionStatus.set(msg.agentId, msg.status);
|
|
119
|
+
console.log(`[Agent Worker] ${agentId} 状态: ${msg.status}`);
|
|
120
|
+
|
|
121
|
+
if (msg.status === 'kicked' || msg.statusCode === 4) {
|
|
122
|
+
// 忽略旧 worker 被踢(新 worker 已顶上或 agent 已被主动停止)
|
|
123
|
+
const currentEntry = this.workers.get(msg.agentId);
|
|
124
|
+
if (!currentEntry || currentEntry.worker !== worker) {
|
|
125
|
+
console.log(`[Agent Worker] ${msg.agentId} 忽略旧 worker 踢回`);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const cfg = currentEntry.config;
|
|
129
|
+
console.log(`[Agent Worker] ${msg.agentId} 被踢,8s 后重启 worker`);
|
|
130
|
+
setTimeout(() => {
|
|
131
|
+
this.stop(msg.agentId);
|
|
132
|
+
setTimeout(() => {
|
|
133
|
+
if (cfg) this.start(msg.agentId, cfg, appPaths);
|
|
134
|
+
}, 500);
|
|
135
|
+
}, 8000);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (msg.status === 'connected' || msg.statusCode === 2) {
|
|
140
|
+
this.connectedAgents.add(msg.agentId);
|
|
141
|
+
// 触发外部监听器(用于离线同步等)
|
|
142
|
+
this.emit('agent-connected', msg.agentId);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
this.emit('status', msg);
|
|
146
|
+
} else if (msg.type === 'sent') {
|
|
147
|
+
this.emit('sent', msg);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
worker.on('exit', (code) => {
|
|
152
|
+
// 3~8 秒随机延迟,避免多个 worker 同时重启造成踢号风暴
|
|
153
|
+
const delay = 3000 + Math.floor(Math.random() * 5000);
|
|
154
|
+
console.log(`[Agent Worker] ${agentId} 退出,code=${code},${delay}ms 后重启`);
|
|
155
|
+
const entry = this.workers.get(agentId);
|
|
156
|
+
if (entry && entry.worker === worker) {
|
|
157
|
+
this.workers.delete(agentId);
|
|
158
|
+
this.connectionStatus.delete(agentId);
|
|
159
|
+
}
|
|
160
|
+
setTimeout(() => {
|
|
161
|
+
// 主动停止的 agent 不自动重启(停止时间在 10 秒内视为主动停止)
|
|
162
|
+
const stoppedAt = this._stoppedAgents.get(agentId);
|
|
163
|
+
if (stoppedAt && Date.now() - stoppedAt < 10000) {
|
|
164
|
+
this._stoppedAgents.delete(agentId);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
if (!this.workers.has(agentId)) {
|
|
168
|
+
console.log(`[Agent Worker] 重启 ${agentId}`);
|
|
169
|
+
this.start(agentId, config, appPaths);
|
|
170
|
+
}
|
|
171
|
+
}, delay);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
worker.on('error', (err) => {
|
|
175
|
+
console.error(`[Agent Worker] ${agentId} 错误:`, err.message);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
console.log(`[Agent Worker] 已启动 ${agentId},UID=${config.uid}`);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* 停止 Agent Worker 进程
|
|
183
|
+
* @returns {Promise<void>}
|
|
184
|
+
*/
|
|
185
|
+
stop(agentId) {
|
|
186
|
+
return new Promise(resolve => {
|
|
187
|
+
const entry = this.workers.get(agentId);
|
|
188
|
+
if (!entry) {
|
|
189
|
+
console.log(`[Agent Worker] ${agentId} 未运行`);
|
|
190
|
+
return resolve();
|
|
191
|
+
}
|
|
192
|
+
this.workers.delete(agentId);
|
|
193
|
+
this.connectionStatus.delete(agentId);
|
|
194
|
+
this._stoppedAgents.set(agentId, Date.now());
|
|
195
|
+
console.log(`[Agent Worker] 正在停止 ${agentId}...`);
|
|
196
|
+
|
|
197
|
+
let resolved = false;
|
|
198
|
+
const timer = setTimeout(() => {
|
|
199
|
+
resolved = true;
|
|
200
|
+
try { entry.worker.kill('SIGKILL'); } catch (_) {}
|
|
201
|
+
console.log(`[Agent Worker] ${agentId} 超时强制终止`);
|
|
202
|
+
resolve();
|
|
203
|
+
}, 3000);
|
|
204
|
+
|
|
205
|
+
entry.worker.once('exit', () => {
|
|
206
|
+
if (!resolved) {
|
|
207
|
+
clearTimeout(timer);
|
|
208
|
+
console.log(`[Agent Worker] ${agentId} 已退出`);
|
|
209
|
+
resolve();
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
entry.worker.send({ type: 'disconnect' });
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** 停止所有 worker */
|
|
218
|
+
async stopAll() {
|
|
219
|
+
const ids = [...this.workers.keys()];
|
|
220
|
+
await Promise.all(ids.map(id => this.stop(id)));
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** 同步强杀所有 worker(用于进程退出时,不等回调) */
|
|
224
|
+
killAll() {
|
|
225
|
+
const ids = [...this.workers.keys()];
|
|
226
|
+
if (ids.length === 0) return;
|
|
227
|
+
// 先标记所有 agent 为主动停止,防止 auto-restart 重新拉起
|
|
228
|
+
for (const id of ids) {
|
|
229
|
+
this._stoppedAgents.set(id, Date.now());
|
|
230
|
+
}
|
|
231
|
+
for (const id of ids) {
|
|
232
|
+
const entry = this.workers.get(id);
|
|
233
|
+
if (!entry) continue;
|
|
234
|
+
try { entry.worker.send({ type: 'disconnect' }); } catch (e) { console.error(`[WorkerManager] killAll ${id} send 失败:`, e.message); }
|
|
235
|
+
try { entry.worker.kill('SIGKILL'); } catch (e) { console.error(`[WorkerManager] killAll ${id} kill 失败:`, e.message); }
|
|
236
|
+
this.workers.delete(id);
|
|
237
|
+
this.connectionStatus.delete(id);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* 发送系统消息给访客
|
|
243
|
+
*/
|
|
244
|
+
sendSystemMessage(agentId, visitorId, content, serverTimestamp) {
|
|
245
|
+
if (!agentId || !visitorId || !content) {
|
|
246
|
+
console.error('[sendSystemMessage] 参数不完整', { agentId, visitorId, content });
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
const agentRow = this.db.prepare(`SELECT imUid FROM agents WHERE agent_id = ?`).get(agentId);
|
|
250
|
+
if (!agentRow) {
|
|
251
|
+
console.error(`[sendSystemMessage] 未找到 agent: ${agentId}`);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
const fromUid = agentRow.imUid;
|
|
255
|
+
const timestamp = serverTimestamp ? (serverTimestamp + 1) : Math.floor(Date.now() / 1000);
|
|
256
|
+
const msgId = `sys-${agentId}-${visitorId}-${timestamp}-${Math.random().toString(36).substr(2, 4)}`;
|
|
257
|
+
|
|
258
|
+
try {
|
|
259
|
+
this.db.prepare(`INSERT INTO messages (id, from_uid, to_uid, content, channel_id, channel_type, agent_id, timestamp, is_me, status, message_seq, client_msg_no, no_persist, red_dot, sync_once, content_type)
|
|
260
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
261
|
+
.run(msgId, fromUid, visitorId, content, visitorId, 1, agentId, timestamp, 2, 'sent', null, null, 0, 0, 0, 1);
|
|
262
|
+
} catch (e) {
|
|
263
|
+
console.error('[sendSystemMessage] messages 写入失败:', e.message);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
try {
|
|
267
|
+
const existConv = this.db.prepare(`SELECT user_uid FROM conversations WHERE user_uid = ? AND channel_id = ?`).get(fromUid, visitorId);
|
|
268
|
+
if (existConv) {
|
|
269
|
+
this.db.prepare(`UPDATE conversations SET last_message = ?, last_timestamp = ? WHERE user_uid = ? AND channel_id = ?`)
|
|
270
|
+
.run(content, timestamp, fromUid, visitorId);
|
|
271
|
+
} else {
|
|
272
|
+
this.db.prepare(`INSERT INTO conversations (user_uid, channel_id, channel_type, name, last_message, last_timestamp, unread_count, agent_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
273
|
+
.run(fromUid, visitorId, 1, visitorId, content, timestamp, 0, agentId);
|
|
274
|
+
}
|
|
275
|
+
} catch (e) {
|
|
276
|
+
console.error('[sendSystemMessage] 会话更新失败:', e.message);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const workerEntry = this.workers.get(agentId);
|
|
280
|
+
if (workerEntry) {
|
|
281
|
+
workerEntry.worker.send({ type: 'send', channelId: visitorId, content, localMsgId: msgId });
|
|
282
|
+
} else {
|
|
283
|
+
console.error(`[sendSystemMessage] 未找到 agent worker: ${agentId}`);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
this.emit('system-message', { agentId, fromUid, visitorId, content, msgId, timestamp });
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* 清理孤儿 worker 进程(来自前一个 Lite/Desktop 会话的残留)
|
|
291
|
+
* 在启动新 worker 前调用,防止同 UID 互踢。
|
|
292
|
+
*/
|
|
293
|
+
static killOrphanedWorkers() {
|
|
294
|
+
try {
|
|
295
|
+
if (process.platform === 'win32') {
|
|
296
|
+
// 用 tasklist 查所有 node.exe,再用 PowerShell 确认命令行含 agent-worker / packages/lite
|
|
297
|
+
let pids = [];
|
|
298
|
+
try {
|
|
299
|
+
const tasks = execSync('tasklist /fi "imagename eq node.exe" /fo csv /nh', { encoding: 'utf8', timeout: 5000, windowsHide: true });
|
|
300
|
+
const allPids = tasks.trim().split('\n')
|
|
301
|
+
.map(l => l.split('","')[1]?.replace(/"/g, '').trim())
|
|
302
|
+
.filter(p => p && !isNaN(p));
|
|
303
|
+
for (const pid of allPids) {
|
|
304
|
+
if (pid === String(process.pid)) continue;
|
|
305
|
+
try {
|
|
306
|
+
const cmd = execSync(
|
|
307
|
+
`powershell -Command "try { (Get-CimInstance Win32_Process -Filter \\\"ProcessId=${pid}\\\").CommandLine } catch { '' }"`,
|
|
308
|
+
{ encoding: 'utf8', timeout: 3000, windowsHide: true }
|
|
309
|
+
).trim();
|
|
310
|
+
if (cmd.includes('agent-worker') || cmd.includes('packages/lite')) {
|
|
311
|
+
pids.push(pid);
|
|
312
|
+
}
|
|
313
|
+
} catch (_) {}
|
|
314
|
+
}
|
|
315
|
+
} catch (e) {
|
|
316
|
+
console.error('[WorkerManager] tasklist 失败:', e.message);
|
|
317
|
+
}
|
|
318
|
+
// 找到的残留进程逐个强杀
|
|
319
|
+
for (const pid of pids) {
|
|
320
|
+
try { execSync(`taskkill /f /t /pid ${pid}`, { stdio: 'ignore', timeout: 3000, windowsHide: true }); }
|
|
321
|
+
catch (e) { console.error(`[WorkerManager] 清理 PID ${pid} 失败:`, e.message); }
|
|
322
|
+
}
|
|
323
|
+
if (pids.length > 0) console.log(`[WorkerManager] 清理了 ${pids.length} 个残留进程 (worker+Lite)`);
|
|
324
|
+
else console.log('[WorkerManager] 无残留进程需清理');
|
|
325
|
+
}
|
|
326
|
+
} catch (e) {
|
|
327
|
+
console.error('[WorkerManager] 扫描残留进程失败:', e.message);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
module.exports = { AgentWorkerManager };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"api": {
|
|
3
|
+
"baseUrl": "http://www.vokovoko.com"
|
|
4
|
+
},
|
|
5
|
+
"oss": {
|
|
6
|
+
"region": "oss-cn-beijing",
|
|
7
|
+
"bucket": "vokofiles",
|
|
8
|
+
"endpoint": "https://vokofiles.oss-cn-beijing.aliyuncs.com",
|
|
9
|
+
"publicUrl": "http://files.vokovoko.com"
|
|
10
|
+
},
|
|
11
|
+
"update": {
|
|
12
|
+
"baseUrl": "http://files.vokovoko.com/updates"
|
|
13
|
+
},
|
|
14
|
+
"im": {
|
|
15
|
+
"baseUrl": "http://im.vokovoko.com",
|
|
16
|
+
"wsUrl": "ws://im.vokovoko.com:5200"
|
|
17
|
+
},
|
|
18
|
+
"payment": {
|
|
19
|
+
"baseUrl": "http://www.vokovoko.com/api/external/v1"
|
|
20
|
+
}
|
|
21
|
+
}
|