Skip to content

fix(msteams): respect thread context in proactive send path#78299

Closed
feiskyer wants to merge 1 commit intoopenclaw:mainfrom
feiskyer:fix/msteams-thread-reply-style
Closed

fix(msteams): respect thread context in proactive send path#78299
feiskyer wants to merge 1 commit intoopenclaw:mainfrom
feiskyer:fix/msteams-thread-reply-style

Conversation

@feiskyer
Copy link
Copy Markdown
Contributor

@feiskyer feiskyer commented May 6, 2026

Summary

  • Problem: The proactive send path in send.ts hardcodes replyStyle: "top-level", causing all proactive/CLI messages to land as new top-level channel posts regardless of thread context.
  • Why it matters: Channel thread replies never thread correctly when sent via the message tool or CLI, breaking conversational continuity for users.
  • What changed: send.ts resolves replyStyle dynamically from conversation context; messenger.ts falls back to proactive send with thread targeting instead of throwing.
  • What did NOT change (scope boundary): DM and group chat behavior unchanged. The inbound reply-dispatcher path (which already threads correctly) is untouched.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • 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

Real behavior proof (required for external PRs)

  • Behavior or issue addressed: Proactive sends to channel conversations always create top-level posts instead of threading into the existing conversation thread.
  • Real environment tested: Local test suite exercising the real sendTextWithMediasendMSTeamsMessagessendProactively chain with mocked Bot Framework adapter.
  • Exact steps or command run after this patch: pnpm test extensions/msteams/src/send.test.ts and pnpm test extensions/msteams/src/messenger.test.ts
  • Evidence after fix (screenshot, recording, terminal capture, console output, redacted runtime log, linked artifact, or copied live output): 13/13 send tests pass (4 new), 31/31 messenger tests pass (2 new). New tests assert replyStyle: "thread" is passed for channel+threadId conversations and that the proactive fallback constructs ;messageid=<threadRootId> conversation IDs.
  • Observed result after fix: Channel conversations with thread context route to sendProactively(messages, 0, resolvedThreadId) which builds 19:abc@thread.tacv2;messageid=thread-root-msg — proven by captured adapter reference in test.
  • What was not tested: Live Teams environment (requires bot registration + tenant). The adapter mock faithfully replicates the continueConversation contract.
  • Before evidence (optional but encouraged): Before this fix, sendMSTeamsMessages was always called with replyStyle: "top-level" (hardcoded at old line 399 of send.ts).

Root Cause (if applicable)

  • Root cause: sendTextWithMedia() hardcoded replyStyle: "top-level" instead of resolving from conversation context. Additionally, sendMSTeamsMessages() threw when replyStyle === "thread" with no turn context, blocking the proactive path from threading.
  • Missing detection / guardrail: No test asserted the replyStyle value passed to sendMSTeamsMessages from the proactive send path.
  • Contributing context (if known): The proactive send path was written before channel thread support was added; the hardcode was never updated.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file: extensions/msteams/src/send.test.ts, extensions/msteams/src/messenger.test.ts
  • Scenario the test should lock in: Channel conversations with thread root resolve to replyStyle: "thread"; no-context thread sends fall back to proactive with thread targeting.
  • Why this is the smallest reliable guardrail: Tests exercise the routing decision and the fallback path at the seam boundary (mocked adapter), covering all 4 conversation scenarios.
  • Existing test that already covers this (if any): None existed before this PR.

User-visible / Behavior Changes

  • Proactive messages (message tool, CLI sends) to Teams channels now thread into the correct conversation thread instead of creating new top-level posts.

Diagram (if applicable)

Before:
[proactive send to channel] -> replyStyle: "top-level" (hardcoded) -> new top-level post

After:
[proactive send to channel] -> isChannelThread? -> replyStyle: "thread" -> sendProactively(msgs, 0, resolvedThreadId) -> ;messageid=<threadRoot> -> threaded reply
[proactive send to DM]      -> !isChannelThread -> replyStyle: "top-level" -> top-level (unchanged)

Security Impact (required)

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? No
  • Command/tool execution surface changed? No
  • Data access scope changed? No

Repro + Verification

Environment

  • OS: Linux
  • Runtime/container: Node 22
  • Integration/channel (if any): MS Teams

Steps

  1. Configure msteams plugin with a channel conversation that has a stored threadId
  2. Send a proactive message via the message tool to that conversation
  3. Observe the message lands in the thread (after fix) vs. top-level post (before fix)

Expected

  • Message threads into the existing channel thread

Actual

  • Before: Message creates a new top-level post
  • After: Message threads correctly

Evidence

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Human Verification (required)

  • Verified scenarios: Channel+threadId → thread, channel+activityId → thread, personal → top-level, channel without thread root → top-level, no-context fallback → proactive with thread suffix
  • Edge cases checked: activityId fallback when threadId absent; channel with neither threadId nor activityId stays top-level
  • What you did not verify: Live Teams tenant delivery (requires bot registration)

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

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

Risks and Mitigations

  • Risk: Stored conversation references without threadId or activityId could miss the thread routing.
    • Mitigation: Falls back to "top-level" when neither field is present — same behavior as before this fix.

@openclaw-barnacle openclaw-barnacle Bot added channel: msteams Channel integration: msteams size: XS triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. labels May 6, 2026
@clawsweeper
Copy link
Copy Markdown
Contributor

clawsweeper Bot commented May 6, 2026

Codex review: needs real behavior proof before merge.

Summary
Review failed before ClawSweeper could summarize the requested change.

Reproducibility: unclear. The review failed before ClawSweeper could establish a reproduction path.

Real behavior proof
Not applicable: Real behavior proof was not assessed because the Codex review failed.

Next step before merge
Review did not complete, so no work-lane recommendation was made.

Review details

Best possible solution:

Retry the Codex review after fixing the execution failure.

Do we have a high-confidence way to reproduce the issue?

Unclear. The review failed before ClawSweeper could establish a reproduction path.

Is this the best way to solve the issue?

Unclear. Retry the review first so ClawSweeper can evaluate the actual issue and fix direction.

What I checked:

  • failure reason: timeout.
  • codex failure detail: Codex review failed for this PR: spawnSync codex ETIMEDOUT.
  • codex stdout: Per-item Codex failure; continuing with the rest of the shard.

Likely related people:

  • unknown: Codex failed before it could trace repository history. (role: review did not complete; confidence: low)

Remaining risk / open question:

  • No close action taken because the review did not complete.

Codex review notes: model gpt-5.5, reasoning high; reviewed against 98cbf7f11c35.

@feiskyer
Copy link
Copy Markdown
Contributor Author

feiskyer commented May 6, 2026

Added behavioral proof in 59ea6bc:

send.test.ts (+4 tests):

  • passes replyStyle thread for channel conversations with threadId — verifies send.ts resolves to "thread" when conversation is a channel with threadId
  • passes replyStyle thread for channel conversations with activityId fallback — same when only activityId is present
  • passes replyStyle top-level for personal conversations — DMs stay top-level
  • passes replyStyle top-level for channel conversations without thread root — no threadId + no activityId = top-level (no regression for new channel posts)

messenger.test.ts (+2 tests):

  • falls back to proactive send with thread targeting when no context is provided — proves the no-throw fallback: sends to 19:abc@thread.tacv2;messageid=thread-root-msg
  • falls back to proactive send using activityId when threadId is absent and no context — activityId fallback constructs correct ;messageid= suffix

All 44 tests pass (13 send + 31 messenger).

The proactive send path in send.ts hardcodes replyStyle: "top-level",
causing all proactive/CLI messages to land as new top-level channel
posts regardless of thread context. Additionally, messenger.ts throws
when replyStyle is "thread" but no turn context is available.

Two changes:
1. send.ts: resolve replyStyle from conversation context (channel +
   thread root → "thread", otherwise → "top-level")
2. messenger.ts: fall back to sendProactively with thread targeting
   instead of throwing when no turn context is available

Fixes openclaw#78298
@feiskyer feiskyer force-pushed the fix/msteams-thread-reply-style branch from 59ea6bc to 4669f05 Compare May 6, 2026 05:51
@openclaw-barnacle openclaw-barnacle Bot added triage: mock-only-proof Candidate: PR proof only shows tests, mocks, snapshots, lint, typecheck, or CI. and removed triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. labels May 6, 2026
@feiskyer feiskyer closed this May 7, 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: M triage: mock-only-proof Candidate: PR proof only shows tests, mocks, snapshots, lint, typecheck, or CI.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: msteams proactive send path hardcodes replyStyle top-level, ignoring thread config

1 participant