fix(msteams): preserve channel reply threading in proactive fallback#55198
fix(msteams): preserve channel reply threading in proactive fallback#55198BradGroux merged 3 commits intoopenclaw:mainfrom
Conversation
When the live turn context is revoked (e.g. due to inbound message
debouncing), the proactive messaging fallback posted replies as new
top-level messages in Teams channels.
The root cause is that normalizeConversationId strips the
";messageid=..." thread suffix from the conversation ID, and the
proactive reference uses this normalized ID — so the Bot Framework
sends the message to the channel root instead of the original thread.
Fix by reconstructing the threaded conversation ID
("{channelId};messageid={activityId}") when the reply style is "thread"
and the conversation type is "channel", ensuring proactive fallback
replies land in the correct thread.
Add tests for channel thread reconstruction and group chat (no suffix).
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Greptile SummaryThis PR fixes a threading regression in the MS Teams proactive messaging fallback: when a Bot Framework turn context is revoked (e.g. due to inbound message debouncing), replies were delivered to the channel root as new top-level posts instead of as threaded replies. The fix reconstructs the Confidence Score: 5/5Safe to merge — minimal, well-scoped change with two targeted regression tests and no impact on non-channel or non-thread paths. The fix is a small, isolated addition that only activates for the specific edge case ( No files require special attention.
|
| Filename | Overview |
|---|---|
| extensions/msteams/src/messenger.ts | Adds optional threadActivityId parameter to sendProactively and reconstructs the ;messageid= thread suffix for channel messages when the proactive fallback fires. Logic is correct: normalizeConversationId already strips any existing suffix before the new one is appended, so double-suffixing cannot occur. Minor: ?? undefined on the activityId extraction is redundant. |
| extensions/msteams/src/messenger.test.ts | Adds two targeted regression tests: one asserts the ;messageid= suffix is appended for conversationType: "channel", the other asserts it is NOT appended for conversationType: "groupChat". Tests capture the continueConversation reference directly, which is the exact bug surface. Good coverage for the fix. |
Prompt To Fix All With AI
This is a comment left during a code review.
Path: extensions/msteams/src/messenger.ts
Line: 526
Comment:
**Redundant `?? undefined` nullish coalescing**
`activityId` is typed as `string | undefined` (an optional property on `StoredConversationReference`), so coalescing to `undefined` is a no-op — it can never be `null`. The expression can be simplified:
```suggestion
const threadActivityId = params.conversationRef.activityId;
```
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "fix(msteams): preserve channel reply thr..." | Re-trigger Greptile
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
…55198) When a thread reply's turn context is revoked and falls back to proactive messaging, the normalized conversation ID lost the thread suffix, causing replies to land in the channel root instead of the original thread. Reconstructs the threaded conversation ID (`;messageid=<activityId>`) for channel conversations in the proactive fallback path, while correctly leaving group chat conversations flat. Fixes #27189 Thanks @hyojin
…penclaw#55198) When a thread reply's turn context is revoked and falls back to proactive messaging, the normalized conversation ID lost the thread suffix, causing replies to land in the channel root instead of the original thread. Reconstructs the threaded conversation ID (`;messageid=<activityId>`) for channel conversations in the proactive fallback path, while correctly leaving group chat conversations flat. Fixes openclaw#27189 Thanks @hyojin
Summary
Describe the problem and fix in 2–5 bullets:
them under the original message.
sendProactivelyinmessenger.tsnow reconstructs the threaded conversation ID ({channelId};messageid={activityId}) for channel messages when the reply style is"thread", so the proactivefallback delivers replies into the correct thread. Two new unit tests added.
buildActivity,sendMessageInContext, andsendMessageBatchInContextfunctions are unchanged. Noconfig or schema changes.
Change Type (select all)
Scope (select all touched areas)
Linked Issue/PR
Root Cause / Regression History (if applicable)
normalizeConversationIdstrips the;messageid=...thread suffix from the conversation ID when storing the conversation reference. WhensendProactivelybuilds the proactive reference, it usesthis normalized ID, so
continueConversationsends the message to the channel root instead of the original thread.conversationType: "channel".withRevokedProxyFallbackto fix the revoked proxy error ([Bug]: MS Teams replies fail with 'Cannot perform set on a proxy that has been revoked #27189), which solved the delivery failure but introduced this threading regression — replies were delivered but as newtop-level messages.
Regression Test Plan (if applicable)
extensions/msteams/src/messenger.test.tsconversationType: "channel"), the proactive reference's conversation ID must include;messageid={activityId}. For group chat(
conversationType: "groupChat"), it must NOT add the suffix.sendMSTeamsMessagesdirectly verifies the conversation reference passed tocontinueConversation, covering the exact bug surface.User-visible / Behavior Changes
When the bot's turn context is revoked and the proactive fallback is used in a Teams channel, replies now appear as threaded replies under the original message instead of new top-level posts.
Security Impact (required)
Repro + Verification
Environment
channels.msteams.enabled: true,messages.inbound.debounceMs: 100(for testing)Steps
runcallback)Expected
Actual (before fix)
Evidence
Before fix (revoke, no fix):
sendProactively: baseRef.activityId=1774527433372 proactiveRef.activityId=undefined conversationId=19:[email protected]
sendMessageInContext: activity.replyToId=undefined activity.type=message
→ New top-level message posted
After fix (revoke, with fix):
sendProactively: baseRef.activityId=1774527601946 replyToId=1774527601946 isChannel=true conversationId=19:[email protected];messageid=1774527601946
sendMessageInContext: activity.replyToId=1774527601946 activity.type=message
→ Reply correctly threaded under original message
Human Verification (required)
Review Conversations
Compatibility / Migration
Failure Recovery (if this breaks)
messenger.tschangedextensions/msteams/src/messenger.tsRisks and Mitigations
{channelId};messageid={activityId}) depends on the Teams Bot Framework convention for thread addressing.conversationType: "channel"with a validactivityId,falling back to the original behavior otherwise.
🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 4.6 (1M context) [email protected]