[MM-67118] Add Agents to @ mention autocomplete in channel (#34881)

* Add support for autocomplete of plugin-created bots

* stashing

* Add support for including agents in the autocomplete list for @ mentions

* Change server response to be users rather than interface

* fix
This commit is contained in:
Nick Misasi
2026-01-19 16:10:57 -05:00
committed by GitHub
parent c62d103d76
commit b2f93dec1a
5 changed files with 64 additions and 0 deletions
+6
View File
@@ -1324,6 +1324,12 @@ func autocompleteUsers(c *Context, w http.ResponseWriter, r *http.Request) {
autocomplete.Users = result
}
// Fetch agent users for autocomplete
agentUsers, appErr := c.App.GetUsersForAgents(c.AppContext, c.AppContext.Session().UserId)
if appErr == nil && agentUsers != nil {
autocomplete.Agents = agentUsers
}
if err := json.NewEncoder(w).Encode(autocomplete); err != nil {
c.Logger.Warn("Error while writing response", mlog.Err(err))
}
+29
View File
@@ -101,6 +101,35 @@ func (a *App) GetAgents(rctx request.CTX, userID string) ([]agentclient.BridgeAg
return agents, nil
}
// GetUsersForAgents retrieves the User objects for all available agents
func (a *App) GetUsersForAgents(rctx request.CTX, userID string) ([]*model.User, *model.AppError) {
agents, appErr := a.GetAgents(rctx, userID)
if appErr != nil {
return nil, appErr
}
if len(agents) == 0 {
return []*model.User{}, nil
}
users := make([]*model.User, 0, len(agents))
for _, agent := range agents {
// Agents have a username field that corresponds to the bot user's username
user, err := a.Srv().Store().User().GetByUsername(agent.Username)
if err != nil {
rctx.Logger().Warn("Failed to get user for agent",
mlog.Err(err),
mlog.String("agent_id", agent.ID),
mlog.String("username", agent.Username),
)
continue
}
users = append(users, user)
}
return users, nil
}
// GetLLMServices retrieves all available LLM services from the bridge API
func (a *App) GetLLMServices(rctx request.CTX, userID string) ([]agentclient.BridgeServiceInfo, *model.AppError) {
// Check if the AI plugin is active and supports the bridge API (v1.5.0+)
+1
View File
@@ -15,4 +15,5 @@ type UserAutocompleteInTeam struct {
type UserAutocomplete struct {
Users []*User `json:"users"`
OutOfChannel []*User `json:"out_of_channel,omitempty"`
Agents []*User `json:"agents,omitempty"`
}
@@ -1,8 +1,10 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import {defineMessage} from 'react-intl';
import {CreationOutlineIcon} from '@mattermost/compass-icons/components';
import type {Group} from '@mattermost/types/groups';
import type {UserProfile} from '@mattermost/types/users';
@@ -306,6 +308,17 @@ export default class AtMentionProvider extends Provider {
// Combine the local and remote members, sorting to mix the results together.
const localAndRemoteMembers = localMembers.concat(remoteMembers).sort(orderUsers);
// Get agents - these are already User objects from the backend
// Only show agents if bridge is enabled (indicated by presence of agents data)
let agents: CreatedProfile[] = [];
if (this.data && this.data.agents && Array.isArray(this.data.agents) && this.data.agents.length > 0) {
const agentUsers = this.data.agents as UserProfileWithLastViewAt[];
agents = agentUsers.
filter((user: UserProfileWithLastViewAt) => this.filterProfile(user)).
map((user: UserProfileWithLastViewAt) => this.createFromProfile(user)).
sort(orderUsers);
}
// handle groups
const localGroups = this.localGroups();
@@ -345,6 +358,9 @@ export default class AtMentionProvider extends Provider {
if (priorityProfiles.length > 0 || localAndRemoteMembers.length > 0) {
items.push(membersGroup([...priorityProfiles, ...localAndRemoteMembers]));
}
if (agents.length > 0) {
items.push(agentsGroup(agents));
}
if (localAndRemoteGroups.length > 0) {
items.push(groupsGroup(localAndRemoteGroups));
}
@@ -453,6 +469,17 @@ export function membersGroup(items: CreatedProfile[]) {
};
}
export function agentsGroup(items: CreatedProfile[]) {
return {
key: 'agents',
label: defineMessage({id: 'suggestion.mention.agents', defaultMessage: 'Agents'}),
icon: <CreationOutlineIcon size={16}/>,
items,
terms: items.map((profile) => '@' + profile.username),
component: AtMentionSuggestion,
};
}
export function groupsGroup(items: Group[]) {
return {
key: 'groups',
+1
View File
@@ -5990,6 +5990,7 @@
"suggestion.emoji": "Emoji",
"suggestion.group_channel": "Group channel",
"suggestion.group.members": "{member_count} {member_count, plural, one {member} other {members}}",
"suggestion.mention.agents": "Agents",
"suggestion.mention.all": "Notifies everyone in this channel",
"suggestion.mention.channel": "Notifies everyone in this channel",
"suggestion.mention.channels": "My Channels",