ProtectedavailableAll 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.
ProtectedPlatformHuman-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.
ProtecteddetectProtectedfetchFetch all messages in a Slack thread using conversations.replies.
ProtectedformatFormat agent response as rich Slack Block Kit blocks.
When a full ExecuteAgentResult is available, builds a rich layout with
agent header, artifact cards, action buttons, and metadata footer.
Falls back to simple markdown→blocks conversion for plain text.
Optionalmetadata: AgentResponseMetadataProtectedgetGet 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?
Slack's Events API marks them three different ways depending on how the message was posted:
a bot_id, a bot_profile, or the bot_message subtype. Kept here rather than on the base
class because these are Slack payload fields; other platforms answer the question their own
way.
ProtectedisEither identifier counts as this bot.
chat:write.customize is required for per-agent identity, and a post made with it comes
back as subtype: 'bot_message' with a bot_id and no user. Comparing only against
user_id therefore never matched this adapter's own replies — which silently made the
multi-bot thread gate decline threads this bot was actively holding, and dropped the
agent's own turns from its context.
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.
ProtectedlookupLook up a Slack user's email address via the Web API.
Requires the users:read.email scope on the bot token.
Slack user ID (e.g., U0123456).
Email address, or null if not available.
Convert a raw Slack event payload into a normalized IncomingMessage.
Called by SlackMessagingExtension when a webhook event is received.
Uses multi-word agent name matching against the loaded agent list.
Raw Slack event object from the Events API.
Normalized incoming message.
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.
ProtectedonFetch the bot's own user ID from the Slack API. This is needed to identify bot messages in thread history.
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
ProtectedsendSend the final formatted response. If a "Thinking..." message exists (no streaming occurred), update it in-place instead of posting a new message.
ProtectedsendPost a new streaming message or update an existing one. Shows the agent's identity on new messages.
Optionalagent: MJAIAgentEntityExtendedProtectedshouldDetermine whether the bot should respond to this message.
Responds to:
ProtectedshowPost a "Thinking..." message as the typing indicator. Shows the agent's identity if available. This message gets replaced in-place by the first streaming update.
Optionalagent: MJAIAgentEntityExtendedProtectedstripProtectedupdateUpdate the streaming progress message with the final formatted response.
ProtecteduploadUpload an agent's binary output as real Slack files, threaded under the reply.
Slack's image blocks can only reference a public https URL, so base64 media — every
generated image, and any file inlined as a data: URI — could not be rendered and was
silently dropped: the agent did the work, the artifact existed, and the user saw nothing.
Uploading makes it an ordinary attachment the user can open or download.
Requires the files:write bot scope; without it Slack answers missing_scope, which the
caller logs (the text reply has already posted by then).
Slack-specific adapter that implements all platform operations using the Slack Web API (
@slack/web-api).Features
conversations.replieschat.postMessage/chat.update@Research Agent)users.infofor MJ user mappingAuthentication
Requires a Bot User OAuth Token (
xoxb-...) with these scopes:chat:write— Post and update messageschat:write.customize— Post with custom username and iconchannels:history/groups:history/im:history— Read thread historyusers:read/users:read.email— Look up user email addressesapp_mentions:read— ReceiveMention
events