fix(msteams): preserve proactive thread replies#78387
Conversation
|
Codex review: needs maintainer review before merge. Summary Reproducibility: yes. Current main source inspection shows proactive sends force Real behavior proof Next step before merge Security Review detailsBest possible solution: Merge one canonical fix for the MS Teams proactive threading path, then resolve #78298 and supersede #78299 if this branch is chosen. Do we have a high-confidence way to reproduce the issue? Yes. Current main source inspection shows proactive sends force Is this the best way to solve the issue? Yes. Reusing the existing Teams route policy and proactive What I checked:
Likely related people:
Remaining risk / open question:
Codex review notes: model gpt-5.5, reasoning high; reviewed against e437763246de. |
|
Just to add a real-world data point: this fix resolved a production case for us on Holding pattern until this lands in npm: Looking forward to the next release. Thanks @amknight for the fix and the test coverage in Filed a small docs follow-up at #78835 to document the |
…ontext preservation (#78835) The existing replyStyle section explains the Posts-vs-Threads tradeoff but doesn't document how the value is actually resolved at send-time, nor what happens to thread-root context across configurations. Operators who hit unexpected top-level posts (e.g., requireMention=false setups, long-running agents whose proactive sends fall outside the live Bot Framework turn) have no docs-side anchor for understanding which knob to flip. Add two subsections under Reply Style: 1. Resolution precedence — channel > team > global > implicit default, plus the requireMention-derived implicit default. 2. Thread context preservation — describes that replyStyle=thread re-attaches the original thread root on outbound (live and proactive paths after #78387), with the threadId/activityId fallback for legacy stored refs. Calls out the deliberate "no thread suffix" behavior for replyStyle=top-level. Pure documentation change, no behavior or surface impact.
Co-authored-by: Alex Knight <15041791+amknight@users.noreply.github.com>
Co-authored-by: Alex Knight <15041791+amknight@users.noreply.github.com>
…ontext preservation (openclaw#78835) The existing replyStyle section explains the Posts-vs-Threads tradeoff but doesn't document how the value is actually resolved at send-time, nor what happens to thread-root context across configurations. Operators who hit unexpected top-level posts (e.g., requireMention=false setups, long-running agents whose proactive sends fall outside the live Bot Framework turn) have no docs-side anchor for understanding which knob to flip. Add two subsections under Reply Style: 1. Resolution precedence — channel > team > global > implicit default, plus the requireMention-derived implicit default. 2. Thread context preservation — describes that replyStyle=thread re-attaches the original thread root on outbound (live and proactive paths after openclaw#78387), with the threadId/activityId fallback for legacy stored refs. Calls out the deliberate "no thread suffix" behavior for replyStyle=top-level. Pure documentation change, no behavior or surface impact.
Co-authored-by: Alex Knight <15041791+amknight@users.noreply.github.com>
…ontext preservation (openclaw#78835) The existing replyStyle section explains the Posts-vs-Threads tradeoff but doesn't document how the value is actually resolved at send-time, nor what happens to thread-root context across configurations. Operators who hit unexpected top-level posts (e.g., requireMention=false setups, long-running agents whose proactive sends fall outside the live Bot Framework turn) have no docs-side anchor for understanding which knob to flip. Add two subsections under Reply Style: 1. Resolution precedence — channel > team > global > implicit default, plus the requireMention-derived implicit default. 2. Thread context preservation — describes that replyStyle=thread re-attaches the original thread root on outbound (live and proactive paths after openclaw#78387), with the threadId/activityId fallback for legacy stored refs. Calls out the deliberate "no thread suffix" behavior for replyStyle=top-level. Pure documentation change, no behavior or surface impact.
Summary
replyStylefrom stored conversation context and the existing Teams channel/team/global policy instead of hardcodingtop-level.threadIdor legacyactivityIdbuild the;messageid=<threadRoot>conversation id.top-levelchannel overrides.Closes #78298.
Root Cause
The proactive text/media send path always passed
replyStyle: "top-level"intosendMSTeamsMessages(), so CLI/message-tool sends ignored stored Teams channel thread roots. Switching that path tothreadwas previously blocked becausesendMSTeamsMessages()threw when no live turn context was present.Manual CLI Repro Proof
I exercised the actual CLI command path with a temp OpenClaw config/state directory and a network shim that only replaced AAD token acquisition and the Bot Framework service URL. The command under test was:
openclaw message send --channel msteams --target conversation:19:abc@thread.tacv2 --message "CLI proactive reply" --jsonorigin/mainsent tohttps://service.example.com/v3/conversations/19:abc@thread.tacv2/activities; this branch sent tohttps://service.example.com/v3/conversations/19:abc@thread.tacv2;messageid=thread-root-msg-id/activities.cbx_2a5cfab75860(coral-crab): same CLI command passed before/after proof withHETZNER_CLI_SUMMARY beforeStatus=0 beforeId=19:abc@thread.tacv2 afterStatus=0 afterId=19:abc@thread.tacv2;messageid=thread-root-msg-id afterRef=93b8a7c9848e3eadaf2f3e03ddcc22f72e5446d2.No live Teams tenant send was run; no Teams/Microsoft/Azure credentials were present in env or
~/.profile.Additional Manual Adapter Proof
A throwaway harness also exercised the Bot Framework proactive adapter boundary directly with a stored channel reference containing
threadId: "thread-root-msg-id"and no live turn context.cbx_2c7ac22b6281(crimson-krill): before patch onorigin/mainfailed with{"ok":false,"name":"Error","message":"Missing context for replyStyle=thread"}.93b8a7c9848e3eadaf2f3e03ddcc22f72e5446d2succeeded with{"ok":true,"ids":["id:manual proactive reply"],"sent":["manual proactive reply"],"proactiveConversationId":"19:abc@thread.tacv2;messageid=thread-root-msg-id","proactiveActivityId":null}.Verification
cbx_2a5cfab75860: passed as described above.origin/mainfailed withMissing context for replyStyle=thread; after branch succeeded with threaded proactive conversation id.cbx_2c7ac22b6281: passed as described above.pnpm test extensions/msteams/src/send-context.test.ts extensions/msteams/src/send.test.ts extensions/msteams/src/messenger.test.tslocally: 3 files, 47 tests passed.pnpm exec oxfmt --check --threads=1 extensions/msteams/src/send-context.ts extensions/msteams/src/send.ts extensions/msteams/src/messenger.ts extensions/msteams/src/send-context.test.ts extensions/msteams/src/send.test.ts extensions/msteams/src/messenger.test.ts CHANGELOG.mdgit diff --checktbx_01kqy8brzxk1e58fhcdggmkdfs: 3 files, 47 tests passed; lease stopped.pnpm check:changedontbx_01kqy8k9cb8300643vamz6d3ss: passed; lease stopped.