@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/index.d.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* - robotName 作为连接索引
|
|
10
10
|
*/
|
|
11
11
|
export { WecomClient, initClient } from './client.js';
|
|
12
|
-
export { connectRobot, disconnectRobot, getClient, getConnectionState, getAllConnectionStates,
|
|
12
|
+
export { connectRobot, disconnectRobot, getClient, getConnectionState, getAllConnectionStates, } from './connection-manager.js';
|
|
13
13
|
export { startHttpServer, stopHttpServer, HTTP_PORT, HOOK_SCRIPT_PATH, } from './http-server.js';
|
|
14
14
|
export type { ApprovalRequest } from './http-server.js';
|
|
15
15
|
export { registerTools } from './tools/index.js';
|
package/dist/index.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// Client 模块
|
|
12
12
|
export { WecomClient, initClient } from './client.js';
|
|
13
13
|
// 连接管理模块
|
|
14
|
-
export { connectRobot, disconnectRobot, getClient, getConnectionState, getAllConnectionStates,
|
|
14
|
+
export { connectRobot, disconnectRobot, getClient, getConnectionState, getAllConnectionStates, } from './connection-manager.js';
|
|
15
15
|
// HTTP 服务模块
|
|
16
16
|
export { startHttpServer, stopHttpServer, HTTP_PORT, HOOK_SCRIPT_PATH, } from './http-server.js';
|
|
17
17
|
// 工具注册
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* - 不再使用 projectDir
|
|
9
9
|
*/
|
|
10
10
|
import { getClient, getConnectionState } from './connection-manager.js';
|
|
11
|
+
import { logger } from './logger.js';
|
|
11
12
|
const KEEPALIVE_INTERVAL_MINUTES = 5; // 每 5 分钟
|
|
12
13
|
const CHECK_INTERVAL_MS = 60000; // 每分钟检查一次
|
|
13
14
|
const MAX_KEEPALIVE_COUNT = 2; // 保活提醒最多 2 次
|
|
@@ -17,7 +18,7 @@ let monitorTimer = null;
|
|
|
17
18
|
*/
|
|
18
19
|
export function startKeepaliveMonitor() {
|
|
19
20
|
monitorTimer = setInterval(checkAndSendKeepalive, CHECK_INTERVAL_MS);
|
|
20
|
-
|
|
21
|
+
logger.log('[keepalive] 保活监控已启动 (每 5 分钟发送审批提醒)');
|
|
21
22
|
}
|
|
22
23
|
/**
|
|
23
24
|
* 停止保活监控
|
|
@@ -27,7 +28,7 @@ export function stopKeepaliveMonitor() {
|
|
|
27
28
|
clearInterval(monitorTimer);
|
|
28
29
|
monitorTimer = null;
|
|
29
30
|
}
|
|
30
|
-
|
|
31
|
+
logger.log('[keepalive] 保活监控已停止');
|
|
31
32
|
}
|
|
32
33
|
/**
|
|
33
34
|
* 检查并发送保活消息
|
|
@@ -75,12 +76,12 @@ async function sendKeepaliveMessage(approval, minutes, robotName) {
|
|
|
75
76
|
if (client) {
|
|
76
77
|
const sent = await client.sendText(message);
|
|
77
78
|
if (sent) {
|
|
78
|
-
|
|
79
|
+
logger.log(`[keepalive] 已发送审批提醒: ${approval.taskId}, 等待 ${minutes} 分钟`);
|
|
79
80
|
}
|
|
80
81
|
}
|
|
81
82
|
}
|
|
82
83
|
catch (err) {
|
|
83
|
-
|
|
84
|
+
logger.error(`[keepalive] 发送审批提醒失败:`, err);
|
|
84
85
|
}
|
|
85
86
|
}
|
|
86
87
|
/**
|
package/dist/logger.d.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 统一日志模块
|
|
3
|
+
*
|
|
4
|
+
* 通过 --debug 参数控制日志输出:
|
|
5
|
+
* - debug 模式:所有日志输出到 stderr
|
|
6
|
+
* - 正常模式:只输出必要信息(如错误、启动消息)
|
|
7
|
+
*
|
|
8
|
+
* Debug 标记文件:~/.wecom-aibot-mcp/debug
|
|
9
|
+
*
|
|
10
|
+
* 用法与 console 完全一致:
|
|
11
|
+
* - logger.log() - debug 模式才输出
|
|
12
|
+
* - logger.error() - 始终输出到 stderr
|
|
13
|
+
* - logger.warn() - 始终输出到 stderr
|
|
14
|
+
* - logger.info() - 始终输出到 stdout
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* 检查是否处于 debug 模式
|
|
18
|
+
*/
|
|
19
|
+
export declare function isDebugMode(): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* 设置 debug 模式
|
|
22
|
+
*/
|
|
23
|
+
export declare function setDebugMode(enabled: boolean): void;
|
|
24
|
+
/**
|
|
25
|
+
* 日志输出(仅 debug 模式)
|
|
26
|
+
* 与 console.log 用法完全一致
|
|
27
|
+
*/
|
|
28
|
+
export declare function log(...args: unknown[]): void;
|
|
29
|
+
/**
|
|
30
|
+
* 错误日志(始终输出到 stderr)
|
|
31
|
+
* 与 console.error 用法完全一致
|
|
32
|
+
*/
|
|
33
|
+
export declare function error(...args: unknown[]): void;
|
|
34
|
+
/**
|
|
35
|
+
* 信息日志(始终输出到 stdout)
|
|
36
|
+
* 与 console.info 用法完全一致
|
|
37
|
+
*/
|
|
38
|
+
export declare function info(...args: unknown[]): void;
|
|
39
|
+
/**
|
|
40
|
+
* 警告日志(始终输出到 stderr)
|
|
41
|
+
* 与 console.warn 用法完全一致
|
|
42
|
+
*/
|
|
43
|
+
export declare function warn(...args: unknown[]): void;
|
|
44
|
+
export declare const logger: {
|
|
45
|
+
log: typeof log;
|
|
46
|
+
error: typeof error;
|
|
47
|
+
info: typeof info;
|
|
48
|
+
warn: typeof warn;
|
|
49
|
+
isDebug: typeof isDebugMode;
|
|
50
|
+
setDebug: typeof setDebugMode;
|
|
51
|
+
};
|
package/dist/logger.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 统一日志模块
|
|
3
|
+
*
|
|
4
|
+
* 通过 --debug 参数控制日志输出:
|
|
5
|
+
* - debug 模式:所有日志输出到 stderr
|
|
6
|
+
* - 正常模式:只输出必要信息(如错误、启动消息)
|
|
7
|
+
*
|
|
8
|
+
* Debug 标记文件:~/.wecom-aibot-mcp/debug
|
|
9
|
+
*
|
|
10
|
+
* 用法与 console 完全一致:
|
|
11
|
+
* - logger.log() - debug 模式才输出
|
|
12
|
+
* - logger.error() - 始终输出到 stderr
|
|
13
|
+
* - logger.warn() - 始终输出到 stderr
|
|
14
|
+
* - logger.info() - 始终输出到 stdout
|
|
15
|
+
*/
|
|
16
|
+
import * as fs from 'fs';
|
|
17
|
+
import * as path from 'path';
|
|
18
|
+
import * as os from 'os';
|
|
19
|
+
const DEBUG_FILE = path.join(os.homedir(), '.wecom-aibot-mcp', 'debug');
|
|
20
|
+
let debugMode = false;
|
|
21
|
+
/**
|
|
22
|
+
* 检查是否处于 debug 模式
|
|
23
|
+
*/
|
|
24
|
+
export function isDebugMode() {
|
|
25
|
+
if (debugMode)
|
|
26
|
+
return true;
|
|
27
|
+
// 检查标记文件
|
|
28
|
+
if (fs.existsSync(DEBUG_FILE)) {
|
|
29
|
+
debugMode = true;
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* 设置 debug 模式
|
|
36
|
+
*/
|
|
37
|
+
export function setDebugMode(enabled) {
|
|
38
|
+
debugMode = enabled;
|
|
39
|
+
if (enabled && !fs.existsSync(DEBUG_FILE)) {
|
|
40
|
+
fs.writeFileSync(DEBUG_FILE, 'true');
|
|
41
|
+
}
|
|
42
|
+
else if (!enabled && fs.existsSync(DEBUG_FILE)) {
|
|
43
|
+
fs.unlinkSync(DEBUG_FILE);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* 日志输出(仅 debug 模式)
|
|
48
|
+
* 与 console.log 用法完全一致
|
|
49
|
+
*/
|
|
50
|
+
export function log(...args) {
|
|
51
|
+
if (isDebugMode()) {
|
|
52
|
+
console.log(...args);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* 错误日志(始终输出到 stderr)
|
|
57
|
+
* 与 console.error 用法完全一致
|
|
58
|
+
*/
|
|
59
|
+
export function error(...args) {
|
|
60
|
+
console.error(...args);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* 信息日志(始终输出到 stdout)
|
|
64
|
+
* 与 console.info 用法完全一致
|
|
65
|
+
*/
|
|
66
|
+
export function info(...args) {
|
|
67
|
+
console.info(...args);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* 警告日志(始终输出到 stderr)
|
|
71
|
+
* 与 console.warn 用法完全一致
|
|
72
|
+
*/
|
|
73
|
+
export function warn(...args) {
|
|
74
|
+
console.warn(...args);
|
|
75
|
+
}
|
|
76
|
+
// 导出 logger 对象,用法与 console 完全一致
|
|
77
|
+
export const logger = {
|
|
78
|
+
log,
|
|
79
|
+
error,
|
|
80
|
+
info,
|
|
81
|
+
warn,
|
|
82
|
+
isDebug: isDebugMode,
|
|
83
|
+
setDebug: setDebugMode,
|
|
84
|
+
};
|
package/dist/message-bus.d.ts
CHANGED
|
@@ -5,6 +5,12 @@
|
|
|
5
5
|
* - WecomClient 收到微信消息 → 发布到 bus
|
|
6
6
|
* - MCP Server 订阅消息 → SSE 推送给 CC
|
|
7
7
|
* - 按 CCID 过滤消息(基于引用内容)
|
|
8
|
+
*
|
|
9
|
+
* v2.1 使用订阅计数替代 tap 计数:
|
|
10
|
+
* - subscribe 时计数 +1
|
|
11
|
+
* - unsubscribe 时计数 -1(自动处理掉线)
|
|
12
|
+
* - 订阅数 = 1 时直接发给唯一订阅者
|
|
13
|
+
* - 订阅数 > 1 时检查 ccId
|
|
8
14
|
*/
|
|
9
15
|
import { Subject } from 'rxjs';
|
|
10
16
|
export interface WecomMessage {
|
|
@@ -17,6 +23,7 @@ export interface WecomMessage {
|
|
|
17
23
|
timestamp: number;
|
|
18
24
|
quoteContent?: string;
|
|
19
25
|
}
|
|
26
|
+
export declare function getSubscriberCount(robotName: string): number;
|
|
20
27
|
declare const wecomMessage$: Subject<WecomMessage>;
|
|
21
28
|
/**
|
|
22
29
|
* 发布微信消息(由 WecomClient 调用)
|
|
@@ -27,7 +34,12 @@ export declare function publishWecomMessage(msg: WecomMessage): void;
|
|
|
27
34
|
*/
|
|
28
35
|
export declare function subscribeWecomMessage(callback: (msg: WecomMessage) => void): import("rxjs").Subscription;
|
|
29
36
|
/**
|
|
30
|
-
*
|
|
37
|
+
* 订阅特定机器人的微信消息(带订阅计数)
|
|
31
38
|
*/
|
|
32
39
|
export declare function subscribeWecomMessageByRobot(robotName: string, callback: (msg: WecomMessage) => void): import("rxjs").Subscription;
|
|
40
|
+
/**
|
|
41
|
+
* 订阅特定机器人且匹配 ccId 的消息
|
|
42
|
+
* 用于多 CC 共用一个机器人场景的过滤
|
|
43
|
+
*/
|
|
44
|
+
export declare function subscribeWecomMessageByCcId(robotName: string, ccId: string, callback: (msg: WecomMessage) => void): import("rxjs").Subscription;
|
|
33
45
|
export { wecomMessage$ };
|
package/dist/message-bus.js
CHANGED
|
@@ -5,9 +5,32 @@
|
|
|
5
5
|
* - WecomClient 收到微信消息 → 发布到 bus
|
|
6
6
|
* - MCP Server 订阅消息 → SSE 推送给 CC
|
|
7
7
|
* - 按 CCID 过滤消息(基于引用内容)
|
|
8
|
+
*
|
|
9
|
+
* v2.1 使用订阅计数替代 tap 计数:
|
|
10
|
+
* - subscribe 时计数 +1
|
|
11
|
+
* - unsubscribe 时计数 -1(自动处理掉线)
|
|
12
|
+
* - 订阅数 = 1 时直接发给唯一订阅者
|
|
13
|
+
* - 订阅数 > 1 时检查 ccId
|
|
8
14
|
*/
|
|
9
15
|
import { Subject } from 'rxjs';
|
|
10
|
-
import { filter } from 'rxjs/operators';
|
|
16
|
+
import { filter, finalize } from 'rxjs/operators';
|
|
17
|
+
// ============================================
|
|
18
|
+
// 订阅计数器(替代 tap 计数)
|
|
19
|
+
// ============================================
|
|
20
|
+
const subscriberCount = new Map(); // robotName → 订阅数
|
|
21
|
+
export function getSubscriberCount(robotName) {
|
|
22
|
+
return subscriberCount.get(robotName) || 0;
|
|
23
|
+
}
|
|
24
|
+
function incrementSubscriberCount(robotName) {
|
|
25
|
+
const current = subscriberCount.get(robotName) || 0;
|
|
26
|
+
subscriberCount.set(robotName, current + 1);
|
|
27
|
+
}
|
|
28
|
+
function decrementSubscriberCount(robotName) {
|
|
29
|
+
const current = subscriberCount.get(robotName) || 0;
|
|
30
|
+
if (current > 0) {
|
|
31
|
+
subscriberCount.set(robotName, current - 1);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
11
34
|
// ============================================
|
|
12
35
|
// 消息总线(RxJS Subject)
|
|
13
36
|
// ============================================
|
|
@@ -28,10 +51,40 @@ export function subscribeWecomMessage(callback) {
|
|
|
28
51
|
return wecomMessage$.subscribe(callback);
|
|
29
52
|
}
|
|
30
53
|
/**
|
|
31
|
-
*
|
|
54
|
+
* 订阅特定机器人的微信消息(带订阅计数)
|
|
32
55
|
*/
|
|
33
56
|
export function subscribeWecomMessageByRobot(robotName, callback) {
|
|
34
|
-
|
|
57
|
+
incrementSubscriberCount(robotName);
|
|
58
|
+
return wecomMessage$.pipe(filter(msg => msg.robotName === robotName), finalize(() => decrementSubscriberCount(robotName)) // 无论何时结束都减计数
|
|
59
|
+
).subscribe(callback);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* 订阅特定机器人且匹配 ccId 的消息
|
|
63
|
+
* 用于多 CC 共用一个机器人场景的过滤
|
|
64
|
+
*/
|
|
65
|
+
export function subscribeWecomMessageByCcId(robotName, ccId, callback) {
|
|
66
|
+
incrementSubscriberCount(robotName);
|
|
67
|
+
return wecomMessage$.pipe(filter(msg => msg.robotName === robotName), filter(msg => isMessageForCcId(msg, ccId)), finalize(() => decrementSubscriberCount(robotName)) // 无论何时结束都减计数
|
|
68
|
+
).subscribe(callback);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* 检查消息是否属于指定的 ccId
|
|
72
|
+
*/
|
|
73
|
+
function isMessageForCcId(msg, ccId) {
|
|
74
|
+
// 有引用内容,检查是否引用该 ccId
|
|
75
|
+
if (msg.quoteContent) {
|
|
76
|
+
const match = msg.quoteContent.match(/【([^】]+)】/);
|
|
77
|
+
if (match && match[1] === ccId) {
|
|
78
|
+
return true; // 引用了该 ccId,消息属于该 CC
|
|
79
|
+
}
|
|
80
|
+
// 引用了其他 ccId,不属于当前 ccId
|
|
81
|
+
if (match) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// 无引用内容,消息不属于任何特定 CC
|
|
86
|
+
// 在多 CC 场景下,需要用户指定
|
|
87
|
+
return false;
|
|
35
88
|
}
|
|
36
89
|
// 导出 Observable(供高级用法)
|
|
37
90
|
export { wecomMessage$ };
|
package/dist/project-config.d.ts
CHANGED
|
@@ -19,6 +19,7 @@ export interface WechatModeConfig {
|
|
|
19
19
|
ccId?: string;
|
|
20
20
|
autoApprove?: boolean;
|
|
21
21
|
autoApproveTimeout?: number;
|
|
22
|
+
heartbeatJobId?: string;
|
|
22
23
|
}
|
|
23
24
|
/**
|
|
24
25
|
* 获取项目配置文件路径
|
|
@@ -128,3 +129,18 @@ export declare function removePermissionHook(projectDir: string): {
|
|
|
128
129
|
path: string;
|
|
129
130
|
existed: boolean;
|
|
130
131
|
};
|
|
132
|
+
/**
|
|
133
|
+
* 添加 TaskCompleted hook 到项目 settings.json
|
|
134
|
+
*/
|
|
135
|
+
export declare function addTaskCompletedHook(projectDir: string): {
|
|
136
|
+
success: boolean;
|
|
137
|
+
path: string;
|
|
138
|
+
};
|
|
139
|
+
/**
|
|
140
|
+
* 删除 TaskCompleted hook 从项目 settings.json
|
|
141
|
+
*/
|
|
142
|
+
export declare function removeTaskCompletedHook(projectDir: string): {
|
|
143
|
+
success: boolean;
|
|
144
|
+
path: string;
|
|
145
|
+
existed: boolean;
|
|
146
|
+
};
|
package/dist/project-config.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 CLAUDE_CONFIG_FILE = path.join(os.homedir(), '.claude.json');
|
|
15
16
|
const PROJECT_CONFIG_SUBDIR = '.claude/wecom-aibot';
|
|
@@ -37,7 +38,7 @@ export function loadProjectConfig(projectDir) {
|
|
|
37
38
|
const config = JSON.parse(content);
|
|
38
39
|
// 验证必需字段
|
|
39
40
|
if (!config.botId || !config.secret || !config.defaultUser) {
|
|
40
|
-
|
|
41
|
+
logger.error(`[project-config] 项目配置缺少必需字段: ${configPath}`);
|
|
41
42
|
return null;
|
|
42
43
|
}
|
|
43
44
|
return {
|
|
@@ -48,7 +49,7 @@ export function loadProjectConfig(projectDir) {
|
|
|
48
49
|
};
|
|
49
50
|
}
|
|
50
51
|
catch (err) {
|
|
51
|
-
|
|
52
|
+
logger.error(`[project-config] 解析项目配置失败: ${configPath}`, err);
|
|
52
53
|
return null;
|
|
53
54
|
}
|
|
54
55
|
}
|
|
@@ -66,7 +67,7 @@ export function saveProjectConfig(projectDir, config) {
|
|
|
66
67
|
fs.mkdirSync(configDir, { recursive: true });
|
|
67
68
|
}
|
|
68
69
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
69
|
-
|
|
70
|
+
logger.log(`[project-config] 已保存项目配置: ${configPath}`);
|
|
70
71
|
}
|
|
71
72
|
/**
|
|
72
73
|
* 删除项目配置
|
|
@@ -77,7 +78,7 @@ export function deleteProjectConfig(projectDir) {
|
|
|
77
78
|
const configPath = getProjectConfigPath(projectDir);
|
|
78
79
|
if (fs.existsSync(configPath)) {
|
|
79
80
|
fs.unlinkSync(configPath);
|
|
80
|
-
|
|
81
|
+
logger.log(`[project-config] 已删除项目配置: ${configPath}`);
|
|
81
82
|
}
|
|
82
83
|
}
|
|
83
84
|
/**
|
|
@@ -129,7 +130,7 @@ export function updateWechatModeConfig(projectDir, updates) {
|
|
|
129
130
|
// 合并更新
|
|
130
131
|
const newConfig = { ...config, ...updates };
|
|
131
132
|
fs.writeFileSync(configPath, JSON.stringify(newConfig, null, 2));
|
|
132
|
-
|
|
133
|
+
logger.log(`[project-config] 已更新微信模式配置: ${configPath}`);
|
|
133
134
|
}
|
|
134
135
|
/**
|
|
135
136
|
* 加载全局配置(从 ~/.claude.json)
|
|
@@ -158,7 +159,7 @@ export function loadGlobalConfig() {
|
|
|
158
159
|
};
|
|
159
160
|
}
|
|
160
161
|
catch (err) {
|
|
161
|
-
|
|
162
|
+
logger.error('[project-config] 加载全局配置失败:', err);
|
|
162
163
|
return null;
|
|
163
164
|
}
|
|
164
165
|
}
|
|
@@ -177,14 +178,14 @@ export function getConfig(projectDir) {
|
|
|
177
178
|
if (projectDir) {
|
|
178
179
|
const projectConfig = loadProjectConfig(projectDir);
|
|
179
180
|
if (projectConfig) {
|
|
180
|
-
|
|
181
|
+
logger.log(`[project-config] 使用项目配置: ${projectDir}`);
|
|
181
182
|
return projectConfig;
|
|
182
183
|
}
|
|
183
184
|
}
|
|
184
185
|
// 2. 回退全局配置
|
|
185
186
|
const globalConfig = loadGlobalConfig();
|
|
186
187
|
if (globalConfig) {
|
|
187
|
-
|
|
188
|
+
logger.log('[project-config] 使用全局配置');
|
|
188
189
|
return globalConfig;
|
|
189
190
|
}
|
|
190
191
|
return null;
|
|
@@ -255,6 +256,17 @@ const PERMISSION_HOOK = {
|
|
|
255
256
|
matcher: '',
|
|
256
257
|
hooks: [{ type: 'command', command: '~/.wecom-aibot-mcp/permission-hook.sh' }],
|
|
257
258
|
};
|
|
259
|
+
/**
|
|
260
|
+
* TaskCompleted hook 脚本路径
|
|
261
|
+
*/
|
|
262
|
+
const TASK_COMPLETED_HOOK_SCRIPT_PATH = path.join(os.homedir(), '.wecom-aibot-mcp', 'task-completed-hook.sh');
|
|
263
|
+
/**
|
|
264
|
+
* TaskCompleted hook 配置
|
|
265
|
+
*/
|
|
266
|
+
const TASK_COMPLETED_HOOK = {
|
|
267
|
+
matcher: '',
|
|
268
|
+
hooks: [{ type: 'command', command: TASK_COMPLETED_HOOK_SCRIPT_PATH }],
|
|
269
|
+
};
|
|
258
270
|
/**
|
|
259
271
|
* 添加 PermissionRequest hook 到项目 settings.json
|
|
260
272
|
*/
|
|
@@ -284,11 +296,11 @@ export function addPermissionHook(projectDir) {
|
|
|
284
296
|
// 写入配置
|
|
285
297
|
try {
|
|
286
298
|
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
287
|
-
|
|
299
|
+
logger.log(`[project-config] 已添加 PermissionRequest hook: ${settingsPath}`);
|
|
288
300
|
return { success: true, path: settingsPath };
|
|
289
301
|
}
|
|
290
302
|
catch (err) {
|
|
291
|
-
|
|
303
|
+
logger.error(`[project-config] 添加 PermissionRequest hook 失败: ${err}`);
|
|
292
304
|
return { success: false, path: settingsPath };
|
|
293
305
|
}
|
|
294
306
|
}
|
|
@@ -312,13 +324,80 @@ export function removePermissionHook(projectDir) {
|
|
|
312
324
|
}
|
|
313
325
|
// 写入配置
|
|
314
326
|
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
315
|
-
|
|
327
|
+
logger.log(`[project-config] 已删除 PermissionRequest hook: ${settingsPath}`);
|
|
328
|
+
return { success: true, path: settingsPath, existed: true };
|
|
329
|
+
}
|
|
330
|
+
return { success: true, path: settingsPath, existed: false };
|
|
331
|
+
}
|
|
332
|
+
catch (err) {
|
|
333
|
+
logger.error(`[project-config] 删除 PermissionRequest hook 失败: ${err}`);
|
|
334
|
+
return { success: false, path: settingsPath, existed: false };
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* 添加 TaskCompleted hook 到项目 settings.json
|
|
339
|
+
*/
|
|
340
|
+
export function addTaskCompletedHook(projectDir) {
|
|
341
|
+
const settingsPath = getProjectSettingsPath(projectDir);
|
|
342
|
+
const settingsDir = path.dirname(settingsPath);
|
|
343
|
+
// 确保目录存在
|
|
344
|
+
if (!fs.existsSync(settingsDir)) {
|
|
345
|
+
fs.mkdirSync(settingsDir, { recursive: true });
|
|
346
|
+
}
|
|
347
|
+
// 读取现有配置
|
|
348
|
+
let settings = {};
|
|
349
|
+
if (fs.existsSync(settingsPath)) {
|
|
350
|
+
try {
|
|
351
|
+
const content = fs.readFileSync(settingsPath, 'utf-8');
|
|
352
|
+
settings = JSON.parse(content);
|
|
353
|
+
}
|
|
354
|
+
catch {
|
|
355
|
+
// ignore
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
// 添加 hooks.TaskCompleted
|
|
359
|
+
if (!settings.hooks) {
|
|
360
|
+
settings.hooks = {};
|
|
361
|
+
}
|
|
362
|
+
settings.hooks.TaskCompleted = [TASK_COMPLETED_HOOK];
|
|
363
|
+
// 写入配置
|
|
364
|
+
try {
|
|
365
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
366
|
+
logger.log(`[project-config] 已添加 TaskCompleted hook: ${settingsPath}`);
|
|
367
|
+
return { success: true, path: settingsPath };
|
|
368
|
+
}
|
|
369
|
+
catch (err) {
|
|
370
|
+
logger.error(`[project-config] 添加 TaskCompleted hook 失败: ${err}`);
|
|
371
|
+
return { success: false, path: settingsPath };
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* 删除 TaskCompleted hook 从项目 settings.json
|
|
376
|
+
*/
|
|
377
|
+
export function removeTaskCompletedHook(projectDir) {
|
|
378
|
+
const settingsPath = getProjectSettingsPath(projectDir);
|
|
379
|
+
if (!fs.existsSync(settingsPath)) {
|
|
380
|
+
return { success: true, path: settingsPath, existed: false };
|
|
381
|
+
}
|
|
382
|
+
try {
|
|
383
|
+
const content = fs.readFileSync(settingsPath, 'utf-8');
|
|
384
|
+
const settings = JSON.parse(content);
|
|
385
|
+
// 删除 hooks.TaskCompleted
|
|
386
|
+
if (settings.hooks && settings.hooks.TaskCompleted) {
|
|
387
|
+
delete settings.hooks.TaskCompleted;
|
|
388
|
+
// 如果 hooks 为空,删除整个 hooks 字段
|
|
389
|
+
if (Object.keys(settings.hooks).length === 0) {
|
|
390
|
+
delete settings.hooks;
|
|
391
|
+
}
|
|
392
|
+
// 写入配置
|
|
393
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
394
|
+
logger.log(`[project-config] 已删除 TaskCompleted hook: ${settingsPath}`);
|
|
316
395
|
return { success: true, path: settingsPath, existed: true };
|
|
317
396
|
}
|
|
318
397
|
return { success: true, path: settingsPath, existed: false };
|
|
319
398
|
}
|
|
320
399
|
catch (err) {
|
|
321
|
-
|
|
400
|
+
logger.error(`[project-config] 删除 TaskCompleted hook 失败: ${err}`);
|
|
322
401
|
return { success: false, path: settingsPath, existed: false };
|
|
323
402
|
}
|
|
324
403
|
}
|