@xalia/agent 0.6.2 → 0.6.4

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 (48) hide show
  1. package/dist/agent/src/agent/agent.js +8 -5
  2. package/dist/agent/src/agent/agentUtils.js +9 -12
  3. package/dist/agent/src/chat/client/chatClient.js +88 -240
  4. package/dist/agent/src/chat/client/constants.js +1 -2
  5. package/dist/agent/src/chat/client/sessionClient.js +4 -13
  6. package/dist/agent/src/chat/client/sessionFiles.js +3 -3
  7. package/dist/agent/src/chat/protocol/messages.js +0 -1
  8. package/dist/agent/src/chat/server/chatContextManager.js +5 -9
  9. package/dist/agent/src/chat/server/connectionManager.test.js +1 -0
  10. package/dist/agent/src/chat/server/conversation.js +9 -4
  11. package/dist/agent/src/chat/server/openSession.js +241 -238
  12. package/dist/agent/src/chat/server/openSessionMessageSender.js +2 -0
  13. package/dist/agent/src/chat/server/sessionRegistry.js +17 -12
  14. package/dist/agent/src/chat/utils/approvalManager.js +82 -64
  15. package/dist/agent/src/chat/{client/responseHandler.js → utils/responseAwaiter.js} +41 -18
  16. package/dist/agent/src/test/agent.test.js +90 -53
  17. package/dist/agent/src/test/approvalManager.test.js +79 -35
  18. package/dist/agent/src/test/chatContextManager.test.js +12 -17
  19. package/dist/agent/src/test/responseAwaiter.test.js +74 -0
  20. package/dist/agent/src/tool/agentChat.js +1 -1
  21. package/dist/agent/src/tool/chatMain.js +2 -2
  22. package/package.json +1 -1
  23. package/scripts/setup_chat +2 -2
  24. package/scripts/test_chat +61 -60
  25. package/src/agent/agent.ts +9 -5
  26. package/src/agent/agentUtils.ts +14 -27
  27. package/src/chat/client/chatClient.ts +167 -296
  28. package/src/chat/client/constants.ts +0 -2
  29. package/src/chat/client/sessionClient.ts +15 -19
  30. package/src/chat/client/sessionFiles.ts +9 -12
  31. package/src/chat/data/dataModels.ts +1 -0
  32. package/src/chat/protocol/messages.ts +9 -12
  33. package/src/chat/server/chatContextManager.ts +7 -12
  34. package/src/chat/server/connectionManager.test.ts +1 -0
  35. package/src/chat/server/conversation.ts +19 -11
  36. package/src/chat/server/openSession.ts +383 -340
  37. package/src/chat/server/openSessionMessageSender.ts +4 -0
  38. package/src/chat/server/sessionRegistry.ts +33 -12
  39. package/src/chat/utils/approvalManager.ts +153 -81
  40. package/src/chat/{client/responseHandler.ts → utils/responseAwaiter.ts} +73 -23
  41. package/src/test/agent.test.ts +130 -62
  42. package/src/test/approvalManager.test.ts +108 -40
  43. package/src/test/chatContextManager.test.ts +19 -20
  44. package/src/test/responseAwaiter.test.ts +103 -0
  45. package/src/tool/agentChat.ts +2 -2
  46. package/src/tool/chatMain.ts +2 -2
  47. package/dist/agent/src/test/responseHandler.test.js +0 -61
  48. package/src/test/responseHandler.test.ts +0 -78
@@ -85,12 +85,15 @@ class Agent {
85
85
  // `toolCallResults`.
86
86
  const result = await this.doToolCall(toolCall);
87
87
  toolCallResults.push([context.length, result]);
88
- context.push({
88
+ const toolResult = {
89
89
  role: "tool",
90
90
  tool_call_id: toolCall.id,
91
91
  content: result.response,
92
- metadata: result.metadata,
93
- });
92
+ };
93
+ if (result.metadata) {
94
+ toolResult.metadata = result.metadata;
95
+ }
96
+ context.push(toolResult);
94
97
  // If the tool call requested that its args be redacted, this can be
95
98
  // done now - before the next LLM invocation.
96
99
  if (result.overwriteArgs) {
@@ -221,14 +224,14 @@ class Agent {
221
224
  result = { response: "User denied tool request." };
222
225
  }
223
226
  else {
224
- const args = JSON.parse(toolCall.function.arguments);
227
+ const args = JSON.parse(toolCall.function.arguments || "{}");
225
228
  result = await agentTool.handler(this, args);
226
229
  }
227
230
  }
228
231
  else {
229
232
  // McpServer tool call (agentTool === undefined). Sanity check the
230
233
  // tool call data, get approval, and then invoke.
231
- const args = JSON.parse(toolCall.function.arguments);
234
+ const args = JSON.parse(toolCall.function.arguments || "{}");
232
235
  const tc = this.mcpServerManager.verifyToolCall(toolName, args);
233
236
  if (!(await this.eventHandler.onToolCall(toolCall, false))) {
234
237
  result = { response: "User denied tool request." };
@@ -17,16 +17,14 @@ const repeatLLM_1 = require("./repeatLLM");
17
17
  const context_1 = require("./context");
18
18
  const imageGenLLM_1 = require("./imageGenLLM");
19
19
  const logger = (0, sdk_1.getLogger)();
20
- async function createAgentWithoutSkills(llmUrl, agentProfile, defaultModel, eventHandler, platform, contextManager, llmApiKey, sudomcpConfig, authorizedUrl, stream = false) {
20
+ async function createAgentWithoutSkills(llmUrl, model, eventHandler, platform, contextManager, llmApiKey, sudomcpConfig, authorizedUrl, stream = false) {
21
21
  // Init SudoMcpServerManager
22
22
  logger.debug("[createAgentWithSkills] creating SudoMcpServerManager.");
23
23
  const sudoMcpServerManager = await sudoMcpServerManager_1.SkillManager.initialize((url, authResultP, displayName) => {
24
24
  platform.openUrl(url, authResultP, displayName);
25
25
  }, sudomcpConfig.backend_url, sudomcpConfig.api_key, authorizedUrl);
26
- logger.debug("[createAgentWithoutSkills] restore mcp settings:" +
27
- JSON.stringify(agentProfile.mcp_settings));
28
26
  // Create agent using the event handler
29
- const agent = await createAgentFromSkillManager(llmUrl, agentProfile, defaultModel, eventHandler, platform, contextManager, llmApiKey, sudoMcpServerManager, stream);
27
+ const agent = await createAgentFromSkillManager(llmUrl, model, eventHandler, platform, contextManager, llmApiKey, sudoMcpServerManager, stream);
30
28
  return [agent, sudoMcpServerManager];
31
29
  }
32
30
  /**
@@ -34,17 +32,16 @@ async function createAgentWithoutSkills(llmUrl, agentProfile, defaultModel, even
34
32
  * IAgentEventHandler interface. This is the preferred way to create
35
33
  * agents.
36
34
  */
37
- async function createAgentWithSkills(llmUrl, agentProfile, defaultModel, eventHandler, platform, contextManager, llmApiKey, sudomcpConfig, authorizedUrl, stream = false) {
38
- const [agent, sudoMcpServerManager] = await createAgentWithoutSkills(llmUrl, agentProfile, defaultModel, eventHandler, platform, contextManager, llmApiKey, sudomcpConfig, authorizedUrl, stream);
39
- logger.debug("[createAgentWithSkills] restoring skills");
40
- await sudoMcpServerManager.restoreMcpSettings(agentProfile.mcp_settings);
35
+ async function createAgentWithSkills(llmUrl, model, eventHandler, platform, contextManager, llmApiKey, sudomcpConfig, mcpSettings, authorizedUrl, stream = false) {
36
+ const [agent, sudoMcpServerManager] = await createAgentWithoutSkills(llmUrl, model, eventHandler, platform, contextManager, llmApiKey, sudomcpConfig, authorizedUrl, stream);
37
+ logger.debug(`[createAgentWithSkills] skilles: ${JSON.stringify(mcpSettings)}`);
38
+ await sudoMcpServerManager.restoreMcpSettings(mcpSettings);
41
39
  return [agent, sudoMcpServerManager];
42
40
  }
43
- async function createAgentFromSkillManager(llmUrl, agentProfile, defaultModel, eventHandler, platform, contextManager, llmApiKey, skillManager, stream = false) {
41
+ async function createAgentFromSkillManager(llmUrl, model, eventHandler, platform, contextManager, llmApiKey, skillManager, stream = false) {
44
42
  // Create agent
45
43
  logger.debug("[createAgentFromSkillManager] creating agent ...");
46
- const llm = await createLLM(llmUrl, llmApiKey, agentProfile.model || defaultModel, stream, platform);
47
- contextManager.setAgentPrompt(agentProfile.system_prompt);
44
+ const llm = await createLLM(llmUrl, llmApiKey, model, stream, platform);
48
45
  const agent = agent_1.Agent.initializeWithLLM(eventHandler, llm, contextManager, skillManager);
49
46
  logger.debug("[createAgentFromSkillManager] done");
50
47
  return agent;
@@ -101,7 +98,7 @@ async function createNonInteractiveAgent(url, agentProfile, defaultModel, conver
101
98
  onToolCallResult: () => { },
102
99
  };
103
100
  const contextManager = new context_1.ContextManager(agentProfile.system_prompt, conversation || []);
104
- const [agent, _] = await createAgentWithSkills(url, agentProfile, defaultModel, eventHandler, platform, contextManager, openaiApiKey, sudomcpConfig, undefined);
101
+ const [agent, _] = await createAgentWithSkills(url, agentProfile.model || defaultModel, eventHandler, platform, contextManager, openaiApiKey, sudomcpConfig, agentProfile.mcp_settings, undefined);
105
102
  return agent;
106
103
  }
107
104
  /**
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ChatClient = void 0;
4
4
  const uuid_1 = require("uuid");
5
+ const assert_1 = require("assert");
5
6
  const sdk_1 = require("@xalia/xmcp/sdk");
6
7
  const connection_1 = require("./connection");
7
8
  const sessionClient_1 = require("./sessionClient");
@@ -9,41 +10,31 @@ const database_1 = require("../data/database");
9
10
  const messages_1 = require("../protocol/messages");
10
11
  const constants_1 = require("./constants");
11
12
  const agentSessionMap_1 = require("../utils/agentSessionMap");
13
+ const responseAwaiter_1 = require("../utils/responseAwaiter");
12
14
  const logger = (0, sdk_1.getLogger)();
15
+ function idExtractor(msg) {
16
+ return msg.client_message_id;
17
+ }
13
18
  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(),
19
- // Whether this client is in guest mode
20
- isGuestMode = false, closed = false, currentSessionId = undefined,
21
- // note: currentTeamId will not be reset when swtiching to a user session.
22
- currentTeamId = undefined,
23
- // session_uuid -> session client
24
- activeSessions = new Map(),
25
- // session_uuid -> true if there is a new message.
26
- newMessage = new Map(),
27
- // Track pending session join request, this should be a singleton
28
- sessionJoinOrCreateRes = undefined) {
19
+ constructor(connection, eventHandler, userAgentSessionMap, teams, isGuestMode) {
29
20
  this.connection = connection;
30
21
  this.eventHandler = eventHandler;
31
22
  this.userAgentSessionMap = userAgentSessionMap;
32
23
  this.teams = teams;
33
24
  this.isGuestMode = isGuestMode;
34
- this.closed = closed;
35
- this.currentSessionId = currentSessionId;
36
- this.currentTeamId = currentTeamId;
37
- this.activeSessions = activeSessions;
38
- this.newMessage = newMessage;
39
- this.sessionJoinOrCreateRes = sessionJoinOrCreateRes;
25
+ this.closed = false;
26
+ this.currentSessionId = undefined;
27
+ this.currentTeamId = undefined;
28
+ this.activeSessions = new Map();
29
+ this.newMessage = new Map();
30
+ this.sessionJoinAwaiter = responseAwaiter_1.ResponseAwaiter.initWithImmediate("control_error", idExtractor, this.onJoinCreateResponse.bind(this), Math.max(constants_1.SESSION_JOIN_TIMEOUT));
40
31
  }
41
32
  /**
42
33
  * Connect to server and create ChatClient instance
43
34
  */
44
35
  static async init(url, token, eventHandler) {
45
36
  // Determine if this is a guest token (format: guest_<hash>_<session-id>)
46
- const isGuestMode = token.startsWith('guest_');
37
+ const isGuestMode = token.startsWith("guest_");
47
38
  const connection = new connection_1.Connection({
48
39
  url,
49
40
  token,
@@ -217,70 +208,26 @@ class ChatClient {
217
208
  this.teams.clear();
218
209
  this.currentSessionId = undefined;
219
210
  this.currentTeamId = undefined;
220
- this.sessionJoinOrCreateRes = undefined;
221
211
  }
222
212
  async createNewSession(newTitle, agentProfileId, teamId) {
223
213
  if (this.closed) {
224
214
  throw new Error("ChatClient is closed");
225
215
  }
226
- if (this.sessionJoinOrCreateRes) {
227
- logger.error(`[ChatClient] creating new session with ` +
228
- `profile id ${JSON.stringify(agentProfileId)}`);
229
- throw new Error("Session join/create is already in progress");
230
- }
231
- return new Promise((resolve, reject) => {
232
- const clientMessageId = (0, uuid_1.v4)();
233
- // Set up timeout for the request
234
- const timeoutId = setTimeout(() => {
235
- this.sessionJoinOrCreateRes = undefined;
236
- reject(new Error(`Session join timeout for creating new session`));
237
- }, constants_1.SESSION_CREATE_TIMEOUT);
238
- // Track this pending request
239
- this.sessionJoinOrCreateRes = {
240
- resolve: (sessionClient, sessionInfo) => {
241
- const sessionId = this.sessionJoinOrCreateRes?.sessionId;
242
- if (!sessionId) {
243
- throw new Error("Session id is not set in resolving.");
244
- }
245
- if (sessionId !== sessionInfo.session_id) {
246
- throw new Error("SessionInfo id mismatch");
247
- }
248
- clearTimeout(timeoutId);
249
- this.activeSessions.set(sessionId, sessionClient);
250
- this.currentSessionId = sessionId;
251
- if (sessionInfo.team_uuid) {
252
- this.currentTeamId = sessionInfo.team_uuid;
253
- }
254
- this.sessionJoinOrCreateRes = undefined;
255
- this.addSessionToAgentSessionMap(sessionInfo);
256
- resolve(sessionClient);
257
- },
258
- reject: (error) => {
259
- clearTimeout(timeoutId);
260
- this.sessionJoinOrCreateRes = undefined;
261
- reject(error);
262
- },
263
- sessionId: "",
264
- clientMessageId,
265
- };
266
- // Send session_create message
267
- try {
268
- this.connection.send({
269
- type: "control_session_create",
270
- client_message_id: clientMessageId,
271
- title: newTitle,
272
- agent_profile_id: agentProfileId,
273
- team_id: teamId,
274
- });
275
- logger.info(`[ChatClient] Sent session_create for session ${newTitle}` +
276
- ` client message id: ${clientMessageId}`);
277
- }
278
- catch (error) {
279
- this.sessionJoinOrCreateRes = undefined;
280
- clearTimeout(timeoutId);
281
- reject(error instanceof Error ? error : new Error(String(error)));
282
- }
283
- });
216
+ const clientMessageId = (0, uuid_1.v4)();
217
+ const createdSessionP = this.sessionJoinAwaiter.waitForResponse(clientMessageId);
218
+ const createMsg = {
219
+ type: "control_session_create",
220
+ client_message_id: clientMessageId,
221
+ title: newTitle,
222
+ agent_profile_id: agentProfileId,
223
+ team_id: teamId,
224
+ };
225
+ this.connection.send(createMsg);
226
+ logger.info(`[ChatClient] Sent ${JSON.stringify(createMsg)}`);
227
+ const createdSession = await createdSessionP;
228
+ const sessionId = createdSession.client.getSessionUUID();
229
+ logger.info(`[ChatClient] joined session ${sessionId}`);
230
+ return createdSession.client;
284
231
  }
285
232
  /**
286
233
  * Connect to an existing session by sending session_join message
@@ -290,58 +237,22 @@ class ChatClient {
290
237
  if (this.closed) {
291
238
  throw new Error("ChatClient is closed");
292
239
  }
293
- if (this.sessionJoinOrCreateRes) {
294
- logger.error(`[ChatClient] connecting to session ${sessionId}`);
295
- throw new Error("Session join/create is already in progress");
296
- }
297
- // TODO: we can directly return a session client if we cache conversation.
298
- // For now, we go through the join process.
299
- return new Promise((resolve, reject) => {
300
- const clientMessageId = (0, uuid_1.v4)();
301
- // Set up timeout for the request
302
- const timeoutId = setTimeout(() => {
303
- this.sessionJoinOrCreateRes = undefined;
304
- reject(new Error(`Session join timeout for session ${sessionId}`));
305
- }, constants_1.SESSION_JOIN_TIMEOUT);
306
- // Track this pending request
307
- this.sessionJoinOrCreateRes = {
308
- resolve: (sessionClient, sessionInfo) => {
309
- clearTimeout(timeoutId);
310
- this.activeSessions.set(sessionId, sessionClient);
311
- this.currentSessionId = sessionId;
312
- if (sessionInfo.team_uuid) {
313
- this.currentTeamId = sessionInfo.team_uuid;
314
- }
315
- this.sessionJoinOrCreateRes = undefined;
316
- this.updateSessionInfo(sessionInfo);
317
- logger.info(`[ChatClient] joined session ${sessionId}`);
318
- resolve(sessionClient);
319
- },
320
- reject: (error) => {
321
- clearTimeout(timeoutId);
322
- this.sessionJoinOrCreateRes = undefined;
323
- logger.error(`[ChatClient] failed to join session` +
324
- ` ${sessionId}: ${error.message}`);
325
- reject(error);
326
- },
327
- sessionId,
328
- clientMessageId,
329
- };
330
- // Send session_join message
331
- try {
332
- this.connection.send({
333
- type: "control_session_join",
334
- client_message_id: clientMessageId,
335
- target_session_id: sessionId,
336
- });
337
- logger.debug(`[ChatClient] Sent session_join for session ${sessionId}`);
338
- }
339
- catch (error) {
340
- this.sessionJoinOrCreateRes = undefined;
341
- clearTimeout(timeoutId);
342
- reject(error instanceof Error ? error : new Error(String(error)));
343
- }
240
+ const clientMessageId = (0, uuid_1.v4)();
241
+ const createdSessionP = this.sessionJoinAwaiter.waitForResponse(clientMessageId);
242
+ this.connection.send({
243
+ type: "control_session_join",
244
+ client_message_id: clientMessageId,
245
+ target_session_id: sessionId,
344
246
  });
247
+ logger.debug(`[ChatClient] Sent session_join for session ${sessionId}`);
248
+ // Await the response
249
+ const createdSession = await createdSessionP;
250
+ const newSessionId = createdSession.client.getSessionUUID();
251
+ if (newSessionId !== sessionId) {
252
+ throw new Error(`unexpected session id ${newSessionId}`);
253
+ }
254
+ logger.info(`[ChatClient] joined session ${sessionId}`);
255
+ return createdSession.client;
345
256
  }
346
257
  /**
347
258
  * Fetch the session list from the server
@@ -649,97 +560,28 @@ class ChatClient {
649
560
  * Handle session_info message which can be for:
650
561
  * 1. A pending session join/create request
651
562
  * 2. An update to an existing active session
563
+ * 3. A new session notification
652
564
  * We update the session list here as well.
653
565
  */
654
566
  handleSessionInfoMessage(msg) {
655
- const sessionId = msg.session_id;
656
- // There is a pending request, check if this is the response
657
- if (this.sessionJoinOrCreateRes &&
658
- msg.client_message_id === this.sessionJoinOrCreateRes.clientMessageId) {
659
- const { resolve, reject } = this.sessionJoinOrCreateRes;
660
- try {
661
- logger.info(`[ChatClient] Creating SessionClient for session ${sessionId}`);
662
- logger.info(`[ChatClient] msg: ${JSON.stringify(msg)}`);
663
- const sessionClient = new sessionClient_1.SessionClient(sessionId, msg.saved_agent_profile, this.connection, msg.mcp_server_briefs, (0, database_1.createSessionParticipantMap)(msg.participants), msg.agent_paused);
664
- // we need to pass the session id if this is a new session
665
- if (this.sessionJoinOrCreateRes.sessionId === "") {
666
- this.sessionJoinOrCreateRes.sessionId = sessionId;
667
- }
668
- else if (this.sessionJoinOrCreateRes.sessionId !== sessionId) {
669
- throw new Error(`[ChatClient] session id mismatch: ` +
670
- `${this.sessionJoinOrCreateRes.sessionId} !== ${sessionId}`);
671
- }
672
- resolve(sessionClient, msg);
673
- }
674
- catch (error) {
675
- const errorMsg = error instanceof Error ? error : new Error(String(error));
676
- logger.error(`[ChatClient] Failed to create SessionClient: ${errorMsg.message}`);
677
- reject(errorMsg);
678
- }
679
- }
680
- else {
681
- this.updateSessionInfo(msg);
567
+ // We intercept messages for th Awaiter here so that we can create and
568
+ // register the client immediately, before other messages for this session
569
+ // are handled. Relying on Awaiter alone (currently) schedules the resolv
570
+ // to be called later (potentially AFTER further messages for the session
571
+ // have been received).
572
+ if (this.sessionJoinAwaiter.onMessage(msg)) {
573
+ return;
682
574
  }
575
+ logger.debug(`[ChatClient.handleSessionInfoMessage] not handled by awaiter - updating`);
576
+ this.upsertSessionToAgentSessionMap(msg);
683
577
  }
684
578
  handleControlError(msg) {
685
- // reject the pending session join/create request if message id matches
686
- if (this.sessionJoinOrCreateRes &&
687
- msg.client_message_id === this.sessionJoinOrCreateRes.clientMessageId) {
688
- this.sessionJoinOrCreateRes.reject(new Error(msg.message));
689
- }
690
- this.eventHandler.onError(`Server error: ${msg.message}`);
691
- }
692
- /**
693
- * Update the session list. This is called when a session is updated.
694
- * An error is thrown if the session id is not found.
695
- * TODO: we might want to resync session map, for now, throw error to
696
- * expose problems in the code.
697
- */
698
- updateAgentSessionMap(sessionInfo) {
699
- const sessionId = sessionInfo.session_id;
700
- // Skip agent session map updates for guest sessions since they don't
701
- // need session organization
702
- if (this.isGuestMode) {
703
- logger.info(`[ChatClient] Skipping agent session map update for ` +
704
- `guest session ${sessionId}`);
579
+ if (this.sessionJoinAwaiter.onMessage(msg)) {
705
580
  return;
706
581
  }
707
- if (sessionInfo.team_uuid) {
708
- const teamInfo = this.teams.get(sessionInfo.team_uuid);
709
- if (!teamInfo) {
710
- throw new Error(`Team ${sessionInfo.team_uuid} not found in team list`);
711
- }
712
- const agentSessionMap = teamInfo.agentSessionMap;
713
- if (!doUpdateAgentSessionMap(agentSessionMap, sessionInfo)) {
714
- throw new Error(`[updateAgentSessionMap] team session ${sessionId}` +
715
- ` not found in agent session map`);
716
- }
717
- }
718
- else {
719
- const agentSessionMap = this.userAgentSessionMap;
720
- if (!doUpdateAgentSessionMap(agentSessionMap, sessionInfo)) {
721
- throw new Error(`[updateAgentSessionMap] user session ${sessionId}` +
722
- ` not found in agent session map`);
723
- }
724
- }
725
- }
726
- /**
727
- * Update the SessionDescriptor info for an existing session, given a full
728
- * ServerSessionInfo. This also passes the session info to the session
729
- * client. An error is thrown if the session client is not found.
730
- */
731
- updateSessionInfo(msg) {
732
- const sessionId = msg.session_id;
733
- const sessionClient = this.activeSessions.get(sessionId);
734
- this.updateAgentSessionMap(msg);
735
- if (sessionClient) {
736
- sessionClient.updateSessionInfo(msg);
737
- }
738
- else {
739
- throw new Error(`Session client not found for session ${sessionId}`);
740
- }
582
+ this.eventHandler.onError(`Server error: ${msg.message}`);
741
583
  }
742
- addSessionToAgentSessionMap(msg) {
584
+ upsertSessionToAgentSessionMap(msg) {
743
585
  // Skip agent session map updates for guest sessions since they don't
744
586
  // need session organization
745
587
  if (this.isGuestMode) {
@@ -761,9 +603,40 @@ class ChatClient {
761
603
  throw new Error(`[addSessionToAgentSessionMap] Agent ${agentUuid}` +
762
604
  ` not found in agent session map`);
763
605
  }
764
- agentSession.sessions.push(sessionInfoToSessionDescriptor(msg));
606
+ // Check if session already exists
607
+ const existingSessionIndex = agentSession.sessions.findIndex((session) => session.session_uuid === msg.session_id);
608
+ if (existingSessionIndex !== -1) {
609
+ // Update existing session
610
+ agentSession.sessions[existingSessionIndex] =
611
+ sessionInfoToSessionDescriptor(msg);
612
+ logger.info(`[ChatClient] Updated existing session ${msg.session_id} in ` +
613
+ `agent session map`);
614
+ }
615
+ else {
616
+ // Add new session
617
+ agentSession.sessions.push(sessionInfoToSessionDescriptor(msg));
618
+ logger.info(`[ChatClient] Added new session ${msg.session_id} to agent session map`);
619
+ }
765
620
  agentSession.updated_at = new Date(msg.updated_at).getTime();
766
621
  }
622
+ // This is the immediate callback of the ResponseAwaiter for session
623
+ // joining. It must create and register the client.
624
+ onJoinCreateResponse(msg) {
625
+ // Errors should not get through here.
626
+ (0, assert_1.strict)(msg.type === "session_info");
627
+ const sessionId = msg.session_id;
628
+ const client = new sessionClient_1.SessionClient(sessionId, msg.saved_agent_profile, this.connection, msg.mcp_server_briefs, (0, database_1.createSessionParticipantMap)(msg.participants), msg.agent_paused);
629
+ this.activeSessions.set(sessionId, client);
630
+ this.currentSessionId = sessionId;
631
+ if (msg.team_uuid) {
632
+ this.currentTeamId = msg.team_uuid;
633
+ }
634
+ this.upsertSessionToAgentSessionMap(msg);
635
+ return {
636
+ client,
637
+ sessionInfo: msg,
638
+ };
639
+ }
767
640
  }
768
641
  exports.ChatClient = ChatClient;
769
642
  function sessionInfoToSessionDescriptor(msg) {
@@ -777,28 +650,3 @@ function sessionInfoToSessionDescriptor(msg) {
777
650
  agent_paused: msg.agent_paused,
778
651
  };
779
652
  }
780
- /**
781
- * Update the agent session map
782
- * @param agentSessionMap - the agent session map
783
- * @param sessionInfo - the session info
784
- * @returns true if the session is updated, false otherwise
785
- */
786
- function doUpdateAgentSessionMap(agentSessionMap, sessionInfo) {
787
- const sessionId = sessionInfo.session_id;
788
- const agentUuid = sessionInfo.saved_agent_profile.uuid;
789
- const agent = agentSessionMap.get(agentUuid);
790
- if (!agent) {
791
- return false;
792
- }
793
- let updated = false;
794
- agent.sessions.map((session) => {
795
- if (session.session_uuid === sessionId) {
796
- updated = true;
797
- return sessionInfoToSessionDescriptor(sessionInfo);
798
- }
799
- else {
800
- return session;
801
- }
802
- });
803
- return updated;
804
- }
@@ -1,5 +1,4 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.SESSION_CREATE_TIMEOUT = exports.SESSION_JOIN_TIMEOUT = void 0;
3
+ exports.SESSION_JOIN_TIMEOUT = void 0;
4
4
  exports.SESSION_JOIN_TIMEOUT = 6000; // 6 seconds
5
- exports.SESSION_CREATE_TIMEOUT = 6000; // 6 seconds
@@ -6,9 +6,8 @@ const uuid_1 = require("uuid");
6
6
  const sdk_1 = require("@xalia/xmcp/sdk");
7
7
  const mcpServerManager_1 = require("../../agent/mcpServerManager");
8
8
  const messages_1 = require("../protocol/messages");
9
- const database_1 = require("../data/database");
10
9
  const sessionFiles_1 = require("./sessionFiles");
11
- const responseHandler_1 = require("./responseHandler");
10
+ const responseAwaiter_1 = require("../utils/responseAwaiter");
12
11
  const logger = (0, sdk_1.getLogger)();
13
12
  class RemoteSudoMcpServerManager {
14
13
  constructor(sender, briefs) {
@@ -153,7 +152,7 @@ class SessionClient {
153
152
  this.sender = sender;
154
153
  this.smsm = new RemoteSudoMcpServerManager(this, serverBriefs);
155
154
  this.sessionFiles = new sessionFiles_1.SessionFiles(sessionUUID, fileList, sender);
156
- this.responseHandler = new responseHandler_1.ResponseHandler("session_error");
155
+ this.responseHandler = responseAwaiter_1.ResponseAwaiter.init("session_error", (msg) => msg.client_message_id);
157
156
  this.participants = participants;
158
157
  this.systemPrompt = "";
159
158
  this.model = "";
@@ -267,11 +266,6 @@ class SessionClient {
267
266
  };
268
267
  this.sender.send(enrichedMessage);
269
268
  }
270
- updateSessionInfo(sessionInfo) {
271
- // TODO: Determine the correct approach. Cache the workspace?
272
- const infoStr = JSON.stringify(sessionInfo.workspace);
273
- logger.debug(`[SessionClient] ignoring session info: ${infoStr}`);
274
- }
275
269
  setWorkspace(message, imageB64) {
276
270
  const msg = {
277
271
  type: "set_workspace",
@@ -303,7 +297,7 @@ class SessionClient {
303
297
  session_id: this.sessionUUID,
304
298
  };
305
299
  this.sender.send(msg);
306
- const response = await this.responseHandler.waitForResponse(msg);
300
+ const response = await this.responseHandler.waitForResponse(msg.client_message_id);
307
301
  if (response.type === "session_shared") {
308
302
  return response.access_token;
309
303
  }
@@ -337,10 +331,7 @@ class SessionClient {
337
331
  this.model = message.model;
338
332
  break;
339
333
  case "session_info":
340
- // Update participants if we receive session_info again
341
- if ("participants" in message) {
342
- this.participants = (0, database_1.createSessionParticipantMap)(message.participants);
343
- }
334
+ // This is handled in the layer above, in the chatClient
344
335
  break;
345
336
  case "user_added":
346
337
  this.participants.set(message.user_uuid, {
@@ -4,7 +4,7 @@ exports.SessionFiles = void 0;
4
4
  const uuid_1 = require("uuid");
5
5
  const assert_1 = require("assert");
6
6
  const sdk_1 = require("@xalia/xmcp/sdk");
7
- const responseHandler_1 = require("./responseHandler");
7
+ const responseAwaiter_1 = require("../utils/responseAwaiter");
8
8
  const logger = (0, sdk_1.getLogger)();
9
9
  /// Object for the UI to use to interact with the FileManager. If the UI
10
10
  /// receives ServerSessionFileChanged or ServerSessionFileDeleted then it
@@ -14,7 +14,7 @@ class SessionFiles {
14
14
  constructor(sessionUUID, initialFileList, sender) {
15
15
  this.sessionUUID = sessionUUID;
16
16
  this.descriptors = new Map(initialFileList.map((d) => [d.name, d]));
17
- this.responseHandler = new responseHandler_1.ResponseHandler(undefined);
17
+ this.responseHandler = responseAwaiter_1.ResponseAwaiter.init(undefined, (msg) => msg.client_message_id);
18
18
  this.sender = sender;
19
19
  }
20
20
  listFiles() {
@@ -31,7 +31,7 @@ class SessionFiles {
31
31
  client_message_id: (0, uuid_1.v4)(),
32
32
  };
33
33
  this.sender.send(msg);
34
- const response = await this.responseHandler.waitForResponse(msg);
34
+ const response = await this.responseHandler.waitForResponse(msg.client_message_id);
35
35
  if (response.name !== name) {
36
36
  throw new Error(`invalid name for file ${name}: ${JSON.stringify(response)}`);
37
37
  }
@@ -72,7 +72,6 @@ function isServerSessionScopedMessage(message) {
72
72
  case "agent_reasoning_chunk":
73
73
  case "user_joined":
74
74
  case "user_left":
75
- case "session_update":
76
75
  case "tool_auto_approval_set":
77
76
  case "tool_call":
78
77
  case "tool_call_approval_result":
@@ -99,7 +99,7 @@ class ChatContextManager {
99
99
  getConversationMessages() {
100
100
  return this.conversationMessages.concat(this.pendingUserMessages);
101
101
  }
102
- processUserMessage(msg, from) {
102
+ processUserMessage(msg, from_uuid, from_nickname) {
103
103
  // TODO: maintain a queue internally instead of relying on the caller to
104
104
  // pass in our generated messages back into `startAgentResponse`.
105
105
  // Filter out null messages immediately.
@@ -112,7 +112,8 @@ class ChatContextManager {
112
112
  session_id: this.sessionUUID,
113
113
  message_idx,
114
114
  message: msg.message,
115
- user_uuid: from,
115
+ user_uuid: from_uuid,
116
+ user_nickname: from_nickname,
116
117
  };
117
118
  if (msg.imageB64) {
118
119
  userMessage.imageB64 = msg.imageB64;
@@ -149,7 +150,7 @@ class ChatContextManager {
149
150
  // Compute the new llm messages
150
151
  const llmUserMessages = [];
151
152
  for (const msg of pendingUserMessages) {
152
- const userMsg = (0, agent_1.createUserMessage)(msg.message, msg.imageB64, msg.user_uuid);
153
+ const userMsg = (0, agent_1.createUserMessage)(msg.message, msg.imageB64, msg.user_nickname);
153
154
  if (userMsg) {
154
155
  llmUserMessages.push(userMsg);
155
156
  }
@@ -200,7 +201,7 @@ class ChatContextManager {
200
201
  }
201
202
  if (JSON.stringify(sMsg.content) !== JSON.stringify(lMsg)) {
202
203
  messageListError(`newSessionMessages[${String(i)}].content !== ` +
203
- `newLLMMessages[${String(i)}].content`);
204
+ `newLLMMessages[${String(i)}]`);
204
205
  }
205
206
  if (sMsg.message_idx !== pMsg.message_idx) {
206
207
  messageListError(`newSessionMessages[${String(i)}].message_idx !== ` +
@@ -244,11 +245,6 @@ class ChatContextManager {
244
245
  this.startingLLMContextLength = undefined;
245
246
  this.pendingMessages = undefined;
246
247
  this.curAgentMsgIdx = undefined;
247
- return {
248
- type: "session_error",
249
- session_id: this.sessionUUID,
250
- message: errMsg,
251
- };
252
248
  }
253
249
  processAgentMessage(msg, end) {
254
250
  (0, assert_1.strict)(typeof this.startingLLMContextLength !== "undefined");
@@ -61,6 +61,7 @@ vitest_1.vi.mock("ws");
61
61
  d: {
62
62
  type: "user_msg",
63
63
  user_uuid: "user_1",
64
+ user_nickname: "User 1",
64
65
  session_id: "session_1",
65
66
  message: "message text",
66
67
  message_idx: 12,