@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,35 @@
1
+ /**
2
+ * Email 渠道元信息
3
+ */
4
+ module.exports = {
5
+ name: 'email',
6
+ displayName: 'Email',
7
+
8
+ configFields: [
9
+ { key: 'ownerEmail', label: '主人邮箱', type: 'text', required: true },
10
+ { key: 'agentEmail', label: 'Agent 邮箱地址', type: 'text', required: true },
11
+ { key: 'authCode', label: '授权码', type: 'password', required: true }
12
+ ],
13
+
14
+ // Email 配置表单有说明文字
15
+ customUI: true,
16
+
17
+ handlerClass: '../server/email-handler',
18
+
19
+ extractConfig(channel) {
20
+ return {
21
+ ownerEmail: channel.config?.ownerEmail,
22
+ agentEmail: channel.config?.agentEmail,
23
+ authCode: channel.config?.authCode,
24
+ smtp: channel.config?.smtp,
25
+ imap: channel.config?.imap
26
+ };
27
+ },
28
+
29
+ extraCallbacks: {},
30
+
31
+ // Email 没有 CLI 脚本,使用内联测试
32
+ test: {
33
+ inlineTest: true
34
+ }
35
+ };
@@ -0,0 +1,31 @@
1
+ /**
2
+ * 飞书渠道元信息
3
+ */
4
+ module.exports = {
5
+ name: 'feishu',
6
+ displayName: '飞书',
7
+
8
+ configFields: [
9
+ { key: 'appId', label: 'App ID', type: 'text', required: true },
10
+ { key: 'appSecret', label: 'App Secret', type: 'password', required: true },
11
+ { key: 'ownerOpenId', label: '主人 OpenID', type: 'text' },
12
+ { key: 'pollInterval', label: '轮询间隔(秒)', type: 'number', min: 1, max: 60 }
13
+ ],
14
+
15
+ handlerClass: '../server/feishu-handler',
16
+
17
+ extractConfig(channel) {
18
+ return {
19
+ appId: channel.config?.appId || channel.appId,
20
+ appSecret: channel.config?.appSecret || channel.appSecret,
21
+ ownerOpenId: channel.config?.ownerOpenId || channel.ownerOpenId,
22
+ pollInterval: (channel.config?.pollInterval || 5) * 1000
23
+ };
24
+ },
25
+
26
+ extraCallbacks: {},
27
+
28
+ test: {
29
+ cliScript: 'owner-intervention-cli.js'
30
+ }
31
+ };
@@ -0,0 +1,30 @@
1
+ /**
2
+ * QQ 邮箱渠道元信息
3
+ * 复用 email-handler,SMTP/IMAP 固定使用 qq.com
4
+ */
5
+ module.exports = {
6
+ name: 'qq-email',
7
+ displayName: 'QQ邮箱',
8
+
9
+ configFields: [
10
+ { key: 'ownerEmail', label: '主人邮箱', type: 'text', required: true },
11
+ { key: 'authCode', label: '授权码', type: 'password', required: true }
12
+ ],
13
+
14
+ customUI: true,
15
+ handlerClass: '../server/email-handler',
16
+
17
+ extractConfig(channel) {
18
+ const email = channel.config?.agentEmail || channel.config?.ownerEmail || '';
19
+ const authCode = channel.config?.authCode || '';
20
+ return {
21
+ ownerEmail: email,
22
+ agentEmail: email,
23
+ authCode,
24
+ smtp: { host: "smtp.qq.com", port: 587, secure: false, auth: { user: email, pass: authCode } },
25
+ imap: { host: "imap.qq.com", port: 993, secure: true, auth: { user: email, pass: authCode } }
26
+ };
27
+ },
28
+
29
+ test: { inlineTest: true }
30
+ };
@@ -0,0 +1,279 @@
1
+ /**
2
+ * 渠道注册表 — 统一管理所有渠道的初始化、回调、运行时访问
3
+ *
4
+ * 新增渠道步骤:
5
+ * 1. 写 handler class 在 src/server/
6
+ * 2. 写元信息文件在 src/channels/
7
+ * 3. 在 REGISTERED 数组中注册
8
+ */
9
+
10
+ const channels = {};
11
+ const bus = require('../core/lite-bus');
12
+
13
+ function loadDef(name) {
14
+ try {
15
+ return require(`./${CHANNEL_FILES[name] || name}`);
16
+ } catch (e) {
17
+ console.error(`[Registry] 加载渠道元信息失败: ${name}`, e.message);
18
+ return null;
19
+ }
20
+ }
21
+
22
+ const CHANNEL_FILES = { feishu: 'feishu', telegram: 'telegram', email: 'email', 'qq-email': 'qq-email', 'voko-email': 'voko-email' };
23
+ const REGISTERED = ['feishu', 'telegram', 'email', 'qq-email', 'voko-email'];
24
+
25
+ const handlers = {};
26
+
27
+ /**
28
+ * 创建统一的 onOwnerReply 回调
29
+ */
30
+ function createOnOwnerReply(channelName, deps) {
31
+ const { databaseAPI, openclawHandler, hermesHandler, mainWindow, db } = deps;
32
+
33
+ return (intervention, content, replyMessageId) => {
34
+ const logTag = channelName.charAt(0).toUpperCase() + channelName.slice(1);
35
+ console.log(`[${logTag}] ================== 主人回复流程 ==================`);
36
+
37
+ // 好友申请自动审批:主人回复"同意"时自动加入白名单
38
+ if (deps.autoApproveWhitelistIfFriendRequest) {
39
+ deps.autoApproveWhitelistIfFriendRequest(intervention, content);
40
+ }
41
+
42
+ let isTestReply = false;
43
+ if (intervention.visitorId && intervention.visitorId.startsWith('system_test:')) {
44
+ const testId = intervention.visitorId.replace('system_test:', '');
45
+ console.log(`[${logTag}] 收到渠道测试回复, testId:`, testId);
46
+ isTestReply = true;
47
+ if (mainWindow && !mainWindow.isDestroyed()) {
48
+ bus.emit('channels:test-success', {
49
+ testId: testId,
50
+ replyContent: content
51
+ });
52
+ }
53
+ }
54
+
55
+ const sessionKeyForForward = intervention.sessionKey;
56
+ const updateResult = databaseAPI.updateOwnerInterventionReply(intervention.id, content, Date.now(), channelName);
57
+ console.log(`[${logTag}] 数据库更新结果: id=${intervention.id} contentChanged=${updateResult.contentChanged}${isTestReply ? ' (测试消息)' : ''}`);
58
+
59
+ if (!isTestReply && updateResult.contentChanged && sessionKeyForForward) {
60
+ const forwardMsg = deps.buildOwnerReplyPrompt(intervention, content);
61
+ const doNotify = () => {
62
+ databaseAPI.markAgentNotified(intervention.id);
63
+ databaseAPI.updateOwnerInterventionStatus(intervention.id, 'resolved', Date.now());
64
+ };
65
+ if (intervention.agentId) {
66
+ const backendRow = db ? db.prepare('SELECT backend_type FROM agents WHERE agent_id = ?').get(intervention.agentId) : null;
67
+ const agentBackend = backendRow?.backend_type;
68
+ if (agentBackend === 'hermes' && hermesHandler?.connected) {
69
+ const hermesSessionKey = 'hermes:' + intervention.agentId + ':' + intervention.visitorId;
70
+ hermesHandler.steer(hermesSessionKey, forwardMsg).then(result => {
71
+ doNotify();
72
+ const output = result?.output || '';
73
+ if (output) {
74
+ const ts = Math.floor(Date.now() / 1000);
75
+ const msgId = 'steer-' + intervention.agentId + '-' + intervention.visitorId + '-' + Date.now();
76
+ try {
77
+ 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) VALUES (?, ?, ?, ?, ?, 1, ?, ?, 1, 'sent', ?, ?, ?, ?, ?, ?)").run(msgId, '', intervention.visitorId, output, intervention.visitorId, intervention.agentId, ts, null, null, 0, 0, 0, 1);
78
+ } catch (_e) { /* ignore */ }
79
+ console.log(`[${logTag}] agent 回复已入库, visitorId=${intervention.visitorId}`);
80
+ }
81
+ }).catch(err => {
82
+ console.error(`[${logTag}] 通知 Hermes agent 失败:`, err.message);
83
+ });
84
+ } else if (agentBackend === 'openclaw' && openclawHandler?.connected) {
85
+ openclawHandler.sendToSession(sessionKeyForForward, forwardMsg).then(doNotify).catch(err => {
86
+ console.error(`[${logTag}] 通知 agent 失败:`, err.message);
87
+ });
88
+ } else {
89
+ console.log(`[${logTag}] -> 后端类型 ${agentBackend} 不支持主动转发,agent 可通过 check_human_replies 自行读取`);
90
+ }
91
+ } else {
92
+ console.log(`[${logTag}] -> 无可用连接,跳过转发`);
93
+ }
94
+ console.log(`[${logTag}] -> 已转发主人回复, sessionKey=${sessionKeyForForward}`);
95
+ } else if (sessionKeyForForward && !updateResult.contentChanged) {
96
+ console.log(`[${logTag}] -> 回复内容无变化,跳过通知 agent`);
97
+ } else if (sessionKeyForForward) {
98
+ console.log(`[${logTag}] -> 无可用连接,跳过转发`);
99
+ }
100
+
101
+ if (mainWindow && !mainWindow.isDestroyed()) {
102
+ bus.emit('owner-reply', {
103
+ channelName,
104
+ visitorId: intervention.visitorId,
105
+ content,
106
+ sessionKey: sessionKeyForForward,
107
+ replyMessageId
108
+ });
109
+ }
110
+
111
+ console.log(`[${logTag}] ================== 流程结束 ==================`);
112
+ };
113
+ }
114
+
115
+ /**
116
+ * 统一初始化所有已启用的渠道
117
+ */
118
+ function initializeAllChannels(deps) {
119
+ const { getEnabledChannel, databaseAPI, openclawHandler, mainWindow } = deps;
120
+ const result = {};
121
+
122
+ for (const name of REGISTERED) {
123
+ const def = loadDef(name);
124
+ if (!def) continue;
125
+
126
+ const channel = getEnabledChannel(name);
127
+ if (!channel || !channel.enabled) {
128
+ console.log(`[${def.displayName}] 渠道未启用或未配置,跳过初始化`);
129
+ continue;
130
+ }
131
+
132
+ const config = def.extractConfig(channel);
133
+ const missingField = def.configFields.find(f => f.required && !config[f.key]);
134
+ if (missingField) {
135
+ console.log(`[${def.displayName}] 配置不完整(缺少 ${missingField.label}),跳过初始化`);
136
+ continue;
137
+ }
138
+
139
+ console.log(`[${def.displayName}] 初始化中...`);
140
+
141
+ try {
142
+ const HandlerClass = require(def.handlerClass);
143
+
144
+ const callbacks = {
145
+ onOwnerReply: createOnOwnerReply(name, deps),
146
+ getInterventionByParentMsgId: (msgId) => databaseAPI.getOwnerInterventionByParentMsgId(msgId),
147
+ isEnabled: () => {
148
+ const ch = getEnabledChannel(name);
149
+ return ch && ch.enabled;
150
+ }
151
+ };
152
+
153
+ if (name === 'wecom' || name === 'feishu') {
154
+ callbacks.getLatestPendingIntervention = () => databaseAPI.getLatestPendingIntervention();
155
+ }
156
+ if (name === 'email') {
157
+ callbacks.getPendingByAgentAndVisitor = (agentId, visitorId) => databaseAPI.getPendingByAgentAndVisitor(agentId, visitorId);
158
+ }
159
+ if (name === 'wecom') {
160
+ callbacks.onSessionExpired = () => {
161
+ console.log('[Wechat] 会话已过期,请重新扫码登录');
162
+ if (mainWindow && !mainWindow.isDestroyed()) {
163
+ bus.emit('wechat:session-expired');
164
+ }
165
+ };
166
+ }
167
+ if (name === 'voko-email') {
168
+ callbacks.agentEmailApi = deps.agentEmailApi;
169
+ callbacks.db = deps.db;
170
+ }
171
+
172
+ const handler = new HandlerClass(config, callbacks);
173
+ result[`${name}Handler`] = handler;
174
+ handlers[name] = handler;
175
+
176
+ // Email 渠道始终启动 IMAP(IDLE + 30秒轮询),无记录时轮询会快速返回
177
+ {
178
+ handler.start().then(() => {
179
+ console.log(`[${def.displayName}] ✅ ${def.displayName} 处理器已启动 at`, new Date().toISOString());
180
+ }).catch(err => {
181
+ console.error(`[${def.displayName}] ❌ 启动失败:`, err.message);
182
+ });
183
+ }
184
+
185
+ } catch (err) {
186
+ console.error(`[${def.displayName}] ❌ 初始化失败:`, err.message);
187
+ }
188
+ }
189
+
190
+ return result;
191
+ }
192
+
193
+ function getChannelDef(name) {
194
+ return loadDef(name);
195
+ }
196
+
197
+ function reinitializeChannel(name, deps) {
198
+ // 停止旧 handler
199
+ const old = handlers[name];
200
+ if (old && typeof old.stop === 'function') {
201
+ old.stop();
202
+ }
203
+ delete handlers[name];
204
+
205
+ // 只初始化这一个渠道
206
+ const def = loadDef(name);
207
+ if (!def) return null;
208
+
209
+ const { getEnabledChannel, databaseAPI, openclawHandler, mainWindow } = deps;
210
+ const channel = getEnabledChannel(name);
211
+ if (!channel || !channel.enabled) {
212
+ console.log(`[${def.displayName}] 渠道未启用,跳过`);
213
+ return null;
214
+ }
215
+
216
+ const config = def.extractConfig(channel);
217
+ const missingField = def.configFields.find(f => f.required && !config[f.key]);
218
+ if (missingField) {
219
+ console.log(`[${def.displayName}] 配置不完整,跳过`);
220
+ return null;
221
+ }
222
+
223
+ try {
224
+ const HandlerClass = require(def.handlerClass);
225
+ const callbacks = {
226
+ onOwnerReply: createOnOwnerReply(name, deps),
227
+ getInterventionByParentMsgId: (msgId) => databaseAPI.getOwnerInterventionByParentMsgId(msgId),
228
+ isEnabled: () => { const ch = getEnabledChannel(name); return ch && ch.enabled; }
229
+ };
230
+
231
+ if (name === 'wecom' || name === 'feishu') {
232
+ callbacks.getLatestPendingIntervention = () => databaseAPI.getLatestPendingIntervention();
233
+ }
234
+ if (name === 'email') {
235
+ callbacks.getPendingByAgentAndVisitor = (agentId, visitorId) => databaseAPI.getPendingByAgentAndVisitor(agentId, visitorId);
236
+ }
237
+ if (name === 'wecom') {
238
+ callbacks.onSessionExpired = () => {
239
+ bus.emit('wechat:session-expired');
240
+ };
241
+ }
242
+
243
+ const handler = new HandlerClass(config, callbacks);
244
+ handlers[name] = handler;
245
+
246
+ handler.start().then(() => {
247
+ console.log(`[${def.displayName}] ✅ 已启动`);
248
+ }).catch(err => {
249
+ console.error(`[${def.displayName}] ❌ 启动失败:`, err.message);
250
+ });
251
+
252
+ return handler;
253
+ } catch (err) {
254
+ console.error(`[${def.displayName}] ❌ 初始化失败:`, err.message);
255
+ return null;
256
+ }
257
+ }
258
+
259
+ function getRegisteredNames() {
260
+ return REGISTERED;
261
+ }
262
+
263
+ function getHandler(name) {
264
+ return handlers[name] || null;
265
+ }
266
+
267
+ function getAllHandlers() {
268
+ return { ...handlers };
269
+ }
270
+
271
+ module.exports = {
272
+ initializeAllChannels,
273
+ createOnOwnerReply,
274
+ getChannelDef,
275
+ getRegisteredNames,
276
+ getHandler,
277
+ getAllHandlers,
278
+ reinitializeChannel
279
+ };
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Telegram 渠道元信息
3
+ */
4
+ module.exports = {
5
+ name: 'telegram',
6
+ displayName: 'Telegram',
7
+
8
+ configFields: [
9
+ { key: 'botToken', label: 'Bot Token', type: 'text', required: true },
10
+ { key: 'ownerChatId', label: 'Owner Chat ID', type: 'text', required: true },
11
+ { key: 'pollInterval', label: '轮询间隔(秒)', type: 'number', min: 5, max: 60 }
12
+ ],
13
+
14
+ // Telegram 配置表单有「获取 Chat ID」等特殊 UI
15
+ customUI: true,
16
+
17
+ handlerClass: '../server/telegram-handler',
18
+
19
+ extractConfig(channel) {
20
+ return {
21
+ botToken: channel.config?.botToken || channel.botToken,
22
+ ownerChatId: channel.config?.ownerChatId || channel.ownerChatId,
23
+ pollInterval: channel.config?.pollInterval || 10000
24
+ };
25
+ },
26
+
27
+ extraCallbacks: {}
28
+ };
@@ -0,0 +1,7 @@
1
+ module.exports = {
2
+ name: 'voko-email',
3
+ displayName: 'VOKO 邮件',
4
+ configFields: [],
5
+ handlerClass: '../server/voko-email-handler',
6
+ extractConfig: (channel) => ({}),
7
+ };
@@ -0,0 +1,35 @@
1
+ /**
2
+ * 微信 clawbot 渠道元信息
3
+ */
4
+ module.exports = {
5
+ name: 'wecom',
6
+ displayName: '微信clawbot',
7
+
8
+ configFields: [
9
+ { key: 'botToken', label: 'Bot Token', type: 'text', required: true },
10
+ { key: 'ownerUserId', label: 'Owner User ID', type: 'text', required: true },
11
+ { key: 'accountId', label: 'Account ID', type: 'text' },
12
+ { key: 'baseUrl', label: 'Base URL', type: 'text' },
13
+ { key: 'pollInterval', label: '轮询间隔(秒)', type: 'number', min: 1, max: 60 }
14
+ ],
15
+
16
+ handlerClass: '../server/wechat-handler',
17
+
18
+ extractConfig(channel) {
19
+ return {
20
+ baseUrl: channel.config?.baseUrl || 'https://ilinkai.weixin.qq.com',
21
+ botToken: channel.config?.botToken,
22
+ ownerUserId: channel.config?.ownerUserId,
23
+ pollInterval: channel.config?.pollInterval || 35000,
24
+ pollIntervalMs: channel.config?.pollIntervalMs || 1000
25
+ };
26
+ },
27
+
28
+ extraCallbacks: {
29
+ onSessionExpired: null // 由 registry 在初始化时注入
30
+ },
31
+
32
+ test: {
33
+ cliScript: 'wechat-owner-intervention.js'
34
+ }
35
+ };
package/src/cli.js ADDED
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Lite CLI 命令实现
3
+ *
4
+ * 直接复用 core 模块,不调 HTTP / MCP。
5
+ */
6
+
7
+ const { registerCapabilitiesForAgent } = require('./core/register-capabilities');
8
+ const { generateOSSSignature } = require('./server/oss');
9
+
10
+ /**
11
+ * voko list — 列出所有 agents
12
+ */
13
+ function listAgents({ db, databaseAPI, agentManager }) {
14
+ const rows = db.prepare(`
15
+ SELECT agent_id, agent_name, backend_type, publish_status, access_mode, owner_email
16
+ FROM agents ORDER BY created_at DESC
17
+ `).all();
18
+
19
+ if (rows.length === 0) {
20
+ console.log('暂无 agent');
21
+ return;
22
+ }
23
+
24
+ console.log('\n=== Agents ===');
25
+ for (const r of rows) {
26
+ const status = agentManager?.getStatus(r.agent_id);
27
+ const imMark = status?.connected ? '●' : '○';
28
+ console.log(` ${imMark} ${r.agent_name || r.agent_id} (${r.backend_type}) [${r.publish_status}] ${r.access_mode === 'private' ? '🔒' : ''}`);
29
+ console.log(` ID: ${r.agent_id}`);
30
+ if (r.owner_email) console.log(` 邮箱: ${r.owner_email}`);
31
+ }
32
+ console.log('');
33
+ }
34
+
35
+ /**
36
+ * voko status — Lite 运行状态
37
+ */
38
+ function showStatus({ db, databaseAPI, agentManager }) {
39
+ const dbPath = db.name;
40
+ const dbSize = require('fs').existsSync(dbPath)
41
+ ? (require('fs').statSync(dbPath).size / 1024 / 1024).toFixed(1)
42
+ : '?';
43
+
44
+ const agentCount = db.prepare('SELECT COUNT(*) as c FROM agents').get()?.c || 0;
45
+ const publishedCount = db.prepare("SELECT COUNT(*) as c FROM agents WHERE publish_status = 'published'").get()?.c || 0;
46
+ const msgCount = db.prepare('SELECT COUNT(*) as c FROM messages').get()?.c || 0;
47
+ const convCount = db.prepare('SELECT COUNT(*) as c FROM conversations').get()?.c || 0;
48
+
49
+ console.log('\n=== VOKO Lite Status ===');
50
+ console.log(` 版本: ${require('../package.json').version}`);
51
+ console.log(` 运行时间: ${Math.floor(process.uptime() / 60)} 分钟`);
52
+ console.log(` 数据库: ${dbPath}`);
53
+ console.log(` 数据库大小: ${dbSize} MB`);
54
+ console.log(` Agents: ${agentCount} (已发布: ${publishedCount})`);
55
+ console.log(` 消息: ${msgCount}`);
56
+ console.log(` 会话: ${convCount}`);
57
+
58
+ if (agentManager) {
59
+ console.log(` Worker 进程: ${agentManager.workers.size}`);
60
+ for (const [id, entry] of agentManager.workers) {
61
+ const s = agentManager.getStatus(id);
62
+ console.log(` ${s.connected ? '●' : '○'} ${id} (${s.status || 'unknown'})`);
63
+ }
64
+ }
65
+ console.log('');
66
+ }
67
+
68
+ /**
69
+ * voko register-capabilities — 为指定 agent 注册能力
70
+ */
71
+ async function registerCapabilities({ db }, agentId) {
72
+ if (!agentId) {
73
+ console.error('需指定 agent_id: voko register-capabilities <agent_id>');
74
+ return;
75
+ }
76
+ console.log(`正在为 ${agentId} 注册能力...`);
77
+ const result = await registerCapabilitiesForAgent({ db, agentId });
78
+ if (result.success) {
79
+ console.log('✅ 能力注册成功');
80
+ } else {
81
+ console.error('❌ 能力注册失败:', result.error);
82
+ }
83
+ }
84
+
85
+ /**
86
+ * 检查 npm 最新版本
87
+ */
88
+ async function checkVersion() {
89
+ try {
90
+ const pkg = require('../package.json');
91
+ const res = await fetch('https://registry.npmjs.org/@voko/lite/latest', { signal: AbortSignal.timeout(5000) });
92
+ if (!res.ok) return;
93
+ const { version: latest } = await res.json();
94
+ if (latest && latest !== pkg.version) {
95
+ const cmp = pkg.version.localeCompare(latest, undefined, { numeric: true });
96
+ if (cmp < 0) {
97
+ console.error(`[VOKO] 新版本 ${latest} 可用(当前 ${pkg.version}),运行 voko update 升级`);
98
+ }
99
+ }
100
+ } catch (_) {}
101
+ }
102
+
103
+ /**
104
+ * voko update — 升级到最新版
105
+ */
106
+ function updateLite() {
107
+ const pkg = require('../package.json');
108
+ console.error(`正在升级 @voko/lite(当前 ${pkg.version})...`);
109
+ try {
110
+ const { execSync } = require('child_process');
111
+ execSync('npm install -g @voko/lite@latest', { stdio: 'inherit', windowsHide: true });
112
+ console.error('✅ 升级完成,请重启 VOKO');
113
+ process.exit(0);
114
+ } catch (e) {
115
+ console.error('❌ 升级失败:', e.message);
116
+ process.exit(1);
117
+ }
118
+ }
119
+
120
+ module.exports = { listAgents, showStatus, registerCapabilities, checkVersion, updateLite };