@vrs-soft/wecom-aibot-mcp 3.4.6 → 3.4.8

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.
@@ -391,16 +391,18 @@ function connectSSE(ccId) {
391
391
  logChannel(`SSE 连接失败(${res.status}),${delay / 1000} 秒后重连`, { ccId });
392
392
  setTimeout(async () => {
393
393
  httpSessionId = null; // 重置 session,防止使用 server 重启前的旧 session
394
- if (ccId) {
395
- // 重新调 enter_headless_mode 恢复 server 端 ccId 注册
396
- await forwardToHttpMcp('enter_headless_mode', {
397
- cc_id: ccId,
398
- robot_id: sseRobotId,
399
- mode: 'channel',
400
- project_dir: sseProjectDir || process.cwd(),
401
- }).catch((e) => logChannel('重注册 ccId 失败', { error: String(e) }));
402
- }
403
- connectSSE(ccId);
394
+ // v3.4.8: SSE 断线自动重连时也走 auth 派生(不用 module-level 缓存的 ccId / sseProjectDir)
395
+ // 避免首次 enter 用了错值时,SSE 重连永远发错值给 daemon
396
+ const authProjectDir = process.cwd();
397
+ const authCcId = path.basename(authProjectDir);
398
+ // 重新调 enter_headless_mode 恢复 server 端 ccId 注册(用 auth 值)
399
+ await forwardToHttpMcp('enter_headless_mode', {
400
+ cc_id: authCcId,
401
+ robot_id: sseRobotId,
402
+ mode: 'channel',
403
+ project_dir: authProjectDir,
404
+ }).catch((e) => logChannel('重注册 ccId 失败', { error: String(e) }));
405
+ connectSSE(authCcId); // 用 auth 值,不用缓存的 ccId
404
406
  }, delay);
405
407
  }
406
408
  return;
@@ -948,24 +950,50 @@ function registerChannelTools(server) {
948
950
  .describe('运行模式:channel=SSE推送(推荐),http=轮询(兼容)'),
949
951
  auto_approve_timeout: z.number().optional().default(600).describe('超时自动决策等待时间(秒,默认 600 即 10 分钟)'),
950
952
  }, async ({ agent_name, cc_id, robot_id, project_dir, mode, auto_approve_timeout }) => {
951
- // v3.3.2: 远端 daemon 看不到本地 .claude/wecom-aibot.json,无法走 existingConfig 复用 ccId 的路径。
952
- // 这里在 channel-server 本地读保存的 ccId,没有传入 cc_id 时自动塞进去 —— 避免每次 enter
953
- // 都因 stale registry entry 撞冲突,生成 商务机器人-2 / -3 之类的奇怪 ccId。
954
- const effectiveProjectDir = project_dir || process.cwd();
955
- let effectiveCcId = cc_id;
956
- if (!effectiveCcId) {
957
- const saved = loadWechatModeConfig(effectiveProjectDir);
958
- if (saved?.ccId) {
959
- effectiveCcId = saved.ccId;
960
- logger.info('reuse saved ccId from local wecom-aibot.json', { projectDir: effectiveProjectDir, ccId: saved.ccId });
961
- }
953
+ // ============================================
954
+ // v3.4.7: 权威源 = 运行时独立派生,LLM 传入不采用
955
+ // ============================================
956
+ // 根因:LLM 会传业务名/记忆里的字面量当 cc_id("集装箱配比优化系统"),
957
+ // 甚至同时篡改 project_dir basename 匹配绕过校验
958
+ // 修复:channel-server 是 Claude Code stdio 子进程,process.cwd() 由 Claude Code
959
+ // 启动时用户 cd 到项目目录设定,LLM 通过 tool-call 改不了 cwd
960
+ // → 强制用 cwd 派生权威 project_dir 和 cc_id
961
+ // LLM 传入的 cc_id / project_dir / agent_name 只 log 不采用
962
+ // 边界:这只挡"LLM 篡改"(当前威胁),挡不了"用户在非项目目录启动 claude"
963
+ // 后者要用 MCP roots capability(后续 v3.4.8)
964
+ const authProjectDir = process.cwd();
965
+ const authCcId = path.basename(authProjectDir);
966
+ const authAgentName = authCcId;
967
+ // 观测 LLM 有没有企图伪造
968
+ if (cc_id && cc_id !== authCcId) {
969
+ logger.warn?.('LLM cc_id ignored', { llmCcId: cc_id, authCcId, authProjectDir });
970
+ }
971
+ if (project_dir && project_dir !== authProjectDir) {
972
+ logger.warn?.('LLM project_dir ignored', { llmProjectDir: project_dir, authProjectDir });
973
+ }
974
+ if (agent_name && agent_name !== authAgentName) {
975
+ logger.warn?.('LLM agent_name ignored', { llmAgentName: agent_name, authAgentName });
976
+ }
977
+ // 只有 saved config 是本地文件(Claude Code 启动前就存在),是可信源,但要跟 auth 值一致才用
978
+ const saved = loadWechatModeConfig(authProjectDir);
979
+ let effectiveCcId = authCcId; // 默认 auth 值
980
+ if (saved?.ccId && saved.ccId === authCcId) {
981
+ // 一致:正常复用
982
+ effectiveCcId = saved.ccId;
983
+ }
984
+ else if (saved?.ccId && saved.ccId !== authCcId) {
985
+ // 本地 config 存了错值(可能是老版本 LLM 拍脑袋写的)→ 强制忽略,改用 auth 值
986
+ logger.warn?.('saved wecom-aibot.json 里 ccId 跟 auth 不一致,强制覆盖', {
987
+ savedCcId: saved.ccId,
988
+ authCcId,
989
+ });
962
990
  }
963
- // 转发请求
991
+ // 转发请求(永远用 auth 派生值,无视 LLM 传入)
964
992
  const result = await forwardToHttpMcp('enter_headless_mode', {
965
- agent_name,
993
+ agent_name: authAgentName,
966
994
  cc_id: effectiveCcId,
967
- robot_id,
968
- project_dir: effectiveProjectDir,
995
+ robot_id, // robot_id 保留(要走 fail-closed 让用户选,见 SKILL.md)
996
+ project_dir: authProjectDir,
969
997
  mode,
970
998
  auto_approve_timeout,
971
999
  });
@@ -977,12 +1005,12 @@ function registerChannelTools(server) {
977
1005
  const parsed = JSON.parse(content[0].text);
978
1006
  if (parsed.ccId) {
979
1007
  logger.info('Got ccId, connecting SSE', { ccId: parsed.ccId, mode });
980
- // 保存连接参数供重连复用
981
- sseRobotId = robot_id || parsed.robotName;
982
- sseProjectDir = project_dir || process.cwd();
1008
+ // v3.4.7: 保存 auth 派生值(不用 LLM 传入的 robot_id / project_dir),SSE 重连时用
1009
+ sseRobotId = parsed.robotName; // daemon 返回的权威值
1010
+ sseProjectDir = authProjectDir; // channel-server 派生的 auth 值
983
1011
  connectSSE(parsed.ccId);
984
1012
  // Channel 模式:在本地项目写入 PermissionRequest hook
985
- const localProjectDir = project_dir || process.cwd();
1013
+ const localProjectDir = authProjectDir; // v3.4.7: 用 auth 值
986
1014
  // v3.4.0: 先确保 ~/.wecom-aibot-mcp/permission-hook.mjs 存在
987
1015
  // (addPermissionHook 只往项目 settings.json 写命令,不装文件;
988
1016
  // 没装文件的话 Claude TUI 执行 hook 命令会 ENOENT)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vrs-soft/wecom-aibot-mcp",
3
- "version": "3.4.6",
3
+ "version": "3.4.8",
4
4
  "description": "企业微信智能机器人 MCP 客户端 - 连接 wecom-aibot-server daemon",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",