@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
@@ -6,7 +6,6 @@ import {
6
6
  SavedAgentProfile,
7
7
  DEFAULT_AGENT_PROFILE_NAME,
8
8
  DEFAULT_AGENT_PROFILE,
9
- DEFAULT_AGENT_PROFILE_SYSTEM_PROMPT,
10
9
  } from "@xalia/xmcp/sdk";
11
10
 
12
11
  import {
@@ -27,24 +26,28 @@ import {
27
26
  ClientControlRemoveTeamUser,
28
27
  ServerToClient,
29
28
  ClientControlAgentProfileCreate,
29
+ ClientControlAgentProfileDelete,
30
30
  } from "../protocol/messages";
31
- import { OpenSession } from "./openSession";
31
+ import { GUEST_TOKEN_PREFIX, OpenSession } from "./openSession";
32
32
  import {
33
33
  SessionCreateData,
34
34
  SessionData,
35
+ SessionDescriptor,
35
36
  TeamInfo,
36
37
  TeamParticipant,
37
38
  } from "../data/dataModels";
38
- import { Database } from "../data/database";
39
+ import { Database, UserData } from "../data/database";
39
40
  import { resolveUserIdentifier } from "../utils/userResolver";
40
41
  import { ChatFatalError } from "../protocol/errors";
41
- import { IUserConnectionManager } from "./connectionManager";
42
- import { DEFAULT_LLM_MODEL } from "../../agent/agentUtils";
42
+ import { IMessageProcessor, IUserConnectionManager } from "./connectionManager";
43
43
  import { getErrorString } from "./errorUtils";
44
+ import { ApiKeyManager } from "../data/apiKeyManager";
44
45
 
45
46
  const logger = getLogger();
46
47
 
47
- export class SessionRegistry {
48
+ export type GuestUser = UserData & { guest_for_session: string };
49
+
50
+ export class SessionRegistry implements IMessageProcessor<ClientToServer> {
48
51
  // In memory session-user/user-session mappings
49
52
  // Note: this mappings ONLY trackes online users and will
50
53
  // be cleaned up when the user disconnects.
@@ -57,12 +60,18 @@ export class SessionRegistry {
57
60
  // sessionId -> OpenSession
58
61
  private openSessions: Map<string, OpenSession> = new Map();
59
62
 
63
+ private guests: Map<string, GuestUser> = new Map();
64
+
65
+ private apiKeyManager: ApiKeyManager;
66
+
60
67
  constructor(
61
68
  private db: Database,
62
69
  private connectionManager: IUserConnectionManager<ServerToClient>,
63
70
  private llmUrl: string,
64
71
  private xmcpUrl: string
65
- ) {}
72
+ ) {
73
+ this.apiKeyManager = new ApiKeyManager(db);
74
+ }
66
75
 
67
76
  /**
68
77
  * Add user to session membership.
@@ -168,6 +177,60 @@ export class SessionRegistry {
168
177
  return this.userSessions.get(userId) || new Set();
169
178
  }
170
179
 
180
+ // IMessageProcessor<ClientToServer>
181
+ async authenticate(token: string): Promise<string | undefined> {
182
+ // Parse the api key to determine the type of connection
183
+ const {
184
+ prefix,
185
+ apiKey,
186
+ payload: sessionUUID,
187
+ } = ApiKeyManager.parseToken(token);
188
+
189
+ if (prefix === GUEST_TOKEN_PREFIX) {
190
+ if (!sessionUUID) {
191
+ throw new Error(`invalid token ${token}`);
192
+ }
193
+
194
+ const descriptor = await this.getSessionDescriptor(sessionUUID);
195
+ if (!descriptor) {
196
+ throw new Error(`no such session ${sessionUUID}`);
197
+ }
198
+ if (descriptor.access_token !== token) {
199
+ throw new Error(`invalid guest key ${apiKey}`);
200
+ }
201
+
202
+ // For now, add a new user to the DB (so simplify the process of adding
203
+ // users).
204
+
205
+ const user_uuid = uuidv4();
206
+ const email = `${user_uuid}@`;
207
+ const nickname = "Guest";
208
+ const timezone = "UTC";
209
+
210
+ await this.db.createUser(user_uuid, email, nickname, timezone);
211
+
212
+ const user: GuestUser = {
213
+ uuid: user_uuid,
214
+ nickname: "Guest",
215
+ email: "guest",
216
+ timezone: "UTC",
217
+ guest_for_session: sessionUUID,
218
+ };
219
+ this.guests.set(user_uuid, user);
220
+ return user.uuid;
221
+
222
+ // throw new Error("unimpl");
223
+ // // return "";
224
+ }
225
+
226
+ const userData = await this.apiKeyManager.verifyApiKey(apiKey);
227
+ if (!userData) {
228
+ throw new ChatFatalError("invalid api key");
229
+ }
230
+ return userData.uuid;
231
+ }
232
+
233
+ // IMessageProcessor<ClientToServer>
171
234
  async processMessage(
172
235
  connectionId: string,
173
236
  userId: string,
@@ -204,11 +267,11 @@ export class SessionRegistry {
204
267
  ): Promise<void> {
205
268
  switch (message.type) {
206
269
  case "control_agent_profile_create":
207
- await this.handleAgentProfileCreate(message, userId);
270
+ await this.handleAgentProfileCreate(userId, message);
208
271
  break;
209
272
  case "control_agent_profile_delete":
210
- // TODO:
211
- throw new Error("not implemented yet");
273
+ await this.handleAgentProfileDelete(userId, message);
274
+ break;
212
275
  case "control_get_session_list":
213
276
  await this.handleGetSessionList(connectionId, userId, message);
214
277
  break;
@@ -251,6 +314,19 @@ export class SessionRegistry {
251
314
  try {
252
315
  const [userSessions, teamSessions, userAgents] =
253
316
  await this.getAllAgentsAndSessionsByUser(userId);
317
+
318
+ // Mark any guest session as a user session
319
+
320
+ const guest = this.guests.get(userId);
321
+ if (guest) {
322
+ const guestSession = await this.db.sessionGetDescriptorById(
323
+ guest.guest_for_session
324
+ );
325
+ if (guestSession) {
326
+ userSessions.push(guestSession);
327
+ }
328
+ }
329
+
254
330
  const response: ServerControlSessionList = {
255
331
  type: "control_session_list",
256
332
  user_sessions: userSessions,
@@ -286,11 +362,11 @@ export class SessionRegistry {
286
362
  await this.validateSessionAccess(sessionId, userId, "owner");
287
363
 
288
364
  // get users/team-uuid before deletion
289
- const [users, teamUuid, sessionData] = await Promise.all([
365
+ const [users, sessionData] = await Promise.all([
290
366
  this.getSessionUsers(sessionId),
291
- this.db.sessionGetTeamUuid(sessionId),
292
- this.db.sessionGetById(sessionId),
367
+ this.db.sessionGetDescriptorById(sessionId),
293
368
  ]);
369
+ const teamUuid = sessionData?.team_uuid;
294
370
 
295
371
  if (!sessionData) {
296
372
  throw new ChatFatalError(`No such session: ${sessionId}`);
@@ -355,9 +431,10 @@ export class SessionRegistry {
355
431
  const validMemberIds: string[] = [];
356
432
  const participants: TeamParticipant[] = [];
357
433
 
358
- // filter out the owner and extract failed lookups
434
+ // No need to filter out the owner here, that is done in
435
+ // `createTeamWithParticipants`
359
436
  resolvedMemberIds.forEach((user, index) => {
360
- if (user && user.uuid !== userId) {
437
+ if (user) {
361
438
  validMemberIds.push(user.uuid);
362
439
  participants.push({
363
440
  user_uuid: user.uuid,
@@ -365,7 +442,7 @@ export class SessionRegistry {
365
442
  email: user.email,
366
443
  role: "participant",
367
444
  });
368
- } else if (user === undefined) {
445
+ } else {
369
446
  failedLookups.push(message.initial_members[index]);
370
447
  }
371
448
  });
@@ -377,17 +454,50 @@ export class SessionRegistry {
377
454
  validMemberIds
378
455
  );
379
456
 
457
+ // Create a default agent for the new team
458
+ const defaultAgentProfile = await this.db.createAgentProfile(
459
+ undefined,
460
+ teamUuid,
461
+ DEFAULT_AGENT_PROFILE_NAME,
462
+ DEFAULT_AGENT_PROFILE
463
+ );
464
+
465
+ if (!defaultAgentProfile) {
466
+ throw new Error(
467
+ `Failed to create default agent profile for team ${teamUuid}`
468
+ );
469
+ }
470
+
471
+ // Get owner's information and add to participants for the response
472
+ const ownerInfo = await this.db.getUserFromUuid(userId);
473
+ if (!ownerInfo) {
474
+ throw new Error(`Cannot find owner user data for user ${userId}`);
475
+ }
476
+
477
+ const ownerParticipant: TeamParticipant = {
478
+ user_uuid: userId,
479
+ nickname: ownerInfo.nickname || "",
480
+ email: ownerInfo.email,
481
+ role: "owner",
482
+ };
483
+
380
484
  const response: ServerControlTeamCreated = {
381
485
  type: "control_team_created",
382
486
  team_uuid: teamUuid,
383
487
  team_owner_uuid: userId,
384
488
  team_name: message.team_name,
385
- members: participants,
489
+ members: [ownerParticipant, ...participants],
386
490
  failed_lookups: failedLookups,
387
491
  };
388
492
  const members = new Set(validMemberIds);
389
493
  members.add(userId);
390
494
  this.connectionManager.sendToUsers(members, response);
495
+
496
+ // Broadcast the default agent profile to all team members
497
+ this.connectionManager.sendToUsers(members, {
498
+ type: "control_agent_profile_created",
499
+ profile: defaultAgentProfile,
500
+ });
391
501
  } catch (error) {
392
502
  logger.error(`[SessionRegistry] Error creating team: ${String(error)}`);
393
503
  this.sendControlError(
@@ -500,6 +610,19 @@ export class SessionRegistry {
500
610
  }
501
611
  }
502
612
 
613
+ // Notify all team members about the updated member list
614
+ const updatedParticipants = await this.db.teamGetMembers(
615
+ message.target_team_id
616
+ );
617
+ this.connectionManager.sendToUsers(
618
+ new Set(updatedParticipants.map((p) => p.user_uuid)),
619
+ {
620
+ type: "control_team_members_updated",
621
+ team_uuid: message.target_team_id,
622
+ members: updatedParticipants,
623
+ }
624
+ );
625
+
503
626
  logger.info(
504
627
  `[SessionRegistry] User ${user.uuid}` +
505
628
  `added to team ${message.target_team_id}`
@@ -592,6 +715,19 @@ export class SessionRegistry {
592
715
  }
593
716
  }
594
717
 
718
+ // Notify all remaining team members about the updated member list
719
+ const updatedParticipants = await this.db.teamGetMembers(
720
+ message.target_team_id
721
+ );
722
+ this.connectionManager.sendToUsers(
723
+ new Set(updatedParticipants.map((p) => p.user_uuid)),
724
+ {
725
+ type: "control_team_members_updated",
726
+ team_uuid: message.target_team_id,
727
+ members: updatedParticipants,
728
+ }
729
+ );
730
+
595
731
  logger.info(
596
732
  `[SessionRegistry] User ${user.uuid} ` +
597
733
  `removed from team ${message.target_team_id}`
@@ -645,7 +781,7 @@ export class SessionRegistry {
645
781
  const access = await this.validateSessionAccess(sessionId, userId);
646
782
  if (!access) {
647
783
  throw new ChatFatalError(
648
- `User ${userId} is not authorized to ` + `join session ${sessionId}`
784
+ `User ${userId} is not authorized to join session ${sessionId}`
649
785
  );
650
786
  }
651
787
 
@@ -658,7 +794,15 @@ export class SessionRegistry {
658
794
  `Server internal error: ` + `failed to load session ${sessionId}`
659
795
  );
660
796
  }
661
-
797
+ const guest = this.guests.get(userId);
798
+ if (guest) {
799
+ session.addParticipant(userId, {
800
+ user_uuid: guest.uuid,
801
+ nickname: guest.nickname || "Guest",
802
+ email: guest.email,
803
+ role: "participant",
804
+ });
805
+ }
662
806
  // To this point, there should be no error thrown.
663
807
  // Update in-memory session-user/user-session mappings
664
808
  this.addUserToSessionMemory(userId, sessionId);
@@ -667,6 +811,7 @@ export class SessionRegistry {
667
811
  if (!this.openSessions.has(sessionId)) {
668
812
  this.openSessions.set(sessionId, session);
669
813
  }
814
+
670
815
  // pass the message to the session to handle the rest
671
816
  session.sendSessionData(connectionId, message.client_message_id);
672
817
  } catch (error) {
@@ -682,8 +827,8 @@ export class SessionRegistry {
682
827
  }
683
828
 
684
829
  async handleAgentProfileCreate(
685
- message: ClientControlAgentProfileCreate,
686
- from: string
830
+ userId: string,
831
+ message: ClientControlAgentProfileCreate
687
832
  ): Promise<string> {
688
833
  // get agent profile from template
689
834
  let agentProfileFromTemplate: AgentProfile | undefined = undefined;
@@ -697,11 +842,13 @@ export class SessionRegistry {
697
842
  agentProfileFromTemplate = template.agent_profile;
698
843
  }
699
844
 
700
- const newAgentProfile: AgentProfile = agentProfileFromTemplate || {
701
- model: DEFAULT_LLM_MODEL,
702
- system_prompt: DEFAULT_AGENT_PROFILE_SYSTEM_PROMPT,
703
- mcp_settings: {},
704
- };
845
+ const newAgentProfile: AgentProfile =
846
+ agentProfileFromTemplate ||
847
+ new AgentProfile(
848
+ DEFAULT_AGENT_PROFILE.model,
849
+ DEFAULT_AGENT_PROFILE.system_prompt,
850
+ DEFAULT_AGENT_PROFILE.mcp_settings
851
+ );
705
852
  const team_uuid = message.team_uuid || undefined;
706
853
 
707
854
  if (team_uuid) {
@@ -732,7 +879,7 @@ export class SessionRegistry {
732
879
  // User AgentProfile
733
880
 
734
881
  const savedAgentProfile = await this.db.createAgentProfile(
735
- from,
882
+ userId,
736
883
  undefined,
737
884
  message.title,
738
885
  newAgentProfile
@@ -742,7 +889,7 @@ export class SessionRegistry {
742
889
  }
743
890
 
744
891
  // Send the new AgentProfile to the user
745
- this.connectionManager.sendToUsers(new Set([from]), {
892
+ this.connectionManager.sendToUsers(new Set([userId]), {
746
893
  type: "control_agent_profile_created",
747
894
  profile: savedAgentProfile,
748
895
  });
@@ -750,6 +897,54 @@ export class SessionRegistry {
750
897
  return savedAgentProfile.uuid;
751
898
  }
752
899
 
900
+ async handleAgentProfileDelete(
901
+ userId: string,
902
+ message: ClientControlAgentProfileDelete
903
+ ): Promise<void> {
904
+ const agentProfileUuid = message.agent_profile_uuid;
905
+
906
+ // Get the agent profile to determine if it's a team or user profile
907
+ const agentProfile =
908
+ await this.db.getSavedAgentProfileById(agentProfileUuid);
909
+ if (!agentProfile) {
910
+ throw new Error(`Agent profile ${agentProfileUuid} not found`);
911
+ }
912
+
913
+ // Validate team access: user must own the profile or be in the team
914
+ // TODO: Only allow the owner to delete the profile?
915
+ if (agentProfile.team_uuid) {
916
+ const hasAccess = await this.validateTeamAccess(
917
+ agentProfile.team_uuid,
918
+ userId
919
+ );
920
+ if (!hasAccess) {
921
+ throw new Error(
922
+ `User ${userId} is not authorized to delete this agent profile`
923
+ );
924
+ }
925
+ }
926
+
927
+ // Delete the agent profile from database
928
+ await this.db.deleteAgentProfile(agentProfileUuid);
929
+
930
+ // Send confirmation to all relevant users
931
+ if (agentProfile.team_uuid) {
932
+ const participants = await this.db.teamGetMembers(agentProfile.team_uuid);
933
+ this.connectionManager.sendToUsers(
934
+ new Set(participants.map((p) => p.user_uuid)),
935
+ {
936
+ type: "control_agent_profile_deleted",
937
+ profile_uuid: agentProfileUuid,
938
+ }
939
+ );
940
+ } else {
941
+ this.connectionManager.sendToUsers(new Set([userId]), {
942
+ type: "control_agent_profile_deleted",
943
+ profile_uuid: agentProfileUuid,
944
+ });
945
+ }
946
+ }
947
+
753
948
  /**
754
949
  * Create a new session for a user.
755
950
  * - create session in database (via `newSession`)
@@ -769,13 +964,13 @@ export class SessionRegistry {
769
964
 
770
965
  // Create AgentProfile and inform the client
771
966
  message.agent_profile_id = await this.handleAgentProfileCreate(
967
+ fromUserId,
772
968
  {
773
969
  type: "control_agent_profile_create",
774
970
  title: "New Agent " + uuidv4(),
775
971
  user_uuid: fromUserId,
776
972
  team_uuid: message.team_id,
777
- },
778
- fromUserId
973
+ }
779
974
  );
780
975
  }
781
976
 
@@ -825,8 +1020,8 @@ export class SessionRegistry {
825
1020
  // validate the agent profile
826
1021
  await this.validateSavedAgentProfile(agentProfileId);
827
1022
 
828
- const newSessionData: SessionData = {
829
- ...userSessionDataCreate(ownerId, title, agentProfileId),
1023
+ const newSessionData: SessionDescriptor = {
1024
+ ...userSessionCreateData(ownerId, title, agentProfileId),
830
1025
  updated_at: new Date().toISOString(),
831
1026
  };
832
1027
  const openSession = await OpenSession.initWithEmptySession(
@@ -860,8 +1055,8 @@ export class SessionRegistry {
860
1055
  );
861
1056
  }
862
1057
 
863
- const newSessionData: SessionData = {
864
- ...teamSessionDataCreate(teamId, fromUserId, title, agentProfileId),
1058
+ const newSessionData: SessionDescriptor = {
1059
+ ...teamSessionCreateData(teamId, fromUserId, title, agentProfileId),
865
1060
  updated_at: new Date().toISOString(),
866
1061
  };
867
1062
 
@@ -917,9 +1112,27 @@ export class SessionRegistry {
917
1112
  * Handle user disconnect - clean up from all sessions.
918
1113
  * Called when a connection is closed to ensure proper cleanup.
919
1114
  */
920
- handleUserDisconnect(userId: string): void {
1115
+ async handleUserDisconnect(userId: string): Promise<void> {
921
1116
  logger.info(`[SessionRegistry] Handling disconnect for user ${userId}`);
922
1117
 
1118
+ // If the user is a guest, remove his DB entry.
1119
+ const guest = this.guests.get(userId);
1120
+ if (guest) {
1121
+ const session = this.openSessions.get(guest.guest_for_session);
1122
+ if (session) {
1123
+ session.removeParticipant(userId);
1124
+ }
1125
+ this.guests.delete(userId);
1126
+ try {
1127
+ logger.info(
1128
+ `[SessionRegistry.handleUserDisconnect] deleting user ${userId}`
1129
+ );
1130
+ await this.db.deleteUser(userId);
1131
+ } catch (e) {
1132
+ logger.warn(`error removing guest ${userId}: ${getErrorString(e)}`);
1133
+ }
1134
+ }
1135
+
923
1136
  // Get all sessions the user is in (copy to avoid concurrent modification)
924
1137
  const userSessionIds = this.getInMemoryUserSessions(userId);
925
1138
  const sessionsToLeave = Array.from(userSessionIds);
@@ -943,11 +1156,13 @@ export class SessionRegistry {
943
1156
  private async getAllAgentsAndSessionsByUser(
944
1157
  userId: string
945
1158
  ): Promise<[SessionData[], TeamInfo[], SavedAgentProfile[]]> {
946
- return Promise.all([
1159
+ const [userSessions, teamInfos, userAgents] = await Promise.all([
947
1160
  this.db.getUserSessions(userId),
948
1161
  this.db.getTeamInfosByUser(userId),
949
1162
  this.agentProfilesGetByUserOrDefault(userId),
950
1163
  ]);
1164
+
1165
+ return [userSessions, teamInfos, userAgents];
951
1166
  }
952
1167
 
953
1168
  private async agentProfilesGetByUserOrDefault(
@@ -987,7 +1202,7 @@ export class SessionRegistry {
987
1202
  /**
988
1203
  * Validates that a user has permission to access a session.
989
1204
  */
990
- private async validateSessionAccess(
1205
+ async validateSessionAccess(
991
1206
  sessionId: string,
992
1207
  userId: string,
993
1208
  accessType?: "participant" | "owner"
@@ -998,6 +1213,13 @@ export class SessionRegistry {
998
1213
  const participants = session.getParticipants();
999
1214
  const role = participants.get(userId);
1000
1215
  if (!role) {
1216
+ // Check for guest access
1217
+ const guest = this.guests.get(userId);
1218
+ if (guest && guest.guest_for_session === sessionId) {
1219
+ return !accessType || accessType === "participant";
1220
+ }
1221
+
1222
+ // Not authorized to join the session
1001
1223
  return false;
1002
1224
  } else {
1003
1225
  return !accessType || role.role === accessType;
@@ -1007,6 +1229,13 @@ export class SessionRegistry {
1007
1229
  const participants = await this.db.sessionGetParticipants(sessionId);
1008
1230
  const role = participants.find((p) => p.user_uuid === userId)?.role;
1009
1231
  if (!role) {
1232
+ // Check for guest access
1233
+ const guest = this.guests.get(userId);
1234
+ logger.info(`[validateSessionAccess] guest: ${JSON.stringify(guest)}`);
1235
+ if (guest && guest.guest_for_session === sessionId) {
1236
+ return !accessType || accessType === "participant";
1237
+ }
1238
+
1010
1239
  return false;
1011
1240
  } else {
1012
1241
  return !accessType || role === accessType;
@@ -1030,9 +1259,24 @@ export class SessionRegistry {
1030
1259
  return !accessType || role === accessType;
1031
1260
  }
1032
1261
  }
1262
+
1263
+ /**
1264
+ * Read from DB if the session is not active
1265
+ */
1266
+ private async getSessionDescriptor(
1267
+ sessionId: string
1268
+ ): Promise<SessionDescriptor | undefined> {
1269
+ const session = this.openSessions.get(sessionId);
1270
+ if (session) {
1271
+ return session.getDescriptor();
1272
+ } else {
1273
+ // fetch the session from database
1274
+ return this.db.sessionGetDescriptorById(sessionId);
1275
+ }
1276
+ }
1033
1277
  }
1034
1278
 
1035
- export function userSessionDataCreate(
1279
+ export function userSessionCreateData(
1036
1280
  ownerId: string,
1037
1281
  title: string,
1038
1282
  agentProfileId: string
@@ -1042,12 +1286,12 @@ export function userSessionDataCreate(
1042
1286
  title,
1043
1287
  team_uuid: undefined,
1044
1288
  agent_profile_uuid: agentProfileId,
1045
- workspace: undefined,
1046
1289
  user_uuid: ownerId,
1290
+ agent_paused: false,
1047
1291
  };
1048
1292
  }
1049
1293
 
1050
- export function teamSessionDataCreate(
1294
+ export function teamSessionCreateData(
1051
1295
  teamId: string,
1052
1296
  ownerId: string,
1053
1297
  title: string,
@@ -1058,7 +1302,7 @@ export function teamSessionDataCreate(
1058
1302
  title,
1059
1303
  team_uuid: teamId,
1060
1304
  agent_profile_uuid: agentProfileId,
1061
- workspace: undefined,
1062
1305
  user_uuid: ownerId,
1306
+ agent_paused: false,
1063
1307
  };
1064
1308
  }
@@ -128,21 +128,18 @@ export function createMockUserConnectionManager(): {
128
128
  mock: IUserConnectionManager<ServerToClient>;
129
129
  spies: {
130
130
  sendToUsers: ReturnType<typeof vi.fn>;
131
- getLiveUserApiKey: ReturnType<typeof vi.fn>;
132
131
  };
133
132
  } {
134
133
  const spies = {
135
134
  sendToUsers: vi.fn(),
136
135
  sendToConnection: vi.fn(),
137
136
  sendServerError: vi.fn(),
138
- getLiveUserApiKey: vi.fn(),
139
137
  };
140
138
 
141
139
  const mock = {
142
140
  sendToUsers: spies.sendToUsers,
143
141
  sendToConnection: spies.sendToConnection,
144
142
  sendServerError: spies.sendServerError,
145
- getLiveUserApiKey: spies.getLiveUserApiKey,
146
143
  } as IUserConnectionManager<ServerToClient>;
147
144
 
148
145
  return { mock, spies };
@@ -154,16 +151,25 @@ export function createMockUserConnectionManager(): {
154
151
  export function createMockSessionRegistry(): {
155
152
  mock: IMessageProcessor<ClientToServer>;
156
153
  spies: {
154
+ authenticate: ReturnType<typeof vi.fn>;
157
155
  processMessage: ReturnType<typeof vi.fn>;
158
156
  handleUserDisconnect: ReturnType<typeof vi.fn>;
159
157
  };
160
158
  } {
161
159
  const spies = {
160
+ authenticate: vi.fn().mockImplementation((apiKey) => {
161
+ if (apiKey === "valid-api-key")
162
+ return Promise.resolve(MOCK_USERS.owner.uuid);
163
+ if (apiKey === "participant-api-key")
164
+ return Promise.resolve(MOCK_USERS.participant.uuid);
165
+ return Promise.resolve(null);
166
+ }),
162
167
  processMessage: vi.fn(),
163
168
  handleUserDisconnect: vi.fn(),
164
169
  };
165
170
 
166
171
  const mock = {
172
+ authenticate: spies.authenticate,
167
173
  processMessage: spies.processMessage,
168
174
  handleUserDisconnect: spies.handleUserDisconnect,
169
175
  } as IMessageProcessor<ClientToServer>;
@@ -267,6 +273,7 @@ export function createMockSessionList(): Array<SessionData> {
267
273
  workspace: undefined,
268
274
  updated_at: MOCK_SESSIONS.active.updated_at || "",
269
275
  user_uuid: MOCK_SESSIONS.active.user_uuid,
276
+ agent_paused: false,
270
277
  },
271
278
  {
272
279
  session_uuid: MOCK_SESSIONS.secondary.uuid,
@@ -276,6 +283,7 @@ export function createMockSessionList(): Array<SessionData> {
276
283
  workspace: undefined,
277
284
  updated_at: MOCK_SESSIONS.secondary.updated_at || "",
278
285
  user_uuid: MOCK_SESSIONS.secondary.user_uuid,
286
+ agent_paused: false,
279
287
  },
280
288
  ];
281
289
  }
@@ -409,14 +417,6 @@ export function setupStandardMockBehaviors(mocks: {
409
417
  }
410
418
 
411
419
  // Setup user connection manager mocks
412
- if (mocks.userConnectionManager) {
413
- mocks.userConnectionManager.spies.getLiveUserApiKey.mockImplementation(
414
- (userId: string) => {
415
- if (userId === MOCK_USERS.owner.uuid) return "valid-api-key";
416
- if (userId === MOCK_USERS.participant.uuid)
417
- return "participant-api-key";
418
- return undefined;
419
- }
420
- );
421
- }
420
+ // if (mocks.userConnectionManager) {
421
+ // }
422
422
  }