Navigácia
ELOWEN · Dokumentácia

Posledná aktualizácia: 20. októbra 2026

Prechádzať dokumentáciu · Microsoft 365 maintainer map

Tento článok zatiaľ nie je preložený. Je dostupný v angličtine.

Developer reference

msteams — maintainer documentation

References name symbols in the files listed rather than line numbers, so they stay valid as the code moves. For the complete, current config and tool reference, see the published Microsoft Teams & Microsoft 365 page.

Four files, no overlap:

  • maintainer.md (this file) — what the plugin is, what it needs, what it exposes, which file owns what.
  • architecture.md — how a message actually travels through the code, and what is persisted.
  • operations.md — Azure/Teams setup, config semantics, failure modes, redeploy behaviour.
  • permissions.md — every Microsoft Graph and Teams permission the plugin can need, which of the four consent surfaces it belongs to, and the checklist for standing a new tenant up the same way.

What it is

A Microsoft Teams platform adapter built on the Azure Bot Framework. Inbound Bot Framework activities arrive on the daemon webhook /hooks/msteams/messages; outbound replies, typing indicators, card edits and images go out through the Bot Connector REST API. It registers itself as an Elowen PlatformAdapter (register in index.mjs), so a Teams conversation becomes a brain channel session the same way a Discord channel or a Telegram chat does.

On top of plain chat it provides: a live tool trace edited in place, AskUserQuestion rendered as Adaptive Cards, slash commands with card pickers, per-chat model/reasoning/display overrides, image round-trips, proactive (host-initiated) pushes, and a downloadable Teams app package.

It can also write to a person first rather than only reply: it builds a people directory out of traffic it already sees, and TeamsMessagePerson opens the 1:1 chat itself. See "Proactive messaging" below and the detail in architecture.md.

What it needs to run

Credential gate

The plugin registers the app-package API route unconditionally, then requires all three of appId, appPassword and tenantId before it does anything else (register in index.mjs). With any of them missing it logs enabled but appId/appPassword/tenantId are not all configured — not connecting and returns: no platform, no webhook, no tools. Pinned by tests/msteams.test.ts (no platform and no HTTP routes without credentials, and both appear once they are configured).

Azure / Teams prerequisites

Summarised here, with the click-path in operations.md:

  1. An Entra app registration (single tenant) — its Application (client) ID is appId, the directory ID is tenantId, and a client secret is appPassword.
  2. An Azure Bot resource bound to that app id, with the messaging endpoint set to https://<your-domain>/hooks/msteams/messages and the Microsoft Teams channel enabled.
  3. The Teams app package (downloadable from this plugin — see below) uploaded to the org's Teams app catalog so users can install the bot.

Nothing beyond the bot credentials is required for normal chat: the member and roster lookups the plugin uses for UPN/e-mail resolution are Bot Connector calls (ConnectorClient.member and ConnectorClient.members in lib/connector.mjs), not Graph calls. Microsoft Graph is a separate, optional layer — see graphLookup below.

Config fields

Declared in the configSchema of elowen-plugin.json; Czech and Slovak translations of every label and hint live in i18n/cs.json and i18n/sk.json.

Key Type Default What it does
appId string, required — Entra Application (client) ID; also the bot id. Used as the JWT audience (makeTokenVerifier in lib/auth.mjs) and to build the bot member id 28:<appId> (openPersonalConversation in lib/proactive.mjs, personalSignInLink in lib/adapter.mjs).
appPassword secret, required — Client secret for the client-credentials token (TokenSource in lib/token.mjs).
tenantId string, required — Tenant-scoped OAuth token endpoint (TokenSource.tokenUrl in lib/token.mjs) and the tenant stamped on a newly opened 1:1 chat (openPersonalConversation).
notifyConversationId string empty Default target for proactive pushes. A conversation id, or a person (e-mail / Entra object ID / display name) whose 1:1 chat the bot opens (notify and notifyConversationFor in lib/proactive.mjs). Empty = pushes are dropped with a warning.
graphLookup boolean false Enables the optional Microsoft Graph layer. Off means the adapter constructor never constructs a GraphClient. The channel-history reader is created only when channelMessagesRsc is on (threadReader in lib/transcript.mjs).
graphCatalogAppId string empty The app's id in the org Teams catalog, needed to install the app for a user (installApp in lib/graph.mjs). Only visible when graphLookup is on.
respondWithoutMention boolean true When false, a non-personal conversation is answered only if the bot is @mentioned (the mention gate in handleActivity).
toolActivity enum off/status/live status What the progress message shows while the agent works.
answerMode enum final/live final One reply at the end, or the answer streamed into a message.
toolOutput enum hidden/summary/tail summary How much of a finished tool's result the progress message keeps.
toolMessageMode enum single/per_tool single One edited progress message, or one message per tool call.
runtimeFooter boolean true Appends the model · context % line (footerLine in lib/format.mjs).
showReasoning boolean false Streams model reasoning into the progress message. Also forces a live stream to exist (observesLiveEvents in elowen-plugin-shared/display).
language enum en/cs/sk en Language of the bot's own service texts (lib/messages.mjs). Not the agent's answer language.
historyLimit number 0–100 0 How many remembered messages seed a brand-new conversation. 0 means nothing is written to disk (recordHistory in lib/transcript.mjs).
visionModel model empty Model used for turns carrying image attachments (handleActivity).
maxImageBytes number 5242880 Largest inbound image the bot downloads, clamped to 1 MiB–20 MiB (collectMedia in lib/adapter.mjs).
maxImages number 1–10 4 Inbound image attachments sent to the vision model (collectMedia).
maxUploadImages number 1–10 4 Images shared with ShareImage attached to one outgoing reply (resolveImageFiles).
rolePolicies rolePolicies — Sender → role name, extra role prompt and the admin flag. First match wins; unmapped senders are ignored (accessFor in lib/adapter.mjs).

The table covers the core adapter settings. Every other key in configSchema (personal-chat sign-in, Microsoft sign-in on the web login page, Microsoft 365 access, channel history, app branding, reply behaviour) is documented with its current default in the Configuration section of the published page. agentName is in configSchema; see operations.md for how empty values fall back.

Two further keys are read by the code but are not in configSchema, so the settings UI never offers them. Both are E2E seams used by tests/e2e/msteams/:

  • openIdMetadataUrl (makeTokenVerifier in lib/auth.mjs), the Bot Framework OpenID metadata URL.
  • oauthTokenUrl (the ConnectorClient constructor in lib/connector.mjs, read by TokenSource.tokenUrl in lib/token.mjs), the token endpoint override.

Tools it exposes

Registered by registerTools in lib/tools.mjs and declared in elowen-plugin.json. Tool availability is decided by the linked account's plugin grants and per-user tool deny-list in the users modal.

Tool Plan-safe What it does
TeamsSend no Post into a conversation by id.
TeamsMessagePerson no Message a person by e-mail / Entra object id / 29:… id / display name. A linked recipient's own agent writes the reply; otherwise the 1:1 chat is opened if needed and the text is sent directly.
TeamsFindPerson yes Read-only directory lookup; sends nothing. Lists at most 25 matches and sweeps the known group rosters on a miss.
TeamsChatInfo yes Conversation type, tenant, member count.
TeamsMembers yes Fresh roster read, at most 50 listed; also feeds the people directory.
TeamsMemberInfo yes One member's name / Entra id / UPN.
TeamsListConversations yes Conversations on the current service host, paged.
TeamsSendFile no Offer a file in a 1:1 chat; uploaded to the recipient's OneDrive only after they accept the card. In a 1:1 chat with no recipient named, the current sender is the recipient.
TeamsApi no Raw Bot Connector REST: any method + path. Output truncated at 4000 characters.

That is 9 Teams* tools; the plugin also exposes 8 Microsoft* tools for the signed-in person's delegated Microsoft 365 access (directory, SharePoint, files, Outlook, tasks, OneNote, Excel, Teams as that person) — see microsoft-365-plugin.md for the full list and their access mode. readOnlyTools covers exactly the five read-only Teams* tools listed above the table split, plus MicrosoftDirectory, whose actions are all reads. The tools that write are deliberately absent, so plan mode and read-only sub-agents withhold them. The manifest also makes every Teams tool's output visible in the transcript.

The permission behavior is pinned by tests in tests/msteams.test.ts: a project-scoped, non-owner session can reach curated and raw connector tools when the registry exposes them, while input validation such as requiring a named recipient still applies.

Proactive messaging

The Bot Connector cannot be addressed by e-mail — it only takes a conversationId. So the plugin builds a people directory (lib/directory.mjs) out of what it already sees: every inbound activity and every conversation roster carries a 29: account id, an Entra aadObjectId, a display name and (from the roster) a UPN/e-mail. No extra Microsoft permission is involved. Once a person is known, their 1:1 chat is opened once and remembered.

When the recipient has a linked Elowen account, TeamsMessagePerson first hands the text to that account's own durable channel session through the host relay control (relayToPerson in lib/proactive.mjs). The relayed turn denies the Teams send, message and file tools, the raw connector tool and every Microsoft 365 tool (MICROSOFT_TOOLS in lib/microsoftTools.mjs), and the original text is framed as untrusted JSON data. A direct send happens only when no relay reply came back, when the recipient is the sender's own account, or when the recipient is not mapped.

graphLookup is a second, optional layer (lib/graph.mjs), off by default. It exists only for people the bot has never met: it resolves an e-mail against the tenant directory and installs the Teams app for that user so a chat can be opened. It requires the application permissions User.ReadBasic.All and TeamsAppInstallation.ReadWriteSelfForUser.All with tenant admin consent, plus graphCatalogAppId for the install. What an admin must do, and what happens when they have not done it, is in operations.md.

Test tests/msteams.test.ts asserts that with the switch off nothing leaves the process on an unknown e-mail — the stubbed global fetch records zero calls.

File map

File What it owns
index.mjs Registration (register): the app-package and people API routes, the credential gate, the StateStore, the notification-destination provider, the adapter, the /hooks mount and the microsoft-requests service, the tools and the microsoftIdentity control.
elowen-plugin.json Manifest: description, provides, icons, showOutput, readOnlyTools, the whole configSchema.
icon.svg The plugin's icon in the Elowen UI.
i18n/cs.json, i18n/sk.json Czech/Slovak translations of the manifest description and every config label/hint/option. Enforced by scripts/check-languages.mjs.
lib/adapter.mjs Webhook dispatch, admission, brain turn, transport and per-turn targeted audiences; wires the protocol owners with explicit dependencies.
lib/transcript.mjs Opt-in rolling history, bounded Graph thread projection and recorded-history fallback.
lib/files.mjs Personal-chat file offers (consent cards), the shared 20 MiB cap, pending bytes, consent, expiration and shared-file delivery.
lib/proactive.mjs Person resolution, roster sweep, personal conversations, the relay to a linked recipient and notifications.
lib/asks.mjs Question owner checks, shared ordered answer projection and core-owned ask_resolved lifecycle; no local expiry clock.
lib/commands.mjs Slash-command classification, local and control pickers, shared display axes and live model/reasoning validation.
lib/accountLinking.mjs OAuth admission, activity validation, one Entra-before-Teams identity resolver and fresh durable-account delegated sessions.
lib/requests.mjs Shared PluginContext policy subscription, finite request scheduling, cancellation and awaited cleanup for Connector/Graph/Microsoft 365 callers.
lib/connector.mjs Bot Connector REST client: reply/send/update/delete/typing/members/createConversation/download, with policy-bounded 429-only retry, and the bounded binary reader.
lib/token.mjs Entra client-credentials tokens, one cached bearer per scope, refreshed ~60 s before expiry.
lib/auth.mjs Inbound JWT verification against Microsoft's JWKS, pinned to our appId and the connector issuer, with a serviceUrl cross-check.
lib/directory.mjs The people directory: remember/list/evict/resolve, persisted under one reserved StateStore key.
lib/graph.mjs The optional Microsoft Graph layer: e-mail → tenant user, app installation for that user, profile photos and channel-thread reads.
lib/tools.mjs The nine Teams* tools.
lib/messages.mjs The bot's own service texts in en/cs/sk.
lib/cards.mjs Adaptive Card builders: the ask card, the paged picker card, the settled one-liner.
lib/stream.mjs Teams binding for the shared live-message engine: transport closures, markdown style, image and shared-file strategy.
lib/format.mjs Teams sizing (CHUNK = 20000) and the runtime-footer markup.
lib/ids.mjs Identity helpers: matchesId, senderIds, senderIsAdmin, displayNameOf.
lib/appPackage.mjs The sideloadable Teams app package: hand-rolled stored ZIP, the colour and outline icons and the Teams manifest.
elowen-plugin-shared/stateStore Imported directly by the entry; owns atomic conversation-state persistence.
lib/config.mjs Config normalization and defaults for the adapter and tools.
lib/microsoftTools.mjs Registers the eight Microsoft* tools and dispatches each call to its product owner.
lib/m365/*.mjs One owner per Microsoft 365 product (directory, SharePoint, files, Outlook, tasks, OneNote, Excel, Teams), plus schema.mjs and shared.mjs.
lib/identityControl.mjs The microsoftIdentity control other plugins can use: identityFor, and driveGraphFor, which returns a client restricted to drive paths.
lib/delegatedGraph.mjs Delegated Graph client helpers, including bounded output and HTML-to-text for thread bodies.
prompt/10-surface.md Prompt fragment describing the Teams surface to the model.
web-src/ Browser sources for the MS Teams workspace (TeamsWorkspace.tsx, PolicyEditor.tsx, policyModel.ts, and the runtime entry).

The entry's browser-safe exports are peopleForUi and notificationDestinationsForUi. Helper modules are imported directly from their owners. tests/msteams.test.ts also exercises the plugin through the host loader.

Verify it works

In the repo, from the registry repository root:

npx vitest run tests/msteams.test.ts tests/msteamsConnector.test.ts tests/msteamsAccountLinking.test.ts tests/msteamsIdentityControl.test.ts --maxWorkers=1
node scripts/check-languages.mjs                     # i18n covers every manifest string
npm run test:e2e:msteams                             # builds, then a real daemon + a fake Bot Framework

The E2E scenario (tests/e2e/msteams/run.mjs) drives a signed activity into the real webhook, checks the async threaded reply and the in-place live-trace edits, and asserts that a garbage JWT bounces with 401 without ever reaching the brain.

On a running instance:

  1. The daemon log should carry msteams platform registered (webhook /hooks/msteams/messages + Teams and delegated Microsoft 365 tools) (from register in index.mjs) and then msteams webhook locally available (app <appId>); provider transport unknown (from connect in lib/adapter.mjs). This confirms local credential initialization, not provider health. msteams credential check failed: … rejects initialization and withdraws readiness; it does not claim the adapter is available.
  2. GET /plugins/msteams/app-package (admin) returns the ZIP. It returns 503 while the plugin is enabled but not configured — deliberately 503 rather than 404. Any non-empty sub-path under that mount is a 404.
  3. Send the bot a direct message from a mapped sender. You should see a typing indicator, then a reply threaded under your message.
  4. /help in that chat answers with the command list (TeamsCommands.handleCommand in lib/commands.mjs); /stats reports the live model and context for an operator.