@vrs-soft/wecom-aibot-mcp 1.5.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.
@@ -5,6 +5,7 @@
5
5
  */
6
6
  import * as fs from 'fs';
7
7
  import * as path from 'path';
8
+ import { logger } from './logger.js';
8
9
  const CONFIG_DIR = path.join(process.env.HOME || '', '.wecom-aibot-mcp');
9
10
  const LOG_FILE = path.join(CONFIG_DIR, 'connection.log');
10
11
  // 全局状态
@@ -30,20 +31,20 @@ function writeLog(record) {
30
31
  const time = record.timestamp;
31
32
  switch (record.event) {
32
33
  case 'connected':
33
- console.log(`[${time}] [conn] WebSocket 连接已建立`);
34
+ logger.log(`[${time}] [conn] WebSocket 连接已建立`);
34
35
  break;
35
36
  case 'authenticated':
36
- console.log(`[${time}] [conn] 认证成功`);
37
+ logger.log(`[${time}] [conn] 认证成功`);
37
38
  break;
38
39
  case 'disconnected':
39
40
  const duration = record.connectionDuration ? ` (持续 ${record.connectionDuration}s)` : '';
40
- console.log(`[${time}] [conn] 连接断开: ${record.reason || '未知'}${duration}`);
41
+ logger.log(`[${time}] [conn] 连接断开: ${record.reason || '未知'}${duration}`);
41
42
  break;
42
43
  case 'reconnecting':
43
- console.log(`[${time}] [conn] 正在重连 (第 ${record.attempt} 次)`);
44
+ logger.log(`[${time}] [conn] 正在重连 (第 ${record.attempt} 次)`);
44
45
  break;
45
46
  case 'error':
46
- console.error(`[${time}] [conn] 错误: ${record.errorMessage}`);
47
+ logger.error(`[${time}] [conn] 错误: ${record.errorMessage}`);
47
48
  break;
48
49
  }
49
50
  }
@@ -199,7 +200,7 @@ export function cleanupOldLogs(daysToKeep = 30) {
199
200
  }
200
201
  });
201
202
  fs.writeFileSync(LOG_FILE, recentLines.join('\n') + '\n', 'utf-8');
202
- console.log(`[conn] 已清理 ${lines.length - recentLines.length} 条旧日志`);
203
+ logger.log(`[conn] 已清理 ${lines.length - recentLines.length} 条旧日志`);
203
204
  }
204
205
  // 导出日志文件路径
205
206
  export function getLogFilePath() {
@@ -13,14 +13,6 @@
13
13
  * - 集成消息总线(用户消息通过 SSE 推送)
14
14
  */
15
15
  import { WecomClient } from './client.js';
16
- /**
17
- * 检查机器人是否被占用(当前进程内)
18
- */
19
- export declare function isRobotOccupied(robotName: string): boolean;
20
- /**
21
- * 获取占用机器人的智能体名称
22
- */
23
- export declare function getRobotOccupiedBy(robotName: string): string | undefined;
24
16
  /**
25
17
  * 连接到指定机器人
26
18
  * 注意:MCP Server 启动时已自动连接所有机器人
@@ -17,6 +17,7 @@ import * as path from 'path';
17
17
  import * as os from 'os';
18
18
  import { WecomClient } from './client.js';
19
19
  import { listAllRobots } from './config-wizard.js';
20
+ import { logger } from './logger.js';
20
21
  // 连接池:robotName → ConnectionState
21
22
  const connectionPool = new Map();
22
23
  const CONFIG_DIR = path.join(os.homedir(), '.wecom-aibot-mcp');
@@ -68,19 +69,6 @@ function waitForConnection(client, timeoutMs) {
68
69
  }, 500);
69
70
  });
70
71
  }
71
- /**
72
- * 检查机器人是否被占用(当前进程内)
73
- */
74
- export function isRobotOccupied(robotName) {
75
- return connectionPool.has(robotName);
76
- }
77
- /**
78
- * 获取占用机器人的智能体名称
79
- */
80
- export function getRobotOccupiedBy(robotName) {
81
- const state = connectionPool.get(robotName);
82
- return state?.agentName;
83
- }
84
72
  /**
85
73
  * 连接到指定机器人
86
74
  * 注意:MCP Server 启动时已自动连接所有机器人
@@ -125,7 +113,7 @@ export async function connectRobot(robotName, agentName) {
125
113
  agentName,
126
114
  };
127
115
  connectionPool.set(robot.name, state);
128
- console.log(`[connection] 已连接机器人: ${robot.name}`);
116
+ logger.log(`[connection] 已连接机器人: ${robot.name}`);
129
117
  return {
130
118
  success: true,
131
119
  client,
@@ -139,7 +127,7 @@ export function disconnectRobot(robotName) {
139
127
  if (state) {
140
128
  state.client.disconnect();
141
129
  connectionPool.delete(robotName);
142
- console.log(`[connection] 已断开机器人: ${robotName}`);
130
+ logger.log(`[connection] 已断开机器人: ${robotName}`);
143
131
  }
144
132
  }
145
133
  /**
@@ -157,16 +145,16 @@ export async function getClient(robotName) {
157
145
  // 断开了,尝试重连
158
146
  const robot = await findRobotConfig(state.robotName);
159
147
  if (robot) {
160
- console.log(`[connection] 重连机器人: ${robot.name}`);
148
+ logger.log(`[connection] 重连机器人: ${robot.name}`);
161
149
  state.client = new WecomClient(robot.botId, robot.secret, robot.targetUserId, robot.name);
162
150
  state.client.connect();
163
151
  const connected = await waitForConnection(state.client, 5000);
164
152
  if (connected) {
165
- console.log(`[connection] 重连成功: ${robot.name}`);
153
+ logger.log(`[connection] 重连成功: ${robot.name}`);
166
154
  return state.client;
167
155
  }
168
156
  else {
169
- console.log(`[connection] 重连失败: ${robot.name}`);
157
+ logger.log(`[connection] 重连失败: ${robot.name}`);
170
158
  return null;
171
159
  }
172
160
  }
@@ -224,21 +212,21 @@ export function updateAgentName(robotName, agentName) {
224
212
  export async function connectAllRobots() {
225
213
  const robots = listAllRobots();
226
214
  if (robots.length === 0) {
227
- console.log('[connection] 未配置任何机器人');
215
+ logger.log('[connection] 未配置任何机器人');
228
216
  return;
229
217
  }
230
218
  // 多机器人场景:不自动连接,等待用户选择
231
219
  if (robots.length > 1) {
232
- console.log(`[connection] 检测到 ${robots.length} 个机器人,等待用户选择`);
220
+ logger.log(`[connection] 检测到 ${robots.length} 个机器人,等待用户选择`);
233
221
  return;
234
222
  }
235
223
  // 单机器人场景:自动连接
236
- console.log(`[connection] 自动连接机器人: ${robots[0].name}`);
224
+ logger.log(`[connection] 自动连接机器人: ${robots[0].name}`);
237
225
  const result = await connectRobot(robots[0].name);
238
226
  if (result.success) {
239
- console.log(`[connection] ✅ ${robots[0].name} 已连接`);
227
+ logger.log(`[connection] ✅ ${robots[0].name} 已连接`);
240
228
  }
241
229
  else {
242
- console.log(`[connection] ❌ ${robots[0].name} 连接失败: ${result.error}`);
230
+ logger.log(`[connection] ❌ ${robots[0].name} 连接失败: ${result.error}`);
243
231
  }
244
232
  }
package/dist/daemon.js CHANGED
@@ -12,6 +12,7 @@ import * as fs from 'fs';
12
12
  import * as path from 'path';
13
13
  import * as os from 'os';
14
14
  import { WecomClient } from './client.js';
15
+ import { logger } from './logger.js';
15
16
  const CONFIG_DIR = path.join(os.homedir(), '.wecom-aibot-mcp');
16
17
  const DAEMON_PORT = 18964;
17
18
  const PID_FILE = path.join(CONFIG_DIR, 'daemon.pid');
@@ -311,7 +312,7 @@ class ConnectionDaemon {
311
312
  log(message) {
312
313
  const timestamp = new Date().toISOString();
313
314
  const line = `[${timestamp}] ${message}`;
314
- console.log(line);
315
+ logger.log(line);
315
316
  fs.appendFileSync(LOG_FILE, line + '\n');
316
317
  }
317
318
  async shutdown() {
@@ -341,15 +342,15 @@ if (command === '--stop') {
341
342
  const pid = parseInt(fs.readFileSync(PID_FILE, 'utf-8'));
342
343
  try {
343
344
  process.kill(pid, 'SIGTERM');
344
- console.log(`守护进程已停止 (PID: ${pid})`);
345
+ logger.log(`守护进程已停止 (PID: ${pid})`);
345
346
  }
346
347
  catch {
347
- console.log('守护进程未运行');
348
+ logger.log('守护进程未运行');
348
349
  }
349
350
  fs.unlinkSync(PID_FILE);
350
351
  }
351
352
  else {
352
- console.log('守护进程未运行');
353
+ logger.log('守护进程未运行');
353
354
  }
354
355
  }
355
356
  else if (command === '--status') {
@@ -358,10 +359,10 @@ else if (command === '--status') {
358
359
  let data = '';
359
360
  res.on('data', chunk => data += chunk);
360
361
  res.on('end', () => {
361
- console.log(JSON.stringify(JSON.parse(data), null, 2));
362
+ logger.log(JSON.stringify(JSON.parse(data), null, 2));
362
363
  });
363
364
  }).on('error', () => {
364
- console.log('守护进程未运行');
365
+ logger.log('守护进程未运行');
365
366
  });
366
367
  }
367
368
  else {
@@ -62,18 +62,6 @@ export declare function getAllHeadlessStates(): Array<{
62
62
  projectDir: string;
63
63
  state: HeadlessState;
64
64
  }>;
65
- /**
66
- * 检查机器人是否被占用
67
- *
68
- * 扫描所有 headless 项目,检查是否有使用该机器人
69
- */
70
- export declare function checkRobotOccupied(robotName: string, excludeProjectDir?: string): {
71
- occupied: boolean;
72
- by?: {
73
- projectDir: string;
74
- agentName: string;
75
- };
76
- };
77
65
  /**
78
66
  * 清理所有项目 Hook 配置(服务重启时)
79
67
  */
@@ -10,6 +10,7 @@
10
10
  import * as fs from 'fs';
11
11
  import * as path from 'path';
12
12
  import * as os from 'os';
13
+ import { logger } from './logger.js';
13
14
  // 配置目录
14
15
  const CONFIG_DIR = path.join(os.homedir(), '.wecom-aibot-mcp');
15
16
  // 全局索引文件
@@ -77,7 +78,7 @@ export function enterHeadlessMode(projectDir, agentName, robotName) {
77
78
  fs.mkdirSync(stateDir, { recursive: true });
78
79
  }
79
80
  fs.writeFileSync(stateFilePath, JSON.stringify(state, null, 2));
80
- console.log(`[headless] 已进入微信模式: ${stateFilePath}`);
81
+ logger.log(`[headless] 已进入微信模式: ${stateFilePath}`);
81
82
  // 2. 添加到全局索引
82
83
  const index = readHeadlessIndex();
83
84
  if (!index.includes(projectDir)) {
@@ -100,14 +101,14 @@ export function exitHeadlessMode(projectDir) {
100
101
  const dir = projectDir || process.cwd();
101
102
  const state = loadHeadlessState(dir);
102
103
  if (!state) {
103
- console.log('[headless] 未在微信模式');
104
+ logger.log('[headless] 未在微信模式');
104
105
  return null;
105
106
  }
106
107
  // 1. 删除项目状态文件
107
108
  const stateFilePath = getProjectHeadlessFile(state.projectDir);
108
109
  if (fs.existsSync(stateFilePath)) {
109
110
  fs.unlinkSync(stateFilePath);
110
- console.log(`[headless] 已删除状态文件: ${stateFilePath}`);
111
+ logger.log(`[headless] 已删除状态文件: ${stateFilePath}`);
111
112
  }
112
113
  // 2. 从全局索引移除
113
114
  const index = readHeadlessIndex();
@@ -130,7 +131,7 @@ export function setAutoApprove(enabled, projectDir) {
130
131
  // 写入状态文件
131
132
  const stateFilePath = getProjectHeadlessFile(state.projectDir);
132
133
  fs.writeFileSync(stateFilePath, JSON.stringify(state, null, 2));
133
- console.log(`[headless] 已${enabled ? '启用' : '禁用'}智能代批`);
134
+ logger.log(`[headless] 已${enabled ? '启用' : '禁用'}智能代批`);
134
135
  return state;
135
136
  }
136
137
  /**
@@ -147,7 +148,7 @@ export function loadHeadlessState(projectDir) {
147
148
  return JSON.parse(content);
148
149
  }
149
150
  catch (err) {
150
- console.error(`[headless] 解析状态文件失败: ${stateFilePath}`, err);
151
+ logger.error(`[headless] 解析状态文件失败: ${stateFilePath}`, err);
151
152
  return null;
152
153
  }
153
154
  }
@@ -176,7 +177,7 @@ function configureProjectHook(projectDir) {
176
177
  settings = JSON.parse(content);
177
178
  }
178
179
  catch (err) {
179
- console.error(`[headless] 读取 settings.json 失败: ${settingsPath}`, err);
180
+ logger.error(`[headless] 读取 settings.json 失败: ${settingsPath}`, err);
180
181
  }
181
182
  }
182
183
  // 设置 PermissionRequest hook
@@ -197,7 +198,7 @@ function configureProjectHook(projectDir) {
197
198
  ];
198
199
  // 写入配置
199
200
  fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
200
- console.log(`[headless] 已配置项目 Hook: ${settingsPath}`);
201
+ logger.log(`[headless] 已配置项目 Hook: ${settingsPath}`);
201
202
  }
202
203
  /**
203
204
  * 清除项目级 Hook 配置
@@ -217,11 +218,11 @@ function clearProjectHook(projectDir) {
217
218
  delete settings.hooks;
218
219
  }
219
220
  fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
220
- console.log(`[headless] 已清除项目 Hook: ${settingsPath}`);
221
+ logger.log(`[headless] 已清除项目 Hook: ${settingsPath}`);
221
222
  }
222
223
  }
223
224
  catch (err) {
224
- console.error(`[headless] 清除项目 Hook 失败: ${settingsPath}`, err);
225
+ logger.error(`[headless] 清除项目 Hook 失败: ${settingsPath}`, err);
225
226
  }
226
227
  }
227
228
  /**
@@ -242,7 +243,7 @@ export function cleanupOrphanFiles() {
242
243
  else {
243
244
  // 状态文件不存在,清理 Hook 配置
244
245
  clearProjectHook(projectDir);
245
- console.log(`[headless] 清理孤儿项目: ${projectDir}`);
246
+ logger.log(`[headless] 清理孤儿项目: ${projectDir}`);
246
247
  }
247
248
  }
248
249
  // 更新索引
@@ -267,31 +268,6 @@ export function getAllHeadlessStates() {
267
268
  }
268
269
  return results;
269
270
  }
270
- /**
271
- * 检查机器人是否被占用
272
- *
273
- * 扫描所有 headless 项目,检查是否有使用该机器人
274
- */
275
- export function checkRobotOccupied(robotName, excludeProjectDir) {
276
- const allStates = getAllHeadlessStates();
277
- for (const { projectDir, state } of allStates) {
278
- // 排除当前项目
279
- if (excludeProjectDir && projectDir === excludeProjectDir) {
280
- continue;
281
- }
282
- // 检查是否使用同一机器人
283
- if (state.robotName === robotName) {
284
- return {
285
- occupied: true,
286
- by: {
287
- projectDir,
288
- agentName: state.agentName || '未知',
289
- },
290
- };
291
- }
292
- }
293
- return { occupied: false };
294
- }
295
271
  /**
296
272
  * 清理所有项目 Hook 配置(服务重启时)
297
273
  */
@@ -15,7 +15,7 @@
15
15
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
16
16
  export declare const HTTP_PORT = 18963;
17
17
  export declare const HOOK_SCRIPT_PATH: string;
18
- export declare function generateCcId(): string;
18
+ export declare function generateCcId(agentName?: string): string;
19
19
  export declare function pushMessageToSession(robotName: string, message: {
20
20
  msgid: string;
21
21
  content: string;
@@ -24,6 +24,14 @@ export declare function pushMessageToSession(robotName: string, message: {
24
24
  chattype: 'single' | 'group';
25
25
  timestamp: number;
26
26
  }): Promise<void>;
27
+ export declare function pushMessageToSSEClient(robotName: string, message: {
28
+ msgid: string;
29
+ content: string;
30
+ from_userid: string;
31
+ chatid: string;
32
+ chattype: 'single' | 'group';
33
+ timestamp: number;
34
+ }, targetCcId?: string): Promise<void>;
27
35
  export interface ApprovalRequest {
28
36
  tool_name: string;
29
37
  tool_input: Record<string, unknown>;
@@ -34,10 +42,21 @@ export interface ApprovalRequest {
34
42
  interface CCRegistryEntry {
35
43
  robotName: string;
36
44
  agentName?: string;
45
+ mode?: 'channel' | 'http';
46
+ projectDir?: string;
47
+ lastOnline: number;
37
48
  }
38
- export declare function registerCcId(ccId: string, robotName: string, agentName?: string): void;
49
+ export declare function registerCcId(ccId: string, robotName: string, agentName?: string, mode?: 'channel' | 'http', projectDir?: string, isReconnect?: boolean): {
50
+ success: boolean;
51
+ ccId: string;
52
+ };
39
53
  export declare function unregisterCcId(ccId: string): void;
54
+ export declare function clearCcIdRegistry(): {
55
+ cleared: number;
56
+ entries: string[];
57
+ };
40
58
  export declare function getRobotByCcId(ccId: string): string | null;
59
+ export declare function getProjectDirByCcId(ccId: string): string | null;
41
60
  export declare function getCCRegistryEntry(ccId: string): CCRegistryEntry | null;
42
61
  export declare function getCCCount(): number;
43
62
  export declare function getCCCountByRobot(robotName: string): number;