@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.
- package/README.md +50 -337
- package/dist/bin.js +172 -85
- package/dist/channel-server.d.ts +15 -0
- package/dist/channel-server.js +492 -0
- package/dist/channel-server.test.d.ts +5 -0
- package/dist/channel-server.test.js +324 -0
- package/dist/client-pool.js +4 -3
- package/dist/client.js +49 -49
- package/dist/config-wizard.d.ts +13 -3
- package/dist/config-wizard.js +424 -141
- package/dist/connection-log.js +7 -6
- package/dist/connection-manager.d.ts +0 -8
- package/dist/connection-manager.js +11 -23
- package/dist/daemon.js +7 -6
- package/dist/headless-state.d.ts +0 -12
- package/dist/headless-state.js +11 -35
- package/dist/http-server.d.ts +21 -2
- package/dist/http-server.js +421 -88
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/keepalive-monitor.js +5 -4
- package/dist/logger.d.ts +51 -0
- package/dist/logger.js +84 -0
- package/dist/message-bus.d.ts +13 -1
- package/dist/message-bus.js +56 -3
- package/dist/project-config.d.ts +16 -0
- package/dist/project-config.js +91 -12
- package/dist/tools/index.js +226 -57
- package/package.json +1 -1
- package/skills/headless-mode/SKILL.md +136 -181
package/dist/connection-log.js
CHANGED
|
@@ -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
|
-
|
|
34
|
+
logger.log(`[${time}] [conn] WebSocket 连接已建立`);
|
|
34
35
|
break;
|
|
35
36
|
case 'authenticated':
|
|
36
|
-
|
|
37
|
+
logger.log(`[${time}] [conn] 认证成功`);
|
|
37
38
|
break;
|
|
38
39
|
case 'disconnected':
|
|
39
40
|
const duration = record.connectionDuration ? ` (持续 ${record.connectionDuration}s)` : '';
|
|
40
|
-
|
|
41
|
+
logger.log(`[${time}] [conn] 连接断开: ${record.reason || '未知'}${duration}`);
|
|
41
42
|
break;
|
|
42
43
|
case 'reconnecting':
|
|
43
|
-
|
|
44
|
+
logger.log(`[${time}] [conn] 正在重连 (第 ${record.attempt} 次)`);
|
|
44
45
|
break;
|
|
45
46
|
case 'error':
|
|
46
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
153
|
+
logger.log(`[connection] 重连成功: ${robot.name}`);
|
|
166
154
|
return state.client;
|
|
167
155
|
}
|
|
168
156
|
else {
|
|
169
|
-
|
|
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
|
-
|
|
215
|
+
logger.log('[connection] 未配置任何机器人');
|
|
228
216
|
return;
|
|
229
217
|
}
|
|
230
218
|
// 多机器人场景:不自动连接,等待用户选择
|
|
231
219
|
if (robots.length > 1) {
|
|
232
|
-
|
|
220
|
+
logger.log(`[connection] 检测到 ${robots.length} 个机器人,等待用户选择`);
|
|
233
221
|
return;
|
|
234
222
|
}
|
|
235
223
|
// 单机器人场景:自动连接
|
|
236
|
-
|
|
224
|
+
logger.log(`[connection] 自动连接机器人: ${robots[0].name}`);
|
|
237
225
|
const result = await connectRobot(robots[0].name);
|
|
238
226
|
if (result.success) {
|
|
239
|
-
|
|
227
|
+
logger.log(`[connection] ✅ ${robots[0].name} 已连接`);
|
|
240
228
|
}
|
|
241
229
|
else {
|
|
242
|
-
|
|
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
|
-
|
|
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
|
-
|
|
345
|
+
logger.log(`守护进程已停止 (PID: ${pid})`);
|
|
345
346
|
}
|
|
346
347
|
catch {
|
|
347
|
-
|
|
348
|
+
logger.log('守护进程未运行');
|
|
348
349
|
}
|
|
349
350
|
fs.unlinkSync(PID_FILE);
|
|
350
351
|
}
|
|
351
352
|
else {
|
|
352
|
-
|
|
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
|
-
|
|
362
|
+
logger.log(JSON.stringify(JSON.parse(data), null, 2));
|
|
362
363
|
});
|
|
363
364
|
}).on('error', () => {
|
|
364
|
-
|
|
365
|
+
logger.log('守护进程未运行');
|
|
365
366
|
});
|
|
366
367
|
}
|
|
367
368
|
else {
|
package/dist/headless-state.d.ts
CHANGED
|
@@ -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
|
*/
|
package/dist/headless-state.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
221
|
+
logger.log(`[headless] 已清除项目 Hook: ${settingsPath}`);
|
|
221
222
|
}
|
|
222
223
|
}
|
|
223
224
|
catch (err) {
|
|
224
|
-
|
|
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
|
-
|
|
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
|
*/
|
package/dist/http-server.d.ts
CHANGED
|
@@ -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):
|
|
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;
|