@xcanwin/manyoyo 7.0.25 → 7.0.26

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/lib/web/server.js CHANGED
@@ -42,6 +42,12 @@ const WEB_TERMINAL_DEFAULT_COLS = 120;
42
42
  const WEB_TERMINAL_DEFAULT_ROWS = 36;
43
43
  const WEB_TERMINAL_MIN_COLS = 40;
44
44
  const WEB_TERMINAL_MIN_ROWS = 12;
45
+ // 终端 WebSocket 空闲心跳:纯交互式终端可能长时间没有任何数据,
46
+ // 反向代理 / 移动网络的空闲超时(常见 60s)会悄悄掐断连接,这里定期发 ping
47
+ // 帧制造流量。判死放宽到连续 3 次没回 pong(约 90s),手机切后台导致连接被
48
+ // 系统短暂挂起时不至于被误杀——误杀意味着容器里的 shell 直接没了
49
+ const WEB_TERMINAL_PING_INTERVAL_MS = 30000;
50
+ const WEB_TERMINAL_MAX_MISSED_PONGS = 3;
45
51
  const WEB_AGENT_CONTEXT_MAX_MESSAGES = 24;
46
52
  const WEB_AGENT_CONTEXT_MAX_CHARS = 6000;
47
53
  const WEB_AGENT_CONTEXT_PER_MESSAGE_MAX_CHARS = 600;
@@ -4406,11 +4412,32 @@ function bindTerminalWebSocket(ctx, state, ws, containerName, cols, rows) {
4406
4412
  rows
4407
4413
  });
4408
4414
 
4415
+ let missedPongs = 0;
4416
+ ws.on('pong', () => {
4417
+ missedPongs = 0;
4418
+ });
4419
+ const pingTimer = setInterval(() => {
4420
+ if (ws.readyState !== WebSocket.OPEN) {
4421
+ return;
4422
+ }
4423
+ if (missedPongs >= WEB_TERMINAL_MAX_MISSED_PONGS) {
4424
+ ws.terminate();
4425
+ return;
4426
+ }
4427
+ missedPongs += 1;
4428
+ try {
4429
+ ws.ping();
4430
+ } catch (e) {
4431
+ // ping 失败说明链路已经不可用,交给 close/error 事件收尾
4432
+ }
4433
+ }, WEB_TERMINAL_PING_INTERVAL_MS);
4434
+
4409
4435
  const cleanup = () => {
4410
4436
  if (session.closing) {
4411
4437
  return;
4412
4438
  }
4413
4439
  session.closing = true;
4440
+ clearInterval(pingTimer);
4414
4441
  state.terminalSessions.delete(sessionId);
4415
4442
  if (ptyProcess && !ptyProcess.killed) {
4416
4443
  ptyProcess.kill('SIGTERM');
@@ -4472,6 +4499,12 @@ function bindTerminalWebSocket(ctx, state, ws, containerName, cols, rows) {
4472
4499
  return;
4473
4500
  }
4474
4501
 
4502
+ if (payload.type === 'ping') {
4503
+ // 浏览器 JS 发不出 WebSocket ping 帧,前端改用应用层消息保活上行链路
4504
+ sendTerminalEvent(ws, 'pong');
4505
+ return;
4506
+ }
4507
+
4475
4508
  if (payload.type === 'close') {
4476
4509
  ws.close();
4477
4510
  }
@@ -6061,6 +6094,12 @@ async function startWebServer(options) {
6061
6094
  url.searchParams.get('rows')
6062
6095
  );
6063
6096
 
6097
+ // 升级成 WebSocket 后这条连接是长连接,不能再受 HTTP 请求的空闲超时约束;
6098
+ // 同时打开 TCP keepalive,避免中间设备(NAT / 代理)回收看起来空闲的连接
6099
+ socket.setTimeout(0);
6100
+ socket.setNoDelay(true);
6101
+ socket.setKeepAlive(true, WEB_TERMINAL_PING_INTERVAL_MS);
6102
+
6064
6103
  ensureWebContainer(ctx, state, sessionRef.containerName)
6065
6104
  .then(() => {
6066
6105
  wsServer.handleUpgrade(req, socket, head, ws => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xcanwin/manyoyo",
3
- "version": "7.0.25",
3
+ "version": "7.0.26",
4
4
  "imageVersion": "1.9.1-common",
5
5
  "playwrightCliVersion": "0.1.18",
6
6
  "description": "AI Agent CLI Security Sandbox for Docker and Podman",