@vrs-soft/wecom-aibot-mcp 2.4.8 → 2.4.9
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/dist/config-wizard.js +98 -36
- package/dist/project-config.d.ts +5 -0
- package/dist/project-config.js +33 -0
- package/dist/tools/index.js +6 -2
- package/package.json +1 -1
package/dist/config-wizard.js
CHANGED
|
@@ -448,15 +448,42 @@ case "$TOOL_NAME" in
|
|
|
448
448
|
;;
|
|
449
449
|
esac
|
|
450
450
|
|
|
451
|
-
#
|
|
452
|
-
|
|
453
|
-
|
|
451
|
+
# 通过进程树匹配活跃项目(以 Claude 进程为准,不依赖 pwd)
|
|
452
|
+
ACTIVE_INDEX="$HOME/.wecom-aibot-mcp/active-projects.json"
|
|
453
|
+
log_debug "[$(date)] Checking active-projects index via PID tree (pid=$$, ppid=$PPID)"
|
|
454
454
|
|
|
455
|
-
|
|
455
|
+
if [[ ! -f "$ACTIVE_INDEX" ]]; then
|
|
456
|
+
log_debug "[$(date)] No active-projects index, exit 0"
|
|
457
|
+
exit 0
|
|
458
|
+
fi
|
|
459
|
+
|
|
460
|
+
# 沿进程树向上查找,深度 8 层
|
|
461
|
+
PROJECT_DIR=""
|
|
462
|
+
SEARCH_PID=$PPID
|
|
463
|
+
for i in {1..8}; do
|
|
464
|
+
if [[ -z "$SEARCH_PID" ]] || [[ "$SEARCH_PID" -le 1 ]]; then
|
|
465
|
+
break
|
|
466
|
+
fi
|
|
467
|
+
MATCH=$(jq -r --argjson p "$SEARCH_PID" '.[] | select(.pid==$p) | .projectDir' "$ACTIVE_INDEX" 2>/dev/null)
|
|
468
|
+
if [[ -n "$MATCH" ]]; then
|
|
469
|
+
PROJECT_DIR="$MATCH"
|
|
470
|
+
log_debug "[$(date)] Found project via PID $SEARCH_PID (depth $i): $PROJECT_DIR"
|
|
471
|
+
break
|
|
472
|
+
fi
|
|
473
|
+
SEARCH_PID=$(ps -o ppid= -p "$SEARCH_PID" 2>/dev/null | tr -d ' ')
|
|
474
|
+
done
|
|
475
|
+
|
|
476
|
+
if [[ -z "$PROJECT_DIR" ]]; then
|
|
477
|
+
log_debug "[$(date)] No PID match in process tree, exit 0"
|
|
478
|
+
exit 0
|
|
479
|
+
fi
|
|
480
|
+
|
|
481
|
+
CONFIG_FILE="$PROJECT_DIR/.claude/wecom-aibot.json"
|
|
482
|
+
log_debug "[$(date)] Found project: $PROJECT_DIR"
|
|
456
483
|
|
|
457
484
|
# 配置文件不存在,不在微信模式
|
|
458
485
|
if [[ ! -f "$CONFIG_FILE" ]]; then
|
|
459
|
-
log_debug "[$(date)] No config
|
|
486
|
+
log_debug "[$(date)] No wecom-aibot.json config, exit 0"
|
|
460
487
|
exit 0
|
|
461
488
|
fi
|
|
462
489
|
|
|
@@ -468,37 +495,48 @@ if [[ "$WECHAT_MODE" != "true" ]]; then
|
|
|
468
495
|
exit 0
|
|
469
496
|
fi
|
|
470
497
|
|
|
471
|
-
# 确定 MCP Server
|
|
498
|
+
# 确定 MCP Server 地址
|
|
499
|
+
# channel 模式直接使用远程地址,http 模式先试本地再回退远程
|
|
500
|
+
MODE=$(jq -r '.mode // "http"' "$CONFIG_FILE" 2>/dev/null)
|
|
472
501
|
MCP_BASE_URL="http://127.0.0.1:$MCP_PORT"
|
|
473
502
|
AUTH_ARGS=()
|
|
474
503
|
|
|
475
|
-
|
|
476
|
-
log_debug "[$(date)] Local health check: $HEALTH"
|
|
477
|
-
if ! echo "$HEALTH" | jq -e '.status == "ok"' > /dev/null 2>&1; then
|
|
478
|
-
log_debug "[$(date)] Local server not available, trying remote channel config..."
|
|
504
|
+
_try_remote() {
|
|
479
505
|
CLAUDE_JSON="$HOME/.claude.json"
|
|
480
|
-
if [[ -f "$CLAUDE_JSON" ]]; then
|
|
481
|
-
REMOTE_URL=$(jq -r '.mcpServers["wecom-aibot-channel"].env.MCP_URL // empty' "$CLAUDE_JSON" 2>/dev/null)
|
|
482
|
-
REMOTE_TOKEN=$(jq -r '.mcpServers["wecom-aibot-channel"].env.MCP_AUTH_TOKEN // empty' "$CLAUDE_JSON" 2>/dev/null)
|
|
483
|
-
if [[ -n "$REMOTE_URL" ]]; then
|
|
484
|
-
REMOTE_HEALTH=$(curl -s -m 5 \${REMOTE_TOKEN:+-H "Authorization: Bearer $REMOTE_TOKEN"} "$REMOTE_URL/health" 2>/dev/null)
|
|
485
|
-
log_debug "[$(date)] Remote health check ($REMOTE_URL): $REMOTE_HEALTH"
|
|
486
|
-
if echo "$REMOTE_HEALTH" | jq -e '.status == "ok"' > /dev/null 2>&1; then
|
|
487
|
-
MCP_BASE_URL="$REMOTE_URL"
|
|
488
|
-
[[ -n "$REMOTE_TOKEN" ]] && AUTH_ARGS=(-H "Authorization: Bearer $REMOTE_TOKEN")
|
|
489
|
-
log_debug "[$(date)] Using remote server: $MCP_BASE_URL"
|
|
490
|
-
else
|
|
491
|
-
log_debug "[$(date)] Remote health check failed, exit 0"
|
|
492
|
-
exit 0
|
|
493
|
-
fi
|
|
494
|
-
else
|
|
495
|
-
log_debug "[$(date)] No remote URL configured, exit 0"
|
|
496
|
-
exit 0
|
|
497
|
-
fi
|
|
498
|
-
else
|
|
506
|
+
if [[ ! -f "$CLAUDE_JSON" ]]; then
|
|
499
507
|
log_debug "[$(date)] No ~/.claude.json found, exit 0"
|
|
500
508
|
exit 0
|
|
501
509
|
fi
|
|
510
|
+
REMOTE_URL=$(jq -r '.mcpServers["wecom-aibot-channel"].env.MCP_URL // empty' "$CLAUDE_JSON" 2>/dev/null)
|
|
511
|
+
REMOTE_TOKEN=$(jq -r '.mcpServers["wecom-aibot-channel"].env.MCP_AUTH_TOKEN // empty' "$CLAUDE_JSON" 2>/dev/null)
|
|
512
|
+
if [[ -z "$REMOTE_URL" ]]; then
|
|
513
|
+
log_debug "[$(date)] No remote URL configured, exit 0"
|
|
514
|
+
exit 0
|
|
515
|
+
fi
|
|
516
|
+
REMOTE_HEALTH=$(curl -s -m 5 \${REMOTE_TOKEN:+-H "Authorization: Bearer $REMOTE_TOKEN"} "$REMOTE_URL/health" 2>/dev/null)
|
|
517
|
+
log_debug "[$(date)] Remote health check ($REMOTE_URL): $REMOTE_HEALTH"
|
|
518
|
+
if echo "$REMOTE_HEALTH" | jq -e '.status == "ok"' > /dev/null 2>&1; then
|
|
519
|
+
MCP_BASE_URL="$REMOTE_URL"
|
|
520
|
+
[[ -n "$REMOTE_TOKEN" ]] && AUTH_ARGS=(-H "Authorization: Bearer $REMOTE_TOKEN")
|
|
521
|
+
log_debug "[$(date)] Using remote server: $MCP_BASE_URL"
|
|
522
|
+
else
|
|
523
|
+
log_debug "[$(date)] Remote health check failed, exit 0"
|
|
524
|
+
exit 0
|
|
525
|
+
fi
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
if [[ "$MODE" == "channel" ]]; then
|
|
529
|
+
# channel 模式:直接使用远程地址,跳过本地检查
|
|
530
|
+
log_debug "[$(date)] Channel mode, using remote server directly"
|
|
531
|
+
_try_remote
|
|
532
|
+
else
|
|
533
|
+
# http 模式:本地优先,失败则尝试远程
|
|
534
|
+
HEALTH=$(curl -s -m 2 "$MCP_BASE_URL/health" 2>/dev/null)
|
|
535
|
+
log_debug "[$(date)] Local health check: $HEALTH"
|
|
536
|
+
if ! echo "$HEALTH" | jq -e '.status == "ok"' > /dev/null 2>&1; then
|
|
537
|
+
log_debug "[$(date)] Local server not available, trying remote channel config..."
|
|
538
|
+
_try_remote
|
|
539
|
+
fi
|
|
502
540
|
fi
|
|
503
541
|
|
|
504
542
|
# 读取当前项目使用的机器人名称和 ccId
|
|
@@ -588,12 +626,14 @@ fi
|
|
|
588
626
|
# 规则:删除命令→拒绝,项目内操作→允许,项目外操作→拒绝
|
|
589
627
|
log_debug "[$(date)] Executing smart auto-approval"
|
|
590
628
|
|
|
591
|
-
#
|
|
629
|
+
# 检查是否是删除命令(仅匹配命令行本身,不匹配 heredoc 内容)
|
|
592
630
|
IS_DELETE=0
|
|
593
631
|
if [[ "$TOOL_NAME" == "Bash" ]]; then
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
632
|
+
# 只取命令的第一行(避免 heredoc 内容干扰)
|
|
633
|
+
FIRST_LINE=$(echo "$TOOL_INPUT" | jq -r '.command // empty' | head -1)
|
|
634
|
+
log_debug "[$(date)] Checking delete: FIRST_LINE=$FIRST_LINE"
|
|
635
|
+
if [[ "$FIRST_LINE" == rm\\ * ]] || [[ "$FIRST_LINE" == rm ]] \\
|
|
636
|
+
|| echo "$FIRST_LINE" | grep -qE '(^|[;&|(] *)(rm |rmdir )'; then
|
|
597
637
|
IS_DELETE=1
|
|
598
638
|
fi
|
|
599
639
|
fi
|
|
@@ -615,10 +655,32 @@ IS_IN_PROJECT=0
|
|
|
615
655
|
case "$TOOL_NAME" in
|
|
616
656
|
Bash)
|
|
617
657
|
CMD=$(echo "$TOOL_INPUT" | jq -r '.command // empty')
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
if [[ "$CMD" == *"$PROJECT_DIR"* ]]
|
|
658
|
+
EXEC_CWD=$(pwd)
|
|
659
|
+
log_debug "[$(date)] Bash CMD=$CMD, EXEC_CWD=$EXEC_CWD"
|
|
660
|
+
if [[ "$CMD" == *"$PROJECT_DIR"* ]]; then
|
|
661
|
+
# 明确包含项目路径 → 项目内
|
|
621
662
|
IS_IN_PROJECT=1
|
|
663
|
+
elif echo "$CMD" | grep -qE '(^|[ \t])/[a-zA-Z0-9]'; then
|
|
664
|
+
# 含有绝对路径:过滤掉项目路径和安全系统目录,看是否还有真正的项目外路径
|
|
665
|
+
OUTSIDE=$(echo "$CMD" | grep -oE '(^| )/[a-zA-Z0-9][^ \t>|;&]*' | tr -d ' ' \
|
|
666
|
+
| grep -v "^$PROJECT_DIR" \
|
|
667
|
+
| grep -vE '^(/tmp/|/var/tmp/|/dev/null|/dev/stdin|/dev/stdout|/dev/stderr|/dev/fd/)')
|
|
668
|
+
if [[ -z "$OUTSIDE" ]]; then
|
|
669
|
+
# 绝对路径全是项目内或安全临时目录 → 以执行位置为准
|
|
670
|
+
log_debug "[$(date)] Only safe abs paths, checking EXEC_CWD: $EXEC_CWD"
|
|
671
|
+
if [[ "$EXEC_CWD" == "$PROJECT_DIR"* ]]; then
|
|
672
|
+
IS_IN_PROJECT=1
|
|
673
|
+
fi
|
|
674
|
+
else
|
|
675
|
+
log_debug "[$(date)] Outside abs path detected: $OUTSIDE"
|
|
676
|
+
IS_IN_PROJECT=0
|
|
677
|
+
fi
|
|
678
|
+
else
|
|
679
|
+
# 无绝对路径(相对路径或纯命令如 npm/git)→ 以执行位置为准
|
|
680
|
+
log_debug "[$(date)] No absolute path, checking EXEC_CWD: $EXEC_CWD"
|
|
681
|
+
if [[ "$EXEC_CWD" == "$PROJECT_DIR"* ]]; then
|
|
682
|
+
IS_IN_PROJECT=1
|
|
683
|
+
fi
|
|
622
684
|
fi
|
|
623
685
|
;;
|
|
624
686
|
Write|Edit)
|
package/dist/project-config.d.ts
CHANGED
|
@@ -20,6 +20,7 @@ export interface WechatModeConfig {
|
|
|
20
20
|
autoApprove?: boolean;
|
|
21
21
|
autoApproveTimeout?: number;
|
|
22
22
|
heartbeatJobId?: string;
|
|
23
|
+
mode?: 'channel' | 'http';
|
|
23
24
|
}
|
|
24
25
|
/**
|
|
25
26
|
* 获取项目配置文件路径
|
|
@@ -147,3 +148,7 @@ export declare function removeStopHook(projectDir: string): {
|
|
|
147
148
|
path: string;
|
|
148
149
|
existed: boolean;
|
|
149
150
|
};
|
|
151
|
+
/** 进入微信模式时注册 PID → projectDir */
|
|
152
|
+
export declare function registerActiveProject(claudePid: number, projectDir: string): void;
|
|
153
|
+
/** 退出微信模式时注销 */
|
|
154
|
+
export declare function unregisterActiveProject(projectDir: string): void;
|
package/dist/project-config.js
CHANGED
|
@@ -405,3 +405,36 @@ export function removeStopHook(projectDir) {
|
|
|
405
405
|
return { success: false, path: settingsPath, existed: false };
|
|
406
406
|
}
|
|
407
407
|
}
|
|
408
|
+
// ============================================================
|
|
409
|
+
// 活跃项目索引(PID → projectDir,供 permission hook 使用)
|
|
410
|
+
// ============================================================
|
|
411
|
+
const ACTIVE_PROJECTS_FILE = path.join(os.homedir(), '.wecom-aibot-mcp', 'active-projects.json');
|
|
412
|
+
function readActiveProjects() {
|
|
413
|
+
if (!fs.existsSync(ACTIVE_PROJECTS_FILE))
|
|
414
|
+
return [];
|
|
415
|
+
try {
|
|
416
|
+
return JSON.parse(fs.readFileSync(ACTIVE_PROJECTS_FILE, 'utf-8'));
|
|
417
|
+
}
|
|
418
|
+
catch {
|
|
419
|
+
return [];
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
function writeActiveProjects(entries) {
|
|
423
|
+
const dir = path.dirname(ACTIVE_PROJECTS_FILE);
|
|
424
|
+
if (!fs.existsSync(dir))
|
|
425
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
426
|
+
fs.writeFileSync(ACTIVE_PROJECTS_FILE, JSON.stringify(entries, null, 2));
|
|
427
|
+
}
|
|
428
|
+
/** 进入微信模式时注册 PID → projectDir */
|
|
429
|
+
export function registerActiveProject(claudePid, projectDir) {
|
|
430
|
+
const entries = readActiveProjects().filter(e => e.projectDir !== projectDir);
|
|
431
|
+
entries.push({ pid: claudePid, projectDir });
|
|
432
|
+
writeActiveProjects(entries);
|
|
433
|
+
logger.log(`[project-config] 注册活跃项目: pid=${claudePid} projectDir=${projectDir}`);
|
|
434
|
+
}
|
|
435
|
+
/** 退出微信模式时注销 */
|
|
436
|
+
export function unregisterActiveProject(projectDir) {
|
|
437
|
+
const entries = readActiveProjects().filter(e => e.projectDir !== projectDir);
|
|
438
|
+
writeActiveProjects(entries);
|
|
439
|
+
logger.log(`[project-config] 注销活跃项目: ${projectDir}`);
|
|
440
|
+
}
|
package/dist/tools/index.js
CHANGED
|
@@ -25,7 +25,7 @@ import { callDocTool } from '../doc-proxy.js';
|
|
|
25
25
|
import { connectRobot, disconnectRobot, getClient, getConnectionState, } from '../connection-manager.js';
|
|
26
26
|
import { registerCcId, unregisterCcId, getRobotByCcId, getProjectDirByCcId, generateCcId, } from '../http-server.js';
|
|
27
27
|
import { subscribeWecomMessageByCcId } from '../message-bus.js';
|
|
28
|
-
import { updateWechatModeConfig, loadWechatModeConfig, addPermissionHook, removePermissionHook, addStopHook, removeStopHook } from '../project-config.js';
|
|
28
|
+
import { updateWechatModeConfig, loadWechatModeConfig, addPermissionHook, removePermissionHook, addStopHook, removeStopHook, registerActiveProject, unregisterActiveProject } from '../project-config.js';
|
|
29
29
|
import { logger } from '../logger.js';
|
|
30
30
|
// 辅助函数:从 ccId 获取客户端
|
|
31
31
|
async function getConnectedClient(ccId) {
|
|
@@ -431,7 +431,10 @@ npx @vrs-soft/wecom-aibot-mcp
|
|
|
431
431
|
ccId: finalCcId,
|
|
432
432
|
autoApprove: auto_approve,
|
|
433
433
|
autoApproveTimeout: auto_approve_timeout,
|
|
434
|
+
mode,
|
|
434
435
|
});
|
|
436
|
+
// 注册 PID → projectDir(供 permission hook 通过进程树匹配项目)
|
|
437
|
+
registerActiveProject(process.ppid ?? process.pid, projectDir);
|
|
435
438
|
// 安装 skill 到项目本地(支持远程部署 MCP)
|
|
436
439
|
const skillResult = installSkill(projectDir);
|
|
437
440
|
// 添加 PermissionRequest hook 到项目 settings.json
|
|
@@ -486,9 +489,10 @@ npx @vrs-soft/wecom-aibot-mcp
|
|
|
486
489
|
}
|
|
487
490
|
// 断开连接
|
|
488
491
|
disconnectRobot(robotName);
|
|
489
|
-
// 更新项目配置文件中的 wechatMode 为 false
|
|
492
|
+
// 更新项目配置文件中的 wechatMode 为 false,注销 PID 索引
|
|
490
493
|
const projectDir = project_dir || process.cwd();
|
|
491
494
|
updateWechatModeConfig(projectDir, { wechatMode: false });
|
|
495
|
+
unregisterActiveProject(projectDir);
|
|
492
496
|
// 删除 PermissionRequest hook 从项目 settings.json
|
|
493
497
|
const hookResult = removePermissionHook(projectDir);
|
|
494
498
|
// 删除 Stop hook 从项目 settings.json
|