Skip to content

fix(msteams): proactive messaging, EADDRINUSE fix, tool status, adaptive cards#23226

Closed
TarogStar wants to merge 9 commits intoopenclaw:mainfrom
TarogStar:feat/msteams-improvements
Closed

fix(msteams): proactive messaging, EADDRINUSE fix, tool status, adaptive cards#23226
TarogStar wants to merge 9 commits intoopenclaw:mainfrom
TarogStar:feat/msteams-improvements

Conversation

@TarogStar
Copy link
Copy Markdown

@TarogStar TarogStar commented Feb 22, 2026

Summary

  • Problem: MS Teams replies fail with "Cannot perform 'set' on a proxy that has been revoked" when the LLM takes >15s to respond. The Bot Framework webhook TurnContext is a proxy that gets revoked after the HTTP handler returns, but with slow local models the agent response arrives well after that. Additionally, the provider promise resolved immediately after server bind, causing the channel manager to interpret it as "provider exited" and trigger EADDRINUSE restart loops.
  • Why it matters: With local LLMs (15-45s response times), every reply delivery, typing indicator, and adaptive card send was failing after the first few seconds. The restart loop compounded the issue by trying to rebind the same port repeatedly.
  • What changed:
    • All reply delivery (thread + top-level), typing indicators, and adaptive card sends now use proactive messaging via adapter.continueConversation() instead of the short-lived webhook TurnContext
    • Provider promise stays pending until abort signal fires (BlueBubbles pattern), preventing EADDRINUSE restart loops
    • Added tool status messages ("Checking email...", "Searching the web..."), GET /health endpoint, and adaptive card action handling (Action.Execute + Action.Submit)
    • Conversation reference seeded on bot install for proactive messaging support before first user message
    • Extracted shared helpers, added size cap to global invoke queue, sanitized untrusted input in synthetic text
  • What did NOT change: No changes to the core OpenClaw engine, config schema, or other channel plugins. The MS Teams webhook contract, Bot Framework SDK usage, and message routing logic are unchanged.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

User-visible / Behavior Changes

  • Bot replies now reliably delivered regardless of LLM response time (no more proxy revocation failures)
  • Thread replies stay in the correct channel thread via replyToId on proactive messages
  • Tool status messages appear during tool execution ("Checking email...", "Searching the web...")
  • Typing indicator shows reliably via proactive messaging
  • Adaptive card button clicks (Action.Execute and Action.Submit) are handled and routed to the agent
  • GET /health endpoint available for monitoring
  • No config changes needed — all improvements are automatic

Security Impact (required)

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? No (same Bot Framework API, just via proactive messaging path)
  • Command/tool execution surface changed? No
  • Data access scope changed? No

Repro + Verification

Environment

  • OS: Linux (WSL2)
  • Runtime/container: Node v24.13.1
  • Model/provider: qwen3-8b via LMStudio (local, 15-45s response times)
  • Integration/channel: MS Teams (personal chat)
  • Relevant config: gateway.mode: "local", channels.msteams.blockStreamingCoalesce: { minChars: 300, maxChars: 800, idleMs: 1500 }

Steps

  1. Configure MS Teams channel with a slow local LLM (>15s response time)
  2. Send a message to the bot in Teams
  3. Observe typing indicator, tool status messages, and reply delivery

Expected

  • Typing dots appear immediately
  • Tool status messages appear during tool execution
  • Reply delivered successfully in the correct thread
  • No errors in logs

Actual (before fix)

  • Reply fails with "Cannot perform 'set' on a proxy that has been revoked"
  • Typing indicator fails silently after webhook context expires
  • Provider restarts in a loop with EADDRINUSE errors

Evidence

  • Failing test/log before + passing after
  • Trace/log snippets

Log output after fix (zero errors, clean dispatch):

[msteams] starting provider (port 3978)
[msteams] received message
[msteams] dispatching to agent
[msteams] dispatch complete

Before fix: logs showed 5+ EADDRINUSE restart attempts and proxy revocation errors on every reply.

All 165 msteams tests pass. pnpm build && pnpm check clean.

Human Verification (required)

  • Verified scenarios: Personal chat reply delivery, typing indicators, tool status messages, adaptive card handling, bot restart (no EADDRINUSE), cold start after bot install
  • Edge cases checked: Slow LLM responses (45s+), multiple sequential messages, gateway restart behavior, conversation reference persistence
  • What I did not verify: Group chat and channel thread behavior (only tested personal chat), other channel plugins

Compatibility / Migration

  • Backward compatible? Yes
  • Config/env changes? No
  • Migration needed? No

Failure Recovery (if this breaks)

  • How to disable/revert this change quickly: Revert the msteams extension commits
  • Files/config to restore: extensions/msteams/src/ directory
  • Known bad symptoms: If proactive messaging fails, replies would not be delivered at all (vs the old behavior where they sometimes worked if the LLM was fast enough). Monitor for "reply failed" log entries.

Risks and Mitigations

  • Risk: Proactive messaging requires a valid stored conversation reference; if the reference becomes stale, replies may fail
    • Mitigation: Conversation references are refreshed on every inbound message and seeded on bot install. The reference includes serviceUrl which Bot Framework keeps stable per tenant.
  • Risk: Global invoke queue (__openclaw_pending_card_invokes) could grow unbounded if invokes are never consumed
    • Mitigation: Added size cap of 50 entries with oldest-first eviction

Opus 4.6 assisted

Greptile Summary

Refactors MS Teams integration to use proactive messaging for all replies, typing indicators, and adaptive card sends, eliminating proxy revocation errors with slow LLMs (>15s responses). Also fixes EADDRINUSE restart loop by keeping provider promise pending until abort signal fires.

  • Switched from webhook TurnContext (revoked after HTTP request) to adapter.continueConversation() for all outbound operations
  • Provider now waits for abort signal before resolving, preventing channel manager from interpreting early resolve as "provider exited"
  • Added /health endpoint for monitoring (accessible before JWT auth)
  • Implemented tool status messages ("Checking email...", "Searching the web...") that appear during tool execution
  • Added handling for adaptive card actions (Action.Execute and Action.Submit) with global invoke queue
  • Conversation reference now seeded on bot install for proactive messaging before first user message
  • All changes are backward compatible with no config modifications required

Confidence Score: 4/5

  • Safe to merge with minor observations - well-architected solution with comprehensive tests
  • Strong implementation with extensive test coverage (392 new test lines), clear architectural benefits, and proper error handling. The proactive messaging pattern correctly addresses the proxy revocation issue. One minor observation about optional chaining usage that could be simplified for clarity.
  • No files require special attention - all changes follow established patterns

Last reviewed commit: d94f5a4

(3/5) Reply to the agent's comments like "Can you suggest a fix for this @greptileai?" or ask follow-up questions!

@openclaw-barnacle openclaw-barnacle bot added channel: msteams Channel integration: msteams size: XL labels Feb 22, 2026
Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

10 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

@TarogStar TarogStar force-pushed the feat/msteams-improvements branch from 820df6f to 443d26a Compare February 22, 2026 04:17
TarogStar and others added 9 commits February 22, 2026 11:48
…upport

- Show interim status messages during tool execution (e.g. "Checking email...")
  that auto-delete when the real reply starts streaming
- Add GET /health endpoint before JWT auth middleware for monitoring
- Handle adaptive card action invokes for Copilot Studio responses
- Extend MSTeamsTurnContext with optional updateActivity/deleteActivity

Co-Authored-By: Claude Opus 4.6 <[email protected]>
The provider promise resolved before expressApp.listen() finished
binding, causing the channel manager to interpret it as "provider
exited" and trigger auto-restart — which failed with EADDRINUSE.

Now awaits the listening callback before resolving, rejects on fatal
bind errors (EADDRINUSE/EACCES), logs error codes inline, and calls
closeAllConnections() on shutdown to release keep-alive sockets.

Also generalises adaptive card queue keys from copilot-studio-specific
names to plugin-agnostic ones so any plugin can use the mechanism.

Refs: openclaw#22169

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Tests for registerMSTeamsHandlers:
- Action.Execute invokes store data in global queue and send invoke response
- Synthetic text generated from verb/action with 'approved' fallback
- Activity properties restored after routing (even on handler error)
- Action.Submit detected via empty text + value object
- Normal messages with text bypass Action.Submit path
- Multiple invokes accumulate in queue in order

Tests for /health endpoint:
- Returns correct JSON shape (status, channel, port, startedAt)
- Accessible before JWT auth middleware (other routes blocked)

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Teams Bot Framework SDK has no native API for bots to react to
messages with emoji. Emulate it by sending the configured ack emoji
as a temporary status message that gets deleted when the reply
starts streaming or a tool status replaces it.

Uses resolveAckReaction() and shouldAckReaction() from the plugin SDK
for config-consistent behavior with Discord, Slack, and Telegram.
Respects messages.ackReaction (emoji), messages.ackReactionScope
(gating), and messages.statusReactions.enabled (master switch).

Refs: openclaw#11361

Co-Authored-By: Claude Opus 4.6 <[email protected]>
…law#22169)

The channel manager interprets a resolved promise as "provider exited"
and triggers auto-restart with exponential backoff. For webhook-based
providers like msteams, this causes an EADDRINUSE loop because the
previous server is still bound to the port.

Hold the promise pending until the abort signal fires, matching the
contract used by other webhook providers (e.g. BlueBubbles).

Co-Authored-By: Claude Opus 4.6 <[email protected]>
…cation

Bot Framework revokes the webhook TurnContext proxy after the HTTP
request handler returns. With slow local LLMs, the agent response
arrives well after that, causing "Cannot perform 'set' on a proxy
that has been revoked" errors that silently drop replies.

Switch thread replies and adaptive card delivery to use
adapter.continueConversation() which creates a fresh context
independent of the webhook lifecycle. This matches the pattern
already used for top-level replies.

For thread replies, set replyToId on outgoing activities to maintain
correct threading in channels.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Save the conversation reference in onMembersAdded so proactive
messaging (e.g. cron announcements) works even before the user
sends their first message. Previously the reference was only
stored on the first inbound message.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
The onReplyStart typing callback was still using the webhook
TurnContext, causing "proxy revoked" errors after the reply was
already delivered. Switch to adapter.continueConversation() to
match the reply and adaptive card delivery paths.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
- Remove dead `context` param from sendMSTeamsMessages and createMSTeamsReplyDispatcher
- Extract duplicated globalThis queue insertion into shared pushPendingCardInvoke helper
- Add size cap (50) to global invoke queue to prevent unbounded growth
- Use fixed synthetic text for adaptive card actions instead of interpolating untrusted input
- Handle missing abortSignal in monitor.ts to avoid forever-pending promise
- Fix test types for makeTurnContext activity properties

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@openclaw-barnacle
Copy link
Copy Markdown

This pull request has been automatically marked as stale due to inactivity.
Please add updates or it will be closed.

@openclaw-barnacle openclaw-barnacle bot added the stale Marked as stale due to inactivity label Feb 28, 2026
@openclaw-barnacle
Copy link
Copy Markdown

Closing due to inactivity.
If you believe this PR should be revived, post in #pr-thunderdome-dangerzone on Discord to talk to a maintainer.
That channel is the escape hatch for high-quality PRs that get auto-closed.

@openclaw-barnacle openclaw-barnacle bot closed this Mar 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: msteams Channel integration: msteams size: XL stale Marked as stale due to inactivity

Projects

None yet

1 participant