@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.
@@ -10,6 +10,9 @@ const logger_1 = require("../libs/logger/index.cjs");
10
10
  const ai_agent_types_2 = require("@multiplayer-app/ai-agent-types");
11
11
  const utils_1 = require("../utils/utils.cjs");
12
12
  class ChatProcessor {
13
+ getChatUserId(chat) {
14
+ return chat.tenants?.userId;
15
+ }
13
16
  constructor(params) {
14
17
  this.chatRepository = params.chatRepository;
15
18
  this.messageRepository = params.messageRepository;
@@ -43,14 +46,34 @@ class ChatProcessor {
43
46
  }
44
47
  return payload.tenants ?? {};
45
48
  }
49
+ getParticipantIds(chat) {
50
+ const primary = chat.userId ?? this.getChatUserId(chat);
51
+ return Array.from(new Set([primary, ...(chat.participantIds ?? [])].filter(Boolean)));
52
+ }
53
+ hasChatAccess(chat, userId) {
54
+ if (!userId)
55
+ return true;
56
+ return this.getParticipantIds(chat).includes(userId);
57
+ }
58
+ emitChatToParticipants(chat, chatToEmit, excludeSocketId) {
59
+ for (const userId of this.getParticipantIds(chatToEmit.participantIds ? chatToEmit : chat)) {
60
+ this.socketService.emitChatUpdate(userId, chatToEmit, excludeSocketId);
61
+ }
62
+ }
63
+ emitMessageToParticipants(chat, message, excludeSocketId) {
64
+ for (const userId of this.getParticipantIds(chat)) {
65
+ this.socketService.emitMessageUpdate(userId, message, excludeSocketId);
66
+ }
67
+ }
46
68
  async updateChatAndEmit(chat, update, excludeSocketId) {
47
69
  await this.chatRepository.update(chat.id, update);
48
70
  const updatedChat = await this.chatRepository.findById(chat.id);
49
71
  const chatToEmit = updatedChat ?? { ...chat, ...update };
50
- this.socketService.emitChatUpdate(chat.userId, chatToEmit, excludeSocketId);
72
+ this.emitChatToParticipants(chat, chatToEmit, excludeSocketId);
51
73
  return chatToEmit;
52
74
  }
53
75
  async startSubagentProcess(params) {
76
+ const parentUserId = params.parentChat.userId ?? this.getChatUserId(params.parentChat) ?? 'guest';
54
77
  const childChat = await this.chatRepository.create({
55
78
  title: `${params.subAgentConfig.name} subagent`,
56
79
  type: ai_agent_types_1.ChatType.Agent,
@@ -60,7 +83,8 @@ class ChatProcessor {
60
83
  parentMessageId: params.parentMessage.id,
61
84
  parentToolCallId: params.parentToolCallId,
62
85
  contextKey: params.parentChat.contextKey,
63
- userId: params.parentChat.userId,
86
+ userId: parentUserId,
87
+ tenants: { ...params.parentChat.tenants },
64
88
  model: params.subAgentConfig.defaultModel || params.parentChat.model,
65
89
  });
66
90
  try {
@@ -75,10 +99,10 @@ class ChatProcessor {
75
99
  toolCalls: params.parentMessage.toolCalls,
76
100
  });
77
101
  if (updatedParentMessage) {
78
- this.socketService.emitMessageUpdate(params.parentChat.userId, updatedParentMessage);
102
+ this.emitMessageToParticipants(params.parentChat, updatedParentMessage);
79
103
  }
80
104
  }
81
- this.socketService.emitChatUpdate(params.parentChat.userId, childChat);
105
+ this.emitChatToParticipants(params.parentChat, childChat);
82
106
  const executionTenants = this.getExecutionTenants(params);
83
107
  const abortController = this.agentStore.registerSubAgentProcess(childChat.id, params.parentChat.id);
84
108
  const userMessage = await this.createMessage(childChat, ai_agent_types_1.MessageRole.User, {
@@ -94,7 +118,7 @@ class ChatProcessor {
94
118
  agentName: params.subAgentConfig.name
95
119
  });
96
120
  const parentActivity = await this.activityRepository.create({
97
- ownerId: params.parentChat.userId,
121
+ ownerId: parentUserId,
98
122
  groupId: params.parentChat.id,
99
123
  name: ai_agent_types_1.ActivityOperationName.SUBAGENT,
100
124
  tenants: executionTenants,
@@ -131,7 +155,7 @@ class ChatProcessor {
131
155
  tenants: executionTenants,
132
156
  });
133
157
  };
134
- const userPreferences = await this.agentConfigRepository.findByUserIdAndAgentName(childChat.userId, agentOptions.name);
158
+ const userPreferences = await this.agentConfigRepository.findByUserIdAndAgentName(parentUserId, agentOptions.name);
135
159
  agentOptions.activeTools = this.getAvailableTools(agentOptions, userPreferences);
136
160
  await this.streamMessageStep({
137
161
  chat: childChat,
@@ -232,11 +256,15 @@ class ChatProcessor {
232
256
  sessionKind: ai_agent_types_2.AgentSessionKind.ROOT
233
257
  };
234
258
  if (params?.userId) {
259
+ // Chats visible to this user: owner, tenant owner, or participant (multi-user).
235
260
  filter.userId = params.userId;
236
261
  }
237
262
  if (params?.contextKey) {
238
263
  filter.contextKey = params.contextKey;
239
264
  }
265
+ if (params?.multiUser !== undefined) {
266
+ filter.multiUser = params.multiUser;
267
+ }
240
268
  // Build query options for sort and limit with defaults.
241
269
  const options = {
242
270
  sort: {
@@ -323,17 +351,12 @@ class ChatProcessor {
323
351
  return descendants;
324
352
  }
325
353
  async upsertAndGetChat(payload, excludeSocketId) {
326
- const targetUserId = payload.userId ?? 'guest';
327
354
  let chat = null;
328
355
  if (payload.chatId) {
329
356
  chat = await this.chatRepository.findById(payload.chatId);
330
357
  if (!chat) {
331
358
  throw new Error('Chat not found');
332
359
  }
333
- // Ensure chat is associated with the caller's userId
334
- if (chat.userId && chat.userId !== targetUserId) {
335
- throw new Error('Chat does not belong to this user');
336
- }
337
360
  }
338
361
  if (!chat) {
339
362
  chat = await this.createChat(payload, excludeSocketId);
@@ -366,19 +389,22 @@ class ChatProcessor {
366
389
  role,
367
390
  content: messageData.content,
368
391
  agentName: messageData.agentName,
392
+ sender: messageData.sender,
393
+ mentions: messageData.mentions ?? [],
394
+ annotations: messageData.annotations,
369
395
  attachments: attachments ?? [],
370
396
  reasoning: "",
371
397
  toolCalls: []
372
398
  });
373
- if (chat.userId) {
374
- this.socketService.emitMessageUpdate(chat.userId, message, excludeSocketId);
375
- }
399
+ this.emitMessageToParticipants(chat, message, excludeSocketId);
376
400
  return message;
377
401
  }
378
402
  async createChat(payload, excludeSocketId) {
379
- const targetUserId = payload.userId ?? 'guest';
403
+ const targetUserId = payload.userId ?? payload.tenants?.userId ?? 'guest';
380
404
  const contextKey = 'contextKey' in payload ? payload.contextKey : 'default';
381
405
  const metadata = 'metadata' in payload ? payload.metadata : {};
406
+ const requestedParticipantIds = 'participantIds' in payload && Array.isArray(payload.participantIds) ? payload.participantIds : [];
407
+ const participantIds = Array.from(new Set([targetUserId, ...requestedParticipantIds].filter(Boolean)));
382
408
  const chat = await this.chatRepository.create({
383
409
  title: this.getTemporaryTitle(contextKey),
384
410
  type: ai_agent_types_1.ChatType.Chat,
@@ -386,10 +412,15 @@ class ChatProcessor {
386
412
  sessionKind: ai_agent_types_2.AgentSessionKind.ROOT,
387
413
  contextKey,
388
414
  userId: targetUserId,
415
+ tenants: {
416
+ ...(payload.tenants ?? {}),
417
+ userId: targetUserId,
418
+ },
419
+ participantIds,
389
420
  metadata,
390
421
  ...(payload.model ? { model: payload.model } : {})
391
422
  });
392
- this.socketService.emitChatUpdate(targetUserId, chat, excludeSocketId);
423
+ this.emitChatToParticipants(chat, chat, excludeSocketId);
393
424
  return chat;
394
425
  }
395
426
  /**
@@ -444,7 +475,7 @@ class ChatProcessor {
444
475
  await this.messageRepository.update(message.id, { toolCalls });
445
476
  // Bump chat updatedAt so listChats ordering reflects the action.
446
477
  await this.chatRepository.update(chat.id, { updatedAt: now });
447
- this.socketService.emitMessageUpdate(chat.userId, updatedMessage, params.excludeSocketId);
478
+ this.emitMessageToParticipants(chat, updatedMessage, params.excludeSocketId);
448
479
  if (params.systemMessage) {
449
480
  //todo discuss support for system messages
450
481
  //await this.createMessage(chat, MessageRole.System, params.systemMessage, undefined, params.excludeSocketId);
@@ -465,7 +496,7 @@ class ChatProcessor {
465
496
  if (!chat) {
466
497
  throw new Error('Chat not found');
467
498
  }
468
- if (params.userId && chat.userId !== params.userId) {
499
+ if (params.userId && !this.hasChatAccess(chat, params.userId)) {
469
500
  throw new Error('Chat does not belong to this user');
470
501
  }
471
502
  const message = await this.messageRepository.findById(params.messageId);
@@ -494,7 +525,7 @@ class ChatProcessor {
494
525
  }
495
526
  const now = new Date().toISOString();
496
527
  await this.chatRepository.update(chat.id, { updatedAt: now });
497
- this.socketService.emitMessageUpdate(chat.userId, updatedMessage, params.excludeSocketId);
528
+ this.emitMessageToParticipants(chat, updatedMessage, params.excludeSocketId);
498
529
  return updatedMessage;
499
530
  }
500
531
  async storeSubagentResponse(params) {
@@ -551,7 +582,7 @@ class ChatProcessor {
551
582
  tenants: executionTenants,
552
583
  });
553
584
  };
554
- const userPreferences = await this.agentConfigRepository.findByUserIdAndAgentName(chat.userId, agentOptions.name);
585
+ const userPreferences = await this.agentConfigRepository.findByUserIdAndAgentName(this.getChatUserId(chat) ?? chat.userId ?? 'guest', agentOptions.name);
555
586
  agentOptions.activeTools = this.getAvailableTools(agentOptions, userPreferences);
556
587
  await this.streamMessageStep({
557
588
  chat,
@@ -568,7 +599,7 @@ class ChatProcessor {
568
599
  const { chat, assistantMessage, existingMessages, signal, excludeSocketId, agentOptions } = params;
569
600
  const persistAndBroadcastToolCalls = async () => {
570
601
  await this.messageRepository.update(assistantMessage.id, { toolCalls: assistantMessage.toolCalls ?? [] });
571
- this.socketService.emitMessageUpdate(chat.userId, assistantMessage, excludeSocketId);
602
+ this.emitMessageToParticipants(chat, assistantMessage, excludeSocketId);
572
603
  await this.agentStore.shareAgentProcessEvent(chat.id, { type: store_1.AgentProcessEventType.Update, data: assistantMessage });
573
604
  };
574
605
  if (signal.aborted) {
@@ -600,7 +631,7 @@ class ChatProcessor {
600
631
  await this.agentStore.shareAgentProcessEvent(chat.id, { type: store_1.AgentProcessEventType.Aborted, data: undefined });
601
632
  await this.updateChatAndEmit(chat, { status: ai_agent_types_1.AgentStatus.Aborted }, excludeSocketId);
602
633
  await this.messageRepository.update(assistantMessage.id, assistantMessage);
603
- this.socketService.emitMessageUpdate(chat.userId, assistantMessage, excludeSocketId);
634
+ this.emitMessageToParticipants(chat, assistantMessage, excludeSocketId);
604
635
  continue;
605
636
  }
606
637
  if (chunk.type === 'finish') {
@@ -617,7 +648,7 @@ class ChatProcessor {
617
648
  ((totalUsage.promptTokens ?? 0) + (totalUsage.completionTokens ?? 0));
618
649
  }
619
650
  await this.messageRepository.update(assistantMessage.id, { ...assistantMessage });
620
- this.socketService.emitMessageUpdate(chat.userId, assistantMessage, excludeSocketId);
651
+ this.emitMessageToParticipants(chat, assistantMessage, excludeSocketId);
621
652
  if (chunk.finishReason === 'stop') {
622
653
  let nextChat = await this.updateChatAndEmit(chat, { status: ai_agent_types_1.AgentStatus.Finished }, excludeSocketId);
623
654
  if (chat.title && this.isTemporaryTitle(chat.title)) {
@@ -695,7 +726,7 @@ class ChatProcessor {
695
726
  ...assistantMessage,
696
727
  content: assistantMessage.content
697
728
  };
698
- this.socketService.emitMessageUpdate(chat.userId, updatedMessage, excludeSocketId);
729
+ this.emitMessageToParticipants(chat, updatedMessage, excludeSocketId);
699
730
  await this.agentStore.shareAgentProcessEvent(chat.id, { type: store_1.AgentProcessEventType.Update, data: updatedMessage });
700
731
  await this.messageRepository.update(assistantMessage.id, { content: assistantMessage.content });
701
732
  continue;
@@ -781,7 +812,7 @@ class ChatProcessor {
781
812
  if (!assistantMessage.reasoning)
782
813
  assistantMessage.reasoning = '';
783
814
  assistantMessage.reasoning += chunk.text;
784
- this.socketService.emitMessageUpdate(chat.userId, assistantMessage, excludeSocketId);
815
+ this.emitMessageToParticipants(chat, assistantMessage, excludeSocketId);
785
816
  await this.messageRepository.update(assistantMessage.id, { reasoning: assistantMessage.reasoning });
786
817
  await this.agentStore.shareAgentProcessEvent(chat.id, { type: store_1.AgentProcessEventType.Update, data: assistantMessage });
787
818
  continue;
@@ -864,11 +895,12 @@ class ChatProcessor {
864
895
  }
865
896
  async processNewUserMessage(chat, payload, prevMessages, excludeSocketId) {
866
897
  const executionTenants = this.getExecutionTenants(payload);
898
+ const chatUserId = this.getChatUserId(chat) ?? 'guest';
867
899
  const userMessage = await this.createMessage(chat, ai_agent_types_1.MessageRole.User, { content: payload.content, agentName: undefined }, payload.attachments);
868
900
  await this.agentStore.shareAgentProcessEvent(chat.id, { type: store_1.AgentProcessEventType.Update, data: userMessage });
869
901
  let assistantMessage = await this.createMessage(chat, ai_agent_types_1.MessageRole.Assistant, { content: '', agentName: undefined }, undefined, excludeSocketId);
870
902
  const parentActivity = await this.activityRepository.create({
871
- ownerId: chat.userId,
903
+ ownerId: chatUserId,
872
904
  groupId: chat.id,
873
905
  name: ai_agent_types_1.ActivityOperationName.MESSAGE_PROCESSING,
874
906
  tenants: executionTenants,
@@ -930,12 +962,235 @@ class ChatProcessor {
930
962
  agentOptions,
931
963
  };
932
964
  }
965
+ getAgentNamesForContext(contextKey) {
966
+ try {
967
+ return store_1.ConfigStore.getInstance().getAgentsForContext(contextKey).map((config) => config.name);
968
+ }
969
+ catch {
970
+ return [];
971
+ }
972
+ }
973
+ normalizeMentionName(value) {
974
+ return value.trim().replace(/^@/, '').toLowerCase();
975
+ }
976
+ extractMentionNames(content) {
977
+ const mentions = content.matchAll(/(^|[\s([{])@([a-zA-Z0-9_.-]+)/g);
978
+ return Array.from(mentions, (match) => match[2]).filter(Boolean);
979
+ }
980
+ getExplicitAgentMention(params) {
981
+ const agentNames = this.getAgentNamesForContext(params.contextKey);
982
+ const normalizedAgentNames = new Map(agentNames.map((name) => [this.normalizeMentionName(name), name]));
983
+ for (const mention of params.mentions ?? []) {
984
+ if (mention.type !== 'agent')
985
+ continue;
986
+ const normalized = this.normalizeMentionName(mention.name);
987
+ return { mentioned: true, agentName: normalizedAgentNames.get(normalized) };
988
+ }
989
+ for (const name of this.extractMentionNames(params.content)) {
990
+ const normalized = this.normalizeMentionName(name);
991
+ if (normalized === 'agent') {
992
+ return { mentioned: true };
993
+ }
994
+ const agentName = normalizedAgentNames.get(normalized);
995
+ if (agentName) {
996
+ return { mentioned: true, agentName };
997
+ }
998
+ }
999
+ return { mentioned: false };
1000
+ }
1001
+ hasNonAgentMention(params) {
1002
+ const agentNames = this.getAgentNamesForContext(params.contextKey);
1003
+ const normalizedAgentNames = new Set(['agent', ...agentNames.map((name) => this.normalizeMentionName(name))]);
1004
+ if ((params.mentions ?? []).some((mention) => mention.type === 'user')) {
1005
+ return true;
1006
+ }
1007
+ return this.extractMentionNames(params.content).some((name) => {
1008
+ const normalized = this.normalizeMentionName(name);
1009
+ return !normalizedAgentNames.has(normalized);
1010
+ });
1011
+ }
1012
+ renderMessageForDecision(message) {
1013
+ const sender = message.sender?.displayName || message.sender?.id || message.agentName || message.role;
1014
+ return `${sender}: ${message.content}`;
1015
+ }
1016
+ async analyzeAgentShouldRespond(params) {
1017
+ try {
1018
+ const recentMessages = [...params.previousMessages.slice(-8), params.userMessage]
1019
+ .map((message) => this.renderMessageForDecision(message))
1020
+ .join('\n');
1021
+ const response = await this.aiHelper.getAssistantResponse([
1022
+ {
1023
+ role: ai_agent_types_1.MessageRole.User,
1024
+ content: `Conversation:\n${recentMessages}\n\nShould the AI agent respond to the latest message?`
1025
+ }
1026
+ ], {
1027
+ system: [
1028
+ 'You decide whether an AI agent should reply in a multi-user chat.',
1029
+ 'Reply with exactly YES or NO.',
1030
+ 'YES only when the latest message asks for help, requests agent action, or is clearly addressed to the AI.',
1031
+ 'NO when the latest message is human-to-human chatter, status, acknowledgement, or otherwise does not need the AI.'
1032
+ ].join('\n'),
1033
+ temperature: 0,
1034
+ maxOutputTokens: 5,
1035
+ model: params.chat.model,
1036
+ executionContext: params.executionContext
1037
+ });
1038
+ return /^yes\b/i.test(response.trim());
1039
+ }
1040
+ catch (error) {
1041
+ logger_1.logger.warn({ error, chatId: params.chat.id }, 'Agent response decision failed');
1042
+ return false;
1043
+ }
1044
+ }
1045
+ async shouldRunAgentForMultiUserMessage(params) {
1046
+ const firstUserMessage = !params.previousMessages.some((message) => message.role === ai_agent_types_1.MessageRole.User);
1047
+ if (firstUserMessage) {
1048
+ return { shouldRun: true };
1049
+ }
1050
+ const explicitAgent = this.getExplicitAgentMention({
1051
+ content: params.payload.content,
1052
+ mentions: params.payload.mentions,
1053
+ contextKey: params.chat.contextKey,
1054
+ });
1055
+ if (explicitAgent.mentioned) {
1056
+ return { shouldRun: true, agentName: explicitAgent.agentName };
1057
+ }
1058
+ if (this.hasNonAgentMention({
1059
+ content: params.payload.content,
1060
+ mentions: params.payload.mentions,
1061
+ contextKey: params.chat.contextKey,
1062
+ })) {
1063
+ return { shouldRun: false };
1064
+ }
1065
+ const shouldRun = await this.analyzeAgentShouldRespond({
1066
+ chat: params.chat,
1067
+ previousMessages: params.previousMessages,
1068
+ userMessage: params.userMessage,
1069
+ executionContext: params.payload.executionContext,
1070
+ });
1071
+ return { shouldRun };
1072
+ }
1073
+ async prepareAgentResponseForUserMessage(chat, payload, prevMessages, userMessage, excludeSocketId, agentName) {
1074
+ const executionTenants = this.getExecutionTenants(payload);
1075
+ let assistantMessage = await this.createMessage(chat, ai_agent_types_1.MessageRole.Assistant, {
1076
+ content: '',
1077
+ agentName: agentName,
1078
+ sender: {
1079
+ id: agentName ?? 'agent',
1080
+ type: 'agent',
1081
+ displayName: agentName ?? 'Agent',
1082
+ }
1083
+ }, undefined, excludeSocketId);
1084
+ const ownerId = payload.userId ?? chat.userId ?? this.getChatUserId(chat) ?? 'guest';
1085
+ const parentActivity = await this.activityRepository.create({
1086
+ ownerId,
1087
+ groupId: chat.id,
1088
+ name: ai_agent_types_1.ActivityOperationName.MESSAGE_PROCESSING,
1089
+ tenants: executionTenants,
1090
+ sourceId: userMessage.id,
1091
+ sourceType: 'AgentMessage',
1092
+ metadata: {
1093
+ contextKey: chat.contextKey,
1094
+ modelId: this.aiHelper.getLanguageModelId(chat.model),
1095
+ },
1096
+ });
1097
+ const agentOptions = await this.aiHelper.getAgentOptions(agentName
1098
+ ? {
1099
+ agentName,
1100
+ modelId: chat.model,
1101
+ context: payload.context,
1102
+ executionContext: payload.executionContext,
1103
+ }
1104
+ : {
1105
+ contextKey: chat.contextKey,
1106
+ messages: [...prevMessages, userMessage],
1107
+ context: payload.context,
1108
+ agentName: undefined,
1109
+ modelId: chat.model,
1110
+ executionContext: payload.executionContext,
1111
+ }, {
1112
+ onStepFinish: async (stepResult) => {
1113
+ await this.storeStepActivity({
1114
+ chat,
1115
+ parentId: parentActivity.id,
1116
+ stepResult,
1117
+ name: ai_agent_types_1.ActivityOperationName.AGENT_SELECTION,
1118
+ sourceId: userMessage.id,
1119
+ sourceType: 'AgentMessage',
1120
+ tenants: executionTenants,
1121
+ });
1122
+ }
1123
+ });
1124
+ const updatedAssistantMessage = await this.messageRepository.update(assistantMessage.id, {
1125
+ agentName: agentOptions.name,
1126
+ activity: parentActivity.id,
1127
+ sender: {
1128
+ id: agentOptions.name,
1129
+ type: 'agent',
1130
+ displayName: agentOptions.name,
1131
+ }
1132
+ });
1133
+ await this.activityRepository.update(parentActivity.id, {
1134
+ metadata: {
1135
+ ...parentActivity.metadata,
1136
+ agentOptions: {
1137
+ name: agentOptions.name,
1138
+ modelId: this.aiHelper.getLanguageModelId(agentOptions.model),
1139
+ temperature: agentOptions.temperature,
1140
+ maxOutputTokens: agentOptions.maxOutputTokens,
1141
+ topP: agentOptions.topP,
1142
+ topK: agentOptions.topK,
1143
+ presencePenalty: agentOptions.presencePenalty,
1144
+ frequencyPenalty: agentOptions.frequencyPenalty,
1145
+ stopSequences: agentOptions.stopSequences,
1146
+ seed: agentOptions.seed,
1147
+ },
1148
+ },
1149
+ });
1150
+ if (!updatedAssistantMessage) {
1151
+ throw new Error(`Assistant message with id ${assistantMessage.id} not found after update`);
1152
+ }
1153
+ assistantMessage = updatedAssistantMessage;
1154
+ return {
1155
+ assistantMessage,
1156
+ agentOptions,
1157
+ executionTenants,
1158
+ };
1159
+ }
1160
+ async streamAgentResponseForUserMessage(params) {
1161
+ const { assistantMessage, agentOptions, executionTenants } = await this.prepareAgentResponseForUserMessage(params.chat, params.payload, params.previousMessages, params.userMessage, params.excludeSocketId, params.agentName);
1162
+ const messagesForAgent = [...params.previousMessages, params.userMessage];
1163
+ agentOptions.onStepFinish = (stepResult) => {
1164
+ return this.storeStepActivity({
1165
+ chat: params.chat,
1166
+ parentId: assistantMessage.activity,
1167
+ stepResult,
1168
+ name: ai_agent_types_1.ActivityOperationName.STEP_FINISHED,
1169
+ sourceId: assistantMessage.id,
1170
+ sourceType: 'AgentMessage',
1171
+ tenants: executionTenants,
1172
+ });
1173
+ };
1174
+ const userPreferences = await this.agentConfigRepository.findByUserIdAndAgentName(params.payload.userId ?? params.chat.userId ?? this.getChatUserId(params.chat) ?? 'guest', agentOptions.name);
1175
+ agentOptions.activeTools = this.getAvailableTools(agentOptions, userPreferences);
1176
+ await this.streamMessageStep({
1177
+ chat: params.chat,
1178
+ existingMessages: messagesForAgent,
1179
+ assistantMessage,
1180
+ agentOptions,
1181
+ excludeSocketId: params.excludeSocketId,
1182
+ signal: params.signal,
1183
+ tenants: executionTenants,
1184
+ executionContext: params.payload.executionContext,
1185
+ context: params.payload.context,
1186
+ });
1187
+ }
933
1188
  async storeStepActivity(params) {
934
1189
  try {
935
1190
  const stepResult = params.stepResult;
936
1191
  const mergedUsage = this.mergeUsageWithProviderMetadata(stepResult.usage, stepResult.providerMetadata);
937
1192
  const activity = await this.activityRepository.create({
938
- ownerId: params.chat.userId,
1193
+ ownerId: this.getChatUserId(params.chat) ?? 'guest',
939
1194
  groupId: params.chat.id,
940
1195
  name: params.name,
941
1196
  tenants: params.tenants || {},
@@ -998,7 +1253,7 @@ class ChatProcessor {
998
1253
  tenants: executionTenants,
999
1254
  });
1000
1255
  };
1001
- const userPreferences = await this.agentConfigRepository.findByUserIdAndAgentName(chat.userId, agentOptions.name);
1256
+ const userPreferences = await this.agentConfigRepository.findByUserIdAndAgentName(this.getChatUserId(chat) ?? 'guest', agentOptions.name);
1002
1257
  agentOptions.activeTools = this.getAvailableTools(agentOptions, userPreferences);
1003
1258
  await this.streamMessageStep({
1004
1259
  chat,
@@ -1128,6 +1383,86 @@ class ChatProcessor {
1128
1383
  }
1129
1384
  return stream;
1130
1385
  }
1386
+ async upsertAndGetMultiUserChat(payload, excludeSocketId) {
1387
+ const targetUserId = payload.userId ?? 'guest';
1388
+ let chat = null;
1389
+ let isNewChat = false;
1390
+ if (payload.chatId) {
1391
+ chat = await this.chatRepository.findById(payload.chatId);
1392
+ if (!chat) {
1393
+ throw new Error('Chat not found');
1394
+ }
1395
+ if (!this.hasChatAccess(chat, targetUserId)) {
1396
+ throw new Error('Chat does not belong to this user');
1397
+ }
1398
+ const participantIds = Array.from(new Set([
1399
+ ...this.getParticipantIds(chat),
1400
+ ...(payload.participantIds ?? []),
1401
+ targetUserId,
1402
+ ].filter(Boolean)));
1403
+ const updates = {};
1404
+ if (participantIds.length !== this.getParticipantIds(chat).length) {
1405
+ updates.participantIds = participantIds;
1406
+ }
1407
+ if ('model' in payload) {
1408
+ updates.model = payload.model || undefined;
1409
+ }
1410
+ if (Object.keys(updates).length > 0) {
1411
+ chat = await this.updateChatAndEmit(chat, updates, excludeSocketId);
1412
+ }
1413
+ }
1414
+ if (!chat) {
1415
+ chat = await this.createChat(payload, excludeSocketId);
1416
+ isNewChat = true;
1417
+ }
1418
+ return { chat, isNewChat };
1419
+ }
1420
+ async postMultiUserMessage(payload, excludeSocketId) {
1421
+ const { chat } = await this.upsertAndGetMultiUserChat(payload, excludeSocketId);
1422
+ const existingMessages = await this.messageRepository.findByChatId(chat.id);
1423
+ const sender = {
1424
+ id: payload.userId ?? 'guest',
1425
+ type: 'user',
1426
+ ...(payload.senderDisplayName ? { displayName: payload.senderDisplayName } : {}),
1427
+ ...(payload.senderAvatarUrl ? { avatarUrl: payload.senderAvatarUrl } : {}),
1428
+ };
1429
+ const userMessage = await this.createMessage(chat, ai_agent_types_1.MessageRole.User, {
1430
+ content: payload.content,
1431
+ agentName: undefined,
1432
+ sender,
1433
+ mentions: payload.mentions ?? [],
1434
+ }, payload.attachments, excludeSocketId);
1435
+ const decision = await this.shouldRunAgentForMultiUserMessage({
1436
+ chat,
1437
+ previousMessages: existingMessages,
1438
+ userMessage,
1439
+ payload,
1440
+ });
1441
+ if (!decision.shouldRun) {
1442
+ const updatedChat = await this.updateChatAndEmit(chat, { status: ai_agent_types_1.AgentStatus.Finished }, excludeSocketId);
1443
+ return { chat: updatedChat, message: userMessage, agentTriggered: false };
1444
+ }
1445
+ const updatedChat = await this.updateChatAndEmit(chat, { status: ai_agent_types_1.AgentStatus.Streaming }, undefined);
1446
+ const abortController = this.agentStore.registerAgentProcess(chat.id);
1447
+ const limitedMessages = helpers_1.ContextLimiter.limitContext(existingMessages, {
1448
+ maxMessages: this.config.ai.maxContextMessages,
1449
+ keepFirstUserMessage: true,
1450
+ keepSystemMessages: true,
1451
+ });
1452
+ void this.streamAgentResponseForUserMessage({
1453
+ chat,
1454
+ payload,
1455
+ previousMessages: limitedMessages,
1456
+ userMessage,
1457
+ excludeSocketId: undefined,
1458
+ signal: abortController.signal,
1459
+ agentName: decision.agentName,
1460
+ }).catch(async (error) => {
1461
+ logger_1.logger.error({ error, chatId: chat.id }, 'Multi-user background agent flow failed');
1462
+ await this.updateChatAndEmit(chat, { status: ai_agent_types_1.AgentStatus.Error }, undefined);
1463
+ });
1464
+ return { chat: updatedChat, message: userMessage, agentTriggered: true };
1465
+ }
1131
1466
  async createMessageStream(payload, excludeSocketId, signal) {
1132
1467
  const stream = new stream_1.PassThrough();
1133
1468
  this.runMessageStream(stream, payload, excludeSocketId, signal)