@vrs-soft/wecom-aibot-mcp 1.4.0 → 2.3.0

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 (44) hide show
  1. package/README.md +52 -307
  2. package/dist/approval-manager.d.ts +38 -0
  3. package/dist/approval-manager.js +129 -0
  4. package/dist/bin.js +233 -65
  5. package/dist/cc-registry.d.ts +62 -0
  6. package/dist/cc-registry.js +278 -0
  7. package/dist/channel-server.d.ts +15 -0
  8. package/dist/channel-server.js +492 -0
  9. package/dist/channel-server.test.d.ts +5 -0
  10. package/dist/channel-server.test.js +324 -0
  11. package/dist/client-pool.js +4 -3
  12. package/dist/client.js +49 -49
  13. package/dist/config-wizard.d.ts +16 -2
  14. package/dist/config-wizard.js +542 -141
  15. package/dist/connection-log.js +7 -6
  16. package/dist/connection-manager.d.ts +2 -8
  17. package/dist/connection-manager.js +22 -33
  18. package/dist/daemon.js +7 -6
  19. package/dist/headless-state.d.ts +0 -12
  20. package/dist/headless-state.js +11 -35
  21. package/dist/http-server.d.ts +30 -18
  22. package/dist/http-server.js +465 -177
  23. package/dist/index.d.ts +1 -1
  24. package/dist/index.js +1 -1
  25. package/dist/keepalive-monitor.js +5 -4
  26. package/dist/logger.d.ts +51 -0
  27. package/dist/logger.js +84 -0
  28. package/dist/message-bus.d.ts +13 -1
  29. package/dist/message-bus.js +56 -3
  30. package/dist/project-config.d.ts +57 -0
  31. package/dist/project-config.js +218 -7
  32. package/dist/tools/headless.d.ts +8 -0
  33. package/dist/tools/headless.js +248 -0
  34. package/dist/tools/index.js +271 -115
  35. package/dist/tools/messaging.d.ts +7 -0
  36. package/dist/tools/messaging.js +170 -0
  37. package/dist/tools/utils-tools.d.ts +11 -0
  38. package/dist/tools/utils-tools.js +249 -0
  39. package/dist/utils/atomic-write.d.ts +4 -0
  40. package/dist/utils/atomic-write.js +9 -0
  41. package/dist/utils/sanitize.d.ts +59 -0
  42. package/dist/utils/sanitize.js +246 -0
  43. package/package.json +1 -1
  44. package/skills/headless-mode/SKILL.md +144 -134
package/dist/bin.js CHANGED
@@ -13,7 +13,7 @@ import { spawn } from 'child_process';
13
13
  import * as fs from 'fs';
14
14
  import * as path from 'path';
15
15
  import * as os from 'os';
16
- import { runConfigWizard, loadConfig, saveConfig, deleteConfig, deleteMcpConfigInteractive, uninstall, addMcpConfig, detectUserIdFromMessage, ensureHookInstalled, listAllRobots, } from './config-wizard.js';
16
+ import { runConfigWizard, loadConfig, saveConfig, deleteRobotConfigInteractive, uninstall, addMcpConfig, detectUserIdFromMessage, ensureHookInstalled, listAllRobots, ensureGlobalConfigs, } from './config-wizard.js';
17
17
  import { initClient } from './client.js';
18
18
  import { registerTools } from './tools/index.js';
19
19
  import { startHttpServer, stopHttpServer, HTTP_PORT } from './http-server.js';
@@ -21,7 +21,8 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
21
21
  import { getAllConnectionStates } from './connection-manager.js';
22
22
  import { loadStats, cleanupOldLogs } from './connection-log.js';
23
23
  import { startKeepaliveMonitor, stopKeepaliveMonitor } from './keepalive-monitor.js';
24
- const VERSION = '1.2.0';
24
+ import { logger } from './logger.js';
25
+ const VERSION = '2.0.0';
25
26
  const PID_FILE = path.join(os.homedir(), '.wecom-aibot-mcp', 'server.pid');
26
27
  function showHelp() {
27
28
  console.log(`
@@ -36,15 +37,22 @@ function showHelp() {
36
37
  选项:
37
38
  --help, -h 显示帮助信息
38
39
  --version, -v 显示版本号
40
+ --upgrade 强制升级全局配置(覆盖 MCP 配置、权限、skill)
41
+ --reinstall 重新安装全局配置(删除后重新写入,保留机器人配置)
39
42
  --start 启动 MCP Server(后台服务模式)
40
43
  --stop 停止 MCP Server
41
44
  --debug 前台启动 MCP Server(日志直接输出到终端,用于调试)
45
+ --channel 启动 Channel MCP Proxy(stdio 代理 + SSE 唤醒)
46
+ --http-only 仅启动 HTTP Server(远程部署场景,不安装 Channel MCP 配置)
47
+ --channel-only 仅配置 Channel MCP(本地连接远程 HTTP Server)
42
48
  --status 显示服务状态和机器人配置
43
49
  --config 重新配置默认机器人(修改 Bot ID / Secret / 目标用户)
44
50
  --add 添加新的机器人配置(多机器人场景)
51
+ --rename [名称] 重命名机器人(可选参数:旧名称,交互式输入新名称)
45
52
  --list 列出所有已配置的机器人及其占用状态
46
- --delete [名称] 删除指定的机器人配置(无参数则显示列表选择)
53
+ --delete [名称] 删除指定的机器人配置(保留 MCP 配置)
47
54
  --uninstall 卸载并删除所有配置(包括 MCP 配置、hook、skill)
55
+ --clean-cache 清空 CC 注册表缓存(清理异常断线残留的 ccId)
48
56
 
49
57
  使用流程:
50
58
  1. 首次安装: npx @vrs-soft/wecom-aibot-mcp
@@ -58,18 +66,34 @@ function showHelp() {
58
66
 
59
67
  4. 停止服务: npx @vrs-soft/wecom-aibot-mcp --stop
60
68
 
61
- MCP 配置(HTTP Transport):
69
+ 拆分部署(远程 HTTP + 本地 Channel):
62
70
 
63
- 编辑 ~/.claude.json:
71
+ 远程服务器:
72
+ npx @vrs-soft/wecom-aibot-mcp --http-only --start
73
+ # 只启动 HTTP Server,不写入本地 MCP 配置
64
74
 
65
- {
66
- "mcpServers": {
67
- "wecom-aibot": {
68
- "type": "http",
69
- "url": "http://127.0.0.1:18963/mcp"
70
- }
71
- }
72
- }
75
+ 本地机器:
76
+ MCP_URL=http://远程IP:18963 npx @vrs-soft/wecom-aibot-mcp --channel-only
77
+ # 必须通过 MCP_URL 指定远程 HTTP MCP 地址
78
+ # 只配置 Channel MCP,连接远程 HTTP Server
79
+
80
+ MCP 配置(默认安装同时配置两种模式):
81
+
82
+ HTTP Transport(轮询模式):
83
+ "wecom-aibot": {
84
+ "type": "http",
85
+ "url": "http://127.0.0.1:18963/mcp"
86
+ }
87
+
88
+ Channel Transport(SSE 推送模式):
89
+ "wecom-aibot-channel": {
90
+ "command": "npx",
91
+ "args": ["@vrs-soft/wecom-aibot-mcp", "--channel"]
92
+ }
93
+
94
+ Channel 模式优势:微信消息自动唤醒 agent,无需主动轮询
95
+ 启动 Channel 模式(研究预览):
96
+ claude --dangerously-load-development-channels server:wecom-aibot-channel
73
97
 
74
98
  更多信息: https://github.com/eric2877/wecom-aibot-mcp
75
99
  `);
@@ -98,11 +122,11 @@ function showStatus() {
98
122
  for (const robot of allRobots) {
99
123
  const usage = robotUsage.get(robot.name);
100
124
  const statusTag = usage ? ` [使用中]` : '';
101
- console.log(` ${robot.name}${statusTag}`);
102
- console.log(` Bot ID: ${robot.botId}`);
103
- console.log(` 目标用户: ${robot.targetUserId}`);
125
+ console.log(` Bot名称: ${robot.name}${statusTag}`);
126
+ console.log(` Bot ID${robot.botId}`);
127
+ console.log(` 目标用户:${robot.targetUserId}`);
104
128
  if (usage) {
105
- console.log(` 使用者: ${usage.agentName}`);
129
+ console.log(` 使用者: ${usage.agentName}`);
106
130
  }
107
131
  console.log('');
108
132
  }
@@ -157,7 +181,7 @@ function stopServer() {
157
181
  return true;
158
182
  }
159
183
  catch (err) {
160
- console.error('[mcp] 停止服务失败:', err);
184
+ logger.error('[mcp] 停止服务失败:', err);
161
185
  if (fs.existsSync(PID_FILE)) {
162
186
  fs.unlinkSync(PID_FILE);
163
187
  }
@@ -181,14 +205,20 @@ async function waitForConnection(client, timeoutMs = 10000) {
181
205
  });
182
206
  }
183
207
  // 启动 MCP Server(前台运行,供 --start 使用)
184
- async function startMcpServerForeground() {
208
+ async function startMcpServerForeground(isDebug = false) {
185
209
  const savedConfig = loadConfig();
186
210
  if (!savedConfig || !savedConfig.botId || !savedConfig.secret || !savedConfig.targetUserId) {
187
- console.error('[mcp] 未找到配置,请先运行: npx @vrs-soft/wecom-aibot-mcp');
211
+ logger.error('[mcp] 未找到配置,请先运行: npx @vrs-soft/wecom-aibot-mcp');
188
212
  process.exit(1);
189
213
  }
190
214
  // 写入 PID 文件
191
215
  fs.writeFileSync(PID_FILE, String(process.pid));
216
+ // Debug 模式:创建 debug 标记文件
217
+ if (isDebug) {
218
+ const debugFile = path.join(os.homedir(), '.wecom-aibot-mcp', 'debug');
219
+ fs.writeFileSync(debugFile, 'true');
220
+ console.log('[mcp] Debug 标记文件已创建');
221
+ }
192
222
  // 确保 hook 已安装
193
223
  ensureHookInstalled();
194
224
  // 加载统计并清理旧日志
@@ -201,20 +231,20 @@ async function startMcpServerForeground() {
201
231
  });
202
232
  registerTools(server);
203
233
  // 启动 HTTP 服务
204
- console.log('');
205
- console.log(' ╔════════════════════════════════════════════════════════╗');
206
- console.log(` ║ 企业微信智能机器人 MCP 服务 v${VERSION} ║`);
207
- console.log(' ║ Claude Code 审批通道 ║');
208
- console.log(' ╚════════════════════════════════════════════════════════╝');
209
- console.log('');
210
- console.log(`[mcp] 启动 MCP HTTP Server (端口: ${HTTP_PORT})...`);
234
+ logger.log('');
235
+ logger.log(' ╔════════════════════════════════════════════════════════╗');
236
+ logger.log(` ║ 企业微信智能机器人 MCP 服务 v${VERSION} ║`);
237
+ logger.log(' ║ Claude Code 审批通道 ║');
238
+ logger.log(' ╚════════════════════════════════════════════════════════╝');
239
+ logger.log('');
240
+ logger.log(`[mcp] 启动 MCP HTTP Server (端口: ${HTTP_PORT})...`);
211
241
  await startHttpServer(server);
212
242
  startKeepaliveMonitor();
213
- console.log(`[mcp] MCP Server 已就绪`);
214
- console.log(`[mcp] HTTP endpoint: http://127.0.0.1:${HTTP_PORT}/mcp`);
215
- console.log(`[mcp] 健康检查: http://127.0.0.1:${HTTP_PORT}/health`);
216
- console.log(`[mcp] 微信模式:enter_headless_mode 时建立连接`);
217
- console.log(`[mcp] PID: ${process.pid}`);
243
+ logger.log(`[mcp] MCP Server 已就绪`);
244
+ logger.log(`[mcp] HTTP endpoint: http://127.0.0.1:${HTTP_PORT}/mcp`);
245
+ logger.log(`[mcp] 健康检查: http://127.0.0.1:${HTTP_PORT}/health`);
246
+ logger.log(`[mcp] 微信模式:enter_headless_mode 时建立连接`);
247
+ logger.log(`[mcp] PID: ${process.pid}`);
218
248
  // 退出处理
219
249
  const gracefulShutdown = () => {
220
250
  console.log('[mcp] 正在关闭...');
@@ -223,6 +253,14 @@ async function startMcpServerForeground() {
223
253
  if (fs.existsSync(PID_FILE)) {
224
254
  fs.unlinkSync(PID_FILE);
225
255
  }
256
+ // Debug 模式:删除 debug 标记文件
257
+ if (isDebug) {
258
+ const debugFile = path.join(os.homedir(), '.wecom-aibot-mcp', 'debug');
259
+ if (fs.existsSync(debugFile)) {
260
+ fs.unlinkSync(debugFile);
261
+ console.log('[mcp] Debug 标记文件已删除');
262
+ }
263
+ }
226
264
  process.exit(0);
227
265
  };
228
266
  process.on('SIGINT', gracefulShutdown);
@@ -233,7 +271,7 @@ function startMcpServerBackground() {
233
271
  // 检查配置是否存在
234
272
  const savedConfig = loadConfig();
235
273
  if (!savedConfig || !savedConfig.botId || !savedConfig.secret || !savedConfig.targetUserId) {
236
- console.error('[mcp] 未找到配置,请先运行: npx @vrs-soft/wecom-aibot-mcp');
274
+ logger.error('[mcp] 未找到配置,请先运行: npx @vrs-soft/wecom-aibot-mcp');
237
275
  process.exit(1);
238
276
  }
239
277
  // 检查是否已运行
@@ -249,13 +287,22 @@ function startMcpServerBackground() {
249
287
  });
250
288
  child.unref();
251
289
  console.log('[mcp] MCP Server 已在后台启动');
252
- console.log(`[mcp] HTTP endpoint: http://127.0.0.1:18963/mcp`);
290
+ logger.log(`[mcp] HTTP endpoint: http://127.0.0.1:18963/mcp`);
253
291
  console.log('[mcp] 健康检查: curl http://127.0.0.1:18963/health');
254
292
  console.log('[mcp] 停止服务: npx @vrs-soft/wecom-aibot-mcp --stop');
255
293
  console.log('[mcp] 调试模式: npx @vrs-soft/wecom-aibot-mcp --debug');
256
294
  }
257
295
  async function main() {
258
296
  const args = process.argv.slice(2);
297
+ // 确定安装模式
298
+ const installMode = args.includes('--http-only') ? 'http-only' :
299
+ args.includes('--channel-only') ? 'channel-only' : 'full';
300
+ // --reinstall 命令需要先删除再安装,跳过开头的 ensureGlobalConfigs
301
+ // --http-only 模式不需要写 MCP 配置
302
+ if (!args.includes('--reinstall') && !args.includes('--http-only')) {
303
+ // 强制覆盖所有全局配置(不依赖智能体)
304
+ ensureGlobalConfigs(installMode);
305
+ }
259
306
  // 解析命令行参数
260
307
  if (args.includes('--help') || args.includes('-h')) {
261
308
  showHelp();
@@ -265,6 +312,68 @@ async function main() {
265
312
  showVersion();
266
313
  process.exit(0);
267
314
  }
315
+ // --upgrade 命令:强制升级全局配置(已在启动时执行,这里显示结果)
316
+ if (args.includes('--upgrade')) {
317
+ console.log('\n[mcp] ✅ 全局配置已更新完成!');
318
+ console.log('[mcp] 配置位置:');
319
+ console.log(' - ~/.claude.json (MCP Server 配置)');
320
+ console.log(' - ~/.claude/settings.local.json (权限和 Hook)');
321
+ console.log(' - ~/.wecom-aibot-mcp/version.json (版本记录)');
322
+ console.log('\n[mcp] 请重启 Claude Code 以加载最新配置');
323
+ process.exit(0);
324
+ }
325
+ // --reinstall 命令:删除所有全局配置(保留机器人配置)后重新安装
326
+ if (args.includes('--reinstall')) {
327
+ logger.log('\n[mcp] 重新安装全局配置...');
328
+ console.log('[mcp] 保留所有机器人配置: ~/.wecom-aibot-mcp/config.json 和 robot-*.json');
329
+ const CLAUDE_CONFIG_FILE = path.join(os.homedir(), '.claude.json');
330
+ const CLAUDE_SETTINGS_FILE = path.join(os.homedir(), '.claude', 'settings.local.json');
331
+ const VERSION_FILE = path.join(os.homedir(), '.wecom-aibot-mcp', 'version.json');
332
+ const HOOK_SCRIPT = path.join(os.homedir(), '.wecom-aibot-mcp', 'permission-hook.sh');
333
+ // 1. 删除 ~/.claude.json 中的 wecom-aibot 配置
334
+ if (fs.existsSync(CLAUDE_CONFIG_FILE)) {
335
+ const content = fs.readFileSync(CLAUDE_CONFIG_FILE, 'utf-8');
336
+ const config = JSON.parse(content);
337
+ if (config.mcpServers?.['wecom-aibot']) {
338
+ delete config.mcpServers['wecom-aibot'];
339
+ fs.writeFileSync(CLAUDE_CONFIG_FILE, JSON.stringify(config, null, 2));
340
+ console.log('[mcp] 已删除 ~/.claude.json 中的 wecom-aibot 配置');
341
+ }
342
+ }
343
+ // 2. 删除 ~/.claude/settings.local.json 中的权限和 Hook
344
+ if (fs.existsSync(CLAUDE_SETTINGS_FILE)) {
345
+ const content = fs.readFileSync(CLAUDE_SETTINGS_FILE, 'utf-8');
346
+ const config = JSON.parse(content);
347
+ if (config.permissions?.allow) {
348
+ config.permissions.allow = config.permissions.allow.filter((p) => !p.startsWith('mcp__wecom-aibot__'));
349
+ console.log('[mcp] 已删除 wecom-aibot 工具权限');
350
+ }
351
+ if (config.hooks?.PermissionRequest) {
352
+ config.hooks.PermissionRequest = config.hooks.PermissionRequest.filter((h) => !h.hooks?.some?.((hook) => hook.command?.includes?.('wecom-aibot-mcp')));
353
+ if (config.hooks.PermissionRequest.length === 0) {
354
+ delete config.hooks.PermissionRequest;
355
+ }
356
+ console.log('[mcp] 已删除 PermissionRequest hook');
357
+ }
358
+ fs.writeFileSync(CLAUDE_SETTINGS_FILE, JSON.stringify(config, null, 2));
359
+ }
360
+ // 3. 删除版本文件
361
+ if (fs.existsSync(VERSION_FILE)) {
362
+ fs.unlinkSync(VERSION_FILE);
363
+ console.log('[mcp] 已删除 ~/.wecom-aibot-mcp/version.json');
364
+ }
365
+ // 4. 删除 hook 脚本
366
+ if (fs.existsSync(HOOK_SCRIPT)) {
367
+ fs.unlinkSync(HOOK_SCRIPT);
368
+ console.log('[mcp] 已删除 ~/.wecom-aibot-mcp/permission-hook.sh');
369
+ }
370
+ // 5. 重新安装全局配置
371
+ logger.log('\n[mcp] 正在重新安装...');
372
+ ensureGlobalConfigs();
373
+ logger.log('\n[mcp] ✅ 重新安装完成!');
374
+ console.log('[mcp] 请重启 Claude Code 以加载最新配置');
375
+ process.exit(0);
376
+ }
268
377
  if (args.includes('--status') || args.includes('--list')) {
269
378
  showStatus();
270
379
  process.exit(0);
@@ -274,6 +383,27 @@ async function main() {
274
383
  stopServer();
275
384
  process.exit(0);
276
385
  }
386
+ // --clean-cache 命令:清空 CC 注册表缓存
387
+ if (args.includes('--clean-cache')) {
388
+ if (!isServerRunning()) {
389
+ console.log('[mcp] 服务未运行,无需清理缓存');
390
+ process.exit(0);
391
+ }
392
+ try {
393
+ const res = await fetch(`http://127.0.0.1:${HTTP_PORT}/admin/clean-cache`, { method: 'POST' });
394
+ const data = await res.json();
395
+ if (data.ok) {
396
+ console.log(`[mcp] 已清空 CC 注册表,共清理 ${data.cleared} 条`);
397
+ if (data.entries.length > 0) {
398
+ console.log(`[mcp] 已清理: ${data.entries.join(', ')}`);
399
+ }
400
+ }
401
+ }
402
+ catch (err) {
403
+ console.error('[mcp] 清理失败:', err);
404
+ }
405
+ process.exit(0);
406
+ }
277
407
  // --uninstall 命令:先停止服务再卸载
278
408
  if (args.includes('--uninstall')) {
279
409
  if (isServerRunning()) {
@@ -290,8 +420,8 @@ async function main() {
290
420
  // --delete 命令:删除单个机器人配置
291
421
  const deleteIndex = args.indexOf('--delete');
292
422
  if (deleteIndex !== -1) {
293
- const instanceName = args[deleteIndex + 1]; // 可选参数:实例名
294
- await deleteMcpConfigInteractive(instanceName);
423
+ const robotName = args[deleteIndex + 1]; // 可选参数:机器人名称
424
+ await deleteRobotConfigInteractive(robotName);
295
425
  process.exit(0);
296
426
  }
297
427
  // --start --foreground:前台启动(内部调用,输出到日志文件)
@@ -299,12 +429,52 @@ async function main() {
299
429
  await startMcpServerForeground();
300
430
  return; // 保持运行,不 exit
301
431
  }
432
+ // --channel:启动 Channel MCP 代理(stdio)
433
+ // 注意:必须在 --debug 之前检查,否则 --channel --debug 会先触发 HTTP Server
434
+ if (args.includes('--channel')) {
435
+ // 检查 HTTP MCP 的 debug 标记文件
436
+ const debugFile = path.join(os.homedir(), '.wecom-aibot-mcp', 'debug');
437
+ const isDebug = fs.existsSync(debugFile) || args.includes('--debug');
438
+ if (isDebug) {
439
+ console.log('[channel] Debug 模式:日志输出到 stderr(跟随 HTTP MCP debug)');
440
+ if (!fs.existsSync(debugFile)) {
441
+ fs.writeFileSync(debugFile, 'true');
442
+ }
443
+ }
444
+ console.log('[channel] Starting Channel MCP Proxy...');
445
+ const { startChannelServer } = await import('./channel-server.js');
446
+ await startChannelServer();
447
+ // Channel MCP 退出时不删除 debug 文件(由 HTTP MCP 管理)
448
+ return; // 保持运行,不 exit
449
+ }
302
450
  // --debug:前台启动,日志直接输出到终端
303
451
  if (args.includes('--debug')) {
304
452
  console.log('[mcp] Debug 模式:前台运行,Ctrl+C 退出');
305
- await startMcpServerForeground();
453
+ await startMcpServerForeground(true);
306
454
  return;
307
455
  }
456
+ // --http-only:仅启动 HTTP Server(远程部署场景)
457
+ if (args.includes('--http-only') && !args.includes('--start')) {
458
+ console.log('[mcp] HTTP-only 模式:仅启动 HTTP Server');
459
+ console.log('[mcp] 不写入 MCP 配置(远程部署场景)');
460
+ console.log('[mcp] 使用 --http-only --start 启动服务');
461
+ process.exit(0);
462
+ }
463
+ // --channel-only:仅配置 Channel MCP(本地连接远程 HTTP Server)
464
+ if (args.includes('--channel-only')) {
465
+ const mcpUrl = process.env.MCP_URL;
466
+ if (!mcpUrl) {
467
+ console.log('[mcp] ❌ Channel-only 模式需要指定远程 HTTP MCP 地址');
468
+ console.log('[mcp] 请设置环境变量 MCP_URL:');
469
+ console.log('[mcp] MCP_URL=http://远程IP:18963 npx @vrs-soft/wecom-aibot-mcp --channel-only');
470
+ process.exit(1);
471
+ }
472
+ console.log(`[mcp] Channel-only 模式:Channel MCP 已配置`);
473
+ console.log(`[mcp] 连接地址: ${mcpUrl}`);
474
+ console.log('[mcp] 请确保远程 HTTP Server 已启动');
475
+ console.log('[mcp] 启动 Channel: npx @vrs-soft/wecom-aibot-mcp --channel');
476
+ process.exit(0);
477
+ }
308
478
  // --start:后台启动
309
479
  if (args.includes('--start')) {
310
480
  startMcpServerBackground();
@@ -312,12 +482,12 @@ async function main() {
312
482
  }
313
483
  const reconfig = args.includes('--config');
314
484
  const isInteractive = process.stdin.isTTY; // 是否为用户交互模式
315
- console.log('');
316
- console.log(' ╔════════════════════════════════════════════════════════╗');
317
- console.log(` ║ 企业微信智能机器人 MCP 服务 v${VERSION} ║`);
318
- console.log(' ║ Claude Code 审批通道 ║');
319
- console.log(' ╚════════════════════════════════════════════════════════╝');
320
- console.log('');
485
+ logger.log('');
486
+ logger.log(' ╔════════════════════════════════════════════════════════╗');
487
+ logger.log(` ║ 企业微信智能机器人 MCP 服务 v${VERSION} ║`);
488
+ logger.log(' ║ Claude Code 审批通道 ║');
489
+ logger.log(' ╚════════════════════════════════════════════════════════╝');
490
+ logger.log('');
321
491
  // 加载统计并清理旧日志(保留 1 小时)
322
492
  loadStats();
323
493
  cleanupOldLogs(1 / 24);
@@ -348,8 +518,8 @@ async function main() {
348
518
  }
349
519
  else {
350
520
  // 非 TTY 模式(MCP HTTP),必须有配置
351
- console.error('[config] 未找到配置,且当前为非交互模式。');
352
- console.error('[config] 请在终端运行: npx @vrs-soft/wecom-aibot-mcp --config');
521
+ logger.error('[config] 未找到配置,且当前为非交互模式。');
522
+ logger.error('[config] 请在终端运行: npx @vrs-soft/wecom-aibot-mcp --config');
353
523
  process.exit(1);
354
524
  }
355
525
  }
@@ -362,11 +532,7 @@ async function main() {
362
532
  const tempClient = initClient(config.botId, config.secret, config.targetUserId || 'placeholder', 'temp-validation');
363
533
  const connected = await waitForConnection(tempClient, 10000);
364
534
  if (!connected) {
365
- console.log('[mcp] 连接失败,可能是配置错误或机器人未授权');
366
- console.log('[mcp] 请检查上面的错误提示,修复后重新配置');
367
- // 删除无效配置,让用户重新输入
368
- deleteConfig();
369
- console.log('\n请检查:');
535
+ console.log('[mcp] ❌ 连接失败,请检查:');
370
536
  console.log(' 1. Bot ID 和 Secret 是否正确');
371
537
  console.log(' 2. 新建机器人需等待约 2 分钟同步');
372
538
  console.log(' 3. 是否已完成授权(机器人详情 → 可使用权限 → 授权)');
@@ -375,35 +541,37 @@ async function main() {
375
541
  process.exit(1);
376
542
  }
377
543
  // 连接成功
378
- console.log('\n[mcp] ✅ 机器人连接成功!');
379
- // 提示用户发送消息来识别用户 ID
380
- const userId = await detectUserIdFromMessage(tempClient, 180);
381
- if (!userId) {
382
- console.log('\n[mcp] 未能在规定时间内识别用户 ID');
383
- console.log('[mcp] 请重新运行配置:npx @vrs-soft/wecom-aibot-mcp --config');
384
- tempClient.disconnect();
385
- process.exit(1);
544
+ logger.log('\n[mcp] ✅ 机器人凭证验证成功!');
545
+ // 保存配置(使用原用户 ID 或等待识别)
546
+ if (!config.targetUserId || config.targetUserId === 'placeholder' || config.targetUserId === '') {
547
+ // 新机器人,需要识别用户 ID
548
+ const userId = await detectUserIdFromMessage(tempClient, 180);
549
+ if (!userId) {
550
+ logger.log('\n[mcp] 未能在规定时间内识别用户 ID');
551
+ console.log('[mcp] 请重新运行配置:npx @vrs-soft/wecom-aibot-mcp --config');
552
+ tempClient.disconnect();
553
+ process.exit(1);
554
+ }
555
+ config.targetUserId = userId;
386
556
  }
387
- // 更新配置中的用户 ID
388
- config.targetUserId = userId;
389
557
  // 保存最终配置
390
558
  saveConfig(config, instanceName);
391
- console.log('\n[mcp] ✅ 配置完成!');
392
- console.log(`[mcp] 用户 ID: ${userId}`);
559
+ logger.log('\n[mcp] ✅ 配置完成!');
560
+ logger.log(`[mcp] 用户 ID: ${config.targetUserId}`);
393
561
  // 配置完成后断开连接
394
562
  tempClient.disconnect();
395
563
  // 首次安装后自动后台启动服务
396
- console.log('\n[mcp] 正在后台启动 MCP Server...');
564
+ logger.log('\n[mcp] 正在后台启动 MCP Server...');
397
565
  startMcpServerBackground();
398
566
  console.log('[mcp] 请重启 Claude Code 以加载 MCP 服务\n');
399
567
  process.exit(0);
400
568
  }
401
569
  // 已有配置,显示状态并提示启动命令
402
570
  showStatus();
403
- console.log('\n[mcp] 使用 --start 启动服务,--stop 停止服务');
571
+ logger.log('\n[mcp] 使用 --start 启动服务,--stop 停止服务');
404
572
  console.log('[mcp] 命令: npx @vrs-soft/wecom-aibot-mcp --start\n');
405
573
  }
406
574
  main().catch((err) => {
407
- console.error('[mcp] 启动失败:', err);
575
+ logger.error('[mcp] 启动失败:', err);
408
576
  process.exit(1);
409
577
  });
@@ -0,0 +1,62 @@
1
+ /**
2
+ * ccId 注册表
3
+ *
4
+ * 管理 ~/.wecom-aibot-mcp/cc-registry.json
5
+ * 维护 ccId → { robotName, lastActive, createdAt } 的映射
6
+ *
7
+ * 文件锁通过 .lock 文件实现(EEXIST 原子性)
8
+ */
9
+ /**
10
+ * 设置配置目录(仅用于测试)
11
+ */
12
+ export declare function setConfigDir(dir: string): void;
13
+ export interface CcRegistryEntry {
14
+ robotName: string;
15
+ lastActive: number;
16
+ createdAt: number;
17
+ lastNotified?: number;
18
+ }
19
+ type Registry = Record<string, CcRegistryEntry>;
20
+ /**
21
+ * 公开接口:独立调用时使用(内部会获取锁)
22
+ */
23
+ export declare function cleanupExpiredEntries(): void;
24
+ export type RegisterResult = 'registered' | 'renewed' | 'occupied';
25
+ /**
26
+ * 注册 ccId
27
+ * - 新 ccId → registered
28
+ * - 已存在且 robotName 相同 → renewed(续期)
29
+ * - 已存在且 robotName 不同 → occupied(被占用)
30
+ */
31
+ export declare function registerCcId(ccId: string, robotName: string): RegisterResult;
32
+ /**
33
+ * 注销 ccId
34
+ */
35
+ export declare function unregisterCcId(ccId: string): void;
36
+ /**
37
+ * 检查 ccId 是否已注册
38
+ */
39
+ export declare function isCcIdRegistered(ccId: string): boolean;
40
+ /**
41
+ * 更新 ccId 的最后活跃时间
42
+ */
43
+ export declare function touchCcId(ccId: string): void;
44
+ /**
45
+ * 获取 ccId 绑定的机器人名称
46
+ */
47
+ export declare function getCcIdBinding(ccId: string): {
48
+ robotName: string;
49
+ } | null;
50
+ /**
51
+ * 获取完整注册表(调试用)
52
+ */
53
+ export declare function getRegistry(): Registry;
54
+ /**
55
+ * 启动心跳检测(5 分钟扫描一次)
56
+ */
57
+ export declare function startHeartbeatMonitor(): void;
58
+ /**
59
+ * 停止心跳检测
60
+ */
61
+ export declare function stopHeartbeatMonitor(): void;
62
+ export {};