@xalia/agent 0.6.1 → 0.6.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.
Files changed (127) hide show
  1. package/dist/agent/src/agent/agent.js +109 -57
  2. package/dist/agent/src/agent/agentUtils.js +24 -26
  3. package/dist/agent/src/agent/compressingContextManager.js +3 -2
  4. package/dist/agent/src/agent/dummyLLM.js +1 -3
  5. package/dist/agent/src/agent/imageGenLLM.js +67 -0
  6. package/dist/agent/src/agent/imageGenerator.js +43 -0
  7. package/dist/agent/src/agent/llm.js +27 -0
  8. package/dist/agent/src/agent/mcpServerManager.js +18 -6
  9. package/dist/agent/src/agent/nullAgentEventHandler.js +6 -0
  10. package/dist/agent/src/agent/openAILLM.js +3 -3
  11. package/dist/agent/src/agent/openAILLMStreaming.js +41 -6
  12. package/dist/agent/src/chat/client/chatClient.js +154 -235
  13. package/dist/agent/src/chat/client/constants.js +1 -2
  14. package/dist/agent/src/chat/client/sessionClient.js +47 -15
  15. package/dist/agent/src/chat/client/sessionFiles.js +102 -0
  16. package/dist/agent/src/chat/data/apiKeyManager.js +38 -7
  17. package/dist/agent/src/chat/data/database.js +83 -70
  18. package/dist/agent/src/chat/data/dbSessionFileModels.js +49 -0
  19. package/dist/agent/src/chat/data/dbSessionFiles.js +76 -0
  20. package/dist/agent/src/chat/data/dbSessionMessages.js +57 -0
  21. package/dist/agent/src/chat/data/mimeTypes.js +44 -0
  22. package/dist/agent/src/chat/protocol/messages.js +21 -1
  23. package/dist/agent/src/chat/server/chatContextManager.js +19 -16
  24. package/dist/agent/src/chat/server/connectionManager.js +14 -36
  25. package/dist/agent/src/chat/server/connectionManager.test.js +3 -16
  26. package/dist/agent/src/chat/server/conversation.js +73 -44
  27. package/dist/agent/src/chat/server/imageGeneratorTools.js +111 -0
  28. package/dist/agent/src/chat/server/openSession.js +398 -233
  29. package/dist/agent/src/chat/server/openSessionMessageSender.js +2 -0
  30. package/dist/agent/src/chat/server/server.js +5 -8
  31. package/dist/agent/src/chat/server/sessionFileManager.js +171 -38
  32. package/dist/agent/src/chat/server/sessionRegistry.js +214 -42
  33. package/dist/agent/src/chat/server/test-utils/mockFactories.js +12 -11
  34. package/dist/agent/src/chat/server/tools.js +27 -6
  35. package/dist/agent/src/chat/utils/approvalManager.js +82 -64
  36. package/dist/agent/src/chat/utils/multiAsyncQueue.js +9 -1
  37. package/dist/agent/src/chat/{client/responseHandler.js → utils/responseAwaiter.js} +41 -18
  38. package/dist/agent/src/test/agent.test.js +104 -63
  39. package/dist/agent/src/test/approvalManager.test.js +79 -35
  40. package/dist/agent/src/test/chatContextManager.test.js +16 -17
  41. package/dist/agent/src/test/clientServerConnection.test.js +2 -2
  42. package/dist/agent/src/test/db.test.js +33 -70
  43. package/dist/agent/src/test/dbSessionFiles.test.js +179 -0
  44. package/dist/agent/src/test/dbSessionMessages.test.js +67 -0
  45. package/dist/agent/src/test/dbTestTools.js +6 -5
  46. package/dist/agent/src/test/imageLoad.test.js +1 -1
  47. package/dist/agent/src/test/mcpServerManager.test.js +1 -1
  48. package/dist/agent/src/test/multiAsyncQueue.test.js +50 -0
  49. package/dist/agent/src/test/responseAwaiter.test.js +74 -0
  50. package/dist/agent/src/test/testTools.js +12 -0
  51. package/dist/agent/src/tool/agentChat.js +25 -6
  52. package/dist/agent/src/tool/agentMain.js +1 -1
  53. package/dist/agent/src/tool/chatMain.js +115 -6
  54. package/dist/agent/src/tool/commandPrompt.js +7 -3
  55. package/dist/agent/src/tool/files.js +23 -15
  56. package/dist/agent/src/tool/options.js +2 -2
  57. package/package.json +1 -1
  58. package/scripts/setup_chat +2 -2
  59. package/scripts/test_chat +95 -36
  60. package/src/agent/agent.ts +152 -41
  61. package/src/agent/agentUtils.ts +34 -41
  62. package/src/agent/compressingContextManager.ts +5 -4
  63. package/src/agent/context.ts +1 -1
  64. package/src/agent/dummyLLM.ts +1 -3
  65. package/src/agent/iAgentEventHandler.ts +15 -2
  66. package/src/agent/imageGenLLM.ts +99 -0
  67. package/src/agent/imageGenerator.ts +60 -0
  68. package/src/agent/llm.ts +128 -4
  69. package/src/agent/mcpServerManager.ts +26 -7
  70. package/src/agent/nullAgentEventHandler.ts +6 -0
  71. package/src/agent/openAILLM.ts +3 -8
  72. package/src/agent/openAILLMStreaming.ts +60 -14
  73. package/src/chat/client/chatClient.ts +262 -286
  74. package/src/chat/client/constants.ts +0 -2
  75. package/src/chat/client/sessionClient.ts +82 -20
  76. package/src/chat/client/sessionFiles.ts +142 -0
  77. package/src/chat/data/apiKeyManager.ts +55 -7
  78. package/src/chat/data/dataModels.ts +17 -7
  79. package/src/chat/data/database.ts +107 -92
  80. package/src/chat/data/dbSessionFileModels.ts +91 -0
  81. package/src/chat/data/dbSessionFiles.ts +99 -0
  82. package/src/chat/data/dbSessionMessages.ts +68 -0
  83. package/src/chat/data/mimeTypes.ts +58 -0
  84. package/src/chat/protocol/messages.ts +136 -25
  85. package/src/chat/server/chatContextManager.ts +42 -24
  86. package/src/chat/server/connectionManager.test.ts +2 -22
  87. package/src/chat/server/connectionManager.ts +18 -53
  88. package/src/chat/server/conversation.ts +106 -59
  89. package/src/chat/server/imageGeneratorTools.ts +138 -0
  90. package/src/chat/server/openSession.ts +606 -325
  91. package/src/chat/server/openSessionMessageSender.ts +4 -0
  92. package/src/chat/server/server.ts +5 -11
  93. package/src/chat/server/sessionFileManager.ts +223 -63
  94. package/src/chat/server/sessionRegistry.ts +317 -52
  95. package/src/chat/server/test-utils/mockFactories.ts +13 -13
  96. package/src/chat/server/tools.ts +43 -8
  97. package/src/chat/utils/agentSessionMap.ts +2 -2
  98. package/src/chat/utils/approvalManager.ts +153 -81
  99. package/src/chat/utils/multiAsyncQueue.ts +11 -1
  100. package/src/chat/{client/responseHandler.ts → utils/responseAwaiter.ts} +73 -23
  101. package/src/test/agent.test.ts +152 -75
  102. package/src/test/approvalManager.test.ts +108 -40
  103. package/src/test/chatContextManager.test.ts +26 -22
  104. package/src/test/clientServerConnection.test.ts +3 -3
  105. package/src/test/compressingContextManager.test.ts +1 -1
  106. package/src/test/context.test.ts +2 -1
  107. package/src/test/conversation.test.ts +1 -1
  108. package/src/test/db.test.ts +41 -83
  109. package/src/test/dbSessionFiles.test.ts +258 -0
  110. package/src/test/dbSessionMessages.test.ts +85 -0
  111. package/src/test/dbTestTools.ts +9 -5
  112. package/src/test/imageLoad.test.ts +2 -2
  113. package/src/test/mcpServerManager.test.ts +3 -1
  114. package/src/test/multiAsyncQueue.test.ts +58 -0
  115. package/src/test/responseAwaiter.test.ts +103 -0
  116. package/src/test/testTools.ts +15 -1
  117. package/src/tool/agentChat.ts +36 -8
  118. package/src/tool/agentMain.ts +7 -7
  119. package/src/tool/chatMain.ts +128 -7
  120. package/src/tool/commandPrompt.ts +10 -5
  121. package/src/tool/files.ts +30 -13
  122. package/src/tool/options.ts +1 -1
  123. package/test_data/dummyllm_script_image_gen.json +19 -0
  124. package/test_data/dummyllm_script_invoke_image_gen_tool.json +30 -0
  125. package/test_data/image_gen_test_profile.json +5 -0
  126. package/dist/agent/src/test/responseHandler.test.js +0 -61
  127. package/src/test/responseHandler.test.ts +0 -78
@@ -1,4 +1,5 @@
1
1
  import { v4 as uuidv4 } from "uuid";
2
+ import { strict as assert } from "assert";
2
3
 
3
4
  import { getLogger, SavedAgentProfile } from "@xalia/xmcp/sdk";
4
5
 
@@ -6,10 +7,10 @@ import { Connection } from "./connection";
6
7
  import { SessionClient } from "./sessionClient";
7
8
  import { IChatClientEventHandler } from "./interfaces";
8
9
  import {
9
- SessionData,
10
10
  TeamInfo,
11
11
  TeamParticipant,
12
12
  AgentSessionData,
13
+ SessionDescriptor,
13
14
  } from "../data/dataModels";
14
15
  import { createSessionParticipantMap } from "../data/database";
15
16
  import {
@@ -21,17 +22,34 @@ import {
21
22
  ServerControlMessage,
22
23
  ServerControlSessionDeleted,
23
24
  ServerControlTeamCreated,
25
+ ServerControlTeamMembersUpdated,
24
26
  ServerToClient,
25
27
  ClientToServer,
26
28
  ServerControlAgentProfileCreated,
27
29
  ServerControlAgentProfileDeleted,
30
+ ClientControlSessionCreate,
28
31
  } from "../protocol/messages";
29
- import { SESSION_CREATE_TIMEOUT, SESSION_JOIN_TIMEOUT } from "./constants";
32
+ import { SESSION_JOIN_TIMEOUT } from "./constants";
30
33
  import { ITeamManager } from "./teamManager";
31
34
  import { buildAgentSessionMap } from "../utils/agentSessionMap";
35
+ import { ResponseAwaiter } from "../utils/responseAwaiter";
32
36
 
33
37
  const logger = getLogger();
34
38
 
39
+ /// A fake message which includes an actual client. This avoids a peculiarity
40
+ /// related to handling of async resolves.
41
+
42
+ type CreatedClient = {
43
+ client: SessionClient;
44
+ sessionInfo: ServerSessionInfo;
45
+ };
46
+
47
+ type JoinCreateResponse = ServerSessionInfo | ServerControlError;
48
+
49
+ function idExtractor(msg: JoinCreateResponse): string | undefined {
50
+ return msg.client_message_id;
51
+ }
52
+
35
53
  // client team info, used for session tracking etc.
36
54
  export type ClientTeamInfo = {
37
55
  team_uuid: string;
@@ -41,37 +59,54 @@ export type ClientTeamInfo = {
41
59
  agentSessionMap: Map<string, AgentSessionData>;
42
60
  };
43
61
 
44
- type SessionJoinOrCreateResolver = {
45
- resolve: (
46
- sessionClient: SessionClient,
47
- sessionInfo: ServerSessionInfo
48
- ) => void;
49
- reject: (error: Error) => void;
50
- sessionId: string;
51
- clientMessageId: string;
52
- };
53
-
54
62
  export class ChatClient implements ITeamManager {
63
+ private connection: Connection<ClientToServer, ServerToClient>;
64
+ private eventHandler: IChatClientEventHandler;
65
+ // agent_uuid -> agent session data
66
+ private userAgentSessionMap: Map<string, AgentSessionData>;
67
+ // team_uuid -> team info
68
+ private teams: Map<string, ClientTeamInfo>;
69
+ // Whether this client is in guest mode
70
+ private isGuestMode: boolean;
71
+ private closed: boolean;
72
+ private currentSessionId: string | undefined;
73
+ // note: currentTeamId will not be reset when swtiching to a user session.
74
+ private currentTeamId: string | undefined;
75
+ // session_uuid -> session client
76
+ private activeSessions: Map<string, SessionClient>;
77
+ // session_uuid -> true if there is a new message.
78
+ private newMessage: Map<string, boolean>;
79
+ // Track pending session join requests.
80
+ private sessionJoinAwaiter: ResponseAwaiter<
81
+ JoinCreateResponse,
82
+ CreatedClient
83
+ >;
84
+
55
85
  private constructor(
56
- private connection: Connection<ClientToServer, ServerToClient>,
57
- private eventHandler: IChatClientEventHandler,
58
- // agent_uuid -> agent session data
59
- private userAgentSessionMap: Map<string, AgentSessionData> = new Map(),
60
- // team_uuid -> team info
61
- private teams: Map<string, ClientTeamInfo> = new Map(),
62
- private closed: boolean = false,
63
- private currentSessionId: string | undefined = undefined,
64
- // note: currentTeamId will not be reset when swtiching to a user session.
65
- private currentTeamId: string | undefined = undefined,
66
- // session_uuid -> session client
67
- private activeSessions: Map<string, SessionClient> = new Map(),
68
- // session_uuid -> true if there is a new message.
69
- private newMessage: Map<string, boolean> = new Map(),
70
- // Track pending session join request, this should be a singleton
71
- private sessionJoinOrCreateRes:
72
- | SessionJoinOrCreateResolver
73
- | undefined = undefined
74
- ) {}
86
+ connection: Connection<ClientToServer, ServerToClient>,
87
+ eventHandler: IChatClientEventHandler,
88
+ userAgentSessionMap: Map<string, AgentSessionData>,
89
+ teams: Map<string, ClientTeamInfo>,
90
+ isGuestMode: boolean
91
+ ) {
92
+ this.connection = connection;
93
+ this.eventHandler = eventHandler;
94
+ this.userAgentSessionMap = userAgentSessionMap;
95
+ this.teams = teams;
96
+ this.isGuestMode = isGuestMode;
97
+
98
+ this.closed = false;
99
+ this.currentSessionId = undefined;
100
+ this.currentTeamId = undefined;
101
+ this.activeSessions = new Map();
102
+ this.newMessage = new Map();
103
+ this.sessionJoinAwaiter = ResponseAwaiter.initWithImmediate(
104
+ "control_error",
105
+ idExtractor,
106
+ this.onJoinCreateResponse.bind(this),
107
+ Math.max(SESSION_JOIN_TIMEOUT)
108
+ );
109
+ }
75
110
 
76
111
  /**
77
112
  * Connect to server and create ChatClient instance
@@ -81,6 +116,9 @@ export class ChatClient implements ITeamManager {
81
116
  token: string,
82
117
  eventHandler: IChatClientEventHandler
83
118
  ): Promise<ChatClient> {
119
+ // Determine if this is a guest token (format: guest_<hash>_<session-id>)
120
+ const isGuestMode = token.startsWith("guest_");
121
+
84
122
  const connection = new Connection<ClientToServer, ServerToClient>({
85
123
  url,
86
124
  token,
@@ -92,7 +130,7 @@ export class ChatClient implements ITeamManager {
92
130
  // Register session_info handler for initialization
93
131
  connection.on("control_session_list", (msg) => {
94
132
  // get user sessions, user agents, and team sessions
95
- const userSessions = new Map<string, SessionData>();
133
+ const userSessions = new Map<string, SessionDescriptor>();
96
134
  msg.user_sessions.forEach((session) => {
97
135
  userSessions.set(session.session_uuid, session);
98
136
  });
@@ -127,7 +165,8 @@ export class ChatClient implements ITeamManager {
127
165
  connection,
128
166
  eventHandler,
129
167
  userAgentSessionMap,
130
- teams
168
+ teams,
169
+ isGuestMode
131
170
  );
132
171
  resolveClient(client);
133
172
  } else {
@@ -291,7 +330,6 @@ export class ChatClient implements ITeamManager {
291
330
  this.teams.clear();
292
331
  this.currentSessionId = undefined;
293
332
  this.currentTeamId = undefined;
294
- this.sessionJoinOrCreateRes = undefined;
295
333
  }
296
334
 
297
335
  public async createNewSession(
@@ -303,74 +341,24 @@ export class ChatClient implements ITeamManager {
303
341
  throw new Error("ChatClient is closed");
304
342
  }
305
343
 
306
- if (this.sessionJoinOrCreateRes) {
307
- logger.error(
308
- `[ChatClient] creating new session with ` +
309
- `profile id ${JSON.stringify(agentProfileId)}`
310
- );
311
- throw new Error("Session join/create is already in progress");
312
- }
344
+ const clientMessageId = uuidv4();
345
+ const createdSessionP =
346
+ this.sessionJoinAwaiter.waitForResponse(clientMessageId);
313
347
 
314
- return new Promise((resolve, reject) => {
315
- const clientMessageId = uuidv4();
348
+ const createMsg: ClientControlSessionCreate = {
349
+ type: "control_session_create",
350
+ client_message_id: clientMessageId,
351
+ title: newTitle,
352
+ agent_profile_id: agentProfileId,
353
+ team_id: teamId,
354
+ };
355
+ this.connection.send(createMsg);
356
+ logger.info(`[ChatClient] Sent ${JSON.stringify(createMsg)}`);
316
357
 
317
- // Set up timeout for the request
318
- const timeoutId = setTimeout(() => {
319
- this.sessionJoinOrCreateRes = undefined;
320
- reject(new Error(`Session join timeout for creating new session`));
321
- }, SESSION_CREATE_TIMEOUT);
322
-
323
- // Track this pending request
324
- this.sessionJoinOrCreateRes = {
325
- resolve: (
326
- sessionClient: SessionClient,
327
- sessionInfo: ServerSessionInfo
328
- ) => {
329
- const sessionId = this.sessionJoinOrCreateRes?.sessionId;
330
- if (!sessionId) {
331
- throw new Error("Session id is not set in resolving.");
332
- }
333
- if (sessionId !== sessionInfo.session_id) {
334
- throw new Error("SessionInfo id mismatch");
335
- }
336
- clearTimeout(timeoutId);
337
- this.activeSessions.set(sessionId, sessionClient);
338
- this.currentSessionId = sessionId;
339
- if (sessionInfo.team_uuid) {
340
- this.currentTeamId = sessionInfo.team_uuid;
341
- }
342
- this.sessionJoinOrCreateRes = undefined;
343
- this.addSessionToAgentSessionMap(sessionInfo);
344
- resolve(sessionClient);
345
- },
346
- reject: (error: Error) => {
347
- clearTimeout(timeoutId);
348
- this.sessionJoinOrCreateRes = undefined;
349
- reject(error);
350
- },
351
- sessionId: "",
352
- clientMessageId,
353
- };
354
-
355
- // Send session_create message
356
- try {
357
- this.connection.send({
358
- type: "control_session_create",
359
- client_message_id: clientMessageId,
360
- title: newTitle,
361
- agent_profile_id: agentProfileId,
362
- team_id: teamId,
363
- });
364
- logger.info(
365
- `[ChatClient] Sent session_create for session ${newTitle}` +
366
- ` client message id: ${clientMessageId}`
367
- );
368
- } catch (error) {
369
- this.sessionJoinOrCreateRes = undefined;
370
- clearTimeout(timeoutId);
371
- reject(error instanceof Error ? error : new Error(String(error)));
372
- }
373
- });
358
+ const createdSession = await createdSessionP;
359
+ const sessionId = createdSession.client.getSessionUUID();
360
+ logger.info(`[ChatClient] joined session ${sessionId}`);
361
+ return createdSession.client;
374
362
  }
375
363
 
376
364
  /**
@@ -382,66 +370,26 @@ export class ChatClient implements ITeamManager {
382
370
  throw new Error("ChatClient is closed");
383
371
  }
384
372
 
385
- if (this.sessionJoinOrCreateRes) {
386
- logger.error(`[ChatClient] connecting to session ${sessionId}`);
387
- throw new Error("Session join/create is already in progress");
388
- }
389
-
390
- // TODO: we can directly return a session client if we cache conversation.
391
- // For now, we go through the join process.
392
- return new Promise((resolve, reject) => {
393
- const clientMessageId = uuidv4();
373
+ const clientMessageId = uuidv4();
374
+ const createdSessionP =
375
+ this.sessionJoinAwaiter.waitForResponse(clientMessageId);
394
376
 
395
- // Set up timeout for the request
396
- const timeoutId = setTimeout(() => {
397
- this.sessionJoinOrCreateRes = undefined;
398
- reject(new Error(`Session join timeout for session ${sessionId}`));
399
- }, SESSION_JOIN_TIMEOUT);
400
-
401
- // Track this pending request
402
- this.sessionJoinOrCreateRes = {
403
- resolve: (
404
- sessionClient: SessionClient,
405
- sessionInfo: ServerSessionInfo
406
- ) => {
407
- clearTimeout(timeoutId);
408
- this.activeSessions.set(sessionId, sessionClient);
409
- this.currentSessionId = sessionId;
410
- if (sessionInfo.team_uuid) {
411
- this.currentTeamId = sessionInfo.team_uuid;
412
- }
413
- this.sessionJoinOrCreateRes = undefined;
414
- this.updateSessionInfo(sessionInfo);
415
- logger.info(`[ChatClient] joined session ${sessionId}`);
416
- resolve(sessionClient);
417
- },
418
- reject: (error: Error) => {
419
- clearTimeout(timeoutId);
420
- this.sessionJoinOrCreateRes = undefined;
421
- logger.error(
422
- `[ChatClient] failed to join session` +
423
- ` ${sessionId}: ${error.message}`
424
- );
425
- reject(error);
426
- },
427
- sessionId,
428
- clientMessageId,
429
- };
430
-
431
- // Send session_join message
432
- try {
433
- this.connection.send({
434
- type: "control_session_join",
435
- client_message_id: clientMessageId,
436
- target_session_id: sessionId,
437
- });
438
- logger.debug(`[ChatClient] Sent session_join for session ${sessionId}`);
439
- } catch (error) {
440
- this.sessionJoinOrCreateRes = undefined;
441
- clearTimeout(timeoutId);
442
- reject(error instanceof Error ? error : new Error(String(error)));
443
- }
377
+ this.connection.send({
378
+ type: "control_session_join",
379
+ client_message_id: clientMessageId,
380
+ target_session_id: sessionId,
444
381
  });
382
+ logger.debug(`[ChatClient] Sent session_join for session ${sessionId}`);
383
+
384
+ // Await the response
385
+
386
+ const createdSession = await createdSessionP;
387
+ const newSessionId = createdSession.client.getSessionUUID();
388
+ if (newSessionId !== sessionId) {
389
+ throw new Error(`unexpected session id ${newSessionId}`);
390
+ }
391
+ logger.info(`[ChatClient] joined session ${sessionId}`);
392
+ return createdSession.client;
445
393
  }
446
394
 
447
395
  /**
@@ -476,6 +424,27 @@ export class ChatClient implements ITeamManager {
476
424
  );
477
425
  }
478
426
 
427
+ /**
428
+ * Delete an agent profile by sending control_agent_profile_delete message
429
+ * @param agentProfileUuid - The UUID of the agent profile to delete
430
+ * @returns void
431
+ */
432
+ deleteAgentProfile(agentProfileUuid: string): void {
433
+ if (this.closed) {
434
+ throw new Error("ChatClient is closed");
435
+ }
436
+
437
+ this.connection.send({
438
+ type: "control_agent_profile_delete",
439
+ agent_profile_uuid: agentProfileUuid,
440
+ });
441
+
442
+ logger.debug(
443
+ `[ChatClient] Sent control_agent_profile_delete for profile ` +
444
+ agentProfileUuid
445
+ );
446
+ }
447
+
479
448
  /**
480
449
  * Create a new team with initial members
481
450
  * @param teamName
@@ -644,6 +613,9 @@ export class ChatClient implements ITeamManager {
644
613
  case "control_team_created":
645
614
  this.handleTeamCreatedMessage(msg);
646
615
  break;
616
+ case "control_team_members_updated":
617
+ this.handleTeamMembersUpdated(msg);
618
+ break;
647
619
  default: {
648
620
  const _exhaustive: never = msg;
649
621
  throw new Error(`unexpected control msg: ${JSON.stringify(msg)}`);
@@ -676,8 +648,36 @@ export class ChatClient implements ITeamManager {
676
648
  void this.eventHandler.onMessage(msg, this);
677
649
  }
678
650
 
679
- private handleAgentProfileDeleted(_msg: ServerControlAgentProfileDeleted) {
680
- throw new Error("handleAgentProfileDeleted not implemented");
651
+ private handleAgentProfileDeleted(msg: ServerControlAgentProfileDeleted) {
652
+ logger.debug(
653
+ `[ChatClient.handleAgentProfileDeleted] msg: ${JSON.stringify(msg)}`
654
+ );
655
+
656
+ const profileUuid = msg.profile_uuid;
657
+ let found = false;
658
+
659
+ // Try to remove from user agent session map
660
+ if (this.userAgentSessionMap.has(profileUuid)) {
661
+ this.userAgentSessionMap.delete(profileUuid);
662
+ found = true;
663
+ } else {
664
+ // Try to remove from team agent session maps
665
+ for (const [_teamId, team] of this.teams.entries()) {
666
+ if (team.agentSessionMap.has(profileUuid)) {
667
+ team.agentSessionMap.delete(profileUuid);
668
+ found = true;
669
+ break;
670
+ }
671
+ }
672
+ }
673
+
674
+ if (!found) {
675
+ logger.warn(
676
+ `[ChatClient] Agent profile ${profileUuid} not found in any session map`
677
+ );
678
+ }
679
+
680
+ void this.eventHandler.onMessage(msg, this);
681
681
  }
682
682
 
683
683
  private handleSessionMessage(msg: ServerSessionScopedMessage): void {
@@ -725,119 +725,71 @@ export class ChatClient implements ITeamManager {
725
725
  return;
726
726
  }
727
727
 
728
+ private handleTeamMembersUpdated(msg: ServerControlTeamMembersUpdated): void {
729
+ const team = this.teams.get(msg.team_uuid);
730
+ if (!team) {
731
+ logger.warn(
732
+ `[ChatClient] Received team_members_updated for unknown team ` +
733
+ msg.team_uuid
734
+ );
735
+ return;
736
+ }
737
+
738
+ // Update the participants map
739
+ team.participants = new Map(
740
+ msg.members.map((participant) => [participant.user_uuid, participant])
741
+ );
742
+
743
+ logger.info(
744
+ `[ChatClient] Updated team members for team ${msg.team_uuid}, ` +
745
+ `now has ${String(msg.members.length)} members`
746
+ );
747
+ void this.eventHandler.onMessage(msg, this);
748
+ }
749
+
728
750
  /**
729
751
  * Handle session_info message which can be for:
730
752
  * 1. A pending session join/create request
731
753
  * 2. An update to an existing active session
754
+ * 3. A new session notification
732
755
  * We update the session list here as well.
733
756
  */
734
757
  private handleSessionInfoMessage(msg: ServerSessionInfo): void {
735
- const sessionId = msg.session_id;
736
-
737
- // There is a pending request, check if this is the response
738
- if (
739
- this.sessionJoinOrCreateRes &&
740
- msg.client_message_id === this.sessionJoinOrCreateRes.clientMessageId
741
- ) {
742
- const { resolve, reject } = this.sessionJoinOrCreateRes;
743
-
744
- try {
745
- logger.info(
746
- `[ChatClient] Creating SessionClient for session ${sessionId}`
747
- );
748
- logger.info(`[ChatClient] msg: ${JSON.stringify(msg)}`);
749
- const sessionClient = new SessionClient(
750
- sessionId,
751
- msg.saved_agent_profile,
752
- this.connection,
753
- msg.mcp_server_briefs,
754
- createSessionParticipantMap(msg.participants)
755
- );
756
-
757
- // we need to pass the session id if this is a new session
758
- if (this.sessionJoinOrCreateRes.sessionId === "") {
759
- this.sessionJoinOrCreateRes.sessionId = sessionId;
760
- } else if (this.sessionJoinOrCreateRes.sessionId !== sessionId) {
761
- throw new Error(
762
- `[ChatClient] session id mismatch: ` +
763
- `${this.sessionJoinOrCreateRes.sessionId} !== ${sessionId}`
764
- );
765
- }
758
+ // We intercept messages for th Awaiter here so that we can create and
759
+ // register the client immediately, before other messages for this session
760
+ // are handled. Relying on Awaiter alone (currently) schedules the resolv
761
+ // to be called later (potentially AFTER further messages for the session
762
+ // have been received).
766
763
 
767
- resolve(sessionClient, msg);
768
- } catch (error) {
769
- const errorMsg =
770
- error instanceof Error ? error : new Error(String(error));
771
- logger.error(
772
- `[ChatClient] Failed to create SessionClient: ${errorMsg.message}`
773
- );
774
- reject(errorMsg);
775
- }
776
- } else {
777
- this.updateSessionInfo(msg);
764
+ if (this.sessionJoinAwaiter.onMessage(msg)) {
765
+ return;
778
766
  }
767
+
768
+ logger.debug(
769
+ `[ChatClient.handleSessionInfoMessage] not handled by awaiter - updating`
770
+ );
771
+ this.upsertSessionToAgentSessionMap(msg);
779
772
  }
780
773
 
781
774
  handleControlError(msg: ServerControlError): void {
782
- // reject the pending session join/create request if message id matches
783
- if (
784
- this.sessionJoinOrCreateRes &&
785
- msg.client_message_id === this.sessionJoinOrCreateRes.clientMessageId
786
- ) {
787
- this.sessionJoinOrCreateRes.reject(new Error(msg.message));
775
+ if (this.sessionJoinAwaiter.onMessage(msg)) {
776
+ return;
788
777
  }
789
- this.eventHandler.onError(`Server error: ${msg.message}`);
790
- }
791
778
 
792
- /**
793
- * Update the session list. This is called when a session is updated.
794
- * An error is thrown if the session id is not found.
795
- * TODO: we might want to resync session map, for now, throw error to
796
- * expose problems in the code.
797
- */
798
- private updateAgentSessionMap(sessionInfo: ServerSessionInfo): void {
799
- const sessionId = sessionInfo.session_id;
800
- if (sessionInfo.team_uuid) {
801
- const teamInfo = this.teams.get(sessionInfo.team_uuid);
802
- if (!teamInfo) {
803
- throw new Error(`Team ${sessionInfo.team_uuid} not found in team list`);
804
- }
805
- //teamInfo.sessions.set(sessionId, ChatClient.toSessionData(sessionInfo));
806
- const agentSessionMap = teamInfo.agentSessionMap;
807
- if (!doUpdateAgentSessionMap(agentSessionMap, sessionInfo)) {
808
- throw new Error(
809
- `[updateAgentSessionMap] team session ${sessionId}` +
810
- ` not found in agent session map`
811
- );
812
- }
813
- } else {
814
- const agentSessionMap = this.userAgentSessionMap;
815
- if (!doUpdateAgentSessionMap(agentSessionMap, sessionInfo)) {
816
- throw new Error(
817
- `[updateAgentSessionMap] user session ${sessionId}` +
818
- ` not found in agent session map`
819
- );
820
- }
821
- }
779
+ this.eventHandler.onError(`Server error: ${msg.message}`);
822
780
  }
823
781
 
824
- /**
825
- * Update the session info for an existing session. This passes the session
826
- * info to the session client. An error is thrown if the session client is not
827
- * found.
828
- */
829
- private updateSessionInfo(msg: ServerSessionInfo): void {
830
- const sessionId = msg.session_id;
831
- const sessionClient = this.activeSessions.get(sessionId);
832
- this.updateAgentSessionMap(msg);
833
- if (sessionClient) {
834
- sessionClient.updateSessionInfo(msg);
835
- } else {
836
- throw new Error(`Session client not found for session ${sessionId}`);
782
+ private upsertSessionToAgentSessionMap(msg: ServerSessionInfo): void {
783
+ // Skip agent session map updates for guest sessions since they don't
784
+ // need session organization
785
+ if (this.isGuestMode) {
786
+ logger.info(
787
+ `[ChatClient] Skipping agent session map add for ` +
788
+ `guest session ${msg.session_id}`
789
+ );
790
+ return;
837
791
  }
838
- }
839
792
 
840
- private addSessionToAgentSessionMap(msg: ServerSessionInfo): void {
841
793
  // get the correct agent session map
842
794
  const agentSessionMap = msg.team_uuid
843
795
  ? this.teams.get(msg.team_uuid)?.agentSessionMap
@@ -854,47 +806,71 @@ export class ChatClient implements ITeamManager {
854
806
  ` not found in agent session map`
855
807
  );
856
808
  }
857
- agentSession.sessions.push(sessionInfoToSessionData(msg));
809
+
810
+ // Check if session already exists
811
+ const existingSessionIndex = agentSession.sessions.findIndex(
812
+ (session) => session.session_uuid === msg.session_id
813
+ );
814
+
815
+ if (existingSessionIndex !== -1) {
816
+ // Update existing session
817
+ agentSession.sessions[existingSessionIndex] =
818
+ sessionInfoToSessionDescriptor(msg);
819
+ logger.info(
820
+ `[ChatClient] Updated existing session ${msg.session_id} in ` +
821
+ `agent session map`
822
+ );
823
+ } else {
824
+ // Add new session
825
+ agentSession.sessions.push(sessionInfoToSessionDescriptor(msg));
826
+ logger.info(
827
+ `[ChatClient] Added new session ${msg.session_id} to agent session map`
828
+ );
829
+ }
830
+
858
831
  agentSession.updated_at = new Date(msg.updated_at).getTime();
859
832
  }
833
+
834
+ // This is the immediate callback of the ResponseAwaiter for session
835
+ // joining. It must create and register the client.
836
+ private onJoinCreateResponse(msg: JoinCreateResponse): CreatedClient {
837
+ // Errors should not get through here.
838
+ assert(msg.type === "session_info");
839
+
840
+ const sessionId = msg.session_id;
841
+ const client = new SessionClient(
842
+ sessionId,
843
+ msg.saved_agent_profile,
844
+ this.connection,
845
+ msg.mcp_server_briefs,
846
+ createSessionParticipantMap(msg.participants),
847
+ msg.agent_paused
848
+ );
849
+
850
+ this.activeSessions.set(sessionId, client);
851
+ this.currentSessionId = sessionId;
852
+ if (msg.team_uuid) {
853
+ this.currentTeamId = msg.team_uuid;
854
+ }
855
+ this.upsertSessionToAgentSessionMap(msg);
856
+
857
+ return {
858
+ client,
859
+ sessionInfo: msg,
860
+ };
861
+ }
860
862
  }
861
863
 
862
- function sessionInfoToSessionData(msg: ServerSessionInfo): SessionData {
864
+ function sessionInfoToSessionDescriptor(
865
+ msg: ServerSessionInfo
866
+ ): SessionDescriptor {
863
867
  return {
864
868
  session_uuid: msg.session_id,
865
869
  title: msg.title,
866
870
  team_uuid: msg.team_uuid,
867
871
  agent_profile_uuid: msg.saved_agent_profile.uuid,
868
- workspace: msg.workspace,
869
872
  updated_at: msg.updated_at,
870
873
  user_uuid: msg.owner_uuid,
874
+ agent_paused: msg.agent_paused,
871
875
  };
872
876
  }
873
-
874
- /**
875
- * Update the agent session map
876
- * @param agentSessionMap - the agent session map
877
- * @param sessionInfo - the session info
878
- * @returns true if the session is updated, false otherwise
879
- */
880
- function doUpdateAgentSessionMap(
881
- agentSessionMap: Map<string, AgentSessionData>,
882
- sessionInfo: ServerSessionInfo
883
- ): boolean {
884
- const sessionId = sessionInfo.session_id;
885
- const agentUuid = sessionInfo.saved_agent_profile.uuid;
886
- const agent = agentSessionMap.get(agentUuid);
887
- if (!agent) {
888
- return false;
889
- }
890
- let updated = false;
891
- agent.sessions.map((session) => {
892
- if (session.session_uuid === sessionId) {
893
- updated = true;
894
- return sessionInfoToSessionData(sessionInfo);
895
- } else {
896
- return session;
897
- }
898
- });
899
- return updated;
900
- }
@@ -1,3 +1 @@
1
1
  export const SESSION_JOIN_TIMEOUT = 6000; // 6 seconds
2
-
3
- export const SESSION_CREATE_TIMEOUT = 6000; // 6 seconds