@xalia/agent 0.6.9 → 0.6.11

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 (199) hide show
  1. package/README.md +11 -0
  2. package/dist/agent/src/agent/agent.js +77 -18
  3. package/dist/agent/src/agent/agentUtils.js +3 -2
  4. package/dist/agent/src/agent/documentSummarizer.js +126 -0
  5. package/dist/agent/src/agent/dummyLLM.js +25 -22
  6. package/dist/agent/src/agent/imageGenLLM.js +22 -19
  7. package/dist/agent/src/agent/llm.js +1 -1
  8. package/dist/agent/src/agent/openAILLM.js +15 -12
  9. package/dist/agent/src/agent/openAILLMStreaming.js +68 -37
  10. package/dist/agent/src/agent/repeatLLM.js +16 -7
  11. package/dist/agent/src/agent/tokenCounter.js +390 -0
  12. package/dist/agent/src/agent/tokenCounter.test.js +206 -0
  13. package/dist/agent/src/agent/toolSettings.js +17 -0
  14. package/dist/agent/src/agent/tools/calculatorTool.js +45 -0
  15. package/dist/agent/src/agent/tools/contentExtractors/pdfToText.js +55 -0
  16. package/dist/agent/src/agent/tools/datetimeTool.js +38 -0
  17. package/dist/agent/src/agent/tools/fileManager/fileManagerTool.js +156 -0
  18. package/dist/agent/src/agent/tools/fileManager/index.js +31 -0
  19. package/dist/agent/src/agent/tools/fileManager/memoryFileManager.js +102 -0
  20. package/dist/agent/src/{chat/data → agent/tools/fileManager}/mimeTypes.js +3 -1
  21. package/dist/agent/src/agent/tools/fileManager/prompt.js +33 -0
  22. package/dist/agent/src/{chat/data/dbSessionFileModels.js → agent/tools/fileManager/types.js} +7 -0
  23. package/dist/agent/src/agent/tools/index.js +64 -0
  24. package/dist/agent/src/agent/tools/openUrlTool.js +57 -0
  25. package/dist/agent/src/agent/tools/renderTool.js +89 -0
  26. package/dist/agent/src/agent/tools/utils.js +61 -0
  27. package/dist/agent/src/{chat/utils/search.js → agent/tools/webSearch.js} +1 -2
  28. package/dist/agent/src/agent/tools/webSearchTool.js +40 -0
  29. package/dist/agent/src/chat/client/chatClient.js +28 -0
  30. package/dist/agent/src/chat/client/index.js +4 -1
  31. package/dist/agent/src/chat/client/sessionClient.js +28 -2
  32. package/dist/agent/src/chat/constants.js +8 -0
  33. package/dist/agent/src/chat/data/dbSessionFiles.js +11 -6
  34. package/dist/agent/src/chat/protocol/messages.js +5 -0
  35. package/dist/agent/src/chat/server/chatContextManager.js +45 -25
  36. package/dist/agent/src/chat/server/conversation.js +3 -0
  37. package/dist/agent/src/chat/server/imageGeneratorTools.js +20 -8
  38. package/dist/agent/src/chat/server/openAIRouterLLM.js +0 -3
  39. package/dist/agent/src/chat/server/openSession.js +218 -55
  40. package/dist/agent/src/chat/server/promptRefiner.js +86 -0
  41. package/dist/agent/src/chat/server/server.js +5 -1
  42. package/dist/agent/src/chat/server/sessionFileManager.js +22 -221
  43. package/dist/agent/src/chat/server/sessionRegistry.js +87 -0
  44. package/dist/agent/src/chat/server/titleGenerator.js +112 -0
  45. package/dist/agent/src/chat/server/titleGenerator.test.js +113 -0
  46. package/dist/agent/src/chat/server/tools.js +63 -287
  47. package/dist/agent/src/chat/utils/approvalManager.js +6 -3
  48. package/dist/agent/src/chat/utils/multiAsyncQueue.js +3 -0
  49. package/dist/agent/src/test/agent.test.js +16 -17
  50. package/dist/agent/src/test/chatContextManager.test.js +15 -3
  51. package/dist/agent/src/test/dbMcpServerConfigs.test.js +4 -4
  52. package/dist/agent/src/test/dbSessionFiles.test.js +17 -17
  53. package/dist/agent/src/test/testTools.js +6 -1
  54. package/dist/agent/src/test/tools.test.js +27 -9
  55. package/dist/agent/src/tool/agentChat.js +5 -2
  56. package/dist/agent/src/tool/chatMain.js +34 -7
  57. package/dist/agent/src/tool/commandPrompt.js +2 -2
  58. package/dist/agent/src/tool/files.js +7 -8
  59. package/package.json +8 -2
  60. package/.env.development +0 -1
  61. package/.prettierrc.json +0 -11
  62. package/dist/agent/src/agent/tools.js +0 -44
  63. package/eslint.config.mjs +0 -38
  64. package/scripts/chat_server +0 -8
  65. package/scripts/git_message +0 -31
  66. package/scripts/git_wip +0 -21
  67. package/scripts/pr_message +0 -18
  68. package/scripts/pr_review +0 -16
  69. package/scripts/setup_chat +0 -90
  70. package/scripts/shutdown_chat_server +0 -42
  71. package/scripts/start_chat_server +0 -24
  72. package/scripts/sudomcp_import +0 -23
  73. package/scripts/test_chat +0 -308
  74. package/src/agent/agent.ts +0 -624
  75. package/src/agent/agentUtils.ts +0 -285
  76. package/src/agent/compressingContextManager.ts +0 -129
  77. package/src/agent/context.ts +0 -265
  78. package/src/agent/contextWithWorkspace.ts +0 -162
  79. package/src/agent/dummyLLM.ts +0 -126
  80. package/src/agent/iAgentEventHandler.ts +0 -64
  81. package/src/agent/imageGenLLM.ts +0 -97
  82. package/src/agent/imageGenerator.ts +0 -45
  83. package/src/agent/iplatform.ts +0 -18
  84. package/src/agent/llm.ts +0 -74
  85. package/src/agent/mcpServerManager.ts +0 -541
  86. package/src/agent/nullAgentEventHandler.ts +0 -26
  87. package/src/agent/nullPlatform.ts +0 -13
  88. package/src/agent/openAI.ts +0 -123
  89. package/src/agent/openAILLM.ts +0 -95
  90. package/src/agent/openAILLMStreaming.ts +0 -609
  91. package/src/agent/promptProvider.ts +0 -87
  92. package/src/agent/repeatLLM.ts +0 -50
  93. package/src/agent/sudoMcpServerManager.ts +0 -361
  94. package/src/agent/tokenAuth.ts +0 -50
  95. package/src/agent/tools.ts +0 -57
  96. package/src/chat/client/chatClient.ts +0 -922
  97. package/src/chat/client/connection.test.ts +0 -241
  98. package/src/chat/client/connection.ts +0 -286
  99. package/src/chat/client/constants.ts +0 -1
  100. package/src/chat/client/index.ts +0 -18
  101. package/src/chat/client/interfaces.ts +0 -34
  102. package/src/chat/client/sessionClient.ts +0 -537
  103. package/src/chat/client/sessionFiles.ts +0 -142
  104. package/src/chat/client/teamManager.ts +0 -29
  105. package/src/chat/data/apiKeyManager.ts +0 -76
  106. package/src/chat/data/dataModels.ts +0 -101
  107. package/src/chat/data/database.ts +0 -997
  108. package/src/chat/data/dbMcpServerConfigs.ts +0 -59
  109. package/src/chat/data/dbSessionFileModels.ts +0 -113
  110. package/src/chat/data/dbSessionFiles.ts +0 -99
  111. package/src/chat/data/dbSessionMessages.ts +0 -102
  112. package/src/chat/data/mimeTypes.ts +0 -58
  113. package/src/chat/protocol/connectionMessages.ts +0 -49
  114. package/src/chat/protocol/constants.ts +0 -55
  115. package/src/chat/protocol/errors.ts +0 -16
  116. package/src/chat/protocol/messages.ts +0 -846
  117. package/src/chat/server/README.md +0 -127
  118. package/src/chat/server/chatContextManager.ts +0 -639
  119. package/src/chat/server/connectionManager.test.ts +0 -246
  120. package/src/chat/server/connectionManager.ts +0 -506
  121. package/src/chat/server/conversation.ts +0 -316
  122. package/src/chat/server/errorUtils.ts +0 -28
  123. package/src/chat/server/imageGeneratorTools.ts +0 -160
  124. package/src/chat/server/openAIRouterLLM.ts +0 -171
  125. package/src/chat/server/openSession.ts +0 -1689
  126. package/src/chat/server/openSessionMessageSender.ts +0 -4
  127. package/src/chat/server/server.ts +0 -175
  128. package/src/chat/server/sessionFileManager.ts +0 -422
  129. package/src/chat/server/sessionRegistry.test.ts +0 -137
  130. package/src/chat/server/sessionRegistry.ts +0 -1425
  131. package/src/chat/server/test-utils/mockFactories.ts +0 -422
  132. package/src/chat/server/tools.ts +0 -397
  133. package/src/chat/utils/agentSessionMap.ts +0 -76
  134. package/src/chat/utils/approvalManager.ts +0 -183
  135. package/src/chat/utils/asyncLock.ts +0 -43
  136. package/src/chat/utils/asyncQueue.ts +0 -62
  137. package/src/chat/utils/htmlToText.ts +0 -61
  138. package/src/chat/utils/multiAsyncQueue.ts +0 -62
  139. package/src/chat/utils/responseAwaiter.ts +0 -181
  140. package/src/chat/utils/search.ts +0 -139
  141. package/src/chat/utils/userResolver.ts +0 -48
  142. package/src/chat/utils/websocket.ts +0 -16
  143. package/src/index.ts +0 -0
  144. package/src/test/agent.test.ts +0 -590
  145. package/src/test/approvalManager.test.ts +0 -141
  146. package/src/test/chatContextManager.test.ts +0 -527
  147. package/src/test/clientServerConnection.test.ts +0 -205
  148. package/src/test/compressingContextManager.test.ts +0 -77
  149. package/src/test/context.test.ts +0 -150
  150. package/src/test/contextTestTools.ts +0 -95
  151. package/src/test/conversation.test.ts +0 -109
  152. package/src/test/db.test.ts +0 -363
  153. package/src/test/dbMcpServerConfigs.test.ts +0 -112
  154. package/src/test/dbSessionFiles.test.ts +0 -258
  155. package/src/test/dbSessionMessages.test.ts +0 -85
  156. package/src/test/dbTestTools.ts +0 -157
  157. package/src/test/imageLoad.test.ts +0 -15
  158. package/src/test/mcpServerManager.test.ts +0 -114
  159. package/src/test/multiAsyncQueue.test.ts +0 -183
  160. package/src/test/openaiStreaming.test.ts +0 -177
  161. package/src/test/prompt.test.ts +0 -27
  162. package/src/test/promptProvider.test.ts +0 -33
  163. package/src/test/responseAwaiter.test.ts +0 -103
  164. package/src/test/sudoMcpServerManager.test.ts +0 -63
  165. package/src/test/testTools.ts +0 -171
  166. package/src/test/tools.test.ts +0 -39
  167. package/src/tool/agentChat.ts +0 -194
  168. package/src/tool/agentMain.ts +0 -180
  169. package/src/tool/chatMain.ts +0 -594
  170. package/src/tool/commandPrompt.ts +0 -264
  171. package/src/tool/files.ts +0 -84
  172. package/src/tool/main.ts +0 -25
  173. package/src/tool/nodePlatform.ts +0 -73
  174. package/src/tool/options.ts +0 -144
  175. package/src/tool/prompt.ts +0 -101
  176. package/test_data/background_test_profile.json +0 -6
  177. package/test_data/background_test_script.json +0 -11
  178. package/test_data/dummyllm_script_crash.json +0 -32
  179. package/test_data/dummyllm_script_image_gen.json +0 -19
  180. package/test_data/dummyllm_script_image_gen_fe.json +0 -29
  181. package/test_data/dummyllm_script_invoke_image_gen_tool.json +0 -37
  182. package/test_data/dummyllm_script_render_tool.json +0 -29
  183. package/test_data/dummyllm_script_simplecalc.json +0 -28
  184. package/test_data/dummyllm_script_test_auto_approve.json +0 -81
  185. package/test_data/dummyllm_script_test_simplecalc_addition.json +0 -29
  186. package/test_data/frog.png +0 -0
  187. package/test_data/frog.png.b64 +0 -1
  188. package/test_data/git_message_profile.json +0 -4
  189. package/test_data/git_wip_system.txt +0 -5
  190. package/test_data/image_gen_test_profile.json +0 -5
  191. package/test_data/pr_message_profile.json +0 -4
  192. package/test_data/pr_review_profile.json +0 -4
  193. package/test_data/prompt_simplecalc.txt +0 -1
  194. package/test_data/simplecalc_profile.json +0 -4
  195. package/test_data/sudomcp_import_profile.json +0 -4
  196. package/test_data/test_script_profile.json +0 -8
  197. package/tsconfig.json +0 -13
  198. package/vitest.config.ts +0 -39
  199. /package/dist/agent/src/{chat/utils → agent/tools/contentExtractors}/htmlToText.js +0 -0
@@ -1,506 +0,0 @@
1
- import * as ws from "ws";
2
- import { v4 as uuidv4 } from "uuid";
3
- import { getLogger } from "@xalia/xmcp/sdk";
4
-
5
- import {
6
- ServerConnectionReady,
7
- ServerConnectionError,
8
- ServerConnectionPing,
9
- ClientConnectionMessage,
10
- ServerConnectionMessage,
11
- } from "../protocol/connectionMessages";
12
- import { ChatErrorMessage, ChatFatalError } from "../protocol/errors";
13
-
14
- const logger = getLogger();
15
-
16
- const HEARTBEAT_INTERVAL_MS: number = parseInt(
17
- process.env["HEARTBEAT_INTERVAL_MS"] || String(30 * 1000)
18
- );
19
-
20
- /**
21
- * Interface for sending messages to clients.
22
- */
23
- export interface IUserConnectionManager<ServerMsgT> {
24
- /**
25
- * Send message to all active connections of specific users.
26
- * Handles user-to-connection routing internally.
27
- */
28
- sendToUsers(userIds: Set<string> | string[], message: ServerMsgT): void;
29
-
30
- /**
31
- * Send message to a specific connection.
32
- */
33
- sendToConnection(connectionId: string, message: ServerMsgT): void;
34
- }
35
-
36
- /**
37
- * Interface for processing messages from the client. Also handles guest
38
- * admission.
39
- */
40
- export interface IMessageProcessor<ClientMsgT> {
41
- authenticate(token: string): Promise<string | undefined>;
42
-
43
- processMessage(
44
- connectionId: string,
45
- userId: string,
46
- message: ClientMsgT
47
- ): Promise<void>;
48
-
49
- /**
50
- * Handle user disconnect - clean up from all sessions.
51
- * Called when a connection is closed to ensure proper cleanup.
52
- */
53
- handleUserDisconnect(userId: string): Promise<void>;
54
- }
55
-
56
- class Connection {
57
- public ws: ws.WebSocket;
58
- public userId: string;
59
- public heartbeat: NodeJS.Timeout;
60
- public lastMessageTime: number;
61
-
62
- constructor(ws: ws.WebSocket, userId: string, heartbeat: NodeJS.Timeout) {
63
- this.ws = ws;
64
- this.userId = userId;
65
- this.heartbeat = heartbeat;
66
- this.lastMessageTime = Date.now();
67
- }
68
- }
69
-
70
- /**
71
- * Manages WebSocket connections and handles protocol-level message routing.
72
- *
73
- * - Connection handshake
74
- * - Connection lifecycle management
75
- * - Message routing between connections and sessions
76
- * - Connection-to-user
77
- */
78
- export class ConnectionManager<ClientMsgT, ServerMsgT>
79
- implements IUserConnectionManager<ServerMsgT>
80
- {
81
- // Connections by id
82
- private connections: Map<string, Connection> = new Map();
83
- // userId -> connectionIds
84
- private userToConnections: Map<string, Set<string>> = new Map();
85
-
86
- // this is a singleton object and can only be initialized by .init method
87
- private constructor(
88
- private sessionMessageProcessor: IMessageProcessor<ClientMsgT>
89
- ) {}
90
-
91
- static init<ClientMsgT, ServerMsgT>(
92
- createMsgProcessor: (
93
- cm: IUserConnectionManager<ServerMsgT>
94
- ) => IMessageProcessor<ClientMsgT>
95
- ): ConnectionManager<ClientMsgT, ServerMsgT> {
96
- const connManager = new ConnectionManager(
97
- {} as IMessageProcessor<ClientMsgT>
98
- );
99
- connManager.sessionMessageProcessor = createMsgProcessor(connManager);
100
- return connManager;
101
- }
102
-
103
- /**
104
- * Handle new WebSocket connection with handshake protocol
105
- * TODO: proper error code returning
106
- */
107
- async handleConnection(
108
- webSocket: ws.WebSocket,
109
- token: string,
110
- req?: { headers: { [key: string]: string | string[] | undefined } }
111
- ): Promise<void> {
112
- const connectionId = uuidv4();
113
- logger.info(`[ConnectionManager] New connection: ${connectionId}`);
114
-
115
- try {
116
- const userUUID = await this.sessionMessageProcessor.authenticate(token);
117
- if (!userUUID) {
118
- throw new ChatFatalError("invalid api key");
119
- }
120
-
121
- // Heartbeat interval
122
- const heartbeat = setInterval(
123
- this.onHeartbeat.bind(this, connectionId),
124
- HEARTBEAT_INTERVAL_MS
125
- );
126
- // Register connection
127
- this.connections.set(
128
- connectionId,
129
- new Connection(webSocket, userUUID, heartbeat)
130
- );
131
-
132
- // Add to user connections
133
- if (!this.userToConnections.has(userUUID)) {
134
- this.userToConnections.set(userUUID, new Set());
135
- }
136
- const userConnections = this.userToConnections.get(userUUID);
137
- if (userConnections) {
138
- userConnections.add(connectionId);
139
- }
140
-
141
- // Setup connection handlers
142
- this.setupConnectionHandlers(connectionId, webSocket);
143
-
144
- // Send connection_ready after WebSocket is fully open
145
- const clientVersion =
146
- (req?.headers["x-client-version"] as string) || "unknown";
147
-
148
- const response: ServerConnectionReady = {
149
- t: "ready",
150
- c_id: connectionId,
151
- user_uuid: userUUID,
152
- };
153
-
154
- // Check if connection still exists before sending
155
- if (this.connections.has(connectionId)) {
156
- this.sendConnectionReadyMessage(connectionId, response);
157
- } else {
158
- logger.warn(
159
- `[ConnectionManager] Connection ${connectionId} was closed` +
160
- ` before ready message could be sent`
161
- );
162
- }
163
-
164
- logger.info(
165
- `[ConnectionManager] Connection ${connectionId} registered ` +
166
- `for user ${userUUID}, version: ${clientVersion}`
167
- );
168
- } catch (error) {
169
- const errorMessage =
170
- error instanceof Error ? error.message : String(error);
171
- logger.error(
172
- `[ConnectionManager] Failed to setup connection ${connectionId}:`,
173
- errorMessage
174
- );
175
-
176
- // Send error to client if possible
177
- this.sendErrorDirect(webSocket, connectionId, errorMessage);
178
-
179
- // Force cleanup of any partial state
180
- this.forceCleanupConnection(connectionId);
181
-
182
- // Close WebSocket - this might trigger handlers if they were attached,
183
- // but we've already cleaned up state above as a safeguard
184
- try {
185
- webSocket.close(4000, "Connection setup failed");
186
- } catch (closeError) {
187
- logger.error(
188
- `[ConnectionManager] Error closing WebSocket ${connectionId}:`,
189
- closeError
190
- );
191
- }
192
- }
193
- }
194
-
195
- /**
196
- * Send connection_ready message with unified error handling
197
- */
198
- private sendConnectionReadyMessage(
199
- connectionId: string,
200
- response: ServerConnectionReady
201
- ): void {
202
- try {
203
- this.sendConnectionMessage(connectionId, response);
204
- } catch (error) {
205
- logger.error(
206
- `[ConnectionManager] Failed connection_ready to ${connectionId}:`,
207
- error
208
- );
209
- this.forceCleanupConnection(connectionId);
210
- }
211
- }
212
-
213
- /**
214
- * Send error message to connection directly.
215
- * This is used when the connection is possibly not yet registered.
216
- */
217
- private sendErrorDirect(
218
- ws: ws.WebSocket,
219
- connectionId: string,
220
- errorMessage: string
221
- ): void {
222
- const errorMsg: ServerConnectionError = {
223
- t: "error",
224
- e: errorMessage,
225
- };
226
- try {
227
- if (ws.readyState === ws.OPEN) {
228
- ws.send(JSON.stringify(errorMsg));
229
- } else {
230
- logger.error(
231
- `[ConnectionManager] Conn not open sending error ${connectionId}`
232
- );
233
- }
234
- } catch (error) {
235
- logger.error(
236
- `[ConnectionManager] Failed to send error to connection` +
237
- ` ${connectionId}:`,
238
- error
239
- );
240
- }
241
- }
242
-
243
- /**
244
- * Setup message and lifecycle handlers for a connection
245
- */
246
- private setupConnectionHandlers(
247
- connectionId: string,
248
- webSocket: ws.WebSocket
249
- ): void {
250
- // TODO: keep the Connection object in the closure here instead of always
251
- // doing many (expensive) lookups.
252
-
253
- // set message handler for incoming messages
254
- webSocket.on("message", (data: ws.RawData) => {
255
- void (async () => {
256
- try {
257
- const msgStr = (data as Buffer).toString("utf8");
258
- logger.info(
259
- `[ConnectionManager] Message from ${connectionId}: ${msgStr}`
260
- );
261
- const message = JSON.parse(
262
- msgStr
263
- ) as ClientConnectionMessage<ClientMsgT>;
264
- await this.routeMessage(connectionId, message);
265
- } catch (error) {
266
- logger.error(`[ConnectionManager] Error processing message:`, error);
267
- this.handleError(connectionId, error);
268
- }
269
- })();
270
- });
271
-
272
- webSocket.on("close", () => {
273
- logger.debug(`[ConnectionManager] Connection ${connectionId} closed`);
274
- this.handleConnectionClose(connectionId);
275
- });
276
-
277
- webSocket.on("error", (error) => {
278
- logger.error(
279
- `[ConnectionManager] WebSocket error on ${connectionId}:`,
280
- error
281
- );
282
- this.handleConnectionClose(connectionId);
283
- });
284
- }
285
-
286
- /**
287
- * Route incoming message to appropriate handler based on message type
288
- */
289
- private async routeMessage(
290
- connectionId: string,
291
- msg: ClientConnectionMessage<ClientMsgT>
292
- ): Promise<void> {
293
- const conn = this.connections.get(connectionId);
294
- if (!conn) {
295
- throw new ChatErrorMessage(
296
- `Connection ${connectionId} not found for message routing`
297
- );
298
- }
299
-
300
- conn.lastMessageTime = Date.now();
301
-
302
- // Handle Connection-level messages
303
- {
304
- switch (msg.t) {
305
- case "data":
306
- await this.sessionMessageProcessor.processMessage(
307
- connectionId,
308
- conn.userId,
309
- msg.d
310
- );
311
- break;
312
- case "pong":
313
- // The only operation is to update the `lastMessageTime`, done above
314
- logger.debug(
315
- `[ConnectionManager.routeMessage] pong from ${connectionId}`
316
- );
317
- break;
318
- default: {
319
- const _: never = msg;
320
- break;
321
- }
322
- }
323
- }
324
- }
325
-
326
- private onHeartbeat(connectionId: string): void {
327
- // TODO: move this and other handling to the Connection class so we can
328
- // avoid all the per-event lookups.
329
-
330
- const conn = this.connections.get(connectionId);
331
- if (!conn) {
332
- // TODO: We intentionally don't try to clean up here, to force
333
- // correctness fo the real cleanup code.
334
- logger.error(
335
- `[onHeartbeat] unexpected callback for connId ${connectionId}`
336
- );
337
- return;
338
- }
339
-
340
- const now = Date.now();
341
- const timeSinceLast = now - conn.lastMessageTime;
342
- logger.debug(
343
- `[onHeartbeat] conn ${connectionId} since ${String(timeSinceLast)}`
344
- );
345
- if (timeSinceLast > 2 * HEARTBEAT_INTERVAL_MS) {
346
- this.forceCleanupConnection(connectionId);
347
- return;
348
- }
349
-
350
- const msg: ServerConnectionPing = { t: "ping" };
351
- this.sendConnectionMessage(connectionId, msg);
352
- }
353
-
354
- /**
355
- * Force cleanup of connection state - used for error recovery.
356
- * This method is safe to call multiple times and handles partial state.
357
- */
358
- private forceCleanupConnection(connectionId: string): void {
359
- const conn = this.connections.get(connectionId);
360
- if (conn) {
361
- // Cancel the heartbeat interval
362
- clearInterval(conn.heartbeat);
363
-
364
- // Remove from user connections
365
- const userConnections = this.userToConnections.get(conn.userId);
366
- if (userConnections) {
367
- userConnections.delete(connectionId);
368
- if (userConnections.size === 0) {
369
- this.userToConnections.delete(conn.userId);
370
- const _ = this.sessionMessageProcessor.handleUserDisconnect(
371
- conn.userId
372
- );
373
- }
374
- }
375
-
376
- this.connections.delete(connectionId);
377
- }
378
-
379
- logger.debug(
380
- `[ConnectionManager] Force cleaned up connection ${connectionId}`
381
- );
382
- }
383
-
384
- /**
385
- * Handle connection close - cleanup all state.
386
- * This is the normal cleanup path triggered by WebSocket events.
387
- */
388
- private handleConnectionClose(connectionId: string): void {
389
- this.forceCleanupConnection(connectionId);
390
- logger.info(`[ConnectionManager] Connection ${connectionId} closed`);
391
- }
392
-
393
- private sendConnectionMessage(
394
- connectionId: string,
395
- message: ServerConnectionMessage<ServerMsgT>
396
- ): void {
397
- const conn = this.connections.get(connectionId);
398
- if (!conn) {
399
- logger.warn(
400
- `[ConnectionManager] Cannot send to connection ${connectionId} - ` +
401
- `not found`
402
- );
403
- return;
404
- }
405
-
406
- const webSocket = conn.ws;
407
- if (webSocket.readyState !== 1) {
408
- // WebSocket.OPEN = 1
409
- logger.warn(
410
- `[ConnectionManager] Cannot send to connection ${connectionId} - ` +
411
- `not open`
412
- );
413
- return;
414
- }
415
-
416
- const msgString = JSON.stringify(message);
417
- logger.debug(
418
- `[ConnectionManager] Sending to ${connectionId}: ${msgString}`
419
- );
420
- webSocket.send(msgString);
421
- }
422
-
423
- /**
424
- * Send message to specific connection.
425
- */
426
- public sendToConnection(connectionId: string, message: ServerMsgT): void {
427
- this.sendConnectionMessage(connectionId, { t: "data", d: message });
428
- }
429
-
430
- /**
431
- * Send error message to connection
432
- */
433
- private sendConnectionError(
434
- connectionId: string,
435
- errorMessage: string
436
- ): void {
437
- const errorMsg: ServerConnectionError = {
438
- t: "error",
439
- e: errorMessage,
440
- };
441
- this.sendConnectionMessage(connectionId, errorMsg);
442
- }
443
-
444
- /**
445
- * Handle errors during message processing
446
- */
447
- private handleError(connectionId: string, error: unknown): void {
448
- let message: string;
449
- let shouldClose = false;
450
-
451
- if (typeof error === "string") {
452
- message = error;
453
- } else if (error instanceof ChatFatalError) {
454
- message = error.message;
455
- shouldClose = true;
456
- } else if (error instanceof ChatErrorMessage) {
457
- message = error.message;
458
- } else if (error instanceof Error) {
459
- message = "Internal server error: " + error.message;
460
- } else {
461
- message = "Unknown error occurred";
462
- }
463
-
464
- logger.warn(
465
- `[ConnectionManager] Error on connection ${connectionId}: ${message}`
466
- );
467
- this.sendConnectionError(connectionId, message);
468
-
469
- if (shouldClose) {
470
- const conn = this.connections.get(connectionId);
471
- logger.info(
472
- `[ConnectionManager] Closing connection ${connectionId} ` +
473
- `due to error: ${message}`
474
- );
475
- if (conn) {
476
- conn.ws.close(4000, message);
477
- }
478
- }
479
- }
480
-
481
- /**
482
- * Implementation of IUserConnectionManager interface.
483
- * Send message to all active connections of specific users.
484
- */
485
- public sendToUsers(userIds: Set<string>, message: ServerMsgT): void {
486
- for (const userId of userIds.values()) {
487
- const connectionIds = this.getUserConnections(userId);
488
- for (const connectionId of connectionIds) {
489
- logger.debug(
490
- `[ConnectionManager] sending to user ${userId} ` +
491
- `connection ${connectionId}`
492
- );
493
- this.sendToConnection(connectionId, message);
494
- }
495
- }
496
- }
497
-
498
- /**
499
- * Get all active connection IDs for a user.
500
- * Helper method for sendToUsers implementation.
501
- */
502
- private getUserConnections(userId: string): string[] {
503
- const connections = this.userToConnections.get(userId);
504
- return connections ? Array.from(connections) : [];
505
- }
506
- }