@xalia/agent 0.5.7 → 0.6.0

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 (186) hide show
  1. package/README.md +23 -8
  2. package/dist/agent/src/agent/agent.js +176 -96
  3. package/dist/agent/src/agent/agentUtils.js +82 -59
  4. package/dist/agent/src/agent/compressingContextManager.js +102 -0
  5. package/dist/agent/src/agent/context.js +189 -0
  6. package/dist/agent/src/agent/dummyLLM.js +46 -5
  7. package/dist/agent/src/agent/mcpServerManager.js +23 -24
  8. package/dist/agent/src/agent/nullAgentEventHandler.js +21 -0
  9. package/dist/agent/src/agent/nullPlatform.js +14 -0
  10. package/dist/agent/src/agent/openAILLMStreaming.js +26 -14
  11. package/dist/agent/src/agent/promptProvider.js +63 -0
  12. package/dist/agent/src/agent/repeatLLM.js +5 -5
  13. package/dist/agent/src/agent/sudoMcpServerManager.js +23 -21
  14. package/dist/agent/src/agent/tokenAuth.js +7 -7
  15. package/dist/agent/src/agent/tools.js +1 -1
  16. package/dist/agent/src/chat/client/chatClient.js +733 -0
  17. package/dist/agent/src/chat/client/connection.js +209 -0
  18. package/dist/agent/src/chat/client/connection.test.js +188 -0
  19. package/dist/agent/src/chat/client/constants.js +5 -0
  20. package/dist/agent/src/chat/client/index.js +15 -0
  21. package/dist/agent/src/chat/client/interfaces.js +2 -0
  22. package/dist/agent/src/chat/client/responseHandler.js +105 -0
  23. package/dist/agent/src/chat/client/sessionClient.js +331 -0
  24. package/dist/agent/src/chat/client/teamManager.js +2 -0
  25. package/dist/agent/src/chat/{apiKeyManager.js → data/apiKeyManager.js} +4 -0
  26. package/dist/agent/src/chat/data/dataModels.js +2 -0
  27. package/dist/agent/src/chat/data/database.js +749 -0
  28. package/dist/agent/src/chat/data/dbMcpServerConfigs.js +47 -0
  29. package/dist/agent/src/chat/protocol/connectionMessages.js +5 -0
  30. package/dist/agent/src/chat/protocol/constants.js +50 -0
  31. package/dist/agent/src/chat/protocol/errors.js +22 -0
  32. package/dist/agent/src/chat/protocol/messages.js +110 -0
  33. package/dist/agent/src/chat/server/chatContextManager.js +405 -0
  34. package/dist/agent/src/chat/server/connectionManager.js +352 -0
  35. package/dist/agent/src/chat/server/connectionManager.test.js +159 -0
  36. package/dist/agent/src/chat/server/conversation.js +198 -0
  37. package/dist/agent/src/chat/server/errorUtils.js +23 -0
  38. package/dist/agent/src/chat/server/openSession.js +869 -0
  39. package/dist/agent/src/chat/server/server.js +177 -0
  40. package/dist/agent/src/chat/server/sessionFileManager.js +161 -0
  41. package/dist/agent/src/chat/server/sessionRegistry.js +700 -0
  42. package/dist/agent/src/chat/server/sessionRegistry.test.js +97 -0
  43. package/dist/agent/src/chat/server/test-utils/mockFactories.js +307 -0
  44. package/dist/agent/src/chat/server/tools.js +243 -0
  45. package/dist/agent/src/chat/utils/agentSessionMap.js +66 -0
  46. package/dist/agent/src/chat/utils/approvalManager.js +85 -0
  47. package/dist/agent/src/{utils → chat/utils}/asyncLock.js +3 -3
  48. package/dist/agent/src/chat/{asyncQueue.js → utils/asyncQueue.js} +12 -2
  49. package/dist/agent/src/chat/utils/htmlToText.js +84 -0
  50. package/dist/agent/src/chat/utils/multiAsyncQueue.js +42 -0
  51. package/dist/agent/src/chat/utils/search.js +145 -0
  52. package/dist/agent/src/chat/utils/userResolver.js +46 -0
  53. package/dist/agent/src/chat/utils/websocket.js +16 -0
  54. package/dist/agent/src/test/agent.test.js +332 -0
  55. package/dist/agent/src/test/approvalManager.test.js +58 -0
  56. package/dist/agent/src/test/chatContextManager.test.js +392 -0
  57. package/dist/agent/src/test/clientServerConnection.test.js +158 -0
  58. package/dist/agent/src/test/compressingContextManager.test.js +65 -0
  59. package/dist/agent/src/test/context.test.js +83 -0
  60. package/dist/agent/src/test/conversation.test.js +89 -0
  61. package/dist/agent/src/test/db.test.js +271 -83
  62. package/dist/agent/src/test/dbMcpServerConfigs.test.js +72 -0
  63. package/dist/agent/src/test/dbTestTools.js +99 -0
  64. package/dist/agent/src/test/imageLoad.test.js +8 -7
  65. package/dist/agent/src/test/mcpServerManager.test.js +23 -20
  66. package/dist/agent/src/test/multiAsyncQueue.test.js +101 -0
  67. package/dist/agent/src/test/openaiStreaming.test.js +64 -35
  68. package/dist/agent/src/test/prompt.test.js +5 -4
  69. package/dist/agent/src/test/promptProvider.test.js +28 -0
  70. package/dist/agent/src/test/responseHandler.test.js +61 -0
  71. package/dist/agent/src/test/sudoMcpServerManager.test.js +24 -25
  72. package/dist/agent/src/test/testTools.js +109 -0
  73. package/dist/agent/src/test/tools.test.js +31 -0
  74. package/dist/agent/src/tool/agentChat.js +21 -10
  75. package/dist/agent/src/tool/agentMain.js +1 -1
  76. package/dist/agent/src/tool/chatMain.js +241 -58
  77. package/dist/agent/src/tool/commandPrompt.js +22 -17
  78. package/dist/agent/src/tool/files.js +20 -16
  79. package/dist/agent/src/tool/nodePlatform.js +47 -3
  80. package/dist/agent/src/tool/options.js +4 -4
  81. package/dist/agent/src/tool/prompt.js +19 -13
  82. package/eslint.config.mjs +14 -1
  83. package/package.json +14 -6
  84. package/scripts/chat_server +8 -0
  85. package/scripts/setup_chat +7 -2
  86. package/scripts/shutdown_chat_server +3 -0
  87. package/scripts/test_chat +135 -17
  88. package/src/agent/agent.ts +283 -138
  89. package/src/agent/agentUtils.ts +143 -108
  90. package/src/agent/compressingContextManager.ts +164 -0
  91. package/src/agent/context.ts +268 -0
  92. package/src/agent/dummyLLM.ts +76 -8
  93. package/src/agent/iAgentEventHandler.ts +54 -0
  94. package/src/agent/iplatform.ts +1 -0
  95. package/src/agent/mcpServerManager.ts +35 -31
  96. package/src/agent/nullAgentEventHandler.ts +20 -0
  97. package/src/agent/nullPlatform.ts +13 -0
  98. package/src/agent/openAILLMStreaming.ts +26 -13
  99. package/src/agent/promptProvider.ts +87 -0
  100. package/src/agent/repeatLLM.ts +5 -5
  101. package/src/agent/sudoMcpServerManager.ts +30 -29
  102. package/src/agent/tokenAuth.ts +7 -7
  103. package/src/agent/tools.ts +3 -1
  104. package/src/chat/client/chatClient.ts +900 -0
  105. package/src/chat/client/connection.test.ts +241 -0
  106. package/src/chat/client/connection.ts +276 -0
  107. package/src/chat/client/constants.ts +3 -0
  108. package/src/chat/client/index.ts +18 -0
  109. package/src/chat/client/interfaces.ts +34 -0
  110. package/src/chat/client/responseHandler.ts +131 -0
  111. package/src/chat/client/sessionClient.ts +443 -0
  112. package/src/chat/client/teamManager.ts +29 -0
  113. package/src/chat/{apiKeyManager.ts → data/apiKeyManager.ts} +6 -2
  114. package/src/chat/data/dataModels.ts +85 -0
  115. package/src/chat/data/database.ts +982 -0
  116. package/src/chat/data/dbMcpServerConfigs.ts +59 -0
  117. package/src/chat/protocol/connectionMessages.ts +49 -0
  118. package/src/chat/protocol/constants.ts +55 -0
  119. package/src/chat/protocol/errors.ts +16 -0
  120. package/src/chat/protocol/messages.ts +682 -0
  121. package/src/chat/server/README.md +127 -0
  122. package/src/chat/server/chatContextManager.ts +612 -0
  123. package/src/chat/server/connectionManager.test.ts +266 -0
  124. package/src/chat/server/connectionManager.ts +541 -0
  125. package/src/chat/server/conversation.ts +269 -0
  126. package/src/chat/server/errorUtils.ts +28 -0
  127. package/src/chat/server/openSession.ts +1332 -0
  128. package/src/chat/server/server.ts +177 -0
  129. package/src/chat/server/sessionFileManager.ts +239 -0
  130. package/src/chat/server/sessionRegistry.test.ts +138 -0
  131. package/src/chat/server/sessionRegistry.ts +1064 -0
  132. package/src/chat/server/test-utils/mockFactories.ts +422 -0
  133. package/src/chat/server/tools.ts +265 -0
  134. package/src/chat/utils/agentSessionMap.ts +76 -0
  135. package/src/chat/utils/approvalManager.ts +111 -0
  136. package/src/{utils → chat/utils}/asyncLock.ts +3 -3
  137. package/src/chat/{asyncQueue.ts → utils/asyncQueue.ts} +14 -3
  138. package/src/chat/utils/htmlToText.ts +61 -0
  139. package/src/chat/utils/multiAsyncQueue.ts +52 -0
  140. package/src/chat/utils/search.ts +139 -0
  141. package/src/chat/utils/userResolver.ts +48 -0
  142. package/src/chat/utils/websocket.ts +16 -0
  143. package/src/test/agent.test.ts +487 -0
  144. package/src/test/approvalManager.test.ts +73 -0
  145. package/src/test/chatContextManager.test.ts +521 -0
  146. package/src/test/clientServerConnection.test.ts +207 -0
  147. package/src/test/compressingContextManager.test.ts +82 -0
  148. package/src/test/context.test.ts +105 -0
  149. package/src/test/conversation.test.ts +109 -0
  150. package/src/test/db.test.ts +358 -89
  151. package/src/test/dbMcpServerConfigs.test.ts +112 -0
  152. package/src/test/dbTestTools.ts +153 -0
  153. package/src/test/imageLoad.test.ts +7 -6
  154. package/src/test/mcpServerManager.test.ts +21 -16
  155. package/src/test/multiAsyncQueue.test.ts +125 -0
  156. package/src/test/openaiStreaming.test.ts +71 -36
  157. package/src/test/prompt.test.ts +4 -3
  158. package/src/test/promptProvider.test.ts +33 -0
  159. package/src/test/responseHandler.test.ts +78 -0
  160. package/src/test/sudoMcpServerManager.test.ts +32 -30
  161. package/src/test/testTools.ts +146 -0
  162. package/src/test/tools.test.ts +39 -0
  163. package/src/tool/agentChat.ts +26 -12
  164. package/src/tool/agentMain.ts +1 -1
  165. package/src/tool/chatMain.ts +292 -100
  166. package/src/tool/commandPrompt.ts +28 -19
  167. package/src/tool/files.ts +25 -19
  168. package/src/tool/nodePlatform.ts +52 -3
  169. package/src/tool/options.ts +4 -2
  170. package/src/tool/prompt.ts +22 -15
  171. package/test_data/dummyllm_script_crash.json +32 -0
  172. package/test_data/frog.png.b64 +1 -0
  173. package/vitest.config.ts +39 -0
  174. package/dist/agent/src/chat/client.js +0 -349
  175. package/dist/agent/src/chat/conversationManager.js +0 -392
  176. package/dist/agent/src/chat/db.js +0 -209
  177. package/dist/agent/src/chat/frontendClient.js +0 -74
  178. package/dist/agent/src/chat/server.js +0 -158
  179. package/src/chat/client.ts +0 -455
  180. package/src/chat/conversationManager.ts +0 -595
  181. package/src/chat/db.ts +0 -290
  182. package/src/chat/frontendClient.ts +0 -123
  183. package/src/chat/messages.ts +0 -235
  184. package/src/chat/server.ts +0 -177
  185. /package/dist/agent/src/{chat/messages.js → agent/iAgentEventHandler.js} +0 -0
  186. /package/{frog.png → test_data/frog.png} +0 -0
@@ -0,0 +1,733 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ChatClient = void 0;
4
+ const uuid_1 = require("uuid");
5
+ const sdk_1 = require("@xalia/xmcp/sdk");
6
+ const connection_1 = require("./connection");
7
+ const sessionClient_1 = require("./sessionClient");
8
+ const database_1 = require("../data/database");
9
+ const messages_1 = require("../protocol/messages");
10
+ const constants_1 = require("./constants");
11
+ const agentSessionMap_1 = require("../utils/agentSessionMap");
12
+ const logger = (0, sdk_1.getLogger)();
13
+ class ChatClient {
14
+ constructor(connection, eventHandler,
15
+ // agent_uuid -> agent session data
16
+ userAgentSessionMap = new Map(),
17
+ // team_uuid -> team info
18
+ teams = new Map(), closed = false, currentSessionId = undefined,
19
+ // note: currentTeamId will not be reset when swtiching to a user session.
20
+ currentTeamId = undefined,
21
+ // session_uuid -> session client
22
+ activeSessions = new Map(),
23
+ // session_uuid -> true if there is a new message.
24
+ newMessage = new Map(),
25
+ // Track pending session join request, this should be a singleton
26
+ sessionJoinOrCreateRes = undefined) {
27
+ this.connection = connection;
28
+ this.eventHandler = eventHandler;
29
+ this.userAgentSessionMap = userAgentSessionMap;
30
+ this.teams = teams;
31
+ this.closed = closed;
32
+ this.currentSessionId = currentSessionId;
33
+ this.currentTeamId = currentTeamId;
34
+ this.activeSessions = activeSessions;
35
+ this.newMessage = newMessage;
36
+ this.sessionJoinOrCreateRes = sessionJoinOrCreateRes;
37
+ }
38
+ /**
39
+ * Connect to server and create ChatClient instance
40
+ */
41
+ static async init(url, token, eventHandler) {
42
+ const connection = new connection_1.Connection({
43
+ url,
44
+ token,
45
+ });
46
+ return new Promise((resolveClient, reject) => {
47
+ let client = undefined;
48
+ const clientMessageId = (0, uuid_1.v4)();
49
+ // Register session_info handler for initialization
50
+ connection.on("control_session_list", (msg) => {
51
+ // get user sessions, user agents, and team sessions
52
+ const userSessions = new Map();
53
+ msg.user_sessions.forEach((session) => {
54
+ userSessions.set(session.session_uuid, session);
55
+ });
56
+ const userAgents = new Map();
57
+ msg.user_agents.forEach((agent) => {
58
+ userAgents.set(agent.uuid, agent);
59
+ });
60
+ const userAgentSessionMap = (0, agentSessionMap_1.buildAgentSessionMap)(userSessions, userAgents);
61
+ logger.info(`[ChatClient.init] session list msg: ${JSON.stringify(msg)}`);
62
+ for (const agentSession of userAgentSessionMap.values()) {
63
+ logger.info(`[ChatClient.init] agent: ` +
64
+ agentSession.agent_profile.profile_name);
65
+ }
66
+ const teams = new Map();
67
+ msg.team_sessions.forEach((team) => {
68
+ teams.set(team.team_uuid, ChatClient.toClientTeamInfo(team));
69
+ });
70
+ if (!client) {
71
+ logger.info("Creating ChatClient");
72
+ client = new ChatClient(connection, eventHandler, userAgentSessionMap, teams);
73
+ resolveClient(client);
74
+ }
75
+ else {
76
+ client.userAgentSessionMap = userAgentSessionMap;
77
+ client.teams = teams;
78
+ void client.eventHandler.onMessage(msg, client);
79
+ }
80
+ });
81
+ // Register general message handler for established client
82
+ connection.on("*", (msg) => {
83
+ if (!client) {
84
+ if (msg.type === "control_error") {
85
+ logger.error(`[ChatClient] client error (init): ${JSON.stringify(msg)}`);
86
+ reject(new Error(msg.message));
87
+ }
88
+ else {
89
+ logger.error(`[ChatClient] invalid message (init): ${JSON.stringify(msg.type)}`);
90
+ }
91
+ return;
92
+ }
93
+ if ((0, messages_1.isServerControlMessage)(msg)) {
94
+ client.handleControlMessage(msg);
95
+ }
96
+ else if ((0, messages_1.isServerSessionScopedMessage)(msg)) {
97
+ client.handleSessionMessage(msg);
98
+ }
99
+ else {
100
+ logger.error(`[ChatClient] unhandled msg: ${JSON.stringify(msg)}`);
101
+ }
102
+ });
103
+ // Register direct error callback for client-side errors.
104
+ connection.onError((errorMessage) => {
105
+ if (client) {
106
+ client.connection.close();
107
+ }
108
+ eventHandler.onError(`Connection error: ${errorMessage}`);
109
+ });
110
+ // Start connection
111
+ connection
112
+ .connect()
113
+ .then(() => {
114
+ connection.send({
115
+ type: "control_get_session_list",
116
+ client_message_id: clientMessageId,
117
+ });
118
+ })
119
+ .catch((error) => {
120
+ reject(error instanceof Error ? error : new Error(String(error)));
121
+ });
122
+ });
123
+ }
124
+ static toClientTeamInfo(teamInfo) {
125
+ const sessions = new Map(teamInfo.sessions.map((session) => [session.session_uuid, session]));
126
+ const agents = new Map(teamInfo.agents.map((agent) => [agent.uuid, agent]));
127
+ const agentSessionMap = (0, agentSessionMap_1.buildAgentSessionMap)(sessions, agents);
128
+ return {
129
+ team_uuid: teamInfo.team_uuid,
130
+ team_name: teamInfo.team_name,
131
+ owner_uuid: teamInfo.owner_uuid,
132
+ participants: new Map(teamInfo.participants.map((participant) => [
133
+ participant.user_uuid,
134
+ participant,
135
+ ])),
136
+ agentSessionMap,
137
+ };
138
+ }
139
+ /**
140
+ * Get the current session.
141
+ * Will throw an error if the current session is not set.
142
+ * @param location - The location of the caller.
143
+ * @returns The current session client.
144
+ */
145
+ getCurrentSession(location) {
146
+ const session = this.getCurrentSessionMaybe();
147
+ if (!session) {
148
+ throw new Error(`[${location}]: No current session`);
149
+ }
150
+ return session;
151
+ }
152
+ getCurrentSessionMaybe() {
153
+ if (!this.currentSessionId) {
154
+ return undefined;
155
+ }
156
+ return this.activeSessions.get(this.currentSessionId);
157
+ }
158
+ getCurrentAgentUuid() {
159
+ const session = this.getCurrentSessionMaybe();
160
+ if (!session) {
161
+ return undefined;
162
+ }
163
+ return session.getAgentUuid();
164
+ }
165
+ getUserAgentSessionMap() {
166
+ return this.userAgentSessionMap;
167
+ }
168
+ getClientTeamInfo(teamUuid) {
169
+ return this.teams.get(teamUuid);
170
+ }
171
+ // ITeamManager -> getCurrentTeamInfo
172
+ getCurrentTeamInfo() {
173
+ if (!this.currentTeamId) {
174
+ return undefined;
175
+ }
176
+ return this.teams.get(this.currentTeamId);
177
+ }
178
+ // ITeamManager -> getTeams
179
+ getTeams() {
180
+ return this.teams;
181
+ }
182
+ /**
183
+ * Get the currently active session ID
184
+ */
185
+ getCurrentSessionId() {
186
+ return this.currentSessionId;
187
+ }
188
+ /**
189
+ * Get the currently team ID
190
+ */
191
+ getCurrentTeamId() {
192
+ return this.currentTeamId;
193
+ }
194
+ // ITeamManager -> setCurrentTeamId
195
+ setCurrentTeamId(teamUuid) {
196
+ if (!this.teams.has(teamUuid)) {
197
+ throw new Error(`Team ${teamUuid} not found`);
198
+ }
199
+ this.currentTeamId = teamUuid;
200
+ }
201
+ /**
202
+ * Check if client is connected and operational
203
+ */
204
+ isClosed() {
205
+ return this.closed;
206
+ }
207
+ shutdown() {
208
+ this.connection.close();
209
+ this.closed = true;
210
+ this.activeSessions.clear();
211
+ this.newMessage.clear();
212
+ this.teams.clear();
213
+ this.currentSessionId = undefined;
214
+ this.currentTeamId = undefined;
215
+ this.sessionJoinOrCreateRes = undefined;
216
+ }
217
+ async createNewSession(newTitle, agentProfileId, teamId) {
218
+ if (this.closed) {
219
+ throw new Error("ChatClient is closed");
220
+ }
221
+ if (this.sessionJoinOrCreateRes) {
222
+ logger.error(`[ChatClient] creating new session with ` +
223
+ `profile id ${JSON.stringify(agentProfileId)}`);
224
+ throw new Error("Session join/create is already in progress");
225
+ }
226
+ return new Promise((resolve, reject) => {
227
+ const clientMessageId = (0, uuid_1.v4)();
228
+ // Set up timeout for the request
229
+ const timeoutId = setTimeout(() => {
230
+ this.sessionJoinOrCreateRes = undefined;
231
+ reject(new Error(`Session join timeout for creating new session`));
232
+ }, constants_1.SESSION_CREATE_TIMEOUT);
233
+ // Track this pending request
234
+ this.sessionJoinOrCreateRes = {
235
+ resolve: (sessionClient, sessionInfo) => {
236
+ const sessionId = this.sessionJoinOrCreateRes?.sessionId;
237
+ if (!sessionId) {
238
+ throw new Error("Session id is not set in resolving.");
239
+ }
240
+ if (sessionId !== sessionInfo.session_id) {
241
+ throw new Error("SessionInfo id mismatch");
242
+ }
243
+ clearTimeout(timeoutId);
244
+ this.activeSessions.set(sessionId, sessionClient);
245
+ this.currentSessionId = sessionId;
246
+ if (sessionInfo.team_uuid) {
247
+ this.currentTeamId = sessionInfo.team_uuid;
248
+ }
249
+ this.sessionJoinOrCreateRes = undefined;
250
+ this.addSessionToAgentSessionMap(sessionInfo);
251
+ resolve(sessionClient);
252
+ },
253
+ reject: (error) => {
254
+ clearTimeout(timeoutId);
255
+ this.sessionJoinOrCreateRes = undefined;
256
+ reject(error);
257
+ },
258
+ sessionId: "",
259
+ clientMessageId,
260
+ };
261
+ // Send session_create message
262
+ try {
263
+ this.connection.send({
264
+ type: "control_session_create",
265
+ client_message_id: clientMessageId,
266
+ title: newTitle,
267
+ agent_profile_id: agentProfileId,
268
+ team_id: teamId,
269
+ });
270
+ logger.info(`[ChatClient] Sent session_create for session ${newTitle}` +
271
+ ` client message id: ${clientMessageId}`);
272
+ }
273
+ catch (error) {
274
+ this.sessionJoinOrCreateRes = undefined;
275
+ clearTimeout(timeoutId);
276
+ reject(error instanceof Error ? error : new Error(String(error)));
277
+ }
278
+ });
279
+ }
280
+ /**
281
+ * Connect to an existing session by sending session_join message
282
+ * and waiting for session_info response from server
283
+ */
284
+ async connectToSession(sessionId) {
285
+ if (this.closed) {
286
+ throw new Error("ChatClient is closed");
287
+ }
288
+ if (this.sessionJoinOrCreateRes) {
289
+ logger.error(`[ChatClient] connecting to session ${sessionId}`);
290
+ throw new Error("Session join/create is already in progress");
291
+ }
292
+ // TODO: we can directly return a session client if we cache conversation.
293
+ // For now, we go through the join process.
294
+ return new Promise((resolve, reject) => {
295
+ const clientMessageId = (0, uuid_1.v4)();
296
+ // Set up timeout for the request
297
+ const timeoutId = setTimeout(() => {
298
+ this.sessionJoinOrCreateRes = undefined;
299
+ reject(new Error(`Session join timeout for session ${sessionId}`));
300
+ }, constants_1.SESSION_JOIN_TIMEOUT);
301
+ // Track this pending request
302
+ this.sessionJoinOrCreateRes = {
303
+ resolve: (sessionClient, sessionInfo) => {
304
+ clearTimeout(timeoutId);
305
+ this.activeSessions.set(sessionId, sessionClient);
306
+ this.currentSessionId = sessionId;
307
+ if (sessionInfo.team_uuid) {
308
+ this.currentTeamId = sessionInfo.team_uuid;
309
+ }
310
+ this.sessionJoinOrCreateRes = undefined;
311
+ this.updateSessionInfo(sessionInfo);
312
+ logger.info(`[ChatClient] joined session ${sessionId}`);
313
+ resolve(sessionClient);
314
+ },
315
+ reject: (error) => {
316
+ clearTimeout(timeoutId);
317
+ this.sessionJoinOrCreateRes = undefined;
318
+ logger.error(`[ChatClient] failed to join session` +
319
+ ` ${sessionId}: ${error.message}`);
320
+ reject(error);
321
+ },
322
+ sessionId,
323
+ clientMessageId,
324
+ };
325
+ // Send session_join message
326
+ try {
327
+ this.connection.send({
328
+ type: "control_session_join",
329
+ client_message_id: clientMessageId,
330
+ target_session_id: sessionId,
331
+ });
332
+ logger.debug(`[ChatClient] Sent session_join for session ${sessionId}`);
333
+ }
334
+ catch (error) {
335
+ this.sessionJoinOrCreateRes = undefined;
336
+ clearTimeout(timeoutId);
337
+ reject(error instanceof Error ? error : new Error(String(error)));
338
+ }
339
+ });
340
+ }
341
+ /**
342
+ * Fetch the session list from the server
343
+ */
344
+ fetchSessionList() {
345
+ this.connection.send({
346
+ type: "control_get_session_list",
347
+ client_message_id: (0, uuid_1.v4)(),
348
+ });
349
+ }
350
+ /**
351
+ * Delete a session by sending session_delete_request message
352
+ * and waiting for session_deleted response from server
353
+ * @param sessionId
354
+ * @returns void
355
+ */
356
+ deleteSession(sessionId) {
357
+ if (this.closed) {
358
+ throw new Error("ChatClient is closed");
359
+ }
360
+ this.connection.send({
361
+ type: "control_session_delete",
362
+ client_message_id: (0, uuid_1.v4)(),
363
+ target_session_id: sessionId,
364
+ });
365
+ logger.debug(`[ChatClient] Sent session_delete_request for` + ` session ${sessionId}`);
366
+ }
367
+ /**
368
+ * Create a new team with initial members
369
+ * @param teamName
370
+ * @param initialMembers could be UUIDs or emails
371
+ */
372
+ createNewTeam(teamName, initialMembers) {
373
+ if (this.closed) {
374
+ throw new Error("ChatClient is closed");
375
+ }
376
+ // send team_create_request message
377
+ try {
378
+ this.connection.send({
379
+ type: "control_team_create",
380
+ client_message_id: (0, uuid_1.v4)(),
381
+ team_name: teamName,
382
+ initial_members: initialMembers,
383
+ });
384
+ }
385
+ catch (error) {
386
+ this.eventHandler.onError(String(error));
387
+ }
388
+ }
389
+ /**
390
+ * Add a user to a team
391
+ * @param teamId - UUID of the team
392
+ * @param userId - UUID of the user to add
393
+ */
394
+ addTeamMember(teamId, userId) {
395
+ if (this.closed) {
396
+ throw new Error("ChatClient is closed");
397
+ }
398
+ try {
399
+ this.connection.send({
400
+ type: "control_add_team_user",
401
+ client_message_id: (0, uuid_1.v4)(),
402
+ target_team_id: teamId,
403
+ user_uuid_or_email: userId,
404
+ });
405
+ }
406
+ catch (error) {
407
+ this.eventHandler.onError(String(error));
408
+ }
409
+ }
410
+ /**
411
+ * Remove a user from a team
412
+ * @param teamId - UUID of the team
413
+ * @param userId - UUID of the user to remove
414
+ */
415
+ removeTeamMember(teamId, userId) {
416
+ if (this.closed) {
417
+ throw new Error("ChatClient is closed");
418
+ }
419
+ try {
420
+ this.connection.send({
421
+ type: "control_remove_team_user",
422
+ client_message_id: (0, uuid_1.v4)(),
423
+ target_team_id: teamId,
424
+ user_uuid_or_email: userId,
425
+ });
426
+ }
427
+ catch (error) {
428
+ this.eventHandler.onError(String(error));
429
+ }
430
+ }
431
+ /**
432
+ * Create a new agent
433
+ * @param agentName - the agent name
434
+ * @param templateName - the template name
435
+ * @param teamUuid - the team uuid
436
+ */
437
+ createNewAgent(agentName, templateName, teamUuid) {
438
+ if (this.closed) {
439
+ throw new Error("ChatClient is closed");
440
+ }
441
+ try {
442
+ this.connection.send({
443
+ type: "control_agent_profile_create",
444
+ title: agentName,
445
+ template_name: templateName,
446
+ team_uuid: teamUuid,
447
+ });
448
+ }
449
+ catch (error) {
450
+ this.eventHandler.onError(String(error));
451
+ }
452
+ }
453
+ /**
454
+ * Delete a session from the agent session map
455
+ * @param agentSessionMap - the agent session map
456
+ * @param agentUuid - the agent uuid
457
+ * @param sessionId - the session id
458
+ * @returns true if the session is deleted, false otherwise
459
+ */
460
+ static deleteSessionFromAgentSessionMap(agentSessionMap, agentUuid, sessionId) {
461
+ const agentSession = agentSessionMap.get(agentUuid);
462
+ if (!agentSession) {
463
+ return false;
464
+ }
465
+ const size = agentSession.sessions.length;
466
+ agentSession.sessions = agentSession.sessions.filter((session) => session.session_uuid !== sessionId);
467
+ return size > agentSession.sessions.length;
468
+ }
469
+ handleSessionDeleted(msg) {
470
+ const sessionId = msg.session_id;
471
+ const agentUuid = msg.agent_profile_uuid;
472
+ logger.info(`[ChatClient] Session deleted: ${JSON.stringify(msg)}`);
473
+ let deleted = false;
474
+ if (msg.team_uuid) {
475
+ deleted = ChatClient.deleteSessionFromAgentSessionMap(this.teams.get(msg.team_uuid)?.agentSessionMap ??
476
+ new Map(), agentUuid, sessionId);
477
+ }
478
+ else {
479
+ deleted = ChatClient.deleteSessionFromAgentSessionMap(this.userAgentSessionMap, agentUuid, sessionId);
480
+ }
481
+ if (!deleted) {
482
+ // could be a race condition, we fetch the session list from the server
483
+ logger.warn(`[ChatClient] Session ${sessionId} is not in session map`);
484
+ this.connection.send({
485
+ type: "control_get_session_list",
486
+ client_message_id: (0, uuid_1.v4)(),
487
+ });
488
+ }
489
+ this.activeSessions.delete(sessionId);
490
+ this.newMessage.delete(sessionId);
491
+ void this.eventHandler.onMessage(msg, this);
492
+ }
493
+ handleControlMessage(msg) {
494
+ switch (msg.type) {
495
+ case "control_session_left":
496
+ this.activeSessions.delete(msg.session_id);
497
+ break;
498
+ case "control_agent_profile_created":
499
+ this.handleAgentProfileCreated(msg);
500
+ break;
501
+ case "control_agent_profile_deleted":
502
+ this.handleAgentProfileDeleted(msg);
503
+ break;
504
+ case "control_error":
505
+ this.handleControlError(msg);
506
+ break;
507
+ case "control_session_deleted":
508
+ this.handleSessionDeleted(msg);
509
+ break;
510
+ case "control_session_list":
511
+ logger.warn(`[ChatClient] Unexpected message in runtime: ${msg.type} `);
512
+ break;
513
+ case "control_team_created":
514
+ this.handleTeamCreatedMessage(msg);
515
+ break;
516
+ default: {
517
+ const _exhaustive = msg;
518
+ throw new Error(`unexpected control msg: ${JSON.stringify(msg)}`);
519
+ }
520
+ }
521
+ void this.eventHandler.onMessage(msg, this);
522
+ }
523
+ handleAgentProfileCreated(msg) {
524
+ logger.debug(`[ChatClient.handleAgentProfileCreated] msg: ${JSON.stringify(msg)}`);
525
+ const teamId = msg.profile.team_uuid;
526
+ const profileId = msg.profile.uuid;
527
+ const newAgentSession = {
528
+ agent_profile: msg.profile,
529
+ sessions: [],
530
+ updated_at: Date.now(),
531
+ };
532
+ if (teamId) {
533
+ const team = this.teams.get(teamId);
534
+ if (!team) {
535
+ throw new Error(`[handleAgentProfileCreated] no such team ${teamId}`);
536
+ }
537
+ team.agentSessionMap.set(profileId, newAgentSession);
538
+ }
539
+ else {
540
+ this.userAgentSessionMap.set(profileId, newAgentSession);
541
+ }
542
+ void this.eventHandler.onMessage(msg, this);
543
+ }
544
+ handleAgentProfileDeleted(_msg) {
545
+ throw new Error("handleAgentProfileDeleted not implemented");
546
+ }
547
+ handleSessionMessage(msg) {
548
+ const sessionId = msg.session_id;
549
+ // TODO: we need to handle create new session case here, previously create
550
+ // new session's param are passed by URL, this is no longer an option
551
+ // since we are not creating a new connection for each session.
552
+ // Handle session_info message for pending session joins
553
+ if (msg.type === "session_info") {
554
+ this.handleSessionInfoMessage(msg);
555
+ // notify the event handler, i.e. UI that the session info update
556
+ void this.eventHandler.onMessage(msg, this);
557
+ return;
558
+ }
559
+ // Handle messages for existing active sessions
560
+ if (this.activeSessions.has(sessionId)) {
561
+ const sessionClient = this.activeSessions.get(sessionId);
562
+ if (sessionClient) {
563
+ sessionClient.handleMessage(msg);
564
+ // we are only calling the onMessage for session scoped message for now.
565
+ void this.eventHandler.onMessage(msg, this);
566
+ }
567
+ else {
568
+ throw new Error(`Session client not found for session ${sessionId}`);
569
+ }
570
+ }
571
+ else {
572
+ // Session not active yet, mark as having new messages
573
+ this.newMessage.set(sessionId, true);
574
+ }
575
+ }
576
+ handleTeamCreatedMessage(msg) {
577
+ const teamInfo = {
578
+ team_uuid: msg.team_uuid,
579
+ team_name: msg.team_name,
580
+ owner_uuid: msg.team_owner_uuid,
581
+ participants: msg.members,
582
+ sessions: [],
583
+ agents: [],
584
+ };
585
+ this.teams.set(teamInfo.team_uuid, ChatClient.toClientTeamInfo(teamInfo));
586
+ this.currentTeamId = teamInfo.team_uuid;
587
+ void this.eventHandler.onMessage(msg, this);
588
+ return;
589
+ }
590
+ /**
591
+ * Handle session_info message which can be for:
592
+ * 1. A pending session join/create request
593
+ * 2. An update to an existing active session
594
+ * We update the session list here as well.
595
+ */
596
+ handleSessionInfoMessage(msg) {
597
+ const sessionId = msg.session_id;
598
+ // There is a pending request, check if this is the response
599
+ if (this.sessionJoinOrCreateRes &&
600
+ msg.client_message_id === this.sessionJoinOrCreateRes.clientMessageId) {
601
+ const { resolve, reject } = this.sessionJoinOrCreateRes;
602
+ try {
603
+ logger.info(`[ChatClient] Creating SessionClient for session ${sessionId}`);
604
+ logger.info(`[ChatClient] msg: ${JSON.stringify(msg)}`);
605
+ const sessionClient = new sessionClient_1.SessionClient(sessionId, msg.saved_agent_profile, this.connection, msg.mcp_server_briefs, (0, database_1.createSessionParticipantMap)(msg.participants));
606
+ // we need to pass the session id if this is a new session
607
+ if (this.sessionJoinOrCreateRes.sessionId === "") {
608
+ this.sessionJoinOrCreateRes.sessionId = sessionId;
609
+ }
610
+ else if (this.sessionJoinOrCreateRes.sessionId !== sessionId) {
611
+ throw new Error(`[ChatClient] session id mismatch: ` +
612
+ `${this.sessionJoinOrCreateRes.sessionId} !== ${sessionId}`);
613
+ }
614
+ resolve(sessionClient, msg);
615
+ }
616
+ catch (error) {
617
+ const errorMsg = error instanceof Error ? error : new Error(String(error));
618
+ logger.error(`[ChatClient] Failed to create SessionClient: ${errorMsg.message}`);
619
+ reject(errorMsg);
620
+ }
621
+ }
622
+ else {
623
+ this.updateSessionInfo(msg);
624
+ }
625
+ }
626
+ handleControlError(msg) {
627
+ // reject the pending session join/create request if message id matches
628
+ if (this.sessionJoinOrCreateRes &&
629
+ msg.client_message_id === this.sessionJoinOrCreateRes.clientMessageId) {
630
+ this.sessionJoinOrCreateRes.reject(new Error(msg.message));
631
+ }
632
+ this.eventHandler.onError(`Server error: ${msg.message}`);
633
+ }
634
+ /**
635
+ * Update the session list. This is called when a session is updated.
636
+ * An error is thrown if the session id is not found.
637
+ * TODO: we might want to resync session map, for now, throw error to
638
+ * expose problems in the code.
639
+ */
640
+ updateAgentSessionMap(sessionInfo) {
641
+ const sessionId = sessionInfo.session_id;
642
+ if (sessionInfo.team_uuid) {
643
+ const teamInfo = this.teams.get(sessionInfo.team_uuid);
644
+ if (!teamInfo) {
645
+ throw new Error(`Team ${sessionInfo.team_uuid} not found in team list`);
646
+ }
647
+ //teamInfo.sessions.set(sessionId, ChatClient.toSessionData(sessionInfo));
648
+ const agentSessionMap = teamInfo.agentSessionMap;
649
+ if (!doUpdateAgentSessionMap(agentSessionMap, sessionInfo)) {
650
+ throw new Error(`[updateAgentSessionMap] team session ${sessionId}` +
651
+ ` not found in agent session map`);
652
+ }
653
+ }
654
+ else {
655
+ const agentSessionMap = this.userAgentSessionMap;
656
+ if (!doUpdateAgentSessionMap(agentSessionMap, sessionInfo)) {
657
+ throw new Error(`[updateAgentSessionMap] user session ${sessionId}` +
658
+ ` not found in agent session map`);
659
+ }
660
+ }
661
+ }
662
+ /**
663
+ * Update the session info for an existing session. This passes the session
664
+ * info to the session client. An error is thrown if the session client is not
665
+ * found.
666
+ */
667
+ updateSessionInfo(msg) {
668
+ const sessionId = msg.session_id;
669
+ const sessionClient = this.activeSessions.get(sessionId);
670
+ this.updateAgentSessionMap(msg);
671
+ if (sessionClient) {
672
+ sessionClient.updateSessionInfo(msg);
673
+ }
674
+ else {
675
+ throw new Error(`Session client not found for session ${sessionId}`);
676
+ }
677
+ }
678
+ addSessionToAgentSessionMap(msg) {
679
+ // get the correct agent session map
680
+ const agentSessionMap = msg.team_uuid
681
+ ? this.teams.get(msg.team_uuid)?.agentSessionMap
682
+ : this.userAgentSessionMap;
683
+ if (!agentSessionMap) {
684
+ throw new Error(`Invalid team uuid ${JSON.stringify(msg.team_uuid)}`);
685
+ }
686
+ // update the agent session map
687
+ const agentUuid = msg.saved_agent_profile.uuid;
688
+ const agentSession = agentSessionMap.get(agentUuid);
689
+ if (!agentSession) {
690
+ throw new Error(`[addSessionToAgentSessionMap] Agent ${agentUuid}` +
691
+ ` not found in agent session map`);
692
+ }
693
+ agentSession.sessions.push(sessionInfoToSessionData(msg));
694
+ agentSession.updated_at = new Date(msg.updated_at).getTime();
695
+ }
696
+ }
697
+ exports.ChatClient = ChatClient;
698
+ function sessionInfoToSessionData(msg) {
699
+ return {
700
+ session_uuid: msg.session_id,
701
+ title: msg.title,
702
+ team_uuid: msg.team_uuid,
703
+ agent_profile_uuid: msg.saved_agent_profile.uuid,
704
+ workspace: msg.workspace,
705
+ updated_at: msg.updated_at,
706
+ user_uuid: msg.owner_uuid,
707
+ };
708
+ }
709
+ /**
710
+ * Update the agent session map
711
+ * @param agentSessionMap - the agent session map
712
+ * @param sessionInfo - the session info
713
+ * @returns true if the session is updated, false otherwise
714
+ */
715
+ function doUpdateAgentSessionMap(agentSessionMap, sessionInfo) {
716
+ const sessionId = sessionInfo.session_id;
717
+ const agentUuid = sessionInfo.saved_agent_profile.uuid;
718
+ const agent = agentSessionMap.get(agentUuid);
719
+ if (!agent) {
720
+ return false;
721
+ }
722
+ let updated = false;
723
+ agent.sessions.map((session) => {
724
+ if (session.session_uuid === sessionId) {
725
+ updated = true;
726
+ return sessionInfoToSessionData(sessionInfo);
727
+ }
728
+ else {
729
+ return session;
730
+ }
731
+ });
732
+ return updated;
733
+ }