dg-lab-mcp-sse-server 1.0.3 → 1.1.3

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 CHANGED
@@ -10,6 +10,7 @@
10
10
  - **波形管理**: 支持解析、保存、发送 DG-LAB 波形数据
11
11
  - **持续播放**: 支持波形持续循环播放
12
12
  - **会话管理**: 支持设备别名、多设备管理
13
+ - **断线重连**: 设备断开后保留会话,支持在超时时间内重连而不丢失设置
13
14
 
14
15
  ## 安装
15
16
 
@@ -69,7 +70,8 @@ PORT=8080 npx dg-lab-mcp-sse-server
69
70
  "env": {
70
71
  "PUBLIC_IP": "your.public.ip",
71
72
  "PORT": "3323",
72
- "CONNECTION_TIMEOUT_MINUTES": "10"
73
+ "CONNECTION_TIMEOUT_MINUTES": "10",
74
+ "RECONNECTION_TIMEOUT_MINUTES": "5"
73
75
  }
74
76
  }
75
77
  }
@@ -87,10 +89,28 @@ PORT=8080 npx dg-lab-mcp-sse-server
87
89
  | `SSE_PATH` | /sse | SSE 端点路径 |
88
90
  | `POST_PATH` | /message | POST 端点路径 |
89
91
  | `CONNECTION_TIMEOUT_MINUTES` | 5 | 未绑定设备的超时时间(分钟) |
92
+ | `RECONNECTION_TIMEOUT_MINUTES` | 5 | 已绑定设备断开后的重连等待时间(分钟),超时后会话将被删除 |
90
93
  | `HEARTBEAT_INTERVAL` | 30000 | WebSocket 心跳间隔 (ms) |
91
94
  | `STALE_DEVICE_TIMEOUT` | 3600000 | 设备活跃超时 (ms),默认 1 小时 |
92
95
  | `WAVEFORM_STORE_PATH` | ./data/waveforms.json | 波形存储路径 |
93
96
 
97
+ ## 会话管理机制
98
+
99
+ ### 连接超时 (CONNECTION_TIMEOUT_MINUTES)
100
+ - 创建设备后,如果在指定时间内未完成 APP 绑定,会话将自动销毁
101
+ - 默认 5 分钟,可通过环境变量配置
102
+
103
+ ### 重连超时 (RECONNECTION_TIMEOUT_MINUTES)
104
+ - 已绑定的设备断开连接后,会话会保留一段时间等待重连
105
+ - 在此期间设备可以重新连接而不丢失设置(强度、波形等)
106
+ - 超时后会话将被自动删除
107
+ - 默认 5 分钟,可通过环境变量配置
108
+ - **注意**: 未绑定的设备断开后会立即删除,不会等待重连
109
+
110
+ ### 状态查询
111
+ - 使用 `dg_list_devices` 可以看到设备的连接状态和剩余重连时间
112
+ - 使用 `dg_get_device_status` 可以获取详细的连接信息,包括 `disconnectedAt` 和 `reconnectionTimeRemaining`
113
+
94
114
  ## 使用流程
95
115
 
96
116
  1. **创建设备**: 调用 `dg_create_device` 获取二维码内容
package/dist/app.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAE7C,OAAO,EAAuC,KAAK,SAAS,EAAE,MAAM,UAAU,CAAC;AAE/E,OAAO,EAAE,WAAW,EAAwB,MAAM,gBAAgB,CAAC;AACnE,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAI5C,OAAO,EAAE,eAAe,EAAiB,MAAM,oBAAoB,CAAC;AAGpE;;GAEG;AACH,MAAM,WAAW,GAAG;IAClB,MAAM,EAAE,YAAY,CAAC;IACrB,MAAM,EAAE,SAAS,CAAC;IAClB,WAAW,EAAE,WAAW,CAAC;IACzB,cAAc,EAAE,cAAc,CAAC;IAC/B,QAAQ,EAAE,aAAa,CAAC;IACxB,eAAe,EAAE,eAAe,CAAC;IACjC,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,SAAS,IAAI,GAAG,CA+C/B;AA4HD;;;;;;GAMG;AACH,wBAAsB,QAAQ,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAqBtD"}
1
+ {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAE7C,OAAO,EAAuC,KAAK,SAAS,EAAE,MAAM,UAAU,CAAC;AAE/E,OAAO,EAAE,WAAW,EAAwB,MAAM,gBAAgB,CAAC;AACnE,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAI5C,OAAO,EAAE,eAAe,EAAiB,MAAM,oBAAoB,CAAC;AAGpE;;GAEG;AACH,MAAM,WAAW,GAAG;IAClB,MAAM,EAAE,YAAY,CAAC;IACrB,MAAM,EAAE,SAAS,CAAC;IAClB,WAAW,EAAE,WAAW,CAAC;IACzB,cAAc,EAAE,cAAc,CAAC;IAC/B,QAAQ,EAAE,aAAa,CAAC;IACxB,eAAe,EAAE,eAAe,CAAC;IACjC,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,SAAS,IAAI,GAAG,CAkD/B;AA4HD;;;;;;GAMG;AACH,wBAAsB,QAAQ,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAqBtD"}
package/dist/cli.js CHANGED
@@ -91,7 +91,8 @@ function loadConfig() {
91
91
  waveformStorePath: getEnvString("WAVEFORM_STORE_PATH", "./data/waveforms.json"),
92
92
  heartbeatInterval: getEnvNumber("HEARTBEAT_INTERVAL", 3e4),
93
93
  staleDeviceTimeout: getEnvNumber("STALE_DEVICE_TIMEOUT", 36e5),
94
- connectionTimeoutMinutes: getEnvNumber("CONNECTION_TIMEOUT_MINUTES", 5)
94
+ connectionTimeoutMinutes: getEnvNumber("CONNECTION_TIMEOUT_MINUTES", 5),
95
+ reconnectionTimeoutMinutes: getEnvNumber("RECONNECTION_TIMEOUT_MINUTES", 5)
95
96
  };
96
97
  validateConfig(config);
97
98
  return config;
@@ -146,6 +147,12 @@ function validateConfig(config) {
146
147
  context: { connectionTimeoutMinutes: config.connectionTimeoutMinutes }
147
148
  });
148
149
  }
150
+ if (config.reconnectionTimeoutMinutes < 1 || config.reconnectionTimeoutMinutes > 60) {
151
+ throw new ConfigError(`\u91CD\u8FDE\u8D85\u65F6\u65F6\u95F4\u65E0\u6548: ${config.reconnectionTimeoutMinutes}\uFF0C\u5FC5\u987B\u5728 1-60 \u5206\u949F\u8303\u56F4\u5185`, {
152
+ code: "CONFIG_LOAD_FAILED" /* CONFIG_LOAD_FAILED */,
153
+ context: { reconnectionTimeoutMinutes: config.reconnectionTimeoutMinutes }
154
+ });
155
+ }
149
156
  }
150
157
  var configInstance = null;
151
158
  function getConfig() {
@@ -754,10 +761,14 @@ var SessionManager = class {
754
761
  cleanupTimer = null;
755
762
  /** 连接超时时间(毫秒) */
756
763
  connectionTimeoutMs;
757
- constructor(connectionTimeoutMinutes = 5) {
764
+ /** 重连超时时间(毫秒) */
765
+ reconnectionTimeoutMs;
766
+ constructor(connectionTimeoutMinutes = 5, reconnectionTimeoutMinutes = 5) {
758
767
  this.connectionTimeoutMs = connectionTimeoutMinutes * 60 * 1e3;
768
+ this.reconnectionTimeoutMs = reconnectionTimeoutMinutes * 60 * 1e3;
759
769
  this.startCleanupTimer();
760
770
  console.log(`[\u4F1A\u8BDD] \u8FDE\u63A5\u8D85\u65F6\u8BBE\u7F6E: ${connectionTimeoutMinutes} \u5206\u949F`);
771
+ console.log(`[\u4F1A\u8BDD] \u91CD\u8FDE\u8D85\u65F6\u8BBE\u7F6E: ${reconnectionTimeoutMinutes} \u5206\u949F`);
761
772
  }
762
773
  /**
763
774
  * 创建新的设备会话
@@ -785,7 +796,9 @@ var SessionManager = class {
785
796
  strengthLimitB: 200,
786
797
  lastActive: now,
787
798
  createdAt: now,
788
- connectionTimeoutId: null
799
+ connectionTimeoutId: null,
800
+ reconnectionTimeoutId: null,
801
+ disconnectedAt: null
789
802
  };
790
803
  session.connectionTimeoutId = setTimeout(() => {
791
804
  const currentSession = this.sessions.get(deviceId);
@@ -858,6 +871,10 @@ var SessionManager = class {
858
871
  clearTimeout(session.connectionTimeoutId);
859
872
  session.connectionTimeoutId = null;
860
873
  }
874
+ if (session.reconnectionTimeoutId) {
875
+ clearTimeout(session.reconnectionTimeoutId);
876
+ session.reconnectionTimeoutId = null;
877
+ }
861
878
  if (session.ws) {
862
879
  try {
863
880
  session.ws.close();
@@ -985,6 +1002,83 @@ var SessionManager = class {
985
1002
  }
986
1003
  return false;
987
1004
  }
1005
+ /**
1006
+ * 处理设备断开连接
1007
+ *
1008
+ * 如果设备已绑定,启动重连超时计时器并保留会话。
1009
+ * 如果设备未绑定,立即删除会话。
1010
+ *
1011
+ * @param deviceId - 设备 ID
1012
+ * @returns 是否保留会话等待重连(true=保留,false=已删除)
1013
+ */
1014
+ handleDisconnection(deviceId) {
1015
+ const session = this.sessions.get(deviceId);
1016
+ if (!session) return false;
1017
+ if (session.connectionTimeoutId) {
1018
+ clearTimeout(session.connectionTimeoutId);
1019
+ session.connectionTimeoutId = null;
1020
+ }
1021
+ if (!session.boundToApp) {
1022
+ console.log(`[\u4F1A\u8BDD] \u672A\u7ED1\u5B9A\u8BBE\u5907\u65AD\u5F00: ${deviceId}\uFF0C\u7ACB\u5373\u5220\u9664`);
1023
+ this.deleteSession(deviceId);
1024
+ return false;
1025
+ }
1026
+ session.connected = false;
1027
+ session.disconnectedAt = /* @__PURE__ */ new Date();
1028
+ session.ws = null;
1029
+ session.reconnectionTimeoutId = setTimeout(() => {
1030
+ const currentSession = this.sessions.get(deviceId);
1031
+ if (currentSession && !currentSession.connected) {
1032
+ console.log(`[\u4F1A\u8BDD] \u91CD\u8FDE\u8D85\u65F6: ${deviceId} (${this.reconnectionTimeoutMs / 6e4} \u5206\u949F\u5185\u672A\u91CD\u8FDE)`);
1033
+ this.deleteSession(deviceId);
1034
+ }
1035
+ }, this.reconnectionTimeoutMs);
1036
+ console.log(`[\u4F1A\u8BDD] \u8BBE\u5907\u65AD\u5F00: ${deviceId}\uFF0C\u7B49\u5F85\u91CD\u8FDE (${this.reconnectionTimeoutMs / 6e4} \u5206\u949F)`);
1037
+ return true;
1038
+ }
1039
+ /**
1040
+ * 处理设备重新连接
1041
+ *
1042
+ * 恢复会话到已连接状态,取消重连超时计时器。
1043
+ * 保留所有会话数据(别名、强度等)。
1044
+ *
1045
+ * @param deviceId - 设备 ID
1046
+ * @param ws - 新的 WebSocket 连接
1047
+ * @param clientId - WebSocket 服务器分配的新 clientId
1048
+ * @returns 是否成功重连
1049
+ */
1050
+ handleReconnection(deviceId, ws, clientId) {
1051
+ const session = this.sessions.get(deviceId);
1052
+ if (!session) return false;
1053
+ if (session.reconnectionTimeoutId) {
1054
+ clearTimeout(session.reconnectionTimeoutId);
1055
+ session.reconnectionTimeoutId = null;
1056
+ }
1057
+ session.ws = ws;
1058
+ session.clientId = clientId;
1059
+ session.connected = true;
1060
+ session.disconnectedAt = null;
1061
+ session.lastActive = /* @__PURE__ */ new Date();
1062
+ console.log(`[\u4F1A\u8BDD] \u8BBE\u5907\u91CD\u8FDE\u6210\u529F: ${deviceId}`);
1063
+ return true;
1064
+ }
1065
+ /**
1066
+ * 获取剩余重连时间
1067
+ *
1068
+ * 计算设备断开后还有多少时间可以重连。
1069
+ *
1070
+ * @param deviceId - 设备 ID
1071
+ * @returns 剩余时间(毫秒),如果设备已连接或不存在则返回 null
1072
+ */
1073
+ getReconnectionTimeRemaining(deviceId) {
1074
+ const session = this.sessions.get(deviceId);
1075
+ if (!session || session.connected || !session.disconnectedAt) {
1076
+ return null;
1077
+ }
1078
+ const elapsed = Date.now() - session.disconnectedAt.getTime();
1079
+ const remaining = this.reconnectionTimeoutMs - elapsed;
1080
+ return remaining > 0 ? remaining : 0;
1081
+ }
988
1082
  /**
989
1083
  * 获取当前会话数量
990
1084
  */
@@ -1059,6 +1153,9 @@ var SessionManager = class {
1059
1153
  if (session.connectionTimeoutId) {
1060
1154
  clearTimeout(session.connectionTimeoutId);
1061
1155
  }
1156
+ if (session.reconnectionTimeoutId) {
1157
+ clearTimeout(session.reconnectionTimeoutId);
1158
+ }
1062
1159
  if (session.ws) {
1063
1160
  try {
1064
1161
  session.ws.close();
@@ -1071,13 +1168,8 @@ var SessionManager = class {
1071
1168
  };
1072
1169
 
1073
1170
  // src/ws-server.ts
1074
- import { WebSocketServer, WebSocket as WebSocket2 } from "ws";
1171
+ import { WebSocketServer, WebSocket } from "ws";
1075
1172
  import { v4 as uuidv43 } from "uuid";
1076
-
1077
- // src/ws-bridge.ts
1078
- import WebSocket from "ws";
1079
-
1080
- // src/ws-server.ts
1081
1173
  var DGLabWSServer = class {
1082
1174
  wss = null;
1083
1175
  clients = /* @__PURE__ */ new Map();
@@ -1385,7 +1477,7 @@ var DGLabWSServer = class {
1385
1477
  }
1386
1478
  /** 发送消息到 WebSocket */
1387
1479
  send(ws, msg) {
1388
- if (ws.readyState === WebSocket2.OPEN) {
1480
+ if (ws.readyState === WebSocket.OPEN) {
1389
1481
  ws.send(JSON.stringify(msg));
1390
1482
  }
1391
1483
  }
@@ -1408,7 +1500,7 @@ var DGLabWSServer = class {
1408
1500
  /** 创建内部控制器的模拟 WebSocket */
1409
1501
  createMockWebSocket(clientId) {
1410
1502
  return {
1411
- readyState: WebSocket2.OPEN,
1503
+ readyState: WebSocket.OPEN,
1412
1504
  send: (data) => {
1413
1505
  console.log(`[WS \u670D\u52A1\u5668] \u53D1\u9001\u7ED9\u63A7\u5236\u5668 ${clientId}: ${data}`);
1414
1506
  },
@@ -1475,7 +1567,7 @@ var DGLabWSServer = class {
1475
1567
  }
1476
1568
  if (client.boundTo) {
1477
1569
  const appClient = this.clients.get(client.boundTo);
1478
- if (appClient && appClient.ws.readyState === WebSocket2.OPEN) {
1570
+ if (appClient && appClient.ws.readyState === WebSocket.OPEN) {
1479
1571
  this.send(appClient.ws, {
1480
1572
  type: "break",
1481
1573
  clientId: controllerId,
@@ -1491,7 +1583,7 @@ var DGLabWSServer = class {
1491
1583
  this.options.onBindChange(controllerId, null);
1492
1584
  }
1493
1585
  }
1494
- if (client.ws.readyState === WebSocket2.OPEN) {
1586
+ if (client.ws.readyState === WebSocket.OPEN) {
1495
1587
  client.ws.close(1e3, "Disconnected by user");
1496
1588
  }
1497
1589
  this.clients.delete(controllerId);
@@ -1720,6 +1812,7 @@ function registerDeviceTools(toolManager, sessionManager, wsServer, publicIp) {
1720
1812
  - boundToApp: APP\u662F\u5426\u5DF2\u626B\u7801\u7ED1\u5B9A\uFF08\u5FC5\u987B\u4E3Atrue\u624D\u80FD\u63A7\u5236\u8BBE\u5907\uFF09
1721
1813
  - strengthA/B: \u5F53\u524DA/B\u901A\u9053\u5F3A\u5EA6(0-200)
1722
1814
  - strengthLimitA/B: A/B\u901A\u9053\u5F3A\u5EA6\u4E0A\u9650\uFF08\u7531APP\u8BBE\u7F6E\uFF09
1815
+ - reconnectionTimeRemaining: \u5269\u4F59\u91CD\u8FDE\u65F6\u95F4\uFF08\u79D2\uFF09\uFF0C\u4EC5\u5728\u8BBE\u5907\u65AD\u5F00\u65F6\u663E\u793A\uFF0Cnull\u8868\u793A\u8BBE\u5907\u5DF2\u8FDE\u63A5
1723
1816
  \u53EF\u9009\u53C2\u6570alias\u7528\u4E8E\u6309\u522B\u540D\u8FC7\u6EE4\u8BBE\u5907\u3002`,
1724
1817
  {
1725
1818
  type: "object",
@@ -1739,6 +1832,8 @@ function registerDeviceTools(toolManager, sessionManager, wsServer, publicIp) {
1739
1832
  }
1740
1833
  const devices = sessions.map((s) => {
1741
1834
  const isBound = s.clientId ? wsServer.isControllerBound(s.clientId) : false;
1835
+ const reconnectionTimeRemaining = sessionManager.getReconnectionTimeRemaining(s.deviceId);
1836
+ const reconnectionTimeRemainingSeconds = reconnectionTimeRemaining !== null ? Math.ceil(reconnectionTimeRemaining / 1e3) : null;
1742
1837
  return {
1743
1838
  deviceId: s.deviceId,
1744
1839
  alias: s.alias,
@@ -1747,7 +1842,8 @@ function registerDeviceTools(toolManager, sessionManager, wsServer, publicIp) {
1747
1842
  strengthA: s.strengthA,
1748
1843
  strengthB: s.strengthB,
1749
1844
  strengthLimitA: s.strengthLimitA,
1750
- strengthLimitB: s.strengthLimitB
1845
+ strengthLimitB: s.strengthLimitB,
1846
+ reconnectionTimeRemaining: reconnectionTimeRemainingSeconds
1751
1847
  };
1752
1848
  });
1753
1849
  return createToolResult(JSON.stringify({ devices, count: devices.length }));
@@ -1818,6 +1914,8 @@ function registerDeviceTools(toolManager, sessionManager, wsServer, publicIp) {
1818
1914
  const sessions = sessionManager.findByAlias(alias);
1819
1915
  const devices = sessions.map((s) => {
1820
1916
  const isBound = s.clientId ? wsServer.isControllerBound(s.clientId) : false;
1917
+ const reconnectionTimeRemaining = sessionManager.getReconnectionTimeRemaining(s.deviceId);
1918
+ const reconnectionTimeRemainingSeconds = reconnectionTimeRemaining !== null ? Math.ceil(reconnectionTimeRemaining / 1e3) : null;
1821
1919
  return {
1822
1920
  deviceId: s.deviceId,
1823
1921
  alias: s.alias,
@@ -1826,7 +1924,8 @@ function registerDeviceTools(toolManager, sessionManager, wsServer, publicIp) {
1826
1924
  strengthA: s.strengthA,
1827
1925
  strengthB: s.strengthB,
1828
1926
  strengthLimitA: s.strengthLimitA,
1829
- strengthLimitB: s.strengthLimitB
1927
+ strengthLimitB: s.strengthLimitB,
1928
+ reconnectionTimeRemaining: reconnectionTimeRemainingSeconds
1830
1929
  };
1831
1930
  });
1832
1931
  return createToolResult(
@@ -2785,8 +2884,11 @@ function registerControlTools(toolManager, sessionManager, wsServer) {
2785
2884
  `\u83B7\u53D6\u8BBE\u5907\u5B8C\u6574\u72B6\u6001\u4FE1\u606F\u3002
2786
2885
  \u5173\u952E\u5B57\u6BB5\uFF1A
2787
2886
  - boundToApp: \u662F\u5426\u5DF2\u7ED1\u5B9AAPP\uFF08\u5FC5\u987B\u4E3Atrue\u624D\u80FD\u63A7\u5236\u8BBE\u5907\uFF09
2887
+ - connected: \u8BBE\u5907\u662F\u5426\u5DF2\u8FDE\u63A5
2788
2888
  - strengthA/B: \u5F53\u524DA/B\u901A\u9053\u5F3A\u5EA6
2789
2889
  - strengthLimitA/B: A/B\u901A\u9053\u5F3A\u5EA6\u4E0A\u9650\uFF08\u7531APP\u8BBE\u7F6E\uFF0C\u4E0D\u53EF\u8D85\u8FC7\uFF09
2890
+ - disconnectedAt: \u8BBE\u5907\u65AD\u5F00\u8FDE\u63A5\u7684\u65F6\u95F4\u6233\uFF08\u4EC5\u5728\u65AD\u5F00\u65F6\u663E\u793A\uFF09
2891
+ - reconnectionTimeRemaining: \u5269\u4F59\u91CD\u8FDE\u65F6\u95F4\uFF08\u79D2\uFF09\uFF0C\u4EC5\u5728\u8BBE\u5907\u65AD\u5F00\u65F6\u663E\u793A
2790
2892
  \u5EFA\u8BAE\u5728dg_connect\u540E\u5728\u7528\u6237\u8BF4\u5DF2\u5B8C\u6210\u540E\u4F7F\u7528\u6B64\u63A5\u53E3\u68C0\u67E5boundToApp\u72B6\u6001\u3002`,
2791
2893
  {
2792
2894
  type: "object",
@@ -2805,6 +2907,8 @@ function registerControlTools(toolManager, sessionManager, wsServer) {
2805
2907
  if ("error" in deviceResult) return createToolError(deviceResult.error);
2806
2908
  const session = deviceResult.session;
2807
2909
  const isBound = session.clientId ? wsServer.isControllerBound(session.clientId) : false;
2910
+ const reconnectionTimeRemaining = sessionManager.getReconnectionTimeRemaining(session.deviceId);
2911
+ const reconnectionTimeRemainingSeconds = reconnectionTimeRemaining !== null ? Math.ceil(reconnectionTimeRemaining / 1e3) : null;
2808
2912
  return createToolResult(
2809
2913
  JSON.stringify({
2810
2914
  deviceId: session.deviceId,
@@ -2814,7 +2918,9 @@ function registerControlTools(toolManager, sessionManager, wsServer) {
2814
2918
  strengthA: session.strengthA,
2815
2919
  strengthB: session.strengthB,
2816
2920
  strengthLimitA: session.strengthLimitA,
2817
- strengthLimitB: session.strengthLimitB
2921
+ strengthLimitB: session.strengthLimitB,
2922
+ disconnectedAt: session.disconnectedAt ? session.disconnectedAt.toISOString() : null,
2923
+ reconnectionTimeRemaining: reconnectionTimeRemainingSeconds
2818
2924
  })
2819
2925
  );
2820
2926
  }
@@ -3017,8 +3123,11 @@ function createApp() {
3017
3123
  const toolManager = new ToolManager(() => {
3018
3124
  broadcastNotification(server, "notifications/tools/list_changed");
3019
3125
  });
3020
- const sessionManager = new SessionManager(config.connectionTimeoutMinutes);
3021
- console.log(`[\u4F1A\u8BDD] \u4EC5\u5185\u5B58\u6A21\u5F0F\uFF08\u8FDE\u63A5\u8D85\u65F6: ${config.connectionTimeoutMinutes} \u5206\u949F\uFF0C\u6D3B\u8DC3\u8D85\u65F6: 1 \u5C0F\u65F6\uFF09`);
3126
+ const sessionManager = new SessionManager(
3127
+ config.connectionTimeoutMinutes,
3128
+ config.reconnectionTimeoutMinutes
3129
+ );
3130
+ console.log(`[\u4F1A\u8BDD] \u4EC5\u5185\u5B58\u6A21\u5F0F\uFF08\u8FDE\u63A5\u8D85\u65F6: ${config.connectionTimeoutMinutes} \u5206\u949F\uFF0C\u91CD\u8FDE\u8D85\u65F6: ${config.reconnectionTimeoutMinutes} \u5206\u949F\uFF0C\u6D3B\u8DC3\u8D85\u65F6: 1 \u5C0F\u65F6\uFF09`);
3022
3131
  const wsServer = createWSServer(config, sessionManager);
3023
3132
  const waveformStorage2 = initWaveforms(config);
3024
3133
  registerProtocolAndTools(server, toolManager, sessionManager, wsServer, config);