@multiplayer-app/ai-agent-node 0.1.0-beta.75 → 0.1.0-beta.77

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.
@@ -17,6 +17,9 @@ export class ChatProcessor {
17
17
  socketService;
18
18
  agentStore;
19
19
  debug;
20
+ getChatUserId(chat) {
21
+ return chat.tenants?.userId;
22
+ }
20
23
  constructor(params) {
21
24
  this.chatRepository = params.chatRepository;
22
25
  this.messageRepository = params.messageRepository;
@@ -50,14 +53,34 @@ export class ChatProcessor {
50
53
  }
51
54
  return payload.tenants ?? {};
52
55
  }
56
+ getParticipantIds(chat) {
57
+ const primary = chat.userId ?? this.getChatUserId(chat);
58
+ return Array.from(new Set([primary, ...(chat.participantIds ?? [])].filter(Boolean)));
59
+ }
60
+ hasChatAccess(chat, userId) {
61
+ if (!userId)
62
+ return true;
63
+ return this.getParticipantIds(chat).includes(userId);
64
+ }
65
+ emitChatToParticipants(chat, chatToEmit, excludeSocketId) {
66
+ for (const userId of this.getParticipantIds(chatToEmit.participantIds ? chatToEmit : chat)) {
67
+ this.socketService.emitChatUpdate(userId, chatToEmit, excludeSocketId);
68
+ }
69
+ }
70
+ emitMessageToParticipants(chat, message, excludeSocketId) {
71
+ for (const userId of this.getParticipantIds(chat)) {
72
+ this.socketService.emitMessageUpdate(userId, message, excludeSocketId);
73
+ }
74
+ }
53
75
  async updateChatAndEmit(chat, update, excludeSocketId) {
54
76
  await this.chatRepository.update(chat.id, update);
55
77
  const updatedChat = await this.chatRepository.findById(chat.id);
56
78
  const chatToEmit = updatedChat ?? { ...chat, ...update };
57
- this.socketService.emitChatUpdate(chat.userId, chatToEmit, excludeSocketId);
79
+ this.emitChatToParticipants(chat, chatToEmit, excludeSocketId);
58
80
  return chatToEmit;
59
81
  }
60
82
  async startSubagentProcess(params) {
83
+ const parentUserId = params.parentChat.userId ?? this.getChatUserId(params.parentChat) ?? 'guest';
61
84
  const childChat = await this.chatRepository.create({
62
85
  title: `${params.subAgentConfig.name} subagent`,
63
86
  type: ChatType.Agent,
@@ -67,7 +90,8 @@ export class ChatProcessor {
67
90
  parentMessageId: params.parentMessage.id,
68
91
  parentToolCallId: params.parentToolCallId,
69
92
  contextKey: params.parentChat.contextKey,
70
- userId: params.parentChat.userId,
93
+ userId: parentUserId,
94
+ tenants: { ...params.parentChat.tenants },
71
95
  model: params.subAgentConfig.defaultModel || params.parentChat.model,
72
96
  });
73
97
  try {
@@ -82,10 +106,10 @@ export class ChatProcessor {
82
106
  toolCalls: params.parentMessage.toolCalls,
83
107
  });
84
108
  if (updatedParentMessage) {
85
- this.socketService.emitMessageUpdate(params.parentChat.userId, updatedParentMessage);
109
+ this.emitMessageToParticipants(params.parentChat, updatedParentMessage);
86
110
  }
87
111
  }
88
- this.socketService.emitChatUpdate(params.parentChat.userId, childChat);
112
+ this.emitChatToParticipants(params.parentChat, childChat);
89
113
  const executionTenants = this.getExecutionTenants(params);
90
114
  const abortController = this.agentStore.registerSubAgentProcess(childChat.id, params.parentChat.id);
91
115
  const userMessage = await this.createMessage(childChat, MessageRole.User, {
@@ -101,7 +125,7 @@ export class ChatProcessor {
101
125
  agentName: params.subAgentConfig.name
102
126
  });
103
127
  const parentActivity = await this.activityRepository.create({
104
- ownerId: params.parentChat.userId,
128
+ ownerId: parentUserId,
105
129
  groupId: params.parentChat.id,
106
130
  name: ActivityOperationName.SUBAGENT,
107
131
  tenants: executionTenants,
@@ -138,7 +162,7 @@ export class ChatProcessor {
138
162
  tenants: executionTenants,
139
163
  });
140
164
  };
141
- const userPreferences = await this.agentConfigRepository.findByUserIdAndAgentName(childChat.userId, agentOptions.name);
165
+ const userPreferences = await this.agentConfigRepository.findByUserIdAndAgentName(parentUserId, agentOptions.name);
142
166
  agentOptions.activeTools = this.getAvailableTools(agentOptions, userPreferences);
143
167
  await this.streamMessageStep({
144
168
  chat: childChat,
@@ -239,11 +263,15 @@ export class ChatProcessor {
239
263
  sessionKind: AgentSessionKind.ROOT
240
264
  };
241
265
  if (params?.userId) {
266
+ // Chats visible to this user: owner, tenant owner, or participant (multi-user).
242
267
  filter.userId = params.userId;
243
268
  }
244
269
  if (params?.contextKey) {
245
270
  filter.contextKey = params.contextKey;
246
271
  }
272
+ if (params?.multiUser !== undefined) {
273
+ filter.multiUser = params.multiUser;
274
+ }
247
275
  // Build query options for sort and limit with defaults.
248
276
  const options = {
249
277
  sort: {
@@ -330,17 +358,12 @@ export class ChatProcessor {
330
358
  return descendants;
331
359
  }
332
360
  async upsertAndGetChat(payload, excludeSocketId) {
333
- const targetUserId = payload.userId ?? 'guest';
334
361
  let chat = null;
335
362
  if (payload.chatId) {
336
363
  chat = await this.chatRepository.findById(payload.chatId);
337
364
  if (!chat) {
338
365
  throw new Error('Chat not found');
339
366
  }
340
- // Ensure chat is associated with the caller's userId
341
- if (chat.userId && chat.userId !== targetUserId) {
342
- throw new Error('Chat does not belong to this user');
343
- }
344
367
  }
345
368
  if (!chat) {
346
369
  chat = await this.createChat(payload, excludeSocketId);
@@ -373,19 +396,22 @@ export class ChatProcessor {
373
396
  role,
374
397
  content: messageData.content,
375
398
  agentName: messageData.agentName,
399
+ sender: messageData.sender,
400
+ mentions: messageData.mentions ?? [],
401
+ annotations: messageData.annotations,
376
402
  attachments: attachments ?? [],
377
403
  reasoning: "",
378
404
  toolCalls: []
379
405
  });
380
- if (chat.userId) {
381
- this.socketService.emitMessageUpdate(chat.userId, message, excludeSocketId);
382
- }
406
+ this.emitMessageToParticipants(chat, message, excludeSocketId);
383
407
  return message;
384
408
  }
385
409
  async createChat(payload, excludeSocketId) {
386
- const targetUserId = payload.userId ?? 'guest';
410
+ const targetUserId = payload.userId ?? payload.tenants?.userId ?? 'guest';
387
411
  const contextKey = 'contextKey' in payload ? payload.contextKey : 'default';
388
412
  const metadata = 'metadata' in payload ? payload.metadata : {};
413
+ const requestedParticipantIds = 'participantIds' in payload && Array.isArray(payload.participantIds) ? payload.participantIds : [];
414
+ const participantIds = Array.from(new Set([targetUserId, ...requestedParticipantIds].filter(Boolean)));
389
415
  const chat = await this.chatRepository.create({
390
416
  title: this.getTemporaryTitle(contextKey),
391
417
  type: ChatType.Chat,
@@ -393,10 +419,15 @@ export class ChatProcessor {
393
419
  sessionKind: AgentSessionKind.ROOT,
394
420
  contextKey,
395
421
  userId: targetUserId,
422
+ tenants: {
423
+ ...(payload.tenants ?? {}),
424
+ userId: targetUserId,
425
+ },
426
+ participantIds,
396
427
  metadata,
397
428
  ...(payload.model ? { model: payload.model } : {})
398
429
  });
399
- this.socketService.emitChatUpdate(targetUserId, chat, excludeSocketId);
430
+ this.emitChatToParticipants(chat, chat, excludeSocketId);
400
431
  return chat;
401
432
  }
402
433
  /**
@@ -451,7 +482,7 @@ export class ChatProcessor {
451
482
  await this.messageRepository.update(message.id, { toolCalls });
452
483
  // Bump chat updatedAt so listChats ordering reflects the action.
453
484
  await this.chatRepository.update(chat.id, { updatedAt: now });
454
- this.socketService.emitMessageUpdate(chat.userId, updatedMessage, params.excludeSocketId);
485
+ this.emitMessageToParticipants(chat, updatedMessage, params.excludeSocketId);
455
486
  if (params.systemMessage) {
456
487
  //todo discuss support for system messages
457
488
  //await this.createMessage(chat, MessageRole.System, params.systemMessage, undefined, params.excludeSocketId);
@@ -472,7 +503,7 @@ export class ChatProcessor {
472
503
  if (!chat) {
473
504
  throw new Error('Chat not found');
474
505
  }
475
- if (params.userId && chat.userId !== params.userId) {
506
+ if (params.userId && !this.hasChatAccess(chat, params.userId)) {
476
507
  throw new Error('Chat does not belong to this user');
477
508
  }
478
509
  const message = await this.messageRepository.findById(params.messageId);
@@ -501,7 +532,7 @@ export class ChatProcessor {
501
532
  }
502
533
  const now = new Date().toISOString();
503
534
  await this.chatRepository.update(chat.id, { updatedAt: now });
504
- this.socketService.emitMessageUpdate(chat.userId, updatedMessage, params.excludeSocketId);
535
+ this.emitMessageToParticipants(chat, updatedMessage, params.excludeSocketId);
505
536
  return updatedMessage;
506
537
  }
507
538
  async storeSubagentResponse(params) {
@@ -558,7 +589,7 @@ export class ChatProcessor {
558
589
  tenants: executionTenants,
559
590
  });
560
591
  };
561
- const userPreferences = await this.agentConfigRepository.findByUserIdAndAgentName(chat.userId, agentOptions.name);
592
+ const userPreferences = await this.agentConfigRepository.findByUserIdAndAgentName(this.getChatUserId(chat) ?? chat.userId ?? 'guest', agentOptions.name);
562
593
  agentOptions.activeTools = this.getAvailableTools(agentOptions, userPreferences);
563
594
  await this.streamMessageStep({
564
595
  chat,
@@ -575,7 +606,7 @@ export class ChatProcessor {
575
606
  const { chat, assistantMessage, existingMessages, signal, excludeSocketId, agentOptions } = params;
576
607
  const persistAndBroadcastToolCalls = async () => {
577
608
  await this.messageRepository.update(assistantMessage.id, { toolCalls: assistantMessage.toolCalls ?? [] });
578
- this.socketService.emitMessageUpdate(chat.userId, assistantMessage, excludeSocketId);
609
+ this.emitMessageToParticipants(chat, assistantMessage, excludeSocketId);
579
610
  await this.agentStore.shareAgentProcessEvent(chat.id, { type: AgentProcessEventType.Update, data: assistantMessage });
580
611
  };
581
612
  if (signal.aborted) {
@@ -607,7 +638,7 @@ export class ChatProcessor {
607
638
  await this.agentStore.shareAgentProcessEvent(chat.id, { type: AgentProcessEventType.Aborted, data: undefined });
608
639
  await this.updateChatAndEmit(chat, { status: AgentStatus.Aborted }, excludeSocketId);
609
640
  await this.messageRepository.update(assistantMessage.id, assistantMessage);
610
- this.socketService.emitMessageUpdate(chat.userId, assistantMessage, excludeSocketId);
641
+ this.emitMessageToParticipants(chat, assistantMessage, excludeSocketId);
611
642
  continue;
612
643
  }
613
644
  if (chunk.type === 'finish') {
@@ -624,7 +655,7 @@ export class ChatProcessor {
624
655
  ((totalUsage.promptTokens ?? 0) + (totalUsage.completionTokens ?? 0));
625
656
  }
626
657
  await this.messageRepository.update(assistantMessage.id, { ...assistantMessage });
627
- this.socketService.emitMessageUpdate(chat.userId, assistantMessage, excludeSocketId);
658
+ this.emitMessageToParticipants(chat, assistantMessage, excludeSocketId);
628
659
  if (chunk.finishReason === 'stop') {
629
660
  let nextChat = await this.updateChatAndEmit(chat, { status: AgentStatus.Finished }, excludeSocketId);
630
661
  if (chat.title && this.isTemporaryTitle(chat.title)) {
@@ -702,7 +733,7 @@ export class ChatProcessor {
702
733
  ...assistantMessage,
703
734
  content: assistantMessage.content
704
735
  };
705
- this.socketService.emitMessageUpdate(chat.userId, updatedMessage, excludeSocketId);
736
+ this.emitMessageToParticipants(chat, updatedMessage, excludeSocketId);
706
737
  await this.agentStore.shareAgentProcessEvent(chat.id, { type: AgentProcessEventType.Update, data: updatedMessage });
707
738
  await this.messageRepository.update(assistantMessage.id, { content: assistantMessage.content });
708
739
  continue;
@@ -788,7 +819,7 @@ export class ChatProcessor {
788
819
  if (!assistantMessage.reasoning)
789
820
  assistantMessage.reasoning = '';
790
821
  assistantMessage.reasoning += chunk.text;
791
- this.socketService.emitMessageUpdate(chat.userId, assistantMessage, excludeSocketId);
822
+ this.emitMessageToParticipants(chat, assistantMessage, excludeSocketId);
792
823
  await this.messageRepository.update(assistantMessage.id, { reasoning: assistantMessage.reasoning });
793
824
  await this.agentStore.shareAgentProcessEvent(chat.id, { type: AgentProcessEventType.Update, data: assistantMessage });
794
825
  continue;
@@ -871,11 +902,12 @@ export class ChatProcessor {
871
902
  }
872
903
  async processNewUserMessage(chat, payload, prevMessages, excludeSocketId) {
873
904
  const executionTenants = this.getExecutionTenants(payload);
905
+ const chatUserId = this.getChatUserId(chat) ?? 'guest';
874
906
  const userMessage = await this.createMessage(chat, MessageRole.User, { content: payload.content, agentName: undefined }, payload.attachments);
875
907
  await this.agentStore.shareAgentProcessEvent(chat.id, { type: AgentProcessEventType.Update, data: userMessage });
876
908
  let assistantMessage = await this.createMessage(chat, MessageRole.Assistant, { content: '', agentName: undefined }, undefined, excludeSocketId);
877
909
  const parentActivity = await this.activityRepository.create({
878
- ownerId: chat.userId,
910
+ ownerId: chatUserId,
879
911
  groupId: chat.id,
880
912
  name: ActivityOperationName.MESSAGE_PROCESSING,
881
913
  tenants: executionTenants,
@@ -937,12 +969,235 @@ export class ChatProcessor {
937
969
  agentOptions,
938
970
  };
939
971
  }
972
+ getAgentNamesForContext(contextKey) {
973
+ try {
974
+ return ConfigStore.getInstance().getAgentsForContext(contextKey).map((config) => config.name);
975
+ }
976
+ catch {
977
+ return [];
978
+ }
979
+ }
980
+ normalizeMentionName(value) {
981
+ return value.trim().replace(/^@/, '').toLowerCase();
982
+ }
983
+ extractMentionNames(content) {
984
+ const mentions = content.matchAll(/(^|[\s([{])@([a-zA-Z0-9_.-]+)/g);
985
+ return Array.from(mentions, (match) => match[2]).filter(Boolean);
986
+ }
987
+ getExplicitAgentMention(params) {
988
+ const agentNames = this.getAgentNamesForContext(params.contextKey);
989
+ const normalizedAgentNames = new Map(agentNames.map((name) => [this.normalizeMentionName(name), name]));
990
+ for (const mention of params.mentions ?? []) {
991
+ if (mention.type !== 'agent')
992
+ continue;
993
+ const normalized = this.normalizeMentionName(mention.name);
994
+ return { mentioned: true, agentName: normalizedAgentNames.get(normalized) };
995
+ }
996
+ for (const name of this.extractMentionNames(params.content)) {
997
+ const normalized = this.normalizeMentionName(name);
998
+ if (normalized === 'agent') {
999
+ return { mentioned: true };
1000
+ }
1001
+ const agentName = normalizedAgentNames.get(normalized);
1002
+ if (agentName) {
1003
+ return { mentioned: true, agentName };
1004
+ }
1005
+ }
1006
+ return { mentioned: false };
1007
+ }
1008
+ hasNonAgentMention(params) {
1009
+ const agentNames = this.getAgentNamesForContext(params.contextKey);
1010
+ const normalizedAgentNames = new Set(['agent', ...agentNames.map((name) => this.normalizeMentionName(name))]);
1011
+ if ((params.mentions ?? []).some((mention) => mention.type === 'user')) {
1012
+ return true;
1013
+ }
1014
+ return this.extractMentionNames(params.content).some((name) => {
1015
+ const normalized = this.normalizeMentionName(name);
1016
+ return !normalizedAgentNames.has(normalized);
1017
+ });
1018
+ }
1019
+ renderMessageForDecision(message) {
1020
+ const sender = message.sender?.displayName || message.sender?.id || message.agentName || message.role;
1021
+ return `${sender}: ${message.content}`;
1022
+ }
1023
+ async analyzeAgentShouldRespond(params) {
1024
+ try {
1025
+ const recentMessages = [...params.previousMessages.slice(-8), params.userMessage]
1026
+ .map((message) => this.renderMessageForDecision(message))
1027
+ .join('\n');
1028
+ const response = await this.aiHelper.getAssistantResponse([
1029
+ {
1030
+ role: MessageRole.User,
1031
+ content: `Conversation:\n${recentMessages}\n\nShould the AI agent respond to the latest message?`
1032
+ }
1033
+ ], {
1034
+ system: [
1035
+ 'You decide whether an AI agent should reply in a multi-user chat.',
1036
+ 'Reply with exactly YES or NO.',
1037
+ 'YES only when the latest message asks for help, requests agent action, or is clearly addressed to the AI.',
1038
+ 'NO when the latest message is human-to-human chatter, status, acknowledgement, or otherwise does not need the AI.'
1039
+ ].join('\n'),
1040
+ temperature: 0,
1041
+ maxOutputTokens: 5,
1042
+ model: params.chat.model,
1043
+ executionContext: params.executionContext
1044
+ });
1045
+ return /^yes\b/i.test(response.trim());
1046
+ }
1047
+ catch (error) {
1048
+ logger.warn({ error, chatId: params.chat.id }, 'Agent response decision failed');
1049
+ return false;
1050
+ }
1051
+ }
1052
+ async shouldRunAgentForMultiUserMessage(params) {
1053
+ const firstUserMessage = !params.previousMessages.some((message) => message.role === MessageRole.User);
1054
+ if (firstUserMessage) {
1055
+ return { shouldRun: true };
1056
+ }
1057
+ const explicitAgent = this.getExplicitAgentMention({
1058
+ content: params.payload.content,
1059
+ mentions: params.payload.mentions,
1060
+ contextKey: params.chat.contextKey,
1061
+ });
1062
+ if (explicitAgent.mentioned) {
1063
+ return { shouldRun: true, agentName: explicitAgent.agentName };
1064
+ }
1065
+ if (this.hasNonAgentMention({
1066
+ content: params.payload.content,
1067
+ mentions: params.payload.mentions,
1068
+ contextKey: params.chat.contextKey,
1069
+ })) {
1070
+ return { shouldRun: false };
1071
+ }
1072
+ const shouldRun = await this.analyzeAgentShouldRespond({
1073
+ chat: params.chat,
1074
+ previousMessages: params.previousMessages,
1075
+ userMessage: params.userMessage,
1076
+ executionContext: params.payload.executionContext,
1077
+ });
1078
+ return { shouldRun };
1079
+ }
1080
+ async prepareAgentResponseForUserMessage(chat, payload, prevMessages, userMessage, excludeSocketId, agentName) {
1081
+ const executionTenants = this.getExecutionTenants(payload);
1082
+ let assistantMessage = await this.createMessage(chat, MessageRole.Assistant, {
1083
+ content: '',
1084
+ agentName: agentName,
1085
+ sender: {
1086
+ id: agentName ?? 'agent',
1087
+ type: 'agent',
1088
+ displayName: agentName ?? 'Agent',
1089
+ }
1090
+ }, undefined, excludeSocketId);
1091
+ const ownerId = payload.userId ?? chat.userId ?? this.getChatUserId(chat) ?? 'guest';
1092
+ const parentActivity = await this.activityRepository.create({
1093
+ ownerId,
1094
+ groupId: chat.id,
1095
+ name: ActivityOperationName.MESSAGE_PROCESSING,
1096
+ tenants: executionTenants,
1097
+ sourceId: userMessage.id,
1098
+ sourceType: 'AgentMessage',
1099
+ metadata: {
1100
+ contextKey: chat.contextKey,
1101
+ modelId: this.aiHelper.getLanguageModelId(chat.model),
1102
+ },
1103
+ });
1104
+ const agentOptions = await this.aiHelper.getAgentOptions(agentName
1105
+ ? {
1106
+ agentName,
1107
+ modelId: chat.model,
1108
+ context: payload.context,
1109
+ executionContext: payload.executionContext,
1110
+ }
1111
+ : {
1112
+ contextKey: chat.contextKey,
1113
+ messages: [...prevMessages, userMessage],
1114
+ context: payload.context,
1115
+ agentName: undefined,
1116
+ modelId: chat.model,
1117
+ executionContext: payload.executionContext,
1118
+ }, {
1119
+ onStepFinish: async (stepResult) => {
1120
+ await this.storeStepActivity({
1121
+ chat,
1122
+ parentId: parentActivity.id,
1123
+ stepResult,
1124
+ name: ActivityOperationName.AGENT_SELECTION,
1125
+ sourceId: userMessage.id,
1126
+ sourceType: 'AgentMessage',
1127
+ tenants: executionTenants,
1128
+ });
1129
+ }
1130
+ });
1131
+ const updatedAssistantMessage = await this.messageRepository.update(assistantMessage.id, {
1132
+ agentName: agentOptions.name,
1133
+ activity: parentActivity.id,
1134
+ sender: {
1135
+ id: agentOptions.name,
1136
+ type: 'agent',
1137
+ displayName: agentOptions.name,
1138
+ }
1139
+ });
1140
+ await this.activityRepository.update(parentActivity.id, {
1141
+ metadata: {
1142
+ ...parentActivity.metadata,
1143
+ agentOptions: {
1144
+ name: agentOptions.name,
1145
+ modelId: this.aiHelper.getLanguageModelId(agentOptions.model),
1146
+ temperature: agentOptions.temperature,
1147
+ maxOutputTokens: agentOptions.maxOutputTokens,
1148
+ topP: agentOptions.topP,
1149
+ topK: agentOptions.topK,
1150
+ presencePenalty: agentOptions.presencePenalty,
1151
+ frequencyPenalty: agentOptions.frequencyPenalty,
1152
+ stopSequences: agentOptions.stopSequences,
1153
+ seed: agentOptions.seed,
1154
+ },
1155
+ },
1156
+ });
1157
+ if (!updatedAssistantMessage) {
1158
+ throw new Error(`Assistant message with id ${assistantMessage.id} not found after update`);
1159
+ }
1160
+ assistantMessage = updatedAssistantMessage;
1161
+ return {
1162
+ assistantMessage,
1163
+ agentOptions,
1164
+ executionTenants,
1165
+ };
1166
+ }
1167
+ async streamAgentResponseForUserMessage(params) {
1168
+ const { assistantMessage, agentOptions, executionTenants } = await this.prepareAgentResponseForUserMessage(params.chat, params.payload, params.previousMessages, params.userMessage, params.excludeSocketId, params.agentName);
1169
+ const messagesForAgent = [...params.previousMessages, params.userMessage];
1170
+ agentOptions.onStepFinish = (stepResult) => {
1171
+ return this.storeStepActivity({
1172
+ chat: params.chat,
1173
+ parentId: assistantMessage.activity,
1174
+ stepResult,
1175
+ name: ActivityOperationName.STEP_FINISHED,
1176
+ sourceId: assistantMessage.id,
1177
+ sourceType: 'AgentMessage',
1178
+ tenants: executionTenants,
1179
+ });
1180
+ };
1181
+ const userPreferences = await this.agentConfigRepository.findByUserIdAndAgentName(params.payload.userId ?? params.chat.userId ?? this.getChatUserId(params.chat) ?? 'guest', agentOptions.name);
1182
+ agentOptions.activeTools = this.getAvailableTools(agentOptions, userPreferences);
1183
+ await this.streamMessageStep({
1184
+ chat: params.chat,
1185
+ existingMessages: messagesForAgent,
1186
+ assistantMessage,
1187
+ agentOptions,
1188
+ excludeSocketId: params.excludeSocketId,
1189
+ signal: params.signal,
1190
+ tenants: executionTenants,
1191
+ executionContext: params.payload.executionContext,
1192
+ context: params.payload.context,
1193
+ });
1194
+ }
940
1195
  async storeStepActivity(params) {
941
1196
  try {
942
1197
  const stepResult = params.stepResult;
943
1198
  const mergedUsage = this.mergeUsageWithProviderMetadata(stepResult.usage, stepResult.providerMetadata);
944
1199
  const activity = await this.activityRepository.create({
945
- ownerId: params.chat.userId,
1200
+ ownerId: this.getChatUserId(params.chat) ?? 'guest',
946
1201
  groupId: params.chat.id,
947
1202
  name: params.name,
948
1203
  tenants: params.tenants || {},
@@ -1005,7 +1260,7 @@ export class ChatProcessor {
1005
1260
  tenants: executionTenants,
1006
1261
  });
1007
1262
  };
1008
- const userPreferences = await this.agentConfigRepository.findByUserIdAndAgentName(chat.userId, agentOptions.name);
1263
+ const userPreferences = await this.agentConfigRepository.findByUserIdAndAgentName(this.getChatUserId(chat) ?? 'guest', agentOptions.name);
1009
1264
  agentOptions.activeTools = this.getAvailableTools(agentOptions, userPreferences);
1010
1265
  await this.streamMessageStep({
1011
1266
  chat,
@@ -1135,6 +1390,86 @@ export class ChatProcessor {
1135
1390
  }
1136
1391
  return stream;
1137
1392
  }
1393
+ async upsertAndGetMultiUserChat(payload, excludeSocketId) {
1394
+ const targetUserId = payload.userId ?? 'guest';
1395
+ let chat = null;
1396
+ let isNewChat = false;
1397
+ if (payload.chatId) {
1398
+ chat = await this.chatRepository.findById(payload.chatId);
1399
+ if (!chat) {
1400
+ throw new Error('Chat not found');
1401
+ }
1402
+ if (!this.hasChatAccess(chat, targetUserId)) {
1403
+ throw new Error('Chat does not belong to this user');
1404
+ }
1405
+ const participantIds = Array.from(new Set([
1406
+ ...this.getParticipantIds(chat),
1407
+ ...(payload.participantIds ?? []),
1408
+ targetUserId,
1409
+ ].filter(Boolean)));
1410
+ const updates = {};
1411
+ if (participantIds.length !== this.getParticipantIds(chat).length) {
1412
+ updates.participantIds = participantIds;
1413
+ }
1414
+ if ('model' in payload) {
1415
+ updates.model = payload.model || undefined;
1416
+ }
1417
+ if (Object.keys(updates).length > 0) {
1418
+ chat = await this.updateChatAndEmit(chat, updates, excludeSocketId);
1419
+ }
1420
+ }
1421
+ if (!chat) {
1422
+ chat = await this.createChat(payload, excludeSocketId);
1423
+ isNewChat = true;
1424
+ }
1425
+ return { chat, isNewChat };
1426
+ }
1427
+ async postMultiUserMessage(payload, excludeSocketId) {
1428
+ const { chat } = await this.upsertAndGetMultiUserChat(payload, excludeSocketId);
1429
+ const existingMessages = await this.messageRepository.findByChatId(chat.id);
1430
+ const sender = {
1431
+ id: payload.userId ?? 'guest',
1432
+ type: 'user',
1433
+ ...(payload.senderDisplayName ? { displayName: payload.senderDisplayName } : {}),
1434
+ ...(payload.senderAvatarUrl ? { avatarUrl: payload.senderAvatarUrl } : {}),
1435
+ };
1436
+ const userMessage = await this.createMessage(chat, MessageRole.User, {
1437
+ content: payload.content,
1438
+ agentName: undefined,
1439
+ sender,
1440
+ mentions: payload.mentions ?? [],
1441
+ }, payload.attachments, excludeSocketId);
1442
+ const decision = await this.shouldRunAgentForMultiUserMessage({
1443
+ chat,
1444
+ previousMessages: existingMessages,
1445
+ userMessage,
1446
+ payload,
1447
+ });
1448
+ if (!decision.shouldRun) {
1449
+ const updatedChat = await this.updateChatAndEmit(chat, { status: AgentStatus.Finished }, excludeSocketId);
1450
+ return { chat: updatedChat, message: userMessage, agentTriggered: false };
1451
+ }
1452
+ const updatedChat = await this.updateChatAndEmit(chat, { status: AgentStatus.Streaming }, undefined);
1453
+ const abortController = this.agentStore.registerAgentProcess(chat.id);
1454
+ const limitedMessages = ContextLimiter.limitContext(existingMessages, {
1455
+ maxMessages: this.config.ai.maxContextMessages,
1456
+ keepFirstUserMessage: true,
1457
+ keepSystemMessages: true,
1458
+ });
1459
+ void this.streamAgentResponseForUserMessage({
1460
+ chat,
1461
+ payload,
1462
+ previousMessages: limitedMessages,
1463
+ userMessage,
1464
+ excludeSocketId: undefined,
1465
+ signal: abortController.signal,
1466
+ agentName: decision.agentName,
1467
+ }).catch(async (error) => {
1468
+ logger.error({ error, chatId: chat.id }, 'Multi-user background agent flow failed');
1469
+ await this.updateChatAndEmit(chat, { status: AgentStatus.Error }, undefined);
1470
+ });
1471
+ return { chat: updatedChat, message: userMessage, agentTriggered: true };
1472
+ }
1138
1473
  async createMessageStream(payload, excludeSocketId, signal) {
1139
1474
  const stream = new PassThrough();
1140
1475
  this.runMessageStream(stream, payload, excludeSocketId, signal)