@vrs-soft/wecom-aibot-mcp 3.4.9-beta.0 → 3.4.9-beta.2

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.
@@ -436,13 +436,32 @@ function connectSSE(ccId) {
436
436
  sseWatchdogActive = true; // v3.4.3
437
437
  // v3.4.5: 每 10s 主动 ping daemon,证明客户端还活着 → 刷 lastOnline
438
438
  // 没 ping daemon 端 30s 后视为客户端死亡,立刻 unregister(v1.3.12 daemon 端机制)
439
+ // v3.4.9-beta.2: ping 收到非 2xx(含 404 "ccId not registered")→ abort SSE 触发重连链
440
+ // 修复"SSE 假死但 daemon 已 unregister,CC 端不知情继续飘着"的漏洞
439
441
  if (ccId) {
440
- pingTimer = setInterval(() => {
442
+ pingTimer = setInterval(async () => {
441
443
  const pingUrl = `${MCP_URL}/cc/${encodeURIComponent(ccId)}/ping`;
442
- fetch(pingUrl, {
443
- method: 'POST',
444
- headers: { 'Content-Type': 'application/json', ...getAuthHeaders() },
445
- }).catch(err => logChannel('ping daemon failed', { error: String(err) }));
444
+ try {
445
+ const res = await fetch(pingUrl, {
446
+ method: 'POST',
447
+ headers: { 'Content-Type': 'application/json', ...getAuthHeaders() },
448
+ });
449
+ if (!res.ok) {
450
+ logger.warn?.('ping 收到非 2xx,abort SSE 触发重连', { ccId, status: res.status });
451
+ try {
452
+ sseAbortController?.abort();
453
+ }
454
+ catch { /* ignore */ }
455
+ }
456
+ }
457
+ catch (err) {
458
+ logChannel('ping daemon failed', { error: String(err) });
459
+ // 网络错误也 abort 触发重连
460
+ try {
461
+ sseAbortController?.abort();
462
+ }
463
+ catch { /* ignore */ }
464
+ }
446
465
  }, 10000);
447
466
  }
448
467
  while (true) {
@@ -1097,6 +1116,11 @@ function registerChannelTools(server) {
1097
1116
  project_dir: z.string().optional().describe('项目目录路径(用于更新配置文件)'),
1098
1117
  }, async ({ cc_id, project_dir }) => {
1099
1118
  const localProjectDir = project_dir || process.cwd();
1119
+ // v3.4.9-beta.2: 顺序改成"先转发 daemon exit,再 abort SSE"
1120
+ // 否则 mcp 端 abort SSE → daemon 端 SSE close 先触发 unregisterCcId 走 grace period(不是 instant)
1121
+ // → daemon 端 exit_headless_mode 走 error 分支(getConnectedClient 找不到 ccId 了)
1122
+ // 结果:instant 释放 robot 的分支永远进不去
1123
+ const result = await forwardToHttpMcp('exit_headless_mode', { cc_id, project_dir: localProjectDir });
1100
1124
  // 断开 SSE 连接(abort 后重连逻辑不会触发)
1101
1125
  if (sseAbortController) {
1102
1126
  sseAbortController.abort();
@@ -1108,7 +1132,15 @@ function registerChannelTools(server) {
1108
1132
  // 注销本地 active-projects 记录
1109
1133
  unregisterActiveProject(localProjectDir);
1110
1134
  logChannel('本地 active-projects 已注销', { projectDir: localProjectDir });
1111
- return forwardToHttpMcp('exit_headless_mode', { cc_id, project_dir: localProjectDir });
1135
+ // v3.4.9-beta.1: 关闭本地 wecom-aibot.json wechatMode 开关(治本)
1136
+ // 否则 stop-hook 读到 wechatMode:true 会阻塞 stop 并发 stderr 提示"恢复轮询",
1137
+ // LLM 看到提示又调 enter_headless_mode → CC 被"自动复活"永远退不掉
1138
+ updateWechatModeConfig(localProjectDir, {
1139
+ wechatMode: false,
1140
+ heartbeatJobId: undefined,
1141
+ });
1142
+ logger.info('本地 wecom-aibot.json wechatMode 已置 false', { projectDir: localProjectDir });
1143
+ return result;
1112
1144
  });
1113
1145
  // ============================================
1114
1146
  // 工具 11: 从消息识别用户
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vrs-soft/wecom-aibot-mcp",
3
- "version": "3.4.9-beta.0",
3
+ "version": "3.4.9-beta.2",
4
4
  "description": "企业微信智能机器人 MCP 客户端 - 连接 wecom-aibot-server daemon",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",