AbstractProtectedavailableAll active agents, loaded at init for multi-word name matching. Sorted longest-name-first.
ProtecteddefaultDefault agent loaded from config DefaultAgentName.
ProtectedfallbackFallback context user (service account) loaded from config email.
ProtectedsettingsExtension settings from mj.config.cjs.
Get the list of available agent names, sorted longest-first. Useful for subclasses implementing multi-word agent name matching.
Protected AbstractPlatformHuman-readable platform name (e.g., "Slack", "Teams") used in conversation names and log messages. Subclasses must return their platform identifier.
ProtectedbuildBuild an AgentIdentity from an agent entity.
Only includes IconURL if it's a valid HTTPS URL.
ProtectedcollectCollect the files an agent produced, from the run's own fileOutputs.
This is the canonical source — fileOutputs is what MJ turns into file artifacts — and it
carries the real filename and MIME type rather than leaving them to be guessed. Entries
already saved to storage (fileId, no fileData) are skipped: those have a durable
location, and re-uploading their bytes to chat is not this method's job.
Preferred over collectInlineFileAttachments, which depends on the model choosing to
emit a data: URI — it does so only sometimes, so relying on it meant a generated document
reached chat on one run and not the next.
ProtectedcollectHarvest files that an agent inlined as data: URIs on its actionable commands.
MJ's document actions fall back to embedding the generated file itself when no file storage account is configured. That URI cannot be opened from a chat client, so the file is decoded here and handed to the platform's uploader as an ordinary attachment.
ProtecteddetectProtected AbstractfetchFetch the thread history for the given message from the platform API.
Channel/conversation ID.
Thread/reply chain ID.
Array of messages in the thread, oldest first.
Protected AbstractformatConvert an agent execution result to the platform's rich format.
The full agent execution result.
The agent that produced the result.
Extracted human-readable response text.
Optionalmetadata: AgentResponseMetadataOptional metadata about the conversation/artifact for deep linking.
Protected AbstractgetGet the bot's own user ID on this platform (to identify bot messages in thread history).
Main entry point: handle an incoming message from the platform.
Orchestrates the full flow from message receipt to response delivery. Errors are caught and reported as user-facing error messages — this method never throws.
Normalized incoming message from the platform adapter.
Initialize the adapter: resolve the fallback context user, load the default MJ agent (using the fallback user as contextUser), load all available agents, then run platform init. Must be called before handling any messages.
ProtectedisWas this history message written by ANY bot (not just this one)?
Thread affinity and conversation context must both ignore other bots' messages: an agent's reply names itself in prose, which the mention matcher would read as a user request, and as a 'user' turn it feeds one agent's self-description into another's context.
The default is false because the marker is platform-specific — see SlackAdapter, which
reads the Events API's bot_id/bot_profile/bot_message fields.
ProtectedisWas this message posted by THIS bot?
Not a bare === getBotUserId(). A platform may publish more than one identifier for the
same bot and return a different one depending on how the message was posted — on Slack a
post carrying a username/icon_url override comes back with a bot_id and NO user,
and this adapter sets that override on every agent reply so the agent answers under its own
name. Platforms with that split override this; the default is a single-identifier compare.
ProtectedisIs this artifact meant for the user, rather than internal system state?
Fails OPEN: an unreadable artifact keeps its link, since losing a working link on a transient read failure is worse than the rare case of surfacing an internal one.
Protected AbstractlookupLook up the email address for a platform user ID. Used for mapping platform identity to MJ user.
Platform-specific user ID.
Email address, or null if not available.
ProtectedmatchMatch agent names mentioned in the message text against known agents.
Strips the bot's platform mention, then checks for known agent names
preceded by @ (case-insensitive). Names are checked longest-first
to avoid prefix collisions (e.g., "Research Agent" before "Research").
The raw message text.
Array of matched agent names.
Protected AbstractonPlatform-specific initialization (e.g., fetch bot user ID, open WebSocket).
Called at the end of Initialize().
ProtectedresolveResolve which agent to use for this message.
Priority:
ProtectedresolveResolve the MJ user for the platform sender.
Flow:
lookupUserEmail() (platform API) then look upProtectedrespondsMay this bot answer a thread reply that did not address it?
false (the default) enables the multi-bot thread gate in HandleMessage: with one
app per agent, an un-mentioned thread reply would otherwise be answered by every bot in the
channel. Platforms that route a message only to its addressee — Teams, where a channel
message reaches a bot solely via an
Protected AbstractsendSend the final formatted response as a new message in the thread.
Protected AbstractsendSend or update a streaming progress message. Returns the message ID.
If existingMessageId is null, posts a new message.
If existingMessageId is provided, updates the existing message in place.
The message being responded to.
Accumulated streamed content so far.
ID of an existing progress message to update, or null.
Optionalagent: MJAIAgentEntityExtendedThe agent generating the response (for per-agent identity).
The message ID of the progress message (new or existing).
ProtectedshouldDetermine whether the bot should respond to this message.
Responds to:
Protected AbstractshowShow a typing/thinking indicator in the channel. Some platforms (Teams) have explicit typing APIs; others (Slack) use a first streaming message as the "thinking" indicator.
The incoming message triggering the indicator.
Optionalagent: MJAIAgentEntityExtendedThe agent that will respond (for per-agent identity).
Protected AbstractstripProtected AbstractupdateUpdate an existing streaming message with the final formatted response.
Protected OptionaluploadOptional per-platform file upload. Implemented by adapters whose platform can accept file
uploads (see SlackAdapter.uploadMediaOutputs); absent elsewhere, in which case binary
output is simply not delivered as files.
Abstract base class for messaging platform adapters.
Handles the common orchestration flow that is shared across all messaging platforms (Slack, Teams, Discord, etc.):
AgentRunner.RunAgent()with streamingPlatform subclasses implement the abstract methods for platform-specific operations (sending messages, fetching threads, formatting responses).
This class is NOT a
BaseServerExtensionitself — the platform-specific Extension classes (e.g.,SlackMessagingExtension) own the Express route registration and delegate message handling to their adapter instance.Multi-Agent Routing
Users can
Mention
different agents in different messages within the same thread. Each message routes to exactly one agent. If multiple agents are mentioned in a single message, the first one is used and a note is included in the response.
Multi-Word Agent Names
Agent names can be multi-word (e.g., "Research Agent"). At initialization, all active agents are loaded from the database. When parsing @mentions, known agent names are matched longest-first to avoid prefix collisions.
User Identity Mapping
The adapter resolves the platform user's email to an MJ
UserInforecord. This gives proper per-user permission scoping without a separate auth flow. Falls back to the configured service account email if no MJ user matches.