Skip to content

fix(msteams): surface quoted message body in Teams quote replies#101856

Merged
steipete merged 6 commits into
openclaw:mainfrom
velanir-ai-manager:fix/msteams-quote-reply-preview-and-fulltext
Jul 9, 2026
Merged

fix(msteams): surface quoted message body in Teams quote replies#101856
steipete merged 6 commits into
openclaw:mainfrom
velanir-ai-manager:fix/msteams-quote-reply-preview-and-fulltext

Conversation

@velanir-ai-manager

@velanir-ai-manager velanir-ai-manager commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

What Problem This Solves

When a user quote-replies to the bot's message in a Microsoft Teams 1:1 DM, the bot could not see the quoted message content — it only saw the user's new text.

Root cause: Teams delivers the quoted text of a quote-reply inside <p itemprop="preview">…</p> (a truncated snippet), but extractMSTeamsQuoteInfo only matched <p itemprop="copy">…</p>. The match failed, quoteInfo came back undefined, and nothing was surfaced to the agent via supplemental.quote.

Why This Change Was Made

  1. Parser (primary): extractMSTeamsQuoteInfo now matches <p itemprop="copy"> or <p itemprop="preview"> (prefers copy, falls back to preview) and captures the blockquote itemid as the quoted message id.
  2. Full text via Graph (enhancement): the preview snippet is Teams-truncated, so when the quoted message id is known and the conversation is a 1:1 DM, fetch the complete text via the app-only endpoint GET /chats/{chatId}/messages/{id} (Chat.Read.All) and use it as the quote body. Restricted to DMs so a fetched body cannot bypass the group supplemental-quote visibility allowlist. Any failure degrades to the truncated preview, so message handling never breaks.

User Impact

Teams users who quote-reply the bot in a 1:1 DM now get responses that account for the quoted message instead of ignoring it. Group and channel quote replies surface the previously-dropped preview snippet. No configuration changes; no impact on non-quote messages.

Evidence

Parser, against the exact Teams quote-reply payload (redacted):

BEFORE (copy-only regex): undefined -> quote dropped
AFTER  (fixed parser): { sender: "<display name>",
                         body: "San Francisco right now ... 54-64 °F (avg…)",
                         id:   "1783379480258" }

Live in a real Teams tenant (1:1 DM; redacted screenshots shared with reviewers, kept as external artifacts rather than committed): the bot now reads back the full quoted message instead of only the new text. Also deployed live on a production Teams gateway (@openclaw/msteams 2026.6.6): the gateway restarted clean ([msteams] starting provider) and a real quote-reply surfaces the quoted body to the agent.

Validation: msteams suites 85 passing (incl. a group-chat gate regression test); pnpm tsgo:extensions and pnpm tsgo:extensions:test clean.

🤖 Generated with Claude Code

@openclaw-barnacle openclaw-barnacle Bot added channel: msteams Channel integration: msteams size: S triage: needs-pr-context Candidate: external PR body lacks required problem context or evidence. labels Jul 7, 2026
@clawsweeper

clawsweeper Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed July 9, 2026, 7:19 AM ET / 11:19 UTC.

Summary
The branch updates the Microsoft Teams plugin to parse quote-reply preview snippets, capture quoted message ids, fetch full quoted chat text only in DMs, add Graph request timeouts, and cover the parser, Graph helper, and authz paths with tests.

PR surface: Source +71, Tests +216. Total +287 across 9 files.

Reproducibility: yes. at source level: current main only matches itemprop="copy", while the PR body and added tests show Teams quote replies can arrive with itemprop="preview", causing quote context to be dropped. I did not run a live Teams tenant repro in this read-only review.

Review metrics: none identified.

Merge readiness
Overall: 🐚 platinum hermit
Proof: 🐚 platinum hermit ✨ media proof bonus
Patch quality: 🐚 platinum hermit
Result: ready for maintainer review.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • Let the remaining exact-head checks finish and refresh the branch if GitHub keeps reporting behind or unknown mergeability.

Risk before merge

  • [P1] This overlaps open Teams normalization work in fix(msteams): normalize inbound mentions and forwards #102379, so whichever PR lands second will likely need a focused rebase and quote-context review.
  • [P1] Exact-head CI still had pending jobs and mergeability was UNKNOWN when checked, so required checks should finish before merge.

Maintainer options:

  1. Decide the mitigation before merge
    Land the Teams plugin fix after required exact-head checks, keeping full-text Graph quote recovery DM-only and leaving group/channel quote context on the visibility-safe preview path.
  2. Pause or close
    Do not merge this PR until maintainers decide whether the risk is worth taking.

Next step before merge

  • [P2] No repair lane is needed because this open PR already contains the implementation candidate; the remaining action is maintainer review plus merge-gate validation.

Security
Cleared: No concrete security or supply-chain concern found; the new full-text Graph read is restricted to 1:1 DMs and degrades to the preview on failure.

Review details

Best possible solution:

Land the Teams plugin fix after required exact-head checks, keeping full-text Graph quote recovery DM-only and leaving group/channel quote context on the visibility-safe preview path.

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

Yes, at source level: current main only matches itemprop="copy", while the PR body and added tests show Teams quote replies can arrive with itemprop="preview", causing quote context to be dropped. I did not run a live Teams tenant repro in this read-only review.

Is this the best way to solve the issue?

Yes, this is the best supported fix shape I found: parse Teams-owned quote HTML in the inbound adapter, recover full text only for 1:1 DMs, and keep group/channel behavior on the preview path behind existing supplemental-context filtering.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 7abf1c7f85a5.

Label changes

Label justifications:

  • P2: The PR fixes a bounded Microsoft Teams quote-reply behavior gap without evidence of a broad outage, data loss, or release-blocking regression.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🐚 platinum hermit and patch quality is 🐚 platinum hermit.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (screenshot): The PR body and follow-up discussion provide after-fix parser output against the Teams payload plus redacted live Teams/prod-gateway proof descriptions, and prior ClawSweeper proof labels mark it sufficient.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body and follow-up discussion provide after-fix parser output against the Teams payload plus redacted live Teams/prod-gateway proof descriptions, and prior ClawSweeper proof labels mark it sufficient.
  • proof: 📸 screenshot: Contributor real behavior proof includes screenshot evidence. The PR body and follow-up discussion provide after-fix parser output against the Teams payload plus redacted live Teams/prod-gateway proof descriptions, and prior ClawSweeper proof labels mark it sufficient.
Evidence reviewed

PR surface:

Source +71, Tests +216. Total +287 across 9 files.

View PR surface stats
Area Files Added Removed Net
Source 4 76 5 +71
Tests 5 221 5 +216
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 9 297 10 +287

What I checked:

Likely related people:

  • sudie-codes: Authored the merged Teams structured quote/reply context work and the Graph thread-history helper that this PR extends. (role: introduced behavior and adjacent feature owner; confidence: high; commits: 9f5d286caf83, 8c852d86f759; files: extensions/msteams/src/inbound.ts, extensions/msteams/src/graph-thread.ts, extensions/msteams/src/monitor-handler/message-handler.ts)
  • steipete: Carried the cross-channel context-visibility refactor, much of the Teams plugin boundary history, and authored the latest maintainer commits on this PR head. (role: recent adjacent owner and current assignee; confidence: high; commits: 694d12a90b4c, d9f9e93deeac, 0ea5402e66f1; files: extensions/msteams/src/monitor-handler/message-handler.ts, extensions/msteams/src/inbound.ts, extensions/msteams/src/graph.ts)
  • jacobtomlinson: Authored the Teams sender-allowlist filtering work for thread history, which is the closest prior security boundary to this quote-context change. (role: adjacent security/context contributor; confidence: medium; commits: 5cca38084074; files: extensions/msteams/src/monitor-handler/message-handler.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.
Review history (5 earlier review cycles)
  • reviewed 2026-07-07T18:55:35.599Z sha 9940bcd :: needs real behavior proof before merge. :: [P1] Gate group-chat quote expansion by sender visibility | [P2] Remove the unsupported $select from chatMessage fetches | [P2] Export the new helper from graph-thread mocks
  • reviewed 2026-07-07T22:07:51.151Z sha 160898a :: needs real behavior proof before merge. :: none
  • reviewed 2026-07-07T22:16:54.000Z sha 160898a :: needs real behavior proof before merge. :: none
  • reviewed 2026-07-08T00:12:30.431Z sha 8c0179c :: found issues before merge. :: [P2] Remove committed proof screenshots from the branch
  • reviewed 2026-07-08T00:31:35.501Z sha 035a84e :: needs maintainer review before merge. :: none

@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal backlog priority with limited blast radius. merge-risk: 🚨 security-boundary 🚨 May affect sandboxing, authorization, credentials, or sensitive data. labels Jul 7, 2026
@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. proof: 📸 screenshot Contributor real behavior proof includes screenshot evidence. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. merge-risk: 🚨 other 🚨 Merging this PR has meaningful risk outside the owned taxonomy. and removed merge-risk: 🚨 security-boundary 🚨 May affect sandboxing, authorization, credentials, or sensitive data. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Jul 7, 2026
@velanir-ai-manager

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review — updated the PR body with real-behavior proof: before/after parser output against the exact Teams payload, and two redacted 1:1 DM screenshots showing the bot reading back the full quoted message. Also addressed the review blockers in 160898a (Graph full-text fetch restricted to DMs, unsupported $select removed, graph-thread mock factories updated + a group-chat gate regression test).

@clawsweeper

clawsweeper Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

@openclaw-barnacle openclaw-barnacle Bot removed the triage: needs-pr-context Candidate: external PR body lacks required problem context or evidence. label Jul 8, 2026
@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. proof: 📸 screenshot Contributor real behavior proof includes screenshot evidence. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. proof: 📸 screenshot Contributor real behavior proof includes screenshot evidence. merge-risk: 🚨 other 🚨 Merging this PR has meaningful risk outside the owned taxonomy. labels Jul 8, 2026
@steipete steipete self-assigned this Jul 9, 2026
@steipete
steipete force-pushed the fix/msteams-quote-reply-preview-and-fulltext branch from 035a84e to fabfe62 Compare July 9, 2026 11:05
Yash Inani and others added 6 commits July 9, 2026 07:12
Teams sends the quoted text of a 1:1 DM quote-reply in <p itemprop="preview">
(a truncated snippet), not <p itemprop="copy"> which the parser matched. The
match failed, so quoteInfo was undefined and nothing was surfaced to the agent.

Fix 1 (primary): extractMSTeamsQuoteInfo now accepts copy OR preview (prefers
copy, falls back to preview) and captures the blockquote itemid as the quoted
message id.

Fix 2 (enhancement): when the quoted message id is known, fetch the full text
via the app-only Graph endpoint GET /chats/{chatId}/messages/{id} (permitted
with Chat.Read.All, unlike the delegated /me/chats listing) and use it as the
quote body. Restricted to chats (DM + group) whose Graph chat id is a 19: id;
any failure degrades to the truncated preview so message handling never breaks.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
…ted $select, fix mocks

Addresses ClawSweeper review on the quote-reply PR:

- Security (P1): restrict the app-only Graph full-text quote fetch to 1:1 DMs.
  Previously it ran for any non-channel chat, so in a group an allowlisted
  sender could quote a non-allowlisted member and the fetched full body would
  bypass the supplemental-quote visibility allowlist. Group/channel quotes keep
  the (now-surfaced) truncated preview from fix 1.
- Graph contract (P2): the get-chatMessage endpoint does not support OData
  query params; drop the ?$select=id,body that tenants enforcing the contract
  would reject (which would silently fall back to the preview).
- Tests (P1): add fetchChatMessageText to the two vi.mock(../graph-thread.js)
  factories prod now touches, and add a group-chat regression test proving the
  Graph full-text fetch does not fire for group quotes.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Real-behavior evidence for the quote-reply fix (personal names + avatars
redacted): a 1:1 DM where the bot now reads back the full quoted message.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Proof media should live as external PR artifacts, not in the product tree.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
@steipete
steipete force-pushed the fix/msteams-quote-reply-preview-and-fulltext branch from fabfe62 to 0ea5402 Compare July 9, 2026 11:12
@steipete

steipete commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Maintainer review/proof complete.

  • Best-fix verdict: yes. The inbound parser owns Teams' copy/preview shapes and quoted item id; the message handler owns the DM-only enrichment decision. Group/channel quote replies retain the filtered preview and never fetch a third participant's complete Graph body.
  • Added a shared 30-second deadline to both relative and absolute Teams Graph requests, so best-effort quote enrichment cannot stall delivery. Added handler-level proof for token scope, Graph chat/message ids, and complete-body substitution.
  • Microsoft documents GET /chats/{chat-id}/messages/{message-id}, application Chat.Read.All, and a chatMessage response: https://learn.microsoft.com/en-us/graph/api/chatmessage-get?view=graph-rest-1.0
  • Exact head 0ea5402e66f17527daa7db046e35be5a2f3baffa: sanitized AWS Crabbox lease cbx_c3ac841e4927, run run_def91265bd65, passed five Teams Graph/parser/handler suites (107 tests). Final autoreview clean; exact-head hosted CI green, including the Real behavior proof.

Known proof gap: I did not have a locally controlled Microsoft 365 tenant. The contributor's current-head Real behavior proof supplies native tenant/gateway evidence; the maintainer replay independently covers parsing, privacy gating, Graph routing, deadline, and fallback behavior.

@steipete
steipete merged commit e84a0dd into openclaw:main Jul 9, 2026
90 checks passed
@steipete

steipete commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Merged via squash.

github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jul 10, 2026
…nclaw#101856)

* fix(msteams): surface quoted message body in Teams quote replies

Teams sends the quoted text of a 1:1 DM quote-reply in <p itemprop="preview">
(a truncated snippet), not <p itemprop="copy"> which the parser matched. The
match failed, so quoteInfo was undefined and nothing was surfaced to the agent.

Fix 1 (primary): extractMSTeamsQuoteInfo now accepts copy OR preview (prefers
copy, falls back to preview) and captures the blockquote itemid as the quoted
message id.

Fix 2 (enhancement): when the quoted message id is known, fetch the full text
via the app-only Graph endpoint GET /chats/{chatId}/messages/{id} (permitted
with Chat.Read.All, unlike the delegated /me/chats listing) and use it as the
quote body. Restricted to chats (DM + group) whose Graph chat id is a 19: id;
any failure degrades to the truncated preview so message handling never breaks.

Co-Authored-By: Claude Opus 4.8 <[email protected]>

* fix(msteams): address review — DM-only full-text fetch, drop unsupported $select, fix mocks

Addresses ClawSweeper review on the quote-reply PR:

- Security (P1): restrict the app-only Graph full-text quote fetch to 1:1 DMs.
  Previously it ran for any non-channel chat, so in a group an allowlisted
  sender could quote a non-allowlisted member and the fetched full body would
  bypass the supplemental-quote visibility allowlist. Group/channel quotes keep
  the (now-surfaced) truncated preview from fix 1.
- Graph contract (P2): the get-chatMessage endpoint does not support OData
  query params; drop the ?$select=id,body that tenants enforcing the contract
  would reject (which would silently fall back to the preview).
- Tests (P1): add fetchChatMessageText to the two vi.mock(../graph-thread.js)
  factories prod now touches, and add a group-chat regression test proving the
  Graph full-text fetch does not fire for group quotes.

Co-Authored-By: Claude Opus 4.8 <[email protected]>

* docs(msteams): add redacted real Teams quote-reply proof screenshots

Real-behavior evidence for the quote-reply fix (personal names + avatars
redacted): a 1:1 DM where the bot now reads back the full quoted message.

Co-Authored-By: Claude Opus 4.8 <[email protected]>

* docs(msteams): remove committed proof screenshots from branch

Proof media should live as external PR artifacts, not in the product tree.

Co-Authored-By: Claude Opus 4.8 <[email protected]>

* test(msteams): bound and prove quote enrichment

* test(msteams): use valid open DM fixture

---------

Co-authored-by: Yash Inani <[email protected]>
Co-authored-by: Claude Opus 4.8 <[email protected]>
Co-authored-by: Peter Steinberger <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: msteams Channel integration: msteams P2 Normal backlog priority with limited blast radius. proof: 📸 screenshot Contributor real behavior proof includes screenshot evidence. proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. size: M status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants