@xalia/agent 0.6.0 → 0.6.2

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 (112) hide show
  1. package/dist/agent/src/agent/agent.js +103 -54
  2. package/dist/agent/src/agent/agentUtils.js +22 -21
  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 +84 -13
  13. package/dist/agent/src/chat/client/sessionClient.js +47 -6
  14. package/dist/agent/src/chat/client/sessionFiles.js +102 -0
  15. package/dist/agent/src/chat/data/apiKeyManager.js +38 -7
  16. package/dist/agent/src/chat/data/database.js +83 -70
  17. package/dist/agent/src/chat/data/dbSessionFileModels.js +49 -0
  18. package/dist/agent/src/chat/data/dbSessionFiles.js +76 -0
  19. package/dist/agent/src/chat/data/dbSessionMessages.js +57 -0
  20. package/dist/agent/src/chat/data/mimeTypes.js +44 -0
  21. package/dist/agent/src/chat/protocol/messages.js +21 -0
  22. package/dist/agent/src/chat/server/chatContextManager.js +14 -7
  23. package/dist/agent/src/chat/server/connectionManager.js +14 -36
  24. package/dist/agent/src/chat/server/connectionManager.test.js +2 -16
  25. package/dist/agent/src/chat/server/conversation.js +69 -45
  26. package/dist/agent/src/chat/server/imageGeneratorTools.js +111 -0
  27. package/dist/agent/src/chat/server/openSession.js +205 -43
  28. package/dist/agent/src/chat/server/server.js +5 -8
  29. package/dist/agent/src/chat/server/sessionFileManager.js +171 -38
  30. package/dist/agent/src/chat/server/sessionRegistry.js +199 -32
  31. package/dist/agent/src/chat/server/test-utils/mockFactories.js +12 -11
  32. package/dist/agent/src/chat/server/tools.js +27 -6
  33. package/dist/agent/src/chat/utils/multiAsyncQueue.js +9 -1
  34. package/dist/agent/src/test/agent.test.js +15 -11
  35. package/dist/agent/src/test/chatContextManager.test.js +4 -0
  36. package/dist/agent/src/test/clientServerConnection.test.js +2 -2
  37. package/dist/agent/src/test/db.test.js +33 -70
  38. package/dist/agent/src/test/dbSessionFiles.test.js +179 -0
  39. package/dist/agent/src/test/dbSessionMessages.test.js +67 -0
  40. package/dist/agent/src/test/dbTestTools.js +6 -5
  41. package/dist/agent/src/test/imageLoad.test.js +1 -1
  42. package/dist/agent/src/test/mcpServerManager.test.js +1 -1
  43. package/dist/agent/src/test/multiAsyncQueue.test.js +50 -0
  44. package/dist/agent/src/test/testTools.js +12 -0
  45. package/dist/agent/src/tool/agentChat.js +25 -6
  46. package/dist/agent/src/tool/agentMain.js +1 -1
  47. package/dist/agent/src/tool/chatMain.js +113 -4
  48. package/dist/agent/src/tool/commandPrompt.js +7 -3
  49. package/dist/agent/src/tool/files.js +23 -15
  50. package/dist/agent/src/tool/options.js +2 -2
  51. package/package.json +1 -1
  52. package/scripts/test_chat +124 -66
  53. package/src/agent/agent.ts +145 -38
  54. package/src/agent/agentUtils.ts +27 -21
  55. package/src/agent/compressingContextManager.ts +5 -4
  56. package/src/agent/context.ts +1 -1
  57. package/src/agent/dummyLLM.ts +1 -3
  58. package/src/agent/iAgentEventHandler.ts +15 -2
  59. package/src/agent/imageGenLLM.ts +99 -0
  60. package/src/agent/imageGenerator.ts +60 -0
  61. package/src/agent/llm.ts +128 -4
  62. package/src/agent/mcpServerManager.ts +26 -7
  63. package/src/agent/nullAgentEventHandler.ts +6 -0
  64. package/src/agent/openAILLM.ts +3 -8
  65. package/src/agent/openAILLMStreaming.ts +60 -14
  66. package/src/chat/client/chatClient.ts +119 -14
  67. package/src/chat/client/sessionClient.ts +75 -9
  68. package/src/chat/client/sessionFiles.ts +145 -0
  69. package/src/chat/data/apiKeyManager.ts +55 -7
  70. package/src/chat/data/dataModels.ts +16 -7
  71. package/src/chat/data/database.ts +107 -92
  72. package/src/chat/data/dbSessionFileModels.ts +91 -0
  73. package/src/chat/data/dbSessionFiles.ts +99 -0
  74. package/src/chat/data/dbSessionMessages.ts +68 -0
  75. package/src/chat/data/mimeTypes.ts +58 -0
  76. package/src/chat/protocol/messages.ts +127 -13
  77. package/src/chat/server/chatContextManager.ts +36 -13
  78. package/src/chat/server/connectionManager.test.ts +1 -22
  79. package/src/chat/server/connectionManager.ts +18 -53
  80. package/src/chat/server/conversation.ts +96 -57
  81. package/src/chat/server/imageGeneratorTools.ts +138 -0
  82. package/src/chat/server/openSession.ts +287 -49
  83. package/src/chat/server/server.ts +5 -11
  84. package/src/chat/server/sessionFileManager.ts +223 -63
  85. package/src/chat/server/sessionRegistry.ts +285 -41
  86. package/src/chat/server/test-utils/mockFactories.ts +13 -13
  87. package/src/chat/server/tools.ts +43 -8
  88. package/src/chat/utils/agentSessionMap.ts +2 -2
  89. package/src/chat/utils/multiAsyncQueue.ts +11 -1
  90. package/src/test/agent.test.ts +23 -14
  91. package/src/test/chatContextManager.test.ts +7 -2
  92. package/src/test/clientServerConnection.test.ts +3 -3
  93. package/src/test/compressingContextManager.test.ts +1 -1
  94. package/src/test/context.test.ts +2 -1
  95. package/src/test/conversation.test.ts +1 -1
  96. package/src/test/db.test.ts +41 -83
  97. package/src/test/dbSessionFiles.test.ts +258 -0
  98. package/src/test/dbSessionMessages.test.ts +85 -0
  99. package/src/test/dbTestTools.ts +9 -5
  100. package/src/test/imageLoad.test.ts +2 -2
  101. package/src/test/mcpServerManager.test.ts +3 -1
  102. package/src/test/multiAsyncQueue.test.ts +58 -0
  103. package/src/test/testTools.ts +15 -1
  104. package/src/tool/agentChat.ts +35 -7
  105. package/src/tool/agentMain.ts +7 -7
  106. package/src/tool/chatMain.ts +126 -5
  107. package/src/tool/commandPrompt.ts +10 -5
  108. package/src/tool/files.ts +30 -13
  109. package/src/tool/options.ts +1 -1
  110. package/test_data/dummyllm_script_image_gen.json +19 -0
  111. package/test_data/dummyllm_script_invoke_image_gen_tool.json +30 -0
  112. package/test_data/image_gen_test_profile.json +5 -0
@@ -1,8 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SessionRegistry = void 0;
4
- exports.userSessionDataCreate = userSessionDataCreate;
5
- exports.teamSessionDataCreate = teamSessionDataCreate;
4
+ exports.userSessionCreateData = userSessionCreateData;
5
+ exports.teamSessionCreateData = teamSessionCreateData;
6
6
  const uuid_1 = require("uuid");
7
7
  const sdk_1 = require("@xalia/xmcp/sdk");
8
8
  const messages_1 = require("../protocol/messages");
@@ -10,8 +10,8 @@ const openSession_1 = require("./openSession");
10
10
  const database_1 = require("../data/database");
11
11
  const userResolver_1 = require("../utils/userResolver");
12
12
  const errors_1 = require("../protocol/errors");
13
- const agentUtils_1 = require("../../agent/agentUtils");
14
13
  const errorUtils_1 = require("./errorUtils");
14
+ const apiKeyManager_1 = require("../data/apiKeyManager");
15
15
  const logger = (0, sdk_1.getLogger)();
16
16
  class SessionRegistry {
17
17
  constructor(db, connectionManager, llmUrl, xmcpUrl) {
@@ -29,6 +29,8 @@ class SessionRegistry {
29
29
  // Session instances
30
30
  // sessionId -> OpenSession
31
31
  this.openSessions = new Map();
32
+ this.guests = new Map();
33
+ this.apiKeyManager = new apiKeyManager_1.ApiKeyManager(db);
32
34
  }
33
35
  /**
34
36
  * Add user to session membership.
@@ -112,6 +114,47 @@ class SessionRegistry {
112
114
  getInMemoryUserSessions(userId) {
113
115
  return this.userSessions.get(userId) || new Set();
114
116
  }
117
+ // IMessageProcessor<ClientToServer>
118
+ async authenticate(token) {
119
+ // Parse the api key to determine the type of connection
120
+ const { prefix, apiKey, payload: sessionUUID, } = apiKeyManager_1.ApiKeyManager.parseToken(token);
121
+ if (prefix === openSession_1.GUEST_TOKEN_PREFIX) {
122
+ if (!sessionUUID) {
123
+ throw new Error(`invalid token ${token}`);
124
+ }
125
+ const descriptor = await this.getSessionDescriptor(sessionUUID);
126
+ if (!descriptor) {
127
+ throw new Error(`no such session ${sessionUUID}`);
128
+ }
129
+ if (descriptor.access_token !== token) {
130
+ throw new Error(`invalid guest key ${apiKey}`);
131
+ }
132
+ // For now, add a new user to the DB (so simplify the process of adding
133
+ // users).
134
+ const user_uuid = (0, uuid_1.v4)();
135
+ const email = `${user_uuid}@`;
136
+ const nickname = "Guest";
137
+ const timezone = "UTC";
138
+ await this.db.createUser(user_uuid, email, nickname, timezone);
139
+ const user = {
140
+ uuid: user_uuid,
141
+ nickname: "Guest",
142
+ email: "guest",
143
+ timezone: "UTC",
144
+ guest_for_session: sessionUUID,
145
+ };
146
+ this.guests.set(user_uuid, user);
147
+ return user.uuid;
148
+ // throw new Error("unimpl");
149
+ // // return "";
150
+ }
151
+ const userData = await this.apiKeyManager.verifyApiKey(apiKey);
152
+ if (!userData) {
153
+ throw new errors_1.ChatFatalError("invalid api key");
154
+ }
155
+ return userData.uuid;
156
+ }
157
+ // IMessageProcessor<ClientToServer>
115
158
  async processMessage(connectionId, userId, message) {
116
159
  if ((0, messages_1.isClientControlMessage)(message)) {
117
160
  // handle connection level message
@@ -134,11 +177,11 @@ class SessionRegistry {
134
177
  async processClientControlMessage(connectionId, userId, message) {
135
178
  switch (message.type) {
136
179
  case "control_agent_profile_create":
137
- await this.handleAgentProfileCreate(message, userId);
180
+ await this.handleAgentProfileCreate(userId, message);
138
181
  break;
139
182
  case "control_agent_profile_delete":
140
- // TODO:
141
- throw new Error("not implemented yet");
183
+ await this.handleAgentProfileDelete(userId, message);
184
+ break;
142
185
  case "control_get_session_list":
143
186
  await this.handleGetSessionList(connectionId, userId, message);
144
187
  break;
@@ -173,6 +216,14 @@ class SessionRegistry {
173
216
  async handleGetSessionList(connectionId, userId, message) {
174
217
  try {
175
218
  const [userSessions, teamSessions, userAgents] = await this.getAllAgentsAndSessionsByUser(userId);
219
+ // Mark any guest session as a user session
220
+ const guest = this.guests.get(userId);
221
+ if (guest) {
222
+ const guestSession = await this.db.sessionGetDescriptorById(guest.guest_for_session);
223
+ if (guestSession) {
224
+ userSessions.push(guestSession);
225
+ }
226
+ }
176
227
  const response = {
177
228
  type: "control_session_list",
178
229
  user_sessions: userSessions,
@@ -196,11 +247,11 @@ class SessionRegistry {
196
247
  // validate the session access
197
248
  await this.validateSessionAccess(sessionId, userId, "owner");
198
249
  // get users/team-uuid before deletion
199
- const [users, teamUuid, sessionData] = await Promise.all([
250
+ const [users, sessionData] = await Promise.all([
200
251
  this.getSessionUsers(sessionId),
201
- this.db.sessionGetTeamUuid(sessionId),
202
- this.db.sessionGetById(sessionId),
252
+ this.db.sessionGetDescriptorById(sessionId),
203
253
  ]);
254
+ const teamUuid = sessionData?.team_uuid;
204
255
  if (!sessionData) {
205
256
  throw new errors_1.ChatFatalError(`No such session: ${sessionId}`);
206
257
  }
@@ -244,9 +295,10 @@ class SessionRegistry {
244
295
  const failedLookups = [];
245
296
  const validMemberIds = [];
246
297
  const participants = [];
247
- // filter out the owner and extract failed lookups
298
+ // No need to filter out the owner here, that is done in
299
+ // `createTeamWithParticipants`
248
300
  resolvedMemberIds.forEach((user, index) => {
249
- if (user && user.uuid !== userId) {
301
+ if (user) {
250
302
  validMemberIds.push(user.uuid);
251
303
  participants.push({
252
304
  user_uuid: user.uuid,
@@ -255,23 +307,44 @@ class SessionRegistry {
255
307
  role: "participant",
256
308
  });
257
309
  }
258
- else if (user === undefined) {
310
+ else {
259
311
  failedLookups.push(message.initial_members[index]);
260
312
  }
261
313
  });
262
314
  // Create the team with initial participants
263
315
  const teamUuid = await this.db.createTeamWithParticipants(message.team_name, userId, validMemberIds);
316
+ // Create a default agent for the new team
317
+ const defaultAgentProfile = await this.db.createAgentProfile(undefined, teamUuid, sdk_1.DEFAULT_AGENT_PROFILE_NAME, sdk_1.DEFAULT_AGENT_PROFILE);
318
+ if (!defaultAgentProfile) {
319
+ throw new Error(`Failed to create default agent profile for team ${teamUuid}`);
320
+ }
321
+ // Get owner's information and add to participants for the response
322
+ const ownerInfo = await this.db.getUserFromUuid(userId);
323
+ if (!ownerInfo) {
324
+ throw new Error(`Cannot find owner user data for user ${userId}`);
325
+ }
326
+ const ownerParticipant = {
327
+ user_uuid: userId,
328
+ nickname: ownerInfo.nickname || "",
329
+ email: ownerInfo.email,
330
+ role: "owner",
331
+ };
264
332
  const response = {
265
333
  type: "control_team_created",
266
334
  team_uuid: teamUuid,
267
335
  team_owner_uuid: userId,
268
336
  team_name: message.team_name,
269
- members: participants,
337
+ members: [ownerParticipant, ...participants],
270
338
  failed_lookups: failedLookups,
271
339
  };
272
340
  const members = new Set(validMemberIds);
273
341
  members.add(userId);
274
342
  this.connectionManager.sendToUsers(members, response);
343
+ // Broadcast the default agent profile to all team members
344
+ this.connectionManager.sendToUsers(members, {
345
+ type: "control_agent_profile_created",
346
+ profile: defaultAgentProfile,
347
+ });
275
348
  }
276
349
  catch (error) {
277
350
  logger.error(`[SessionRegistry] Error creating team: ${String(error)}`);
@@ -338,6 +411,13 @@ class SessionRegistry {
338
411
  });
339
412
  }
340
413
  }
414
+ // Notify all team members about the updated member list
415
+ const updatedParticipants = await this.db.teamGetMembers(message.target_team_id);
416
+ this.connectionManager.sendToUsers(new Set(updatedParticipants.map((p) => p.user_uuid)), {
417
+ type: "control_team_members_updated",
418
+ team_uuid: message.target_team_id,
419
+ members: updatedParticipants,
420
+ });
341
421
  logger.info(`[SessionRegistry] User ${user.uuid}` +
342
422
  `added to team ${message.target_team_id}`);
343
423
  }
@@ -388,6 +468,13 @@ class SessionRegistry {
388
468
  this.removeUserFromSessionMemory(user.uuid, sessionData.session_uuid);
389
469
  }
390
470
  }
471
+ // Notify all remaining team members about the updated member list
472
+ const updatedParticipants = await this.db.teamGetMembers(message.target_team_id);
473
+ this.connectionManager.sendToUsers(new Set(updatedParticipants.map((p) => p.user_uuid)), {
474
+ type: "control_team_members_updated",
475
+ team_uuid: message.target_team_id,
476
+ members: updatedParticipants,
477
+ });
391
478
  logger.info(`[SessionRegistry] User ${user.uuid} ` +
392
479
  `removed from team ${message.target_team_id}`);
393
480
  }
@@ -421,7 +508,7 @@ class SessionRegistry {
421
508
  // Validate session access permissions
422
509
  const access = await this.validateSessionAccess(sessionId, userId);
423
510
  if (!access) {
424
- throw new errors_1.ChatFatalError(`User ${userId} is not authorized to ` + `join session ${sessionId}`);
511
+ throw new errors_1.ChatFatalError(`User ${userId} is not authorized to join session ${sessionId}`);
425
512
  }
426
513
  // get or create the session
427
514
  const session = await this.getAndActivateSession(sessionId);
@@ -430,6 +517,15 @@ class SessionRegistry {
430
517
  // since we have validated the access
431
518
  throw new errors_1.ChatFatalError(`Server internal error: ` + `failed to load session ${sessionId}`);
432
519
  }
520
+ const guest = this.guests.get(userId);
521
+ if (guest) {
522
+ session.addParticipant(userId, {
523
+ user_uuid: guest.uuid,
524
+ nickname: guest.nickname || "Guest",
525
+ email: guest.email,
526
+ role: "participant",
527
+ });
528
+ }
433
529
  // To this point, there should be no error thrown.
434
530
  // Update in-memory session-user/user-session mappings
435
531
  this.addUserToSessionMemory(userId, sessionId);
@@ -445,7 +541,7 @@ class SessionRegistry {
445
541
  this.sendControlError(connectionId, message.client_message_id, String(error));
446
542
  }
447
543
  }
448
- async handleAgentProfileCreate(message, from) {
544
+ async handleAgentProfileCreate(userId, message) {
449
545
  // get agent profile from template
450
546
  let agentProfileFromTemplate = undefined;
451
547
  if (message.template_name) {
@@ -455,11 +551,8 @@ class SessionRegistry {
455
551
  }
456
552
  agentProfileFromTemplate = template.agent_profile;
457
553
  }
458
- const newAgentProfile = agentProfileFromTemplate || {
459
- model: agentUtils_1.DEFAULT_LLM_MODEL,
460
- system_prompt: sdk_1.DEFAULT_AGENT_PROFILE_SYSTEM_PROMPT,
461
- mcp_settings: {},
462
- };
554
+ const newAgentProfile = agentProfileFromTemplate ||
555
+ new sdk_1.AgentProfile(sdk_1.DEFAULT_AGENT_PROFILE.model, sdk_1.DEFAULT_AGENT_PROFILE.system_prompt, sdk_1.DEFAULT_AGENT_PROFILE.mcp_settings);
463
556
  const team_uuid = message.team_uuid || undefined;
464
557
  if (team_uuid) {
465
558
  // TODO: should be able to reconstruct the full SavedAgentProfile in one
@@ -474,17 +567,49 @@ class SessionRegistry {
474
567
  return savedAgentProfile.uuid;
475
568
  }
476
569
  // User AgentProfile
477
- const savedAgentProfile = await this.db.createAgentProfile(from, undefined, message.title, newAgentProfile);
570
+ const savedAgentProfile = await this.db.createAgentProfile(userId, undefined, message.title, newAgentProfile);
478
571
  if (!savedAgentProfile) {
479
572
  throw new Error("failed creating agent profile (createAgentProfile)");
480
573
  }
481
574
  // Send the new AgentProfile to the user
482
- this.connectionManager.sendToUsers(new Set([from]), {
575
+ this.connectionManager.sendToUsers(new Set([userId]), {
483
576
  type: "control_agent_profile_created",
484
577
  profile: savedAgentProfile,
485
578
  });
486
579
  return savedAgentProfile.uuid;
487
580
  }
581
+ async handleAgentProfileDelete(userId, message) {
582
+ const agentProfileUuid = message.agent_profile_uuid;
583
+ // Get the agent profile to determine if it's a team or user profile
584
+ const agentProfile = await this.db.getSavedAgentProfileById(agentProfileUuid);
585
+ if (!agentProfile) {
586
+ throw new Error(`Agent profile ${agentProfileUuid} not found`);
587
+ }
588
+ // Validate team access: user must own the profile or be in the team
589
+ // TODO: Only allow the owner to delete the profile?
590
+ if (agentProfile.team_uuid) {
591
+ const hasAccess = await this.validateTeamAccess(agentProfile.team_uuid, userId);
592
+ if (!hasAccess) {
593
+ throw new Error(`User ${userId} is not authorized to delete this agent profile`);
594
+ }
595
+ }
596
+ // Delete the agent profile from database
597
+ await this.db.deleteAgentProfile(agentProfileUuid);
598
+ // Send confirmation to all relevant users
599
+ if (agentProfile.team_uuid) {
600
+ const participants = await this.db.teamGetMembers(agentProfile.team_uuid);
601
+ this.connectionManager.sendToUsers(new Set(participants.map((p) => p.user_uuid)), {
602
+ type: "control_agent_profile_deleted",
603
+ profile_uuid: agentProfileUuid,
604
+ });
605
+ }
606
+ else {
607
+ this.connectionManager.sendToUsers(new Set([userId]), {
608
+ type: "control_agent_profile_deleted",
609
+ profile_uuid: agentProfileUuid,
610
+ });
611
+ }
612
+ }
488
613
  /**
489
614
  * Create a new session for a user.
490
615
  * - create session in database (via `newSession`)
@@ -497,12 +622,12 @@ class SessionRegistry {
497
622
  if (!message.agent_profile_id) {
498
623
  logger.info("[handleSessionCreate] creating new AgentProfile");
499
624
  // Create AgentProfile and inform the client
500
- message.agent_profile_id = await this.handleAgentProfileCreate({
625
+ message.agent_profile_id = await this.handleAgentProfileCreate(fromUserId, {
501
626
  type: "control_agent_profile_create",
502
627
  title: "New Agent " + (0, uuid_1.v4)(),
503
628
  user_uuid: fromUserId,
504
629
  team_uuid: message.team_id,
505
- }, fromUserId);
630
+ });
506
631
  }
507
632
  // Create new session and get its instance
508
633
  const { openSession, sessionId } = message.team_id
@@ -531,7 +656,7 @@ class SessionRegistry {
531
656
  // validate the agent profile
532
657
  await this.validateSavedAgentProfile(agentProfileId);
533
658
  const newSessionData = {
534
- ...userSessionDataCreate(ownerId, title, agentProfileId),
659
+ ...userSessionCreateData(ownerId, title, agentProfileId),
535
660
  updated_at: new Date().toISOString(),
536
661
  };
537
662
  const openSession = await openSession_1.OpenSession.initWithEmptySession(this.db, newSessionData, this.llmUrl, this.xmcpUrl, this.connectionManager);
@@ -550,7 +675,7 @@ class SessionRegistry {
550
675
  throw new errors_1.ChatFatalError(`User ${fromUserId} is not a participant of team ${teamId}`);
551
676
  }
552
677
  const newSessionData = {
553
- ...teamSessionDataCreate(teamId, fromUserId, title, agentProfileId),
678
+ ...teamSessionCreateData(teamId, fromUserId, title, agentProfileId),
554
679
  updated_at: new Date().toISOString(),
555
680
  };
556
681
  // initialize the open session
@@ -589,8 +714,24 @@ class SessionRegistry {
589
714
  * Handle user disconnect - clean up from all sessions.
590
715
  * Called when a connection is closed to ensure proper cleanup.
591
716
  */
592
- handleUserDisconnect(userId) {
717
+ async handleUserDisconnect(userId) {
593
718
  logger.info(`[SessionRegistry] Handling disconnect for user ${userId}`);
719
+ // If the user is a guest, remove his DB entry.
720
+ const guest = this.guests.get(userId);
721
+ if (guest) {
722
+ const session = this.openSessions.get(guest.guest_for_session);
723
+ if (session) {
724
+ session.removeParticipant(userId);
725
+ }
726
+ this.guests.delete(userId);
727
+ try {
728
+ logger.info(`[SessionRegistry.handleUserDisconnect] deleting user ${userId}`);
729
+ await this.db.deleteUser(userId);
730
+ }
731
+ catch (e) {
732
+ logger.warn(`error removing guest ${userId}: ${(0, errorUtils_1.getErrorString)(e)}`);
733
+ }
734
+ }
594
735
  // Get all sessions the user is in (copy to avoid concurrent modification)
595
736
  const userSessionIds = this.getInMemoryUserSessions(userId);
596
737
  const sessionsToLeave = Array.from(userSessionIds);
@@ -607,11 +748,12 @@ class SessionRegistry {
607
748
  * This will also create a default agent profile if none exists.
608
749
  */
609
750
  async getAllAgentsAndSessionsByUser(userId) {
610
- return Promise.all([
751
+ const [userSessions, teamInfos, userAgents] = await Promise.all([
611
752
  this.db.getUserSessions(userId),
612
753
  this.db.getTeamInfosByUser(userId),
613
754
  this.agentProfilesGetByUserOrDefault(userId),
614
755
  ]);
756
+ return [userSessions, teamInfos, userAgents];
615
757
  }
616
758
  async agentProfilesGetByUserOrDefault(userId) {
617
759
  const agentProfiles = await this.db.agentProfilesGetByUser(userId);
@@ -645,6 +787,12 @@ class SessionRegistry {
645
787
  const participants = session.getParticipants();
646
788
  const role = participants.get(userId);
647
789
  if (!role) {
790
+ // Check for guest access
791
+ const guest = this.guests.get(userId);
792
+ if (guest && guest.guest_for_session === sessionId) {
793
+ return !accessType || accessType === "participant";
794
+ }
795
+ // Not authorized to join the session
648
796
  return false;
649
797
  }
650
798
  else {
@@ -656,6 +804,12 @@ class SessionRegistry {
656
804
  const participants = await this.db.sessionGetParticipants(sessionId);
657
805
  const role = participants.find((p) => p.user_uuid === userId)?.role;
658
806
  if (!role) {
807
+ // Check for guest access
808
+ const guest = this.guests.get(userId);
809
+ logger.info(`[validateSessionAccess] guest: ${JSON.stringify(guest)}`);
810
+ if (guest && guest.guest_for_session === sessionId) {
811
+ return !accessType || accessType === "participant";
812
+ }
659
813
  return false;
660
814
  }
661
815
  else {
@@ -676,25 +830,38 @@ class SessionRegistry {
676
830
  return !accessType || role === accessType;
677
831
  }
678
832
  }
833
+ /**
834
+ * Read from DB if the session is not active
835
+ */
836
+ async getSessionDescriptor(sessionId) {
837
+ const session = this.openSessions.get(sessionId);
838
+ if (session) {
839
+ return session.getDescriptor();
840
+ }
841
+ else {
842
+ // fetch the session from database
843
+ return this.db.sessionGetDescriptorById(sessionId);
844
+ }
845
+ }
679
846
  }
680
847
  exports.SessionRegistry = SessionRegistry;
681
- function userSessionDataCreate(ownerId, title, agentProfileId) {
848
+ function userSessionCreateData(ownerId, title, agentProfileId) {
682
849
  return {
683
850
  session_uuid: database_1.Database.sessionNewUUID(),
684
851
  title,
685
852
  team_uuid: undefined,
686
853
  agent_profile_uuid: agentProfileId,
687
- workspace: undefined,
688
854
  user_uuid: ownerId,
855
+ agent_paused: false,
689
856
  };
690
857
  }
691
- function teamSessionDataCreate(teamId, ownerId, title, agentProfileId) {
858
+ function teamSessionCreateData(teamId, ownerId, title, agentProfileId) {
692
859
  return {
693
860
  session_uuid: database_1.Database.sessionNewUUID(),
694
861
  title,
695
862
  team_uuid: teamId,
696
863
  agent_profile_uuid: agentProfileId,
697
- workspace: undefined,
698
864
  user_uuid: ownerId,
865
+ agent_paused: false,
699
866
  };
700
867
  }
@@ -102,13 +102,11 @@ function createMockUserConnectionManager() {
102
102
  sendToUsers: vitest_1.vi.fn(),
103
103
  sendToConnection: vitest_1.vi.fn(),
104
104
  sendServerError: vitest_1.vi.fn(),
105
- getLiveUserApiKey: vitest_1.vi.fn(),
106
105
  };
107
106
  const mock = {
108
107
  sendToUsers: spies.sendToUsers,
109
108
  sendToConnection: spies.sendToConnection,
110
109
  sendServerError: spies.sendServerError,
111
- getLiveUserApiKey: spies.getLiveUserApiKey,
112
110
  };
113
111
  return { mock, spies };
114
112
  }
@@ -117,10 +115,18 @@ function createMockUserConnectionManager() {
117
115
  */
118
116
  function createMockSessionRegistry() {
119
117
  const spies = {
118
+ authenticate: vitest_1.vi.fn().mockImplementation((apiKey) => {
119
+ if (apiKey === "valid-api-key")
120
+ return Promise.resolve(exports.MOCK_USERS.owner.uuid);
121
+ if (apiKey === "participant-api-key")
122
+ return Promise.resolve(exports.MOCK_USERS.participant.uuid);
123
+ return Promise.resolve(null);
124
+ }),
120
125
  processMessage: vitest_1.vi.fn(),
121
126
  handleUserDisconnect: vitest_1.vi.fn(),
122
127
  };
123
128
  const mock = {
129
+ authenticate: spies.authenticate,
124
130
  processMessage: spies.processMessage,
125
131
  handleUserDisconnect: spies.handleUserDisconnect,
126
132
  };
@@ -189,6 +195,7 @@ function createMockSessionList() {
189
195
  workspace: undefined,
190
196
  updated_at: exports.MOCK_SESSIONS.active.updated_at || "",
191
197
  user_uuid: exports.MOCK_SESSIONS.active.user_uuid,
198
+ agent_paused: false,
192
199
  },
193
200
  {
194
201
  session_uuid: exports.MOCK_SESSIONS.secondary.uuid,
@@ -198,6 +205,7 @@ function createMockSessionList() {
198
205
  workspace: undefined,
199
206
  updated_at: exports.MOCK_SESSIONS.secondary.updated_at || "",
200
207
  user_uuid: exports.MOCK_SESSIONS.secondary.user_uuid,
208
+ agent_paused: false,
201
209
  },
202
210
  ];
203
211
  }
@@ -295,13 +303,6 @@ function setupStandardMockBehaviors(mocks) {
295
303
  });
296
304
  }
297
305
  // Setup user connection manager mocks
298
- if (mocks.userConnectionManager) {
299
- mocks.userConnectionManager.spies.getLiveUserApiKey.mockImplementation((userId) => {
300
- if (userId === exports.MOCK_USERS.owner.uuid)
301
- return "valid-api-key";
302
- if (userId === exports.MOCK_USERS.participant.uuid)
303
- return "participant-api-key";
304
- return undefined;
305
- });
306
- }
306
+ // if (mocks.userConnectionManager) {
307
+ // }
307
308
  }
@@ -17,23 +17,34 @@ const expr_eval_1 = require("expr-eval");
17
17
  const htmlToText_1 = require("../utils/htmlToText");
18
18
  const sdk_1 = require("@xalia/xmcp/sdk");
19
19
  const search_1 = require("../utils/search");
20
+ const sessionFileManager_1 = require("./sessionFileManager");
21
+ const imageGeneratorTools_1 = require("./imageGeneratorTools");
20
22
  const logger = (0, sdk_1.getLogger)();
21
23
  /**
22
24
  * Returns a function which parses an `args` struct and attempts to extract
23
25
  * multiple string parameters with the given names. e.g.
24
26
  *
25
- * const parseFn = makeParseArgsFn(["arg0", "arg1"] as const)
27
+ * const parseFn = makeParseArgsFn(
28
+ * ["arg0", "arg1"] as const,
29
+ * ["opt0"] as const)
26
30
  *
27
- * creates `parseFn: (args: unknown) => { arg0: string, arg1: string }` which
28
- * can be used to parse tool arguments.
31
+ * creates
32
+ *
33
+ * parseFn: (args: unknown) => {
34
+ * arg0: string,
35
+ * arg1: string,
36
+ * opt0: string|undefined
37
+ * }
38
+ *
39
+ * which can be used to parse tool arguments.
29
40
  *
30
41
  * NOTE, the complex type parameters ensures that the name list is a
31
42
  * compile-time value, which in turn ensures that the return value of this
32
43
  * function is well-typed.
33
44
  */
34
- function makeParseArgsFn(names) {
45
+ function makeParseArgsFn(names, optNames) {
35
46
  return (args) => {
36
- if (typeof args !== "object") {
47
+ if (!args || typeof args !== "object") {
37
48
  throw new Error(`invalid args: ${typeof args}`);
38
49
  }
39
50
  const argsObj = args;
@@ -43,6 +54,14 @@ function makeParseArgsFn(names) {
43
54
  throw new Error(`invalid expr args.${name}: ${typeof val}`);
44
55
  }
45
56
  }
57
+ if (optNames) {
58
+ for (const name of optNames) {
59
+ const val = argsObj[name];
60
+ if (typeof val !== "undefined" && typeof val !== "string") {
61
+ throw new Error(`invalid expr args.${name}: ${typeof val}`);
62
+ }
63
+ }
64
+ }
46
65
  return argsObj;
47
66
  };
48
67
  }
@@ -234,10 +253,12 @@ function openURLTool() {
234
253
  /**
235
254
  * Add a set of agent tools for chat sessions.
236
255
  */
237
- async function addDefaultChatTools(agent, timezone, platform) {
256
+ async function addDefaultChatTools(agent, timezone, platform, fileManager, llmUrl, llmApiKey) {
238
257
  await agent.addAgentToolProvider(datetimeTool(timezone));
239
258
  await agent.addAgentToolProvider(exports.calculatorTool);
240
259
  await agent.addAgentToolProvider(renderTool(platform));
241
260
  await agent.addAgentToolProvider(webSearchTool());
242
261
  await agent.addAgentToolProvider(openURLTool());
262
+ await agent.addAgentToolProvider((0, sessionFileManager_1.fileManagerTool)(fileManager));
263
+ await agent.addAgentToolProvider(await (0, imageGeneratorTools_1.genImageFileTool)(llmUrl, llmApiKey, fileManager));
243
264
  }
@@ -4,6 +4,7 @@ exports.MultiAsyncQueue = void 0;
4
4
  class MultiAsyncQueue {
5
5
  constructor(process, maxBacklog = 100) {
6
6
  this.running = false;
7
+ this.paused = false;
7
8
  this.queue = [];
8
9
  this.process = process;
9
10
  this.maxBacklog = maxBacklog;
@@ -22,8 +23,15 @@ class MultiAsyncQueue {
22
23
  setTimeout(() => void this.tryNext(), 0);
23
24
  return true;
24
25
  }
26
+ pause() {
27
+ this.paused = true;
28
+ }
29
+ unpause() {
30
+ this.paused = false;
31
+ setTimeout(() => void this.tryNext(), 0);
32
+ }
25
33
  async tryNext() {
26
- if (this.running) {
34
+ if (this.running || this.paused) {
27
35
  return;
28
36
  }
29
37
  if (this.queue.length > 0) {
@@ -57,7 +57,10 @@ function createCallTestToolScript(tool_call_ids, param1, param2) {
57
57
  const toolFn = async (_, args) => {
58
58
  const { param1, param2 } = args;
59
59
  return new Promise((r) => {
60
- r({ response: `tool_result: '${param1}' '${String(param2)}'` });
60
+ r({
61
+ response: `tool_result: '${param1}' '${String(param2)}'`,
62
+ metadata: { type: "text/plain" },
63
+ });
61
64
  });
62
65
  };
63
66
  // A script that uses the tool
@@ -103,11 +106,12 @@ function createCallTestToolScript(tool_call_ids, param1, param2) {
103
106
  content: `tool_result: '${param1}' '${String(param2)}'`,
104
107
  role: "tool",
105
108
  tool_call_id: t_id,
109
+ metadata: { type: "text/plain" },
106
110
  };
107
111
  });
108
112
  return {
109
113
  script,
110
- expectCompletions: script.map((s) => s.message),
114
+ expectCompletions: script.map((s) => (0, agent_1.completionToAssistantMessageParam)(s.message)),
111
115
  expectAgentMessages,
112
116
  expectToolResults,
113
117
  toolDescriptor,
@@ -141,9 +145,9 @@ describe("Agent", () => {
141
145
  (0, vitest_1.expect)(eventHandler.getToolCallResults()).eql(expectToolResults);
142
146
  // Check the ordering
143
147
  const allExpect = [
144
- script[0].message,
148
+ (0, agent_1.completionToAssistantMessageParam)(script[0].message),
145
149
  ...expectToolResults,
146
- script[1].message,
150
+ (0, agent_1.completionToAssistantMessageParam)(script[1].message),
147
151
  ];
148
152
  const all = eventHandler.getAll();
149
153
  (0, vitest_1.expect)(all).eql(allExpect);
@@ -213,9 +217,9 @@ describe("Agent", () => {
213
217
  // Check the event handler was called with all completions and tool
214
218
  // results, in the correct order.
215
219
  const allExpect = [
216
- script[0].message,
220
+ (0, agent_1.completionToAssistantMessageParam)(script[0].message),
217
221
  ...expectToolResults,
218
- script[1].message,
222
+ (0, agent_1.completionToAssistantMessageParam)(script[1].message),
219
223
  ];
220
224
  const all = eventHandler.getAll();
221
225
  (0, vitest_1.expect)(all).eql(allExpect);
@@ -264,14 +268,14 @@ describe("Agent", () => {
264
268
  };
265
269
  };
266
270
  (0, assert_1.strict)(scriptCopy[0].message.tool_calls);
267
- const transformedFirstMsg = {
271
+ const transformedFirstMsg = (0, agent_1.completionToAssistantMessageParam)({
268
272
  ...scriptCopy[0].message,
269
273
  tool_calls: scriptCopy[0].message.tool_calls.map(transformToolCall),
270
- };
274
+ });
271
275
  const allExpect = [
272
276
  transformedFirstMsg,
273
277
  ...expectToolResults,
274
- scriptCopy[1].message,
278
+ (0, agent_1.completionToAssistantMessageParam)(scriptCopy[1].message),
275
279
  ];
276
280
  const all = eventHandler.getAll();
277
281
  const conv = agent.getConversation();
@@ -311,9 +315,9 @@ describe("Agent", () => {
311
315
  };
312
316
  });
313
317
  const allExpect = [
314
- script[0].message,
318
+ (0, agent_1.completionToAssistantMessageParam)(script[0].message),
315
319
  ...expectToolCallResults,
316
- script[1].message,
320
+ (0, agent_1.completionToAssistantMessageParam)(script[1].message),
317
321
  ];
318
322
  const all = eventHandler.getAll();
319
323
  const conv = agent.getConversation();