Skip to content

feat: add Microsoft Teams gateway adapter with OpenClaw parity - #10037

Open
shuwenjunn wants to merge 2 commits into
NousResearch:mainfrom
shuwenjunn:feat/msteams-openclaw-parity
Open

feat: add Microsoft Teams gateway adapter with OpenClaw parity#10037
shuwenjunn wants to merge 2 commits into
NousResearch:mainfrom
shuwenjunn:feat/msteams-openclaw-parity

Conversation

@shuwenjunn

Copy link
Copy Markdown

Summary

  • add a native Microsoft Teams gateway adapter with Bot Framework ingress and Microsoft Graph helpers
  • implement OpenClaw-parity Teams messaging flows: mentions, cards, polls, direct sends, FileConsentCard DM uploads, SharePoint group/channel uploads, Graph fallback media recovery, and core message actions
  • wire Teams through Hermes gateway config, CLI surfaces, toolsets, send_message delivery, tests, and user docs

What’s included

  • new Teams platform modules:
    • gateway/platforms/msteams.py
    • gateway/platforms/msteams_graph.py
    • gateway/platforms/msteams_mentions.py
    • gateway/platforms/msteams_state.py
  • gateway/config/runtime integration for msteams
  • send_message and cron delivery support for Teams proactive sends
  • Graph-backed actions and enrichments:
    • read / edit / delete
    • pin / unpin / list-pins
    • react / unreact / reactions
    • search
    • member-info
    • channel-list / channel-info
  • docs:
    • Teams messaging guide
    • environment variable reference
    • messaging index updates
  • comprehensive Teams regression tests

Validation

python -m pytest tests/gateway/test_msteams.py tests/gateway/test_send_image_file.py tests/gateway/test_webhook_adapter.py tests/cli/test_cli_init.py -q -o addopts=

Result:

  • 168 passed

Notes

  • proactive/direct Teams sends require a persisted conversation reference for the target conversation
  • reaction operations follow the current OpenClaw/Graph beta behavior

@wupatrick2025-code

Copy link
Copy Markdown

tested this branch in my environment and it works for text chat, but not support attachment like picture or file.

@alt-glitch alt-glitch added type/feature New feature or request P3 Low — cosmetic, nice to have comp/gateway Gateway runner, session dispatch, delivery labels Apr 26, 2026
SadiqJaf added a commit to Satscryption/Hermes-A365 that referenced this pull request May 5, 2026
Bot Framework / A365 channel infra retries deliveries on 5xx and slow
ACK; without a dedupe the operator webhook is hit multiple times for
the same activity. Add a TTL-keyed in-memory cache on
`(conversationId, activityId)` so duplicate POSTs short-circuit
before touching the webhook.

`_IdempotencyCache` is a small TTL-pruned dict (matching the
`_JwksCache` pattern), driven by `BridgeConfig.idempotency_ttl_seconds`
(default 1h). Activities without either id (channel-control flows
sometimes lack `id`) bypass dedupe — better to over-deliver than to
risk dropping legitimate traffic on missing-id alone.

Pattern adapted from NousResearch/hermes-agent#10037
(`gateway/platforms/msteams.py::_is_duplicate_delivery`). Their
adapter is classic-BF shaped (issue #7) so we re-implement in our
idiom rather than vendor.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
SadiqJaf added a commit to Satscryption/Hermes-A365 that referenced this pull request May 5, 2026
The bridge's outbound reply path POSTs the inbound activity's
`serviceUrl` plus a freshly minted user-FIC bearer for the Messaging
Bot API SP. JWT validation (slice 19f) ties that bearer to our
tenant + audience, but once we POST to the URL the destination has
full control over the bearer's traffic. A forged or malformed
activity could redirect us anywhere.

Gate the messages handler on `_is_trusted_service_url(activity.serviceUrl,
cfg.trusted_service_url_suffixes)` — must be `https`, must have a
hostname, and the hostname must end with one of the configured DNS
suffixes. Untrusted: 403 with `untrusted serviceUrl: <repr>`.

Default suffixes adapted from NousResearch/hermes-agent#10037's
`TRUSTED_SERVICE_URL_HOST_SUFFIXES`. The round-3 walkthrough pinned
real Teams traffic at `*.trafficmanager.net` so that's the
load-bearing entry; `.botframework.com`, `.botframework.us`,
`.cloud.microsoft`, `.azure.com` cover cross-channel and Bot
Service-hosted endpoints.

The DNS-boundary detail matters: each suffix carries a leading `.`
so `evil-trafficmanager.net` doesn't slip through a naive endswith.
Tested.

Empty `cfg.trusted_service_url_suffixes` is treated as a config bug
and 403s every request, matching the empty-allowlist behaviour from
slice 19f.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
SadiqJaf pushed a commit to Satscryption/Hermes-A365 that referenced this pull request May 12, 2026
After the 2026-05-12 walkthrough surfaced Azure Bot Service as a
hard prerequisite for Custom Engine Agent / Copilot Chat
surfacing, the project's value props sharpen materially:

- Path A (AI Teammate): agent-as-M365-tenant-directory-identity.
  Teams 1:1 chat with M365-native identity. No Azure subscription.
- Path B (Custom Engine Agent + Azure Bot Service): Copilot Chat
  agents picker + side-panels in Word/Excel/PowerPoint/Outlook.
  Requires Azure subscription.

Both are structurally distinct from Hermes' sibling Teams adapter
at plugins/platforms/teams/adapter.py (classic Bot Framework,
shipped v2026.4.30; in-flight in NousResearch/hermes-agent#10037
and #13767). The sibling has none of:
- M365 tenant directory identity (Path A's value prop)
- Copilot Chat fabric surfacing (Path B's value prop)

And Hermes-A365 has none of the sibling's:
- Generic Teams chat reach (group, channel, threading,
  attachments without M365 directory presence)

So no overlap. The reframe makes that explicit.

Changes:
- references/m365-surface-coverage.md -- new "Positioning"
  section with two-path table, sibling-plugin signposts, when-
  to-pick-what guidance. Coverage matrix rewritten to attribute
  each surface to its right home (Hermes-A365 Path A / Path B /
  sibling / separate skill). Validation status updated for
  round-8 + slice 19u-a. Highest-value walkthroughs ranked
  against unique Hermes-A365 lane.
- README.md -- new "Hermes-A365 vs sibling Teams plugins"
  section near the top. Surface table reworked for path
  attribution. Known limitations refreshed (streaming shipped,
  wizard shipped, Path B Azure prerequisite documented, #25 +
  #26 follow-ups listed).
- SKILL.md -- frontmatter description and Overview rewritten
  with two-path framing + sibling-plugin distinction. When/
  Don't-Use sections clarified. Surfaces-that-work-today
  refreshed for round-8 + slice 19u-a + Path B Azure
  prerequisite.

Issue cleanup:
- #17 (Teams group + channel walkthrough) closed -- explicitly
  sibling-plugin lane under reframe.
- #18 (invoke activities umbrella) scope-narrowed via comment
  -- composeExtension invokes move to sibling lane; Path
  B-relevant invokes (Outlook task/* via Copilot, search,
  signin/verifyState) stay.
- #16 (Copilot Chat walkthrough) classified as Path B's
  primary validation in comment.
- Upstream comment posted on NousResearch/hermes-agent#20133
  to clarify the non-overlap to maintainers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the substantial Teams work. Current main already ships Microsoft Teams as the bundled teams platform plugin (commit b3137d758), including native media sends (5f55f0ff8) and inbound document/media caching (0fd34e8c5). The remaining Graph-action work may still be useful, but this implementation needs substantial re-scoping.

Problems

  • gateway/platforms/msteams.py:329 defines connect() without is_reconnect; current gateway/run.py:3445-3462 always passes that keyword, so this adapter raises TypeError on current main.
  • gateway/config.py adds a separate core msteams platform, while current main’s Teams channel is the lazily registered teams plugin (plugins/platforms/teams/adapter.py:1412-1451). Keeping both would create duplicate Teams configuration and runtime paths.
  • gateway/platforms/msteams.py:350 creates web.Application() without client_max_size, despite exposing _max_body_bytes; current Teams code applies this limit at application construction (plugins/platforms/teams/adapter.py:744-750).

Suggested changes

  • Port only unimplemented Graph actions into plugins/platforms/teams/adapter.py and its tests; drop the parallel core adapter/config/toolset wiring.
  • Match the base adapter connect(*, is_reconnect=False) contract and enforce the aiohttp body limit at construction.

Automated hermes-sweeper review.

self._conversations = ConversationRegistry.load_from_path(self._state_path)

async def connect(self) -> bool:
if not check_msteams_requirements():

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Current GatewayRunner._connect_adapter_with_timeout() always invokes adapter.connect(is_reconnect=...) (gateway/run.py:3445-3462). This signature will raise TypeError on startup and reconnect; accept *, is_reconnect: bool = False even if this adapter does not use the flag.

self._graph = MSTeamsGraphClient(self._app_id, self._app_password, self._tenant_id, self._http)
self._auth_validator = BotFrameworkJWTValidator(self._app_id, self._http, self._auth_cache_ttl_seconds)
self._app = web.Application()
self._app.router.add_get("/health", self._handle_health)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_max_body_bytes is checked only after aiohttp has parsed/read the request. Pass it as web.Application(client_max_size=self._max_body_bytes) so the configured cap is enforced by the server parser as well.

@teknium1 teknium1 added sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 12, 2026

@GottZ GottZ left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was generated by AI during triage.

Summary

Four PRs address Microsoft Teams integration: #10037, #13753, and #13767 add parallel first-class msteams adapters with Bot Framework/Graph support, while #17683 implements the SDK-based teams platform that aligns with the plugin architecture now shipped on main. The attachment gap motivating the later variants is also covered on main by native outbound media and inbound document/media caching.

Related pull requests

  • #10037 related — (+6496/-54) — close as superseded, salvage Graph actions only: the diff adds a separate core msteams stack, but current main already provides the bundled teams plugin, and this adapter is incompatible with current connect(is_reconnect=...) calls and omits client_max_size. Despite the keep_open review on #10037, the diff shows that retaining the PR would duplicate configuration/runtime paths; any still-useful Graph actions should be re-scoped onto the existing plugin.
  • #13753 [closed] related — (+4939/-7) — superseded by #13767: this closed attempt adds a broad standalone msteams adapter with Bot Framework ingress, Graph, multiple auth flows, files, setup, and docs, but #13767 is its expanded successor and the same base capability is now implemented on main.
  • #13767 [closed] related — (+6542/-30) — close as implemented on main: this expands the standalone msteams approach with inbound image/document caching, FileConsent and SharePoint uploads, edits, and standalone delivery, but current main's dynamically registered teams plugin already supplies gateway integration plus the attachment paths that motivated the PR.
  • #17683 [closed] related — (+1524/-11) — superseded by #17764 and relevant as the architectural precursor: unlike the msteams variants, it uses the Microsoft Teams SDK and the teams platform path, adding webhook ingress, conversation references, proactive sends, and interactive approval cards. It was closed in favor of #17764, and current main now ships Teams through this plugin-oriented architecture.

Duplicates

#10037, #13753, and #13767 are substantially overlapping standalone msteams implementations, with #13753 superseding #10037 and #13767 superseding both; #17683 covers the same platform capability through the distinct SDK/plugin-oriented teams architecture and was superseded by #17764.

Suggested consolidation

Merge none — retain the Microsoft Teams implementation already shipped on main through the bundled teams plugin. Close #10037 as superseded despite its keep_open review because its diff duplicates the active plugin path and is incompatible with the current adapter lifecycle; #13753 and #13767 are duplicate standalone implementations already superseded by main, and #17683 remains closed as superseded by #17764. If any Graph-only actions from #10037 are still missing, extract them into a narrowly scoped follow-up against the existing teams plugin rather than reopening or merging these PRs.

Cross-PR triage: Reviewed 4 pull requests and 0 issues in this complex. Each diff was read against this issue; Assessment working set: 932 kB of PR diffs, 21 kB of issue/PR text, 5 kB of discussion (12 comments), 0 verify verdicts. verdicts reflect diff content, not PR titles. Part of an automated triage batch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/gateway Gateway runner, session dispatch, delivery P3 Low — cosmetic, nice to have sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants