@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,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
  }
@@ -398,15 +485,16 @@ class SessionRegistry {
398
485
  async getAndActivateSession(sessionId) {
399
486
  if (this.openSessions.has(sessionId)) {
400
487
  logger.info(`[SessionRegistry] Session ${sessionId} already exists`);
401
- const openSession = this.openSessions.get(sessionId);
402
- if (!openSession) {
488
+ const session = this.openSessions.get(sessionId);
489
+ if (!session) {
403
490
  throw new errors_1.ChatFatalError(`Internal error: No such session: ${sessionId}`);
404
491
  }
405
- return openSession;
492
+ return { session, isNew: false };
406
493
  }
407
494
  else {
408
495
  logger.info(`[SessionRegistry] loading session ${sessionId}`);
409
- return openSession_1.OpenSession.initWithExistingSession(this.db, sessionId, this.llmUrl, this.xmcpUrl, this.connectionManager);
496
+ const session = await openSession_1.OpenSession.initWithExistingSession(this.db, sessionId, this.llmUrl, this.xmcpUrl, this.connectionManager);
497
+ return { session, isNew: true };
410
498
  }
411
499
  }
412
500
  /**
@@ -421,15 +509,25 @@ class SessionRegistry {
421
509
  // Validate session access permissions
422
510
  const access = await this.validateSessionAccess(sessionId, userId);
423
511
  if (!access) {
424
- throw new errors_1.ChatFatalError(`User ${userId} is not authorized to ` + `join session ${sessionId}`);
512
+ throw new errors_1.ChatFatalError(`User ${userId} is not authorized to join session ${sessionId}`);
425
513
  }
426
514
  // get or create the session
427
- const session = await this.getAndActivateSession(sessionId);
428
- if (!session) {
515
+ const sessionInfo = await this.getAndActivateSession(sessionId);
516
+ if (!sessionInfo) {
429
517
  // this in theory should not happen
430
518
  // since we have validated the access
431
519
  throw new errors_1.ChatFatalError(`Server internal error: ` + `failed to load session ${sessionId}`);
432
520
  }
521
+ const { session, isNew } = sessionInfo;
522
+ const guest = this.guests.get(userId);
523
+ if (guest) {
524
+ session.addParticipant(userId, {
525
+ user_uuid: guest.uuid,
526
+ nickname: guest.nickname || "Guest",
527
+ email: guest.email,
528
+ role: "participant",
529
+ });
530
+ }
433
531
  // To this point, there should be no error thrown.
434
532
  // Update in-memory session-user/user-session mappings
435
533
  this.addUserToSessionMemory(userId, sessionId);
@@ -438,14 +536,14 @@ class SessionRegistry {
438
536
  this.openSessions.set(sessionId, session);
439
537
  }
440
538
  // pass the message to the session to handle the rest
441
- session.sendSessionData(connectionId, message.client_message_id);
539
+ await session.sendSessionData(connectionId, message.client_message_id, isNew);
442
540
  }
443
541
  catch (error) {
444
542
  logger.error(`[SessionRegistry] Error handling user join: ${String(error)}`);
445
543
  this.sendControlError(connectionId, message.client_message_id, String(error));
446
544
  }
447
545
  }
448
- async handleAgentProfileCreate(message, from) {
546
+ async handleAgentProfileCreate(userId, message) {
449
547
  // get agent profile from template
450
548
  let agentProfileFromTemplate = undefined;
451
549
  if (message.template_name) {
@@ -455,11 +553,8 @@ class SessionRegistry {
455
553
  }
456
554
  agentProfileFromTemplate = template.agent_profile;
457
555
  }
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
- };
556
+ const newAgentProfile = agentProfileFromTemplate ||
557
+ 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
558
  const team_uuid = message.team_uuid || undefined;
464
559
  if (team_uuid) {
465
560
  // TODO: should be able to reconstruct the full SavedAgentProfile in one
@@ -474,17 +569,49 @@ class SessionRegistry {
474
569
  return savedAgentProfile.uuid;
475
570
  }
476
571
  // User AgentProfile
477
- const savedAgentProfile = await this.db.createAgentProfile(from, undefined, message.title, newAgentProfile);
572
+ const savedAgentProfile = await this.db.createAgentProfile(userId, undefined, message.title, newAgentProfile);
478
573
  if (!savedAgentProfile) {
479
574
  throw new Error("failed creating agent profile (createAgentProfile)");
480
575
  }
481
576
  // Send the new AgentProfile to the user
482
- this.connectionManager.sendToUsers(new Set([from]), {
577
+ this.connectionManager.sendToUsers(new Set([userId]), {
483
578
  type: "control_agent_profile_created",
484
579
  profile: savedAgentProfile,
485
580
  });
486
581
  return savedAgentProfile.uuid;
487
582
  }
583
+ async handleAgentProfileDelete(userId, message) {
584
+ const agentProfileUuid = message.agent_profile_uuid;
585
+ // Get the agent profile to determine if it's a team or user profile
586
+ const agentProfile = await this.db.getSavedAgentProfileById(agentProfileUuid);
587
+ if (!agentProfile) {
588
+ throw new Error(`Agent profile ${agentProfileUuid} not found`);
589
+ }
590
+ // Validate team access: user must own the profile or be in the team
591
+ // TODO: Only allow the owner to delete the profile?
592
+ if (agentProfile.team_uuid) {
593
+ const hasAccess = await this.validateTeamAccess(agentProfile.team_uuid, userId);
594
+ if (!hasAccess) {
595
+ throw new Error(`User ${userId} is not authorized to delete this agent profile`);
596
+ }
597
+ }
598
+ // Delete the agent profile from database
599
+ await this.db.deleteAgentProfile(agentProfileUuid);
600
+ // Send confirmation to all relevant users
601
+ if (agentProfile.team_uuid) {
602
+ const participants = await this.db.teamGetMembers(agentProfile.team_uuid);
603
+ this.connectionManager.sendToUsers(new Set(participants.map((p) => p.user_uuid)), {
604
+ type: "control_agent_profile_deleted",
605
+ profile_uuid: agentProfileUuid,
606
+ });
607
+ }
608
+ else {
609
+ this.connectionManager.sendToUsers(new Set([userId]), {
610
+ type: "control_agent_profile_deleted",
611
+ profile_uuid: agentProfileUuid,
612
+ });
613
+ }
614
+ }
488
615
  /**
489
616
  * Create a new session for a user.
490
617
  * - create session in database (via `newSession`)
@@ -497,12 +624,12 @@ class SessionRegistry {
497
624
  if (!message.agent_profile_id) {
498
625
  logger.info("[handleSessionCreate] creating new AgentProfile");
499
626
  // Create AgentProfile and inform the client
500
- message.agent_profile_id = await this.handleAgentProfileCreate({
627
+ message.agent_profile_id = await this.handleAgentProfileCreate(fromUserId, {
501
628
  type: "control_agent_profile_create",
502
629
  title: "New Agent " + (0, uuid_1.v4)(),
503
630
  user_uuid: fromUserId,
504
631
  team_uuid: message.team_id,
505
- }, fromUserId);
632
+ });
506
633
  }
507
634
  // Create new session and get its instance
508
635
  const { openSession, sessionId } = message.team_id
@@ -513,8 +640,9 @@ class SessionRegistry {
513
640
  this.openSessions.set(sessionId, openSession);
514
641
  // add owner to session memory
515
642
  this.addUserToSessionMemory(fromUserId, sessionId);
516
- // send session info to the connection
517
- openSession.sendSessionData(connectionId, message.client_message_id);
643
+ // send session info to the connection. It has just been created so we
644
+ // must also restore the mcp servers.
645
+ await openSession.sendSessionData(connectionId, message.client_message_id, true);
518
646
  logger.info(`[SessionRegistry] new session ${sessionId}:` +
519
647
  ` ${message.title} for ${fromUserId}`);
520
648
  }
@@ -531,7 +659,7 @@ class SessionRegistry {
531
659
  // validate the agent profile
532
660
  await this.validateSavedAgentProfile(agentProfileId);
533
661
  const newSessionData = {
534
- ...userSessionDataCreate(ownerId, title, agentProfileId),
662
+ ...userSessionCreateData(ownerId, title, agentProfileId),
535
663
  updated_at: new Date().toISOString(),
536
664
  };
537
665
  const openSession = await openSession_1.OpenSession.initWithEmptySession(this.db, newSessionData, this.llmUrl, this.xmcpUrl, this.connectionManager);
@@ -542,15 +670,16 @@ class SessionRegistry {
542
670
  */
543
671
  async newTeamSession(fromUserId, teamId, title, agentProfileId) {
544
672
  // validate agent profile and team access
545
- const [_savedAgentProfile, access] = await Promise.all([
673
+ const [_savedAgentProfile, access, participants] = await Promise.all([
546
674
  this.validateSavedAgentProfile(agentProfileId),
547
675
  this.validateTeamAccess(teamId, fromUserId),
676
+ this.db.teamGetMembers(teamId),
548
677
  ]);
549
678
  if (!access) {
550
679
  throw new errors_1.ChatFatalError(`User ${fromUserId} is not a participant of team ${teamId}`);
551
680
  }
552
681
  const newSessionData = {
553
- ...teamSessionDataCreate(teamId, fromUserId, title, agentProfileId),
682
+ ...teamSessionCreateData(teamId, participants, fromUserId, title, agentProfileId),
554
683
  updated_at: new Date().toISOString(),
555
684
  };
556
685
  // initialize the open session
@@ -589,8 +718,24 @@ class SessionRegistry {
589
718
  * Handle user disconnect - clean up from all sessions.
590
719
  * Called when a connection is closed to ensure proper cleanup.
591
720
  */
592
- handleUserDisconnect(userId) {
721
+ async handleUserDisconnect(userId) {
593
722
  logger.info(`[SessionRegistry] Handling disconnect for user ${userId}`);
723
+ // If the user is a guest, remove his DB entry.
724
+ const guest = this.guests.get(userId);
725
+ if (guest) {
726
+ const session = this.openSessions.get(guest.guest_for_session);
727
+ if (session) {
728
+ session.removeParticipant(userId);
729
+ }
730
+ this.guests.delete(userId);
731
+ try {
732
+ logger.info(`[SessionRegistry.handleUserDisconnect] deleting user ${userId}`);
733
+ await this.db.deleteUser(userId);
734
+ }
735
+ catch (e) {
736
+ logger.warn(`error removing guest ${userId}: ${(0, errorUtils_1.getErrorString)(e)}`);
737
+ }
738
+ }
594
739
  // Get all sessions the user is in (copy to avoid concurrent modification)
595
740
  const userSessionIds = this.getInMemoryUserSessions(userId);
596
741
  const sessionsToLeave = Array.from(userSessionIds);
@@ -607,11 +752,12 @@ class SessionRegistry {
607
752
  * This will also create a default agent profile if none exists.
608
753
  */
609
754
  async getAllAgentsAndSessionsByUser(userId) {
610
- return Promise.all([
755
+ const [userSessions, teamInfos, userAgents] = await Promise.all([
611
756
  this.db.getUserSessions(userId),
612
757
  this.db.getTeamInfosByUser(userId),
613
758
  this.agentProfilesGetByUserOrDefault(userId),
614
759
  ]);
760
+ return [userSessions, teamInfos, userAgents];
615
761
  }
616
762
  async agentProfilesGetByUserOrDefault(userId) {
617
763
  const agentProfiles = await this.db.agentProfilesGetByUser(userId);
@@ -645,6 +791,12 @@ class SessionRegistry {
645
791
  const participants = session.getParticipants();
646
792
  const role = participants.get(userId);
647
793
  if (!role) {
794
+ // Check for guest access
795
+ const guest = this.guests.get(userId);
796
+ if (guest && guest.guest_for_session === sessionId) {
797
+ return !accessType || accessType === "participant";
798
+ }
799
+ // Not authorized to join the session
648
800
  return false;
649
801
  }
650
802
  else {
@@ -656,6 +808,12 @@ class SessionRegistry {
656
808
  const participants = await this.db.sessionGetParticipants(sessionId);
657
809
  const role = participants.find((p) => p.user_uuid === userId)?.role;
658
810
  if (!role) {
811
+ // Check for guest access
812
+ const guest = this.guests.get(userId);
813
+ logger.info(`[validateSessionAccess] guest: ${JSON.stringify(guest)}`);
814
+ if (guest && guest.guest_for_session === sessionId) {
815
+ return !accessType || accessType === "participant";
816
+ }
659
817
  return false;
660
818
  }
661
819
  else {
@@ -676,25 +834,39 @@ class SessionRegistry {
676
834
  return !accessType || role === accessType;
677
835
  }
678
836
  }
837
+ /**
838
+ * Read from DB if the session is not active
839
+ */
840
+ async getSessionDescriptor(sessionId) {
841
+ const session = this.openSessions.get(sessionId);
842
+ if (session) {
843
+ return session.getDescriptor();
844
+ }
845
+ else {
846
+ // fetch the session from database
847
+ return this.db.sessionGetDescriptorById(sessionId);
848
+ }
849
+ }
679
850
  }
680
851
  exports.SessionRegistry = SessionRegistry;
681
- function userSessionDataCreate(ownerId, title, agentProfileId) {
852
+ function userSessionCreateData(ownerId, title, agentProfileId) {
682
853
  return {
683
854
  session_uuid: database_1.Database.sessionNewUUID(),
684
855
  title,
685
856
  team_uuid: undefined,
686
857
  agent_profile_uuid: agentProfileId,
687
- workspace: undefined,
688
858
  user_uuid: ownerId,
859
+ agent_paused: false,
689
860
  };
690
861
  }
691
- function teamSessionDataCreate(teamId, ownerId, title, agentProfileId) {
862
+ function teamSessionCreateData(teamId, participants, ownerId, title, agentProfileId) {
692
863
  return {
693
864
  session_uuid: database_1.Database.sessionNewUUID(),
694
865
  title,
695
866
  team_uuid: teamId,
867
+ participants,
696
868
  agent_profile_uuid: agentProfileId,
697
- workspace: undefined,
698
869
  user_uuid: ownerId,
870
+ agent_paused: false,
699
871
  };
700
872
  }
@@ -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
  }