@wu529778790/open-im 1.11.8-beta.2 → 1.11.8-beta.21

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.
Files changed (60) hide show
  1. package/dist/adapters/codebuddy-adapter.js +3 -17
  2. package/dist/adapters/codex-adapter.js +3 -16
  3. package/dist/adapters/opencode-adapter.d.ts +0 -5
  4. package/dist/adapters/opencode-adapter.js +10 -25
  5. package/dist/adapters/registry.js +10 -1
  6. package/dist/clawbot/client.js +72 -78
  7. package/dist/clawbot/qr-login.d.ts +1 -0
  8. package/dist/clawbot/qr-login.js +7 -0
  9. package/dist/codebuddy/cli-runner.d.ts +2 -22
  10. package/dist/codebuddy/cli-runner.js +9 -27
  11. package/dist/codex/cli-runner.d.ts +2 -22
  12. package/dist/codex/cli-runner.js +24 -75
  13. package/dist/commands/handler.d.ts +1 -0
  14. package/dist/commands/handler.js +34 -0
  15. package/dist/config/types.d.ts +23 -0
  16. package/dist/config-web-auth.d.ts +13 -0
  17. package/dist/config-web-auth.js +114 -0
  18. package/dist/config-web-browser.d.ts +4 -0
  19. package/dist/config-web-browser.js +48 -0
  20. package/dist/config-web-cors.d.ts +5 -0
  21. package/dist/config-web-cors.js +48 -0
  22. package/dist/config-web-health.d.ts +7 -0
  23. package/dist/config-web-health.js +65 -0
  24. package/dist/config-web-http.d.ts +3 -0
  25. package/dist/config-web-http.js +31 -0
  26. package/dist/config-web-page-i18n.d.ts +36 -2
  27. package/dist/config-web-page-i18n.js +36 -2
  28. package/dist/config-web-payload.d.ts +84 -0
  29. package/dist/config-web-payload.js +260 -0
  30. package/dist/config-web-probes.d.ts +2 -0
  31. package/dist/config-web-probes.js +271 -0
  32. package/dist/config-web.d.ts +3 -11
  33. package/dist/config-web.js +45 -815
  34. package/dist/config.js +22 -1
  35. package/dist/constants.d.ts +10 -0
  36. package/dist/constants.js +11 -0
  37. package/dist/opencode/cli-runner.d.ts +2 -22
  38. package/dist/opencode/cli-runner.js +47 -68
  39. package/dist/opencode/sdk-manager.d.ts +11 -0
  40. package/dist/opencode/sdk-manager.js +46 -0
  41. package/dist/opencode/sdk-runner.d.ts +7 -0
  42. package/dist/opencode/sdk-runner.js +225 -0
  43. package/dist/platform/handle-ai-request.js +18 -1
  44. package/dist/qq/client.js +45 -53
  45. package/dist/shared/ai-task.d.ts +15 -0
  46. package/dist/shared/ai-task.js +174 -2
  47. package/dist/shared/cli-runner-base.d.ts +39 -0
  48. package/dist/shared/cli-runner-base.js +108 -0
  49. package/dist/shared/connection-manager.d.ts +111 -0
  50. package/dist/shared/connection-manager.js +157 -0
  51. package/dist/shared/session-invalid-detector.d.ts +5 -0
  52. package/dist/shared/session-invalid-detector.js +20 -0
  53. package/dist/wework/client.js +79 -127
  54. package/dist/workbuddy/client.js +40 -74
  55. package/package.json +4 -1
  56. package/web/dist/assets/index-6e51Wtaa.css +1 -0
  57. package/web/dist/assets/index-Da9qKWA2.js +57 -0
  58. package/web/dist/index.html +2 -2
  59. package/web/dist/assets/index-XKVTb-0p.css +0 -1
  60. package/web/dist/assets/index-yDVGC_84.js +0 -57
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Shared connection management infrastructure for self-managing platform clients.
3
+ *
4
+ * Encapsulates three cross-cutting concerns duplicated across wework, clawbot,
5
+ * qq, and workbuddy clients:
6
+ *
7
+ * - **ReconnectManager**: jittered backoff scheduling with fatal→slow-probe mode
8
+ * - **HeartbeatMonitor**: interval-based heartbeat / watchdog timer
9
+ * - **StateManager**: state variable with change-notification callback
10
+ */
11
+ import { createLogger } from '../logger.js';
12
+ import { jitteredDelay, SLOW_PROBE_MS } from './reconnect.js';
13
+ /**
14
+ * Manages reconnection scheduling with jittered backoff and fatal→slow-probe.
15
+ *
16
+ * Replaces the duplicated `scheduleReconnect()` pattern across 4 client files:
17
+ * timer cleanup, attempt counting, backoff computation, jitter, logging.
18
+ */
19
+ export class ReconnectManager {
20
+ timer = null;
21
+ attempt = 0;
22
+ _fatal = false;
23
+ _stopped = false;
24
+ log;
25
+ cfg;
26
+ constructor(cfg) {
27
+ this.cfg = cfg;
28
+ this.log = createLogger(`${cfg.name}/Reconnect`);
29
+ }
30
+ get attemptCount() { return this.attempt; }
31
+ get fatal() { return this._fatal; }
32
+ get stopped() { return this._stopped; }
33
+ /** Mark a fatal (auth) error — next scheduleReconnect uses slow-probe delay. */
34
+ setFatal(value) { this._fatal = value; }
35
+ /** Reset attempt counter and fatal flag after a successful connection. */
36
+ reset() {
37
+ this.attempt = 0;
38
+ this._fatal = false;
39
+ }
40
+ /**
41
+ * Schedule a reconnect attempt. Clears any pending timer, computes the
42
+ * backoff delay (with jitter), and invokes `onReconnect` after the delay.
43
+ */
44
+ schedule() {
45
+ this.clearTimer();
46
+ if (this._stopped)
47
+ return;
48
+ const max = this.cfg.maxAttempts;
49
+ if (max && max > 0 && this.attempt >= max) {
50
+ this.log.warn(`Max reconnect attempts (${max}) reached, resetting counter`);
51
+ this.attempt = 0;
52
+ }
53
+ const baseDelay = this.computeBaseDelay();
54
+ const delay = this._fatal ? jitteredDelay(SLOW_PROBE_MS) : jitteredDelay(baseDelay);
55
+ this.attempt++;
56
+ const fatalTag = this._fatal ? ' [slow-probe]' : '';
57
+ this.log.info(`Reconnecting in ${delay}ms (attempt ${this.attempt})${fatalTag}...`);
58
+ this.timer = setTimeout(async () => {
59
+ this.timer = null;
60
+ try {
61
+ await this.cfg.onReconnect();
62
+ }
63
+ catch (err) {
64
+ this.log.warn(`Reconnect callback failed: ${err instanceof Error ? err.message : String(err)}`);
65
+ }
66
+ }, delay);
67
+ }
68
+ /** Cancel any pending reconnect timer. */
69
+ stop() {
70
+ this._stopped = true;
71
+ this.clearTimer();
72
+ }
73
+ /** Allow scheduling again (undo `stop`). */
74
+ resume() {
75
+ this._stopped = false;
76
+ }
77
+ clearTimer() {
78
+ if (this.timer) {
79
+ clearTimeout(this.timer);
80
+ this.timer = null;
81
+ }
82
+ }
83
+ computeBaseDelay() {
84
+ const { backoff } = this.cfg;
85
+ if (backoff.mode === 'exponential') {
86
+ const cap = backoff.cap ?? 5;
87
+ return Math.min(backoff.baseMs * Math.pow(1.5, Math.floor(this.attempt / cap)), backoff.maxMs);
88
+ }
89
+ const { delays } = backoff;
90
+ return delays[Math.min(this.attempt, delays.length - 1)];
91
+ }
92
+ }
93
+ // ── HeartbeatMonitor ─────────────────────────────────────────────
94
+ /**
95
+ * Manages a setInterval-based heartbeat or watchdog timer.
96
+ *
97
+ * Replaces duplicated `heartbeatTimer` / `watchdogTimer` patterns:
98
+ * timer cleanup on re-start, clearInterval on stop.
99
+ */
100
+ export class HeartbeatMonitor {
101
+ timer = null;
102
+ _lastResponseTime = 0;
103
+ get lastResponseTime() { return this._lastResponseTime; }
104
+ /** Record that a response was received (for watchdog stale detection). */
105
+ recordResponse() { this._lastResponseTime = Date.now(); }
106
+ /**
107
+ * Start a periodic callback. Clears any existing timer first.
108
+ * @param intervalMs Interval in milliseconds.
109
+ * @param onTick Callback invoked on each tick.
110
+ */
111
+ start(intervalMs, onTick) {
112
+ this.stop();
113
+ this.timer = setInterval(() => {
114
+ Promise.resolve(onTick()).catch(() => { });
115
+ }, intervalMs);
116
+ }
117
+ /** Clear the heartbeat timer. */
118
+ stop() {
119
+ if (this.timer) {
120
+ clearInterval(this.timer);
121
+ this.timer = null;
122
+ }
123
+ }
124
+ /**
125
+ * Watchdog convenience: returns true if more than `staleMs` have elapsed
126
+ * since the last `recordResponse()` call.
127
+ */
128
+ isStale(staleMs) {
129
+ return this._lastResponseTime > 0 && Date.now() - this._lastResponseTime > staleMs;
130
+ }
131
+ }
132
+ // ── StateManager ─────────────────────────────────────────────────
133
+ /**
134
+ * Manages a connection state variable with change-notification callback.
135
+ *
136
+ * Replaces duplicated `connectionState` / `channelState` + `stateChangeHandler`
137
+ * patterns across wework, clawbot, and workbuddy clients.
138
+ */
139
+ export class StateManager {
140
+ _state;
141
+ onChange;
142
+ constructor(initialState) {
143
+ this._state = initialState;
144
+ }
145
+ get current() { return this._state; }
146
+ /** Register a callback invoked whenever the state changes. */
147
+ setOnChange(handler) {
148
+ this.onChange = handler;
149
+ }
150
+ /** Transition to a new state and notify the callback if it changed. */
151
+ set(newState) {
152
+ if (this._state === newState)
153
+ return;
154
+ this._state = newState;
155
+ this.onChange?.(newState);
156
+ }
157
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Unified session-invalid detection — consolidates all session-expiry
3
+ * error patterns from codex, codebuddy, and opencode into one place.
4
+ */
5
+ export declare function isSessionInvalidMessage(msg: string): boolean;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Unified session-invalid detection — consolidates all session-expiry
3
+ * error patterns from codex, codebuddy, and opencode into one place.
4
+ */
5
+ const SESSION_INVALID_PATTERNS = [
6
+ 'No session found',
7
+ 'No conversation found',
8
+ 'Unable to find session',
9
+ 'Session not found',
10
+ 'Invalid session',
11
+ 'Unable to resume',
12
+ 'session not found',
13
+ 'no sessions found',
14
+ 'session expired',
15
+ 'session corrupt',
16
+ ];
17
+ export function isSessionInvalidMessage(msg) {
18
+ const lower = msg.toLowerCase();
19
+ return SESSION_INVALID_PATTERNS.some((p) => lower.includes(p.toLowerCase()));
20
+ }
@@ -10,26 +10,44 @@
10
10
  import { WebSocket } from 'ws';
11
11
  import { randomBytes } from 'node:crypto';
12
12
  import { createLogger } from '../logger.js';
13
- import { jitteredDelay, SLOW_PROBE_MS, isFatalReconnectError } from '../shared/reconnect.js';
13
+ import { isFatalReconnectError } from '../shared/reconnect.js';
14
+ import { ReconnectManager, HeartbeatMonitor, StateManager } from '../shared/connection-manager.js';
14
15
  const log = createLogger('WeWork');
15
16
  const DEFAULT_WS_URL = 'wss://openws.work.weixin.qq.com';
16
17
  const HEARTBEAT_INTERVAL = 30000; // 30秒
17
18
  const PONG_TIMEOUT = HEARTBEAT_INTERVAL * 3; // 90秒无任何服务端响应则判定连接死亡
18
- const MAX_RECONNECT_ATTEMPTS = 100;
19
- // Global state
19
+ // Connection infrastructure (shared managers replace raw timers/counters)
20
20
  let ws = null;
21
- let connectionState = 'disconnected';
22
- let reconnectTimer = null;
23
- let heartbeatTimer = null;
24
- let reconnectAttempts = 0;
21
+ const stateManager = new StateManager('disconnected');
22
+ const heartbeatMonitor = new HeartbeatMonitor();
25
23
  let shouldReconnect = false;
26
24
  let isStopping = false;
27
- let lastServerResponseTime = 0; // 上次收到服务端消息的时间
28
25
  // Event handlers
29
26
  let messageHandler = null;
30
- let stateChangeHandler = null;
31
27
  // Configuration
32
28
  let config = null;
29
+ // Reconnect manager: exponential backoff 5s → 7.5s → 11s → ... max 60s
30
+ const reconnectManager = new ReconnectManager({
31
+ name: 'WeWork',
32
+ backoff: { mode: 'exponential', baseMs: 5000, maxMs: 60000, cap: 5 },
33
+ maxAttempts: 100,
34
+ onReconnect: async () => {
35
+ log.info('Reconnecting...');
36
+ try {
37
+ await connectWebSocket();
38
+ reconnectManager.setFatal(false); // 连上后恢复正常退避
39
+ }
40
+ catch (err) {
41
+ if (isFatalReconnectError(err)) {
42
+ reconnectManager.setFatal(true);
43
+ log.warn('WeWork 致命错误(鉴权失败),转慢探测:', err);
44
+ }
45
+ else {
46
+ log.error('Reconnection failed:', err);
47
+ }
48
+ }
49
+ },
50
+ });
33
51
  /**
34
52
  * Generate unique request ID
35
53
  */
@@ -40,7 +58,7 @@ function generateReqId() {
40
58
  * Get current connection state
41
59
  */
42
60
  export function getConnectionState() {
43
- return connectionState;
61
+ return stateManager.current;
44
62
  }
45
63
  /**
46
64
  * 主动推送消息 (aibot_send_msg)
@@ -48,7 +66,7 @@ export function getConnectionState() {
48
66
  * 注意:需用户曾与机器人对话后,才能向该会话主动推送
49
67
  */
50
68
  export function sendProactiveMessage(chatId, content) {
51
- if (!ws || connectionState !== 'connected') {
69
+ if (!ws || stateManager.current !== 'connected') {
52
70
  throw new Error('Cannot send proactive message: WebSocket not connected');
53
71
  }
54
72
  if (!chatId) {
@@ -77,7 +95,7 @@ export function sendProactiveMessage(chatId, content) {
77
95
  * 长连接模式下必须用此方式回复,透传 req_id
78
96
  */
79
97
  export function sendWebSocketReply(reqId, body) {
80
- if (!ws || connectionState !== 'connected') {
98
+ if (!ws || stateManager.current !== 'connected') {
81
99
  throw new Error('Cannot send reply: WebSocket not connected');
82
100
  }
83
101
  if (!reqId) {
@@ -109,11 +127,13 @@ export async function initWeWork(cfg, eventHandler, onStateChange) {
109
127
  websocketUrl: cfg.weworkWsUrl || DEFAULT_WS_URL,
110
128
  };
111
129
  messageHandler = eventHandler;
112
- stateChangeHandler = onStateChange ?? null;
130
+ stateManager.setOnChange(onStateChange);
113
131
  isStopping = false;
114
132
  shouldReconnect = false;
133
+ reconnectManager.reset();
134
+ reconnectManager.resume();
115
135
  log.info('Initializing WeWork client');
116
- // 首次连接支持重试:单独启用企微时偶发 TLS 连接失败,加飞书后因初始化顺序有“预热”效果则稳定
136
+ // 首次连接支持重试:单独启用企微时偶发 TLS 连接失败,加飞书后因初始化顺序有"预热"效果则稳定
117
137
  const maxAttempts = 3;
118
138
  const retryDelayMs = 1500;
119
139
  let lastErr = null;
@@ -136,7 +156,7 @@ export async function initWeWork(cfg, eventHandler, onStateChange) {
136
156
  * Connect to WeWork WebSocket server
137
157
  */
138
158
  async function connectWebSocket() {
139
- if (connectionState === 'connecting') {
159
+ if (stateManager.current === 'connecting') {
140
160
  log.warn('WebSocket connection already in progress');
141
161
  return;
142
162
  }
@@ -154,16 +174,16 @@ async function connectWebSocket() {
154
174
  }
155
175
  ws = null;
156
176
  }
157
- updateState('connecting');
177
+ stateManager.set('connecting');
158
178
  const websocketUrl = config.websocketUrl || DEFAULT_WS_URL;
159
179
  return new Promise((resolve, reject) => {
160
180
  try {
161
181
  ws = new WebSocket(websocketUrl);
162
182
  ws.on('open', async () => {
163
183
  log.info('WeWork WebSocket connected');
164
- reconnectAttempts = 0;
165
- updateState('connected');
166
- startHeartbeat();
184
+ reconnectManager.reset();
185
+ stateManager.set('connected');
186
+ heartbeatMonitor.start(HEARTBEAT_INTERVAL, heartbeatTick);
167
187
  // 发送认证订阅消息,并等待服务端确认(否则 aibot_send_msg 会报 846609 not subscribed)
168
188
  try {
169
189
  await sendSubscribeAndWaitAck(resolve, reject);
@@ -176,7 +196,7 @@ async function connectWebSocket() {
176
196
  }
177
197
  });
178
198
  ws.on('message', async (data) => {
179
- lastServerResponseTime = Date.now();
199
+ heartbeatMonitor.recordResponse();
180
200
  try {
181
201
  const message = JSON.parse(data.toString());
182
202
  await handleMessage(message);
@@ -187,21 +207,21 @@ async function connectWebSocket() {
187
207
  });
188
208
  ws.on('error', (err) => {
189
209
  log.error('WeWork WebSocket error:', err);
190
- updateState('error');
210
+ stateManager.set('error');
191
211
  reject(err);
192
212
  });
193
213
  ws.on('close', () => {
194
214
  log.info('WeWork WebSocket closed');
195
- stopHeartbeat();
196
- updateState('disconnected');
215
+ heartbeatMonitor.stop();
216
+ stateManager.set('disconnected');
197
217
  if (!isStopping && shouldReconnect) {
198
- scheduleReconnect();
218
+ reconnectManager.schedule();
199
219
  }
200
220
  });
201
221
  }
202
222
  catch (err) {
203
223
  log.error('Error creating WebSocket connection:', err);
204
- updateState('error');
224
+ stateManager.set('error');
205
225
  reject(err);
206
226
  }
207
227
  });
@@ -279,7 +299,7 @@ async function handleMessage(message) {
279
299
  * Send message to WeWork
280
300
  */
281
301
  export function sendMessage(message) {
282
- if (!ws || connectionState !== 'connected') {
302
+ if (!ws || stateManager.current !== 'connected') {
283
303
  log.warn('Cannot send message: WebSocket not connected');
284
304
  return;
285
305
  }
@@ -319,108 +339,43 @@ export function sendStreamWithItems(reqId, streamId, content, finish, msgItem) {
319
339
  });
320
340
  }
321
341
  /**
322
- * Update connection state and notify listeners
323
- */
324
- function updateState(state) {
325
- connectionState = state;
326
- if (stateChangeHandler) {
327
- stateChangeHandler(state);
328
- }
329
- log.debug('Connection state:', state);
330
- }
331
- /**
332
- * Start heartbeat to keep connection alive
342
+ * Heartbeat tick: send ping and detect stale connections.
333
343
  * 同时检测服务端是否响应,超时无响应则主动断开触发重连
334
344
  */
335
- function startHeartbeat() {
336
- stopHeartbeat();
337
- lastServerResponseTime = Date.now();
338
- heartbeatTimer = setInterval(() => {
339
- if (connectionState === 'connected' && ws) {
340
- // 检测连接是否已死:长时间未收到任何服务端响应
341
- const elapsed = Date.now() - lastServerResponseTime;
342
- if (lastServerResponseTime > 0 && elapsed > PONG_TIMEOUT) {
343
- log.warn(`No server response for ${Math.round(elapsed / 1000)}s, forcing reconnect`);
344
- stopHeartbeat();
345
- try {
346
- ws.removeAllListeners();
347
- ws.close();
348
- }
349
- catch {
350
- /* ignore */
351
- }
352
- ws = null;
353
- updateState('disconnected');
354
- scheduleReconnect();
355
- return;
356
- }
357
- const pingMessage = {
358
- cmd: "ping" /* WeWorkCommand.PING */,
359
- headers: {
360
- req_id: generateReqId(),
361
- },
362
- body: {},
363
- };
364
- try {
365
- ws.send(JSON.stringify(pingMessage));
366
- log.debug('Sent ping');
367
- }
368
- catch (err) {
369
- log.error('Error sending ping:', err);
370
- }
345
+ function heartbeatTick() {
346
+ if (stateManager.current !== 'connected' || !ws)
347
+ return;
348
+ // 检测连接是否已死:长时间未收到任何服务端响应
349
+ if (heartbeatMonitor.isStale(PONG_TIMEOUT)) {
350
+ const elapsed = Date.now() - heartbeatMonitor.lastResponseTime;
351
+ log.warn(`No server response for ${Math.round(elapsed / 1000)}s, forcing reconnect`);
352
+ heartbeatMonitor.stop();
353
+ try {
354
+ ws.removeAllListeners();
355
+ ws.close();
371
356
  }
372
- }, HEARTBEAT_INTERVAL);
373
- }
374
- /**
375
- * Stop heartbeat
376
- */
377
- function stopHeartbeat() {
378
- if (heartbeatTimer) {
379
- clearInterval(heartbeatTimer);
380
- heartbeatTimer = null;
381
- }
382
- }
383
- /**
384
- * Schedule reconnection attempt
385
- * 超过 MAX_RECONNECT_ATTEMPTS 后自动重置计数器继续重试,避免永久断连
386
- */
387
- /** 致命(鉴权)错误后转慢探测,避免紧密 hammer 网关 */
388
- let fatalSlowProbe = false;
389
- function scheduleReconnect() {
390
- if (isStopping || !shouldReconnect) {
357
+ catch {
358
+ /* ignore */
359
+ }
360
+ ws = null;
361
+ stateManager.set('disconnected');
362
+ reconnectManager.schedule();
391
363
  return;
392
364
  }
393
- if (reconnectTimer) {
394
- return;
365
+ const pingMessage = {
366
+ cmd: "ping" /* WeWorkCommand.PING */,
367
+ headers: {
368
+ req_id: generateReqId(),
369
+ },
370
+ body: {},
371
+ };
372
+ try {
373
+ ws.send(JSON.stringify(pingMessage));
374
+ log.debug('Sent ping');
395
375
  }
396
- // 超过最大重试次数后重置计数器,降低频率继续重试
397
- if (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
398
- log.warn(`Max reconnect attempts (${MAX_RECONNECT_ATTEMPTS}) reached, resetting counter and retrying at lower frequency`);
399
- reconnectAttempts = 0;
376
+ catch (err) {
377
+ log.error('Error sending ping:', err);
400
378
  }
401
- // 逐步增加间隔,5s → 7.5s → 11s → ... 最大 60s;致命(鉴权)错误转慢探测
402
- const backoff = fatalSlowProbe
403
- ? SLOW_PROBE_MS
404
- : Math.min(5000 * Math.pow(1.5, Math.floor(reconnectAttempts / 5)), 60000);
405
- const interval = jitteredDelay(Math.round(backoff));
406
- reconnectTimer = setTimeout(async () => {
407
- reconnectTimer = null;
408
- reconnectAttempts++;
409
- log.info(`Reconnecting... Attempt ${reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS} (interval: ${interval}ms${fatalSlowProbe ? ', slow-probe' : ''})`);
410
- try {
411
- await connectWebSocket();
412
- fatalSlowProbe = false; // 连上后恢复正常退避
413
- }
414
- catch (err) {
415
- if (isFatalReconnectError(err)) {
416
- fatalSlowProbe = true;
417
- log.warn('WeWork 致命错误(鉴权失败),转慢探测:', err);
418
- }
419
- else {
420
- log.error('Reconnection failed:', err);
421
- }
422
- }
423
- }, interval);
424
379
  }
425
380
  /**
426
381
  * Stop WeWork client
@@ -428,15 +383,12 @@ function scheduleReconnect() {
428
383
  export function stopWeWork() {
429
384
  isStopping = true;
430
385
  shouldReconnect = false;
431
- stopHeartbeat();
432
- if (reconnectTimer) {
433
- clearTimeout(reconnectTimer);
434
- reconnectTimer = null;
435
- }
386
+ heartbeatMonitor.stop();
387
+ reconnectManager.stop();
436
388
  if (ws) {
437
389
  ws.close();
438
390
  ws = null;
439
391
  }
440
- updateState('disconnected');
392
+ stateManager.set('disconnected');
441
393
  log.info('WeWork client stopped');
442
394
  }